/* =========================================================================
   BAZA ARKUSZY FRONTU — szablony, które renderują się WIDOKAMI KLASYKA
   (wydzielona 11.09.2026 z `front.css`, kolejność reguł Klasyka 1:1).

   Layout ładuje najpierw TEN plik, potem SKÓRĘ szablonu:
     Klasyk = front-base.css + front.css
     Łukasz = front-base.css + front-lukasz.css
   Mapa jest jedna: `App\Support\FrontTemplates::ARKUSZE` — z niej czytają
   layouty i strażniki (testy czytają POŁĄCZENIE bazy i skóry, tak jak
   przeglądarka).

   ⚠️ CO JEST TUTAJ, A CO W SKÓRZE. Tu stoi wszystko, czego potrzebuje markup
   partiali Klasyka: siatka treści, zakres sekcji, fokus, ramy pasów, kafle,
   moduły. W skórze stoją WARTOŚCI TOŻSAMOŚCI (tokeny: promienie, tło stopki,
   szerokość treści, kroje — patrz `:root` niżej, gdzie zostawiono po nich
   znacznik) i reguły, którymi szablon się od Klasyka RÓŻNI. Poprawka
   w bazie dociera do wszystkich szablonów naraz — po to ta baza jest.

   ⚠️ NOWOCZESNY NA NIEJ NIE STOI — i to jest decyzja (wariant A, 11.09.2026),
   nie zaległość. Ma własne widoki z makiety i własny arkusz; z Klasykiem
   dzieli 86 identycznych reguł na ~1100, a 352 selektory ma te same z INNYMI
   deklaracjami. Wciągnięcie go tutaj byłoby przepisaniem Nowoczesnego.

   ⚠️ NICZEGO TU NIE PRZESTAWIAJ „dla porządku". Kolejność jest wynikiem
   kaskady — przy równej specyficzności wygrywa reguła późniejsza. Dowodem
   przy zmianie tego pliku jest porównanie stylów wyliczonych
   (`tools/audyt-frontu/style.mjs`), nie zrzut ekranu (docs/UI-STYLE.md).

   Źródło wyglądu Klasyka: "Parafia - Strona główna (standalone)_ok.html".
   Paleta frontu jest inna niż panel Filament — to celowe (patrz CLAUDE.md).
   --pri / --sec nadpisuje paleta parafii, inline na .front-root w layoucie;
   wartości w `:root` niżej to tylko zapas.
   ========================================================================= */

:root {
  --pri: #4A2532;            /* bordowy — kolor główny */
  --sec: #B08A3E;            /* złoty — kolor akcentu */
  --bg: #FAF7F2;             /* kremowe tło strony */
  --surface: #FFFFFF;
  --surface-alt: #FAF7F2;
  --surface-2: #F3EDE3;
  --border: #EBE4D8;
  --border-2: #E8E1D6;
  --border-3: #EFE8DC;
  /* Skala promieni (radius-xs do radius-4xl) stoi w SKÓRZE szablonu — Klasyk
     zaokrągla od 2 do 14 px, Łukasz jest kanciasty. Pigułka i koło zostają
     tutaj, bo opisują KSZTAŁT, a nie stopień zaokrąglenia. */
  --radius-pill: 999px;
  --radius-circle: 50%;
  /* Cienie POWTARZALNE (E1c). Pozostałe box-shadow są jednorazowe i mają różne
     wartości (kolor/rozmycie/krycie) — zostają dosłownie, bo scalenie zmieniłoby
     piksele (kryterium: zero zmian wizualnych). */

  /* ============================================================
     SKALA WARSTW (26.07.2026) — JEDNA dla całego frontu.

     Powstała po realnym błędzie: przycisk nakładki baneru wyświetlał się NAD
     powiększonym zdjęciem w galerii. Przyczyną nie była za wysoka liczba przy
     przycisku, tylko to, że LIGHTBOX SIEDZIAŁ WEWNĄTRZ TREŚCI — a element
     pozycjonowany w środku kontenera, który tworzy własny kontekst nakładania
     (u nas robi to `container-type`), nie może przebić się nad rodzeństwo tego
     kontenera, choćby miał z-index 1000. Dlatego lightbox jest teraz
     WYPROWADZANY DO <body> (`x-teleport`), a warstwy mają jedną skalę.

     ZASADA: nowy element z podniesioną warstwą bierze zmienną z tej listy.
     Żadnych liczb wpisywanych wprost — inaczej za pół roku znów nie będzie
     wiadomo, co nad czym leży.
     ============================================================ */
  --z-tlo: 0;              /* wzory i zdjęcia w tle sekcji */
  --z-tresc: 1;            /* treść sekcji nad tłem */
  --z-plakietka: 2;        /* plakietki na zdjęciach, ikona odtwarzania, nakładka baneru */
  --z-sterowanie: 3;       /* strzałki karuzeli */
  --z-rozwijane: 30;       /* podmenu nagłówka, menu mobilne */
  --z-naglowek: 60;        /* przyklejony nagłówek */
  --z-belka: 70;           /* belka zgody na ciasteczka */
  --z-nakladka: 80;        /* nakładka „pełne informacje" */
  --z-zdjecie: 90;         /* POWIĘKSZONE ZDJĘCIE — nad wszystkim */

  --shadow-1: 0 8px 24px rgba(46, 32, 22, 0.12);
  --shadow-2: 0 6px 18px rgba(46,32,22,0.18);
  /* Cień PLAKIETKI leżącej na zdjęciu (25.07.2026). Kartka z datą jest biała,
     więc na ciemnym zdjęciu widać ją od razu, ale na jasnym jej krawędź znikała
     (biel na bieli = 1:1) — sam włosowy obrys z palety bywa na to za słaby.
     Cień odrysowuje krawędź dokładnie tam, gdzie jest potrzebny: na jasnym tle
     jest widoczny, na ciemnym niewidoczny i niepotrzebny. */
  --shadow-stamp: 0 1px 4px rgba(20, 12, 10, 0.28);
  --ink: #29241F;           /* główny tekst */
  --ink-2: #4B4238;
  --ink-3: #5B5248;
  --muted: #8A7F70;
  /* Tło stopki (footer-bg) stoi w SKÓRZE szablonu — Klasyk: własny ciemny brąz, Łukasz: kolor główny. */
  --footer-ink: rgba(246, 239, 228, 0.85);
  --footer-ink-dim: rgba(246, 239, 228, 0.55);
  --footer-hair: rgba(255, 255, 255, 0.12);
  /* JEDNA SIATKA TREŚCI (31.07.2026). Wcześniej były DWA tokeny — `--container`
     Każdy pas z osobna wyglądał poprawnie, ale na całej stronie treść zaczynała
     się w dwóch różnych miejscach i pasy nie stały w jednej linii.

     Teraz JEDNA szerokość dla WSZYSTKIEGO: strefy dwukolumnowej, menu bocznego,
     pasów pełnej szerokości, kart-linków, taśm, stopki i nagłówka. Wyrównane
     w GÓRĘ, do szerszej z dwóch wartości — właścicielowi zależy na szerokim
     układzie. Tła zostają na całą szerokość okna; wyrównana jest wyłącznie treść.

     `--tresc-pad` to margines boczny na ekranach węższych niż `--tresc-max`. */
  /* Szerokość treści (tresc-max) stoi w SKÓRZE szablonu — Klasyk 1320 px, Łukasz 1170 px. */
  --tresc-pad: 24px;
  /* WCIĘCIE DO LINII TREŚCI — JEDEN token dla wszystkiego, co ma tło na całą
     szerokość, a treść na wspólnej siatce (02.08.2026). Pas strony głównej
     używał tej formuły wprost w `padding-inline`; odkąd korzysta z niej też
     treść przyklejonego paska akcji, musi być JEDNA — inaczej tekst banera
     i tytuły sekcji zaczynałyby się w dwóch różnych miejscach, a nikt by tego
     nie zauważył przy zmianie jednej z kopii.

     `100%` NIE jest tu liczone w chwili deklaracji: procenty w wartości
     zmiennej zostają tokenem i rozwiązują się dopiero przy użyciu, względem
     bloku zawierającego DANY element. Dlatego ten sam token daje 60 px na pasie
     pełnej szerokości (okno 1440) i 24 px w kolumnie głównej — bez wariantów. */
  --tresc-wciecie: max(var(--tresc-pad), calc((100% - var(--tresc-max)) / 2));
  /* Wcięcie wewnętrzne bloku w pasie trójdzielnym. */
  --trio-pad: 26px;

  /* ============================================================
     CZYTELNA DŁUGOŚĆ WIERSZA (31.07.2026, reguła 3).

     Pas pełnej szerokości ma 1272 px treści. Akapit rozciągnięty na całą tę
     szerokość to ~170 znaków w wierszu — oko gubi początek następnego wiersza.
     Blok TEKSTOWY albo ogranicza się do tej miary, albo dzieli treść na kolumny
     (listy niezależnych pozycji: intencje, nagłówki wiadomości).

     Jednostka `ch` zamiast pikseli, bo miara ma iść za stopniem pisma, a nie za
     rozdzielczością — po zmianie kroju wiersz zostaje tak samo długi. */
  --proza-max: 68ch;
  /* JEDEN token odstępu pionowego pasa strony głównej — zastąpił rozjechane
     44px (Klasyk) i trzy różne clamp-y (Nowoczesny). Padding-block pasa, więc
     dwa sąsiednie pasy dzieli 2× ta wartość; clamp sam redukuje ją na telefonie
     (≈36px) i rozciąga na desktopie (≈64px), bez osobnego tokenu mobilnego. */
  --band-space: clamp(2.25rem, 4.5vw, 4rem);
  /* Kroje (font-serif i font-sans) stoją w SKÓRZE szablonu. Nazwy mówią o ROLI
     (pismo nagłówkowe / tekst ciągły), nie o klasyfikacji kroju. */
  /* Tokeny odstępów (korekta bloków treści, zgłoszenie Bartka) — ta sama
     skala matematyczna 8/12/16/24/32px co w panelu (UI-STYLE.md), tylko
     w rem, żeby marginesy bloków treści nie były pojedynczymi, przypadkowymi
     wartościami px. */
  --space-xs: 0.5rem;   /* 8px */
  --space-sm: 0.75rem;  /* 12px */
  --space-md: 1rem;     /* 16px */
  --space-lg: 1.5rem;   /* 24px */
  --space-xl: 2rem;     /* 32px */
  --space-2xl: 3rem;    /* 48px */
}

html, body { margin: 0; padding: 0; background: var(--bg); }
* { box-sizing: border-box; }

/* ============================================================
   TŁO STRONY (12.09.2026) — zamknięty zestaw, wybór parafii.
   Rejestr: App\Support\Front\PageBackgrounds; progi i pomiary:
   docs/funkcje/tlo-strony-rekonesans.md.

   TO NIE JEST TŁO SEKCJI. Pas z mocnym tłem (`dark`, `accent`, `photo`)
   zasłania tło strony u siebie i tak ma zostać — tło strony widać tam, gdzie
   dziś widać ton strony. „Przygaszone" i łuki „wzoru" liczą się od koloru tła
   strony, a przy zdjęciu automat ich nie daje (12.09.2026).

   KOLOR nie potrzebuje tu ANI JEDNEJ reguły: wchodzi w paletę jako kotwica
   `bg`, więc `--bg` już jest tym kolorem. Zdejmujemy tylko tło z `.front-root`,
   żeby przy ZDJĘCIU nie zakrywało warstwy.

   DLACZEGO WARSTWA `position: fixed` POD TREŚCIĄ, a nie `background-attachment:
   fixed` na `html`: Safari na iPhonie tego nie obsługuje (rysuje tło jak
   przewijane, rozciągnięte na całą wysokość strony). Pomiar w renderze
   potwierdził, że warstwa daje identyczne wyniki.

   DLACZEGO `z-index: -1`: warstwa ma iść POD treść. Przy `z-index: 0` element
   pozycjonowany maluje się NAD tłami i tekstem elementów niepozycjonowanych —
   złapane przy pomiarach, zasłaniało pół strony. Ujemna warstwa maluje się
   zaraz po tle korzenia, więc `html` i `body` muszą być wtedy przezroczyste.
   ============================================================ */
html[data-tlo] .front-root { background: transparent; }
html[data-tlo="zdjecie"],
html[data-tlo="zdjecie"] body { background: transparent; }
html[data-tlo="zdjecie"] body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--tlo-strony) center / cover no-repeat;
  pointer-events: none;
}

.front-root {
  font-family: var(--font-sans);
  color: var(--ink);
  background: var(--bg);
  min-height: 100vh;
  line-height: 1.6;
}

/* Linki treści dziedziczą kolor tekstu (reset domyślnego niebieskiego), ale
   NIE przyciski (.btn*) — te mają własny kolor (biel na ciemnym tle). Bez
   :not(.btn) reguła (0,1,1) wygrywała z .btn--dark (0,1,0) → ciemny tekst na
   ciemnym przycisku. */
/* ============================================================
   E2 — ZAKRES SEKCJI (tła pasów strony głównej)
   Sekcja ustawia zmienne inline (serwerowo, z palety — patrz
   App\Support\Front\SectionBackgrounds), a wnętrza widgetów konsumują TE
   zmienne zamiast palety. DOMYŚLNE wartości poniżej = DZISIEJSZE kolory,
   dlatego wariant „jasne" nie nadpisuje niczego i render jest identyczny
   jak przed E2 (dowód braku regresji).
   ============================================================ */
:root {
  --section-bg: transparent;        /* dziś sekcje nie malują własnego tła */
  --section-text: var(--ink);
  --section-heading: var(--pri);
  --section-muted: var(--muted);
  --section-border: var(--border);
  --section-link: var(--pri);
  --section-accent: var(--sec);
  --card-bg: var(--surface);
  --card-text: var(--ink);
  --card-heading: var(--pri);
  --card-muted: var(--muted);
  --card-link: var(--pri);
  --card-border: var(--border);
  --card-bg-alt: var(--surface-2);
  /* STANY INTERAKCJI (25.07.2026). Domyślne wartości = wariant „jasne";
     warianty ciemny/akcentowy podmieniają je w App\Support\Front\SectionBackgrounds,
     dokładnie tym samym mechanizmem co kolory tekstu i obwódek. */
  --card-border-hover: var(--sec-strong, var(--sec));
  --card-bg-hover: var(--card-bg);      /* na jasnym reaguje cień, tło zostaje */
  --card-bg-active: var(--surface-2);
  /* Obrys fokusu klawiatury. Na jasnych tłach ciemny akcent palety, bo trzy
     z czterech palet nie mają w sobie niebieskiego i systemowy pierścień byłby
     tam ciałem obcym; na ciemnych i akcentowych — biel (patrz SectionBackgrounds). */
  --section-focus: var(--sec-strong, var(--pri));
  /* AKCENT DO TEKSTU I CIENKICH LINII (25.07.2026, Etap 6 pracy nocnej).
     Jasne złoto/srebro ma na białym tle 2,6–3,1:1 — poniżej progu. Ten token
     jest jego CZYTELNYM odpowiednikiem: na jasnym tle wariant przyciemniony,
     na ciemnym i akcentowym z powrotem jasny (patrz SectionBackgrounds).
     `--section-accent` zostaje dla DEKORACJI: ramek zdjęć, pasków nad kartami,
     obramowań cytatów — tam kolor niczego nie komunikuje. */
  --section-accent-ink: var(--sec-strong, var(--sec));
  /* TŁO WIERSZA przy najechaniu i wciśnięciu — osobne od kafla, bo wiersz nie
     ma cienia i tło jest jego głównym sygnałem (patrz `.news-row:hover`). */
  --row-bg-hover: var(--surface-2);
  --row-bg-active: var(--border-3);

  /* PODŁOGA CZYTELNOŚCI TEKSTU FUNKCJONALNEGO (07.09.2026).
     Wartość była wpisana wprost w `max()` przy minucie godziny; teraz jest
     tokenem, bo tę samą granicę trzyma etykieta rodzaju na kaflu Multimediów
     w Nowoczesnym. Ta sama nazwa i wartość stoi w public/css/front-modern.css.
     Pilnuje TypographyFloorTest. */
  --text-floor: 11px;
}
.front-section { background: var(--section-bg); color: var(--section-text); }
.front-section .section-title { color: var(--section-heading); }

.front-root a:not(.btn) { color: inherit; }

/* ============================================================
   FOKUS KLAWIATURY — warstwa dostępności (25.07.2026)

   Do tej pory front Klasyka NIE MIAŁ ANI JEDNEJ reguły fokusu: obrys
   rysowała przeglądarka po swojemu, a na ciemnym pasie bywał niewidoczny.
   Osoba poruszająca się klawiaturą nie wiedziała, gdzie jest.

   `:focus-visible`, nie `:focus` — obrys pokazuje się przy nawigacji
   klawiaturą, a nie po kliknięciu myszą. Odstęp (`outline-offset`) sprawia,
   że obrys leży na tle SEKCJI, więc kontrast liczymy względem niego —
   i dlatego kolor jest tokenem zakresu sekcji, nie stałą.
   ============================================================ */
.front-root :is(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--section-focus);
  outline-offset: 2px;
}
/* Fokus NIGDY nie jest wyłączany bez zamiennika — gdyby ktoś kiedyś dopisał
   `outline: none`, ta reguła i strażnik w testach mają go zatrzymać. */

/* ============================================================
   OGRANICZONY RUCH (systemowe ustawienie dostępności)
   Przejścia stanów stają się natychmiastowe. Zmiana koloru i cienia zostaje —
   znika tylko sam RUCH w czasie.
   ============================================================ */
/* Ograniczenie ruchu (prefers-reduced-motion) obsługuje JEDEN zapis dla obu
   szablonów — public/css/motion.css. Nie dopisywać tu drugiego: różniły się
   i jeden z nich wyłączał przejścia zamiast skracać ich czas. */

/* ---- Typografia pomocnicza ---- */
.serif { font-family: var(--font-serif); }
.kicker {
  font-size: 13px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--section-accent-ink);
  font-weight: 700;
}
.section-title {
  font-family: var(--font-serif);
  /* SKALA IDZIE ZA KOLUMNĄ, NIE ZA OKNEM (08.08.2026).
     Do tej pory `3.4vw` mierzyło szerokość OKNA, więc nagłówek w komórce ⅓
     pasa trójdzielnego (395 px) dostawał dokładnie ten sam stopień co nagłówek
     pasa pełnej szerokości (1272 px) — zmierzone 40 px w obu. W jednym rzędzie
     stały wtedy obok siebie nagłówki 40 px i 20 px (Ogłoszenia kompakt), co
     czytało się jak usterka renderowania, a nie jak hierarchia.

     `cqi` mierzy kontener, a `container-type: inline-size` stoi już na
     `.front-section` (używają go siatki kafli).

     WSPÓŁCZYNNIK 4.5 DOBRANY POMIAROWO, nie przepisany z poprzedniej wartości:
     przy 3.4 (tyle, ile było przy `vw`) kolumna główna strefy spadała z 40 na
     30 px, a przy 4.3 — na 38,3 px, czyli zmiana wychodziła poza wąskie komórki,
     o które chodziło. Przy 4.5 górną granicę osiąga już kontener ~890 px, więc
     zmienia się WYŁĄCZNIE to, co naprawdę wąskie. Zmierzone po zmianie: pas
     pełny 40 px, strefa 40 px, telefon 28 px — wszystkie bez zmian; komórka ⅓
     pasa trójdzielnego 40 → 28 px. */
  font-size: clamp(28px, 4.5cqi, 40px);
  font-weight: 600;
  color: var(--section-link);
  margin: 0;
  line-height: 1.15;
}

/* PRZEJDŹ DO TREŚCI (07.09.2026, A23) — pierwszy przystanek klawisza Tab.

   UKRYCIE PRZEZ WYPROWADZENIE POZA EKRAN, nie `display: none` ani
   `visibility: hidden`: te dwa WYJMUJĄ element z kolejności tabulacji, więc
   odnośnik nigdy by fokusu nie dostał i nie miał jak się pokazać. Po
   otrzymaniu fokusu wjeżdża w widok — w pełni widoczny, nie półprzezroczysty.

   Kolor obrysu z zakresu sekcji, jak wszędzie; odnośnik leży na tle strony,
   czyli poza pasami, więc bierze zapas z `:root`.
   Cel skoku (`<main id="tresc">`) ma `tabindex="-1"`, ale NIE gasimy mu obrysu:
   `outline: none` jest w tym arkuszu zakazane (FocusVisibleTest), a i tak nie
   byłoby potrzebne — fokus po skoku z odnośnika nie jest fokusem KLAWIATUROWYM
   w rozumieniu `:focus-visible`, więc przeglądarka obrysu tam nie rysuje.
   Sprawdzone na żywo w obu szablonach. */
/* Selektor `.front-root a.front-skip`, nie samo `.front-skip`: wyżej w arkuszu
   stoi `.front-root a:not(.btn) { color: inherit }` (reset domyślnego
   niebieskiego), które ma tę samą wagę i przebiłoby kolor odnośnika — biały
   napis na ciemnej pigułce zamieniłby się w ciemny na ciemnym, 1,45:1.
   Ta reguła stoi PÓŹNIEJ, więc przy równej wadze wygrywa. */
.front-root a.front-skip {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: calc(var(--z-naglowek) + 1);
  transform: translateY(-200%);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 22px;
  border-radius: var(--radius-pill);
  background: var(--pri);
  color: var(--surface);
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: var(--shadow-2, 0 8px 24px rgba(0, 0, 0, 0.28));
  transition: transform var(--motion-state) var(--motion-ease-state);
}
/* `:focus`, NIE `:focus-visible` — wyjątek świadomy i jedyny w tym arkuszu.
   Odnośnika pominięcia nie da się złapać myszą (leży poza ekranem), więc
   rozróżnienie „klawiatura kontra kliknięcie" nic tu nie wnosi, a `:focus` jest
   ściśle szerszy: pokazuje odnośnik w każdym przypadku, w którym zrobiłby to
   `:focus-visible`, i w kilku, w których tamten by się zawahał. Odnośnik, który
   przy fokusie się nie pokazał, jest gorszy niż jego brak. */
.front-root a.front-skip:focus { transform: translateY(0); }

/* =========================== NAGŁÓWEK =========================== */
.front-header {
  background: var(--surface);
  border-bottom: 1px solid var(--border-2);
}
.front-header__inner {
  max-width: var(--tresc-max);
  margin: 0 auto;
  padding: 14px var(--tresc-pad);
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}
/* Logo parafii: skalowane NA WYSOKOŚĆ z zachowaniem proporcji, bez kadrowania.
   Logo ma nieprzewidywalny kształt (poziome/pionowe/herb/z tekstem) — NIGDY nie
   wymuszamy okręgu ani przycięcia. Okrąg zarezerwowany dla zdjęć OSÓB (Duszpasterze). */
.front-header__logo-link { display: inline-flex; align-items: center; flex: 0 0 auto; }
.front-header__logo {
  height: 56px;
  width: auto;
  max-width: 240px;      /* zabezpieczenie układu przy bardzo szerokim logo */
  flex: 0 0 auto;
  object-fit: contain;   /* całe logo widoczne, bez obcięcia */
  display: block;
}
/* Blok tożsamości (logo + nazwa) — jedna całość, bo na poziomie 2/3 układu
   przenosi się do własnego rzędu w komplecie. Odstęp 18px = dawny odstęp
   kontenera, więc poziom 1 wygląda dokładnie jak przed zmianą. */
.front-header__brand {
  display: flex;
  align-items: center;
  gap: 18px;
  flex: 0 1 auto;
  min-width: 0;
}
/* Blok tytułu nie „rozpycha" nagłówka — kotwice trzyma margin-left:auto na menu
   (niżej), więc ukrycie nazwy/podtytułu NIE przesuwa menu. */
.front-header__titles { flex: 0 1 auto; min-width: 0; }
.front-header__name {
  font-family: var(--font-serif);
  font-size: clamp(22px, 2.6vw, 30px);
  font-weight: 600;
  color: var(--pri);
  line-height: 1.15;
  text-decoration: none;
}
.front-header__subtitle {
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 3px;
}

/* ---- Menu górne (desktop) ---- */
/* margin-left:auto = stała kotwica po PRAWEJ: menu zostaje przy prawej krawędzi
   niezależnie od obecności nazwy/podtytułu (ukrycie tekstu nie przesuwa menu). */
.front-topnav { display: flex; gap: 6px; align-items: center; margin-left: auto; }
.front-topnav__group { position: relative; }
.front-topnav__link {
  text-decoration: none;
  color: var(--ink-2);
  font-size: 16px;
  letter-spacing: 0.04em;
  padding: 10px 14px;
  border-bottom: 2px solid transparent;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: color var(--motion-state) var(--motion-ease-state), border-color var(--motion-state) var(--motion-ease-state);
  cursor: pointer;
}
.front-topnav__link:hover,
.front-topnav__group:hover > .front-topnav__link,
.front-topnav__link--active {
  color: var(--pri);
  border-bottom-color: var(--sec);
}
/* Aktywna GRUBIEJ niż hover — inaczej „gdzie jestem" wyglądałoby tak samo jak
   „na co najechałem". Ujemny margines dolny kompensuje grubszą ramkę, żeby
   pozycje nie skakały o 1 px. */
.front-topnav__link--active {
  font-weight: 700;
  /* Akcent PRZYCIEMNIONY: pasek niesie informację „tu jesteś", więc musi mieć
     kontrast (jasne złoto ma na białym 2,6–3,1:1, ten wariant 6,0–6,9:1 we
     wszystkich czterech paletach). Hover zostaje przy jasnym akcencie. */
  border-bottom-color: var(--sec-strong, var(--sec));
  border-bottom-width: 3px;
  margin-bottom: -1px;
}
.front-topnav__caret { font-size: 11px; color: var(--section-accent-ink); }

/* podmenu (poziom 2) — rozwijane na hover na desktopie */
.front-topnav__submenu {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 220px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: 0 12px 30px rgba(46, 32, 22, 0.14);
  padding: 6px 0;
  margin: 0;
  list-style: none;
  z-index: var(--z-rozwijane);
  display: none;
}
.front-topnav__group:hover > .front-topnav__submenu,
.front-topnav__group:focus-within > .front-topnav__submenu { display: block; }
.front-topnav__sublink {
  display: block;
  text-decoration: none;
  color: var(--ink-2);
  font-size: 15px;
  padding: 10px 18px;
  border-left: 3px solid transparent;
}
.front-topnav__sublink:hover {
  color: var(--pri);
  background: var(--surface-alt);
  border-left-color: var(--section-accent-ink);
}
/* Oznaczenie strony, na której jesteśmy (kolory z tokenów palety, nie na sztywno).
   W podmenu podkreślenia nie ma — rolę wskaźnika pełni pasek z lewej i tło. */
.front-topnav__sublink--active {
  color: var(--pri);
  background: var(--surface-alt);
  border-left-color: var(--sec-strong, var(--sec));
  font-weight: 700;
}

/* ---- Hamburger + menu mobilne ---- */
.front-burger {
  background: none;
  border: 1px solid #D9CFC0;
  border-radius: var(--radius-lg);
  width: 46px;
  height: 46px;
  display: none;
  flex-direction: column;
  gap: 5px;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  margin-left: auto;   /* kotwica po prawej na mobile (gdy topnav ukryty) */
}
.front-burger span { display: block; width: 20px; height: 2px; background: var(--pri); }

.front-mobilenav {
  border-top: 1px solid var(--border-2);
  background: var(--surface);
  padding: 8px 0;
}
.front-mobilenav a,
.front-mobilenav__parent {
  display: block;
  text-decoration: none;
  color: var(--ink-2);
  font-size: 16px;
  padding: 13px 24px;
}
.front-mobilenav__parent { font-weight: 700; color: var(--pri); }
.front-mobilenav__child { padding-left: 40px; font-size: 15px; }
/* Aktywna pozycja w menu mobilnym — pasek akcentu z lewej zamiast podkreślenia
   (na liście pionowej podkreślenie zlewa się z odstępami między pozycjami). */
.front-mobilenav a.is-active {
  color: var(--pri);
  font-weight: 700;
  box-shadow: inset 4px 0 0 var(--sec-strong, var(--sec));
  background: var(--surface-alt);
}

/* ---- Dopasowanie nagłówka: poziomy 1-3 (patrz front/partials/header-fit) ---- */
/* Pozycja schowana do „Więcej" (albo sam kontener „Więcej", zanim jest potrzebny). */
.front-topnav .is-header-overflow { display: none !important; }
/* Pomiar naturalnej szerokości menu — chwilowo bez zawijania I BEZ KURCZENIA
   pozycji. Samo `nowrap` nie wystarcza: pozycje jako elementy flex domyślnie
   mają flex-shrink:1, więc ściskają się do dostępnej szerokości i menu
   raportuje węższy rozmiar niż naprawdę potrzebuje (błąd wykryty w przeglądarce
   18.07.2026 — nagłówek zostawał na poziomie 1 mimo dwóch linii menu). */
.front-topnav[data-header-measuring] { flex-wrap: nowrap !important; }
.front-topnav[data-header-measuring] > * {
  flex: 0 0 auto !important;
  white-space: nowrap !important;
}
/* Podmenu „Więcej" wyrównane do PRAWEJ (pozycja stoi przy prawej krawędzi). */
[data-header-more] .front-topnav__submenu { left: auto; right: 0; }
.front-topnav__sublink--child { padding-left: 34px; font-size: 14px; }

@media (min-width: 901px) {
  /* Poziom 2 i 3: blok logo+nazwa dostaje własny rząd, menu rząd pod nim —
     oba wyśrodkowane. Żadnego ściskania nazwy obok linków. */
  .front-header[data-header-level="2"] .front-header__inner,
  .front-header[data-header-level="3"] .front-header__inner {
    flex-direction: column;
    align-items: center;
    gap: 10px;
  }
  .front-header[data-header-level="2"] .front-header__brand,
  .front-header[data-header-level="3"] .front-header__brand {
    justify-content: center;
    text-align: center;
  }
  .front-header[data-header-level="2"] .front-topnav,
  .front-header[data-header-level="3"] .front-topnav {
    margin-left: 0;
    justify-content: center;
    flex-wrap: wrap;
    row-gap: 2px;   /* zwarte, gdy menu i tak zejdzie do dwóch linii */
  }
}

/* ---- Przyklejony nagłówek (ustawienie: Wygląd strony → Tożsamość) ---- */
.front-header[data-sticky] {
  position: sticky;
  top: 0;
  z-index: var(--z-naglowek);
  /* Kompensacja skoku: o ile nagłówek się skurczy, tyle dokładamy pod nim, więc
     treść strony NIE PODSKAKUJE w momencie zwinięcia (--header-shrink liczy
     skrypt dopasowania; obie zmiany mają ten sam czas i krzywą). */
  margin-bottom: 0;
  transition: margin-bottom 180ms ease, box-shadow 180ms ease;
}
.front-header[data-sticky][data-condensed] {
  margin-bottom: var(--header-shrink, 0px);
  box-shadow: 0 6px 18px rgba(46, 32, 22, 0.08);
}
.front-header[data-sticky] .front-header__inner {
  transition: padding 180ms ease, gap 180ms ease;
}
.front-header[data-sticky] .front-header__name,
.front-header[data-sticky] .front-header__logo {
  transition: font-size 180ms ease, height 180ms ease;
}

/* Kurczenie na DESKTOPIE (poprawka 24.07.2026): w układzie wyśrodkowanym
   (nazwa w swoim rzędzie, menu pod spodem) przyklejony nagłówek zasłaniał zbyt
   dużo strony. Po przewinięciu wraca do JEDNEGO RZĘDU: mała nazwa z lewej,
   menu z prawej. NAZWA ZOSTAJE i dalej prowadzi na stronę główną — bez niej nie
   wiadomo, gdzie się jest, i nie ma czym wrócić na górę serwisu. */
@media (min-width: 901px) {
  .front-header[data-sticky][data-condensed] .front-header__inner {
    flex-direction: row !important;
    align-items: center;
    gap: 18px;
    padding-top: 8px;
    padding-bottom: 8px;
    /* Nic nie spada do drugiej linii — nadmiar pozycji menu chowa skrypt
       dopasowania pod „Więcej ▾" (liczy je dla węższego, zwiniętego rzędu). */
    flex-wrap: nowrap !important;
  }
  .front-header[data-sticky][data-condensed] .front-header__brand {
    justify-content: flex-start !important;
    text-align: left !important;
  }
  .front-header[data-sticky][data-condensed] .front-topnav {
    margin-left: auto !important;
    justify-content: flex-end !important;
    flex-wrap: nowrap;
  }
  .front-header[data-sticky][data-condensed] .front-header__logo { height: 34px; }
  .front-header[data-sticky][data-condensed] .front-header__subtitle { display: none; }
  .front-header[data-sticky][data-condensed] .front-header__name { font-size: 20px; }
}
/* Skoki do kotwic nie chowają treści pod nagłówkiem — wysokość publikuje
   skrypt dopasowania do zmiennej --header-h. */
html { scroll-padding-top: calc(var(--header-h, 0px) + 12px); }

@media (max-width: 900px) {
  /* Po przewinięciu nagłówek zwija się do wąskiego paska: zostaje nazwa
     (skrócona wielokropkiem) i przycisk menu; logo i podtytuł chowamy, żeby
     przyklejony nagłówek nie zjadał małego ekranu. */
  .front-header[data-sticky][data-condensed] .front-header__inner {
    padding-top: 6px;
    padding-bottom: 6px;
  }
  .front-header[data-sticky][data-condensed] .front-header__logo-link,
  .front-header[data-sticky][data-condensed] .front-header__subtitle { display: none; }
  .front-header[data-sticky][data-condensed] .front-header__name {
    font-size: 18px;
    display: block;
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* =========================== KORPUS DWUKOLUMNOWY =========================== */
.front-body { width: 100%; }
.front-body__inner {
  max-width: var(--tresc-max);
  margin: 0 auto;
  padding: 48px var(--tresc-pad);
}
.front-body--with-sidebar .front-body__inner {
  display: flex;
  gap: 32px;
  align-items: flex-start;
}
.front-main { flex: 1 1 auto; min-width: 0; }

/* ---- Okruszki (decyzja B2, 09.09.2026) ----
   Ten sam markup co w Nowoczesnym (`front.partials.breadcrumbs`), typografia
   Klasyka: krój tekstowy, stopień 13 px jak `.kicker`, kolor przygaszony.
   Stoją NAD nagłówkiem podstrony, w kolumnie treści — pas pełnej szerokości to
   pozycja B1 i nie jest rozstrzygnięta. */
.front-breadcrumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 14px;
  color: var(--ink-3);
  font-size: 13px;
  line-height: 1.4;
}
.front-breadcrumbs a { color: var(--pri); text-decoration: none; text-underline-offset: 0.2em; transition: color var(--motion-state) var(--motion-ease-state); }
.front-breadcrumbs a:hover { text-decoration: underline; }
.front-breadcrumbs a:active { opacity: 0.75; }
.front-breadcrumbs .sep { opacity: 0.55; }

/* ---- Lewa kolumna ---- */
/* `order: -1` — kolumna boczna stoi w drzewie ZA treścią (żeby `h1` podstrony
   był pierwszym nagłówkiem dokumentu, a skok „przejdź do treści" nie zostawiał
   jej za plecami), a po LEWEJ stawia ją arkusz. To jest miejsce, w którym
   `FrontTemplates::railSide('classic') === 'start'` przekłada się na wygląd —
   do 08.09.2026 robiła to kolejność w Blade. Reguła obowiązuje na obu
   szerokościach: przy jednej kolumnie rail zostaje NAD treścią. */
.front-sidebar {
  order: -1;
  flex: 0 0 262px;
  position: sticky;
  top: 24px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.front-sidebar__toggle { display: none; }
.front-sidebar__box {
  background: var(--surface);
  border: 1px solid var(--border);
}
.front-sidebar__head {
  padding: 18px 22px 14px;
  border-bottom: 1px solid var(--border-3);
  font-size: 13px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--section-accent-ink);
  font-weight: 700;
}
.front-sidebar__list, .front-sidebar__sublist { list-style: none; margin: 0; padding: 0; }
.front-sidebar__list { padding: 8px 0; }
.front-sidebar__row {
  display: flex;
  align-items: stretch;
  border-left: 3px solid transparent;
}
/* Wiersz folderu jest <button> (klik w cały wiersz rozwija) — pełny reset natywnego
   wyglądu przycisku (Chrome dorysowywał szarą ramkę/tło), zachowana tylko lewa
   krawędź/podświetlenie z .front-sidebar__row (border-left re-dodany po border:none). */
.front-sidebar__row--toggle {
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  border: none;
  border-left: 3px solid transparent;
  border-radius: 0;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.front-sidebar__row.is-active { border-left-color: var(--section-accent-ink); background: var(--surface-alt); }
.front-sidebar__link {
  flex: 1;
  text-decoration: none;
  color: var(--ink-2);
  font-size: 16px;
  padding: 12px 22px;
  transition: color var(--motion-state) var(--motion-ease-state), background var(--motion-state) var(--motion-ease-state);
}
.front-sidebar__row:hover { background: var(--surface-alt); border-left-color: var(--section-accent-ink); }
.front-sidebar__row:hover .front-sidebar__link { color: var(--pri); }
.front-sidebar__row.is-active .front-sidebar__link { color: var(--pri); font-weight: 700; }
.front-sidebar__link--plain { cursor: default; }
.front-sidebar__caret {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--section-accent-ink);
  font-size: 13px;
  padding: 0 16px;
  display: flex;
  align-items: center;
}
.front-sidebar__sublist { padding: 2px 0 8px; }
.front-sidebar__sublink {
  display: block;
  text-decoration: none;
  color: var(--ink-3);
  font-size: 15px;
  padding: 9px 22px 9px 34px;
  border-left: 3px solid transparent;
}
.front-sidebar__sublink:hover { color: var(--pri); background: var(--surface-alt); border-left-color: var(--section-accent-ink); }
.front-sidebar__sublink.is-active { color: var(--pri); font-weight: 700; border-left-color: var(--section-accent-ink); background: var(--surface-alt); }

/* =========================== PRZYCISKI =========================== */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.06em;
  padding: 13px 26px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--motion-state) var(--motion-ease-state), border-color var(--motion-state) var(--motion-ease-state), color var(--motion-state) var(--motion-ease-state);
}
/* ============================ AKCJE — DWA WARIANTY ====================
   Jeden komponent, dwa warianty (30.07.2026, ujednolicenie akcji na froncie).
   Do tej pory ta sama akcja bywała raz linkiem, raz przyciskiem, w dwóch
   rozmiarach i z etykietą „Czytaj więcej" niezależnie od tego, dokąd prowadzi.

   PODSTAWOWY (.btn--action) — wypełniony MOCNYM kolorem palety. To jest `--pri`,
   czyli granat, bordo albo zieleń ZALEŻNIE OD PALETY parafii; nie opisujemy go
   kolorem, tylko rolą.
   POMOCNICZY (.btn--action-quiet) — tło pasa, obrys i napis w kolorze palety.

   Oba: ten sam rozmiar, ten sam promień, ta sama strzałka (rysuje ją komponent
   jako element `aria-hidden`, NIE znak w treści etykiety) i to samo przejście,
   wzięte z tokenu warstwy ruchu.

   NA PASIE CIEMNYM I AKCENTOWYM warianty odwracają się przez tokeny zakresu
   (--section-action-*): podstawowy wypełnia się jasnym z ciemnym napisem,
   pomocniczy zostaje obrysowany bielą. Bez tego oba wyglądałyby tak samo.
   Zapasy w var() to wartości dla pasa jasnego. */
.btn--action,
.btn--action-quiet {
  border-radius: var(--radius-sm);
  /* Przejście z tokenu warstwy ruchu — nie własny czas trwania. */
  transition: background-color var(--motion-state) var(--motion-ease-state),
              border-color var(--motion-state) var(--motion-ease-state),
              color var(--motion-state) var(--motion-ease-state);
}
.btn--action {
  background: var(--section-action-bg, var(--pri));
  color: var(--section-action-ink, #FFFFFF);
  border-color: transparent;
}
.btn--action:hover {
  background: var(--section-action-bg-hover, var(--pri-strong, var(--ink)));
  color: var(--section-action-ink-hover, #FFFFFF);
}
.btn--action-quiet {
  background: transparent;
  color: var(--section-action-quiet-ink, var(--pri));
  border-color: var(--section-action-quiet-border, var(--pri));
}
.btn--action-quiet:hover {
  background: var(--section-action-quiet-bg-hover, var(--surface-2));
  color: var(--section-action-quiet-ink-hover, var(--pri));
}
/* Strzałka wspólna dla obu wariantów. Odsunięcie przy najechaniu robimy
   marginesem, nie `transform` — przycisk nie ma zmieniać wymiarów. */
.btn__arrow { flex: 0 0 auto; transition: margin-left var(--motion-state) var(--motion-ease-state); }
.btn--action:hover .btn__arrow,
.btn--action-quiet:hover .btn__arrow { margin-left: 4px; }
/* WARIANT WSTECZ: strzałka stoi PRZED napisem i wskazuje w lewo, więc przy
   najechaniu odsuwa się w LEWO, nie w prawo — ruch ma iść za kierunkiem, w
   którym przycisk prowadzi. */
.btn--action-back:hover .btn__arrow { margin-left: 0; margin-right: 4px; }

.link-more {
  text-decoration: none;
  color: var(--section-link);
  font-weight: 700;
  font-size: 16px;
}
.link-more--gold { color: var(--section-accent-ink); letter-spacing: 0.06em; }

/* =========================== KAFLE SZYBKIEGO DOSTĘPU =========================== */
/* (Dawna `.front-quick { margin: 0 0 8px }` USUNIĘTA 23.07.2026 — była martwa:
   `section.front-section` nadpisywał ją specyficznością/kolejnością, margines
   nigdy nie działał.) */
/* ============================================================
   NIEPEŁNY RZĄD KAFLI — WITRYNA KONTRA PRZEGLĄDANIE (02.08.2026)

   Rozróżnienie idzie po MIEJSCU, nie po module:
     STRONA GŁÓWNA  — niepełny rząd WYŚRODKOWANY. To witryna; puste miejsce po
                      prawej wygląda jak niedokończona sekcja.
     KATALOG        — niepełny rząd przy LEWEJ krawędzi. To przeglądanie, a nie
                      ekspozycja; oko wraca na początek listy.

   JEDEN MECHANIZM DLA WSZYSTKICH SIATEK. Siatka nie wie nic o trasie — mówi jej
   to zmienna ODZIEDZICZONA z korzenia strony (`.front-root--home`). Każda siatka
   deklaruje własną `--kafel` (szerokość kafla) i sięga po dwie wspólne:

     --kafle-track    — czym wypełnić `repeat(auto-fill, …)`.
                        Katalog (zapas): `minmax(min(--kafel,100%), 1fr)` — ścieżki
                        rozciągają się i wypełniają pas, a puste zostają po prawej.
                        Strona główna: STAŁA szerokość — dopiero wtedy zostaje luz,
                        który `justify-content` ma co wyśrodkować.
     --kafle-justify  — `start` (zapas) albo `center`.

   DLACZEGO ZMIENNA, A NIE `.front-root--home .news-grid, .front-root--home …`:
   lista siatek rosłaby przy każdym nowym widgecie i prędzej czy później któraś
   by z niej wypadła. Tu nowa siatka wchodzi w regułę samym zadeklarowaniem
   `--kafel`. Podstawienie zmiennej w zmiennej działa, bo `var()` rozwija się
   W MIEJSCU UŻYCIA: korzeń niesie napis `min(var(--kafel), 100%)`, a `--kafel`
   rozwiązuje się dopiero w zakresie konkretnej siatki.

   NIE ZMIENIA to szerokości kafla — pojedynczy element dalej nie rozciąga się na
   cały pas (strażnik `test_siatka_kafli_nie_oddaje_jednemu_elementowi_calego_pasa`).
   ============================================================ */
.front-root--home { --kafle-justify: center; }

/* PUŁAPKA, przez którą pierwsze podejście nie zadziałało (02.08.2026):
   wartość zmiennej zawierającej `var()` jest podstawiana w zakresie, w którym
   ZOSTAŁA ZADEKLAROWANA, a nie w miejscu użycia. `--kafle-track: min(var(--kafel), …)`
   na korzeniu strony rozwiązywało się więc tam, gdzie `--kafel` nie istnieje —
   i schodziło w dół jako wartość PUSTA. `--kafle-justify` działa, bo nie zawiera
   `var()`; sam `justify-content` nie ma jednak czego wyśrodkować, dopóki ścieżki
   są elastyczne (`1fr` zjada cały luz).

   Dlatego stałą szerokość ścieżki nadaje JEDNA reguła z listą siatek. Listy
   pilnuje strażnik `test_kazda_siatka_kafli_jest_objeta_regula_strony_glownej`:
   siatka, która deklaruje `--kafel`, a nie stoi tutaj, zapala test. Regresja
   wracała już dwa razy, więc blokuje ją test, a nie komentarz. */
.front-root--home :is(
  .front-quick__grid,
  .cards-links-grid,
  .gallery-grid,
  .front-pastors-row,
  .sacrament-grid,
  .news-grid
) {
  /* ZWIJANIE ZAMIAST SIATKI (03.08.2026) — i to jest tu sedno.
     Poprzednia wersja dawała ścieżkom STAŁĄ szerokość, żeby wyśrodkować
     niepełny rząd. Skutek uboczny był taki, że rząd nigdy nie dochodził do
     krawędzi pasa: przy oknie 1440 kafle Aktualności zostawiały po 111 px
     z każdej strony, karty-linki po 171 px. Wyśrodkowanie tylko rozkładało
     tę nadwyżkę po równo — nie było jej przyczyną.

     Zwijanie robi obie rzeczy naraz: kafle ROSNĄ (`flex-grow`), więc pełny
     rząd wypełnia pas co do piksela, a rząd niepełny grupuje się ciasno
     i `justify-content` środkuje całą grupę. Siatka nie potrafiła jednego
     i drugiego jednocześnie.

     WZROST JEST OGRANICZONY: `max-width` z `--kafel-max`, a bez tej zmiennej
     kafel nie rośnie wcale (zapas = szerokość bazowa). Dzięki temu pojedynczy
     kafel NIGDY nie rozlewa się na cały pas — reguła, której pilnuje strażnik
     `test_wzrost_kafla_jest_ograniczony_maksymalna_szerokoscia`. Siatka, która
     ma wypełniać rząd, deklaruje `--kafel-max` świadomie. */
  display: flex;
  flex-wrap: wrap;
}
.front-root--home :is(
  .front-quick__grid,
  .cards-links-grid,
  .gallery-grid,
  .front-pastors-row,
  .sacrament-grid,
  .news-grid
) > * {
  flex: 1 1 var(--kafel);
  max-width: var(--kafel-max, var(--kafel));
  min-width: 0;
}

/* ============================================================
   POJEDYNCZY KAFEL NIE ROZCIĄGA SIĘ NA CAŁY PAS (02.08.2026).

   `auto-fit` ZWIJA puste ścieżki, więc siatka z jednym kaflem oddawała mu całą
   szerokość pasa — jedna galeria wyglądała jak baner. `auto-fill` ścieżek NIE
   zwija: kafel dostaje dokładnie tę szerokość, którą miałby w PEŁNYM rzędzie,
   staje przy lewej krawędzi treści, a reszta rzędu zostaje pusta.

   Liczba kolumn przy pełnym rzędzie się NIE zmienia — oba tryby liczą ścieżki
   tak samo; różnią się wyłącznie tym, co robią z pustymi.
   ============================================================ */
.front-quick__grid {
  display: grid;
  /* min(240px, 100%) zamiast samego 240px — w wąskiej kolumnie (strefa
     dwukolumnowa) kafel schodzi do pełnej szerokości zamiast wystawać poza
     kontener. Bez tego kafle nachodziły na siebie i na kolumnę boczną
     (błąd zgłoszony w przeglądzie wizualnym, naprawiony w Etapie 4). */
  /* KOMPLET TO TRZY KAFLE (msze / biuro / kontakt) — i to jest tu „pełny rząd".
     Przy 240 px auto-fill utworzyłby na pełnej szerokości CZTERY ścieżki, więc
     trzy kafle stanęłyby węższe, z pustym miejscem po prawej. Próg podniesiony
     tak, żeby na pełnej szerokości powstały dokładnie trzy:
       pełny 1272 cqi → 30cqi = 381 → 3 kafle
       połowa 596     →       240   → 2
       ⅓      356     →       240   → 1 */
  --kafel: clamp(240px, 31cqi, 400px);
  --kafel-max: 430px;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--kafel), 100%), 1fr));
  justify-content: var(--kafle-justify, start);
  gap: 20px;
}
.quick-card {
  background: var(--card-bg);
  border-top: 3px solid var(--section-accent);
  box-shadow: 0 8px 28px rgba(46, 32, 22, 0.10);
  padding: 28px 26px;
}
/* Wspólna klasa ikony sekcji (front/partials/section-icon.blade.php) —
   domyślny rozmiar, gdy miejsce nie narzuca własnego. */
.front-section-icon { display: inline-block; width: 20px; height: 20px; vertical-align: -0.2em; }
.front-section-icon svg { width: 100%; height: 100%; display: block; }

/* Ikona przy etykiecie w stopce (konto, BLIK) — mniejsza, w linii z tekstem. */
.front-footer__icon { display: inline-block; width: 15px; height: 15px; vertical-align: -0.18em; margin-right: 5px; }
.front-footer__icon svg { width: 100%; height: 100%; display: block; }
.quick-card a.inline-link { color: var(--card-text); font-weight: 700; text-decoration: none; }

/* =========================== PODSTRONA (moduł Strony) =========================== */
.front-page__head { margin-bottom: 24px; }
.front-page__title {
  font-family: var(--font-serif);
  font-size: clamp(30px, 4vw, 46px);
  font-weight: 600;
  color: var(--pri);
  margin: 0;
  line-height: 1.14;
  text-wrap: pretty;
}
.front-page__subtitle {
  font-size: 18px;
  color: var(--muted);
  margin: 10px 0 0;
  line-height: 1.5;
}
.front-page__content { font-size: 17px; }
/* Stan pusty listy/sekcji. Wcześniej goła kursywa w szarości — na pustej
   stronie wyglądała jak niedokończony akapit. Teraz spokojny, wyśrodkowany
   blok w ramce przerywanej (ten sam język co w szablonie Nowoczesnym), więc
   pustka czyta się jako stan ZAMIERZONY. Kolory ze zmiennych zakresu, żeby
   działał też na pasie ciemnym i akcentowym. */
.front-empty {
  margin: 0;
  padding: var(--space-2xl) var(--space-lg);
  text-align: center;
  color: var(--section-muted);
  background: var(--card-bg-alt);
  border: 1px dashed var(--section-border);
  border-radius: var(--radius-xl);
}

/* ---- Zakładki nad treścią (dzieci poziomu 3) ---- */
.front-tabs__bar {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  border-bottom: 2px solid var(--border);
  margin-bottom: 26px;
}
.front-tabs__tab {
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  margin-bottom: -2px;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--ink-2);
  padding: 12px 18px;
  transition: color var(--motion-state) var(--motion-ease-state), border-color var(--motion-state) var(--motion-ease-state);
}
.front-tabs__tab:hover { color: var(--pri); }
.front-tabs__tab.is-active { color: var(--pri); border-bottom-color: var(--section-accent-ink); }
.front-tabs__announcements { padding: 8px 0; }

/* ---- Siatka zdjęć (zakładka galerii / widok galerii) ---- */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
}
.photo-grid__item { margin: 0; }
.photo-grid__img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.photo-grid__caption { font-size: 13px; color: var(--muted); margin-top: 5px; line-height: 1.4; }

/* Galeria w treści, wariant siatki RÓWNOMIERNEJ (front.partials.post-gallery-grid,
   reużyty tu — wcześniej BEZ własnego marginesu, korekta odstępów: spójny
   rytm z resztą bloków treści, gdy renderuje się WEWNĄTRZ .rich-text). */
.post-gallery { margin: var(--space-xl) 0; }

/* Kafle galerii z KATEGORII pod treścią podstrony (11.08.2026) — ta sama
   siatka co katalog, tylko z własnym odstępem od treści. */
.page-gallery-tiles { margin: var(--space-xl) 0; }

/* ---- Przejście z wpisu do PEŁNEJ galerii (29.07.2026) ----
   Element DOMYKAJĄCY artykuł, nie odnośnik zgubiony w akapicie: własny blok
   pod zdjęciami, z ikoną, tytułem galerii i liczbą zdjęć. Wygląda jak kafel,
   więc bierze te same tokeny stanów co kafle (`--card-*`) — na pasie ciemnym
   i akcentowym reaguje rozjaśnieniem tła, na jasnym cieniem. Podkreślenia nie
   dodajemy: to nie jest odnośnik w prozie. */
.post-gallery-link { margin: var(--space-xl) 0 0; }
.post-gallery-link__anchor {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  color: inherit;
  text-decoration: none;
  transition: box-shadow var(--motion-state) var(--motion-ease-state), border-color var(--motion-state) var(--motion-ease-state), background-color var(--motion-state) var(--motion-ease-state);
}
.post-gallery-link__anchor:hover {
  box-shadow: var(--shadow-1);
  border-color: var(--card-border-hover);
  background: var(--card-bg-hover);
}
.post-gallery-link__anchor:active {
  background: var(--card-bg-active);
  border-color: var(--card-border-hover);
  box-shadow: none;
}
.post-gallery-link__icon { flex: 0 0 auto; display: inline-flex; color: var(--section-accent-ink); }
.post-gallery-link__icon svg { width: 26px; height: 26px; }
.post-gallery-link__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.post-gallery-link__label { font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.post-gallery-link__title {
  font-family: var(--font-display, inherit);
  font-size: 18px;
  color: var(--card-heading);
  overflow-wrap: anywhere;
}
/* Liczba odsuwa się na koniec wiersza, a przy wąskiej kolumnie schodzi pod
   tytuł razem z nim (kontener jest elastyczny, więc nie trzeba media query). */
.post-gallery-link__count { margin-left: auto; flex: 0 0 auto; font-size: 14px; color: var(--muted); }
@media (max-width: 520px) {
  .post-gallery-link__anchor { flex-wrap: wrap; }
  .post-gallery-link__count { margin-left: 0; width: 100%; }
}

/* ---- Galeria w treści, wariant siatki MASONRY (App\Support\Front\RichContent) ----
   Kolumny CSS (bez JS) — zdjęcia w naturalnych proporcjach, przeglądarka sama
   pakuje kolejne miniatury pod poprzednimi w tej samej kolumnie. */
.photo-masonry { columns: 4; column-gap: 12px; margin: var(--space-xl) 0; }
.photo-masonry__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  break-inside: avoid;
  margin-bottom: 12px;
}
.photo-masonry__img { display: block; width: 100%; height: auto; border-radius: var(--radius-md); background: var(--surface-2); border: 1px solid var(--border); }
@media (max-width: 900px) { .photo-masonry { columns: 3; } }
@media (max-width: 600px) { .photo-masonry { columns: 2; } }

/* ---- Galeria w treści, wariant siatki "GAZETKA"/kolaż (App\Support\Front\RichContent) ----
   1 duże zdjęcie + 6 mniejszych, wzór powtarza się co 7 pozycji (nth-child
   modulo) — grid-auto-flow:dense domyka luki, gdy ostatnia grupa jest
   niepełna. */
.photo-collage {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 70px;
  grid-auto-flow: dense;
  gap: 10px;
  margin: var(--space-xl) 0;
}
.photo-collage__btn { padding: 0; border: none; background: none; cursor: pointer; overflow: hidden; border-radius: var(--radius-md); }
.photo-collage__img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform var(--motion-state) var(--motion-ease-state); background: var(--surface-2); border: 1px solid var(--border); }
.photo-collage__btn:hover .photo-collage__img { transform: scale(1.02); }
.photo-collage__btn:nth-child(7n+1) { grid-column: span 2; grid-row: span 3; }
.photo-collage__btn:nth-child(7n+2) { grid-row: span 2; }
.photo-collage__btn:nth-child(7n+3) { grid-row: span 2; }
@media (max-width: 600px) {
  .photo-collage { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 90px; }
  .photo-collage__btn:nth-child(7n+1) { grid-column: span 2; grid-row: span 2; }
  .photo-collage__btn:nth-child(7n+2), .photo-collage__btn:nth-child(7n+3) { grid-row: span 1; }
}

/* ---- Galeria w treści, tryb PASEK (App\Support\Front\RichContent) ----
   Rząd małych miniatur przewijany poziomo (CSS scroll-snap) + strzałki na
   końcach (scrollBy, Alpine) + lightbox po kliknięciu (frontLightbox, jak
   siatka) — zgłoszenie Bartka 11.07.2026. */
.photo-strip-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: var(--space-xl) 0;
}
.photo-strip {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 6px;
  -webkit-overflow-scrolling: touch;
  flex: 1;
  min-width: 0;
}
.photo-strip__btn { padding: 0; border: none; background: none; cursor: pointer; display: block; flex: 0 0 auto; }
.photo-strip__img {
  width: 130px;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius-md);
  background: var(--surface-2);
  border: 1px solid var(--border);
  scroll-snap-align: start;
  display: block;
  transition: transform var(--motion-state) var(--motion-ease-state), box-shadow var(--motion-state) var(--motion-ease-state);
}
.photo-strip__btn:hover .photo-strip__img { transform: scale(1.03); box-shadow: var(--shadow-2); }
.photo-strip__arrow {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-circle);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.photo-strip__arrow:hover { background: var(--pri); border-color: var(--pri); color: #FFFFFF; }
@media (max-width: 600px) {
  .photo-strip__img { width: 100px; }
  .photo-strip__arrow { width: 30px; height: 30px; font-size: 16px; }
}

/* =========================== WIDOK WPISU AKTUALNOŚCI =========================== */
/* NAGŁÓWEK WPISU — dwie kolumny: kartka z datą i blok tytułu (10.08.2026).
   Kolumna daty ma szerokość TREŚCI (`auto`), nie stałą — kartka z rocznikiem
   jest wyższa i szersza od kartki bez niego, a sztywna szerokość albo ucinałaby
   rok, albo zostawiała pustkę przy wpisach z bieżącego roku.
   `align-items: start` — kartka trzyma się GÓRNEJ linii tytułu, zamiast
   dryfować do środka przy tytule łamiącym się na trzy wiersze. */
.front-post__head {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--space-sm, 14px);
  margin-bottom: 20px;
}
.front-post__date { padding-top: 6px; }
.front-post__headline { min-width: 0; }

/* Poniżej progu telefonu jedna kolumna — kolejność czytania zostaje ta sama
   (data, tytuł, kategoria), bo taka jest kolejność w kodzie. */
@media (max-width: 640px) {
  .front-post__head { grid-template-columns: 1fr; gap: var(--space-xs, 10px); }
  .front-post__date { padding-top: 0; }
}

/* PLAKIETKA KATEGORII NA STRONIE WPISU — własna, nie ta z kafla.
   Na kaflu plakietka leży NA ZDJĘCIU i dlatego ma półprzezroczyste ciemne tło
   (`.news-stamps__category`); tutaj leży na papierze, więc to samo tło dawało
   szarawy prostokąt bez kontrastu — nieczytelny, zgłoszone 10.08.2026.
   Kolory WYŁĄCZNIE z tokenów szablonu: miękkie złoto jako tło i przyciemniony
   akcent jako napis — ten sam token, którym Klasyk pisze miesiąc na kartce
   z kalendarza i podkreśla aktywną pozycję menu. Jasne złoto (`--sec`) miałoby
   na tym tle 2,6–3,1:1, czyli poniżej progu. */
.front-post__category {
  display: inline-block;
  margin-top: 10px;
  padding: 3px 9px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--sec-strong, var(--sec));
  background: var(--sec-soft, var(--surface-2));
  border: 1px solid var(--sec-strong, var(--sec));
  border-radius: var(--radius-sm);
}
.front-post__category:hover { color: var(--surface); background: var(--sec-strong, var(--sec)); }
.front-post__title {
  font-family: var(--font-serif);
  font-size: clamp(30px, 4vw, 46px);
  font-weight: 600;
  color: var(--pri);
  margin: 0;
  line-height: 1.14;
  text-wrap: pretty;
}
.front-post__subtitle { font-size: 19px; color: var(--ink-3); margin: 12px 0 0; line-height: 1.5; }
.front-post__byline {
  margin-top: 12px;
  font-size: 14px;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-style: italic;
}
.front-post__content { font-size: 17px; margin-top: 20px; }
/* WIERSZ POWROTU — jedyny wiersz akcji wyrównany do LEWEJ (02.08.2026).
   Powrót prowadzi wstecz, a nie dalej, więc nie domyka bloku od prawej strony,
   tylko otwiera drogę odwrotu od lewej. `justify-content: flex-start` zapisane
   JAWNIE, żeby strażnik wyrównania widział decyzję, a nie brak reguły. */
.front-post__foot { display: flex; justify-content: flex-start; margin-top: 32px; padding-top: 20px; border-top: 1px solid var(--border); }

/* Nagłówek strony kategorii */
.news-cat-head { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; margin-bottom: 26px; }
.news-cat-head__img {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: var(--radius-xl);
  border: 1px solid var(--border);
  flex: 0 0 auto;
}
.news-cat-head__desc { font-size: 16px; color: var(--ink-3); margin: 8px 0 0; max-width: 60ch; }

/* Karuzela zdjęć nad wpisem (i "slider" bloku galerii w treści — korekta
   odstępów: margines token zamiast pojedynczej wartości px, żeby rytm
   pasował do reszty bloków treści, gdy karuzela renderuje się WEWNĄTRZ
   .rich-text). */
.post-carousel { position: relative; margin: var(--space-xl) 0; }
.post-carousel__viewport { overflow: hidden; border: 1px solid var(--border); background: #000; }
.post-carousel__track { display: flex; transition: transform 0.5s ease; }
.post-carousel__slide { flex: 0 0 100%; min-width: 0; margin: 0; position: relative; }
.post-carousel__img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; }
.post-carousel__caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 10px 16px;
  background: linear-gradient(180deg, rgba(20,12,10,0) 0%, rgba(20,12,10,0.72) 100%);
  color: #FFFFFF;
  font-size: 14px;
}
.post-carousel__dots { margin-top: 12px; }

/* Prosty lightbox */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-zdjecie);
  background: rgba(15, 10, 8, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.lightbox__img { max-width: 92vw; max-height: 88vh; object-fit: contain; box-shadow: 0 10px 40px rgba(0,0,0,0.5); }
.lightbox__close {
  position: absolute; top: 16px; right: 20px;
  width: 46px; height: 46px; border-radius: var(--radius-circle);
  border: none; cursor: pointer;
  background: rgba(255,255,255,0.12); color: #FFFFFF; font-size: 26px;
  display: flex; align-items: center; justify-content: center;
}
.lightbox__close:hover { background: rgba(255,255,255,0.24); }
.lightbox__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 52px; height: 52px; border-radius: var(--radius-circle);
  border: none; cursor: pointer;
  background: rgba(255,255,255,0.12); color: #FFFFFF; font-size: 28px;
  display: flex; align-items: center; justify-content: center;
}
.lightbox__nav:hover { background: rgba(255,255,255,0.24); }
.lightbox__nav--prev { left: 16px; }
.lightbox__nav--next { right: 16px; }
/* KTÓRE TO ZDJĘCIE Z ILU (29.07.2026). Pod fotografią, nie na niej — na zdjęciu
   ginęło na jasnych kadrach, a nakładka ma i tak własne, zawsze ciemne tło.
   Wpis na białej liście SectionScopeLiteralColoursTest: nakładka jest osobnym
   elementem poza zakresem sekcji, więc biel jest tu poprawna niezależnie od palety. */
.lightbox__counter {
  position: absolute;
  bottom: 18px;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  padding: 5px 12px;
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.12);
  color: #FFFFFF;
  font-size: 14px;
  letter-spacing: 0.02em;
}
/* Fokus klawiatury WEWNĄTRZ nakładki: globalna reguła fokusu używa
   `--section-focus`, którego tu nie ma (nakładka stoi poza zakresem sekcji),
   więc obrys byłby liczony z wartości zapasowej i na czerni bywał niewidoczny. */
.lightbox button:focus-visible { outline: 3px solid #FFFFFF; outline-offset: 3px; }
.photo-grid__btn { padding: 0; border: none; background: none; cursor: pointer; display: block; }
/* Autor pojedynczego zdjęcia — pokazywany tylko wtedy, gdy różni się od autora
   całej galerii (ten stoi w nagłówku). Brak autora nie zostawia pustego miejsca,
   bo element w ogóle się nie renderuje. */
.photo-grid__author { display: block; margin-top: 4px; font-size: .78rem; color: var(--muted, #6b6b6b); text-align: left; }
.photo-grid__btn .photo-grid__img { transition: transform var(--motion-state) var(--motion-ease-state), box-shadow var(--motion-state) var(--motion-ease-state); }
.photo-grid__btn:hover .photo-grid__img { transform: scale(1.02); box-shadow: var(--shadow-2); }

/* =========================== OGŁOSZENIA =========================== */
.liturgical-dot {
  display: inline-block;
  width: 11px;
  height: 11px;
  border-radius: var(--radius-circle);
  /* Kolor wypelnienia to KOLOR SZAT — nigdy go nie barwimy paleta. Na mocnych
     tlach dokladamy pierscien w kolorze tekstu pasa (E2), zeby ciemna kropka
     nie ginela; na jasnych pierscien ma zerowa grubosc. */
  box-shadow: 0 0 0 var(--section-dot-ring-width, 0) var(--section-dot-ring, transparent);
  border: 1px solid rgba(0, 0, 0, 0.18);
  vertical-align: middle;
  margin-right: 6px;
}
/* Widoczny opis szat przy kropce („szaty zielone") — dyskretny, dostępnościowy
   (kolor nie jest jedynym nośnikiem informacji). */
.liturgical-dot__text {
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--section-muted);
  font-style: italic;
  white-space: nowrap;
}
.ann-head { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.ann-date { font-size: 16px; color: var(--muted); white-space: nowrap; }
.ann-body { font-size: 17px; }
.ann-current { padding-bottom: 34px; margin-bottom: 34px; border-bottom: 1px solid var(--border); }

.ann-archive__list { list-style: none; margin: 0; padding: 0; }
.ann-archive__row { border-bottom: 1px solid var(--border-3); }
.ann-archive__link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 4px;
  text-decoration: none;
  color: var(--ink-2);
  transition: color var(--motion-state) var(--motion-ease-state);
}
.ann-archive__link:hover { color: var(--pri); }
.ann-archive__date { color: var(--muted); font-variant-numeric: tabular-nums; flex: 0 0 auto; min-width: 42px; }
.ann-archive__name { font-weight: 700; }

/* =========================== WIDOK GALERII =========================== */
.gallery-head { margin-bottom: 24px; }
.gallery-head__meta { display: flex; gap: 16px; flex-wrap: wrap; color: var(--muted); font-size: 15px; margin-top: 8px; }
.gallery-head__author { font-style: italic; }
.gallery-head__desc { font-size: 17px; color: var(--ink-3); margin: 14px 0 0; max-width: 70ch; line-height: 1.6; }

/* ---- „Artykuł:" — wpisy, w których użyto tej galerii (29.07.2026) ----
   Stoi w okolicy daty, pod tytułem galerii (wzorzec ze starego ISP). Odnośnik
   niesie TYTUŁ wpisu, więc jest prozą-podobny: kolor + pogrubienie, bez
   podkreślenia w spoczynku (reguła stanów interakcji). */
.gallery-used-in { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; margin-top: 10px; font-size: 15px; }
.gallery-used-in__label { color: var(--muted); }
.gallery-used-in__link {
  color: var(--section-accent-ink);
  font-weight: 700;
  text-decoration: none;
  transition: color var(--motion-state) var(--motion-ease-state);
}
.gallery-used-in__link:hover { text-decoration: underline; text-underline-offset: 0.18em; }
/* Kilka wpisów: krótka lista, nie moduł. Znaczniki wyliczenia zbędne — etykieta
   „Artykuły:" stoi obok i już to mówi. */
.gallery-used-in__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }

/* Galeria wideo — osadzenia 16:9 */
.video-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 26px; }
.video-item { margin: 0; }
.video-item__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  border: 1px solid var(--border);
  overflow: hidden;
}
.video-item__frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* Fasada wewnątrz kafla playlisty (reorganizacja 12.07.2026) — .video-embed ma
   własny aspect-ratio dla strony pojedynczego filmu; w kaflu playlisty wymiar
   nadaje .video-item__frame, więc fasada wypełnia go absolutnie. */
.video-item__frame .video-embed { position: absolute; inset: 0; aspect-ratio: auto; border: 0; }
.video-embed__play--sm { width: 56px; height: 56px; border-width: 2px; }
.video-item__duration {
  position: absolute;
  right: 8px;
  bottom: 8px;
  padding: 2px 8px;
  font-size: 13px;
  font-weight: 600;
  color: #FFFFFF;
  background: rgba(20, 12, 10, 0.75);
  border-radius: var(--radius-md);
}
.video-item__error {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: rgba(255,255,255,0.75); font-size: 14px; text-align: center; padding: 16px;
}
.video-item__caption { margin-top: 10px; }
.video-item__title { font-family: var(--font-serif); font-size: 19px; font-weight: 600; color: var(--ink); }
.video-item__desc { font-size: 15px; color: var(--ink-3); margin-top: 4px; }

/* Galeria audio — natywny player HTML5 */
.audio-list { display: flex; flex-direction: column; gap: 16px; }
.audio-item { background: var(--surface); border: 1px solid var(--border); padding: 16px 18px; }
.audio-item__title { font-weight: 700; color: var(--ink); margin-bottom: 10px; }
.audio-item__player { width: 100%; }

/* =========================== PAGINACJA =========================== */
.front-pagination-wrap { margin-top: 34px; display: flex; justify-content: center; }
.front-pagination { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.front-pagination__page, .front-pagination__btn {
  min-width: 40px; height: 40px; padding: 0 10px;
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none;
  border: 1px solid var(--border);
  color: var(--ink-2);
  font-weight: 700;
  font-size: 15px;
  background: var(--surface);
  transition: border-color var(--motion-state) var(--motion-ease-state), color var(--motion-state) var(--motion-ease-state), background var(--motion-state) var(--motion-ease-state);
}
.front-pagination__page:hover, .front-pagination__btn:not(.is-disabled):hover { border-color: var(--sec); color: var(--pri); }
.front-pagination__page.is-active { background: var(--pri); border-color: var(--pri); color: #FFFFFF; }
.front-pagination__btn.is-disabled { color: #C9BFB0; cursor: default; }
.front-pagination__gap { padding: 0 6px; color: var(--muted); }

/* =========================== PORZĄDEK MSZY (wspólny partial) =========================== */
/* Notatka to rich-text (frontRichText → akapity) — zbij marginesy <p>, żeby
   zachowała rytm drobnej adnotacji zamiast domyślnych odstępów przeglądarki. */
.mass-schedule__notes :is(p, ul, ol) { margin: 0 0 4px; }
.mass-schedule__notes > :last-child { margin-bottom: 0; }
.mass-schedule--footer .mass-schedule__notes { border-top-color: var(--footer-hair); color: var(--footer-ink-dim); }

/* =========================== SEKCJE OGÓLNE =========================== */
/* Tło i kolor bierze KAŻDA sekcja (pasy strony głównej ORAZ treść podstron);
   RYTM i pełną szerokość dostają WYŁĄCZNIE pasy strony głównej (`<section
   class="front-section">`), nie treść podstron (`<div>`/`<article>` z tą samą
   klasą) — patrz WYGLAD-STRONY.md → „Pas strony głównej: pełna szerokość,
   treść w kontenerze". Podstrony zachowują dawny rytm marginesowy niżej. */
.front-section { margin: 44px 0; }
.front-section:first-child { margin-top: 0; }

/* PAS STRONY GŁÓWNEJ (tylko element <section>): tło na CAŁĄ szerokość okna,
   treść wyśrodkowana do kontenera przez padding poziomy (technika „full-bleed
   bez dodatkowego diva"). container-type = element OGRANICZAJĄCY TREŚĆ, więc
   siatki wewnątrz pytają o szerokość TREŚCI (kontenera/komórki), nie okna —
   zwijają się według KONTENERA, nie ekranu (23.07.2026). --section-content
   steruje szerokością treści — JEDNA dla wszystkich pasów (--tresc-max). */
section.front-section {
  --section-content: var(--tresc-max);
  margin: 0;
  padding-block: var(--band-space);
  padding-inline: var(--tresc-wciecie);
  container-type: inline-size;
}
/* `--wide` NIE zmienia już szerokości (jedna siatka) — zostaje jako znacznik
   dla widoków, które go używają, żeby nie przepisywać kilkunastu plików. */
section.front-section--wide { --section-content: var(--tresc-max); }

.front-section--band {
  background: var(--card-bg);
  border-top: 3px solid var(--section-accent);
}

/* BLOK PASA TRÓJDZIELNEGO (31.07.2026) — jeden wygląd dla całej trójki.
   Do tej pory tylko Ogłoszenia były kartą (padding 26 px + włosowa linia u góry),
   a Ewangelia i Wiadomości miały padding ZERO — bloki nie wyglądały jak
   rodzeństwo, mimo wspólnego układu nagłówka.

   Tło zostaje z ZAKRESU PASA, nie z karty: ton każdego bloku jest ustawieniem
   parafii (naprzemienne tony to domyślne układu #1), więc nie wolno go tu
   nadpisać — inaczej wszystkie trzy zrobiłyby się białe i różnica tonów by
   zniknęła. Linia u góry idzie akcentem palety, tak jak w Ogłoszeniach. */
.home-row__cell--third .front-section {
  border-top: 3px solid var(--section-accent);
}
/* NAGŁÓWEK BLOKU = nadtytuł + tytuł jako JEDNA całość (31.07.2026). Wyrównujemy
   odstęp pod nadtytułem dla wszystkich trzech bloków, a nie sam tytuł jednego
   z nich: tytuł ogłoszeń stał 12 px niżej, bo jego widok miał ten margines
   wpisany na sztywno, a sąsiedzi zero. Dzięki wspólnej regule wyrównanie działa
   też wtedy, gdy któryś tytuł zawinie się na dwa wiersze. */
.home-row__cell .kicker { margin-bottom: 8px; }
/* Ogłoszenia nie potrzebują już własnego wariantu karty w tym pasie —
   wygląd niesie reguła wyżej, a tło ton pasa. */
.home-row__cell--third .front-section--band { background: var(--section-bg, var(--card-bg)); }

/* Pas W STREFIE DWUKOLUMNOWEJ (kolumna główna obok menu bocznego) NIE jest
   pełnoszerokościowy — mieszka w kolumnie, którą już wcina .front-body__inner.
   Rytm marginesowy zamiast band-space (pasy w wąskiej kolumnie potrzebują
   mniej oddechu niż pełnoszerokościowe); padding poziomy (24px z formuły bazowej)
   zostaje jako wcięcie karty. container-type z bazy działa dalej → siatki w
   strefie też zwijają się według szerokości kolumny. */
.front-main section.front-section {
  padding-block: 0;
  margin-block: 28px;
}
.front-main section.front-section:first-child { margin-top: 0; }
.front-main section.front-section:last-child { margin-bottom: 0; }
/* PAS Z TŁEM W STREFIE ODDYCHA TAKŻE W PIONIE (13.09.2026). Zerowy odstęp
   pionowy wyżej jest dobry dla pasa przezroczystego — tam rytm robi margines.
   Pas z tłem ma jednak widoczne krawędzie, więc przy zerze nagłówek stał −1 px
   od górnej, a przycisk wychodził poza dolną. Odstęp = boczny (`--tresc-pad`).
   Rozpoznanie „ma tło" po zmiennej, którą wstawia na korzeń WYŁĄCZNIE wariant
   z tłem (App\Support\Front\SectionBackgrounds; „jasne" i „wzór" jej nie mają)
   ALBO pas-karta (`.front-section--band`, Ogłoszenia: własne tło karty bez tej
   zmiennej — tekst stał 2 px pod górną krawędzią).
   Poza regułą: pas Informacji (tło maluje karta w środku, korzeń jest
   przezroczysty — patrz `.front-main .front-quick`) i pas przyklejony. */
.front-main section.front-section:is([style*="--section-bg"], .front-section--band):not(.front-quick):not(.front-section--flush) {
  padding-block: var(--tresc-pad);
}
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 24px;
}
/* Link „więcej / wszystkie…" zawsze w PRAWYM DOLNYM rogu widgetu (przeniesiony
   z nagłówka sekcji). Spójnie dla wszystkich widgetów strony głównej. */
.section-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 24px;
}

/* ---- Ogłoszenia (skrót na stronie głównej) ---- */
.front-ann__meta { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }
.front-ann__date { font-size: 16px; color: var(--section-muted); }
.front-ann__excerpt {
  font-size: 16px;
  color: var(--section-text-soft, var(--ink-2));
  line-height: 1.6;
  /* REGUŁA 3 (31.07.2026): treść ogłoszenia to PROZA — dzielenie jej na kolumny
     rozbiłoby numerację punktów, więc zamiast tego ograniczamy długość wiersza.
     Blok zostaje przy lewej krawędzi (nie centrujemy), bo nagłówek, data i
     przyciski też tam stoją — wyśrodkowany akapit zerwałby oś bloku. */
  max-width: var(--proza-max);
}
.front-ann__excerpt :is(p, ul, ol) { margin: 0 0 10px; }
.front-ann__excerpt :last-child { margin-bottom: 0; }
/* RZĄD AKCJI DOMYKAJĄCY BLOK — do PRAWEJ (01.08.2026, rozszerzenie reguły).
   Reguła „przycisk domykający stoi po prawej" obowiązuje tak samo dla rzędu
   dwóch czy trzech przycisków, jak dla pojedynczego — dotąd rząd stał po lewej
   (`space-between` rozpychało go na całą szerokość bloku). */
.front-ann__actions { display: flex; gap: 10px 16px; flex-wrap: wrap; margin-top: 24px; align-items: center; justify-content: flex-end; }

/* ---- Ogłoszenia — tryb KOMPAKT (myślany pod wąską kolumnę ⅓, pas trójdzielny:
   skrót ogłoszeń | Ewangelia dnia | feed katolicki) — flex + min-width:0, żaden
   fragment nie ma stałej szerokości, więc rząd zwęża się bez przelewania treści. */
.front-ann-compact__heading { font-family: var(--font-serif, inherit); font-size: 20px; font-weight: 600; color: var(--section-text); margin: 0 0 16px; }
.front-ann-compact__row { display: flex; align-items: flex-start; gap: 14px; text-decoration: none; color: inherit; min-width: 0; }
.front-ann-compact__icon { flex: 0 0 auto; width: 22px; height: 22px; color: var(--section-accent); margin-top: 2px; }
.front-ann-compact__body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.front-ann-compact__title { font-weight: 700; color: var(--section-text); line-height: 1.4; }
.front-ann-compact__row:hover .front-ann-compact__title { color: var(--section-accent-ink); }
.front-ann-compact__date { font-size: 14px; color: var(--section-muted); display: inline-flex; align-items: center; gap: 6px; }

/* =========================== AKTUALNOŚCI =========================== */
.news-featured {
  text-decoration: none;
  color: inherit;
  display: block;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  margin-bottom: 22px;
  transition: box-shadow var(--motion-state) var(--motion-ease-state);
}
.news-featured:hover { box-shadow: var(--shadow-1); }
.news-featured__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); height: 100%; }
/* Wpis BEZ przypisanego zdjęcia nie dostaje pustej kolumny — treść idzie na całą
   szerokość pasa (przegląd 25.07.2026). Brak zdjęcia to stan normalny, więc nie
   ma prawa wyglądać jak nieudane ładowanie. */
.news-featured--textonly .news-featured__grid { grid-template-columns: 1fr; }
.news-featured__media { position: relative; min-height: 220px; }
.news-featured__img {
  width: 100%;
  height: 100%;
  min-height: 220px;
  object-fit: cover;
  display: block;
  background: var(--card-bg-alt);
}
.news-featured__body {
  padding: 26px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  justify-content: center;
}
/* Odnośnik „Czytaj więcej →" kończy blok OD PRAWEJ — czyta się jako domknięcie
   kafla, a nie jako kolejny wers treści. */
.news-featured__more { align-self: flex-end; }
.news-featured__title {
  font-family: var(--font-serif);
  font-size: clamp(24px, 2.6vw, 30px);
  font-weight: 600;
  color: var(--card-text);
  margin: 0;
  line-height: 1.2;
  text-wrap: pretty;
}
/* REGUŁA 3: wpis wyróżniony BEZ zdjęcia rozkłada treść na całą szerokość karty
   (to celowe — pusta kolumna po zdjęciu wyglądałaby jak błąd ładowania), więc
   zajawka musi sama pilnować długości wiersza. Ze zdjęciem karta i tak dzieli
   się na pół i limit nie zadziała. */
.news-featured__excerpt { margin: 0; font-size: 16px; color: var(--card-muted); max-width: var(--proza-max); }

/* ---- PLAKIETKI WPISU: kategoria + data (przegląd Aktualności, 25.07.2026) ----
   Jeden zestaw reguł dla obu miejsc, w których się pojawiają. Różni je wyłącznie
   kontekst pozycjonowania, nie wygląd samych plakietek:
     --over   leżą NA ZDJĘCIU: kategoria w lewym górnym rogu, data w prawym dolnym;
     --inline wiersz NAD TYTUŁEM, gdy wpis nie ma zdjęcia (kategoria z lewej,
              data z prawej — ten sam podział stron co na zdjęciu).
   `margin-left: auto` przy dacie trzyma ją przy prawej krawędzi także wtedy,
   gdy wpis nie ma kategorii i data jest jedyną plakietką. */
.news-stamps { display: flex; align-items: flex-start; gap: var(--space-xs); }
.news-stamps--over {
  position: absolute;
  inset: 10px;
  pointer-events: none;
  z-index: var(--z-plakietka);
}
.news-stamps--over .news-date { align-self: flex-end; }
.news-stamps .news-date { margin-left: auto; }
.news-stamps--inline { align-items: center; margin-bottom: 2px; }

/* KATEGORIA JEST PODPISEM, NIE NAGŁÓWKIEM (runda 2, 25.07.2026).
   Wspólna `.cat-badge` jest zestrojona pod kafle Galerii i Multimediów, gdzie
   plakietka niesie TYP zbioru i ma prawo być mocna. W Aktualnościach ta sama
   waga ustawiała hierarchię na głowie: ciemny prostokąt przyciągał wzrok
   bardziej niż tytuł wpisu. Wyciszamy ją TYLKO tutaj — reszta modułów nie była
   przedmiotem przeglądu i nie zmieniamy jej przy okazji.

   Trzy dźwignie naraz, bo sama zmiana koloru albo sam mniejszy stopień pisma
   nie wystarczały: mniejsze i lżejsze pismo, ciaśniejsza plakietka (mniejsza
   plama) i bardziej przezroczyste tło (słabszy kontrast prostokąta wobec
   zdjęcia). Krycie 0,58 to granica: przy niej biały napis ma na CAŁKOWICIE
   BIAŁYM zdjęciu 4,74:1, czyli wciąż powyżej progu AA — niżej zejść nie można.

   Selektor CELOWO dwuczłonowy: sama `.news-stamps__category` ma tę samą
   specyficzność co `.cat-badge`, a leży w arkuszu WCZEŚNIEJ, więc przegrywała
   kolejnością i nic nie robiła (sprawdzone na ekranie — plakietka dalej miała
   13 px i krycie 0,7). */
/* Na stronie wpisu plakietka jest ODNOŚNIKIEM do kategorii — ma wyglądać
   dokładnie tak jak napis na kaflu, więc bez podkreślenia (podkreślenie
   zostawiamy najechaniu). */
a.news-stamps__category { text-decoration: none; }
a.news-stamps__category:hover { text-decoration: underline; }

.news-stamps .news-stamps__category {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  padding: 2px 6px;
  background: rgba(20, 12, 10, 0.58);
}

/* Data w formie KARTKI Z KALENDARZA. Plakietka niesie WŁASNE, nieprzezroczyste
   tło, więc jest czytelna niezależnie od tego, czy leży na zdjęciu jasnym, czy
   ciemnym — i nie zmienia się razem z tłem pasa (na ciemnym pasie biała kartka
   na ciemnej karcie wygląda tak samo poprawnie jak na zdjęciu). Kolory pochodzą
   z palety: papier = powierzchnia, cyfra = kolor tekstu, miesiąc i rok =
   przyciemniony akcent (jasne złoto/srebro miałoby na bieli 2,6–3,1:1). */
.news-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 46px;
  padding: 5px 7px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  line-height: 1;
  text-align: center;
  box-shadow: var(--shadow-stamp);
}
.news-date__day { font-family: var(--font-serif); font-size: 24px; font-weight: 700; }
.news-date__month {
  margin-top: 3px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sec-strong, var(--sec));
}
/* Rok pojawia się TYLKO przy wpisie z innego roku niż bieżący (patrz
   front/partials/news-stamps.blade.php) — stąd osobny, drobniejszy wiersz. */
.news-date__year {
  margin-top: 2px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--ink-3);
}

/* Tekst wyłącznie dla czytników ekranu — pełna data obok jej wizualnego
   rozbicia na dzień/miesiąc/rok. */
.front-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Treść bogata (WYSIWYG/TipTap) w kartach — np. godziny biura na stronie głównej. */
.rich-text { font-size: 16px; color: var(--section-text-soft, var(--ink-2)); line-height: 1.6; }
.rich-text :is(p, ul, ol) { margin: 0 0 10px; }
.rich-text :is(ul, ol) { padding-left: 20px; }
.rich-text > :last-child { margin-bottom: 0; }
.rich-text strong { color: var(--section-text); }
.rich-text a { color: var(--section-link); font-weight: 700; }
/* Domknięcie floatów bloków zdjęcia — oblewane zdjęcie nie może wystawać poza treść. */
.rich-text::after { content: ""; display: table; clear: both; }

/* ---- Blok "zdjęcie w treści" (edytor TipTap, figure.content-image) ----
   Układy: pełna szerokość (domyślny), oblewanie tekstem z lewej/prawej
   (max ~45% szerokości treści). Podpis (figcaption) drobny, przygaszony.
   Goły <img> bez figure = starsze treści sprzed bloków — tylko dopasowanie
   do szerokości, bez układów.
   KOREKTA ODSTĘPÓW (zgłoszenie Bartka) — przy okazji naprawiona PUŁAPKA
   specyficzności CSS: `.rich-text figure.content-image` (2 klasy + typ
   figure = specyficzność 0,0,2,1) miało WYŻSZĄ specyficzność niż
   `.rich-text .content-image--left/--right` (2 klasy, bez typu = 0,0,2,0),
   więc reguła bazowa ZAWSZE wygrywała i asymetryczne marginesy oblewania
   (odstęp po stronie tekstu / pod zdjęciem) faktycznie NIGDY się nie
   stosowały — renderował się tylko margines bazowy jednolity. Warianty
   niżej mają teraz też `figure`, żeby wygrywały z regułą bazową (ta sama
   specyficzność + późniejsza pozycja w pliku = wygrywa). Wszystkie
   marginesy przez tokeny --space-* (:root wyżej). */
.rich-text img { max-width: 100%; height: auto; }
.rich-text figure.content-image { margin: var(--space-xl) 0; }
.rich-text figure.content-image img { display: block; width: 100%; height: auto; }
.rich-text figure.content-image figcaption {
  font-size: 13px;
  color: var(--section-muted);
  line-height: 1.5;
  margin-top: var(--space-sm);
}
.rich-text figure.content-image--left { float: left; width: min(45%, 420px); margin: 0 var(--space-lg) var(--space-md) 0; }
.rich-text figure.content-image--right { float: right; width: min(45%, 420px); margin: 0 0 var(--space-md) var(--space-lg); }
.rich-text figure.content-image--full { float: none; width: 100%; margin: var(--space-xl) 0; }

/* Mobile: oblewanie wyłącza się, zdjęcie idzie na pełną szerokość. */
@media (max-width: 900px) {
  .rich-text figure.content-image--left,
  .rich-text figure.content-image--right { float: none; width: 100%; margin: var(--space-xl) 0; }
}

.news-grid {
  display: grid;
  /* REGUŁA 1+2: stałe 220 px dawało przy pełnej szerokości PIĘĆ kafli po ~230 px
     — miniatura schodziła wtedy poniżej rozmiaru, w którym cokolwiek widać.
        pełny  1272 cqi → 22cqi = 280 → 280 px → 4 kafle po ~306
        połowa  596 cqi → 22cqi = 131 → 220 px → 2 kafle
        ⅓       356 cqi →               220 px → 1 kafel */
  /* SZEROKOŚĆ BAZOWA decyduje o LICZBIE kafli w rzędzie, `--kafel-max`
     o tym, jak szeroki może być pojedynczy. 26cqi daje TRZY kafle na pełnej
     szerokości (1320 px treści → 424 px kafel) i trzy w kolumnie głównej
     (930 → 294 px); przy 23.5cqi wychodziły cztery, a polskie tytuły łamały
     się w nich na trzy wiersze. Limit 430 px = szerokość, przy której trzy
     kafle wypełniają najszerszy możliwy pas — powyżej niej nie ma czego
     wypełniać, więc kafel dalej nie rośnie. */
  --kafel: clamp(220px, 26cqi, 340px);
  --kafel-max: 430px;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--kafel), 100%), 1fr));
  justify-content: var(--kafle-justify, start);
  gap: 24px;
}
/* ---- Kafel aktualności: pełny komplet stanów (25.07.2026) ----
   Zasady, których pilnują te reguły:
     * ŻADEN stan nie zmienia wymiarów — obwódka zostaje 1 px i zmienia tylko
       kolor, cień i obrys leżą poza pudełkiem, wewnątrz przesuwa się wyłącznie
       odstęp przed strzałką. Sąsiednie kafle stoją nieruchomo.
     * Najechanie mówi TRZEMA sygnałami naraz (obwódka, podkreślenie tytułu,
       odsunięta strzałka), żeby żaden nie musiał być jedyny.
     * Wciśnięcie to jedyny stan działający na telefonie — stąd wyraźna zmiana
       tła, niezależna od tego, czy wcześniej było najechanie.
     * Stan „aktywny" nie dotyczy tego elementu: kafel prowadzi do wpisu,
       nie reprezentuje strony, na której jesteśmy. */
.news-card {
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  transition: box-shadow var(--motion-state) var(--motion-ease-state), border-color var(--motion-state) var(--motion-ease-state), background-color var(--motion-state) var(--motion-ease-state);
}
.news-card:hover {
  box-shadow: var(--shadow-1);
  border-color: var(--card-border-hover);
  background: var(--card-bg-hover);
}
/* PODKREŚLENIE ZDJĘTE 29.07.2026 (odwrócenie decyzji z 25.07). Kafel dostawał
   trzy sygnały naraz, a podkreślenie było z nich najgłośniejsze i najbardziej
   myliło się z odnośnikiem w prozie. W jego miejsce wchodzą dwa spokojniejsze:
   przesunięcie koloru tytułu i delikatne przybliżenie zdjęcia.

   KOLOR IDZIE W STRONĘ BARWY NAGŁÓWKÓW, nie akcentu: akcent znaczy w tym
   serwisie „tu jesteś" (oznaczenie pozycji menu), więc najechanie wyglądałoby
   jak bieżąca strona. `--card-heading` jest już zdefiniowany dla każdego
   wariantu tła, więc kontrast trzyma się sam.

   UWAGA: na pasie ciemnym i akcentowym `--card-heading` i `--card-text` są
   z definicji tym samym (oba to biel), więc przesunięcia koloru tam NIE MA —
   najechanie niosą wtedy pozostałe sygnały: obwódka, rozjaśnienie tła kafla,
   odsunięta strzałka i przybliżenie zdjęcia. */
.news-card:hover .news-card__title { color: var(--card-heading); }
/* Przybliżenie SAMEGO ZDJĘCIA — ramka (`.news-card__media`) przycina nadmiar,
   więc kafel nie zmienia rozmiaru i nie rusza sąsiadów w siatce. Czas i krzywa
   z tokenu zmiany stanu, żeby wszystkie sygnały kafla zaczynały się i kończyły
   razem. */
.news-card__media { overflow: hidden; }
.news-card__img { transition: transform var(--motion-state) var(--motion-ease-state); }
.news-card:hover .news-card__img { transform: scale(1.04); }
/* Strzałka jest częścią NAPISU (tłumaczenie „Czytaj więcej →"), więc bierze
   kolor tekstu sama z siebie — nigdy nie zostaje w innym kolorze niż napis.
   Odsuwamy ją odstępem MIĘDZY SŁOWAMI: napis zostaje w jednej linii, a kafel
   nie zmienia wymiarów. */
.news-card__more { transition: word-spacing var(--motion-state) var(--motion-ease-state); }
.news-card:hover .news-card__more { word-spacing: 5px; }
.news-card:active {
  background: var(--card-bg-active);
  border-color: var(--card-border-hover);
  box-shadow: none;
}
.news-card__media { position: relative; }
.news-card__img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  background: var(--card-bg-alt);
}
.news-card__body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 7px; flex: 1; }
.news-card__title {
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 600;
  color: var(--card-text);
  margin: 0;
  line-height: 1.25;
  text-wrap: pretty;
}
/* Podtytuł kafla — druga linia KAPITALIKAMI pod tytułem (23.07.2026, jak na
   stronach referencyjnych). Renderuje się TYLKO gdy wpis ma podtytuł, więc kafel
   bez niego wygląda jak dawniej; równą wysokość w rzędzie daje siatka (stretch).
   Wspólna klasa dla kafla, wyróżnionego i archiwum, w obu szablonach. */
/* PODTYTUŁ TO ZDANIE, NIE ETYKIETA (08.08.2026).
   Rola „label" (12 px, waga 700, wersaliki, rozstrzelenie) jest w tym systemie
   przeznaczona na KRÓTKI nadkreślnik. Podtytuł kafla bywa pełnym zdaniem —
   na danych demo 94 znaki — a wersaliki odbierają słowom kształt (ascendery
   i descendery), po którym je rozpoznajemy, więc akurat tekst decydujący
   o kliknięciu czytało się najwolniej z całego kafla.

   Zostaje wyróżnienie kolorem akcentu i wagą; znika sama forma etykiety.
   Wersaliki na krótkiej etykiecie są dalej w porządku — patrz `.kicker`. */
.news-card__subtitle {
  margin: 6px 0 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--section-accent-ink);
  line-height: 1.45;
}
/* zajawka na kaflu (widget Aktualności, tryb kafla „+ zajawka") */
.news-card__excerpt {
  margin: 2px 0 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--card-muted);
  text-wrap: pretty;
}
/* „Czytaj więcej →" domyka kafel OD PRAWEJ (25.07.2026) — ta sama zasada co
   w kaflu wyróżnionym, żeby oba czytały się tak samo. `margin-top: auto`
   dosuwa go do dołu (równa linia w rzędzie kafli), `align-self` do prawej. */
.news-card__more { margin-top: auto; align-self: flex-end; text-decoration: none; color: var(--card-link); font-weight: 700; font-size: 16px; }

/* wyróżnione „przypięte na górze" — kafle jeden pod drugim (widget Aktualności) */
.news-featured-pinned { display: flex; flex-direction: column; gap: 18px; }

/* =========================== WIDGET „KARTY-LINKI" =========================== */
/* Siatka: liczba kolumn = liczbie kart (1-3, egzekwowane w panelu) — układ jest
   sprawą SZABLONU, nie ustawień parafii. */
/* Karty-linki — zwijanie według KONTENERA (23.07.2026).
   Siatka sama tworzy tyle kolumn, ile kart mieści szerokość TREŚCI pasa,
   i schodzi do jednej w wąskiej kolumnie ⅓ — bez @media na szerokość ekranu
   (dawne `repeat(2/3,1fr)` + @media 900 nie łapały widgetu w wąskiej kolumnie
   na szerokim monitorze). Wariant „1 karta" dostaje sufit szerokości, żeby
   pojedyncza karta nie rozjechała się na całą treść. */
.cards-links-grid {
  display: grid;
  gap: 24px;
  --kafel: clamp(260px, 23.5cqi, 320px);
  --kafel-max: 430px;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--kafel), 100%), 1fr));
  justify-content: var(--kafle-justify, start);
}
/* `.cards-links-grid--1` USUNIĘTE (02.08.2026). Był to LOKALNY plaster na ten
   sam problem, który rozwiązuje teraz `auto-fill` w całym froncie: „żeby
   pojedyncza karta nie rozjechała się na całą treść". Wyśrodkowanie było w nim
   skutkiem ubocznym (`margin: 0 auto`), nie decyzją — a od dziś pojedynczy kafel
   staje przy lewej krawędzi, tak jak w każdej innej siatce. Klasa i tak nie była
   nadawana przez żaden widok (sprawdzone). */

.card-link {
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  display: flex;
  flex-direction: column;
  text-decoration: none;
  transition: box-shadow var(--motion-state) var(--motion-ease-state);
}
.card-link { position: relative; }
.card-link:hover { box-shadow: var(--shadow-1); }
/* WARSTWA KLIKALNA rozciągnięta z odnośnika „więcej" na cały kafel. Kafel ma
   `position: relative`, więc warstwa obejmuje dokładnie jego pudełko.
   ZNANY KOSZT tej techniki: warstwa leży nad tekstem, więc przeciągnięcie
   myszą po opisie zaczyna się na odnośniku i nie zaznacza. Sprawdzone
   i odnotowane — patrz raport z 29.07.2026. */
.card-link__more::after {
  content: "";
  position: absolute;
  inset: 0;
}
.card-link__image { overflow: hidden; }
.card-link__image img,
.card-link__img-empty {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  background: var(--card-bg-alt);
}
/* Przybliżenie zdjęcia — te same zasady co na kaflu aktualności: skalowane
   zdjęcie, kadr przycina, czas i krzywa z tokenu zmiany stanu. */
.card-link__image img { transition: transform var(--motion-state) var(--motion-ease-state); }
.card-link:hover .card-link__image img { transform: scale(1.04); }
/* Zastępnik trzyma DOKŁADNIE ten sam kadr co zdjęcie (4:3), więc karta bez
   grafiki ma tę samą wysokość co sąsiadki ze zdjęciem. */
.card-link__img-empty { display: flex; }
.card-link__body { padding: 18px 20px 20px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.card-link__heading {
  font-family: var(--font-serif);
  font-size: 21px;
  font-weight: 600;
  color: var(--card-text);
  margin: 0;
  line-height: 1.25;
  text-wrap: pretty;
}
.card-link__subtitle {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--section-accent-ink);
}
.card-link__description { margin: 0; font-size: 15px; line-height: 1.55; color: var(--card-muted); text-wrap: pretty; }
/* Akcja karty-linku: przycisk POMOCNICZY o szerokości TREŚCI, nie całej karty
   (30.07.2026). Rozciągnięty na całą szerokość wyglądał jak główna akcja pasa,
   a w siatce trzech kart dawał trzy wielkie prostokąty obok siebie.

   Ważniejsze: `.card-link__more::after { inset: 0 }` wyżej rozciąga ten odnośnik
   na CAŁĄ kartę — klikalna jest więc karta, nie sam przycisk. Przycisk ma
   pokazywać, że akcja istnieje, a nie udawać granicę celu kliknięcia. */
.card-link__more { margin-top: auto; align-self: flex-start; }
/* (Dawne @media 900 → 1fr USUNIĘTE — siatka zwija się według kontenera.) */

/* etykieta kategorii */
.cat-badge {
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #FFFFFF;
  background: rgba(20, 12, 10, 0.7);
  padding: 3px 8px;
  border-radius: var(--radius-sm);
  font-weight: 700;
}
.cat-badge--float { position: absolute; top: 10px; left: 10px; }

/* Zastępnik zdjęcia (brak grafiki). Kolory WYŁĄCZNIE ze zmiennych zakresu —
   ten sam element pojawia się na pasie jasnym, przygaszonym, ciemnym i
   akcentowym, i w każdej z czterech palet. Wcześniej miał wpisany na stałe
   kremowy gradient: na ciemnym pasie robił jasną plamę, a w paletach innych
   niż kremowa był po prostu obcy (poprawka 18.07.2026).
   Ma wyglądać na ZAMIERZONY, nie na błąd ładowania — stąd spokojna
   powierzchnia karty z ledwie widocznym pociągnięciem akcentu i wyciszonym
   znakiem, a nie ikona „złamanego zdjęcia”. */
.img-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    linear-gradient(135deg, transparent 0%, color-mix(in srgb, var(--section-accent) 12%, transparent) 100%),
    var(--card-bg-alt);
  color: color-mix(in srgb, var(--card-muted) 55%, transparent);
  font-family: var(--font-serif);
  font-size: 40px;
  line-height: 1;
}

/* =========================== GALERIE =========================== */
/* Siatka kafli (Galerie I Multimedia) — zwijanie według KONTENERA, nie ekranu
   (23.07.2026). container-type żyje na section.front-section widgetu, więc te
   @container mierzą szerokość TREŚCI pasa: pełny/½/⅓ dostają właściwą liczbę
   kolumn niezależnie od szerokości okna (dawne @media na szerokość ekranu nie
   łapały widgetu stojącego w wąskiej kolumnie na szerokim monitorze). */
.gallery-grid {
  display: grid;
  /* REGUŁA 1: liczba kolumn wynika z minimalnej szerokości kafla, a nie z trzech
     progów @container (te dawały skok 3→2→1 i nic pomiędzy).
        pełny  1272 cqi → 24cqi = 305 → 305 px → 3 kafle po ~420 (16:9 czytelne)
        połowa  596 cqi → 24cqi = 143 → 220 px → 2 kafle
        ⅓       356 cqi →               220 px → 1 kafel */
  --kafel: clamp(220px, 31cqi, 410px);
  --kafel-max: 430px;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--kafel), 100%), 1fr));
  justify-content: var(--kafle-justify, start);
  gap: 22px;
}
.gallery-tile {
  text-decoration: none;
  color: inherit;
  position: relative;
  display: block;
  overflow: hidden;
  border: 1px solid var(--card-border);
  transition: box-shadow var(--motion-state) var(--motion-ease-state);
}
.gallery-tile:hover { box-shadow: 0 10px 26px rgba(46, 32, 22, 0.16); }
.gallery-tile__img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  background: var(--card-bg-alt);
  transition: transform var(--motion-state) var(--motion-ease-state);
}
/* To samo przybliżenie co na kaflu aktualności — kafel galerii ma już własne
   `overflow: hidden`, więc kadr przycina nadmiar bez dodatkowej ramki. */
.gallery-tile:hover .gallery-tile__img { transform: scale(1.04); }
/* PODPIS NA KADRZE, ODSŁANIANY NAJECHANIEM — decyzja z 30.07.2026.
   Karta albumu z podpisem POD kadrem (Etap 8, 25.07.2026) została WYCOFANA po
   obejrzeniu katalogu na żywo: właściciel uznał podpis na zdjęciu za czytelny,
   a jednakowe kadry dają katalogowi rytm, którego drabinka podpisów nie miała.
   Reguły `.gallery-tile__frame` i `.gallery-tile__caption` usunięto razem z tą
   decyzją — nie były już przez żaden widok używane.
   Pełne uzasadnienie: docs/UI-STYLE.md → „Kafel galerii".

   NA DOTYKU nakładka jest widoczna OD RAZU (@media (hover: hover) niżej):
   najechanie nie może być jedynym sposobem dotarcia do tytułu. */
/* Pierwszy album WIODĄCY — szerszy kadr otwierający katalog, jak strona
   tytułowa. Tylko gdy albumów jest co najmniej czworo: przy dwóch czy trzech
   wyróżnienie jednego robiłoby dziurę w siatce. */
@container (min-width: 861px) {
  .gallery-grid > .gallery-tile:first-child:nth-last-child(n + 4) { grid-column: span 2; }
  .gallery-grid > .gallery-tile:first-child:nth-last-child(n + 4) .gallery-tile__img { aspect-ratio: 16 / 9; }
  .gallery-grid > .gallery-tile:first-child:nth-last-child(n + 4) .gallery-tile__title { font-size: 24px; }
}
/* CIEMNY TYTUŁ NA CIEMNYM GRADIENCIE — 1,00:1 (12.09.2026).

   Ta reguła maluje przyciemnienie pod tekstem, czyli ubranie pod BIAŁY napis,
   a `.gallery-tile__title` niżej ma kolor `--card-text`, czyli niemal czerń.
   Tytuł znikał więc w gradiencie. Zmierzone w renderze
   (tools/audyt-frontu/kontrast.mjs), stan spoczynku, bo te kafle nie mają
   najechania i napis jest widoczny ZAWSZE:

     1,00:1  kafel filmu, /multimedia — Klasyk i Łukasz
     2,75:1  ten sam kafel na białym zdjęciu (Klasyk), 2,27:1 u Łukasza
     1,00:1  kafel galerii pod zakładką podstrony (page-tab-content)

   DLACZEGO BIEL, A NIE ZDJĘCIE GRADIENTU: bez przyciemnienia ciemny tytuł
   siadałby wprost na fotografii i zależał od tego, co akurat jest pod literami
   — czyli byłby nie do zagwarantowania. Kierunek jest ten sam co przy kaflach
   `--cover` naprawionych tego samego dnia: rampa zakotwiczona pod napisem
   (`--tile-scrim`) plus biały tytuł. Różnica jedna: tu nakładka jest widoczna
   zawsze, więc nie ma stanu najechania do odsłonięcia.

   ⚠️ BIEL ZAKRESOWANA DO WNĘTRZA NAKŁADKI, nie do `.gallery-tile__title`.
   Ten sam element bywa podpisem POD kadrem, na tle karty (komentarz niżej,
   25.07.2026) — tam biel byłaby białym tekstem na białej karcie.

   Wariant `--cover` nadpisuje tło, wcięcie i kolor niżej, więc tej zmiany
   nie widzi; punkt odniesienia sprzed zmiany: 5,57–6,40:1 na białym zdjęciu. */
.gallery-tile__overlay {
  --tile-scrim: 44px;
  position: absolute;
  inset: auto 0 0 0;
  background: linear-gradient(180deg,
    rgba(20, 12, 10, 0) 0,
    rgba(20, 12, 10, 0.62) var(--tile-scrim),
    rgba(20, 12, 10, 0.82) 100%);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: calc(var(--tile-scrim) + 2px) 18px 16px;
}
.gallery-tile__overlay .gallery-tile__title { color: #FFFFFF; }
.gallery-tile__overlay .gallery-tile__meta { color: rgba(255, 255, 255, 0.86); }
.gallery-tile__title {
  font-family: var(--font-serif);
  font-size: 21px;
  font-weight: 600;
  /* Podpis stoi teraz POD kadrem, na tle karty — kolor z zakresu sekcji,
     nie biel „na zdjęciu" (25.07.2026, Etap 8). */
  color: var(--card-text, var(--ink));
  margin: 0;
  line-height: 1.2;
}
.gallery-tile__meta { display: block; font-size: 13px; color: var(--card-muted, var(--muted)); margin-top: 4px; }

/* ---------------- TAŚMA OKŁADEK (widget Galerie, 29.07.2026) ----------------

   Poziomy rząd okładek przewijany w bok. Przewijanie jest NATYWNE
   (`overflow-x` + `scroll-snap`) — JavaScript go nie napędza, tylko odczytuje
   położenie i podstawia je pod kropki (public/js/front.js → frontStrip). Bez
   skryptu taśma dalej działa palcem i kółkiem myszy; znika samo sterowanie.

   SĄSIEDNIE OKŁADKI MUSZĄ WYSTAWAĆ PRZY KRAWĘDZI. To nie jest ozdoba: ucięta
   okładka jest jedynym sygnałem, że taśma idzie dalej. Dlatego szerokość liczymy
   z UŁAMKA widocznych okładek (3,3 / 2,3 / 1,25), a nie z okrągłej liczby kolumn.

   Progi są @container, nie @media — widget bywa w wąskiej kolumnie na szerokim
   monitorze i wtedy liczy się szerokość TREŚCI PASA, a nie okna (ta sama zasada
   co siatki wyżej). */
.gallery-strip-wrap {
  --strip-gap: 18px;
  --strip-visible: 3.3;
  position: relative;
}
.gallery-strip {
  display: flex;
  gap: var(--strip-gap);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  /* Zapas na cień i obwódkę okładki: `overflow` ucina wszystko, co wystaje poza
     kadr, więc bez niego najechanie obcinałoby cień przy górnej krawędzi. */
  padding: 4px 4px 14px;
  margin: 0 -4px;
  /* Bez tego taśma startuje przewinięta o szerokość zapasu: punkt zaczepienia
     pierwszej okładki wyrównuje się do krawędzi TREŚCI, więc przeglądarka
     przyciąga ją o te cztery piksele — a wtedy taśma świeżo wczytana nie jest
     „na początku" i strzałka powrotu zapala się, choć nie ma dokąd wracać. */
  scroll-padding-left: 4px;
  /* Pasek przewijania chowamy — położenie pokazują kropki, a systemowy pasek
     pod rzędem zdjęć rozbijał rytm pasa. Przewijanie zostaje w pełni sprawne. */
  scrollbar-width: none;
}
.gallery-strip::-webkit-scrollbar { display: none; }
.gallery-strip > .gallery-tile {
  flex: 0 0 calc((100% - (var(--strip-visible) - 1) * var(--strip-gap)) / var(--strip-visible));
  scroll-snap-align: start;
}
@container (max-width: 860px) { .gallery-strip-wrap { --strip-visible: 2.3; } }
@container (max-width: 520px) { .gallery-strip-wrap { --strip-visible: 1.25; --strip-gap: 12px; } }

/* Okrągłe przyciski przewijania — LEŻĄ NA ZDJĘCIACH (nie na tekście, jak
   w karuzeli aktualności, gdzie właśnie dlatego zostały usunięte). Na krańcu
   taśmy przycisk zostaje na swoim miejscu, ale gaśnie i przestaje być klikalny:
   znikający przycisk przesuwałby wzrok, a `disabled` mówi to samo wprost. */
.gallery-strip__nav {
  position: absolute;
  top: calc(50% - 7px);
  transform: translateY(-50%);
  z-index: var(--z-plakietka);
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-circle);
  border: 1px solid var(--card-border);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--shadow-2);
  transition: background var(--motion-state) var(--motion-ease-state),
              color var(--motion-state) var(--motion-ease-state),
              opacity var(--motion-state) var(--motion-ease-state);
}
.gallery-strip__nav--prev { left: -6px; }
.gallery-strip__nav--next { right: -6px; }
/* NA DOTYKU STRZAŁEK NIE MA. Tam taśmę przesuwa się palcem — to gest naturalny
   i niczego nie trzeba nim trafiać — a przyciski zabierają wąskiej okładce
   kawał widocznego zdjęcia i kładą się na nim nawet wtedy, gdy są wygaszone.
   Kropki zostają: dalej pokazują położenie i dalej są klikalne. */
@media (hover: none) {
  .gallery-strip__nav { display: none; }
}
.gallery-strip__nav:hover { background: var(--section-accent); color: var(--section-accent-ink, var(--ink)); }
.gallery-strip__nav:active { opacity: 0.85; }
.gallery-strip__nav[disabled] { opacity: 0.32; cursor: default; box-shadow: none; }

/* Kropki = STRONY taśmy. Nie ma tu paska odliczania z karuzeli aktualności —
   taśma nie zmienia się sama, więc nie ma czego odliczać. */
.gallery-strip__dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 14px;
}
.gallery-strip__dot {
  width: 26px;
  height: 4px;
  padding: 0;
  border: none;
  border-radius: 2px;
  background: var(--border);
  cursor: pointer;
  transition: background var(--motion-state) var(--motion-ease-state);
}
.gallery-strip__dot:hover { background: var(--card-border-hover); }
.gallery-strip__dot.is-active { background: var(--section-accent-ink, var(--sec-strong, var(--sec))); }
/* Powiększony cel kliknięcia/dotyku (30.07.2026, ustalenie H5 z audytu frontu).
   Sama kreska ma 4 px wysokości — zmierzony cel dotyku wynosił 25×3 px, czyli
   poniżej progu WCAG 2.5.8 (24×24). Warstwa jest PRZEZROCZYSTA, więc forma
   graficzna kreski się nie zmienia; dokładnie ten sam zabieg co przy kreskach
   karuzeli Aktualności, gdzie działa od 25.07.2026. */
.gallery-strip__dot { position: relative; }
.gallery-strip__dot::after {
  content: "";
  position: absolute;
  inset: -12px -4px;
}

/* ---- Okładka w taśmie: podpis NA zdjęciu ----
   Wariant kafla galerii dla taśmy. Kadr jednakowy dla wszystkich (4/3), żeby
   taśma nie skakała na wysokości przy pionowych i poziomych fotografiach. */
.gallery-tile--cover { border-radius: var(--radius-md); }
.gallery-tile--cover .gallery-tile__img { aspect-ratio: 4 / 3; }
/* Liczba zdjęć — widoczna ZAWSZE, biel na przyciemnieniu (leży na fotografii,
   więc kolor nie zależy od tła sekcji; wpis na białej liście
   SectionScopeLiteralColoursTest). */
.gallery-tile--cover .gallery-tile__count {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: var(--z-plakietka);
  padding: 4px 9px;
  border-radius: var(--radius-md);
  background: rgba(20, 12, 10, 0.62);
  color: #FFFFFF;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
/* PRZYCIEMNIENIE ZAKOTWICZONE POD NAPISEM, NIE W WYSOKOŚCI KAFLA (12.09.2026).

   Rampa liczona w PROCENTACH kafla dawała pod literami tyle przyciemnienia, ile
   akurat wynikało z proporcji kafla i z tego, czy tytuł złamał się na dwa
   wiersze. Zmierzone w renderze (tools/audyt-frontu/kontrast.mjs) na taśmie
   strony głównej przy 390 px, gdzie nakładka jest widoczna ZAWSZE, bo na dotyku
   nie ma stanu najechania:

     tytuł jednowierszowy  — góra liter na 87% wysokości kafla
     tytuł dwuwierszowy    — góra liter na 76%, czyli w rampie jeszcze
                             przezroczystej

   i wynikające z tego kontrasty, bez żadnej podmiany, czyli dokładnie to, co
   widzi odwiedzający telefonem:

     2,60:1  „Triduum Paschalne i Wielkanoc"  (bez okładki, dwa wiersze)
     3,64:1  „Remont dachu prezbiterium"      (Z OKŁADKĄ, prawdziwe zdjęcie)
     4,41:1  „Koncert organowy", „Boże Narodzenie"
     2,13:1  najgorszy przypadek — jasna fotografia (niebo, śnieg, białe wnętrze)

   TO NIE BYŁA USTERKA ZASTĘPNIKA OKŁADKI, choć tak wyglądała. Zastępnik jest
   CIEMNIEJSZY od jasnej fotografii, więc w pomiarze wychodził LEPIEJ niż kafel
   ze zdjęciem (5,02 kontra 4,24:1 na desktopie). Przyciemnienie zastępnika ani
   zamiana bieli na kolor z palety nie naprawiłyby ani jednego kafla ze zdjęciem.

   ⚠️ POMIAR, KTÓRY ZGŁOSIŁ 1,25:1, MIERZYŁ NAPIS O KRYCIU 0 — patrz `opacity: 0`
   w `@media (hover: hover)` niżej. Na desktopie w spoczynku tytułu po prostu nie
   ma na ekranie, a `getComputedStyle().color` krycia przodka nie widzi. Ten sam
   kafel z wymuszonym najechaniem: 7,33:1. Mierząc tu kontrast, NAJPIERW sprawdź
   krycie nakładki.

   Teraz nakładka jest przyklejona do DOŁU kafla, a rampa kończy się dokładnie
   tam, gdzie zaczyna się tekst (`--tile-scrim`). Przyciemnienie pod literami nie
   zależy więc ani od wysokości kafla, ani od liczby wierszy tytułu, ani od
   stopnia pisma (kafel wiodący katalogu ma 24 px zamiast 18 px).

   ALFA DOBRANA POMIAROWO, NIE NA OKO — najgorszy kafel, białe zdjęcie:
     0,55 → 4,19:1  (poniżej progu)
     0,62 → 5,33:1
     0,84 → 11,99:1 (niepotrzebnie ciężkie)
   Dolne 0,82 zostaje bez zmian, więc dół kafla wygląda jak dotąd — zmienia się
   tylko to, że rampa dobiega do końca PRZED literami, a nie w ich połowie.

   ⚠️ REGUŁY BAZOWEJ `.gallery-tile__overlay` NIE RUSZAĆ TYM SAMYM RUCHEM. Używa
   jej kafel Multimediów i galeria pod zakładką podstrony, a te mają tytuł
   w kolorze `--card-text` (CIEMNY) na tym samym gradiencie — wzmocnienie
   przyciemnienia pogorszyłoby tamto miejsce. Osobna usterka, osobna decyzja.

   Strażnik: tests/Feature/Front/GalleryTileScrimTest.php */
.gallery-tile--cover .gallery-tile__overlay {
  --tile-scrim: 44px;
  inset: auto 0 0 0;
  background: linear-gradient(180deg,
    rgba(20, 12, 10, 0) 0,
    rgba(20, 12, 10, 0.62) var(--tile-scrim),
    rgba(20, 12, 10, 0.82) 100%);
  padding: calc(var(--tile-scrim) + 2px) 16px 14px;
  transition: opacity var(--motion-state) var(--motion-ease-state);
}
.gallery-tile--cover .gallery-tile__title {
  color: #FFFFFF;
  font-size: 18px;
  display: block;
  transition: transform var(--motion-state) var(--motion-ease-state);
}
/* NA DOTYKU NAKŁADKA JEST WIDOCZNA OD RAZU — chowamy ją wyłącznie tam, gdzie
   istnieje kursor, bo najechanie nie może być jedynym sposobem dotarcia do
   tytułu. Chowamy przezroczystością (nigdy `display`), więc tytuł zostaje
   w drzewie dostępności i czytnik ekranu przeczyta go zawsze. Fokus klawiatury
   odsłania nakładkę tak samo jak kursor. */
@media (hover: hover) {
  .gallery-tile--cover .gallery-tile__overlay { opacity: 0; }
  .gallery-tile--cover .gallery-tile__title { transform: translateY(var(--motion-rise)); }
  .gallery-tile--cover:hover .gallery-tile__overlay,
  .gallery-tile--cover:focus-visible .gallery-tile__overlay { opacity: 1; }
  .gallery-tile--cover:hover .gallery-tile__title,
  .gallery-tile--cover:focus-visible .gallery-tile__title { transform: none; }
}

/* =========================== MULTIMEDIA =========================== */
/* Kafel filmu = gallery-tile + ikona play (środek) i czas trwania (róg). */
.video-tile__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 54px;
  height: 54px;
  border-radius: var(--radius-circle);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #FFFFFF;
  background: rgba(20, 12, 10, 0.55);
  border: 2px solid rgba(255, 255, 255, 0.9);
  transition: background var(--motion-state) var(--motion-ease-state), transform var(--motion-state) var(--motion-ease-state);
  pointer-events: none;
}
.video-tile:hover .video-tile__play { background: var(--section-accent); transform: translate(-50%, -50%) scale(1.06); }
.video-tile__duration {
  position: absolute;
  bottom: 10px;
  right: 10px;
  font-size: 12px;
  font-weight: 700;
  color: #FFFFFF;
  background: rgba(20, 12, 10, 0.78);
  padding: 2px 7px;
  border-radius: var(--radius-sm);
  font-variant-numeric: tabular-nums;
}

/* Filtr kategorii (chipy) */
.mm-filter { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.mm-chip {
  text-decoration: none;
  font-size: 14px;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  transition: background 0.15s ease, color 0.15s ease;
}
.mm-chip:hover { background: #efe7db; }
/* Prefiks `.front-root` nie jest ozdobą: globalna reguła `.front-root a:not(.btn)
   { color: inherit }` ma wyższą specyficzność (0,2,1) niż samo `.mm-chip.is-active`
   (0,2,0) i PRZEBIJAŁA tu kolor tekstu. Aktywny chips wychodził przez to prawie
   czarny na ciemnym tle — 1,9:1, czyli tekst nie do przeczytania. Zmierzone na
   ekranie 28.07.2026 przy dokładaniu rzędu miesięcy, który kopiował ten wzorzec. */
.front-root .mm-chip.is-active { background: var(--pri); color: var(--surface); border-color: var(--pri); }

/* Blok "film w treści" (front.partials.content-video-embed) — odstęp od
   sąsiednich akapitów, spójny z .post-gallery. */
.content-video { margin: var(--space-xl) 0; }

/* Blok "plik w treści" (front.partials.content-file-embed, 13.07.2026) —
   trzy warianty: audio (odtwarzacz, .video-audio-player__el niżej), link
   (zwykły odnośnik) i karta z miniaturą/ikoną (domyślny tryb "Ikonka").
   Plakietki rozszerzenia w UNIWERSALNYCH kolorach (czerwony PDF/niebieski
   Word/zielony Excel) — to sygnały informacyjne, nie element marki, więc
   świadomie POZA paletą bordowo-złotą frontu (ten sam duch co "success"
   Filamenta w panelu, patrz CLAUDE.md). */
.content-file { margin: var(--space-xl) 0; }

.content-file__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--pri);
  font-weight: 700;
  text-decoration: underline;
}
.content-file__link-icon { flex-shrink: 0; color: var(--muted); display: inline-flex; }

.content-file__card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  text-decoration: none;
  transition: border-color .15s, box-shadow .15s;
}
.content-file__card:hover { border-color: var(--section-accent-ink); box-shadow: 0 4px 14px rgba(0,0,0,.06); }
.content-file__card-thumb {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface-2);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
}
.content-file__card-thumb img { width: 100%; height: 100%; object-fit: cover; }
.content-file__card-meta { min-width: 0; }
.content-file__card-title {
  display: block;
  color: var(--ink);
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.content-file__card-sub { display: flex; align-items: center; gap: 6px; margin-top: 3px; }
.content-file__badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .02em;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  color: #FFFFFF;
  background: var(--muted);
}
.content-file__badge--pdf { background: #B5433B; }
.content-file__badge--word { background: #2B5B84; }
.content-file__badge--excel { background: #2F7D4F; }
.content-file__size { font-size: 13px; color: var(--muted); }

/* Osadzenie filmu na stronie filmu — fasada 16:9 (miniatura + play), iframe
   dostawiany po kliknięciu (public/js/front.js → videoFacade). */
.video-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000000;
  overflow: hidden;
  border: 1px solid var(--border);
}
.video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.video-embed__facade {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  cursor: pointer;
  background: #000000;
}
.video-embed__thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.video-embed__thumb--placeholder { background: linear-gradient(135deg, #2a1f1a 0%, #140c0a 100%); }
.video-embed__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 74px;
  height: 74px;
  border-radius: var(--radius-circle);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #FFFFFF;
  background: rgba(20, 12, 10, 0.6);
  border: 3px solid rgba(255, 255, 255, 0.92);
  transition: background var(--motion-state) var(--motion-ease-state), transform var(--motion-state) var(--motion-ease-state);
}
.video-embed__facade:hover .video-embed__play { background: var(--sec); transform: translate(-50%, -50%) scale(1.06); }

/* ====================== BIBLIOTEKI DOKUMENTÓW PDF ======================
   (reorganizacja 12.07.2026, Część 2) — lista dokumentów zbioru
   (/dokumenty/{id}) i strona dokumentu z czytnikiem PDF.js w iframe. */
.doc-list { display: flex; flex-direction: column; gap: 18px; }
.doc-item {
  display: flex;
  gap: 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 16px;
}
.doc-item__thumb {
  flex-shrink: 0;
  width: 96px;
  height: 128px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #F3EEE5;
  border: 1px solid var(--border);
  overflow: hidden;
  text-decoration: none;
}
.doc-item__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.doc-item__icon { color: #9AA6B2; display: flex; }
.doc-item__body { min-width: 0; }
.doc-item__title { font-family: var(--font-serif); font-size: 20px; font-weight: 600; color: var(--ink); }
.doc-item__meta { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 4px; font-size: 14px; color: var(--muted); }
.doc-item__desc { font-size: 15px; color: var(--ink-3); margin-top: 8px; }
.doc-item__actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.doc-item__missing { font-size: 14px; color: #D9534F; font-style: italic; }
.doc-viewer {
  border: 1px solid var(--border);
  background: #FFFFFF;
}
.doc-viewer iframe { display: block; width: 100%; height: 78vh; border: 0; }
.doc-viewer__fallback { font-size: 13px; color: var(--muted); margin-top: 8px; }
@media (max-width: 900px) {
  .doc-item { flex-direction: row; }
  .doc-item__thumb { width: 72px; height: 96px; }
  .doc-viewer iframe { height: 70vh; }
}

/* Odtwarzacz strony pozycji AUDIO (App\Models\Video, type=Audio) — okładka
   (opcjonalna) nad natywnym <audio controls>, ten sam duch co .audio-item
   playlist, tylko większy (strona pojedynczego nagrania, nie lista). */
.video-audio-player {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 20px;
}
.video-audio-player__cover {
  width: 100%;
  max-width: 320px;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  margin: 0 auto 18px;
  border-radius: var(--radius-md);
}
.video-audio-player__el { width: 100%; }

/* =========================== STOPKA =========================== */
/* MARGINES NAD STOPKĄ USUNIĘTY (01.08.2026) — to on był „białym paskiem"
   między ostatnim pasem a stopką: 48 px marginesu, przez które prześwitywało
   tło strony (`--bg`, jasne). Dopóki ostatni pas był jasny, nikt tego nie
   widział; odkąd Sakramenty i Myśl dnia mają ciemne tło, między dwoma ciemnymi
   blokami świecił jasny pas.
   Odstęp i tak jest — daje go `padding-block: var(--band-space)` pasa (64 px na
   desktopie) plus własny padding stopki. Margines był drugim, zbędnym odstępem,
   a jedynym, który przy okazji odsłaniał tło. */
.front-footer { background: var(--footer-bg); color: var(--footer-ink); }
.front-footer__inner {
  max-width: var(--tresc-max);
  margin: 0 auto;
  padding: 56px var(--tresc-pad) 32px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 36px;
}
/* KOLUMNA STOPKI (01.08.2026): przycisk domykający kolumnę stoi przy jej DOLNEJ
   krawędzi, więc trzy kolumny mają przyciski na jednej linii niezależnie od
   tego, ile treści jest nad nimi. Ten sam mechanizm co w pasie trójdzielnym:
   kolumna flex + `margin-top: auto` na wierszu domykającym.
   `align-items: start` zapobiega rozciąganiu dzieci na pełną szerokość — bez
   tego przycisk w wierszu domykającym rozdymał się na całą kolumnę. */
.front-footer__col { display: flex; flex-direction: column; gap: 14px; align-items: stretch; }
.front-footer__foot { margin-top: auto; padding-top: 4px; }
.front-footer__name { font-family: var(--font-serif); font-size: 22px; font-weight: 600; color: #FFFFFF; }
.front-footer a { color: var(--footer-ink); text-decoration: none; }
.front-footer a:hover { color: #FFFFFF; }

/* ZAKRES AKCJI DLA STOPKI (02.08.2026) — te same tokeny, którymi posługują się
   CIEMNE PASY (App\Support\Front\SectionBackgrounds). Stopka jest ciemna, ale
   nie jest „sekcją", więc zakresu nie dostawała i przycisk spadał na zapasy
   liczone dla pasa JASNEGO: tło `--surface-2` (jasnoszare) z napisem, który
   `.front-footer a:hover` malowało na BIAŁO — 1,24 : 1, nie do przeczytania.

   W spoczynku biała obwódka i biały napis, po najechaniu pełne białe wypełnienie
   z napisem w kolorze tła stopki (16,74 : 1). */
.front-footer {
  /* Obrys fokusu na ciemnej stopce: ciemny akcent z `:root` ma tu 2,44:1 przy
     progu 3:1 (znalezione 07.09.2026 przy A6, ta sama usterka co w Nowoczesnym).
     Stopka stoi poza zakresem sekcji, więc token trzeba ustawić tutaj. */
  --section-focus: #FFFFFF;
  --section-action-quiet-ink: #FFFFFF;
  --section-action-quiet-border: rgba(255, 255, 255, 0.72);
  --section-action-quiet-bg-hover: #FFFFFF;
  --section-action-quiet-ink-hover: var(--footer-bg);
}
/* SPECYFICZNOŚĆ, nie `:not(.btn)`: reguły odnośnika stopki (`.front-footer a`,
   0,1,1 i 0,2,1) przebijały reguły przycisku (0,1,0 i 0,2,0). Zamiast osłabiać
   tamte selektory — a wisi na nich kilka innych zasad frontu — wzmacniamy te.
   Kolor bierzemy z tokenów zakresu wyżej, więc jest JEDNO miejsce prawdy. */
.front-footer .btn--action-quiet { color: var(--section-action-quiet-ink); text-decoration: none; }
.front-footer .btn--action-quiet:hover { color: var(--section-action-quiet-ink-hover); text-decoration: none; }
/* NAGŁÓWEK BLOKU STOPKI (26.07.2026): ikona ZŁOTA po lewej, napis BIAŁY
   po prawej, w jednym rzędzie — ta sama zasada co w nakładce baneru.
   Złoto zostaje na ikonach i PODETYKIETACH (konto, BLIK) wewnątrz bloków. */
.front-footer__label {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--footer-ink, #FFFFFF);
  font-weight: 700;
  margin-bottom: 10px;
}
.front-footer__label-icon {
  display: block;
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  color: var(--sec);
}
.front-footer__label-icon svg { width: 100%; height: 100%; display: block; }
/* PODETYKIETY wewnątrz bloku (numer konta, BLIK) zostają ZŁOTE i drobne —
   to nie są nagłówki sekcji, tylko opisy pojedynczych danych. Ta sama zasada
   co w nakładce baneru: złoto niesie ikonę i podetykietę, biel nagłówek. */
/* ROZMIAR DROBNY (01.08.2026) — ten sam komponent akcji, mniejszy.
   Stopka jest gęsta: przycisk w rozmiarze ze strony (16 px / 13×26 padding)
   byłby w niej za ciężki, a osobny, „stopkowy" odnośnik oznaczałby powrót do
   dwóch systemów akcji. Zmieniamy WYŁĄCZNIE stopień pisma i wcięcia; kolory,
   promień, strzałka i przejście zostają wspólne. */
.btn--action-sm {
  font-size: 13px;
  padding: 8px 16px;
  gap: 6px;
  letter-spacing: 0.05em;
}


.front-footer__sublabel {
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sec);
  font-weight: 700;
  margin-bottom: 4px;
}
.front-footer__col .contact-details__label { color: var(--sec); }
.front-footer__lines { font-size: 16px; line-height: 1.8; }
.front-footer__bank { font-family: monospace; font-size: 13px; color: #FFFFFF; }
/* C4 (26.07.2026): TRZY kafle w rzędzie (nie „ile się zmieści") — mniej
   miniatur, ale większych, w wąskiej kolumnie stopki. Feed (GalleriesFeed::
   latestPhoto()) przepuszcza tylko galerie z okładką i ucina do 6, więc
   ostatni rząd bywa niepełny — grid go po prostu zostawia z lewej. */
/* SIATKA MINIATUR W STOPCE (przebudowa 01.08.2026).

   Było: sztywne trzy kolumny i miniatura o stałej wysokości 62 px. Przy sześciu
   galeriach wychodziły dwa pełne rzędy, ale przy czterech czy pięciu ostatni
   rząd urywał się w połowie, a w wąskiej kolumnie kafle robiły się paskami.

   Jest: liczba kolumn z szerokości KOLUMNY STOPKI (ta sama zasada co w pasach
   strony), a `grid-auto-rows: 1fr` + `aspect-ratio` na kafelku trzymają równe
   rzędy. `grid-auto-flow: dense` domyka luki, gdy ostatni rząd jest niepełny. */
.front-footer__thumbs {
  --thumb-gap: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--thumb-gap);
  /* Niepełny ostatni rząd ŚRODKUJEMY — ta sama konwencja co przy rzędach pasów
     na stronie głównej (`.home-row`). Przy sześciu miniaturach nic to nie
     zmienia (rzędy wychodzą pełne), przy czterech ratuje układ przed jednym
     kaflem dosuniętym do lewej krawędzi. */
  justify-content: center;
}
/* FLEX, NIE SIATKA — i to jest tu sedno. Siatka o stałej liczbie kolumn zostawia
   przy niepełnym ostatnim rzędzie dziurę (przy sześciu galeriach i czterech
   kolumnach wychodziło 4 + 2 i pusty prawy brzeg). We flexie kafle ostatniego
   rzędu ROZRASTAJĄ SIĘ na wolne miejsce, więc rząd zawsze jest pełny — niezależnie
   od tego, ile miniatur ustawiła parafia (3, 4, 5, 6, 8…).

   Podstawa to trzecia część kolumny: przy typowej szerokości bloku stopki daje
   trzy kafle w rzędzie, a w wąskiej kolumnie na telefonie zwija się sama. */
.front-footer__thumb-link {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--radius-sm);
  flex: 1 1 calc(33.333% - var(--thumb-gap));
  /* Górna granica, żeby pojedynczy kafel w ostatnim rzędzie nie zrobił się
     pasem na całą szerokość kolumny. */
  max-width: calc(50% - var(--thumb-gap) / 2);
  aspect-ratio: 4 / 3;
  background: rgba(255, 255, 255, 0.06);
}
.front-footer__thumb { width: 100%; height: 100%; object-fit: cover; display: block; }

/* NAKŁADKA Z TYTUŁEM — ten sam gest co na kaflach strony: delikatny ruch,
   czasy i krzywe z warstwy ruchu (--motion-state / --motion-ease-state), nigdy
   własne. Animujemy WYŁĄCZNIE `opacity` i `transform` (zasada warstwy ruchu),
   więc nakładka nie przelicza układu przy każdej klatce. */
.front-footer__thumb-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  padding: 6px 8px;
  background: linear-gradient(180deg, rgba(20, 12, 10, 0) 35%, rgba(20, 12, 10, 0.86) 100%);
  color: #FFFFFF;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.25;
  opacity: 0;
  transform: translateY(var(--motion-rise, 10px));
  transition: opacity var(--motion-state) var(--motion-ease-state),
              transform var(--motion-state) var(--motion-ease-state);
}
.front-footer__thumb-link:hover .front-footer__thumb-overlay,
.front-footer__thumb-link:focus-visible .front-footer__thumb-overlay {
  opacity: 1;
  transform: translateY(0);
}
/* Tytuł bywa długi, a kafel w stopce jest mały — trzy wiersze i koniec. */
.front-footer__thumb-title {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.front-footer__bar { border-top: 1px solid var(--footer-hair); }
/* PODPIS SYSTEMU W BELCE (08.08.2026 — przeniesione ze stylów inline).
   ⚠️ PRZEZROCZYSTOŚCI SIĘ MNOŻĄ. Wcześniej `<span>` miał `opacity: 0.8`, a jego
   dzieci własne kolory z alfą (0,55 i 0,85) — efektywnie 0,44 i 0,68, czyli
   napis „powered by" schodził do kontrastu 1,3:1 na ciemnym tle. Przygaszenie
   niesie teraz WYŁĄCZNIE kolor z tokenu: jeden nośnik, jedna wartość, policzalny
   kontrast. Nie dokładaj tu `opacity`. */
.front-footer__powered {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--footer-ink-dim);
}
.front-footer__powered-name { color: var(--footer-ink); font-size: 16px; }
.front-footer__bar-inner {
  max-width: var(--tresc-max);
  margin: 0 auto;
  padding: 18px 24px;
  display: flex;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  align-items: center;
  font-size: 13px;
  color: var(--footer-ink-dim);
}

/* Bloki stopki (Wygląd strony → Stopka) — kafle O parafii / Mapa dojazdu /
   Tekst własny reużywają .front-footer__lines/__label; poniżej tylko to, czego
   dotąd nie było. */
.front-footer__about-logo { height: 44px; width: auto; object-fit: contain; display: block; }

/* Mapa dojazdu (App\Support\Front\MapEmbed) — proporcja 4:3, iframe lazy. */
.front-map { display: flex; flex-direction: column; gap: 8px; }
.front-map__iframe {
  width: 100%;
  aspect-ratio: 4 / 3;
  border: 0;
  border-radius: var(--radius-md);
  filter: grayscale(0.2) contrast(0.95);
}
/* `.front-map__link` USUNIĘTY (01.08.2026) — odnośnik mapy jest teraz akcją
   z systemu (`<x-front.action>` w wierszu domykającym `.front-map__foot`). */
.front-map__foot { margin-top: 10px; }

/* Wariant PODSTRONY Kontakt (24.07.2026): mapa jest tam pierwszą sekcją i ma
   od razu pokazywać okolicę, więc jest szeroka i płaska (16:9), a nie wąska
   jak kafel stopki. Dolna granica wysokości chroni przed paskiem mapy na
   bardzo szerokich ekranach. Na telefonie wracamy do 4:3 — tam pas byłby za
   niski, żeby cokolwiek rozpoznać. */
.front-page-block--map { margin-bottom: 28px; }
.front-map--page .front-map__iframe {
  aspect-ratio: 16 / 9;
  min-height: 320px;
  filter: none;
}
@media (max-width: 900px) {
  .front-map--page .front-map__iframe { aspect-ratio: 4 / 3; min-height: 260px; }
}

/* Social media (belka dolna) — zastępuje stary pływający przycisk FB. */
.front-footer__social { display: inline-flex; align-items: center; gap: 10px; }
.front-footer__social-link { display: inline-flex; color: var(--footer-ink-dim); transition: color .18s ease; }
.front-footer__social-link:hover { color: #FFFFFF; }

/* Mobile — kolumny stopki jedna pod drugą wg kolejności, niezależnie od
   liczby włączonych bloków (nadpisuje inline grid-template-columns). */
@media (max-width: 900px) {
  .front-footer__inner {
    grid-template-columns: 1fr !important;
    max-width: none !important;
    margin: 0 !important;
  }
}

/* =========================== KARUZELA WYRÓŻNIONYCH =========================== */
/* JEDNA WYSOKOŚĆ WSZYSTKICH SLAJDÓW (25.07.2026).
   Do tej pory każdy kafel miał własną wysokość — krótszy zostawiał pod sobą
   pustkę, a przy przełączaniu slajdów dolna krawędź kafla i kreseczki skakały.
   Ścieżka pozioma (`display: flex` na torze) już wyrównywała same SLAJDY;
   brakowało tego, żeby KAFEL wypełnił swój slajd. Stąd łańcuch rozciągania:
   slajd → kafel → siatka zdjęcie/tekst.
   Wysokość bierze się z najwyższego kafla, a nie ze stałych proporcji zdjęcia:
   proporcje wymuszałyby przycinanie treści przy dłuższym tytule, a tak żaden
   slajd nic nie traci, a strona i tak stoi nieruchomo. Zero JavaScriptu, więc
   nic nie trzeba przeliczać po zmianie rozmiaru okna ani po wczytaniu krojów. */
.news-carousel { position: relative; }
.news-carousel__viewport { overflow: hidden; }
.news-carousel__track { display: flex; transition: transform 0.5s ease; align-items: stretch; }
.news-carousel__slide { flex: 0 0 100%; min-width: 0; display: flex; }
.news-carousel__slide > .news-featured { flex: 1 1 auto; margin-bottom: 0; }
/* (Dawne odsunięcie treści slajdu bez zdjęcia o 64 px USUNIĘTE 25.07.2026 —
   robiło miejsce strzałkom karuzeli, a strzałek w tym widgecie już nie ma.) */
.news-carousel__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 42px;
  height: 42px;
  border-radius: var(--radius-circle);
  border: none;
  background: rgba(41, 36, 31, 0.55);
  color: #FFFFFF;
  font-size: 20px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-sterowanie);
  transition: background var(--motion-state) var(--motion-ease-state);
}
.news-carousel__arrow:hover { background: var(--section-link); }
.news-carousel__arrow--prev { left: 12px; }
.news-carousel__arrow--next { right: 12px; }
/* Wskaźniki slajdów należą do karuzeli, więc stoją tuż pod nią. Wcześniej
   wisiały w przestrzeni między pasem a kaflami: odstęp 14 px liczył się od
   NAJWYŻSZEGO kafla, a pod niższym dochodziła do tego jego niewykorzystana
   wysokość. Wyrównanie wysokości slajdów zamknęło tamtą lukę, ten odstęp
   dociąga resztę. */
.news-carousel__dots {
  display: flex;
  justify-content: center;
  gap: 9px;
  margin-top: 10px;
}
.news-carousel__dot {
  width: 26px;
  height: 4px;
  border-radius: var(--radius-xs);
  border: none;
  padding: 0;
  cursor: pointer;
  background: #D9CFC0;
  transition: background 0.3s ease;
}
.news-carousel__dot.is-active { background: var(--section-accent); }

/* ---- KRESKA JAKO PASEK POSTĘPU (widget Aktualności, 25.07.2026) ----
   Po usunięciu strzałek kreski są JEDYNYM sterowaniem karuzeli, więc dostają
   drugą rolę: kreska aktywnego slajdu wypełnia się w miarę odliczania do
   automatycznej zmiany. Jeden element mówi naraz „tu jesteś" i „tyle zostało".

   Wariant, nie zmiana globalna: te same klasy nosi karuzela zdjęć nad wpisem,
   która pasków postępu nie ma i jej aktywna kreska ma zostać pełnym akcentem.
   Kreski nieaktywne zostają dokładnie takie jak dotąd (forma zaakceptowana).

   Rowek aktywnej kreski jest bladym akcentem, nie beżem kresek nieaktywnych —
   inaczej tuż po przełączeniu, przy pustym pasku, nie dałoby się rozpoznać,
   który slajd jest aktywny. */
.news-carousel__dots--progress .news-carousel__dot { position: relative; }
.news-carousel__dots--progress .news-carousel__dot.is-active { background: var(--sec-soft, var(--surface-2)); }
.news-carousel__dots--progress .news-carousel__dot:hover { background: var(--section-border); }
.news-carousel__dots--progress .news-carousel__dot.is-active:hover { background: var(--sec-soft, var(--surface-2)); }
/* Powiększony cel kliknięcia/dotyku. Sama kreska ma 4 px wysokości — jako
   jedyne sterowanie karuzeli nie może być celem nie do trafienia palcem.
   Warstwa jest przezroczysta, więc forma graficzna kreski się nie zmienia.

   OD 30.07.2026 na klasie BAZOWEJ, nie tylko w wariancie z paskiem postępu:
   te same kreski bez paska (karuzela zdjęć nad wpisem) miały cel 26×4 px. */
.news-carousel__dot { position: relative; }
.news-carousel__dot::after {
  content: "";
  position: absolute;
  inset: -12px -4px;
}
/* Pasek leży NA kresce warstwą pozycjonowaną, nie jako zwykłe dziecko: procent
   szerokości ma wtedy jednoznaczny punkt odniesienia (blok zawierający), a
   wewnątrz <button> zwykłe dziecko o szerokości procentowej wychodzi zerowe
   — sprawdzone na ekranie, pasek się nie pojawiał mimo poprawnego stylu. */
.news-carousel__dot-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0;
  border-radius: var(--radius-xs);
  /* PRZYCIEMNIONY akcent, nie surowy: pasek jest jedynym sygnałem „ta kreska
     jest aktywna", a jasne złoto/srebro ma wobec beżu kresek nieaktywnych
     zaledwie 1,7–2,0:1 — poniżej progu 3:1 dla elementów nietekstowych.
     Przyciemniony wariant daje 3,9–4,5:1 wobec kresek i 5,1–5,6:1 wobec rowka,
     we wszystkich czterech paletach. Token jest niezależny od tła pasa, tak jak
     same kreski (te są jasne również na pasie ciemnym), więc działa wszędzie. */
  background: var(--sec-strong, var(--sec));
  /* BEZ `transition` na szerokości — i to jest świadome. Odliczanie dosyła nową
     szerokość co 100 ms, czyli dokładnie w tempie, w jakim przejście musiałoby
     się kończyć; przy takim zestrojeniu każda kolejna wartość restartowała
     interpolację i pasek nie ruszał z zera (zmierzone na ekranie: styl inline
     mówił 13,75%, a wyliczona szerokość zostawała 0 px). Kroki i tak są
     subpikselowe — na kresce szerokiej 26 px jeden krok to ok. 0,3 px. */
}

/* =========================== RESPONSYWNOŚĆ =========================== */
/* Breakpoint główny 900px (UI-STYLE.md). Poniżej: menu górne → hamburger,
   lewa kolumna → akordeon nad treścią. */
@media (max-width: 900px) {
  .front-topnav { display: none; }
  .front-burger { display: flex; }

  /* Kolumna flex zamiast bloku: `order` nie działa w układzie blokowym, a bez
     niego rail spadłby POD treść (w drzewie stoi za nią). Odstęp niesie `gap`
     zamiast marginesu — w układzie flex marginesy się nie zlewają, więc
     zostawiony `margin-bottom` dodałby się do odstępu zamiast go zastąpić. */
  .front-body--with-sidebar .front-body__inner {
    display: flex;
    flex-direction: column;
    /* `align-items` MUSI wrócić do `stretch`: reguła szerokiego ekranu ustawia
       `flex-start`, a w kolumnie ta wartość przestaje wyrównywać do góry i
       zaczyna sterować SZEROKOŚCIĄ — kolumna główna kurczy się do treści, czyli
       rozpycha się do 975 px przy oknie 375 px (zmierzone). */
    align-items: stretch;
    gap: 28px;
  }
  .front-sidebar { position: static; flex: none; margin-bottom: 0; }
  .front-sidebar__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    background: var(--surface);
    border: 1px solid var(--border);
    cursor: pointer;
    padding: 15px 20px;
    font-family: var(--font-sans);
    font-size: 13px;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    /* Jasne złoto `--sec` na białym przycisku miało 2,61–3,13:1. `--sec-strong`
       to najciemniejszy token przechodzący próg w każdej palecie Klasyka
       (najsłabiej 6,03:1). Łukasz nadpisuje ten kolor w skórze (14.09.2026). */
    color: var(--sec-strong);
    font-weight: 700;
  }
  .front-sidebar__toggle-icon { color: var(--pri); font-size: 18px; }
  /* box ukryty domyślnie na mobile, pokazywany po rozwinięciu (Alpine .is-open) */
  .front-sidebar__box { display: none; border-top: none; }
  .front-sidebar__box.is-open { display: block; }
  .front-sidebar__head { display: none; }
  /* Siatki (.gallery-grid/.news-grid) zwijają się teraz według KONTENERA
     (@container przy definicji / auto-fill), nie według szerokości ekranu —
     dawne nadpisania @media tutaj USUNIĘTE, patrz sekcja siatki kafli. */
}

/* menu mobilne ukryte domyślnie, pokazywane przez toggling w Alpine/JS */
[x-cloak] { display: none !important; }

/* ── Duszpasterze (moduł Duszpasterze) ────────────────────────────────────── */
.front-pastors__group { margin-top: var(--space-xl); }
.front-pastors__group:first-child { margin-top: 0; }
.front-pastors__heading {
  font-size: 1.75rem;
  color: var(--pri);
  margin: 0 0 var(--space-lg);
  padding-bottom: var(--space-xs);
  border-bottom: 2px solid var(--section-accent-ink);
}
.front-pastors__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-lg);
}
.front-pastor {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-4xl);
  padding: var(--space-lg);
}
.front-pastor__photo {
  width: 140px;
  height: 140px;
  border-radius: var(--radius-circle);
  overflow: hidden;
  background: var(--card-bg-alt);
  flex-shrink: 0;
  margin-bottom: var(--space-md);
  border: 3px solid var(--card-bg);
  box-shadow: 0 0 0 1px var(--card-border);
}
.front-pastor__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Zastępnik portretu: sylwetka WPISANA W KOŁO z zapasem przy krawędzi.
   Wcześniej svg zajmował pełne 100% kadru — barki dotykały obwódki i przy
   kadrze kołowym sylwetka wyglądała na uciętą. Kolor ze zmiennych zakresu
   (był wpisany na stałe beż #C9BCA8, obcy w trzech z czterech palet). */
.front-pastor__placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: color-mix(in srgb, var(--card-muted) 45%, transparent);
}
.front-pastor__placeholder svg { width: 52%; height: 52%; }
.front-pastor__body { width: 100%; }
.front-pastor__name { font-size: 1.5rem; color: var(--pri); margin: 0 0 var(--space-xs); line-height: 1.2; }
.front-pastor__status { font-size: 0.95rem; color: var(--ink-2); margin: 0 0 var(--space-md); }
.front-pastor__role { font-weight: 600; }
.front-pastor__sep { margin: 0 0.35rem; color: var(--muted); }
.front-pastor__scope { color: var(--ink-3); }
.front-pastor__about { font-size: 0.95rem; color: var(--ink-2); line-height: 1.6; margin-bottom: var(--space-md); text-align: left; }
.front-pastor__duties {
  text-align: left;
  background: var(--surface-2);
  border-left: 3px solid var(--sec);
  border-radius: 0 var(--radius-xl) var(--radius-xl) 0;
  padding: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-md);
  font-size: 0.92rem;
  color: var(--ink-2);
  line-height: 1.55;
}
.front-pastor__duties-label { display: block; font-weight: 600; color: var(--pri); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 0.2rem; }
.front-pastor__facts { text-align: left; margin: 0 0 var(--space-md); display: grid; gap: 0.25rem; }
.front-pastor__fact { display: flex; gap: 0.5rem; font-size: 0.9rem; }
.front-pastor__fact dt { color: var(--muted); min-width: 96px; margin: 0; }
.front-pastor__fact dd { color: var(--ink-2); margin: 0; font-weight: 500; }
.front-pastor__table { text-align: left; margin-bottom: var(--space-md); }
.front-pastor__table-title { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--pri); margin: 0 0 0.35rem; }
.front-pastor__rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.2rem; }
.front-pastor__rows li { display: flex; justify-content: space-between; gap: 0.75rem; font-size: 0.9rem; color: var(--ink-2); padding: 0.2rem 0; border-bottom: 1px solid var(--border-3); }
.front-pastor__rows li:last-child { border-bottom: none; }
.front-pastor__rows-meta { color: var(--muted); white-space: nowrap; }
.front-pastor__contact { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem 1rem; font-size: 0.9rem; margin: var(--space-sm) 0 0; }
.front-pastor__contact a { color: var(--section-accent-ink); text-decoration: none; }
.front-pastor__contact a:hover { text-decoration: underline; }

@media (max-width: 600px) {
  .front-pastors__grid { grid-template-columns: 1fr; }
}

/* ===================== POLECANE (moduł Polecane) ===================== */

/* Strefa action_bar — pełnoszerokościowe banery nad treścią strony głównej,
   jeden pod drugim. Klikalne, delikatne uniesienie na hover. */
.front-promoted-bar { display: flex; flex-direction: column; gap: 14px; margin-bottom: 28px; }
/* BEZ OBWÓDKI (03.08.2026, oba ustawienia odstępu i wszystkie trzy tryby).
   Obramowanie pochodziło z TEJ reguły — własnej reguły pozycji paska, a nie
   ze wspólnego stylu kafla; token `--card-border` jest współdzielony, ale
   deklaracja nie. Dlatego zawężenie było proste: kasujemy ją tutaj, a kafle
   Aktualności (.news-card), galerii (.gallery-tile) i kart-linków (.card-link)
   mają WŁASNE reguły z tym samym tokenem i zostają nietknięte.

   Na kolorowym tonie ta ramka czytała się jak brudna krawędź, a nie jak ramka
   kafla: 1 px szarości między kolorem banera a tłem strony. Stany najechania
   i wciśnięcia mówią teraz samym cieniem — wymiary dalej się nie zmieniają. */
.front-promoted-bar__item {
  display: block; border-radius: var(--radius-2xl); overflow: hidden;
  border: 0; background: var(--card-bg);
  transition: transform .15s ease, box-shadow .15s ease;
}
.front-promoted-bar__item { text-decoration: none; }
.front-promoted-bar__item:hover { box-shadow: 0 8px 24px rgba(41,36,31,0.12); }
.front-promoted-bar__item:active { box-shadow: none; }
.front-promoted-bar__img { display: block; width: 100%; height: auto; }
/* Kafel Polecanych w SZPALCIE zostaje kartą (ma ramkę), ale w trybie „gotowy
   baner" grafika jest treścią — ramka rysowałaby przy niej wyłącznie szew.
   Pozycja PASKA akcji nie ma ramki w żadnym trybie (patrz reguła bazowa). */
.front-promoted-tiles__item[data-mode="banner"] { border: 0; }

/* TREŚĆ POZYCJI (tytuł, ewentualny przycisk) — tryby „sam kolor" i „zdjęcie
   z treścią" (02.08.2026; do 01.08.2026 element nazywał się `__fallback`, bo
   pojawiał się wyłącznie zamiast brakującej grafiki — dziś niesie treść
   w dwóch z trzech trybów, więc nazwa opisuje rolę, a nie brak).

   ZAMIERZONY baner typograficzny: pas akcentu z lewej (ten sam gest co przy
   telefonie dyżurnym), tytuł krojem szeryfowym i strzałka „prowadzi dalej"
   po prawej. Wysokość jest naturalna, ale znacząca (padding 26 px), żeby
   w pasie MIESZANYM pozycja tekstowa nie wyglądała jak cienka wypustka przy
   wysokim banerze. */
.front-promoted-bar__content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 26px;
  border-left: 4px solid var(--promo-accent, var(--section-accent, var(--sec)));
  background: var(--promo-bg, var(--sec-soft, var(--card-bg-alt)));
  font-family: var(--font-serif);
  font-size: 21px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--promo-ink, var(--card-link, var(--pri)));
  text-align: left;
}
/* Strzałka jako pseudo-element PRZYPIĘTY do pozycji — nigdy osobny element
   z marginesami (ta sama zasada, co przy separatorze godzin Mszy). */
.front-promoted-bar__content::after {
  content: "→";
  flex: 0 0 auto;
  font-family: var(--font-sans);
  font-size: 20px;
  transition: transform var(--motion-state) var(--motion-ease-state);
}
.front-promoted-bar__item:hover .front-promoted-bar__content::after { transform: translateX(4px); }
/* Pozycja Z PRZYCISKIEM nie dokłada drugiej strzałki — tę niesie już przycisk. */
.front-promoted-bar__content[data-cta]::after { content: none; }

/* ------------------------------------------------------------------
   PRZYKLEJANIE PASA (01.08.2026, ustawienie „Odstępy" widgetu).

   „Przyklejony" ma znaczyć jedno: banery stykają się ze sobą I z sekcją nad
   i pod pasem. Zerowanie WŁASNEGO odstępu pasa załatwia tylko połowę sprawy —
   sąsiad ma swój padding (pas pełnej szerokości) albo margines (pas w strefie)
   i to on zostawiłby widoczną szparę. Dlatego sięgamy po `:has()` do sąsiada
   NAD pasem; sąsiada POD nim adresuje zwykły `+`.

   ŁAGODNA DEGRADACJA: przeglądarka bez `:has()` zostawi górny odstęp sąsiada —
   pas dalej wygląda poprawnie, po prostu nie dokleja się od góry.
   ------------------------------------------------------------------ */
.front-promoted-bar[data-flush] { gap: 0; margin-bottom: 0; }
/* Zaokrąglenia znikają: sklejone banery mają wyglądać jak jeden blok, a nie
   jak kafle wciśnięte jeden w drugi. Wspólna krawędź rysuje się raz. */
.front-promoted-bar[data-flush] .front-promoted-bar__item { border-radius: 0; }


/* ------------------------------------------------------------------
   TŁO DO KRAWĘDZI OKNA, TREŚĆ NA WSPÓLNEJ SIATCE (02.08.2026).

   ZMIERZONY OBJAW: przy oknie 1440 px pas przyklejony kończył się na 60 px
   i 1380 px — czyli dokładnie tam, gdzie kontener treści. Rozlanie poza
   kontener robił PAS (padding-inline), a baner był jego zwykłym dzieckiem,
   więc nic go poza tę linię nie wypuszczało.

   ROZWIĄZANIE to ta sama technika, o jeden poziom niżej: pas przyklejony
   oddaje swoje wcięcie (padding-inline: 0), a przejmuje je TREŚĆ pozycji —
   z tego samego tokenu `--tresc-wciecie`, którego używa pas. Dzięki temu tło
   dochodzi do krawędzi okna, a tekst zaczyna się dokładnie na linii tytułów
   sekcji. Bez ujemnych marginesów i bez jednostek liczących pasek przewijania,
   więc strona nie zaczyna się przewijać w bok.

   W KOLUMNIE GŁÓWNEJ ta sama reguła daje to, o co chodzi: sekcja rozlewa się
   na całą szerokość kolumny, a token — liczony względem kolumny, nie okna —
   schodzi do `--tresc-pad`, czyli do linii treści sąsiednich pasów w kolumnie.

   GRAFIKA zostaje BEZ wcięcia (wcięcie ma treść, nie obraz) — w trybie
   „gotowy baner" rozciąga się na całość.
   ------------------------------------------------------------------ */
section.front-section--flush { padding-inline: 0; padding-block: 0; }
.front-promoted-bar[data-flush] .front-promoted-bar__content {
  padding-inline: var(--tresc-wciecie);
  /* Pas akcentu z lewej to gest KARTY. Przy krawędzi okna zostawał jako
     pionowy pasek innego koloru niż baner — i przesuwał tekst o swoje
     4 px, więc napis nie stawał dokładnie na linii tytułów sekcji. */
  border-left: 0;
}


section.front-section:has(+ section.front-section--flush) { padding-bottom: 0; }
section.front-section--flush + section.front-section { padding-top: 0; }
/* GRANICA PASA I STREFY DWUKOLUMNOWEJ. Pas nad strefą i sama strefa nie są
   rodzeństwem (każdy ma własne opakowanie w layoucie), a odstęp między nimi
   robi oddech korpusu — więc gdy pas przyklejony jest OSTATNI nad strefą albo
   PIERWSZY pod nią, zabieramy oddech od tej właśnie strony. Bez tego
   „przyklejony" doklejałby się tylko do sąsiadów w obrębie tego samego pasa. */
[data-motion-enter-list]:has(> section.front-section--flush:last-child) + .front-body > .front-body__inner { padding-top: 0; }
.front-body:has(+ [data-motion-enter-list] > section.front-section--flush:first-child) > .front-body__inner { padding-bottom: 0; }
/* W strefie dwukolumnowej odstęp robią MARGINESY (padding jest już zerowy),
   a marginesy sąsiadów się zwijają do większego — więc trzeba wyzerować oba. */
.front-main section.front-section--flush { margin-block: 0; }
.front-main section.front-section:has(+ section.front-section--flush) { margin-bottom: 0; }
.front-main section.front-section--flush + section.front-section { margin-top: 0; }

/* ------------------------------------------------------------------
   TRZY TRYBY POZYCJI (02.08.2026) — App\Enums\PromotedLinkMode.

   Zmienne `--promo-*` wchodzą inline z App\Support\Front\PromotedTones
   (kolory liczone SERWEROWO z katalogu tonów; w CSS ani jednego color-mix).

   banner — gotowa grafika na całość: żadnych nadpisań, wysokość z proporcji
            pliku, zero kadrowania. To jest render sprzed wprowadzenia trybów.
   color  — samo tło w tonie pod treścią.
   photo  — fotografia + nakładka w tonie POD treścią. Siła nakładki
            (--promo-overlay-alpha) przychodzi z pozycji i nigdy nie schodzi
            poniżej granicy czytelności policzonej z jasności tego zdjęcia
            (App\Support\Front\PromotedOverlay).
   ------------------------------------------------------------------ */
.front-promoted-bar__item[data-mode="color"],
.front-promoted-bar__item[data-mode="photo"],
.front-promoted-tiles__item[data-mode="color"],
.front-promoted-tiles__item[data-mode="photo"] {
  background: var(--promo-bg);
}
/* Ramkę w tonie ma już tylko kafel w SZPALCIE — pasek akcji jej nie rysuje. */
.front-promoted-tiles__item[data-mode="color"],
.front-promoted-tiles__item[data-mode="photo"] { border-color: var(--promo-border); }

/* ZDJĘCIE Z TREŚCIĄ. Zdjęcie i treść leżą w TEJ SAMEJ komórce siatki — stąd
   nakładanie się bez pozycjonowania bezwzględnego, a wysokość rzędu wynika
   z wyższego z dwojga (czyli z minimalnej wysokości kadru albo z długiego
   tytułu, jeśli ten się nie mieści).

   KADROWANIE jest tu ZAMIERZONE i jest jedyną taką różnicą wobec trybu
   „gotowy baner": tekst potrzebuje wysokości, a fotografii wolno przyciąć —
   gotowej grafice z napisem nie wolno, bo obcięlibyśmy jej brzeg. */
.front-promoted-bar__item[data-mode="photo"],
.front-promoted-tiles__item[data-mode="photo"] {
  position: relative;
  display: flex;
}
.front-promoted-bar__item[data-mode="photo"] { min-height: clamp(190px, 20vw, 320px); }
.front-promoted-tiles__item[data-mode="photo"] { min-height: 8.5rem; }

/* Zdjęcie POZA PRZEPŁYWEM — i to jest tu sedno (zmierzone w przeglądarce).
   Przy zwykłym ułożeniu obrazek narzuca wysokość swoją PROPORCJĄ: kwadratowa
   fotografia w pasie szerokim na 1370 px robiła kafel wysoki na 1370 px, mimo
   `height: 100%` i `object-fit: cover`. Wyjęty z przepływu nie wnosi już
   własnej wysokości — decyduje minimalna wysokość kadru albo długość tytułu. */
.front-promoted-bar__item[data-mode="photo"] .front-promoted-bar__img,
.front-promoted-tiles__item[data-mode="photo"] .front-promoted-tiles__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.front-promoted-bar__item[data-mode="photo"] .front-promoted-bar__content,
.front-promoted-tiles__item[data-mode="photo"] .front-promoted-tiles__content {
  position: relative;
  flex: 1;
  background: rgba(var(--promo-overlay), var(--promo-overlay-alpha, 0.45));
}
.front-promoted-tiles__item[data-mode="photo"] .front-promoted-tiles__content { justify-content: center; }

/* WYRÓWNANIE treści pozycji. `start` (domyślne) zostaje przy dotychczasowym
   `space-between`: tytuł przy lewej, strzałka przy prawej. Kafle kolumny
   bocznej mają własne reguły — układają się kolumnowo, patrz
   .front-promoted-tiles__content niżej. */
.front-promoted-bar__content[data-align="center"] { justify-content: center; text-align: center; }
.front-promoted-bar__content[data-align="end"] { justify-content: flex-end; text-align: right; }

/* PRZYCISK POZYCJI — wariant systemu akcji (.btn--action), tylko z kolorami
   TONU POZYCJI zamiast tonu pasa. Zapasy = tokeny pasa, więc pozycja bez tonu
   wygląda dokładnie jak każda inna akcja na tym pasie. */
.btn--promo {
  background: var(--promo-btn-bg, var(--section-action-bg, var(--pri)));
  color: var(--promo-btn-ink, var(--section-action-ink, #FFFFFF));
  border-color: transparent;
}
.btn--promo:hover {
  background: var(--promo-btn-bg-hover, var(--section-action-bg-hover, var(--pri-strong, var(--ink))));
  color: var(--promo-btn-ink, var(--section-action-ink, #FFFFFF));
}
/* CAŁY BANER KLIKALNY mimo obecności przycisku — warstwa rozciągnięta z
   odnośnika, ten sam patent co w kartach-linkach (odnośnik w odnośniku dałby
   nieprawidłowy dokument). Pozycja ma `position: relative`, więc warstwa
   obejmuje dokładnie jej pudełko. */
.front-promoted-bar__item:has(.front-promoted-bar__cta),
.front-promoted-tiles__item:has(.front-promoted-tiles__cta) { position: relative; }
.front-promoted-bar__cta::after,
.front-promoted-tiles__cta::after { content: ""; position: absolute; inset: 0; }
/* Sam przycisk musi zostać NAD tą warstwą, żeby jego własny stan najechania
   i obrys fokusu dalej działały. */
.front-promoted-bar__cta,
.front-promoted-tiles__cta { position: relative; z-index: 1; }
.front-promoted-bar__title,
.front-promoted-tiles__title { min-width: 0; }

/* Strefa footer — rząd logotypów. Pasek pełnej szerokości nad stopką praw
   autorskich. Styl wybierany w Wygląd strony → Stopka (App\Enums\
   PromotedFooterStyle) — modyfikator .front-promoted-logos--{styl} niżej. */
.front-promoted-logos { border-top: 1px solid var(--footer-hair); }
.front-promoted-logos__inner {
  max-width: var(--tresc-max); margin: 0 auto; padding: var(--tresc-pad);
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 14px 28px;
}
.front-promoted-logos__item {
  display: inline-flex; align-items: center;
  /* Cel dotykowy nie schodzi poniżej progu także wtedy, gdy pozycja NIE MA
     grafiki i renderuje się zastępczym napisem (zmierzone 22 px — sam wiersz
     tekstu). Z logotypem element ma 46 px, więc ta granica nic tam nie zmienia. */
  min-height: 24px;
  transition: opacity .18s ease, filter .18s ease, background .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.front-promoted-logos__img { display: block; height: 46px; width: auto; object-fit: contain; }
.front-promoted-logos__fallback { color: var(--footer-ink); font-size: 14px; }

/* 1. Czarno-białe (domyślny, patent ze starego ISP): grayscale, kolor po najechaniu. */
.front-promoted-logos--grayscale .front-promoted-logos__item { opacity: 0.65; filter: grayscale(1); }
.front-promoted-logos--grayscale .front-promoted-logos__item:hover { opacity: 1; filter: grayscale(0); }

/* 2. Pełny kolor: zawsze bez filtra. */
.front-promoted-logos--color .front-promoted-logos__item { opacity: 1; filter: none; }

/* 3. Przygaszone: obniżona intensywność (bez grayscale), pełna po najechaniu. */
.front-promoted-logos--dimmed .front-promoted-logos__item { opacity: 0.55; filter: none; }
.front-promoted-logos--dimmed .front-promoted-logos__item:hover { opacity: 1; }

/* 4. Karty: logo na jasnym kafelku z delikatną ramką. */
.front-promoted-logos--cards .front-promoted-logos__item {
  opacity: 1; filter: none;
  background: #FFFFFF;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: var(--radius-xl);
  padding: 12px 20px;
}
.front-promoted-logos--cards .front-promoted-logos__item:hover { border-color: var(--sec); }

/* Strefa sidebar — kafle promowanych odnośników, jeden pod drugim, w bocznej
   kolumnie (obok/pod menu). Dziedziczy ramkę boksu .front-sidebar__box. */
.front-promoted-tiles { display: flex; flex-direction: column; gap: 10px; padding: 14px; }
.front-promoted-tiles__item {
  display: block; border-radius: var(--radius-xl); overflow: hidden; border: 1px solid var(--border);
  transition: transform .15s ease, box-shadow .15s ease;
}
.front-promoted-tiles__item:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(41,36,31,0.10); }
.front-promoted-tiles__img { display: block; width: 100%; height: auto; }
/* KAFEL BEZ GRAFIKI w kolumnie bocznej. Od 01.08.2026 układa treść kolumnowo
   (tytuł, pod nim opcjonalny przycisk) i słucha ustawienia wyrównania pozycji —
   wcześniej napis był ZAWSZE wyśrodkowany. Kafle Z GRAFIKĄ, czyli praktycznie
   wszystkie, wyglądają bez zmian. */
.front-promoted-tiles__content {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  padding: 14px; text-align: left; color: var(--promo-ink, var(--pri));
  font-family: var(--font-serif); font-size: 16px;
  background: var(--promo-bg, var(--surface-2));
}
.front-promoted-tiles__content[data-align="center"] { align-items: center; text-align: center; }
.front-promoted-tiles__content[data-align="end"] { align-items: flex-end; text-align: right; }

/* ===================== BANER NAGŁÓWKA (moduł Wygląd strony) ===================== */
.front-banner {
  /* Baner jest CIEMNY (zdjęcie z maską albo kolor główny palety) i stoi poza
     zakresem sekcji — obrys fokusu musi iść bielą. Ciemny akcent z `:root` miał
     tu 1,56:1 przy progu 3:1 (znalezione 07.09.2026 przy A6). Zakres obejmuje
     przycisk „Informacje podstawowe" i sterowanie pokazem slajdów. */
  --section-focus: #FFFFFF;
  position: relative;
  /* B2 (26.07.2026): baner nieco wyższy niż dawne clamp(300px, 42vh, 460px).
     Skoro jest już tylko na stronie głównej, może zająć więcej miejsca — a przy
     rozwiniętej nakładce daje jej więcej wysokości. Dolna granica podniesiona
     umiarkowanie (340 px), bo to wartość dla telefonu. */
  min-height: clamp(340px, 48vh, 540px);
  background: var(--pri);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.front-banner__stage {
  position: absolute;
  inset: 0;
}
/* ===================== PARALAKSA ZDJĘCIA BANERU (29.07.2026) =====================
   JEDYNE takie miejsce w serwisie: górna część strony ma robić wrażenie, reszta
   ma się uspokajać. Nie rozprowadzać tego po pozostałych sekcjach.

   ZERO NASŁUCHU PRZEWIJANIA W JAVASCRIPCIE — ruch prowadzi sama przeglądarka
   (animacja sterowana przewijaniem). Całość siedzi w `@supports`, więc
   przeglądarka, która tego nie zna, pokazuje baner NIERUCHOMY: efekt wtedy nie
   istnieje, zamiast działać gorzej.

   PRZEKSZTAŁCENIE LEŻY NA WARSTWIE ZE ZDJĘCIEM (`__stage`), nigdy na
   `.front-banner`. W kontenerze siedzi nakładka „Informacje podstawowe"
   z własnymi warstwami i przyciskiem, a przekształcenie na przodku tworzy nowy
   kontekst układania i nowy blok zawierający dla elementów pozycjonowanych na
   sztywno — ten sam mechanizm kazał kiedyś wyprowadzać powiększone zdjęcie do
   korzenia dokumentu. `__stage` jest RODZEŃSTWEM nakładki, nie jej przodkiem.

   Warstwa jest rozciągnięta o zakres przesunięcia w obie strony, żeby przy
   skrajnych położeniach nie odsłonić pustej krawędzi. `.front-banner` ma
   `overflow: hidden`, więc nadmiar jest przycięty.

   Ograniczenie ruchu w systemie zeruje `--motion-parallax` (warstwa tokenów),
   więc zakres znika i baner stoi — bez osobnej reguły przy samym efekcie. */
/* NAPRAWA 29.07.2026 — `view()` NIE MOGŁO TU DZIAŁAĆ Z ZAŁOŻENIA.
   Oś typu `view()` mierzy położenie elementu wobec jego NAJBLIŻSZEGO KONTENERA
   PRZEWIJANIA. `overflow: hidden` na `.front-banner` TWORZY taki kontener,
   a ten kontener nigdy się nie przewija (treść się w nim mieści), więc oś nie
   miała postępu i animacja nie dawała żadnej wartości. To samo dotyczyło tła
   pasa (kadr `.front-section-photo` też ma `overflow: hidden`) — stąd jedna
   przyczyna dla obu miejsc, które milczały.

   Rozwiązanie: oś NAZWANA, deklarowana na elemencie LEŻĄCYM POZA kadrem.
   Podmiotem osi jest sam baner, którego najbliższym kontenerem przewijania jest
   już dokument (sprawdzone przejściem po przodkach). Warstwa ze zdjęciem tylko
   się do tej osi odwołuje.

   Zapytanie o wsparcie obejmuje OBIE własności — samo `view()` nie gwarantuje
   nazwanych osi. Bez wsparcia baner stoi nieruchomo, jak dotąd. */
@supports (animation-timeline: view()) and (view-timeline-name: --isp) {
  .front-banner {
    view-timeline-name: --isp-baner;
    view-timeline-axis: block;
  }

  .front-banner__stage {
    top: calc(-1 * var(--motion-parallax));
    bottom: calc(-1 * var(--motion-parallax));
    /* KOLEJNOŚĆ MA ZNACZENIE: skrócone `animation` resetuje `animation-timeline`
       do wartości domyślnej, więc oś musi stać PO nim. Czas trwania zostaje
       `auto` (nie ustawiamy go nigdzie) — przy osi opartej na postępie to on
       oddaje sterowanie osi. */
    animation: isp-banner-parallax linear both;
    animation-timeline: --isp-baner;
    /* Ruch liczony przez CAŁE przejście baneru przez okno — inaczej zdjęcie
       „dojeżdżałoby" do końca, zanim baner zdąży zniknąć z ekranu. */
    animation-range: cover 0% cover 100%;
  }

  @keyframes isp-banner-parallax {
    from { transform: translate3d(0, calc(-1 * var(--motion-parallax)), 0); }
    to   { transform: translate3d(0, var(--motion-parallax), 0); }
  }
}
.front-banner__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.2s ease-in-out;
}
.front-banner__slide.is-active {
  opacity: 1;
}
.front-banner__img {
  width: 100%;
  height: 100%;
  object-fit: cover;      /* focal point przez object-position (inline) */
  display: block;
}
/* Maska przyciemniająca — TYLKO gdy nakładka aktywna (czytelność tekstu). */
.front-banner__mask {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(15, 10, 12, 0.35) 0%, rgba(15, 10, 12, 0.62) 100%);
  pointer-events: none;
}
.front-banner__overlay {
  position: relative;
  z-index: var(--z-tresc);
  max-width: min(760px, 88%);
  margin: 0 auto;
  padding: 32px 24px;
  text-align: center;
  color: #FFFFFF;
}
.front-banner--placeholder .front-banner__overlay {
  color: #FFFFFF;
}
/* BANER BEZ GRAFIKI (18.07.2026). Decyzja: pas ZOSTAJE, ale jest zwarty i
   niesie tożsamość parafii — nie znika. Powody: (1) w Klasyku baner renderuje
   layout, czyli jest na KAŻDEJ podstronie — znikanie zmieniałoby rytm całej
   witryny zależnie od tego, czy parafia zdążyła wgrać zdjęcie; (2) w trybie
   „Informacje podstawowe” pas niesie FUNKCJĘ (przycisk rozwijający dane
   parafii), więc usunięcie go zabrałoby działającą treść; (3) nowa parafia
   przed wgraniem zdjęć ma wyglądać na gotową, a nie na uciętą.
   Wysokość ~2x mniejsza niż pasa ze zdjęciem — tyle, ile potrzeba na treść. */
.front-banner--placeholder {
  min-height: 0;
  padding: clamp(38px, 6vh, 64px) var(--space-lg);
  flex-direction: column;
  gap: 20px;
}
.front-banner__identity {
  text-align: center;
  color: #FFFFFF;
  position: relative;
  z-index: var(--z-tresc);
}
/* Tryb „Informacje podstawowe” podnosi pas do ~480px, żeby rozwinięty panel
   zmieścił się NA ZDJĘCIU. Bez zdjęcia ten zapas nie ma czego mieścić i
   zostawiał pustkę — dwie klasy dają wyższą specyficzność, więc reguła działa
   niezależnie od kolejności w arkuszu. */
.front-banner--placeholder.front-banner--info { min-height: 0; }
.front-banner__overlay--link {
  display: block;
  text-decoration: none;
  transition: transform .18s ease;
}
.front-banner__overlay--link:hover {
  transform: translateY(-2px);
}
.front-banner__quote {
  font-size: clamp(20px, 3vw, 32px);
  font-weight: 500;
  line-height: 1.34;
  margin: 0;
  text-wrap: balance;
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.4);
}
.front-banner__author {
  margin: 14px 0 0;
  font-size: 15px;
  letter-spacing: 0.04em;
  color: var(--sec);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.45);
}
.front-banner__kicker {
  font-size: 13px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--sec);
  font-weight: 700;
  margin: 0 0 10px;
}
.front-banner__title {
  font-family: var(--font-serif);
  font-size: clamp(30px, 4.6vw, 52px);
  font-weight: 600;
  color: #FFFFFF;
  margin: 0;
  line-height: 1.12;
  text-wrap: pretty;
}
/* Nakładka „własny tekst" — wariant przycisk (CTA): wyraźny przycisk wg palety. */
.front-banner__cta {
  font-size: 17px;
  padding: 15px 34px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
}

/* --- Nakładka „Informacje podstawowe" (tryb Info) --- */
/* Desktop: przycisk przypięty do dołu banera; panel rozwija się W GÓRĘ nad
   zdjęciem (kontener bottom:0, panel w DOM nad przyciskiem). */
.front-banner-info {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-plakietka);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
}
/* Po rozwinieciu tlo kryje CALA wysokosc baneru (od gornej krawedzi do dolnej),
   nie tylko pasek z trescia. Dwie warstwy: na dole przyciemnienie zdjecia, na
   niej kolor palety z kryciem 94% — zdjecie delikatnie przebija.

   TŁO MALUJE PSEUDO-ELEMENT (26.07.2026), a nie sam kontener. Powód: przy
   zamykaniu tło znikało NATYCHMIAST, a treść zostawała jeszcze przez 0,4 s
   zwijania panelu i wisiała goła nad zdjęciem. Gradientu nie da się płynnie
   animować, ale KRYCIE warstwy owszem — stąd osobny element pod spodem. */
.front-banner-info::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(rgba(var(--pri-rgb), 0.94), rgba(var(--pri-rgb), 0.94)),
    rgba(10, 12, 18, 0.28);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.front-banner-info.is-open::before { opacity: 1; }

/* BANER ROŚNIE Z NAKŁADKĄ (26.07.2026) — to była WŁAŚCIWA przyczyna ucinania
   treści, nie wysokość panelu. Nakładka jest `absolute`, więc leżała poza
   przepływem i baner zostawał przy swoim `min-height`: treść potrzebowała
   451 px, baner dawał 360 i reszta znikała pod `overflow: hidden`.
   Po rozwinięciu nakładka WCHODZI W PRZEPŁYW jako pozycja flex banera —
   baner dostosowuje wysokość do treści, a `min-height` zostaje minimum.
   Zdjęcie (`__stage`, inset: 0) wypełnia nową wysokość samo. */
.front-banner-info.is-open {
  position: relative;
  inset: auto;
  flex: 1 1 100%;
  align-self: stretch;
}
/* Treść i przycisk NAD warstwą tła.

   PUŁAPKA (26.07.2026): kontener jest flexem, więc `::before` STAJE SIĘ
   pozycją flex z `order: 0` — a przycisk po rozwinięciu dostaje `order: -1`,
   żeby stanąć na górze. W kontenerze flex kolejność malowania idzie za
   `order`, więc przycisk malował się PRZED tłem i tło go zakrywało.
   Stąd jawny `z-index` na dzieciach, a nie samo `position`. */
.front-banner-info > * { position: relative; z-index: 1; }
.front-banner-info::before { z-index: 0; }
.front-banner-info__toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0 auto 18px;
  padding: 12px 26px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--sec);
  color: #FFFFFF;
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
  transition: background var(--motion-state) var(--motion-ease-state);
}
/* Napis na złotym tle musi być CIEMNY: biel dawała 2,61–3,13:1 zależnie od
   palety, a kolor główny palety (granat/bordo) tylko 3,39:1 — też za mało.
   Dopiero kolor pisma strony daje 4,9–5,4:1 we wszystkich czterech paletach. */
.front-banner-info__toggle { color: var(--ink); }
.front-banner-info__toggle:hover { background: var(--sec-strong, #9c7833); color: #FFFFFF; }
.front-banner-info__caret { font-size: 12px; }

/* STEROWANIE POKAZEM SLAJDÓW (07.09.2026, A20). Treść, która sama się zmienia
   dłużej niż 5 s, musi dać się zatrzymać (WCAG 2.2.2) — a ten pokaz przełączał
   zdjęcie co 6 s bez żadnego wyjścia i bez sprawdzenia systemowego „ograniczaj
   ruch". Przycisk stoi w rogu, żeby nie wchodzić w drogę nakładce.

   Obrys fokusu WŁASNY, biały: przycisk leży na ZDJĘCIU, czyli poza zakresem
   sekcji, a zdjęcie bywa dowolnie jasne — token zakresu nie ma tu czego opisać. */
.front-banner__slides-toggle {
  position: absolute;
  right: 18px;
  bottom: 18px;
  z-index: var(--z-plakietka);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 40 px — powyżej progu celu dotykowego (WCAG 2.5.8 mówi o 24 px). */
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.72);
  border-radius: var(--radius-circle);
  background: rgba(20, 12, 10, 0.62);
  color: #FFFFFF;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.30);
  transition: background-color var(--motion-state) var(--motion-ease-state),
              color var(--motion-state) var(--motion-ease-state);
}
.front-banner__slides-toggle:hover { background: #FFFFFF; color: var(--ink); }
.front-banner__slides-toggle:active { background: var(--sec-strong, var(--sec)); color: #FFFFFF; }
.front-banner__slides-toggle:focus-visible { outline: 3px solid #FFFFFF; outline-offset: 3px; }

/* Panel jest przypiety do DOLU baneru i rosnie w GORE, wiec przy wyzszej tresci
   wychodzil poza sekcje i byl przycinany przez jej overflow:hidden. To defekt
   ISTNIEJACY (przy dawnym odstepie ucinalo ~6 px), ktory wiekszy odstep z
   punktu 3 by uwydatnil. W trybie „Informacje" dajemy wiec banerowi nieco
   wyzszy pas — panel miesci sie w calosci, a stan zwiniety zyskuje spokojniejsze
   proporcje. Zmiana dotyczy WYLACZNIE tego trybu nakladki. */
.front-banner--info { min-height: clamp(360px, 48vh, 520px); }

/* PUNKT 2: tlo panelu na CALEJ szerokosci baneru (tresc zostaje w kontenerze —
   patrz .front-banner-info__cols). Rodzic ma align-items:center, wiec element
   musi jawnie rozciagnac sie sam. */
.front-banner-info__panel {
  align-self: stretch;
  width: 100%;
  /* Kotwica dla krzyżyka zamykającego (`position: absolute` w prawym górnym
     rogu PANELU, nie całej nakładki). */
  position: relative;
  /* WYSOKOŚĆ NIE JEST ANIMOWANA (29.07.2026). Do tej pory panel rozwijał się
     przez `max-height`, czyli dokładnie tak, jak warstwa ruchu zabrania —
     przeliczanie układu w każdej klatce szarpie na słabszym telefonie. Teraz
     panel zajmuje miejsce albo go nie zajmuje (`display`), a animowane są
     WYŁĄCZNIE przezroczystość i przesunięcie jego treści.

     Zachowanie bez zmian: baner rośnie tak, żeby wszystko się zmieściło, nic
     nie jest przycinane, nie ma wewnętrznego przewijania. Zmienił się wyłącznie
     sposób dochodzenia do tego stanu. */
  display: none;
  /* Tlo maluje KONTENER (.front-banner-info.is-open), zeby krylo CALA wysokosc
     baneru, a nie tylko wysokosc tresci. Panel zostaje przezroczysty — inaczej
     dwie polprzezroczyste warstwy nalozylyby sie i przyciemnily podwojnie. */
  color: #FFFFFF;
}
/* PANEL DOPASOWUJE SIĘ DO TREŚCI (26.07.2026, naprawa błędu).
   Wcześniej `max-height: 80vh` + `overflow: hidden` ucinało zawartość w połowie
   zdania przy parafii z kompletem danych — urywał się rząd godzin i opis przy
   telefonie dyżurnym. Teraz wysokość idzie do NATURALNEJ (`1fr` w siatce
   jednokomórkowej — jedyny sposób animowania do `auto`), a limit ekranu
   zostaje wyłącznie jako zabezpieczenie: gdy treści jest naprawdę dużo,
   panel daje się PRZEWINĄĆ, zamiast ją obcinać. */
/* PANEL ROŚNIE Z TREŚCIĄ (26.07.2026) — żadnego przewijania w środku.
   Ograniczenie wysokości z przewijaniem było złą drogą: nakładka to nie okno,
   tylko część strony. Baner ma być po prostu tak wysoki, żeby wszystko się
   zmieściło — kontener nakładki rozciąga się wraz z panelem. */
.front-banner-info__panel.is-open { display: block; }
/* KOLEJNOŚĆ ZAMYKANIA (26.07.2026): treść gaśnie PIERWSZA, panel zwija się po
   niej. Przy otwieraniu odwrotnie — najpierw rusza panel, treść dochodzi
   z opóźnieniem. Robi to samo opóźnienie przejścia: w stanie zamkniętym jest
   zerowe, więc gaśnięcie startuje natychmiast; w otwartym — dodatnie.
   Przy systemowym ograniczeniu ruchu globalna reguła zeruje czasy i zamknięcie
   jest natychmiastowe. */
/* Treść wchodzi wzorcem ROZWINIĘCIA z warstwy ruchu: przezroczystość
   i przesunięcie, czasy i krzywa z tokenów.

   KOLEJNOŚĆ ZAMYKANIA rozwiązana przez USUNIĘCIE kolejności: dawniej tło gasło
   szybciej niż treść i zamknięcie wyglądało na zepsute. Teraz panel znika
   w całości naraz — bez wysokości do przewinięcia nie ma czego etapować, a dwa
   elementy gasnące w różnym tempie były właśnie tym, co raziło. */
.front-banner-info__cols {
  opacity: 0;
  transform: translateY(calc(-1 * var(--motion-rise)));
  transition:
    opacity var(--motion-expand) var(--motion-ease-enter),
    transform var(--motion-expand) var(--motion-ease-enter);
}
.front-banner-info__panel.is-open .front-banner-info__cols {
  opacity: 1;
  transform: none;
}
.front-banner-info__cols {
  max-width: var(--tresc-max);
  margin: 0 auto;
  /* Odstęp GÓRNY mniejszy od dolnego (26.07.2026): nad nagłówkami stoi już
     przycisk zwijania z własnym marginesem, więc pełne 48 px robiło pustą
     przestrzeń w połowie ekranu. Nadal ze skali odstępów, nie „na oko". */
  padding: var(--space-lg) var(--space-lg) var(--space-xl);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 30px;
}
/* Tekst na ciemnym tle palety — wymuszona czytelność (msze, biuro, kontakt). */
.front-banner-info__panel .rich-text,
.front-banner-info__panel .rich-text * { color: rgba(255, 255, 255, 0.92); font-size: 15px; line-height: 1.6; }
/* Linki na bialo z podkresleniem (nie zlotem): zloty akcent na kolorze glownym
   palety daje ok. 3,4:1 — ponizej AA — i tak bylo JUZ przed ta zmiana. Biel
   trzyma warunek twardy >= 4,5:1 na wszystkich paletach. Nowoczesny mial biel
   od poczatku; ujednolicone. */
/* Odnośniki W TREŚCI nakładki (telefon, e-mail) podkreślone — to zwykłe linki
   w tekście. PRZYCISK „zobacz…" jest wyjątkiem: ma ramkę i strzałkę, więc
   podkreślenie tylko go zaśmiecało (A4, 26.07.2026). */
/* Odnośniki W TREŚCI nakładki są białe, bo leżą na granacie. WYJĄTKI:
   przycisk „zobacz…" (ma własny wygląd) ORAZ numer telefonu dyżurnego, który
   leży w JASNEJ RAMCE — biel dawała tam 1,12:1, czyli napis niewidoczny.
   To był ten sam defekt zgłaszany trzeci raz: reguła dla odnośników ma wyższą
   specyficzność niż reguła koloru bloku, więc wygrywała niezależnie od palety. */
.front-banner-info__panel a:not(.info-block__more):not(.office-duty__phone) { color: #FFFFFF; text-decoration: underline; text-underline-offset: 0.2em; }
.front-banner-info__panel a:not(.info-block__more):not(.office-duty__phone):hover { text-decoration-thickness: 2px; }
.front-banner-info__panel .info-block__more { text-decoration: none; }

/* Mobile — agresywniejsze cięcie: niższy pas, focal point decyduje o kadrze. */
@media (max-width: 900px) {
  .front-banner {
    min-height: clamp(240px, 38vh, 340px);
  }

  /* Tryb Info na mobile: informacje OD RAZU rozwinięte, na jednolitym tle palety
     (nie na zdjęciu) — sekcja rośnie, zdjęcie/maska schowane, przycisk zbędny. */
  .front-banner--info {
    min-height: 0;
    display: block;
  }
  /* Krzyżyk znika na telefonie razem z tym przyciskiem, ale reguła musi stać
     PO jego definicji (szukaj `.front-banner-info__close` niżej) — inaczej
     przegrywa kaskadę przy równej specyficzności. */
  .front-banner--info .front-banner__stage,
  .front-banner--info .front-banner__mask,
  .front-banner-info__toggle { display: none; }
  /* Mobile: zdjecie jest ukryte, panel stoi na jednolitym tle palety —
     bez przezroczystosci (nie ma czego przepuszczac) i bez zaleznosci od
     klasy is-open (na mobile panel jest rozwiniety zawsze). */
  .front-banner-info { position: static; background: var(--pri); }
  /* Na telefonie panel jest rozwiniety ZAWSZE — niezaleznie od klasy is-open,
     bo przycisk jest ukryty. Po przejsciu z `max-height` na `display` trzeba to
     powiedziec wprost, inaczej panel zostalby schowany bez sposobu otwarcia. */
  .front-banner-info__panel { display: block; }
  .front-banner-info__cols { opacity: 1; transform: none; }
  .front-banner-info__cols { grid-template-columns: 1fr; gap: 24px; padding: 26px 20px; }
}

/* ============================================================
   Widgety strony głównej — Etap 3 (Wygląd strony)
   ============================================================ */

/* Pas Informacji jako wariant baner: pełna szerokość, wewnętrzny kontener
   ogranicza szerokość treści (spójnie z korpusem). Wariant „stopka" USUNIĘTY
   (16.07.2026) — patrz FooterBlocks. */
.front-quick--strip { margin: 0; padding: var(--band-space) 0; }
.front-quick--strip > .front-body__inner { padding-left: 24px; padding-right: 24px; }

/* Duszpasterze — rząd okrągłych portretów (klik → strona modułu). */
/* ============================================================
   DUSZPASTERZE (przebudowa 31.07.2026, reguły 1+2+5).

   Było: flex z kafelkiem na sztywno 148 px i zdjęciem 108 px — identycznym w
   pasie pełnej szerokości i w jednej trzeciej. W szerokim pasie zostawało wokół
   portretów morze pustki, a o osobie nie było wiadomo nic poza imieniem.

   Jest: siatka o minimalnej szerokości liczonej z kontenera, kafelek jest
   WŁASNYM kontenerem (więc zdjęcie i napisy skalują się kolumną), a treść
   dokłada się warstwami — patrz `.pastor-chip__extra` niżej.
        pełny  1272 cqi → 18cqi = 229 → 229 px → 5 kolumn po ~229
        połowa  596 cqi → 18cqi = 107 → 140 px → 3 kolumny
        ⅓       356 cqi → 18cqi =  64 → 140 px → 2 kolumny
   ============================================================ */
.front-pastors-row {
  display: grid;
  /* BEZ `--kafel-max` ŚWIADOMIE (03.08.2026): rząd okrągłych portretów nie ma
     wypełniać pasa. Sam kafel i tak zatrzymuje się na 260 px (.pastor-chip),
     a portret na 176 px — trzej duszpasterze rozciągnięci na 1320 px dałyby
     koła wielkości talerza. Tu wyśrodkowana grupa JEST zamierzonym układem,
     nie skutkiem ubocznym; wypisane w raporcie, żeby nie wyglądało na przeoczenie. */
  --kafel: clamp(140px, 18.6cqi, 245px);
  grid-template-columns: repeat(auto-fill, minmax(min(var(--kafel), 100%), 1fr));
  justify-content: var(--kafle-justify, start);
  gap: 28px 22px;
  justify-items: center;
}
.pastor-chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: 100%;
  max-width: 260px;
  text-decoration: none;
  color: inherit;
  container-type: inline-size;
}
.pastor-chip__photo {
  /* REGUŁA 2: portret rośnie z kolumną. 96 px to próg rozpoznawalności twarzy,
     176 px — rozmiar, powyżej którego rząd portretów zaczyna przytłaczać pas. */
  width: clamp(96px, 74cqi, 176px);
  height: clamp(96px, 74cqi, 176px);
  flex: 0 0 auto;
  border-radius: var(--radius-circle);
  overflow: hidden;
  background: var(--card-bg);
  border: 3px solid var(--section-accent);
  box-shadow: 0 8px 22px rgba(46, 32, 22, 0.14);
  color: var(--card-muted);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.pastor-chip__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pastor-chip__placeholder { display: flex; width: 100%; height: 100%; align-items: center; justify-content: center; }
.pastor-chip__placeholder svg { width: 64%; height: 64%; }
.pastor-chip:hover .pastor-chip__photo { transform: translateY(-3px); box-shadow: 0 12px 30px rgba(46, 32, 22, 0.20); }
.pastor-chip__name { margin-top: 12px; font-size: clamp(15px, 8cqi, 19px); font-weight: 600; color: var(--card-text); line-height: 1.2; }
.pastor-chip__role { margin-top: 2px; font-size: clamp(12px, 6cqi, 14px); color: var(--card-muted); }

/* ------------------------------------------------------------
   REGUŁA 5 — TREŚĆ TEŻ SIĘ DOPASOWUJE.

   Kafelek renderuje wyłącznie te pola, które parafia wypełniła (o resztę dba
   Blade), a CSS decyduje, ile z nich POKAZAĆ przy danej szerokości kolumny.
   Dwa progi, liczone od kafelka:
     < 190 px — sam portret, imię i status (jak dotąd),
     ≥ 190 px — dochodzi zakres posługi („w parafii od 2019"),
     ≥ 230 px — dochodzą obowiązki (jedno zdanie, przycięte).

   Warstwa domyślnie schowana, a nie „pokazana i chowana" — dzięki temu brak
   pola NIGDY nie zostawia po sobie pustego wiersza ani dziury w rytmie: element
   po prostu nie istnieje w DOM, a schowana warstwa nie zajmuje miejsca.
   ------------------------------------------------------------ */
.pastor-chip__extra { display: none; margin-top: 6px; }
.pastor-chip__since { font-size: 12px; color: var(--card-muted); }
.pastor-chip__duties {
  margin-top: 3px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--card-muted);
  display: none;
  /* Obowiązki bywają zdaniem albo akapitem — dwa wiersze i koniec, żeby jeden
     rozgadany opis nie rozpychał całego rzędu portretów. */
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
@container (min-width: 190px) {
  .pastor-chip__extra { display: block; }
}
@container (min-width: 230px) {
  .pastor-chip__duties { display: -webkit-box; }
}

/* Cytaty — pas „Myśl dnia" (pełna szerokość) */
/* Kolory pasa pochodzą z ZAKRESU SEKCJI (E2) — tło/tekst maluje .front-section,
   tu zostaje sama geometria. Domyślny wariant tego pasa to „ciemne".
   Odstęp pionowy z tokenu band-space (section.front-section) — dawna dokładka
   `padding: 40px 24px` USUNIĘTA (23.07.2026, jeden rytm dla wszystkich pasów). */
.front-quote-strip { text-align: center; }
.front-quote-strip__inner { max-width: 820px; margin: 0 auto; }
.front-quote-strip__kicker {
  display: inline-block;
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--section-accent-ink);
  font-weight: 700;
  margin-bottom: 10px;
}
.front-quote-strip__text {
  margin: 0;
  font-size: clamp(21px, 2.6vw, 30px);
  font-weight: 500;
  line-height: 1.35;
  font-style: italic;
  text-wrap: pretty;
}
.front-quote-strip__author { display: block; margin-top: 14px; font-size: 15px; color: var(--section-muted); font-style: normal; }

/* Cytaty — mały boks „Myśl dnia" w kolumnie bocznej */
.front-quote-box { padding: 18px 18px 20px; }
.front-quote-box__kicker {
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--section-accent-ink);
  font-weight: 700;
  margin-bottom: 8px;
}
.front-quote-box__text { margin: 0; font-size: 16px; line-height: 1.5; font-style: italic; color: var(--ink); text-wrap: pretty; }
.front-quote-box__author { display: block; margin-top: 10px; font-size: 13px; color: var(--muted); font-style: normal; }

/* =========================== INTENCJE MSZALNE (widok tygodnia) =========================== */
.front-intentions-nav {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 0 26px;
}
.front-intentions-nav__btn {
  text-decoration: none;
  font-weight: 700;
  font-size: 15px;
  color: var(--pri);
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 7px 14px;
}
.front-intentions-nav__btn:hover { border-color: var(--section-accent-ink); }
.front-intentions-nav__range {
  margin-left: auto;
  font-size: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 700;
}
/* DZIEŃ JAKO PAS (08.08.2026, zgłoszenie Bartka: „nie widać, gdzie kończy się
   jeden dzień, a zaczyna następny").

   Dni stykają się bez przerwy i różnią TŁEM — co drugi jaśniejszy. Granica jest
   wtedy widoczna od razu, bez czytania dat, a odstęp niczego nie musi udawać:
   pusta przerwa między dniami była dwuznaczna, bo tej samej wielkości odstępy
   są też WEWNĄTRZ dnia, między godzinami.

   Tło bierzemy z tokenu zakresu (`--card-bg`), więc pas jest jaśniejszy od
   sekcji na KAŻDYM jej wariancie: na jasnym paśmie wychodzi biel, na ciemnym
   i akcentowym — rozjaśniona wersja tła pasa. Żadnego koloru wpisanego wprost. */
.front-intentions-day {
  margin: 0;
  padding: 18px 20px;
}
.front-intentions-day--alt { background: var(--card-bg); }

/* KRESKA POD NAGŁÓWKIEM JEST KRÓTKA (08.08.2026, to samo zgłoszenie).
   Na całą szerokość czytała się jak granica MIĘDZY DNIAMI, a oddziela tylko
   nagłówek dnia od jego godzin — czyli mówiła coś, czego nie znaczyła. Krótki
   odcinek podkreśla nagłówek i nie udaje separatora; rozdzielanie dni przejęło
   tło pasa. */
.front-intentions-day__head {
  position: relative;
  padding-bottom: 10px;
  margin-bottom: 12px;
}
.front-intentions-day__head::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 56px;
  height: 2px;
  background: var(--section-accent-ink);
}
.front-intentions-day__date { font-size: 22px; margin: 0; color: var(--ink); }
.front-intentions-day__feast {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
  color: var(--section-muted);
  margin-top: 2px;
}
.front-intentions-day__desc { font-size: 14px; font-style: italic; color: var(--muted); margin-top: 2px; }
/* ============================================================
   INTENCJE (przebudowa 31.07.2026, reguły 3+5).

   Było: jedna siatka `auto 1fr` na całą listę — w pasie pełnej szerokości treść
   intencji rozciągała się na 1217 px, czyli ~160 znaków w wierszu. W połowie
   czytało się dobrze, na pełnej fatalnie, i to była cała różnica.

   Jest: WIELOKOLUMNOWOŚĆ. Wybrana świadomie zamiast siatki, bo `columns` układa
   pozycje PIONOWO (kolumna po kolumnie), więc godziny czytają się od góry do
   dołu — przy siatce `auto-fit` szłyby w poprzek rzędami i porządek dnia by się
   rozsypał. Każda Msza to blok nierozrywalny.

   REGUŁA 5: to jest jedyne dopasowanie treści, jakie tu zachodzi — lista NIGDY
   nie jest skracana ani ucinana, bo intencje muszą być pokazane wszystkie.
     pełny  1272 cqi → 27cqi = 343 → 340 px → 3 kolumny po ~400 (wiersz ~340)
     połowa  596 cqi → 27cqi = 161 → 300 px → 1 kolumna
     ⅓       356 cqi →                300 px → 1 kolumna
   ============================================================ */
.front-intentions-masses {
  columns: clamp(300px, 27cqi, 340px);
  column-gap: 34px;
  margin: 0;
}
.front-intentions-mass {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px 18px;
  /* Msza nie może się rozerwać między kolumnami — godzina bez intencji albo
     intencja bez godziny to nie jest „ta sama treść, tylko inaczej ułożona". */
  break-inside: avoid;
  page-break-inside: avoid;
  padding-block: 4px;
}
.front-intentions-mass__time {
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: var(--section-link);
  white-space: nowrap;
}
.front-intentions-mass__type {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--section-accent-ink);
  margin-left: 6px;
}
.front-intentions-mass__body { margin: 0; }
.front-intentions-mass__desc { font-size: 14px; color: var(--section-muted); }
.front-intentions-list { margin: 0; padding-left: 18px; }
.front-intentions-list li { margin: 2px 0; line-height: 1.5; }
.front-intentions-free { font-size: 15px; font-style: italic; color: var(--section-muted); }
/* Intencje — para „godzina | treść" zwija się w pion według KONTENERA
   (23.07.2026, „miękki" przypadek z audytu: gęsto przy wielu intencjach w
   wąskiej kolumnie; ten sam mechanizm co reszta siatek go poprawia). */
@container (max-width: 30rem) {
  /* W wąskiej kolumnie godzina staje NAD intencją zamiast obok — mechanizm
     kolumn wyżej daje tu i tak jedną kolumnę, to jest ułożenie WEWNĄTRZ Mszy. */
  .front-intentions-mass { grid-template-columns: 1fr; gap: 2px; }
  .front-intentions-mass__body { margin: 0 0 10px 14px; }
}
@media (max-width: 900px) {
  .front-intentions-nav__range { margin-left: 0; width: 100%; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Baner zgody na cookies (moduł Statystyki, Etap 4). Minimalny, dolny pas,
   kolory z tokenów palety szablonu (--pri/--sec).
   ───────────────────────────────────────────────────────────────────────── */
.front-cookie-banner {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: 16px;
  z-index: var(--z-belka);
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 14px 18px;
  background: #fff;
  border: 1px solid #E4D8C9;
  border-radius: var(--radius-3xl);
  box-shadow: 0 6px 26px rgba(0, 0, 0, .16);
}
.front-cookie-banner__text {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: #3A2A2E;
  flex: 1 1 auto;
}
.front-cookie-banner__link {
  color: var(--pri, #4A2532);
  text-decoration: underline;
  white-space: nowrap;
}
.front-cookie-banner__actions {
  display: flex;
  gap: 8px;
  flex: 0 0 auto;
}
.front-cookie-banner__btn {
  padding: 8px 16px;
  border-radius: var(--radius-xl);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background-color .15s, border-color .15s;
}
.front-cookie-banner__btn--reject {
  background: transparent;
  border-color: #CBB79A;
  color: #6A4A50;
}
.front-cookie-banner__btn--reject:hover {
  border-color: var(--pri, #4A2532);
  color: var(--pri, #4A2532);
}
.front-cookie-banner__btn--accept {
  background: var(--pri, #4A2532);
  color: #fff;
}
.front-cookie-banner__btn--accept:hover {
  background: var(--sec, #B08A3E);
}
@media (max-width: 640px) {
  .front-cookie-banner {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  .front-cookie-banner__actions {
    justify-content: flex-end;
  }
}

/* ============================================================
   E2 Etap 3 — nakładka „wzór" (łuki/arkady)
   Kafel jest INLINE w Blade (front/partials/section-pattern.blade.php), bo
   zewnętrzny SVG nie dziedziczy currentColor i nie dałoby się go zabarwić
   paletą. Tu tylko pozycjonowanie i kolor.
   ============================================================ */
.front-pattern {
  position: absolute;
  inset: 0;
  z-index: 0;              /* POD treścią sekcji */
  color: var(--pattern-ink);
  pointer-events: none;    /* wzór nie łapie zdarzeń myszy */
  overflow: hidden;
}
.front-pattern__svg { width: 100%; height: 100%; display: block; }
/* Treść pasa musi lec NAD wzorem/zdjęciem (oba są pierwszym dzieckiem sekcji). */
.front-section > *:not(.front-pattern):not(.front-section-photo) { position: relative; z-index: 1; }
.front-pattern__rect { fill: var(--wzor-desktop); }
/* Węższe ekrany — mniejszy kafel (600 → 360). Przełączamy WŁAŚCIWOŚCIĄ CSS
   (fill), nie atrybutem SVG, więc media query działa pewnie. */
@media (max-width: 900px) {
  .front-pattern__rect { fill: var(--wzor-mobile); }
}

/* ============================================================
   Zdjęcie w tle sekcji (wariant „Zdjęcie", zmiana decyzji 19.07.2026) —
   zdjęcie + OBOWIĄZKOWA maska przyciemniająca (siła jako zmienna CSS INLINE,
   per sekcja — App\Support\Front\PhotoBackgroundMask pilnuje granic WCAG AA
   przy zapisie w panelu). Działa identycznie w rzędzie wielokolumnowym i
   w wąskiej kolumnie — to sekcja/kafel ma object-fit:cover, więc kadr się
   dopasowuje niezależnie od szerokości.
   ============================================================ */
.front-section-photo {
  position: absolute;
  inset: 0;
  z-index: 0;              /* POD treścią sekcji, jak .front-pattern */
  overflow: hidden;
  pointer-events: none;    /* zdjęcie nie łapie zdarzeń myszy */
}
.front-section-photo__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* ========== PARALAKSA TŁA PASA (29.07.2026) ==========
   Ten sam mechanizm co w banerze: animacja sterowana przewijaniem, zero
   nasłuchu w JavaScripcie, całość w `@supports` — przeglądarka bez obsługi
   pokazuje tło NIERUCHOME, czyli efekt nie istnieje zamiast działać gorzej.

   ZAKRES MNIEJSZY NIŻ W BANERZE (60%). Baner jest jeden i na górze strony, więc
   może zrobić wrażenie; pasów ze zdjęciem może być na stronie kilka i wtedy ten
   sam ruch powtórzony trzy razy robi z przewijania kołysanie. Liczony z tego
   samego tokenu, żeby oba miejsca zostały ze sobą związane — a ograniczenie
   ruchu w systemie zeruje token i wygasza jedno i drugie naraz.

   PRZEKSZTAŁCENIE LEŻY NA SAMYM ZDJĘCIU (liściu drzewa), nigdy na sekcji ani na
   `.front-section-photo`: sekcje strony głównej zawierają rozwijane menu,
   powiększane zdjęcia galerii i nakładkę baneru, a przekształcenie na przodku
   tworzy nowy kontekst układania i nowy blok zawierający dla elementów
   pozycjonowanych na sztywno.

   Zdjęcie jest WYŻSZE od kadru o dwa zakresy przesunięcia (po jednym na górę
   i dół), więc przy skrajnych położeniach nie odsłania krawędzi — także
   w pasie niskim, jednorzędowym. `.front-section-photo` ma `overflow: hidden`,
   więc nadmiar jest przycięty. */
/* NAPRAWA 29.07.2026 — patrz opis przy paralaksie baneru. Kadr
   `.front-section-photo` ma `overflow: hidden`, czyli JEST kontenerem
   przewijania, który się nie przewija — `view()` na zdjęciu w środku nie miało
   od czego liczyć postępu. Oś deklaruje więc SEKCJA (poza kadrem), a zdjęcie
   tylko się do niej odwołuje. */
@supports (animation-timeline: view()) and (view-timeline-name: --isp) {
  .front-section:has(> .front-section-photo) {
    view-timeline-name: --isp-pas;
    view-timeline-axis: block;
  }

  .front-section-photo__img {
    --section-parallax: calc(var(--motion-parallax) * 0.6);
    height: calc(100% + 2 * var(--section-parallax));
    top: calc(-1 * var(--section-parallax));
    position: relative;
    animation: isp-section-parallax linear both;
    animation-timeline: --isp-pas;
    animation-range: cover 0% cover 100%;
  }

  @keyframes isp-section-parallax {
    from { transform: translate3d(0, calc(-1 * var(--section-parallax)), 0); }
    to   { transform: translate3d(0, var(--section-parallax), 0); }
  }
}
.front-section-photo__mask {
  position: absolute;
  inset: 0;
  /* rgba() z alfą ZE ZMIENNEJ (nie kolor wpisany wprost) — dozwolone przez
     strażnika SectionScopeLiteralColoursTest (biała lista, uzasadnienie tam). */
  background: rgba(0, 0, 0, var(--section-photo-mask, 0.65));
}

/* Zastępniki zdjęć W STOPCE (18.07.2026). Stopka to osobny, ZAWSZE ciemny
   element poza mechanizmem tła sekcji — zmienne zakresu mają tu domyślne,
   jasne wartości, więc miniatury bez grafiki świeciły jasnymi plamami na
   ciemnym tle. Przestawiamy sam zakres zmiennych: reguła `.img-placeholder`
   zostaje jedna, a zastępnik sam dopasowuje się do stopki. */
.front-footer {
  --card-bg-alt: rgba(255, 255, 255, 0.06);
  --card-muted: var(--footer-ink-dim);
  --section-accent: rgba(255, 255, 255, 0.10);
}

/* Zastępnik brakującej miniatury (front/partials/media-placeholder.blade.php) —
   ikona zamiast dawnego znaku z kroju pisma; kolor z tokenów sekcji. */
.img-placeholder { display: grid; place-items: center; }
.media-placeholder__icon { display: block; width: 28%; min-width: 20px; max-width: 44px; opacity: 0.55; }
.media-placeholder__icon svg { width: 100%; height: 100%; display: block; }

/* =========================== BIURO PARAFIALNE (struktura) ===========================
   Wspólny render dla kafla Informacji, nakładki baneru i stopki
   (front/partials/office-details.blade.php). */
.office-details { display: grid; gap: 10px; }
.office-details__location { margin: 0; font-size: 15px; line-height: 1.5; }
.office-details__hours { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.office-details__row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; font-size: 15px; }
.office-details__days { font-weight: 600; }
.office-details__time { white-space: nowrap; }
.office-details__note { font-size: 13px; color: var(--muted); }
.office-details__closed { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.5; }
.office-details__notes { font-size: 14px; }

/* TELEFON DYŻURNY — celowo wyróżniony: najczęściej szukany numer na stronie
   parafii (sięgają po niego w nocy). Wcześniej ginął w środku akapitu. */
.office-duty {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px;
  border-left: 3px solid var(--section-accent, var(--sec));
  background: var(--surface-alt);
}
.office-duty__icon { display: block; width: 20px; height: 20px; flex: 0 0 auto; margin-top: 2px; color: var(--section-accent, var(--sec)); }
.office-duty__icon svg { width: 100%; height: 100%; display: block; }
.office-duty__label { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.office-duty__phone { display: inline-block; font-size: 18px; font-weight: 700; color: var(--pri); text-decoration: none; }
.office-duty__note { font-size: 13px; color: var(--muted); line-height: 1.45; }

/* Wariant stopki — jaśniejsza paleta na ciemnym tle. */
.front-footer__col .office-duty { background: rgba(255, 255, 255, 0.06); border-left-color: var(--footer-ink); }
.front-footer__col .office-duty__phone { color: #FFFFFF; }
.front-footer__col .office-details__note,
.front-footer__col .office-details__closed,
.front-footer__col .office-duty__label,
.front-footer__col .office-duty__note { color: var(--footer-ink-dim); }

/* =========================== GENEROWANE PODSTRONY ===========================
   „Msze i nabożeństwa" oraz „Kontakt" (Etap 3). Sekcje składane z danych
   parafii — pusta sekcja NIE renderuje się wcale, więc odstępy są na blokach,
   nie na separatorach. */
.front-page-block { margin: 0 0 34px; }
.front-page-block:last-child { margin-bottom: 0; }
.front-page-block__title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  font-size: clamp(20px, 2.4vw, 26px);
  font-weight: 600;
  color: var(--section-heading, var(--pri));
}
.front-page-block__icon { display: block; width: 24px; height: 24px; flex: 0 0 auto; color: var(--section-accent, var(--sec)); }
.front-page-block__icon svg { width: 100%; height: 100%; display: block; }

/* Lista nabożeństw — wspólna dla podstrony i (od Etapu 4) widgetu. */
.devotions-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.devotions-list__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  padding-bottom: 8px;
  border-bottom: 1px dashed var(--border-2);
}
.devotions-list__row:last-child { border-bottom: 0; padding-bottom: 0; }
.devotions-list__name { font-weight: 700; color: var(--section-text, var(--ink)); }
.devotions-list__schedule { color: var(--section-text-soft, var(--ink-2)); }
.devotions-list__note { font-size: 13px; color: var(--muted); }

/* Dane kontaktowe — wspólny render (kafel, stopka, podstrona, widget). */
.contact-details { display: grid; gap: 12px; }
.contact-details__name { font-size: 18px; font-weight: 600; color: var(--section-heading, var(--pri)); }
.contact-details__lines { display: grid; gap: 7px; }
.contact-details__row { display: flex; align-items: flex-start; gap: 9px; font-size: 16px; }
.contact-details__icon { display: block; width: 18px; height: 18px; flex: 0 0 auto; margin-top: 2px; color: var(--section-accent, var(--sec)); }
.contact-details__icon svg { width: 100%; height: 100%; display: block; }
.contact-details__payments { display: grid; gap: 10px; padding-top: 12px; border-top: 1px dashed var(--border-2); }
.contact-details__label { display: flex; align-items: center; gap: 7px; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.contact-details__value { font-size: 15px; line-height: 1.5; }

/* Stopka: ten sam markup na ciemnym tle. */
.front-footer__col .contact-details__name { color: #FFFFFF; }
.front-footer__col .contact-details__label { color: var(--footer-ink-dim); }

/* =========================== PORZĄDEK MSZY — SIATKA ===========================
   Etap 4. Wcześniej godziny szły ciągiem rozdzielonym `·`, gdzie separator był
   osobnym elementem z marginesami — przy zawijaniu lądował na początku wiersza
   i powstawał rozstrzelony bałagan z wiszącymi kropkami.

   Teraz: nazwa dnia NAGŁÓWKIEM nad rzędem, każda godzina osobną komórką równej
   szerokości, ZERO separatorów. auto-fit + minmax daje sześć kolumn w szerokim
   kontenerze i schodzi do trzech w wąskim — bez media queries, bo o liczbie
   kolumn decyduje realna szerokość KONTENERA, nie ekranu (ten sam komponent
   żyje w pasie, w wąskiej kolumnie bocznej i w stopce). */
/* JEDNA LICZBA NA WARIANT: stopień pisma godziny; szerokość komórki i górna
   granica rzędu z niej wynikają (06.09.2026). Mnożnik 2,6 to nie szerokość
   glifu — „10³⁰" zajmuje 36 px przy 22 px pisma — tylko GÓRNA GRANICA, która ma
   powstrzymać rozciąganie grupy z mniejszą liczbą godzin. Progi kontenerowe niżej
   liczą się od GLIFU, nie od tej granicy, i dlatego są od niej mniejsze. */
.mass-schedule {
  --time-size: 22px;
  --time-cell: calc(var(--time-size) * 2.6);
  display: grid;
  gap: 16px;
  /* Zawijanie rzędu godzin ma zależeć od SZEROKOŚCI TEGO KOMPONENTU, nie od
     ekranu — ten sam porządek stoi w szerokim pasie, w wąskiej kolumnie
     bocznej, w stopce i w nakładce baneru. */
  container-type: inline-size;
  container-name: mass;
}
.mass-schedule__group { display: grid; gap: 8px; }
.mass-schedule__day {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--section-text-soft, var(--muted));
}

/* Rząd godzin. Flex, nie grid, ŚWIADOMIE: `justify-content: center` środkuje
   ostatni NIEPEŁNY rząd (5 godzin → 3 + 2 pod spodem, wyśrodkowane), czego
   grid nie potrafi bez liczenia pozycji. Szerokość komórki liczona z --cols,
   więc rzędy są równe, a nie „ile się zmieści". */
/* RZĄD MIEŚCI DOKŁADNIE `--cols` GODZIN (06.09.2026). Sama górna granica na
   KOMÓRCE tego nie zapewnia: gdy podstawa flexa jest szersza od granicy, komórki
   kurczą się do niej i w rzędzie mieści się ich WIĘCEJ, niż policzył serwer.
   Skutek był realny: siedem godzin na podstronie „Msze i nabożeństwa" (kontener
   ~1000 px, podział serwera 4 + 3) szło jednym rzędem po siedem — czyli i ponad
   górną granicą sześciu, i wbrew podziałowi. Granica na RZĘDZIE domyka kontrakt.
   `+1px` na zaokrąglenia subpikselowe: rząd o szerokości co do piksela równej
   swojej treści łamie ostatnią komórkę. */
.mass-schedule__times {
  --gap: 8px;
  --cols: var(--cols-6, 6);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--gap);
  width: min(100%, calc(var(--cols) * var(--time-cell) + (var(--cols) - 1) * var(--gap) + 1px));
  margin-inline: auto;
}
.mass-schedule__times > .mass-time {
  /* `0 1` (nie `0 0`) + górna granica szerokości: przy PEŁNYM rzędzie komórki
     kurczą się do wyliczonej szerokości, a przy grupie z mniejszą liczbą godzin
     NIE rozciągają się na całą kolumnę — trzy godziny stały wtedy rozstrzelone
     na 240 px. Odstęp jest stały, a cała grupa wyśrodkowana (justify-content). */
  flex: 0 1 calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols));
  max-width: var(--time-cell);
}
/* Wąski kontener: najpierw trzy godziny w rzędzie, potem dwie. `min()` działa
   tu dlatego, że --cols trafia do calc(), a nie do repeat() — grid by tego
   nie przyjął (repeat wymaga stałej liczby całkowitej). */
/* Każdy próg bierze GOTOWY podział policzony przez serwer dla tej szerokości —
   nie obcina liczby kolumn, bo obcinanie łamałoby regułę o sierocie w ostatnim
   rzędzie (siedem godzin obcięte do sześciu dałoby 6 + 1 zamiast 4 + 3). */
/* PROGI PRZELICZONE Z REALNYCH POMIARÓW (26.07.2026), nie z oka.
   Najszerszy realny przypadek to godzina dwucyfrowa z minutami („10³⁰"):
     wariant pasa/podstrony (22 px)  →  36 px na godzinę
     wariant nakładki i stopki (17 px) → 28 px na godzinę
   Sześć godzin + pięć odstępów po 8 px potrzebuje więc 256 px (16 rem)
   w wariancie większym i 208 px (13 rem) w mniejszym. Progi biorą wariant
   WIĘKSZY, bo jeden zestaw obsługuje wszystkie umiejscowienia — w nakładce
   i stopce zostaje dzięki temu zapas. */
@container mass (max-width: 16rem) {
  .mass-schedule__times { --cols: var(--cols-4, 4); }
}
@container mass (max-width: 11rem) {
  .mass-schedule__times { --cols: var(--cols-3, 3); }
}
@container mass (max-width: 8rem) {
  .mass-schedule__times { --cols: var(--cols-2, 2); }
}

/* ---- Godzina: KLASYK — „7³⁰”, drukowany porządek nabożeństw ----
   Bez ramki, bez tła, bez plakietki. Minuty jako subtelny indeks górny;
   dwukropek jest w treści (czytnik ekranu i kopiowanie dostają „7:30”),
   ale wizualnie znika. */
.mass-time {
  display: block;
  text-align: center;
  white-space: nowrap;
  font-family: var(--font-serif);
  /* 22 px zamiast 26 (26.07.2026): przy sześciu godzinach w rzędzie liczy się
     szerokość, a nie wielkość liter. Zmierzone: „10³⁰" zajmuje przy 26 px
     42 px, przy 22 px — 36 px. */
  font-size: var(--time-size);
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--section-text, var(--ink));
}
.mass-time__sep {
  /* Ukryty WIZUALNIE, nie usunięty — inaczej skopiowany tekst dałby „730”. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.mass-time__minute {
  /* PODŁOGA CZYTELNOŚCI (08.08.2026). Minuta jest ułamkiem stopnia godziny, więc
     każdy nowy, drobniejszy wariant `.mass-time` po cichu spycha ją poniżej progu
     — tak powstało 9,86 px w stopce, niezauważone przez pół roku. `max()` pilnuje
     dolnej granicy sam, bez pamiętania o niej przy każdym wariancie. Tam, gdzie
     proporcja daje więcej (pas 12,8 px, nakładka 13,3 px), nie zmienia nic. */
  font-size: max(var(--text-floor), 0.58em);
  /* Uniesienie SUBTELNE — `vertical-align: super` podnosi za wysoko i wygląda
     jak indeks matematyczny. */
  vertical-align: 0.42em;
  letter-spacing: 0.02em;
  margin-left: 0.06em;
}

/* Wariant zwarty (kolumna boczna) — mniejszy stopień, ciaśniejszy rząd. */
.mass-schedule--compact { gap: 12px; }
.mass-schedule--compact .mass-schedule__times { --gap: 8px; }
.mass-schedule--compact { --time-size: 21px; }

/* Wariant stopki — ciemne tło. Kolor z tokenów stopki, reszta bez zmian:
   ta sama typografia, bo to ten sam element, tylko na innym tle. */
/* W stopce i w nakładce baneru liczy się WYSOKOŚĆ panelu, nie wielkość liter —
   mniejszy stopień pozwala zmieścić sześć godzin w rzędzie w kolumnie ~300 px. */
/* C1 (26.07.2026): w wąskiej kolumnie stopki reszta treści (etykiety, adres,
   linki) stoi do lewej — środkowanie godzin/nazw dni (dziedziczone z wariantu
   bazowego, dobre w szerokim pasie) się z tym rozjeżdżało. TYLKO w stopce;
   pas, nakładka baneru i wariant --compact zostają wyśrodkowane. */
/* A2 (08.08.2026) — DOKOŃCZENIE POPRAWKI A1. Przy 17 px minuta wychodziła
   9,86 px (0.58em), czyli poniżej progu czytelności tekstu funkcjonalnego, a to
   NAJCZĘŚCIEJ SZUKANA informacja na stronie parafii — czytana często przez osoby
   starsze. A1 uznało 17 px za za drobne i podniosło nakładkę baneru do 23 px,
   ale stopka została pominięta: te same style, dwa różne miejsca użycia.
   20 px daje minutę 11,6 px i mieści sześć godzin w rzędzie kolumny stopki. */
.mass-schedule--footer { --time-size: 20px; }
.mass-schedule--footer .mass-time { color: var(--footer-ink, #FFFFFF); text-align: left; }
/* Stopka wyrównuje rząd do LEWEJ, więc rząd zwężony granicą nie może być
   dodatkowo środkowany marginesami. */
.mass-schedule--footer .mass-schedule__times { --gap: 6px; justify-content: flex-start; margin-inline: 0; }
/* A1 (26.07.2026): 17 px było za drobne. Zapas w kolumnie idzie na STOPIEŃ
   PISMA, nie na przerwy — przy 23 px i odstępie 4 px sześć godzin zajmuje
   ~236 px z 307 dostępnych, więc rząd zostaje, a cyfry są czytelne. */
.front-banner-info__panel .mass-schedule { --time-size: 23px; }
.front-banner-info__panel .mass-schedule__times { --gap: 4px; }
.mass-schedule--footer .mass-schedule__day { color: var(--footer-ink-dim); text-align: left; }

/* PODSTRONA „MSZE I NABOŻEŃSTWA" — WSZYSTKO DO LEWEJ (06.09.2026, A27).

   Etykieta grupy stała przy lewej krawędzi, a rząd godzin na środku — dwa
   wyrównania w jednym bloku czytały się jak pomyłka składu.

   ⚠️ TO NIE JEST ODWRÓCENIE DECYZJI B7. Panel „Informacje podstawowe", stopka
   i ściągawka w kolumnie bocznej ZOSTAJĄ wyśrodkowane i tak ma być: tam godziny
   stoją zwartym rzędem bez niczego obok, więc środek jest ich naturalną osią.
   Na podstronie rząd sąsiaduje z etykietą grupy, adnotacją i nagłówkiem sekcji —
   wszystkim, co zaczyna się przy lewej krawędzi. Różnica wynika z SĄSIEDZTWA,
   nie z upodobania; pełny zapis w docs/UI-STYLE.md. Nie ujednolicać w żadną stronę.
   Dotyczy OBU szablonów — Nowoczesny ma tę samą regułę w public/css/front-modern.css. */
.front-page-block .mass-schedule__day { text-align: left; }
.front-page-block .mass-schedule__times { justify-content: flex-start; margin-inline: 0; }
.front-page-block .mass-time { text-align: left; }


/* =========================== BLOKI INFORMACJI ===========================
   Wspólne wnętrze bloku (front/partials/info-blocks.blade.php) — kontener
   narzuca szablon, wnętrze wygląda wszędzie tak samo. */
.info-block__title {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 12px;
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 600;
  color: var(--card-link, var(--pri));
}
.info-block__icon { display: block; width: 22px; height: 22px; flex: 0 0 auto; color: var(--section-accent, var(--sec)); }
.info-block__icon svg { width: 100%; height: 100%; display: block; }
/* PRZYCISK „zobacz całość" (25.07.2026, dawniej podkreślony odnośnik).
   Pojawia się tylko wtedy, gdy proza faktycznie została przycięta — jest więc
   obietnicą z pokryciem, a nie ozdobą.

   Rola DRUGORZĘDNA (obrys, przezroczyste tło), a kolor dziedziczony przez
   `currentColor` z bloku. Dzięki temu ten sam przycisk jest czytelny w trzech
   zupełnie różnych miejscach — na jasnym pasie, na ZAWSZE ciemnej stopce
   i na nakładce leżącej na zdjęciu — bez ani jednej łatki per miejsce. */
.info-block__more {
  display: inline-block;
  margin-top: 14px;
  padding: 7px 16px;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--card-link, var(--pri));
  border: 1px solid currentColor;
  border-radius: var(--radius-pill);
  background: transparent;
  text-decoration: none;
  transition: background-color var(--motion-state) var(--motion-ease-state), color var(--motion-state) var(--motion-ease-state);
}
/* Najechanie WYPEŁNIA przycisk kolorem obrysu, a napis bierze kolor tła bloku —
   działa na każdym tle, bo oba kolory są tokenami zakresu sekcji. */
.info-block__more:hover {
  background: var(--card-link, var(--pri));
  color: var(--card-bg, var(--surface));
}
.info-block__more:active { background: var(--card-link, var(--pri)); color: var(--card-bg, var(--surface)); }

/* --- Wariant ŚCIĄGAWKI (kolumna boczna) --- */
.front-sidebar-info { display: grid; gap: 14px; margin-bottom: 18px; }
.sidebar-info-box {
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--border-2);
}
.sidebar-info-box .info-block__title { font-size: 17px; margin-bottom: 10px; }
.sidebar-info-box .info-block__icon { width: 18px; height: 18px; }
.sidebar-info-box .info-block__more { margin-top: 10px; font-size: 13px; }

.info-phone { display: grid; gap: 10px; }
.info-phone__number { display: inline-block; font-size: 18px; font-weight: 700; color: var(--pri); text-decoration: none; }
.info-phone__duty { padding-top: 10px; border-top: 1px dashed var(--border-2); }
.info-phone__label { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.info-phone__note { font-size: 13px; color: var(--muted); line-height: 1.45; }

/* BŁĄD KLASYKA (Etap 4): pas Informacji w STREFIE DWUKOLUMNOWEJ wnosił ze sobą
   tło i wzór pasa pełnej szerokości — element zaprojektowany do samodzielnego
   pasa, przez co w kolumnie „zaciągał" obce kolory tła i tracił marginesy.
   W strefie sekcja ma być przezroczysta i mieć własny odstęp. */
.front-main .front-quick,
.front-main .front-section.front-quick {
  background: none !important;
  margin: 0 0 28px;
  padding: 0;
}
.front-main .front-quick .front-pattern,
.front-main .front-quick .front-section-photo { display: none; }

/* Nakładka „Zobacz pełne informacje" — TYLKO desktop (na telefonie odnośnik
   prowadzi wprost na podstronę; patrz public/js/front.js). */
.info-overlay { position: fixed; inset: 0; z-index: var(--z-nakladka); display: grid; place-items: center; }
.info-overlay[hidden] { display: none; }

@media (max-width: 900px) {
  /* Nakładka nie istnieje na wąskich ekranach — bezpiecznik, gdyby JS
     kiedykolwiek ją otworzył mimo progu szerokości. */
  .info-overlay { display: none; }
}

/* =========================== PAS WIELOKOLUMNOWY ===========================
   Sąsiadujące widgety pełnej szerokości sklejone w rząd (punkt 1, 19.07.2026).
   Grupowanie robi PHP (App\Support\Front\WidgetRows); tu tylko szerokości.

   flex: 0 0 <basis> — komórka zajmuje DOKŁADNIE zadaną szerokość, bez
   rozciągania. `justify-content: center` na samym rzędzie: gdy komórki
   sumują się do pełnych 12 jednostek, i tak wypełniają całą szerokość —
   środkowanie niczego wtedy nie zmienia. Gdy rząd jest NIEPEŁNY (np. ½ + ⅓ =
   ⅚), zostaje wyśrodkowany zamiast wyrównany do lewej (poprawka 26.07.2026 —
   ujednolicenie z dwoma pozostałymi miejscami, które niepełny rząd już
   środkowały: ostatni rząd godzin Mszy — `.mass-schedule__times` niżej — i
   samotny widget w rzędzie — `.home-row--solo`; JEDNA reguła zamiast wyjątku).
   gap odejmowany od basis, żeby dwie połówki + gap mieściły się w 100%. */
.home-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 24px;
  align-items: stretch;
  /* Rząd renderuje się POZA strefą (bezpośrednio pod .front-root, pełne okno),
     więc SAM ogranicza treść do kontenera-szerokiego i dostaje rytm pasa. */
  max-width: var(--tresc-max);
  margin-inline: auto;
  padding-inline: 24px;
  padding-block: var(--band-space);
}
.home-row__cell { flex: 0 0 100%; min-width: 0; }
.home-row__cell > * { height: 100%; }
/* Widget W KOMÓRCE rzędu nie jest pełnoszerokościowym pasem — rytm i wcięcie
   daje rząd, nie każda karta. Zerujemy padding pasa; kartom band przywracamy
   własne, skromne wcięcie (inaczej treść dotknęłaby krawędzi komórki). */
/* WCIĘCIE WEWNĘTRZNE BLOKU W WIERSZU (poprawka 31.07.2026).
   Do tej pory padding dostawał WYŁĄCZNIE wariant karty (`--band`), a pozostałe
   bloki miały zero — w pasie trójdzielnym Ogłoszenia stały wcięte o 26 px,
   a Ewangelia i Wiadomości przyklejone do krawędzi. Trzy bloki jednego pasa mają
   wyglądać jak rodzeństwo, więc wcięcie jest WSPÓLNE. */
.home-row__cell section.front-section { padding: clamp(18px, 4cqi, var(--trio-pad)); }

@media (min-width: 901px) {
  .home-row__cell--half  { flex: 0 0 calc(50% - 12px); }
  .home-row__cell--third { flex: 0 0 calc(33.333% - 16px); }
  /* full zostaje 100% (domyślne) — pełny widget nigdy nie stoi w rzędzie
     z innym, WidgetRows daje mu osobny wiersz. */
}

/* ============================================================
   WIDGETY TREŚCI ZEWNĘTRZNYCH (23.07.2026) — Ewangelia dnia,
   Transmisja na żywo, Wiadomości katolickie.
   Kolory WYŁĄCZNIE ze zmiennych zakresu sekcji (--section-… i --card-…),
   żeby działały na każdym wariancie tła pasa.
   ============================================================ */

/* ---- Ewangelia dnia ---- */
.front-gospel__day { display: flex; align-items: center; gap: 10px; font-weight: 700; color: var(--section-text); }
.front-gospel__ref { margin: 6px 0 14px; color: var(--section-muted); font-size: 15px; max-width: var(--proza-max); }
.front-gospel__text-title { margin: 0 0 8px; font-family: var(--font-serif); font-size: 20px; font-weight: 600; color: var(--section-heading); }
/* Zdanie Pisma to proza — ta sama miara wiersza co w Ogłoszeniach (reguła 3). */
.front-gospel__body { margin: 0; color: var(--section-text-soft, var(--section-text)); line-height: 1.65; max-width: var(--proza-max); }
/* Wiersz domykający — sam przycisk, do prawej (01.08.2026). */
.front-gospel__foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 10px 16px; margin-top: 18px; }
/* Atrybucja stoi NAD przyciskiem jako akapit, nie obok niego w wierszu. */
.front-gospel__credit { display: block; margin: 16px 0 0; font-size: 13px; color: var(--section-muted); }
.front-gospel__credit a { color: inherit; }

/* DWIE FORMY PREZENTACJI WYNIKAJĄ Z SZEROKOŚCI KONTENERA, nie z ustawienia
   w panelu (wymaganie Bartka). W wąskim pasie (⅓, kolumna boczna) tekst
   skraca się do kilku pierwszych zdań, a „czytaj więcej" prowadzi do źródła. */
@container (max-width: 34rem) {
  .front-gospel__body {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}

/* ---- Transmisja na żywo ---- */
/* REGUŁA 2: odtwarzacz rośnie z pasem zamiast stać w stałych 860 px i zostawiać
   przy pełnej szerokości 400 px pustki po prawej. Wyśrodkowany, bo transmisja
   nie ma obok siebie tekstu, do którego miałaby się wyrównywać.
     pełny  1272 cqi → 78cqi = 992 px    połowa/⅓ → cała szerokość kolumny */
.front-live__embed { max-width: min(100%, clamp(520px, 78cqi, 1000px)); margin-inline: auto; }
.front-live__link-tile {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 18px 20px;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  color: var(--card-text);
  text-decoration: none;
  font-weight: 700;
}
.front-live__icon { flex: 0 0 auto; width: 24px; height: 24px; color: var(--section-accent); }

/* ---- Wiadomości katolickie ---- */
/* REGUŁA 3 dla listy nagłówków: w pasie pełnej szerokości tytuł rozciągał się
   na 1272 px. Nagłówki są od siebie NIEZALEŻNE, więc dzielimy je na kolumny
   zamiast ograniczać szerokość i zostawiać pół pasa pusto.
     pełny  1272 cqi → 26cqi = 331 → 331 px → 3 kolumny
     połowa  596 cqi → 26cqi = 155 → 280 px → 1 kolumna
     ⅓       356 cqi →               280 px → 1 kolumna */
.front-extnews__list {
  list-style: none;
  margin: 0;
  padding: 0;
  columns: clamp(280px, 26cqi, 360px);
  column-gap: 32px;
}
.front-extnews__item { break-inside: avoid; page-break-inside: avoid; }
.front-extnews__item { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--card-border); }
.front-extnews__item:last-child { border-bottom: 0; }
.front-extnews__icon { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 3px; color: var(--section-accent); }
.front-extnews__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.front-extnews__title { font-weight: 700; color: var(--section-text); text-decoration: none; line-height: 1.45; }
a.front-extnews__title:hover { color: var(--section-accent-ink); }
.front-extnews__meta { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 13px; color: var(--section-muted); }
.front-extnews__source { font-weight: 700; }
.front-extnews__foot { margin-top: 16px; font-size: 12px; color: var(--section-muted); line-height: 1.5; }

/* ============================================================
   SAKRAMENTY I POSŁUGI (23.07.2026) — wizualne menu „co chcesz załatwić".
   Siatka okrągłych kafli. Zwijanie WEDŁUG KONTENERA (nie ekranu), więc
   działa tak samo w pasie pełnym, ½, ⅓ i w kolumnie strefy.
   minmax na kontenerze: liczba kolumn wynika z szerokości TREŚCI, a nie
   z liczby kafli — dzięki temu 4, 6 i 8 kafli układa się równo.
   ============================================================ */
.sacrament-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* REGUŁA 1+2 (31.07.2026). Stałe 240 px dawało przy pełnej szerokości PIĘĆ
     kafli (zmierzone: 1272 px treści / 258 = 5), więc ośmioelementowy zestaw
     układał się „pięć plus trzy". Minimalna szerokość kafla sama skaluje się z
     kontenerem, przez co liczba kolumn wychodzi z szerokości pasa:
        pełny  1272 cqi → 21cqi = 267 → 267 px →  4 kafle (równy rząd)
        połowa  596 cqi → 21cqi = 125 → 150 px →  3 kafle
        ⅓       356 cqi → 21cqi =  75 → 150 px →  2 kafle
     Górna granica 270 px jest dobrana tak, żeby przy pełnej szerokości nie
     zmieściło się pięć (potrzeba > 249,6 px), a zmieściły cztery (≤ 316,5 px). */
  --kafel: clamp(150px, 23.5cqi, 300px);
  --kafel-max: 330px;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--kafel), 100%), 1fr));
  justify-content: var(--kafle-justify, start);
  gap: 28px 18px;
}
/* Komórka jest WŁASNYM kontenerem, więc koło i podpis skalują się szerokością
   KOLUMNY, a nie całego pasa — o to chodzi w regule 2. */
.sacrament-grid__cell { min-width: 0; container-type: inline-size; }
.sacrament-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
  text-decoration: none;
  color: inherit;
}
/* Kadr KOŁOWY z punktem ogniskowym — ten sam mechanizm co portrety
   Duszpasterzy (object-fit: cover + object-position z focal). */
.sacrament-tile__photo {
  /* REGUŁA 2: koło rośnie razem z kolumną (cqi liczy się od komórki siatki).
     Zakres 92–150 px: dolna granica to rozmiar, poniżej którego ikona przestaje
     być czytelna, górna — powyżej którego koło zaczyna dominować nad podpisem.
     Kolumna 267 px → 150 px, kolumna 170 px → 96 px. */
  width: clamp(92px, 56cqi, 150px);
  height: clamp(92px, 56cqi, 150px);
  border-radius: var(--radius-circle);
  overflow: hidden;
  background: var(--card-bg-alt);
  border: 3px solid var(--section-accent);
  color: var(--card-muted);
  display: grid;
  place-items: center;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.sacrament-tile__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sacrament-tile__icon { width: 44%; height: 44%; }
.sacrament-tile:hover .sacrament-tile__photo { transform: translateY(-3px); box-shadow: var(--shadow-1); }
.sacrament-tile__caption {
  font-family: var(--font-serif);
  /* Podpis idzie za kołem — inaczej duży kafel miałby drobny napis pod spodem. */
  font-size: clamp(15px, 7cqi, 19px);
  font-weight: 600;
  line-height: 1.25;
  color: var(--section-text);
  text-wrap: balance;
}
.sacrament-tile:hover .sacrament-tile__caption { color: var(--section-accent-ink); }

/* W wąskim kontenerze zostaje sam ciaśniejszy odstęp — liczbę kolumn i rozmiar
   koła liczy już mechanizm wyżej (dawne `repeat(2, …)` i sztywne 92 px zostały
   USUNIĘTE 31.07.2026: wymuszały dokładnie dwa kafle także tam, gdzie zmieściłby
   się trzeci, i zamrażały koło zamiast pozwolić mu rosnąć). */
@container (max-width: 30rem) {
  .sacrament-grid { gap: 22px 14px; }
}

/* Ikona odtwarzania na kaflu galerii WIDEO (widget Galerie, filtr typu,
   23.07.2026) — leży NA OKŁADCE, więc biel jest poprawna niezależnie od tła
   sekcji (wpis na białej liście SectionScopeLiteralColoursTest). Wzorzec 1:1
   z .video-tile__play. */
.gallery-tile__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: var(--z-plakietka);
  width: 54px;
  height: 54px;
  border-radius: var(--radius-circle);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #FFFFFF;
  background: rgba(20, 12, 10, 0.55);
  border: 2px solid rgba(255, 255, 255, 0.9);
  transition: background var(--motion-state) var(--motion-ease-state), transform var(--motion-state) var(--motion-ease-state);
  pointer-events: none;
}
.gallery-tile--video:hover .gallery-tile__play { background: var(--section-accent); transform: translate(-50%, -50%) scale(1.06); }

/* Widgety w KOLUMNIE BOCZNEJ (placement 'sidebar', 23.07.2026) — sekcja pasa
   wciśnięta w wąską kolumnę gubi rytm pełnego pasa: zerujemy padding pasa i
   dajemy zwarty margines (jak reszta boksów bocznych). container-type z bazy
   działa dalej → treść zwija się według szerokości kolumny. */
.front-sidebar section.front-section {
  padding: 0;
  margin: 0 0 18px;
  background: transparent;
}
.front-sidebar section.front-section:last-child { margin-bottom: 0; }
.front-sidebar section.front-section .front-pattern,
.front-sidebar section.front-section .front-section-photo { display: none; }

/* Pomiar wysokości nagłówka (skrypt front/partials/header-fit): oba stany —
   pełny i zwinięty — mierzymy z WYŁĄCZONYMI przejściami, inaczej dostalibyśmy
   wysokość w połowie animacji. Atrybut żyje ułamek milisekundy, w tej samej
   klatce, więc nic nie zdąży mrugnąć. */
[data-header-measuring-height],
[data-header-measuring-height] * { transition: none !important; }

/* ============================================================
   STANY INTERAKCJI — KLASYK (25.07.2026, Etap 3 pracy nocnej)

   Rozszerzenie wzorca ustalonego na kaflu aktualności na resztę elementów.
   Reguły, od których nie ma tu odstępstw:

     * AKCENT ZNACZY „TU JESTEŚ" i nic innego — najechanie mówi WŁASNYM
       kolorem tekstu elementu, żeby dwa komunikaty się nie myliły.
     * ŻADEN stan nie zmienia wymiarów — obwódki zmieniają kolor, nie grubość;
       cienie i obrysy leżą poza pudełkiem; wewnątrz przesuwa się co najwyżej
       odstęp między słowami.
     * Najechanie nigdy nie jest jedynym sygnałem: wciśnięcie musi być czytelne
       samo z siebie, bo to jedyny stan działający na dotyku.
     * Fokus klawiatury obsługuje jedna globalna reguła wyżej (`--section-focus`)
       — tutaj nie dublujemy go per element.

   Charakter Klasyka: podkreślenia, miękkie cienie, obwódki. Uniesienie
   i pierścienie są językiem Nowoczesnego (front-modern.css).
   ============================================================ */

/* ---- Nawigacja: menu górne, rozwijane, boczne, mobilne ---- */
.front-topnav__link:active,
.front-topnav__sublink:active,
.front-mobilenav a:active { color: var(--pri); }
.front-sidebar__row:active { background: var(--surface-2); }
.front-sidebar__sublink:active { background: var(--surface-2); }

/* ---- Zakładki podstrony ----
   Aktywna zakładka niesie akcent (przyciemniony — patrz Etap 6), najechanie
   tylko własnym kolorem tekstu i cienką kreską, żeby nie udawało aktywnej. */
.front-tabs__tab:hover { color: var(--pri); border-bottom-color: var(--border); }
.front-tabs__tab:active { color: var(--pri); background: var(--surface-2); }

/* ---- Kafel galerii ----
   Ten sam język co kafel aktualności: obwódka + cień przy najechaniu,
   przyciemnienie przy wciśnięciu. Cień nie działa na ciemnym pasie, więc tam
   rolę przejmuje obwódka (kolor z zakresu sekcji). */
.gallery-tile {
  transition: box-shadow var(--motion-state) var(--motion-ease-state), border-color var(--motion-state) var(--motion-ease-state);
}
.gallery-tile:hover { border-color: var(--card-border-hover); }
.gallery-tile:active { box-shadow: none; border-color: var(--card-border-hover); }
.gallery-tile:active .gallery-tile__img { opacity: 0.88; }

/* ---- Kafel sakramentu (zdjęcie w kółku) ----
   Kółko dostaje pierścień w kolorze tekstu; podpis się podkreśla. Rozmiar
   zdjęcia BEZ ZMIAN — pierścień rysuje `box-shadow`, więc nie przesuwa sąsiadów. */
.sacrament-tile__photo { transition: box-shadow var(--motion-state) var(--motion-ease-state); }
.sacrament-tile:hover .sacrament-tile__photo { box-shadow: 0 0 0 3px var(--card-bg), 0 0 0 5px currentColor; }
.sacrament-tile:hover .sacrament-tile__caption { text-decoration: underline; text-underline-offset: 3px; }
.sacrament-tile:active .sacrament-tile__photo { box-shadow: 0 0 0 3px var(--card-bg), 0 0 0 5px currentColor; opacity: 0.9; }

/* ---- Odnośniki „→" i odnośniki stopki ----
   Strzałka jest częścią napisu (tłumaczenie), więc odsuwamy ją odstępem
   MIĘDZY SŁOWAMI — napis zostaje w jednej linii, nic nie zmienia wymiarów. */
.link-more { transition: word-spacing var(--motion-state) var(--motion-ease-state), color var(--motion-state) var(--motion-ease-state); }
.link-more:hover { word-spacing: 5px; text-decoration: underline; text-underline-offset: 3px; }
.link-more:active { word-spacing: 5px; opacity: 0.85; }
.front-footer a:hover { text-decoration: underline; text-underline-offset: 3px; }
.front-footer a:active { opacity: 0.8; }

/* Okruszków nawigacyjnych Klasyk NIE MA — są wyłącznie w Nowoczesnym
   (.mod-breadcrumbs), więc ich stany żyją w front-modern.css. */

/* ---- Filtr kategorii ----
   Aktywny filtr = akcent (jest „tu jesteś"). Najechanie tylko przyciemnia tło
   — nie może wyglądać jak wybrany. */
.mm-chip:hover { background: var(--surface-2); border-color: var(--ink-3); }
.mm-chip:active { background: var(--surface-alt); }
.mm-chip.is-active:hover { background: var(--pri); }

/* ---- Sterowanie karuzelą ---- */
.news-carousel__arrow { transition: background-color var(--motion-state) var(--motion-ease-state); }
.news-carousel__arrow:active { transform: none; opacity: 0.85; }
.news-carousel__dot { transition: background-color var(--motion-state) var(--motion-ease-state); }
.news-carousel__dot:hover { background: var(--section-link); }

/* ---- Przyciski ----
   Wciśnięcie przyciemnia, nie pomniejsza: `transform: scale()` zmieniałby
   wymiary, a to jest w tym systemie zakazane. */
.btn { transition: background-color var(--motion-state) var(--motion-ease-state), color var(--motion-state) var(--motion-ease-state), border-color var(--motion-state) var(--motion-ease-state); }
.btn:active { filter: brightness(0.92); }

/* Rząd z JEDNYM węższym widgetem — wyśrodkowany zamiast dosuniętego do lewej
   (25.07.2026, Etap 5). Szerokość komórki zostaje ta, którą ustawiła parafia;
   zmienia się wyłącznie położenie w pasie. Od 26.07.2026 `.home-row` bazowo
   też środkuje (patrz wyżej) — ta reguła jest więc dziś nadmiarowa, zostaje
   jednak jako jawne udokumentowanie zamiaru dla tego konkretnego przypadku
   (klasa nadana w home-rows.blade.php), gdyby kiedyś reguły się rozjechały. KLASYK */
.home-row--solo { justify-content: center; }

/* ============================================================
   ARCHIWUM AKTUALNOŚCI (28.07.2026)

   JEDEN układ, niezależny od liczby wpisów. Dawne trzy tryby (siatka kafli /
   kronika z lepkim rokiem w szpalcie / oś czasu) USUNIĘTE razem z ich CSS —
   próg przełączający zmieniał parafii wygląd strony po dodaniu kolejnego wpisu.

   Nad listą stoi nawigacja po czasie: rząd lat, pod nim rząd miesięcy. Lista to
   WIERSZE, nie kafle — archiwum ma być spisem do przejrzenia, a nie drugą stroną
   główną, więc na jednym ekranie ma się mieścić kilkanaście pozycji, nie cztery.
   ============================================================ */
/* Rząd kategorii zachowuje się jak dwa rzędy nad nim: JEDNA linia z przewijaniem
   w bok. To nie jest ozdoba, tylko rozwiązanie skoku treści — przy zawijaniu rząd
   kurczył się z dwóch wierszy do jednego przy przełączeniu miesiąca i wszystko pod
   nim podskakiwało o wysokość wiersza. Jedna linia znaczy stałą wysokość niezależnie
   od liczby kategorii, czyli przesunięcie równe zeru. */
.news-filter {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  gap: var(--space-xs);
  margin: 0 0 var(--space-md);
  padding-bottom: var(--space-xs);
}
.news-filter .mm-chip { flex: 0 0 auto; white-space: nowrap; }

/* PASEK PRZEWIJANIA UKRYTY w całej rodzinie rzędów nawigacji archiwum.
   Powód jest mierzalny, nie estetyczny: klasyczny pasek pojawia się dopiero przy
   nadmiarze treści i ZABIERA 11 px wysokości. Rząd kategorii zmienia liczbę
   pigułek z miesiąca na miesiąc, więc pasek raz był, raz go nie było — i o te
   11 px podskakiwała cała treść pod nim przy przełączaniu miesiąca.
   `scrollbar-gutter: stable` tu nie pomaga: rezerwuje miejsce wyłącznie dla
   paska PIONOWEGO (sprawdzone — właściwość jest wspierana, a wysokość została
   ta sama). Przewijanie palcem, gładzikiem i Shift+kółkiem działa dalej,
   a widoczną zapowiedzią jest treść ucięta na krawędzi. */
.news-archive-years,
.news-archive-months,
.news-filter { scrollbar-width: none; }
.news-archive-years::-webkit-scrollbar,
.news-archive-months::-webkit-scrollbar,
.news-filter::-webkit-scrollbar { display: none; }

/* Rzędy nawigacji przewijają się W BOK zamiast zawijać: dziesięć lat albo
   trzynaście miesięcy w zawiniętym rzędzie zajęłoby pół ekranu telefonu.
   Bez media query — na szerokim ekranie po prostu nie ma czego przewijać. */
.news-archive-years,
.news-archive-months {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  gap: var(--space-xs);
  margin: 0 0 var(--space-md);
  padding-bottom: var(--space-xs);
}
.news-archive-years { border-bottom: 1px solid var(--section-border, var(--border-2)); }
.news-archive-years__item,
.news-archive-months__item {
  flex: 0 0 auto;
  text-decoration: none;
  white-space: nowrap;
  color: var(--section-text, var(--ink));
  transition: color var(--motion-state) var(--motion-ease-state), border-color var(--motion-state) var(--motion-ease-state), background-color var(--motion-state) var(--motion-ease-state);
}
/* ROK — zakładka: podkreślenie zamiast pigułki, żeby rząd lat czytał się jako
   nadrzędny wobec rzędu miesięcy pod nim. */
.news-archive-years__item {
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 600;
  padding: 6px 12px;
  border-bottom: 3px solid transparent;
  margin-bottom: -1px;
}
.news-archive-years__item:hover { border-bottom-color: var(--section-border, var(--border)); }
.news-archive-years__item.is-active {
  color: var(--section-heading, var(--pri));
  border-bottom-color: var(--section-accent-ink);
}
/* MIESIĄC — drobna pigułka, podrzędna wobec roku. */
.news-archive-months__item {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
}
.news-archive-months__item:hover { border-color: var(--section-border, var(--border)); }
/* Aktywna pigułka bierze kolory ze STAŁYCH tokenów palety, nie ze zmiennych
   zakresu sekcji: niesie własne, nieprzezroczyste tło, więc ma wyglądać tak samo
   niezależnie od tego, na czym leży (dokładnie jak `.mm-chip.is-active` obok).
   Prefiks `.front-root` przebija globalne `a { color: inherit }` — bez niego
   napis dziedziczy kolor tekstu strony i ginie na własnym tle. */
.front-root .news-archive-months__item.is-active {
  background: var(--pri);
  border-color: var(--pri);
  color: var(--surface);
}
/* Miesiąc bez wpisów: przygaszony i NIE jest odnośnikiem (w markupie to <span>,
   nie <a>) — rząd mówi wtedy wprost, kiedy parafia publikowała, a kiedy milczała,
   i nie da się kliknąć w pustkę. */
.news-archive-months__item.is-empty {
  color: var(--section-muted, var(--muted));
  opacity: 0.45;
  cursor: default;
}

/* ---- LISTA WIERSZY ---- */
.news-rows { display: grid; gap: var(--space-xl); }
.news-rows__period { display: grid; gap: 0; }
/* Nagłówek miesiąca zostaje separatorem WEWNĄTRZ listy (tak jak dotąd). */
.news-rows__month {
  margin: 0 0 var(--space-xs);
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--section-border, var(--border-2));
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--section-accent-ink);
}
.news-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-xs);
  border-bottom: 1px solid var(--section-border, var(--border-3));
  text-decoration: none;
  color: inherit;
  transition: background-color var(--motion-state) var(--motion-ease-state);
}
/* PODKREŚLENIE ZDJĘTE 29.07.2026 (druga zmiana decyzji). W poprzedniej rundzie
   zostało w wierszach, bo miniatura 96×72 px wydawała się za mała na zauważalny
   ruch. Wniosek odwrócony: zamiast rezygnować z sygnału, dobieramy go do
   rozmiaru — mocniejsze przybliżenie miniatury i wyraźniejsze tło wiersza.

   TŁO MA WŁASNY TOKEN, nie `--card-bg-hover`. Ten drugi jest dla KAFLI, gdzie
   na jasnym pasie celowo NIE zmienia tła (reaguje cień), więc wiersz dostawał
   przez niego zero zmiany — całą robotę wykonywało podkreślenie. Wiersz nie ma
   cienia, więc potrzebuje tła, które naprawdę się zmienia. */
.news-row:hover { background: var(--row-bg-hover); }
.news-row:active { background: var(--row-bg-active); }
/* Miniatura przybliża się MOCNIEJ niż zdjęcie dużego kafla: przy 96 px
   szerokości te same 4% dałyby przyrost 3,8 px, czyli ruch na granicy
   dostrzegalności. 10% daje 9,6 px (4,8 px na krawędź) — wciąż spokojnie,
   a już widać. Skalowane jest samo zdjęcie, kadr przycina nadmiar, więc wiersz
   nie zmienia wymiarów i sąsiedzi stoją nieruchomo. */
.news-row__media { overflow: hidden; border-radius: var(--radius-sm); }
.news-row__thumb { transition: transform var(--motion-state) var(--motion-ease-state); }
.news-row:hover .news-row__thumb { transform: scale(1.10); }
.news-row__body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* KATEGORIA JAKO PODPIS: drobne pismo w kolorze akcentu, bez wypełnionego
   prostokąta. Na kaflu plakietka leży na zdjęciu i musi mieć własne tło; tutaj
   leży na tle karty, więc tło byłoby wyłącznie hałasem — a to ono robiło
   z kategorii najważniejszy element wpisu. */
.news-row__cat {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--section-accent-ink);
}
.news-row__title {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--section-text, var(--ink));
  text-wrap: pretty;
}
.news-row__excerpt {
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--section-muted, var(--muted));
  text-wrap: pretty;
}
/* Miniatura po prawej. Wpis bez zdjęcia nie ma tego elementu w ogóle — kolumna
   siatki jest `auto`, więc po prostu znika i treść zajmuje jej miejsce. */
.news-row__media { flex: 0 0 auto; }
.news-row__thumb {
  width: 96px;
  height: 72px;
  object-fit: cover;
  display: block;
  border-radius: var(--radius-sm);
  background: var(--card-bg-alt);
}
/* Kartka z kalendarza w wierszu — ta sama forma, mniejsza. */
.news-date--sm { min-width: 40px; padding: 4px 6px; }
.news-date--sm .news-date__day { font-size: 18px; }
.news-date--sm .news-date__month { font-size: 10px; margin-top: 2px; }
.news-date--sm .news-date__year { font-size: 9px; }

@media (max-width: 560px) {
  /* Na telefonie miniatura schodzi pod treść, żeby tytuł nie ściskał się
     do dwóch słów w wierszu. */
  .news-row { grid-template-columns: auto minmax(0, 1fr); gap: var(--space-sm); }
  .news-row__media { grid-column: 2; }
  .news-row__thumb { width: 100%; height: 120px; }
}

/* ---- STANY PUSTE ARCHIWUM ---- */
.news-empty { display: grid; gap: var(--space-sm); justify-items: center; text-align: center; }
.news-empty__lead { margin: 0; font-size: 17px; color: var(--section-text, var(--ink)); }
.news-empty__hint { margin: 0; font-size: 15px; color: var(--section-muted, var(--muted)); }
.news-empty__ways { display: flex; flex-wrap: wrap; gap: var(--space-md); justify-content: center; }

/* ============================================================
   NAKŁADKA BANERU „Informacje podstawowe" — KLASYK (26.07.2026)

   Panel leży na ciemnym tle palety, a bloki Informacji są wspólne z pasem
   i stopką — więc same z siebie przynoszą kolory dla JASNEGO tła. Tu ustawiamy
   dla nich zakres: nagłówki i etykiety na akcent, treść na biel, telefon
   dyżurny w jasnej ramce. To ten sam mechanizm co przy pasach sekcji, tylko
   zapisany lokalnie — nakładka nie jest sekcją i nie dostaje `--card-*`.
   ============================================================ */
.front-banner-info__panel {
  /* Zakres kolorów dla wspólnych bloków Informacji. */
  /* AKCENT NA CIEMNYM — zmierzone na tle panelu (kolor główny palety):
       surowy akcent      2,67–4,02:1  ← poniżej progu, stąd go tu NIE MA
       accent_on_dark     3,45–5,12:1  ← nagłówki (24 px, waga 600 = duży tekst, próg 3:1)
       accent_soft        6,69–9,60:1  ← etykiety grup (12 px kapitaliki = mały tekst, próg 4,5:1)
     Mniejszy tekst dostaje jaśniejszy wariant — inaczej nie przeszedłby progu. */
  --card-link: var(--sec-on-dark, var(--sec));
  --card-text: #FFFFFF;
  --card-muted: rgba(255, 255, 255, 0.78);
  --section-accent-ink: var(--sec-on-dark, var(--sec));
  /* AKCENT GRAFICZNY (ikony przy wierszach danych) — TEŻ w wersji „na ciemnym".
     Bez tego ikony kontaktu brały surowy akcent i w palecie zieleń/kość
     miały 2,67:1 przy progu 3:1. To był ten sam defekt co przy nagłówkach,
     tylko w innym miejscu — dlatego wariant ustawiamy raz, dla CAŁEGO panelu,
     zamiast poprawiać kolejny pojedynczy selektor. */
  --section-accent: var(--sec-on-dark, var(--sec));
}

/* NAGŁÓWKI BLOKÓW — były granatem NA GRANACIE (kontrast ~1:1, praktycznie
   niewidoczne). Teraz akcent palety: krój szeryfowy, wyraźnie większy,
   ikona NAD nagłówkiem, pod spodem cienka złota kreska. */
/* NAGŁÓWKI KOLUMN — ikona i napis w JEDNYM RZĘDZIE (korekta 26.07.2026).
   ZŁOTO to ikona i podetykieta, BIEL to nagłówek sekcji — ta sama zasada co
   w stopce, żeby cały front schodził się na jedno rozwiązanie zamiast dwóch
   różnych w dwóch miejscach.

   Biel ma na tle panelu 8,13–11,48:1 we wszystkich czterech paletach, więc
   przechodzi z zapasem; złoty napis miał 2,67–4,02:1 i był granicą. */
.front-banner-info__panel .info-block__title {
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  padding-bottom: 12px;
  font-size: 22px;
  color: #FFFFFF;
  border-bottom: 1px solid var(--sec-on-dark, var(--sec));
}
.front-banner-info__panel .info-block__icon { width: 30px; height: 30px; color: var(--sec-on-dark, var(--sec)); }

/* KOLUMNY rozdzielone delikatnymi liniami pionowymi. Pierwsza bez linii —
   inaczej panel zaczynałby się kreską przy krawędzi. */
/* A3: w nakładce wszystko na środku — nagłówek z ikoną, treść i przycisk.
   Wcześniej nagłówki i przyciski stały przy lewej krawędzi, a godziny na
   środku, przez co kolumna wyglądała na złożoną z dwóch różnych rzeczy. */
/* Kolumny są tej samej wysokości (siatka), a przycisk każdej z nich siada
   przy DOLNEJ krawędzi — dzięki temu trzy przyciski stoją równo niezależnie
   od tego, ile treści jest nad nimi. */
.front-banner-info__col {
  padding: 0 22px;
  text-align: center;
  display: flex;
  flex-direction: column;
}
.front-banner-info__col .info-block__body { flex: 1 1 auto; }
.front-banner-info__col .info-block__more { margin-top: auto; align-self: center; }
.front-banner-info__panel .info-block__title { justify-content: center; }
/* PUNKT 7 (26.07.2026): treść wyśrodkowana WYŁĄCZNIE w kolumnie Mszy — tam są
   same liczby, więc środek wygląda dobrze. Biuro i Kontakt to wiersze danych
   różnej długości; wyśrodkowane robiły „choinkę", więc wracają do lewej.
   Nagłówki z ikoną zostają wyśrodkowane we WSZYSTKICH kolumnach. */
.front-banner-info__col.info-block--office .info-block__body,
.front-banner-info__col.info-block--contact .info-block__body { text-align: left; }
.front-banner-info__panel .info-block__more { margin-left: auto; margin-right: auto; }
.front-banner-info__col + .front-banner-info__col {
  border-left: 1px solid rgba(255, 255, 255, 0.16);
}

/* ETYKIETY GRUP GODZIN — były szare i przy lewej krawędzi, przy wyśrodkowanych
   godzinach wyglądało to na pomyłkę. Teraz akcent, kapitaliki, wyśrodkowane,
   a między grupami cienka linia. */
.front-banner-info__panel .mass-schedule__day {
  text-align: center;
  /* Kapitaliki 12 px to MAŁY tekst (próg 4,5:1) — stąd najjaśniejszy wariant
     akcentu; ciemniejsze warianty miałyby tu 2,7–5,1:1. */
  color: var(--sec-soft, var(--sec));
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
}
.front-banner-info__panel .mass-schedule__group + .mass-schedule__group {
  padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.front-banner-info__panel .mass-time { color: #FFFFFF; }

/* TELEFON DYŻURNY — był BIAŁY NA BIAŁYM: blok ma jasne tło z wariantu dla
   jasnych sekcji, a tekst dziedziczył biel panelu. Zgodnie z kierunkiem:
   jasna, kremowa ramka wyraźnie odcięta od reszty, z ciemnym tekstem. */
/* A5: w rzędzie dyżuru DNI są złote, a GODZINY białe — inaczej jedno i drugie
   zlewało się w jeden biały ciąg i nie było widać, gdzie kończy się dzień. */
.front-banner-info__panel .office-details__days { color: var(--sec-soft, var(--sec)); font-weight: 700; }
.front-banner-info__panel .office-details__time { color: #FFFFFF; }
.front-banner-info__panel .office-details__note { color: rgba(255, 255, 255, 0.75); }
.front-banner-info__panel .office-duty {
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-left: 3px solid var(--sec-on-dark, var(--sec));
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  color: var(--ink);
  margin-top: 14px;
}
.front-banner-info__panel .office-duty { padding: 8px 12px; gap: 8px; }
.front-banner-info__panel .office-duty__label { color: var(--muted); }
.front-banner-info__panel .office-duty__phone { color: var(--pri); }
.front-banner-info__panel .office-duty__note { color: var(--ink-3); }
.front-banner-info__panel .office-duty__icon { color: var(--sec-strong, var(--sec)); }

/* Dane kontaktowe i płatności — biel na granacie, kreska nad płatnościami. */
.front-banner-info__panel .contact-details__payments {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}
.front-banner-info__panel .contact-details__label { color: var(--sec-soft, var(--sec)); }
.front-banner-info__panel .office-details__closed { color: rgba(255, 255, 255, 0.78); }

/* ZAMYKANIE PANELU — KRZYŻYK W ROGU (01.08.2026, zmiana wobec 26.07.2026).

   BYŁO: po rozwinięciu przycisk otwierający przenosił się `order: -1` NAD
   kolumny, malał i odwracał strzałkę. Działało, ale zjadało miejsce na górze:
   sam przycisk plus jego marginesy odsuwały pierwszy nagłówek od krawędzi
   panelu, a to najcenniejsze miejsce w całej nakładce.

   JEST: przycisk otwierający po prostu ZNIKA, a zamyka krzyżyk w prawym górnym
   rogu panelu. Leży `absolute`, czyli POZA PRZEPŁYWEM — nie odbiera treści ani
   piksela, a stoi tam, gdzie zamknięcia szuka się odruchowo. */
.front-banner-info.is-open .front-banner-info__toggle { display: none; }
.front-banner-info.is-open { flex-direction: column; }

/* BIAŁY KRĄŻEK Z ZNAKIEM W KOLORZE PALETY.

   Kontrast zmierzony we WSZYSTKICH czterech paletach (tło panelu = kolor główny
   z kryciem 94% nad przyciemnionym zdjęciem):
     krążek (biel) na tle panelu   7,38 : 1  –  10,21 : 1
     znak (kolor główny) na krążku 8,13 : 1  –  11,48 : 1
   Najsłabsza para wypada w palecie „zieleń i kość słoniowa"; próg 4,5 : 1 jest
   przekroczony z dużym zapasem w każdej.

   Wariant „przezroczyste tło + biała obwódka" (poprzednia wersja przycisku
   zamykającego) był poprawny kontrastowo, ale jako krążek 36 px robił się mało
   widoczny na zdjęciu — pełne wypełnienie czyta się od razu. */
.front-banner-info__close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 40 px — powyżej progu celu dotykowego (WCAG 2.5.8 mówi o 24 px). */
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-circle);
  background: #FFFFFF;
  color: var(--pri);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.30);
  transition: background-color var(--motion-state) var(--motion-ease-state),
              color var(--motion-state) var(--motion-ease-state);
}
.front-banner-info__close:hover { background: var(--sec); color: var(--ink); }
/* WCIŚNIĘCIE nie zmienia WYMIARÓW (zasada „żaden stan nie zmienia wymiarów
   elementu" — złapane przez InteractionStatesTest, bo pierwsza wersja miała tu
   `transform: scale(0.94)`). Reaguje samo tło: przygaszone złoto czyta się jako
   wciśnięcie, a jest to jedyny stan działający też na dotyku. */
.front-banner-info__close:active { background: var(--sec-strong, var(--sec)); color: #FFFFFF; }
/* Obrys fokusu BIAŁY z ciemnym odsunięciem — na krążku i na panelu naraz musi
   być widoczny, a jedno i drugie jest tu jasne/ciemne. */
.front-banner-info__close:focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}
/* KRZYŻYK ZNIKA NA TELEFONIE (08.08.2026). Poniżej 900 px panel jest rozwinięty
   ZAWSZE (reguła w bloku mobilnym wyżej), bo przycisk otwierający jest tam ukryty
   — więc `open = false` nie ma czego zamknąć. Krzyżyk zostawał widoczny i
   klikalny, a naciśnięcie nie robiło NIC: klasyczne „strona się zawiesiła", po
   którym ktoś naciska drugi raz i zamyka kartę. Sterowany `tabindex` w szablonie
   chronił przed tym wyłącznie klawiaturę, nie palec.

   ⚠️ REGUŁA MUSI STAĆ TUTAJ, PO DEKLARACJI `.front-banner-info__close`, a nie
   w bloku mobilnym obok ukrycia przycisku otwierającego. Specyficzność jest
   identyczna (0,1,0), a `@media` jej nie podnosi — wygrywa więc reguła PÓŹNIEJSZA
   w pliku. Pierwsze podejście wylądowało 1500 linii wcześniej i nie robiło nic;
   arkusz przeszedł test, a przycisk dalej był widoczny w przeglądarce. */
@media (max-width: 900px) {
  .front-banner-info__close { display: none; }
}

/* Strzałka przycisku otwierającego wskazuje W GÓRĘ — panel wysuwa się w górę.
   Obrót zniknął razem z przenoszeniem przycisku (w stanie otwartym go nie ma). */
.front-banner-info__caret { transition: transform var(--motion-state) var(--motion-ease-state); }

/* ============================================================
   ZAREZERWOWANE MIEJSCE BLOKU (27.07.2026)
   Blok, którego parafia używa, ale który akurat nie ma czym się wypełnić
   (dzisiejsza Ewangelia jeszcze nieopublikowana, brak intencji na dziś).
   Zostaje ze swoją ramką, tytułem i szerokością, żeby pas nie przeliczał się
   co noc z trójdzielnego na dwudzielny.

   Wyłącznie tokeny zakresu sekcji (--section-*), więc reguła działa na każdym
   wariancie tła. CICHSZY niż blok z treścią, ale NIE wygląda na zepsuty:
   bez czerwieni, bez ikony ostrzeżenia, bez przerywanej ramki „braku".
   ============================================================ */
/* BLOK CZEKAJĄCY NA TREŚĆ (31.07.2026) — rozsądna wysokość zamiast rozciągania
   na całą wysokość najwyższej kolumny. Wcześniej pas trójdzielny z jednym
   długim blokiem robił z sąsiadów dwa wielkie puste prostokąty.

   `min-height` na TREŚCI, nie na sekcji: sekcja dalej wypełnia komórkę siatki
   (pas ma zostać trójdzielny i kolumna ma nie zniknąć), ale komunikat dostaje
   własne, stałe pole i staje w jego środku. Blok wygląda wtedy na spokojnie
   czekający, a nie na dziurę po brakującej treści. */
/* ⚠️ TA KLASA BYŁA ZADEKLAROWANA DWA RAZY (naprawione 08.08.2026).
   Druga deklaracja stała 11 linii niżej i przy równej specyficzności wygrywała
   dla wspólnych właściwości — po cichu ZEROWAŁA boczne wcięcie, które pierwsza
   ustawiała świadomie (`padding: 0 var(--tresc-pad)` → `… 0`), więc komunikat
   dotykał krawędzi wąskiej komórki. Reszta pierwszej deklaracji (pole o stałej
   wysokości, wyśrodkowanie) działała dalej, bo druga jej nie dotykała — i to
   właśnie sprawiało, że rozjazd był niewidoczny.

   Scalone w JEDNĄ deklarację; zachowane obie intencje: własne pole komunikatu
   ze środkowaniem (31.07.2026) ORAZ pionowy oddech i kreska u góry. */
.front-awaiting__text {
  display: grid;
  place-items: center;
  min-height: 180px;
  margin: 0;
  padding: clamp(14px, 3cqi, 22px) var(--tresc-pad);
  text-align: center;
  text-wrap: balance;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--section-muted, var(--muted));
  border-top: 1px solid var(--section-border);
}

/* Wygaszona strzałka paska miniatur (przepięcie na frontStrip, 29.07.2026) —
   na krańcu nie ma czego przewijać, więc przycisk zostaje na miejscu (znikający
   przesuwałby pasek), ale gaśnie i przestaje być klikalny. */
.photo-strip__arrow[disabled] { opacity: 0.32; cursor: default; }

/* =========================== STRONY BŁĘDÓW =========================== */
/* 404/403 w szablonie parafii (30.07.2026, C1 z audytu frontu). Klasy własne,
   bo ten sam widok renderuje się też w Nowoczesnym, który ma inne nazwy
   nagłówków sekcji. Strony 500/503 mają styl wpisany w siebie i tego arkusza
   NIE ładują — przy padniętej bazie nie zakładamy, że cokolwiek się dociągnie. */
.front-error { max-width: 44rem; }
.front-error__title {
  margin: 0 0 12px;
  font-family: var(--font-serif);
  font-size: clamp(24px, 3.4vw, 34px);
  line-height: 1.2;
  color: var(--section-heading, var(--ink));
}
.front-error__body { margin: 0 0 24px; color: var(--section-muted, var(--muted)); line-height: 1.6; }
.front-error__ways { margin-bottom: 32px; }
.front-error__more { padding-top: 20px; border-top: 1px solid var(--section-border, var(--border)); }
.front-error__more-label {
  display: block; margin-bottom: 10px;
  font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--section-muted, var(--muted));
}
.front-error__list { margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 8px 28px; }
/* Wiersz listy dostaje wysokość celu dotyku — te odnośniki są jedynym wyjściem
   ze strony błędu, więc nie mogą być cieńsze niż 24 px (WCAG 2.5.8). */
.front-error__list li { display: flex; align-items: center; min-height: 28px; }
/* Nadpisanie hovera na stronie błędu USUNIĘTE 30.07.2026 — nie jest już
   potrzebne. Wariant podstawowy (.btn--action) ma własny, tokenowy stan po
   najechaniu (--pri-strong, 11,30–14,28), więc napis nie ma jak zgasnąć. */

/* Podstawiony tydzień intencji (naprawa C2, 30.07.2026) — informacja, nie błąd:
   dyskretny pas z akcentem z lewej, bez czerwieni i bez ikony ostrzeżenia.
   Odwiedzający ma wiedzieć, że patrzy na inny tydzień niż bieżący. */
.front-intentions-fallback {
  margin: 0 0 20px;
  padding: 12px 16px;
  border-left: 3px solid var(--section-accent, var(--sec));
  background: var(--section-surface-2, var(--surface-2));
  color: var(--section-ink, var(--ink));
  font-size: 15px;
  line-height: 1.5;
}

/* ---- Ogłoszenia: poprzednie wpisy i sąsiedzi (30.07.2026, naprawa H2) ----
   Archiwum jest osobną podstroną, a tutaj zostaje krótka lista „co było
   wcześniej" plus przejście do sąsiednich tygodni. Odnośnik do archiwum stoi
   po prawej (`.section-foot`), powrót po lewej (`.front-post__foot`). */
.ann-previous { margin-top: 40px; padding-top: 28px; border-top: 1px solid var(--section-border, var(--border)); }
/* Na stronie bieżących ogłoszeń separator daje już `.ann-current` (border-bottom).
   Bez tego wiersze „Poprzednie ogłoszenia" poprzedzały DWIE kreski ~38 px od
   siebie — zauważone na zrzucie, nie w kodzie. Na stronie pojedynczego wpisu
   `.ann-previous` stoi w osobnej sekcji i swoją kreskę zachowuje. */
.ann-current + .ann-previous { margin-top: 0; padding-top: 0; border-top: 0; }
/* OSTATNIE AKTUALNOŚCI pod wpisem (02.08.2026) — te same wymiary co „Poprzednie
   ogłoszenia", bo to ten sam element strony w drugim module. Lista w środku to
   zwykłe `.news-rows` z archiwum. */
.news-recent { margin-top: 40px; padding-top: 28px; border-top: 1px solid var(--section-border, var(--border)); }
.news-recent__title {
  margin: 0 0 12px;
  font-size: clamp(20px, 2.2vw, 26px);
  color: var(--section-heading, var(--ink));
}

.ann-previous__title {
  margin: 0 0 12px;
  font-size: clamp(20px, 2.2vw, 26px);
  color: var(--section-heading, var(--ink));
}
/* WIERSZ DOMYKAJĄCY STRONĘ OGŁOSZENIA (30.07.2026) — trzy miejsca w jednym
   rzędzie: starsze | wyjścia | nowsze. Zastąpił trzy osobne bloki nawigacji.

   SIATKA `1fr auto 1fr`, a boki renderują się ZAWSZE (także puste) — dzięki temu
   przy najstarszym i najnowszym ogłoszeniu środek zostaje dokładnie pośrodku,
   a wiersz nie ma dziury. */
.front-footnav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px 24px;
  margin-top: 8px;
  padding-top: 20px;
  border-top: 1px solid var(--section-border, var(--border));
}
.front-footnav__side { min-width: 0; }
.front-footnav__side--end { justify-self: end; }
/* SĄSIAD JAKO PRZYCISK (02.08.2026) — wygląd i stany bierze `.btn--action-quiet`
   z systemu akcji; tutaj zostaje tylko to, czego przycisk nie wie: że w środku
   są DWA wiersze (rola nad tytułem) i że tytuł wolno przyciąć.
   Dawne `.front-footnav__arrow` USUNIĘTE — strzałkę rysuje komponent. */
.front-footnav__neighbour {
  min-width: 0;
  max-width: 100%;
  /* Napisy w środku wyrównane do lewej także wtedy, gdy przycisk stoi po prawej
     stronie wiersza — dwuwierszowa etykieta wyśrodkowana czyta się gorzej. */
  text-align: left;
}
.front-footnav__text { min-width: 0; display: block; }
/* Rola dziedziczy kolor przycisku (obrys + napis w kolorze palety), więc nie
   nadpisujemy jej `--section-muted` — na przycisku dawało to napis o niższym
   kontraście niż jego własna obwódka. */
.front-footnav__role { display: block; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.8; }
/* Długi tytuł PRZYCINAMY wielokropkiem, nie łamiemy — wiersz ma zostać jednym
   wierszem. Pełne brzmienie niesie `aria-label` odnośnika (patrz partial). */
.front-footnav__title {
  display: block;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 400;
  /* Kolor z przycisku (currentColor) — patrz komentarz przy roli. */
}
.front-footnav__exits { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 8px 12px; }
/* Wąski ekran: sąsiedzi ZOSTAJĄ RAZEM w jednej linii, wyjścia schodzą pod nich. */
@media (max-width: 640px) {
  .front-footnav {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "start end" "exits exits";
  }
  .front-footnav__side--start { grid-area: start; }
  .front-footnav__side--end { grid-area: end; }
  .front-footnav__exits { grid-area: exits; justify-content: flex-start; }
  .front-footnav__title { max-width: 14ch; }
}

/* Dawne `margin-left: auto` na ostatnim przycisku USUNIĘTE (01.08.2026):
   rozdzielało parę przycisków na przeciwne krawędzie, a od czasu, gdy cały rząd
   idzie do prawej, oba mają stać obok siebie. Po zawinięciu w wąskiej kolumnie
   `justify-content: flex-end` dosuwa do prawej każdy wiersz z osobna. */
/* ZAJAWKA OGŁOSZEŃ W PASIE TRÓJDZIELNYM (31.07.2026).

   Kolumna miała u dołu pustkę, bo zajawka kończyła się wcześniej niż sąsiedzi.
   Serwer podaje teraz WIĘCEJ tekstu, niż zwykle się mieści, a o tym, ile widać,
   decyduje CSS: obcięcie po LICZBIE WIERSZY. To jest odporne na skrajne
   długości — ogłoszenie bardzo krótkie po prostu nie dobija do limitu i nic się
   nie psuje, a bardzo długie zostaje przycięte wielokropkiem zamiast rozpychać
   kolumnę i rozjeżdżać wysokości całego pasa.

   Blok jest kolumną flex, więc rząd akcji trzyma się dołu (margin-top: auto)
   niezależnie od tego, ile wierszy zajęła zajawka. */
.home-row__cell .front-section--band { display: flex; flex-direction: column; }
.home-row__cell .front-ann__excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--ann-lines, 14);
  overflow: hidden;
}
.home-row__cell .front-ann__actions { margin-top: auto; }

/* ============================================================
   REGUŁA 4 (31.07.2026): AKCJA DOMYKAJĄCA BLOK JEST PRZYKLEJONA DO DOŁU,
   także gdy widgety stoją obok siebie w pasie o wyrównanych wysokościach.

   Pierwsza wersja wymieniała trzy widgety z nazwy i przez to nie obejmowała
   reszty — Ewangelia zostawiała pod stopką 129 px pustki, podczas gdy przyciski
   Ogłoszeń stały równo przy krawędzi. Teraz zasada jest OGÓLNA: każdy pas
   stojący w komórce jest kolumną flex, a wiersz domykający dosuwa się do dołu.

   Bezpieczne dla tła: wzór i zdjęcie sekcji są `position: absolute`, więc nie
   są elementami flex i nic ich nie przesunie.

   Lista klas domykających jest ta sama, której pilnuje strażnik reguły —
   dopisanie nowego wiersza domykającego wymaga dopisania go w OBU miejscach
   (test to wychwyci). */
.home-row__cell section.front-section,
.home-row__cell .front-gospel,
.home-row__cell .front-extnews { display: flex; flex-direction: column; }
.home-row__cell .section-foot,
.home-row__cell .front-ann__actions,
.home-row__cell .front-gospel__foot,
.home-row__cell .front-extnews__foot { margin-top: auto; }

/* Nagłówek widgetu w KOLUMNIE BOCZNEJ (30.07.2026, ustalenie H4 z audytu).
   `.section-title` jest wymiarowany pod pas pełnej szerokości — w railu (262 px)
   dawał 40 px stopnia pisma i tytuł łamał się na dwie linie z rozstrzeloną
   spacją (zmierzone: 92 px wysokości nagłówka). W railu schodzimy do stopnia
   bliskiego nagłówkom pozostałych boksów kolumny. */
.front-sidebar .front-live .section-title,
.front-sidebar .front-section .section-title {
  font-size: clamp(18px, 2.2vw, 22px);
  line-height: 1.25;
}

/* ============================================================
   STRONA „EWANGELIA DNIA" (31.07.2026)

   Widget na stronie głównej jest zwięzły — tu jest komplet. Cała treść to
   proza, więc obowiązuje ta sama miara wiersza co w pasach (--proza-max).
   ============================================================ */
.front-gospel-page__day { font-weight: 700; color: var(--ink); margin: 0 0 4px; }
.front-gospel-page__ref { color: var(--muted); margin: 0 0 20px; font-size: 15px; }
.front-gospel-page__player { margin: 0 0 26px; }
.front-gospel-page__player audio { width: 100%; max-width: min(100%, clamp(320px, 60cqi, 560px)); }
.front-gospel-page__text { max-width: var(--proza-max); font-size: 17px; line-height: 1.7; }
.front-gospel-page__text p { margin: 0 0 14px; }
.front-gospel-page__note {
  max-width: var(--proza-max);
  padding: 16px 18px;
  border-left: 3px solid var(--sec);
  background: var(--surface-2, var(--card-bg-alt));
  color: var(--ink-2);
  margin: 0 0 26px;
}
/* KOMPLET CZYTAŃ (01.08.2026) — porządek liturgii słowa, jedna sekcja na
   czytanie. Nazwa czytania jest DROGOWSKAZEM, nie tytułem artykułu: mała,
   wersalikami, w kolorze pomocniczym, żeby nie konkurowała z treścią. */
.front-gospel-readings { max-width: var(--proza-max); }
/* Nagłówek formularza mszy — pojawia się TYLKO w dni, które mają po dwa komplety
   czytań, i jest wtedy najważniejszym drogowskazem na stronie. */
.front-gospel-readings__mass {
  margin: 34px 0 16px;
  padding-top: 20px;
  border-top: 2px solid var(--sec);
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
}
.front-gospel-readings__mass:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.front-gospel-reading + .front-gospel-reading { margin-top: 30px; padding-top: 24px; border-top: 1px solid var(--border); }
.front-gospel-reading__name {
  margin: 0 0 6px;
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 700;
}
.front-gospel-reading__ref { margin: 0 0 4px; font-weight: 700; color: var(--ink); }
.front-gospel-reading__intro { margin: 0 0 12px; color: var(--ink-2); font-style: italic; }
/* Refren psalmu i aklamacja — to, co odpowiada lud; wyróżnione paskiem, bo
   czyta się je inaczej niż ciągły tekst czytania. */
.front-gospel-reading__response {
  margin: 0 0 12px;
  padding-left: 14px;
  border-left: 3px solid var(--sec);
  font-weight: 700;
  color: var(--ink);
}
.front-gospel-reading__body { font-size: 17px; line-height: 1.7; }
.front-gospel-reading__body p { margin: 0 0 14px; }
.front-gospel-reading__body p:last-child { margin-bottom: 0; }

.front-gospel-page__reflection { max-width: var(--proza-max); margin: 26px 0 0; padding-top: 20px; border-top: 1px solid var(--border); }
.front-gospel-page__reflection-title { margin: 0 0 8px; font-size: 20px; font-weight: 600; color: var(--ink); }
.front-gospel-page__reflection p { margin: 0; color: var(--ink-2); line-height: 1.65; }
.front-gospel-page__sources { margin-top: 34px; padding-top: 18px; border-top: 1px solid var(--border); font-size: 14px; }
.front-gospel-page__sources-title { margin: 0 0 8px; font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.front-gospel-page__sources ul { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-wrap: wrap; gap: 6px 20px; }
.front-gospel-page__sources-role { color: var(--muted); }

/* ── SZUKANIE W ARCHIWUM AKTUALNOŚCI (09.08.2026) ────────────────────────────
   Jeden wiersz: pole, przycisk i (gdy szukanie trwa) wyjście. Pole rośnie do
   pełnej dostępnej szerokości, przycisk zostaje przy swojej treści.

   Wiersz stoi NAD rzędami lat i kategorii, bo szuka w całym archiwum, a tamte
   zawężają to, co szukanie znajdzie — kolejność na stronie ma odpowiadać
   kolejności zawężania. */
.news-search {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  margin: 0 0 var(--space-md);
}
.news-search__field {
  flex: 1 1 220px;
  min-width: 0;
  padding: .55rem .8rem;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--section-border, var(--border-2));
  border-radius: var(--radius-sm, 6px);
}
.news-search__field::placeholder { color: var(--muted); }
.news-search__submit {
  flex: 0 0 auto;
  padding: .55rem 1rem;
  font: inherit;
  font-weight: 600;
  color: var(--section-on-accent, #fff);
  cursor: pointer;
  background: var(--section-accent, var(--pri));
  border: 1px solid transparent;
  border-radius: var(--radius-sm, 6px);
}
.news-search__clear {
  flex: 0 0 auto;
  font-size: .9rem;
  color: var(--muted);
}

/* ⚠️ TA KLAMRA WYŻEJ ZNIKNĘŁA 07.08.2026 (commit d21776d, bramka kodu dostępu)
   i ZABIŁA WSZYSTKO PONIŻEJ: przeglądarka wciągnęła resztę pliku jako reguły
   ZAGNIEŻDŻONE w `.news-search__clear`, więc od tamtego dnia nie działał ani
   wygląd pudełka z kodem dostępu, ani zdjęcie główne nad wpisem (09.08), ani
   nic dopisanego później. Nic tego nie zgłaszało — strona wyglądała po prostu
   „trochę inaczej".

   To trzeci taki przypadek w tym projekcie (wcześniej dwa niedomknięte
   komentarze: motyw panelu i `.mod-section`). PRZY DOPISYWANIU DO ARKUSZA
   SPRAWDŹ, CZY POPRZEDNIA REGUŁA JEST DOMKNIĘTA — objaw pojawia się w zupełnie
   innym miejscu niż przyczyna. Strażnik: tests/Feature/Front/StylesheetsAreParsableTest.php */

/* ==========================================================================
   PODSTRONA ZAMKNIĘTA KODEM DOSTĘPU
   Wygląd jest celowo skromny: to nie jest ekran błędu ani logowania, tylko
   krótka prośba o kod. Jedno pudełko na środku kolumny treści.
   Widoki: front/page-locked.blade.php (+ wariant Nowoczesnego).
   ========================================================================== */
.front-locked__box {
    max-width: 32rem;
    margin: 2rem auto;
    padding: 1.75rem;
    background: var(--surface, #fff);
    border: 1px solid var(--border, rgba(0, 0, 0, .12));
    border-radius: 10px;
    text-align: center;
}

.front-locked__heading {
    margin: 0 0 .5rem;
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--ink);
}

.front-locked__intro {
    margin: 0 0 1.25rem;
    color: var(--muted);
}

.front-locked__form {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: .6rem;
    text-align: left;
}

.front-locked__label {
    font-weight: 700;
    font-size: .85rem;
    color: var(--ink);
}

.front-locked__input {
    padding: .7rem .85rem;
    font-size: 1rem;
    color: var(--ink);
    background: var(--bg, #fff);
    border: 1px solid var(--border, rgba(0, 0, 0, .18));
    border-radius: 8px;
}

/* Czerwień wpisana wprost, nie z palety: żadna z palet parafii nie ma koloru
   błędu, a komunikat „kod nieprawidłowy" musi być rozpoznawalny niezależnie od
   tego, którą wybrała parafia. Ciemna, więc czytelna na każdym jasnym tle. */
.front-locked__error {
    margin: 0;
    color: #9a2b2b;
    font-size: .9rem;
    font-weight: 700;
}

.front-locked__submit {
    align-self: flex-start;
    margin-top: .25rem;
}

.front-locked__help {
    margin: 1.25rem 0 0;
    font-size: .85rem;
    color: var(--muted);
}


/* ── PYTANIA I ODPOWIEDZI ──────────────────────────────────────────────────
   Akordeon na `<details>`: działa bez skryptu, Ctrl+F przeglądarki otwiera
   dopasowany element, a klawiatura obsługuje go bez ani jednej linijki po
   naszej stronie. */
.faq-block { margin-top: var(--space-l); }

.faq-block__heading {
  font-family: var(--font-display);
  font-size: 1.4rem;
  margin-bottom: var(--space-s);
}

.faq-block__list { display: grid; gap: 0.5rem; }

.faq-item {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
}

.faq-item__question {
  padding: 0.85rem 1rem;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}

/* Własny znacznik zamiast domyślnego trójkąta — obraca się przy otwarciu. */
.faq-item__question::-webkit-details-marker { display: none; }

.faq-item__question::before {
  content: '+';
  flex: 0 0 auto;
  font-weight: 400;
  color: var(--sec-strong, var(--sec));
}

.faq-item[open] > .faq-item__question::before { content: '−'; }
.faq-item__question:hover { color: var(--pri); }

.faq-item__answer { padding: 0 1rem 1rem; }


/* ── ZDJĘCIE GŁÓWNE NAD TREŚCIĄ WPISU (10.08.2026) ───────────────────────────
   Pełna szerokość kolumny treści. Proporcje zostają oryginalne — kadrowanie
   do stałego pasa ucinałoby głowy na zdjęciach pionowych, a wpis parafialny
   bywa ilustrowany portretem albo skanem plakatu. `max-height` chroni przed
   pionem na dwa ekrany: bardzo wysokie zdjęcie zostaje wpisane w kadr, a nie
   spycha treść poza widok. */
.front-post__lead-image {
  margin: 0 0 var(--space-md);
}
.front-post__lead-image img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 70vh;
  object-fit: contain;
  border-radius: var(--radius-sm, 6px);
  background: var(--surface-2);
}

/* ==========================================================================
   FORMULARZE PARAFII (10.08.2026)
   Jeden zestaw klas dla obu szablonów — formularz to zbieranie danych, nie
   element wyglądu, więc różnicowanie go dałoby dwa miejsca do poprawiania.
   Wszystkie kolory z tokenów palety; ten sam arkusz w front-modern.css.
   ========================================================================== */
.front-form { margin: 28px 0; padding: 22px 24px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md, 10px); }
.front-form__heading { margin: 0 0 6px; font-size: 22px; }
.front-form__intro { margin: 0 0 16px; color: var(--muted); }
.front-form__ok { margin: 0 0 14px; padding: 10px 14px; background: var(--surface); border-left: 4px solid var(--sec-strong, var(--sec)); border-radius: var(--radius-sm, 6px); }
.front-form__closed { margin: 0; color: var(--muted); font-style: italic; }
.front-form__body { display: grid; gap: 16px; }
.front-form__field { display: grid; gap: 6px; }
.front-form__label { font-weight: 700; font-size: 14px; }
.front-form__required { color: var(--sec-strong, var(--sec)); }
.front-form__control { width: 100%; padding: 10px 12px; font: inherit; color: var(--ink); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm, 6px); }
.front-form__control:focus-visible { outline: 2px solid var(--section-focus, var(--sec-strong, var(--sec))); outline-offset: 2px; }
.front-form__field.is-invalid .front-form__control { border-color: #a4343a; }
.front-form__help { margin: 0; font-size: 13px; color: var(--muted); }
.front-form__error { margin: 0; font-size: 13px; color: #a4343a; }
.front-form__error--top { padding: 10px 14px; background: var(--surface); border-left: 4px solid #a4343a; border-radius: var(--radius-sm, 6px); }
.front-form__check { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: start; cursor: pointer; }
.front-form__check input { margin-top: 4px; }
.front-form__check-text { font-size: 14px; line-height: 1.5; }
.front-form__radios { margin: 0; padding: 0; border: 0; display: grid; gap: 8px; }
.front-form__submit { justify-self: start; }
.front-form__confirm { padding: 14px 18px; background: var(--surface-2); border-left: 4px solid var(--sec-strong, var(--sec)); border-radius: var(--radius-sm, 6px); }
.front-form__confirm.is-failed { border-left-color: #a4343a; }
/* Pułapka na automaty — poza ekranem, ale NIE display:none: część skryptów
   pomija pola ukryte w ten sposób, a o to, żeby ją wypełniły, tu chodzi. */
.front-form__trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ==========================================================================
   WYDARZENIA (11.08.2026)
   Kafel niesie TERMIN, nie wydarzenie — stąd kartka z datą jako pierwszy
   element. Odwołany termin zostaje na liście, przygaszony i przekreślony:
   zniknięcie byłoby nie do odróżnienia od tego, że nigdy go nie planowano.
   ========================================================================== */
.event-list { display: flex; flex-direction: column; gap: 12px; margin: 18px 0; }
.event-card { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md, 10px); }
.event-card__link { display: grid; grid-template-columns: auto 1fr auto; gap: 16px; align-items: center; padding: 14px 16px; text-decoration: none; color: inherit; }
.event-card__link:hover { background: var(--surface-2); }
.event-card__link:focus-visible { outline: 2px solid var(--section-focus, var(--sec-strong, var(--sec))); outline-offset: -2px; }
.event-card__date { display: grid; place-items: center; width: 58px; padding: 6px 0; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm, 6px); }
.event-card__day { font-size: 24px; font-weight: 700; line-height: 1; }
.event-card__month { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }
.event-card__body { min-width: 0; }
.event-card__title { margin: 0 0 4px; font-size: 19px; }
.event-card__when { margin: 0; font-size: 14px; color: var(--muted); display: flex; flex-wrap: wrap; gap: 10px; }
.event-card__hour { font-weight: 700; color: var(--ink); }
.event-card__facts { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 14px; color: var(--muted); }
.event-card__fact--signup { color: var(--sec-strong, var(--sec)); font-weight: 700; }
.event-card__img { width: 88px; height: 66px; object-fit: cover; border-radius: var(--radius-sm, 6px); }
.event-card__category { position: absolute; top: 10px; right: 12px; padding: 2px 8px; font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; text-decoration: none; color: var(--sec-strong, var(--sec)); background: var(--sec-soft, var(--surface-2)); border-radius: var(--radius-sm, 6px); }
/* Odwołane — widoczne, ale wyraźnie inne. */
.event-card.is-cancelled .event-card__title,
.event-card.is-cancelled .event-card__date { opacity: .55; }
.event-card.is-cancelled .event-card__title { text-decoration: line-through; }
.event-card__cancelled { margin: 4px 0 0; font-size: 14px; font-weight: 700; color: var(--section-danger, #a4343a); }
.event-list__exit { margin: 18px 0 0; }

.event-page__when { margin: 8px 0 0; font-size: 16px; font-weight: 700; }
.event-page__repeat { font-weight: 400; color: var(--muted); }
.event-page__cancelled { margin: 10px 0 0; padding: 10px 14px; background: var(--surface-2); border-left: 4px solid #a4343a; border-radius: var(--radius-sm, 6px); font-weight: 700; }
.event-page__notes { margin: 14px 0; padding: 10px 14px; background: var(--surface-2); border-radius: var(--radius-sm, 6px); }
.event-page__categories { margin: 0 0 8px; display: flex; flex-wrap: wrap; gap: 6px; }

.event-facts { margin: 24px 0; padding: 18px 20px; background: var(--surface-2); border-radius: var(--radius-md, 10px); }
.event-facts__heading { margin: 0 0 10px; font-size: 20px; }
.event-facts__list { display: grid; grid-template-columns: auto 1fr; gap: 6px 18px; margin: 0; }
.event-facts__list dt { font-weight: 700; color: var(--muted); }
.event-facts__list dd { margin: 0; }
.event-facts__account { font-variant-numeric: tabular-nums; }

.event-terms { margin: 24px 0; }
.event-terms__heading { margin: 0 0 6px; font-size: 20px; }
.event-terms__rule { margin: 0 0 10px; color: var(--muted); }
.event-terms__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.event-terms__item { display: flex; gap: 10px; align-items: baseline; padding: 6px 10px; background: var(--surface-2); border-radius: var(--radius-sm, 6px); }
.event-terms__item.is-cancelled .event-terms__when { text-decoration: line-through; opacity: .6; }
.event-terms__badge { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #a4343a; }
.event-terms__toggle { margin-top: 10px; }

@media (max-width: 640px) {
  .event-card__link { grid-template-columns: auto 1fr; }
  .event-card__img { display: none; }
}

/* ==========================================================================
   ARTYKUŁY (11.08.2026)
   Spis treści serii, nie strumień kafli: sam tytuł, opcjonalnie data.
   Wyróżniony tekst stoi na górze listy i dostaje wyraźniejszy napis — to
   jedyne, co wyróżnienie w tym module robi.
   ========================================================================== */
.article-list { margin: 26px 0; }
.article-list__heading { margin: 0 0 10px; font-size: 20px; }
.article-list__items { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.article-list__item { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; padding: 9px 12px; border-bottom: 1px solid var(--section-border, var(--border)); }
.article-list__item:last-child { border-bottom: 0; }
.article-list__item:hover { background: var(--surface-2); }
.article-list__link { font-size: 17px; text-decoration: none; color: var(--section-link, var(--ink)); }
.article-list__link:hover { text-decoration: underline; }
.article-list__link:focus-visible { outline: 2px solid var(--section-focus, var(--sec-strong, var(--sec))); outline-offset: 3px; }
.article-list__item.is-featured .article-list__link { font-weight: 700; }
.article-list__date { font-size: 14px; color: var(--section-muted, var(--muted)); }
.article-page__back { margin: 18px 0 0; }

/* Klauzula zgody — rozwijany blok pod ptaszkiem. `<details>` zamiast własnego
   skryptu: działa bez JS, obsługuje wyszukiwanie w przeglądarce i klawiaturę. */
.front-form__consent { margin: 6px 0 0 30px; font-size: 13px; }
.front-form__consent-summary { cursor: pointer; color: var(--muted); text-decoration: underline; }
.front-form__consent-summary:focus-visible { outline: 2px solid var(--section-focus, var(--sec-strong, var(--sec))); outline-offset: 2px; }
.front-form__consent-text { margin: 8px 0 0; padding: 10px 12px; color: var(--muted); line-height: 1.55; background: var(--surface); border-radius: var(--radius-sm, 6px); }
