/* =========================================================
   TOKENY STYLI — jedno miejsce, z którego czerpią kolory/odstępy
   powtarzające się w wielu miejscach pliku. Poza wygodą (zmiana w jednym
   miejscu zamiast szukania po całym CSS) to też przygotowanie pod
   przyszły przełącznik jasny/ciemny motyw: cały motyw ciemny (obecny,
   jedyny na razie) jest zdefiniowany tu, w :root. Jasny motyw, kiedy
   powstanie, nadpisze te same nazwy zmiennych w jednym dodatkowym bloku
   (np. `html[data-theme="light"] { --bg-1: #fff; ... }`) zamiast wymagać
   przepisywania reguł w całym pliku.
   ========================================================= */
:root {
    /* AlkoCzwartek — akcent z logo (bursztyn piwa #e4ad3c). --accent to
       przyciemniony bursztyn: używany jako kolor TEKSTU linków i tła pod
       BIAŁYM napisem (pigułki menu, plakietki), więc musi mieć kontrast
       z bielą ok. 4.3:1 — czysty #e4ad3c ma tylko 2:1 i byłby nieczytelny.
       Jasny bursztyn z logo żyje w --accent-light i --brand-amber (przyciski
       z ciemnym tekstem, poświaty, wyróżnienia). */
    --accent: #a86f10;
    --accent-light: #e4ad3c;
    --accent-dark: #7a4d08;
    --brand-amber: #e4ad3c;
    --brand-amber-strong: #d69b22;
    --brand-ink: #20201e;
    --brand-red: #c10016;

    /* Tło strony (radialny gradient pod całą stroną) i najciemniejszy
       odcień, w który wtapia się dół baneru działu — musi być NIEMAL
       identyczny z kolorem tła w tym miejscu, inaczej widać szew. */
    --bg-1: #161616;
    --bg-2: #0a0a0a;
    --bg-3: #050505;
    --bg-deep: #060504;

    /* Baner-nagłówek działu (Pulpit/Trasy/Ogłoszenia/Wydarzenia/Galeria/Czat).
       --banner-gradient to CAŁA nakładka na zdjęciu jako jedna zmienna (nie
       tylko kolor końcowy) — ważne, bo w ciemnym motywie ma sens dociemniać
       AŻ do koloru tła strony (--bg-deep) i dać treści na nią "nachodzić"
       (--banner-overlap), a w jasnym motywie próba zrobienia tego samego
       (ciemna od razu na zdjęciu -> jasne tło strony) dawała brzydki,
       mętny "szew" — dwa bardzo różne kolory nie da się ładnie przenikać
       na krótkim dystansie. W jasnym motywie baner więc kończy się zwykłym,
       czystym cięciem (umiarkowany ciemny winieta na zdjęciu, bez próby
       dopasowania do koloru tła) i BEZ nachodzenia — patrz override niżej. */
    --banner-gradient: linear-gradient(180deg, rgba(6,5,4,0.34) 0%, rgba(6,5,4,0.4) 30%, rgba(6,5,4,0.6) 55%, rgba(6,5,4,0.86) 76%, var(--bg-deep) 92%, var(--bg-deep) 100%);
    /* Osobna, dużo lżejsza nakładka na baner rozgrywki Statki
       (.battleship-game-banner) — --banner-gradient wyżej był dobrany pod
       zdjęcia bez wyrazistego pierwszego planu (dociemnia mocno, żeby
       nachodzący tekst zawsze był czytelny), a tu obcinał sporą część
       ładnej grafiki tła. Też kończy się w --bg-deep dla płynnego przejścia
       w tło strony, ale wcześniejsze przystanki są dużo jaśniejsze. */
    --battleship-banner-gradient: linear-gradient(180deg, rgba(6,5,4,0.18) 0%, rgba(6,5,4,0.22) 35%, rgba(6,5,4,0.4) 62%, rgba(6,5,4,0.7) 84%, var(--bg-deep) 96%, var(--bg-deep) 100%);
    --banner-min-height: 260px;
    --banner-min-height-mobile: 190px;
    /* Wyrównane z jasnym motywem (patrz html[data-theme="light"] niżej) —
       brak nachodzenia treści na baner, tylko odstęp po nim. Wcześniej
       ciemny motyw miał overlap:46px/30px (treść wjeżdżała na baner) a
       jasny 0px + gap 20px — ta różnica dawała widoczne "skakanie" treści
       (i pośrednio paska do pisania w czacie) w dół/górę przy przełączaniu
       motywu. Ujednolicone na żądanie usera (test na telefonie ma
       zdecydować, czy to jest właściwy kierunek, czy odwrotny —
       nachodzenie w obu motywach). */
    --banner-overlap: 0px;
    --banner-overlap-mobile: 0px;
    --banner-gap-after: 20px;

    /* --- "Chrome" strony — topbar, stopka, powitanie na pulpicie i inne
       elementy leżące BEZPOŚREDNIO na tle strony (nie w .card/.chat-layout/
       modalu — te są już jasne i niezależne od motywu, więc nie są tu
       tokenizowane). To jest właściwa "podstawa pod przełącznik": jasny
       motyw (niżej, html[data-theme="light"]) nadpisuje te same nazwy. */
    --text-hero: #fff;
    --text-hero-muted: #999;
    --text-chrome: #eee;
    --text-chrome-muted: #999;
    --text-chrome-faint: #666;
    --text-chrome-dim: #888;

    --chrome-fill: rgba(255,255,255,0.06);
    --chrome-fill-hover: rgba(255,255,255,0.11);
    --chrome-fill-strong: rgba(255,255,255,0.13);
    --chrome-border: rgba(255,255,255,0.08);
    --chrome-border-strong: rgba(255,255,255,0.12);

    --panel-bg: #151515;
    --panel-border: rgba(255,255,255,0.08);
    --panel-hover: rgba(255,255,255,0.07);
    --panel-shadow: 0 2px 12px rgba(0,0,0,0.3);
    --panel-ring: #1a1a1a;

    --nav-pill-bg: rgba(255,255,255,0.05);
    --nav-pill-hover: rgba(255,255,255,0.07);

    /* Karty/czat/auth-card są już jasne niezależnie od motywu (patrz
       komentarz przy html[data-theme="light"] niżej) — ta jedna zmienna to
       jedyna rzecz, którą dostają inną: odrobinę mocniejszą ramkę w jasnym
       motywie, żeby nie "znikały" na też-już-jasnym tle strony. */
    --card-border: rgba(0,0,0,0.05);

    /* Tło ".card" (Trasy społeczności/Ranking/W tym tygodniu/Ogłoszenia i
       praktycznie każde inne okienko w serwisie) — zgłoszenie użytkownika
       28.08.2026: po zmianie koloru motywu na niebieski zmieniły się tylko
       linki/przyciski, "a co z okienkami i ich odcieniami, to miał być cały
       serwis". Wydzielone do zmiennej właśnie po to, żeby dało się to
       nadpisać per-kolor (patrz html[data-theme-color=...] niżej).
       15.09.2026, dwuetapowo: 1) --card-bg okazał się podpięty tylko pod
       samą .card — dziesiątki innych "okienek" (pasek boczny/pole czatu,
       dropdowny, kalendarz flatpickr, tło .chat-fullscreen, końcówki
       gradientów kilku kart specjalnych) miały ten sam odcień WPISANY NA
       SZTYWNO jako #f6f3ef, nie przez tę zmienną — nigdy nie doprowadzone
       do końca przy wprowadzaniu --card-bg 28.08.2026 (zgłoszenie: "okna
       się nie zmieniają kolorystycznie"). Wszystkie te miejsca podpięte
       pod var(--card-bg). 2) Dopiero PO tym domknięciu (żeby nie zrobić
       widocznego szwu) domyślny motyw dostaje własny, delikatnie
       czerwonawy odcień zamiast neutralnego kremu — ten sam wzorzec co
       reszta kolorów niżej (por. 'red'/"Bordowy" #f7eeec). 'orange'
       (dawny domyślny, patrz html[data-theme-color="orange"] niżej)
       NADPISUJE to z powrotem na dokładny stary #f6f3ef — nie jest tym
       dotknięty.
       POPRAWKA 17.09.2026, druga iteracja (produkcja, zrzut ekranu):
       #fae7e3 nadal był za mocno nasycony i przy dużych, jednolitych
       powierzchniach (Ranking/"W tym tygodniu"/"Z czatu") czytał się jako
       wyraźny róż, nie ciepły odcień kremu — zgłoszenie: "średnio pasuje...
       taki róż majtkowy" (i słusznie: dobierałem hex na oko, zamiast metodą,
       jaką robią to gotowe systemy). Sprawdzone jak podchodzi do tego Radix
       UI Colors/Material Design: tonalną powierzchnię buduje się jako
       niski % głównego koloru wmieszany w bazowe tło, nie osobno dobierany
       odcień — stąd teraz color-mix() zamiast sztywnego hex. Podgląd 3
       wariantów (3/5/8%) — użytkownik najpierw wybrał środkowy (5%), potem
       jednak zmienił zdanie na najsubtelniejszy (3%, wariant A). Efekt ten
       sam co ten podgląd (#f5ece9 przy obecnym czerwonym --accent), ale
       automatycznie przeliczy się poprawnie, gdyby --accent kiedyś znów
       się zmienił — bez powtórki tego samego zgadywania. */
    --card-bg: color-mix(in srgb, var(--accent) 3%, #f6f3ef);
}

/* --- Jasny motyw — nadpisuje WYŁĄCZNIE nazwy zmiennych z bloku :root
   powyżej. Zdjęcie w banerze zostaje wizualnie takie samo w obu motywach
   (ciemna nakładka na zdjęciu + biały tekst zawsze dobrze się czyta,
   niezależnie od tła strony wokół) — zmienia się tylko tło strony, topbar,
   stopka i elementy leżące bezpośrednio na tle. Karty/czat/modale są już
   jasne i teoretycznie niezależne od motywu, więc nie są tu ruszane —
   dostają jedynie odrobinę innej ramki (patrz .card niżej), żeby nie
   "zlewać się" wizualnie z też-już-jasnym tłem strony. */
html[data-theme="light"] {
    --bg-1: #fbf7ee;
    --bg-2: #f5eedd;
    --bg-3: #eee2c8;
    --bg-deep: #eee2c8;

    --text-hero: #17140f;
    --text-hero-muted: #6b6560;
    --text-chrome: #17140f;
    --text-chrome-muted: #6b6560;
    --text-chrome-faint: #8a847d;
    --text-chrome-dim: #8a847d;

    --chrome-fill: rgba(23,20,15,0.05);
    --chrome-fill-hover: rgba(23,20,15,0.09);
    --chrome-fill-strong: rgba(23,20,15,0.12);
    --chrome-border: rgba(23,20,15,0.1);
    --chrome-border-strong: rgba(23,20,15,0.16);

    --panel-bg: #ffffff;
    --panel-border: rgba(23,20,15,0.08);
    --panel-hover: rgba(23,20,15,0.05);
    --panel-shadow: 0 2px 12px rgba(23,20,15,0.14);
    --panel-ring: #f3f0e9;

    --nav-pill-bg: rgba(23,20,15,0.05);
    --nav-pill-hover: rgba(23,20,15,0.07);

    --card-border: rgba(23,20,15,0.13);

    /* Baner w jasnym motywie: zwykła, umiarkowana ciemna winieta na zdjęciu,
       która kończy się SAMA W SOBIE (nie próbuje dojechać do jasnego tła
       strony — stąd brak var(--bg-deep) w tym gradiencie, w przeciwieństwie
       do wersji ciemnej). Bez nachodzenia treści (overlap=0, gap zamiast
       tego daje odrobinę oddechu pod spodem) — czyste, płaskie przejście. */
    --banner-gradient: linear-gradient(180deg, rgba(6,5,4,0.22) 0%, rgba(6,5,4,0.3) 40%, rgba(6,5,4,0.48) 70%, rgba(6,5,4,0.68) 100%);
    /* Lżejszy odpowiednik dla .battleship-game-banner, patrz komentarz przy
       wersji ciemnej wyżej. */
    --battleship-banner-gradient: linear-gradient(180deg, rgba(6,5,4,0.12) 0%, rgba(6,5,4,0.18) 40%, rgba(6,5,4,0.32) 70%, rgba(6,5,4,0.51) 100%);
    --banner-overlap: 0px;
    --banner-overlap-mobile: 0px;
    --banner-gap-after: 20px;
}

/* --- Kolor motywu (opcjonalny, NIEZALEŻNY od atrybutu jasny/ciemny, ale
   każdy kolor ma WŁASNĄ ciemną i jasną odmianę — patrz drugi blok niżej) —
   zgłoszenie użytkownika 28.08.2026: "nie każdy lubi czarny i taki
   beżowy... żeby user mogli w profilu sobie wybrać dla całego serwisu",
   potem doprecyzowane: "pomarańczowy jest domyślny i zostaje z czarnym
   tłem, a pozostałe [kolory] jak ten kompletny motyw beżowy przy zmianie u
   góry na jasny" — czyli KAŻDY kolor ma być pełnym motywem (tło strony,
   panele, karty), nie tylko przefarbowanym akcentem, w obu wariantach
   jasny/ciemny (wybrane przez użytkownika w pytaniu doprecyzowującym:
   "Kolor = jasny I ciemny wariant").
   Ten pierwszy blok = CIEMNA odmiana każdego koloru (domyślna, gdy nie ma
   html[data-theme="light"]) — nadpisuje --accent/--accent-light/
   --accent-dark (sterują przyciskami/linkami/plakietkami/focus ringiem/
   kalendarzem w całym serwisie, dzięki refaktorowi tego samego dnia, w
   którym każdy dotąd twardo wpisany #d97b2e/#b8631f/rgba(217,123,46,...)
   zamieniono na var(--accent...)/color-mix(...)), --card-bg (kremowe tło
   .card — WSPÓLNE dla jasnej i ciemnej odmiany tego samego koloru, karty
   zawsze zostają jasne niezależnie od motywu, dokładnie jak dotychczas przy
   przełączniku jasny/ciemny — stąd ta sama wartość --card-bg wystarcza
   tutaj, nie trzeba jej powtarzać w bloku jasnym niżej) oraz --bg-1/-2/-3/
   -deep i --panel-bg/--panel-ring (żeby SAMO tło strony i paneli też się
   przefarbowało, nie tylko akcent — to dokładnie ta poprawka, o którą
   poprosił użytkownik). --chrome-fill/--chrome-border/--nav-pill-*
   celowo NIE są tu nadpisywane — to półprzezroczyste nakładki bieli
   (rgba(255,255,255,x)), które automatycznie dobrze wyglądają na dowolnym
   przefarbowanym tle, bez potrzeby osobnego nadpisania dla każdego koloru.
   Atrybut ustawiany server-side w render_header() (includes/layout.php) na
   podstawie users.theme_color — w przeciwieństwie do
   html[data-theme="light"] (ustawianego inline-skryptem z localStorage, bo
   to wybór lokalny dla przeglądarki), kolor motywu jest zapisany na
   koncie, więc może być ustawiony od razu, bez błysku złego koloru i bez
   osobnego JS-a. Brak atrybutu (wartość '' w users.theme_color) =
   domyślny pomarańczowy z :root wyżej, zawsze z czarnym tłem w trybie
   ciemnym — nic tu nie trzeba nadpisywać.
   AKTUALIZACJA 15.09.2026: domyślny (bezatrybutowy) motyw to teraz czerwony,
   nie pomarańczowy (patrz "Historia zmian domyślnego akcentu" w :root
   wyżej) — dawny pomarańczowy domyślny motyw został przeniesiony 1:1 do
   nowego, wybieralnego html[data-theme-color="orange"] niżej, więc nikt go
   nie stracił, tylko przestał być wymuszony. Klucze muszą się zgadzać z
   theme_color_options() w includes/helpers.php (tam też podgląd swatchy w
   profile.php). */
/* 'orange' DODANY 15.09.2026, tego samego dnia co zmiana domyślnego akcentu
   z pomarańczowego na czerwony (patrz "Historia zmian domyślnego akcentu"
   w :root wyżej) — zgłoszenie użytkownika: "jeśli ktoś jednak będzie chciał
   mieć ten stary kolor to dodajmy go do palety... czerwony jest nowy
   domyślny a pomarańcz zostaje do wyboru". Wartości 1:1 identyczne z tym,
   co przez cały czas było DOMYŚLNYM (bezatrybutowym) motywem przed tą
   zmianą: ten sam pomarańczowy akcent, to samo neutralne czarne tło w
   ciemnym wariancie i ten sam kremowy --card-bg — celowo BEZ własnego
   przefarbowanego tła jak blue/red/pink/gray niżej, bo pomarańczowy jako
   ówczesny domyślny zawsze zostawał z czarnym tłem (patrz komentarz o
   "Kolor motywu" wyżej: "pomarańczowy jest domyślny i zostaje z czarnym
   tłem"). Wybranie tego presetu odtwarza więc dokładnie to, jak serwis
   wyglądał przed dzisiejszą zmianą. */
html[data-theme-color="orange"] {
    --accent: #d97b2e;
    --accent-light: #f0994a;
    --accent-dark: #b8631f;
    --card-bg: #f6f3ef;
    --bg-1: #161616;
    --bg-2: #0a0a0a;
    --bg-3: #050505;
    --bg-deep: #060504;
    --panel-bg: #151515;
    --panel-ring: #1a1a1a;
}
html[data-theme-color="blue"] {
    --accent: #2f74c4;
    --accent-light: #5b9bdb;
    --accent-dark: #123457;
    --card-bg: #eef2f7;
    --bg-1: #121820;
    --bg-2: #0a0d12;
    --bg-3: #05070a;
    --bg-deep: #05070a;
    --panel-bg: #141a22;
    --panel-ring: #182028;
}
html[data-theme-color="red"] {
    --accent: #b8342c;
    --accent-light: #e2574f;
    --accent-dark: #6e120e;
    --card-bg: #f7eeec;
    --bg-1: #1a1210;
    --bg-2: #0d0908;
    --bg-3: #060504;
    --bg-deep: #060504;
    --panel-bg: #1c1412;
    --panel-ring: #201613;
}
/* 'purple' zastąpione 'pink' 28.08.2026 (zgłoszenie użytkownika ze
   screenshotem kafelka "Nadchodzących wydarzeń": "zamiast fioletowego
   zrobiłbym projekt pasujący do tych odcieni... jest ładny") — kolory 1:1
   przejęte z .stat-card-tint-pink niżej w tym pliku (#c14a76/#611f37,
   obwódka #e07ea3), żeby motyw wyglądał dokładnie jak ten polubiony
   kafelek. Klucz musi się zgadzać z theme_color_options() w
   includes/helpers.php. */
html[data-theme-color="pink"] {
    --accent: #c14a76;
    --accent-light: #e07ea3;
    --accent-dark: #611f37;
    --card-bg: #f8eef2;
    --bg-1: #1a1218;
    --bg-2: #0d090b;
    --bg-3: #060505;
    --bg-deep: #060505;
    --panel-bg: #1c141a;
    --panel-ring: #201620;
}
html[data-theme-color="gray"] {
    --accent: #5a6472;
    --accent-light: #8a94a3;
    --accent-dark: #2e343d;
    --card-bg: #eef0f1;
    --bg-1: #17181a;
    --bg-2: #0b0c0d;
    --bg-3: #060606;
    --bg-deep: #060606;
    --panel-bg: #191a1c;
    --panel-ring: #1c1d1f;
}

/* Drugi blok = JASNA odmiana każdego koloru — selektor złożony z DWÓCH
   atrybutów (html[data-theme="light"][data-theme-color="blue"]) ma wyższą
   specyficzność niż sam html[data-theme-color="blue"] wyżej, więc te
   --bg-1/-2/-3/-deep/--panel-ring/--panel-bg poprawnie nadpisują ciemne
   wartości z bloku wyżej, gdy ktoś ma WŁĄCZONY jasny motyw (księżyc/słońce
   w topbarze) I wybrany kolor.
   NAPRAWA 28.08.2026 (zgłoszenie użytkownika ze screenshotem: czarny,
   nieczytelny prostokąt menu użytkownika w rogu na jasnym niebieskim
   motywie): --panel-bg (tło m.in. rozwijanego menu w topbarze) MUSI być tu
   jawnie powtórzone jako białe, nie wystarczy samo poleganie na tym, że
   zwykły html[data-theme="light"] wyżej już je ustawia na białe — ten blok
   i html[data-theme-color="blue"] (ciemna odmiana, ustawia --panel-bg na
   ciemny granat) mają RÓWNĄ specyficzność (jeden atrybut + html), więc o
   tym, który wygrywa, decydowała wyłącznie kolejność w pliku — a
   html[data-theme-color="blue"] jest zdefiniowany NIŻEJ (po
   html[data-theme="light"]), więc to jego ciemna wartość wygrywała nawet
   przy włączonym jasnym motywie. --bg-1/-2/-3/-deep/--panel-ring nie miały
   tego problemu, bo są jawnie powtórzone w tym bloku — --panel-bg był
   jedyną zmienną, która "wypadła" z tej reguły. --accent/--accent-light/
   --accent-dark/--card-bg NIE są tu powtórzone (i to jest OK) — nie mają
   tego konfliktu, bo zwykły html[data-theme="light"] w ogóle ich nie
   dotyka, więc nie ma z czym "remisować" na równej specyficzności. */
/* Jasny wariant 'orange' — te same wartości co zwykły html[data-theme="light"]
   wyżej (bo to dokładnie to samo, w co zawsze zmieniał się dawny domyślny
   pomarańczowy motyw po przełączeniu na jasny), powtórzone jawnie z tego
   samego powodu specyficzności co reszta kolorów w tym bloku (patrz
   komentarz nad html[data-theme="light"][data-theme-color="blue"] niżej). */
html[data-theme="light"][data-theme-color="orange"] {
    --bg-1: #f3f0e9;
    --bg-2: #ebe6da;
    --bg-3: #e3ddce;
    --bg-deep: #e3ddce;
    --panel-bg: #ffffff;
    --panel-ring: #f3f0e9;
}
html[data-theme="light"][data-theme-color="blue"] {
    --bg-1: #eef2f8;
    --bg-2: #e2e9f2;
    --bg-3: #d7e0ec;
    --bg-deep: #d7e0ec;
    --panel-bg: #ffffff;
    --panel-ring: #eef2f8;
}
html[data-theme="light"][data-theme-color="red"] {
    --bg-1: #f8eeed;
    --bg-2: #f1e0de;
    --bg-3: #ead3d0;
    --bg-deep: #ead3d0;
    --panel-bg: #ffffff;
    --panel-ring: #f8eeed;
}
html[data-theme="light"][data-theme-color="pink"] {
    --bg-1: #f8eef3;
    --bg-2: #f1e0e8;
    --bg-3: #ead0dc;
    --bg-deep: #ead0dc;
    --panel-bg: #ffffff;
    --panel-ring: #f8eef3;
}
html[data-theme="light"][data-theme-color="gray"] {
    --bg-1: #eff0f1;
    --bg-2: #e3e5e7;
    --bg-3: #d7dadc;
    --bg-deep: #d7dadc;
    --panel-bg: #ffffff;
    --panel-ring: #eff0f1;
}

* { box-sizing: border-box; }

/* Siatka bezpieczeństwa dla edytora TinyMCE (patrz initEditorDraftSafetyNet()
   w assets/editor.js) — zgłoszenie użytkownika 26.09.2026: kontener treści
   edytora potrafił zapaść się do wysokości 0 (zostawał tylko toolbar),
   najpewniej kaprys biblioteki po niefortunnym zamknięciu okienka (charmap/
   link). min-height na samym kontenerze TinyMCE to druga, niezależna linia
   obrony — nawet gdyby to samo się powtórzyło, treść fizycznie nie zniknie
   z widoku (chowa się co najwyżej pod inną zawartością, nie do zera). Nie
   dotyka wewnętrznych elementów TinyMCE poza wysokością kontenera, więc nie
   koliduje z jego własnym layoutem/skórką. */
.tox-tinymce { min-height: 420px !important; }

body {
    margin: 0;
    min-height: 100vh;
    overflow-x: hidden;
    /* Wyłącza powiększanie strony podwójnym stuknięciem na telefonie (np.
       przy szybkim, podwójnym kliknięciu w pozycję menu) — celowo
       "touch-action: manipulation", NIE "user-scalable=no" w meta viewport
       ani "touch-action: none": manipulation kasuje TYLKO double-tap-zoom,
       przewijanie i przybliżanie DWOMA palcami (pinch-zoom) — ważne dla osób
       słabowidzących — dalej działa normalnie. Zgłoszenie użytkownika
       25.09.2026 ("czy idzie zablokować serwis, aby się nie powiększał przy
       dwukrotnym kliknięciu"). */
    touch-action: manipulation;
    background: radial-gradient(ellipse at top, var(--bg-1) 0%, var(--bg-2) 55%, var(--bg-3) 100%);
    background-attachment: fixed;
    color: var(--text-chrome);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    -webkit-font-smoothing: antialiased;
}

/* --- Baner-nagłówek działu: pełna szerokość okna, wtapia się dołem w tło
   strony (--bg-deep), a treść pod nim (.card / .chat-layout / .welcome-hero)
   nachodzi na ten wtapiający się fragment tak, żeby nie było widać żadnej
   przerwy ani szwu. Oba elementy dostają position:relative + z-index, bo
   inaczej pozycjonowany .section-banner (position:relative, potrzebne dla
   sztuczki z pełną szerokością) potrafi malować się NAD zwykłymi,
   niepozycjonowanymi elementami mimo że jest wcześniej w DOM — to właśnie
   ten błąd z nakładaniem się grafiki na treść. --- */
.section-banner {
    position: relative;
    z-index: 0;
    left: 50%;
    right: 50%;
    width: 100vw;
    margin-left: -50vw;
    margin-right: -50vw;
    margin-top: 0;
    margin-bottom: calc(-1 * var(--banner-overlap));
    padding-bottom: var(--banner-overlap);
    min-height: var(--banner-min-height);
    background-size: cover;
    background-position: center;
    background-color: #1a1712;
    display: flex;
    align-items: flex-end;
}
.section-banner-inner {
    max-width: 1180px;
    margin: 0 auto;
    width: 100%;
    padding: 24px 16px 58px;
}
.section-banner-inner h1 {
    color: #fff;
    font-size: 22px;
    margin: 0 0 4px;
    text-shadow: 0 1px 6px rgba(0,0,0,0.5);
}
.section-banner-inner p {
    color: #f0ece4;
    font-size: 13px;
    margin: 0;
    max-width: 640px;
    text-shadow: 0 1px 6px rgba(0,0,0,0.5);
}
/* Cokolwiek jest zaraz po banerze (.card, .chat-layout, .welcome-hero...)
   musi wygrywać nad nim w kolejności malowania — patrz komentarz wyżej. */
.section-banner + * {
    position: relative;
    z-index: 1;
    margin-top: var(--banner-gap-after);
}

/* Powitanie na pulpicie (.welcome-hero) nie ma własnego tła (w
   przeciwieństwie do .card, które zawsze jest jasne niezależnie od motywu)
   — więc "nachodzenie" na baner (--banner-overlap), które w reszcie
   serwisu chowa się bez śladu pod tłem kart, tutaj w motywie ciemnym
   wystawało na widok wprost na zdjęcie baneru. Efekt: zawsze czyste
   odsunięcie od baneru, tak jak w jasnym motywie, niezależnie od tego,
   czy w danym motywie baner ma nachodzenie włączone.
   (10.08.2026: quizzes.php/quiz_admin.php miały tu tymczasowo tę samą
   poprawkę pod .quiz-hero/.quiz-back-link, bo te dwa nie były kartami —
   ostatecznie naprawione u źródła: oba są teraz zawsze prawdziwą .card
   zaraz po banerze, więc dodatkowa reguła przestała być potrzebna.) */
.section-banner + .welcome-hero {
    margin-top: calc(var(--banner-overlap) + 20px);
}

@media (max-width: 720px) {
    .section-banner {
        min-height: var(--banner-min-height-mobile);
        margin-top: -14px;
        margin-bottom: calc(-1 * var(--banner-overlap-mobile));
        padding-bottom: var(--banner-overlap-mobile);
    }
    .section-banner-inner { padding: 16px 12px 38px; }
    .section-banner-inner h1 { font-size: 18px; }

    .section-banner + .welcome-hero {
        margin-top: calc(var(--banner-overlap-mobile) + 16px);
    }
}

a { color: inherit; }

.icon { display: inline-block; vertical-align: -3px; flex-shrink: 0; }

.wrap {
    max-width: 1180px;
    margin: 0 auto;
    padding: 20px 16px 60px;
}

/* --- Górny pasek nawigacji (dwuwierszowy: logo/szukaj/użytkownik + osobny rząd menu) --- */
.topbar {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 14px 4px 18px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--chrome-border);
}
.topbar-row1 {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}
.topbar .brand {
    display: flex;
    align-items: center;
    gap: 9px;
    font-weight: 700;
    font-size: 15px;
    letter-spacing: .01em;
    text-decoration: none;
    flex-shrink: 0;
}
.topbar .brand img { height: 54px; width: auto; display: block; }
/* Logo AlkoCzwartek (assets/logo-alko.svg) jest kolorowe i ma własne,
   okrągłe tło — dobrze wygląda i na jasnym, i na ciemnym motywie, więc bez
   filtra (w SOG biały plik był tu przyciemniany do czerni). */

/* Przycisk "hamburger" i wrapper reszty paska — na desktopie oba są
   niewidoczne/przezroczyste dla layoutu (display:contents na wrapperze
   sprawia, że jego dzieci — szukajka/dzwonek/użytkownik — zachowują się
   dokładnie tak, jakby nie było wokół nich żadnego diva), więc desktop
   wygląda identycznie jak wcześniej. Realną rolę oba elementy zaczynają
   grać dopiero w mobilnej media query niżej. */
.topbar-menu-toggle {
    display: none;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 8px;
    border: none;
    background: var(--chrome-fill);
    color: var(--text-chrome);
    cursor: pointer;
    flex-shrink: 0;
}
.topbar-menu-toggle:hover { background: var(--chrome-fill-hover); }
.topbar-row1-extras { display: contents; }
/* Wrapper Pomoc+dzwonek — na desktopie ma być przezroczysty dla layoutu,
   tak samo jak .topbar-row1-extras powyżej; własny display:flex dostaje
   dopiero na telefonie (patrz @media niżej), żeby te dwie ikonki stanęły
   obok siebie zamiast jedna pod drugą. Zgłoszenie 20.09.2026. */
.topbar-icon-row { display: contents; }
/* Etykiety "Menu"/"Konto" nad wysuwanymi listami na telefonie (patrz
   includes/layout.php) — na desktopie oba dropdowny są same w sobie
   wystarczająco czytelne (małe, kontekstowe panele), więc etykiety tam są
   zbędne i pozostają ukryte; dostają wygląd dopiero w mobilnej media query
   niżej. */
.topbar-section-label { display: none; }

.topbar-nav-row {
    display: flex;
    align-items: center;
    gap: 2px;
    background: var(--nav-pill-bg);
    border-radius: 7px;
    padding: 4px;
    flex-wrap: wrap;
}
.topbar-nav-row a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    border-radius: 5px;
    font-size: 12px;
    font-weight: 500;
    color: var(--text-chrome-muted);
    text-decoration: none;
    transition: background .15s, color .15s;
    white-space: nowrap;
}
.topbar-nav-row a:hover { color: var(--text-chrome); background: var(--nav-pill-hover); }
.topbar-nav-row a.active { background: var(--accent); color: #fff; font-weight: 600; }

/* Submenu "Rozrywka" (Quizy + Statki) w głównym pasku — przycisk-wyzwalacz
   wygląda jak zwykła pozycja .topbar-nav-row a (ten sam padding/rozmiar),
   ale to <button>, bo otwiera rozwijany panel zamiast linkować gdzieś
   bezpośrednio. Panel wzorowany 1:1 na .user-menu-dropdown (patrz niżej w
   tym pliku) — ten sam wygląd okienka w całym serwisie. Dodane 17.09.2026,
   zgłoszenie użytkownika: "połączyłbym w submenu quiz i statki i nazwał
   dział rozrywka". */
.nav-dropdown-wrap { position: relative; }
.nav-dropdown-trigger {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    border-radius: 5px;
    font-size: 12px;
    font-weight: 500;
    font-family: inherit;
    color: var(--text-chrome-muted);
    background: none;
    border: none;
    cursor: pointer;
    white-space: nowrap;
    transition: background .15s, color .15s;
}
.nav-dropdown-trigger:hover { color: var(--text-chrome); background: var(--nav-pill-hover); }
.nav-dropdown-trigger.active { background: var(--accent); color: #fff; font-weight: 600; }
.nav-dropdown-trigger .chevron { color: inherit; opacity: .75; transition: transform .15s; }
.nav-dropdown-trigger.open .chevron { transform: rotate(180deg); }
.nav-dropdown-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 6px;
    padding: 6px;
    min-width: 160px;
    display: none;
    flex-direction: column;
    z-index: 50;
    box-shadow: var(--panel-shadow);
}
.nav-dropdown-menu.open { display: flex; }
.nav-dropdown-menu a {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 10px;
    border-radius: 5px;
    color: var(--text-chrome);
    text-decoration: none;
    font-size: 12px;
    white-space: nowrap;
}
.nav-dropdown-menu a:hover { background: var(--panel-hover); }
.nav-dropdown-menu a.active { background: var(--accent); color: #fff; font-weight: 600; }

/* --- Zagnieżdżone podmenu (Ustawienia strony -> Menu, dowolna głębokość) —
   wcięty, zwijany <details> WEWNĄTRZ już otwartego panelu (.nav-dropdown-menu
   / .user-menu-dropdown / .site-footer-links), zamiast kolejnego pływającego
   panelu z boku — działa identycznie na każdej głębokości i na telefonie bez
   dodatkowej logiki pozycjonowania. Patrz render_menu_tree() w
   includes/helpers.php. */
.menu-subdetails { display: flex; flex-direction: column; }
.menu-subsummary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 10px;
    border-radius: 5px;
    color: var(--text-chrome-muted);
    font-size: 12px;
    cursor: pointer;
    list-style: none;
}
.menu-subsummary::-webkit-details-marker { display: none; }
.menu-subsummary:hover { background: var(--panel-hover); color: var(--text-chrome); }
.menu-subdetails.active > .menu-subsummary { color: var(--text-chrome); font-weight: 600; }
.menu-subsummary .chevron { margin-left: auto; opacity: .75; transition: transform .15s; }
.menu-subdetails[open] > .menu-subsummary .chevron { transform: rotate(180deg); }
.menu-submenu-list { display: flex; flex-direction: column; padding-left: 16px; }

.topbar-search {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--chrome-fill);
    border: 1px solid var(--chrome-border-strong);
    border-radius: 20px;
    padding: 8px 14px;
    color: var(--text-chrome-muted);
    width: 100%;
}
.topbar-search:focus-within { border-color: var(--accent); color: var(--accent-light); }

/* Zawijający kontener szukajki + rozwijana lista podpowiedzi użytkowników
   "na żywo" (jak na Facebooku) — dodane 18.08.2026, patrz assets/user-search.js
   i search_users_suggest.php. position:relative tutaj (nie na .topbar-search
   samym), żeby dropdown mógł być zwykłym rodzeństwem <form>, a nie jego
   dzieckiem — dzięki temu klik w wynik (link <a>) nie jest traktowany jak
   submit formularza. Wzorowane na .user-menu-dropdown/.notif-panel.
   flex:1 + max-width:360px PRZENIESIONE tutaj z .topbar-search (v373 miał tu
   błąd — oba elementy miały flex:1, .topbar-search miał max-width:360px, ale
   .topbar-search-wrap (bez capa) rozrastał się na całą wolną przestrzeń,
   spychając dzwonek/avatar w prawo i wizualnie "rozjeżdżając" pasek —
   zgłoszenie użytkownika ze screenami). Teraz cap jest tylko w jednym
   miejscu, tak jak przed dodaniem podpowiedzi. */
.topbar-search-wrap { position: relative; flex: 1; max-width: 360px; display: flex; }
.topbar-search-suggest {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 10px;
    padding: 6px;
    z-index: 55;
    box-shadow: var(--panel-shadow);
    max-height: 320px;
    overflow-y: auto;
}
.topbar-search-suggest.open { display: block; }
.topbar-search-suggest-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 8px;
    border-radius: 6px;
    color: var(--text-chrome);
    text-decoration: none;
}
.topbar-search-suggest-item:hover,
.topbar-search-suggest-item.active { background: var(--panel-hover); }
.topbar-search-suggest-item .avatar { flex-shrink: 0; }
.topbar-search-suggest-name { display: block; font-size: 13px; font-weight: 600; }
.topbar-search-suggest-meta { display: block; font-size: 11px; color: var(--text-chrome-muted); margin-top: 1px; }
.topbar-search-suggest-empty { padding: 10px 8px; font-size: 12px; color: var(--text-chrome-muted); }
.topbar-search input[type=text] {
    background: transparent;
    border: none;
    color: var(--text-chrome);
    width: 100%;
    padding: 0;
    font-size: 16px; /* patrz komentarz przy .chat-composer-row input[type=text] — ten sam powód (blokada auto-zoomu iOS) */
    /* Reset wspólnej reguły "input[type=text] { height:44px }" (dodanej przy
       ujednolicaniu wysokości pól formularzy) — tutaj to pole żyje wewnątrz
       własnej "pigułki" (.topbar-search) z własnym paddingiem, więc
       wymuszona wysokość 44px rozdymała cały pasek wyszukiwania w górnym
       menu do groteskowych rozmiarów. Wysokość ma dawać padding rodzica, nie
       samo pole. */
    height: auto;
}
@media (min-width: 769px) {
    .topbar-search input[type=text] { font-size: 13px; }
}
.topbar-search input[type=text]:focus { outline: none; box-shadow: none; }

.avatar {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 600;
    object-fit: cover;
    vertical-align: middle;
    flex-shrink: 0;
}

/* Kropka "online teraz" (users.last_seen_at, patrz is_user_online() w
   helpers.php) — doklejana jako mały, pozycjonowany element w prawym-dolnym
   rogu awatara. Wrapper (.avatar-wrap) jest potrzebny tam, gdzie awatar nie
   ma już własnego position:relative z innego powodu (np. .chat-bubble-avatar
   ma je już nadane bezpośrednio, więc kropkę można dokleić bez dodatkowego
   wrappera — patrz includes/chat_render.php). Stały rozmiar kropki (nie
   skalowany razem z awatarem) — przy większych awatarach (profil) wygląda
   odrobinę mniej "dopasowana", ale zostaje czytelna wszędzie bez dodatkowych
   wariantów rozmiaru. */
.avatar-wrap { position: relative; display: inline-flex; flex-shrink: 0; vertical-align: middle; }
.online-dot {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #3fb950;
    border: 2px solid #fff;
    box-sizing: content-box;
}

.topbar .user-menu-wrap { position: relative; flex-shrink: 0; }
.topbar .user-menu-trigger {
    display: flex;
    align-items: center;
    gap: 7px;
    background: var(--chrome-fill);
    border: none;
    border-radius: 20px;
    padding: 4px 12px 4px 4px;
    cursor: pointer;
    color: var(--text-chrome);
    font-size: 12px;
    font-family: inherit;
    font-weight: 400;
    box-shadow: none;
    transition: background .15s;
    white-space: nowrap;
}
.topbar .user-menu-trigger:hover { background: var(--chrome-fill-hover); opacity: 1; transform: none; box-shadow: none; }
.topbar .user-menu-trigger .badge { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent-light); }
.topbar .user-menu-trigger .chevron { color: var(--text-chrome-dim); transition: transform .15s; }
.topbar .user-menu-trigger.open .chevron { transform: rotate(180deg); }

.user-menu-dropdown {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 6px;
    padding: 6px;
    min-width: 170px;
    display: none;
    flex-direction: column;
    z-index: 50;
    box-shadow: var(--panel-shadow);
}
.user-menu-dropdown.open { display: flex; }
.user-menu-dropdown a {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 10px;
    border-radius: 5px;
    color: var(--text-chrome);
    text-decoration: none;
    font-size: 12px;
}
.user-menu-dropdown a:hover { background: var(--panel-hover); }
.user-menu-dropdown a.active { background: var(--accent); color: #fff; font-weight: 600; }

.notif-wrap { position: relative; flex-shrink: 0; }
.notif-trigger {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    min-width: 34px;
    padding: 0;
    flex-shrink: 0;
    background: var(--chrome-fill);
    border: none;
    border-radius: 50%;
    cursor: pointer;
    color: var(--text-chrome);
    text-decoration: none;
    transition: background .15s;
}
/* .notif-trigger jest teraz też używana na linku <a> (ikonka "?" Pomocy,
   includes/layout.php) — text-decoration:none wyżej dopisane 05.09.2026,
   żeby nie dostała podkreślenia jak zwykły link. */
.notif-trigger:hover { background: var(--chrome-fill-hover); opacity: 1; transform: none; box-shadow: none; }
.notif-trigger.open { background: var(--chrome-fill-strong); }
.notif-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    border-radius: 8px;
    background: var(--accent-light, #d97b2e);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 15px;
    text-align: center;
    box-shadow: 0 0 0 2px var(--panel-ring);
}
.notif-panel {
    position: absolute;
    top: calc(100% + 8px);
    /* left:50% + translateX(-50%) (zamiast dawnego right:0) wyśrodkowuje
       panel dokładnie pod dzwonkiem (.notif-wrap ma position:relative,
       więc to on jest punktem odniesienia), zamiast rozwijać go w lewo od
       prawej krawędzi przycisku — na szerszych oknach desktopowych right:0
       potrafił wylądować wizualnie przesunięty względem samej ikonki i
       zachodzić na wiersz menu głównego, gdy ten zawijał się do drugiej
       linii. Zgłoszenie użytkownika 20.09.2026 (zrzut ekranu:
       hub.sog-poland.com, chmurka nachodząca na ShadowManiak/Mapę/Czat). */
    left: 50%;
    transform: translateX(-50%);
    width: 320px;
    max-width: calc(100vw - 24px);
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 10px;
    display: none;
    flex-direction: column;
    z-index: 60;
    box-shadow: var(--panel-shadow);
    overflow: hidden;
}
.notif-panel.open { display: flex; }
.notif-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 12px 14px;
    border-bottom: 1px solid var(--panel-border);
    color: var(--text-chrome);
    font-size: 13px;
    font-weight: 600;
}
.notif-mark-all {
    background: none;
    border: none;
    color: var(--text-chrome-muted);
    font-size: 11px;
    font-family: inherit;
    cursor: pointer;
    padding: 0;
    box-shadow: none;
    text-align: right;
}
.notif-mark-all:hover { color: var(--accent-light, #d97b2e); opacity: 1; transform: none; box-shadow: none; }
.notif-list { max-height: 360px; overflow-y: auto; }
.notif-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--panel-border);
    color: var(--text-chrome);
    text-decoration: none;
    font-size: 12.5px;
    line-height: 1.4;
}
.notif-item:last-child { border-bottom: none; }
.notif-item:hover { background: var(--panel-hover); }
.notif-item.unread { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.notif-item-icon {
    flex-shrink: 0;
    width: 26px;
    height: 26px;
    margin-top: 1px;
    border-radius: 50%;
    background: var(--chrome-fill);
    color: var(--text-chrome-muted);
    display: flex;
    align-items: center;
    justify-content: center;
}
.notif-item.unread .notif-item-icon { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent-light, #d97b2e); }
.notif-item-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.notif-item-time { color: var(--text-chrome-dim); font-size: 11px; }
/* Krzyżyk do odrzucenia pojedynczego powiadomienia bez wchodzenia pod link
   — dzięki temu można "wyczyścić" dzwoneczek, nie zamieniając go w historię
   (patrz komentarz w notifications_api.php). */
.notif-item-dismiss {
    flex-shrink: 0;
    align-self: flex-start;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: none;
    background: transparent;
    color: var(--text-chrome-dim);
    font-size: 15px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    opacity: .5;
}
.notif-item-dismiss:hover { opacity: 1; background: rgba(0,0,0,0.12); color: #c0392b; }

/* Wizualnie ukryty input pliku dla szybkiej zmiany awatara (profile.php) —
   trigger to <label> (avatar + plakietka z aparatem), sam input pozostaje
   dostępny dla czytników ekranu, tylko niewidoczny. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
    border: 0;
}
.avatar-edit-trigger {
    position: relative;
    display: inline-block;
    cursor: pointer;
    border-radius: 50%;
}
.avatar-edit-badge {
    position: absolute;
    right: -2px;
    bottom: -2px;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px solid var(--card-bg);
}

.btn-logout {
    background: #e5e5e5;
    color: #111;
    padding: 6px 12px;
    border-radius: 4px;
    text-decoration: none;
    font-weight: 500;
    font-size: 12px;
}

/* --- Karty --- */
.card {
    background: var(--card-bg);
    color: #171512;
    border-radius: 8px;
    padding: 20px;
    margin-bottom: 16px;
    border: 1px solid var(--card-border);
}
.card h2 {
    font-size: 15px;
    font-weight: 700;
    color: #17140f;
    margin: 0 0 14px;
    letter-spacing: .01em;
    display: flex;
    align-items: center;
    /* flex-wrap dopisany 08.09.2026 (zgłoszenie użytkownika ze zrzutem
       ekranu z telefonu) — bez tego nagłówki z kilkoma znacznikami obok
       tytułu (np. announcement_view.php: tytuł + "Wyróżnione" + "Na górze
       listy" + kategoria) przy DŁUGIM tytule i wąskim ekranie próbowały
       zmieścić WSZYSTKO w jednym wierszu: flex bez zawijania kurczył sam
       tytuł do granic możliwości (więc łamał się na wiele wąskich linijek),
       a nieściskające się znaczniki (white-space:nowrap) zostawały w tym
       samym wierszu, co dawało rozjechany, dwukolumnowy bałagan zamiast
       czytelnego układu. Z flex-wrap:wrap tytuł zajmuje tyle miejsca, ile
       potrzebuje, a znaczniki, które się nie zmieszczą, spadają równym
       rządkiem pod spód — dotyczy to WSZYSTKICH nagłówków kart w serwisie
       (ten sam, wspólny styl bazowy), nie tylko ogłoszeń. */
    flex-wrap: wrap;
    gap: 8px;
}
.card p.muted { color: #6b6560; font-size: 13px; }

/* --- Wyróżniona karta (np. "Regulamin, który zaakceptowałeś" na profilu,
   akceptacja regulaminu) — sam pasek akcentu po lewej krawędzi + kolorowy
   nagłówek, BEZ gradientu/podbicia całego tła (pierwsza wersja z gradientem
   w tym samym odcieniu co ikona/nagłówek dawała rozmyty, "zlewający się"
   efekt zamiast czytelnego wyróżnienia — zgłoszenie użytkownika 26.08.2026).
   Karta zostaje na zwykłym jasnym tle .card, więc kontrast tekstu jest
   identyczny jak w zwykłej karcie. --- */
.card.card-highlight {
    border: 1px solid var(--card-border);
    border-left: 4px solid var(--accent);
}
.card.card-highlight h2 { color: var(--accent-dark); }

/* --- Zwijane karty "Dodaj..." (ogłoszenie/trasa/wydarzenie/zdjęcia) —
   domyślnie zwinięte, żeby formularz nie straszył kogoś, kto wchodzi tylko
   przejrzeć listę. <details>/<summary>
   zamiast <div>/<h2>, ten sam wzorzec wizualny co .year-archive/
   .rsvp-details niżej (strzałka obracana o 90°), ale w rozmiarze/kolorze
   nagłówka karty (.card h2), żeby wyglądało jak zwykła karta z klikalnym
   tytułem, a nie jak osobny mniejszy element. --- */
.card.collapsible-card { padding: 0; }
.card.collapsible-card summary {
    cursor: pointer;
    padding: 20px;
    font-size: 15px;
    font-weight: 700;
    color: #17140f;
    letter-spacing: .01em;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 8px;
}
.card.collapsible-card summary::-webkit-details-marker { display: none; }
.card.collapsible-card summary::before {
    content: '';
    width: 0; height: 0;
    border-left: 6px solid #8a847d;
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
    transition: transform .12s;
    flex-shrink: 0;
}
.card.collapsible-card[open] summary::before { transform: rotate(90deg); }
.card.collapsible-card .collapsible-body { padding: 0 20px 20px; }

/* --- Ekran logowania / rejestracji ---
   "Kierunek A / Moto Editorial", zdecydowany przez użytkownika 15.09.2026
   — dopasowanie do nowej strony wejściowej klubu (sog-poland.com): czarno
   + jeden mocny czerwony akcent, ostre kąty, Anton na nagłówkach.

   15.09.2026, drugi krok: "trzeba będzie dopasować całą kolorystykę
   domyślną do tej obecnie strony" — użytkownik świadomie rozszerzył
   zasięg z samych stron logowania na DOMYŚLNY motyw całego Hubu (był
   już o to pytany wcześniej i wtedy wybrał węższy zasięg tylko dla
   auth-*, stąd wcześniej osobne tokeny --auth-accent* NIE nadpisujące
   --accent — teraz to świadoma, druga decyzja, nie przypadek). Domyślny
   --accent zmieniony z pomarańczowego na ten sam czerwony; --auth-accent*
   zostają jako ALIASY do --accent (nie duplikat wartości) — cała reszta
   CSS w tej sekcji, która już poprawnie odwołuje się do --auth-accent*,
   działa bez zmian. Jak poprzednio: dotyczy TYLKO domyślnego motywu (brak
   users.theme_color) — kto w profilu wybrał inny kolor (niebieski/różowy/
   szary/istniejący "czerwony" preset, inny odcień niż ten tu), zostaje
   przy swoim wyborze. */
:root {
    --auth-accent: var(--accent);
    --auth-accent-light: var(--accent-light);
    --auth-accent-dark: var(--accent-dark);
}
.auth-shell {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 10px 16px 170px;
    position: relative;
    overflow: hidden;
    background-size: cover;
    background-position: center;
    background-color: #0a0a0b;
    /* background-attachment:fixed — bez tego "cover" liczy się względem
       WYSOKOŚCI SAMEGO ELEMENTU .auth-shell, nie okna przeglądarki. Formularz
       rejestracji ma więcej pól niż logowanie, więc jest wyższy niż jeden
       ekran (min-height:100vh go nie ogranicza, tylko ustawia minimum) —
       przez to to samo tło jest inaczej "docinane"/skalowane na rejestracji
       niż na logowaniu, mimo identycznego markupu i CSS. Przypięcie tła do
       viewportu (fixed) usuwa tę zależność od wysokości treści. */
    background-attachment: fixed;
}
.auth-shell.has-bg::before, .auth-shell.has-bg::after { display: none; }
.auth-shell-scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(8,6,4,0.6), rgba(8,6,4,0.8));
    pointer-events: none;
}
.auth-shell::before {
    content: '';
    position: absolute;
    top: -280px;
    left: 50%;
    transform: translateX(-50%);
    width: 900px;
    height: 900px;
    border-radius: 50%;
    /* 9%, nie 22% — przy poprzednim, bardziej stonowanym pomarańczowym
       (#d97b2e) 22% wyglądało subtelnie, ale ten sam procent przy
       nasyconej czerwieni (#e0121a) czytał się jako duża, dominująca,
       "przesunięta" plama (zgłoszenie użytkownika 15.09.2026, ze
       zrzutem ekranu strony przerwy technicznej: "chyba gradient ci się
       przesunął") — czerwień w tym natężeniu po prostu przyciąga oko dużo
       mocniej niż tamten pomarańcz przy tej samej przezroczystości. */
    background: radial-gradient(circle, color-mix(in srgb, var(--auth-accent) 9%, transparent) 0%, color-mix(in srgb, var(--auth-accent) 0%, transparent) 62%);
    pointer-events: none;
    /* Powolne "oddychanie" poświaty (8s, nieskończone) — żeby tło NIGDY nie
       stało zupełnie bez ruchu, nawet gdy pasek .auth-ticker akurat nie
       przełącza wiadomości. Zgłoszenie użytkownika 15.09.2026: "dynamika...
       cały czas żyła, nie tylko do załadowania". Różne czasy trwania na
       ::before/::after (8s/11s) świadomie — dwa identyczne cykle razem
       wyglądałyby mechanicznie, przesunięcie w fazie daje bardziej
       organiczny efekt. Amplituda też zmniejszona razem z powyższym —
       skala do 1.03 (nie 1.06) i węższy zakres opacity, żeby "oddech" był
       ledwo zauważalny, nie pulsującą plamą. */
    animation: auth-glow-breathe 8s ease-in-out infinite;
}
.auth-shell::after {
    content: '';
    position: absolute;
    bottom: -320px;
    right: -120px;
    width: 640px;
    height: 640px;
    border-radius: 50%;
    background: radial-gradient(circle, color-mix(in srgb, var(--auth-accent) 6%, transparent) 0%, color-mix(in srgb, var(--auth-accent) 0%, transparent) 65%);
    pointer-events: none;
    animation: auth-glow-breathe 11s ease-in-out infinite;
}
@keyframes auth-glow-breathe {
    0%, 100% { opacity: 0.7; transform: scale(1); }
    50% { opacity: 1; transform: scale(1.03); }
}
@media (prefers-reduced-motion: reduce) {
    .auth-shell::before, .auth-shell::after { animation: none; }
}
.auth-shell-inner { position: relative; z-index: 1; width: 100%; max-width: 400px; }
.auth-logo { text-align: center; margin-bottom: 18px; }
.auth-logo img { height: 172px; width: auto; }
/* "Puls klubu" v2 (15.09.2026) — zastępuje pierwszą wersję z dużymi
   pogrubionymi licznikami (3 kolumny, animacja liczenia TYLKO przy
   wejściu). Zmiana na żądanie użytkownika:
   1) "ilość członków... nikt nie chce się chwalić" — usunięta jedyna
      metryka, która była czystym "rozmiarem" (member count), nie
      aktywnością — patrz $authTickerMsgs w login.php.
   2) "ten licznik powinien być delikatniejszy/subtelniejszy" — mała,
      cienka czcionka (Barlow, nie Anton/pogrubienie) zamiast bloków 26px.
   3) "dynamika... cały czas żyła, nie tylko do załadowania" — jedna
      wąska belka z pulsującą kropką ("na żywo") i TEKSTEM, który
      przenika (cross-fade) między kilkoma krótkimi wiadomościami w
      nieskończonej pętli co ~4.5s (patrz <script> w login.php), zamiast
      jednorazowej animacji liczenia od 0.
   Świadomie NIE pokazujemy tu prawdziwych zdjęć z Galerii w tle (jak
   pierwotnie planowano) — galeria nie ma dziś żadnej flagi "publiczne"/
   "zgoda na pokazanie niezalogowanym", więc auto-karuzela realnych zdjęć
   członków na stronie dostępnej dla KAŻDEGO (także bez konta) byłaby
   cichą zmianą widoczności prywatnych treści. Zamiast tego animowana
   poświata (.auth-shell::before/::after wyżej) daje ruch/życie w tle bez
   tego ryzyka. */
.auth-ticker {
    display: flex;
    align-items: center;
    gap: 10px;
    background: rgba(13,13,14,0.6);
    border: 1px solid rgba(255,255,255,0.08);
    border-left: 2px solid var(--auth-accent);
    padding: 9px 14px;
    margin-bottom: 18px;
    font-size: 12px;
    color: #b8b3aa;
}
.auth-ticker-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--auth-accent);
    flex-shrink: 0;
    animation: auth-ticker-pulse 1.7s ease-in-out infinite;
}
@keyframes auth-ticker-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.35; transform: scale(0.65); }
}
.auth-ticker-text {
    position: relative;
    flex: 1;
    min-width: 0;
    height: 15px;
}
.auth-ticker-msg {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 0.6s ease;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.auth-ticker-msg.active { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .auth-ticker-dot { animation: none; }
    .auth-ticker-msg { transition: none; }
}
.auth-card {
    background: #0d0d0e;
    color: #ece9e2;
    border-radius: 0;
    padding: 32px 28px 28px;
    border: 1px solid rgba(255,255,255,0.08);
    position: relative;
    overflow: hidden;
}
.auth-card::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--auth-accent);
}
.auth-card h2 {
    text-align: center;
    margin: 4px 0 3px;
    font-size: 24px;
    color: #ece9e2;
    font-family: 'Anton', Impact, sans-serif;
    font-weight: 400;
    letter-spacing: 0.5px;
    text-transform: uppercase;
}
.auth-card .auth-sub { text-align: center; color: #9a958d; font-size: 12.5px; margin: 0 0 18px; }
.auth-field { position: relative; }
.auth-field .auth-field-icon {
    position: absolute;
    left: 12px;
    bottom: 11px;
    color: #8a847d;
    pointer-events: none;
    display: flex;
}
.auth-field input {
    padding-left: 34px !important;
    background: #161616 !important;
    border-color: rgba(255,255,255,0.14) !important;
    color: #ece9e2 !important;
    border-radius: 0 !important;
}
/* Kolor tekstu wpisywanego w polu i podpowiedzi przeglądarki (autofill) —
   bez tego pole zostawało JASNE od środka mimo ciemnego tła dookoła,
   Chrome/Safari renderują autofill własnym, na sztywno białym tłem,
   niezależnie od background wyżej. */
.auth-field input:-webkit-autofill,
.auth-field input:-webkit-autofill:focus {
    -webkit-text-fill-color: #ece9e2;
    -webkit-box-shadow: 0 0 0 1000px #161616 inset;
    caret-color: #ece9e2;
}
.auth-submit {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-top: 8px;
    background: var(--auth-accent);
    border-radius: 0;
    text-transform: uppercase;
    font-family: 'Barlow Condensed', sans-serif;
    font-weight: 700;
    letter-spacing: 0.5px;
    font-size: 15px;
    /* Jawny padding zamiast polegania na globalnej regule "button, .btn"
       (patrz niżej w pliku) — ta reguła łapie prawdziwy <button>
       (formularz logowania), ale NIE zwykły <a> — a .auth-submit jest też
       używany jako link (np. przycisk "Zaloguj się" w wizytówce dla gościa,
       render_share_guest_gate() w includes/layout.php). Bez tego <a> wypadał
       "wąziutki", bez pionowego oddechu, mimo identycznej klasy co przycisk
       logowania. Zgłoszenie użytkownika 24.09.2026 ze screenshotem. */
    padding: 11px 18px;
    box-sizing: border-box;
}
.auth-submit:hover { background: var(--auth-accent-light); opacity: 1; }
.auth-footer-link { text-align: center; margin-top: 18px; margin-bottom: 0; font-size: 12.5px; color: #9a958d; }
.auth-footer-link a { color: var(--auth-accent-light); font-weight: 600; text-decoration: none; }
.auth-footer-link a:hover { text-decoration: underline; }
.auth-check { display: flex; align-items: flex-start; gap: 8px; margin: 14px 0 4px; font-size: 12px; color: #9a958d; }
.auth-check input { margin-top: 2px; }
.auth-honeypot { position: absolute; left: -9999px; top: -9999px; opacity: 0; height: 0; width: 0; }
@media (max-width: 480px) {
    .auth-shell { padding: 14px 16px 64px; }
    .auth-card { padding: 26px 20px 22px; }
    .auth-logo img { height: 138px; }
}

/* "Udostępnij" — wizytówka posta dla niezalogowanego gościa (patrz
   render_share_guest_gate() w includes/layout.php). */
.share-guest-image { margin: -10px -10px 18px; border-radius: 4px; overflow: hidden; }
.share-guest-image img { width: 100%; max-height: 220px; object-fit: cover; display: block; }

/* Przycisk "Udostępnij" na widoku ogłoszenia/wydarzenia/trasy — patrz
   assets/share-button.js. Krótki stan "Skopiowano link!" po kliknięciu na
   urządzeniach bez natywnego okna udostępniania (desktop). */
.share-post-btn.share-post-btn--copied { background: var(--accent); color: #fff; border-color: var(--accent); }
/* Popover "Wyślij e-mailem" / "Kopiuj link" — pokazuje się zamiast
   natywnego okna udostępniania tam, gdzie przeglądarka go nie wspiera
   (głównie desktop). Ten sam wygląd co .comment-mention-suggestions. */
.share-post-menu {
    display: none;
    flex-direction: column;
    position: absolute;
    background: #fff;
    border-radius: 8px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 4px 16px rgba(0,0,0,0.16);
    z-index: 70;
    min-width: 170px;
    overflow: hidden;
}
.share-post-menu.open { display: flex; }
.share-post-menu-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 12px;
    font-size: 13px;
    font-weight: 600;
    color: #45403a;
    background: none;
    border: none;
    text-align: left;
    cursor: pointer;
    width: 100%;
}
.share-post-menu-item:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent-dark); }

.empty-state {
    text-align: center;
    padding: 28px 10px;
    color: #8a847d;
}
.empty-state .icon-lg { opacity: .4; margin-bottom: 10px; }
.empty-state p { margin: 0; font-size: 13px; }
.empty-state a { color: var(--accent-dark); font-weight: 600; text-decoration: none; }
.empty-state a:hover { text-decoration: underline; }

.list-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 12px 14px;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 6px;
    margin-bottom: 8px;
    text-decoration: none;
    color: #171512;
    transition: transform .12s, box-shadow .12s, border-color .12s;
}
a.list-item:hover { border-color: color-mix(in srgb, var(--accent) 35%, transparent); }

/* Admin → Ustawienia strony → System → "Przyznaj lub odejmij punkty".
   Wiersz z nazwą/mailem osoby obok formularza przyznawania punktów. Odznaki
   (saldo ręcznych punktów, punkty z konkursów) mają WŁASNY, zawijający się
   wiersz pod nazwą — nie są wepchnięte jako zwykły tekst do .meta, bo przy
   dwóch odznakach naraz robiło to bałagan (łamanie .meta na 2 linie
   rozpychało cały wiersz i form zawijał się w nieprzewidywalnej kolejności).
   Zgłoszenie użytkownika 01.10.2026: "możesz poprawić ukłąd... bo tak troche
   chaotycznie wyglada" (dot. identycznego problemu przy walkowerze) oraz
   kolejne "ułuż to prosze ładnie" przy tym konkretnie widoku. */
.admin-points-row { align-items: flex-start; flex-wrap: wrap; gap: 10px; }
.admin-points-info { flex: 1 1 220px; min-width: 180px; }
.admin-points-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.admin-points-form { margin: 0; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: 2 1 340px; }
@media (max-width: 640px) {
    .admin-points-form { flex-direction: column; align-items: stretch; }
    .admin-points-form input, .admin-points-form button { width: 100%; }
}
.list-item .title { font-size: 13px; font-weight: 600; margin: 0; }

/* Klikalne linki do publicznego profilu członka (member_profile.php) —
   wtopione w tekst, bez domyślnego niebieskiego podkreślenia przeglądarki,
   z podkreśleniem tylko po najechaniu. */
.title a[href^="member_profile.php"],
.who a[href^="member_profile.php"],
.meta a[href^="member_profile.php"],
p.muted a[href^="member_profile.php"],
.materials-post-meta a[href^="member_profile.php"] {
    color: inherit;
    text-decoration: none;
}
.title a[href^="member_profile.php"]:hover,
.who a[href^="member_profile.php"]:hover,
.meta a[href^="member_profile.php"]:hover,
p.muted a[href^="member_profile.php"]:hover,
.materials-post-meta a[href^="member_profile.php"]:hover {
    text-decoration: underline;
}
.list-item .meta { font-size: 11px; color: #7a746e; margin: 2px 0 0; }
.list-item .badge { font-size: 12px; color: #555; white-space: nowrap; background: #eee; padding: 2px 8px; border-radius: 20px; }
.badge { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--accent-dark); white-space: nowrap; background: color-mix(in srgb, var(--accent) 15%, transparent); padding: 3px 9px; border-radius: 20px; font-weight: 500; }

/* Zagnieżdżona lista menedżera menu (admin.php, sekcja "Menu") z
   przeciąganiem Sortable.js — każdy <ul class="menu-admin-list"> to osobna
   strefa upuszczania (bez wspólnego "group"), więc przeciąganie jest z
   natury ograniczone do rodzeństwa w obrębie tego samego rodzica. Zagnieżdżone
   listy podpozycji mają wcięcie przez padding-left na samej liście, nie przez
   inline style na elementach — spójne z .menu-submenu-list używanym na
   żywym serwisie. Ten sam wzorzec (i te same klasy — celowo NIE
   przemianowane na coś bardziej ogólnego, żeby nie dotykać już wdrożonego
   kodu) ponownie użyty w tools.php dla kafelków statystyk Pulpitu, karuzeli
   promocyjnej, kolejności modułów Pulpitu i kategorii/pytań w Pomocy —
   patrz assets/drag-order.js. Zgłoszenie użytkownika 19.09.2026 (po
   wdrożeniu przeciągania w menedżerze menu): "czy mogę prosić o takie
   zastosowanie w cały taki sposób kolejności... jest to petarda". */
.menu-admin-list { list-style: none; margin: 0; padding: 0; }
.menu-admin-list .menu-admin-list { padding-left: 24px; margin-top: 8px; }
.menu-admin-item { margin-bottom: 8px; }
.menu-admin-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 12px;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 6px;
}
.menu-admin-row-main { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1; }
.menu-admin-row .menu-admin-row-text { min-width: 0; }
.menu-admin-row .title { font-size: 13px; font-weight: 600; margin: 0; }
.menu-admin-row .meta { font-size: 11px; color: #7a746e; margin: 2px 0 0; }
.menu-admin-row-actions { display: flex; gap: 8px; flex-wrap: wrap; flex-shrink: 0; }
.menu-drag-handle {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    cursor: grab;
    color: #a39c93;
    flex-shrink: 0;
    touch-action: none;
}
.menu-drag-handle:active { cursor: grabbing; }
.menu-admin-row-main > svg:not(.menu-drag-handle) { flex-shrink: 0; }
.sortable-ghost { opacity: .4; }
.sortable-chosen .menu-admin-row { border-color: color-mix(in srgb, var(--accent) 45%, transparent); box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.sortable-drag .menu-admin-row { background: #fdfcfb; }

/* Kategorie Pomocy (całe karty przeciągane bezpośrednio, bez pośredniej
   <ul>/<li> — patrz #help-categories-list w tools.php) i pytania w obrębie
   kategorii (.help-article-list/.help-article-item — uchwyt OBOK <details>,
   nie w środku <summary>, żeby kliknięcie w uchwyt nie rozwijało/zwijało
   pytania). */
.help-article-list { list-style: none; margin: 0; padding: 0; }
.help-article-item { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 8px; }
.help-article-item .help-faq-details { flex: 1; min-width: 0; margin-bottom: 0; }
.help-article-item .menu-drag-handle { margin-top: 14px; }
#help-categories-list .card { cursor: default; }

/* Plakietka "szuka towarzystwa" TAM, gdzie zwykły .badge ginie na szarym tle
   listy (np. .list-item .badge wyżej dostaje stonowane, szare tło) — ma być
   widoczna kolorem na pulpicie. Osobna,
   nadrzędna klasa zamiast modyfikacji .list-item .badge, bo tamten szary
   styl jest współdzielony z innymi plakietkami (licznik komentarzy, "prywatna"
   itd.), którym ma zostać bez zmian. Ten sam pomarańcz co .route-card-badge
   na kafelkach tras (spójny kolor wszędzie, gdzie oznaczamy to samo). */
.badge-companions { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: #fff; white-space: nowrap; background: var(--accent); padding: 3px 9px; border-radius: 20px; }

/* Plakietka "Audycja" na kartach/liście wydarzeń — zgłoszenie użytkownika
   25.08.2026: "może audycje wyróżnić na kolor jak z kafelki pulpitu". Ten
   sam czerwony gradient co .stat-card-shadowmaniak na pulpicie i
   .audycja-live-card w dziale ShadowManiak, żeby kolor jednoznacznie
   kojarzył się z audycją wszędzie na stronie.
   (14.09.2026: próba przebarwienia całego brandu ShadowManiaka na pomarańcz
   po liftingu kolorystyki na czerwień — cofnięta na prośbę użytkownika:
   "kolor ShadowManiak trzeba poprawić - cofnij do jego koloru". ShadowManiak
   zostaje czerwony wszędzie poza jedną, punktową poprawką ikonki na czacie,
   patrz .chat-group-avatar--audycja niżej.) */
.badge-audycja { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: #fff; white-space: nowrap; background: linear-gradient(135deg, #c33a30, #7a1712); padding: 3px 9px; border-radius: 20px; }

/* Plakietki statusu akceptacji regulaminu na liście osób w Panelu admina
   (Narzędzia -> Użytkownicy) — żeby dało się to zobaczyć od razu na całej
   liście, bez klikania "Edytuj" na każdym koncie z osobna. Zgłoszenie
   użytkownika 26.08.2026: "jak zobaczę, jak [ktoś] musiał zaakceptować".
   Osobne, pełne klasy (nie łączone z .badge) z tego samego powodu co
   .badge-companions/.badge-audycja wyżej — .list-item .badge nadpisuje
   kolor na szary, a te dwie MAJĄ zostać kolorowe (zielona/czerwona) właśnie
   na liście. */
.badge-ok { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: #fff; white-space: nowrap; background: #3f8f4f; padding: 3px 9px; border-radius: 20px; }
.badge-warn { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: #fff; white-space: nowrap; background: #a32d2d; padding: 3px 9px; border-radius: 20px; }

.list-item-thumb {
    width: 40px;
    height: 40px;
    border-radius: 6px;
    object-fit: cover;
    flex-shrink: 0;
}
.list-item-thumb-icon {
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 700;
}
/* Ta sama para kolorów co w chat.php (chat_group_visual()) — moduł "Z
   czatu" na pulpicie (index.php) też pokazuje wyciąg z czatu wycieczki/
   wydarzenia i powinien od razu być rozpoznawalny tym samym kolorem.
   Bez obwódki z .chat-group-avatar--* (tam pasuje do okrągłego .avatar,
   tutaj kwadratowy .list-item-thumb-icon jak reszta miniaturek na liście —
   zostawiamy go płaskim, spójnym z innymi wpisami). Zgłoszenie 18.08.2026. */
.list-item-thumb-icon.chat-group-avatar--route,
.list-item-thumb-icon.chat-group-avatar--event,
.list-item-thumb-icon.chat-group-avatar--audycja,
.list-item-thumb-icon.chat-group-avatar--manual-icon { box-shadow: none; }

/* --- Materiały: gotowe posty na Facebooka (siatka kart o stałej,
   ograniczonej szerokości — żeby pojedynczy post nie rozciągał się na
   całą szerokość karty, a kilka postów wyglądało jednolicie). --- */
.materials-post-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 220px));
    justify-content: start;
    gap: 12px;
}
.materials-post-card {
    background: #fff;
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 8px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.materials-post-card img {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    display: block;
    background: #ece7de;
}
.materials-post-body {
    padding: 10px;
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: 7px;
}
/* Karta promująca sklep SOG na samej górze materials.php — baner na CAŁĄ
   szerokość strony jak inne banery sekcji (render_section_banner()), ale
   celowo dużo NIŻSZY (nie "kinowy" hero na wiele setek px), a opis + link do
   sklepu idą POD zdjęciem, nie obok niego z boku. Tło to ten sam ciepły,
   lekko podbity gradient co .leaderboard-compact na pulpicie — spójnie
   z już przyjętym stylem, zamiast płaskiego kremowego tła zwykłej .card. */
/* .card.materials-shop-card (obie klasy w selektorze) celowo, NIE samo
   .materials-shop-card — inaczej na telefonie (@media max-width:720px niżej
   w tym pliku) przegrywało z mobilnym padding:14px zwykłej .card, bo obie
   reguły mają tę samą specyficzność i o wyniku decydowała kolejność w
   pliku. Efekt na telefonie: gradientowe tło "prześwitywało" jako rama
   wokół zdjęcia zamiast zdjęcie sięgało od krawędzi do krawędzi. */
.card.materials-shop-card {
    overflow: hidden;
    padding: 0;
    background: linear-gradient(160deg, #fbe0c2, #f9ead9 55%, var(--card-bg) 100%);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.materials-shop-card img { width: 100%; display: block; max-height: 220px; object-fit: cover; background: #ece7de; }
.materials-shop-body { padding: 16px 18px; }
.materials-shop-body .muted { color: #5a4632; }
@media (max-width: 640px) {
    .materials-shop-card img { max-height: 150px; }
}

/* Baner "Audycje ShadowManiaka" nad Listą wydarzeń (events.php) — ten sam
   pomysł co karta Sklepu SOG wyżej (zdjęcie na górze + tekst pod spodem,
   cała karta klikalna), ale w czerwonym gradiencie ShadowManiaka (ten sam co
   .stat-card-shadowmaniak na pulpicie i .audycja-live-card w dziale
   ShadowManiak), żeby kolor jednoznacznie kojarzył się z audycją. Obrazek to
   ten sam baner sekcji co na górze strony ShadowManiak
   ($config['banner_shadowmaniak_path']) — nic nowego do wgrania, jeśli
   admin go już ustawił. Bez zdjęcia (jeszcze nie wgrane) karta i tak działa,
   samym gradientem. Zgłoszenie użytkownika 28.08.2026: "nie ładnie to
   wygląda... zaprojektować banner jak w Materiałach Sklep SOG". */
.card.audycja-hub-banner {
    display: block;
    overflow: hidden;
    padding: 0;
    text-decoration: none;
    background: linear-gradient(150deg, #b8342c, #6e120e 75%);
    border: 1px solid #e2574f;
    transition: transform .15s ease, box-shadow .15s ease;
}
.audycja-hub-banner:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(110,18,14,.35); }
/* 200px (zamiast dawnych 130px) — po przeniesieniu do wąskiego (300px) panelu
   bocznego Wydarzeń 130px robiło się za niskie, baner "gubił się" wśród
   kalendarza i kategorii nad/pod nim. Zgłoszenie użytkownika 21.09.2026:
   "zrób ShadowManiak na 200px wysokość bo sie gubi". */
.audycja-hub-banner img { width: 100%; display: block; height: 200px; max-height: 200px; object-fit: cover; background: #1a1a1a; }
.audycja-hub-banner-body { padding: 14px 18px; color: rgba(255,255,255,.85); font-size: 13px; }
.audycja-hub-banner-body h2 { margin: 0 0 4px; font-size: 16px; display: flex; align-items: center; gap: 8px; color: #fff; }
/* Opis idzie przez TinyMCE (audycja_hub_desc, sanitize_html()), więc może
   przyjść jako <p>/<ul>/itd. — nie tylko goły tekst jak wcześniej. */
.audycja-hub-banner-body p { margin: 0 0 6px; }
.audycja-hub-banner-body p:last-child { margin-bottom: 0; }
.audycja-hub-banner-body a { color: #fff; text-decoration: underline; }
@media (max-width: 640px) {
    .audycja-hub-banner img { max-height: 100px; }
}

/* Graficzny baner + opis nad "Plikami do pobrania", scalony w JEDNĄ kartę
   z listą plików poniżej (nie osobna karta) — obrazek "wychodzi" poza
   padding .card (ujemny margines), żeby
   sięgał od krawędzi do krawędzi z zaokrąglonymi tylko górnymi rogami, tak
   jak w bannerze sklepu, mimo że jest teraz wewnątrz zwykłej .card. */
.materials-files-banner-wrap {
    margin: -20px -20px 14px;
    overflow: hidden;
    border-radius: 8px 8px 0 0;
}
.materials-files-banner-wrap img { width: 100%; display: block; max-height: 200px; object-fit: cover; background: #ece7de; }
.materials-files-banner-desc { margin: 0 0 14px; }
.materials-files-sep { border: none; border-top: 1px solid rgba(0,0,0,0.08); margin: 0 0 16px; }
@media (max-width: 720px) {
    /* .card ma tu mniejszy padding (14px zamiast 20px) — ujemny margines
       banera musi się z nim zgadzać, inaczej zostanie szpara albo obrazek
       wyjdzie za daleko poza kartę. */
    .materials-files-banner-wrap { margin: -14px -14px 12px; border-radius: 6px 6px 0 0; }
}
@media (max-width: 640px) {
    .materials-files-banner-wrap img { max-height: 140px; }
}
.materials-post-grow { flex: 1; }
.materials-post-card .title { font-size: 12.5px; }
.materials-post-text {
    font-size: 11.5px;
    line-height: 1.45;
    color: #45403a;
    white-space: pre-line;
    margin: 0;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.materials-post-meta {
    font-size: 11px;
    color: #7a746e;
    margin: 0;
}
.material-edit-form {
    display: none;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid rgba(0,0,0,0.08);
}
.material-edit-form.open { display: block; }

label { display: block; font-size: 12px; font-weight: 500; color: #6b6560; margin: 12px 0 5px; }
input[type=text], input[type=email], input[type=url], input[type=password],
input[type=tel], input[type=number], input[type=date], input[type=datetime-local], input[type=file], textarea, select {
    width: 100%;
    min-width: 0; /* bez tego Mobile Safari ignorował width:100% na
                     input[type=date] i pole wystawało poza kartę — pole
                     formularza domyślnie nie kurczy się poniżej rozmiaru
                     własnej treści (kalendarzyk/spinner), trzeba to
                     jawnie zezwolić */
    padding: 10px 12px;
    border-radius: 6px;
    border: 1px solid #d8d3cc;
    background: #fff;
    color: #171512;
    /* MUSI być >=16px — to jest wspólna, BAZOWA reguła dla niemal każdego
       pola tekstowego w serwisie (bez niej pole dziedziczy właśnie stąd, o
       ile nie ma własnego, bardziej specyficznego selektora niżej w pliku).
       Przy 15px każde pole bez własnego nadpisania łapało na iOS Safari
       auto-zoom po dotknięciu — dokładnie ten sam mechanizm, co przy
       .comment-pill-row textarea / .chat-composer-row input[type=text]
       (patrz komentarze przy nich), tylko że tu dotyczyło to od razu
       WSZYSTKICH pól w serwisie, nie jednego. Zgłoszenie użytkownika
       22.09.2026: zauważył ten sam problem przy tytule "Nowego ogłoszenia"
       i podejrzewał, że będzie to w każdym formularzu — miał rację, to była
       wspólna przyczyna. */
    font-size: 16px;
    font-family: inherit;
    line-height: 1.4;
    box-sizing: border-box;
    transition: border-color .12s, box-shadow .12s;
}
/* Jednolita wysokość pól jednoliniowych — bez tego input[type=date] (czasem
   też select) renderował się z inną wysokością wewnętrznego kontrolera
   przeglądarki niż zwykłe pola tekstowe, przez co formularz "skakał" w
   wysokości między polami (widoczne zwłaszcza na telefonie, np. przy dacie
   urodzenia w profilu). Textarea i file celowo pominięte — mają inną, własną
   logikę wysokości (textarea się rozciąga, file ma natywny przycisk).
   Obniżone z 44px na 38px 04.09.2026 (zgłoszenie użytkownika: pola i
   przyciski w całym serwisie "różnej wysokości", poprosił o ujednolicenie w
   dół, do lżejszego, delikatniejszego wyglądu — kompromis, nie tak nisko jak
   zmierzone ~33px domyślnego przycisku, żeby zostać wygodnym celem
   dotykowym na telefonie). Zmiana jest GLOBALNA — dotyczy każdego pola w
   całym serwisie, nie tylko panelu admina. Osobne, świadomie 44px cele
   dotykowe (np. #theme-toggle-btn/.topbar-menu-toggle w topbarze — patrz
   komentarz przy nich, "38px to za mało") celowo NIE zmienione — to małe,
   ikonkowe przyciski bez podpisu, inna kategoria niż pola formularza. */
input[type=text], input[type=email], input[type=url], input[type=password],
input[type=tel], input[type=number], input[type=date], input[type=datetime-local], select {
    height: 38px;
}
/* input[type=date] ma inaczej dopasowaną wysokość wewnętrznego kontrolera
   przeglądarki (kalendarzyk/spinner) niż reszta pól — bez tego wyrównania
   wyglądał "wyżej" i nie do pary z resztą formularza. Mobile Safari dodatkowo
   renderuje ten kontroler z własną, natywną szerokością minimalną, którą
   ignoruje mimo width:100%/min-width:0 (oba już nie wystarczały) — appearance:none
   każe przeglądarce porzucić natywny "chrom" kontrolki i w pełni respektować
   nasz box-sizing, więc pole faktycznie mieści się w karcie.
   Ten sam appearance:none zdejmuje jednak też natywną ikonkę kalendarza i
   szary podpowiadacz formatu, przez co pole wyglądało na "inne"/puste niż
   reszta formularza — dorysowujemy więc własną ikonkę kalendarza (SVG, ten
   sam kształt co icon('calendar') w includes/icons.php) po prawej stronie,
   żeby pole nadal było rozpoznawalne jako "data" i spójne wizualnie. */
input[type=date] {
    line-height: 1.4;
    -webkit-appearance: none;
    appearance: none;
    max-width: 100%;
    box-sizing: border-box;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b6560' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='2'%3E%3C/rect%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3Cline x1='8' y1='3' x2='8' y2='7'%3E%3C/line%3E%3Cline x1='16' y1='3' x2='16' y2='7'%3E%3C/line%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 16px;
    padding-right: 36px;
}
/* Widoczne pole własnego date/time pickera (flatpickr altInput, patrz
   assets/datepicker.js) — ta sama ikonka kalendarza co przy dawnym natywnym
   input[type=date] wyżej, żeby pole nadal było wizualnie rozpoznawalne jako
   "data", tylko teraz identyczne na każdym telefonie/przeglądarce. */
.js-datepicker-input {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b6560' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='2'%3E%3C/rect%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3Cline x1='8' y1='3' x2='8' y2='7'%3E%3C/line%3E%3Cline x1='16' y1='3' x2='16' y2='7'%3E%3C/line%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 16px;
    padding-right: 36px;
    cursor: pointer;
}
input:focus, textarea:focus, select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
textarea { resize: vertical; min-height: 70px; }
.hint { font-size: 11px; color: #8a847d; margin-top: 3px; }

/* Własny, jednolity wygląd checkboxów zamiast natywnego — na iOS Safari
   niezaznaczony i zaznaczony checkbox potrafią wyglądać jak dwie różne
   kontrolki (różny rozmiar/kształt), bo przeglądarka rysuje stan "zaznaczone"
   inaczej niż samą ramkę. appearance:none + własny kwadracik/haczyk daje
   identyczny wygląd zawsze i wszędzie (iOS/Android/desktop).
   !important na width/height/appearance: w wielu miejscach w kodzie checkboxy
   mają jeszcze stary, sprzed tego stylu, inline "style=width:auto;margin:0"
   (pisany pod natywny wygląd) — bez !important te inline style wygrywałyby
   specyficznością i psuły rozmiar własnej kontrolki. */
input[type=checkbox] {
    appearance: none !important;
    -webkit-appearance: none !important;
    width: 19px !important;
    height: 19px !important;
    min-width: 19px;
    flex-shrink: 0;
    margin: 0;
    border: 1.5px solid #c9c2b8;
    border-radius: 4px;
    background: #fff;
    display: inline-block;
    vertical-align: middle;
    position: relative;
    cursor: pointer;
    transition: background .12s, border-color .12s;
}
input[type=checkbox]:checked {
    background: var(--accent);
    border-color: var(--accent);
}
input[type=checkbox]:checked::after {
    content: '';
    position: absolute;
    left: 5.5px;
    top: 1.5px;
    width: 5px;
    height: 9px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
input[type=checkbox]:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}

/* --- Własny wybór daty/godziny (flatpickr, patrz assets/datepicker.js) ---
   Kalendarzyk dokleja się do <body>, nie do karty, więc stylujemy go tu na
   stałe w jasnej wersji (tak jak karty/auth-card — te są zawsze jasne,
   niezależnie od ciemnego/jasnego motywu strony, patrz komentarz przy
   .auth-card). max-width chroni przed wystawaniem poza ekran na telefonie —
   dokładnie to, co wcześniej psuło natywne pola daty w formularzu wydarzenia. */
.flatpickr-calendar {
    background: var(--card-bg);
    border: 1px solid var(--card-border, rgba(0,0,0,0.13));
    border-radius: 10px;
    box-shadow: 0 8px 28px rgba(0,0,0,0.28);
    max-width: calc(100vw - 24px);
    font-family: inherit;
}
.flatpickr-calendar.arrowTop:before, .flatpickr-calendar.arrowTop:after { border-bottom-color: var(--card-bg); }
.flatpickr-months, .flatpickr-weekdays { background: var(--card-bg); }
.flatpickr-month { color: #17140f; fill: #17140f; }
.flatpickr-current-month .flatpickr-monthDropdown-months, .flatpickr-current-month input.cur-year { color: #17140f; font-weight: 600; }
.flatpickr-weekday { color: #8a847d; font-weight: 600; }
.flatpickr-day { color: #33302b; border-radius: 6px; }
.flatpickr-day.today { border-color: var(--accent); }
.flatpickr-day.selected, .flatpickr-day.selected:hover {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}
.flatpickr-day:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.flatpickr-day.flatpickr-disabled, .flatpickr-day.prevMonthDay, .flatpickr-day.nextMonthDay { color: #c9c2b8; }
.flatpickr-time { border-top: 1px solid var(--card-border, rgba(0,0,0,0.13)); }
.flatpickr-time input { color: #17140f; }
.flatpickr-time .flatpickr-am-pm:hover, .flatpickr-time input:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.numInputWrapper span.arrowUp:after { border-bottom-color: #6b6560; }
.numInputWrapper span.arrowDown:after { border-top-color: #6b6560; }

button, .btn {
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    border: none;
    padding: 11px 18px;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
    transition: transform .12s, opacity .12s;
}
button:hover, .btn:hover { opacity: .95; transform: translateY(-1px); }
button:active, .btn:active { transform: translateY(0); }
.btn.secondary {
    background: transparent;
    color: #eee;
    border: 1px solid rgba(255,255,255,0.28);
    box-shadow: none;
}
.btn.secondary:hover { background: rgba(255,255,255,0.08); box-shadow: none; transform: none; }
/* Wewnątrz jasnej karty potrzebny jest ciemny wariant — inaczej tekst
   znika na jasnym tle (ten sam błąd co z ikonami: kolor dobrany pod
   ciemne tło strony, niewidoczny na jasnej karcie, i na odwrót). */
.card .btn.secondary { color: #171512; border-color: #d8d3cc; }
.card .btn.secondary:hover { background: rgba(0,0,0,0.04); }
/* .auth-card w AlkoCzwartek jest JASNA (patrz blok "MOTYW AlkoCzwartek" na
   końcu pliku) — tam też ciemny wariant .btn.secondary. */
.btn-row { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }

.errors {
    background: #f6d0d0;
    border: 1px solid #d99;
    color: #7a1f1f;
    padding: 10px 12px;
    border-radius: 5px;
    font-size: 13px;
    margin-bottom: 14px;
}

/* --- Wyślij e-mail (mail_compose.php) --- */
.mail-section { margin-bottom: 18px; }
.mail-section:last-of-type { margin-bottom: 0; }
.mail-result {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: 6px;
    font-size: 13px;
    background: #e2f2e2;
    color: #285c28;
}
.mail-result.fail { background: #f6d0d0; color: #7a1f1f; }
.recipient-modes { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 14px; }
.recipient-mode-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    border-radius: 20px;
    background: #eee9e2;
    color: #45403a;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    margin: 0;
    border: 1px solid transparent;
    transition: background .15s, color .15s;
}
.recipient-mode-btn input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}
.recipient-mode-btn:hover { background: #e2dcd0; }
.recipient-mode-btn.active {
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
}
.recipient-mode-count {
    background: rgba(0,0,0,0.12);
    border-radius: 20px;
    padding: 1px 7px;
    font-size: 11px;
}
.recipient-mode-btn.active .recipient-mode-count { background: rgba(255,255,255,0.28); }
.recipient-target { margin-bottom: 4px; }

.stop-row {
    display: grid;
    grid-template-columns: 1.4fr 1fr 1fr auto;
    gap: 8px;
    margin-bottom: 8px;
    align-items: center;
}
.stop-row .remove-stop {
    background: transparent;
    border: 1px solid #bbb;
    color: #555;
    padding: 8px 10px;
    font-size: 12px;
}
.stop-actions {
    display: flex;
    gap: 4px;
}
.stop-move {
    background: transparent;
    border: 1px solid #bbb;
    color: #555;
    padding: 8px 9px;
    font-size: 12px;
    line-height: 1;
}
.stop-move:disabled {
    opacity: .35;
    cursor: default;
}
/* Przycisk "wskaż na mapie" przy przystanku trasy (assets/route_stops_map.js)
   — geokodowanie wpisanego adresu czasem daje inny punkt na mapie niż
   faktyczne miejsce, więc trzeba dać możliwość dokładnego
   wskazania/poprawienia go ręcznie. */
.stop-map-btn {
    background: transparent;
    border: 1px solid #bbb;
    color: #555;
    padding: 8px 9px;
    font-size: 12px;
    line-height: 1;
}
.stop-map-btn.active {
    background: #2f6fed;
    border-color: #2f6fed;
    color: #fff;
}
.route-map-picker { margin-top: 4px; }

.gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 12px;
    align-items: start;
}
.gallery-grid figure {
    margin: 0;
    background: #fff;
    border-radius: 6px;
    overflow: hidden;
}
.gallery-grid .media-click {
    position: relative;
    cursor: pointer;
    transition: opacity .15s;
}
.gallery-grid .media-click:hover { opacity: .9; }
.gallery-grid img, .gallery-grid video { width: 100%; height: 130px; object-fit: cover; display: block; }
.gallery-grid .video-fallback-cover {
    width: 100%;
    height: 130px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #3a352e, #17140f);
}
.gallery-grid .video-fallback-cover .play-circle {
    width: 42px;
    height: 42px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
}
.gallery-grid figcaption { font-size: 11px; color: #555; padding: 6px 8px; }
.gallery-grid .play-badge {
    position: absolute;
    top: 6px;
    right: 6px;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    background: rgba(0,0,0,0.65);
    color: #fff;
    font-size: 10px;
    padding: 3px 7px;
    border-radius: 3px;
}
.gallery-grid .img-fallback {
    display: none;
    height: 130px;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: 4px;
    background: #eee;
    color: #999;
    font-size: 20px;
}
.gallery-grid .img-fallback span { font-size: 10px; opacity: .8; }
.gallery-grid .media-click.broken .img-fallback { display: flex; }
.gallery-grid .media-click.broken .play-badge { display: none; }

.gallery-back-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 600;
    color: var(--accent-dark);
    text-decoration: none;
    margin-bottom: 12px;
}
.gallery-back-link:hover { text-decoration: underline; }

.category-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 12px;
}
.category-head h2 { margin: 0; }
.category-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.category-head .category-rename-form {
    display: none;
    width: 100%;
    margin-top: 8px;
}
.category-head .category-rename-form.open { display: flex; }
.inline-form {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0;
}
.inline-form input[type=text] {
    width: auto;
    padding: 5px 8px;
    font-size: 16px; /* >=16px — blokada auto-zoomu iOS, patrz komentarz przy .modal-inner input[type=text] */
    border-radius: 4px;
    height: auto; /* reset wspólnego "input[type=text] { height:44px }" — patrz komentarz przy .topbar-search input[type=text] */
}
.btn-icon {
    background: #d9d9d9;
    color: #333;
    border: none;
    border-radius: 4px;
    padding: 5px 9px;
    font-size: 11px;
    cursor: pointer;
    white-space: nowrap;
    transition: background .15s;
}
.btn-icon:hover { background: #ccc; }
.btn-icon.danger { background: #f3d2d2; color: #7a1f1f; }
.btn-icon.danger:hover { background: #eabcbc; }

/* --- Przycisk obok pola input w jednym rzędzie (Zapisz/Zbanuj/Wyczyść/
   Odbanuj/kosz) — zgłoszenie użytkownika 04.09.2026: dziesiątki takich
   rzędów w Narzędziach (Regiony/Charakter tras/Kategorie ogłoszeń/Tagi
   wydarzeń/Poziomy rankingu/Kamienie milowe/Zbanuj lub usuń konto itd.)
   miały każdy swój osobny, przypadkowy inline padding — input miał zawsze
   44px (patrz input[type=text] { height:44px } wyżej), przyciski różne,
   mniejsze wysokości, więc wizualnie "skakały" względem pola. Te dwie klasy
   ZASTĘPUJĄ te osobne inline style="padding:..." wszędzie, gdzie przycisk
   stoi w rzędzie z inputem — .btn-form dla przycisku z tekstem (Zapisz/
   Zbanuj/Wyczyść/Odbanuj), .btn-form-icon dla przycisku tylko-z-ikoną
   (kosz przy usuwaniu wiersza). */
.btn-form {
    height: 38px;
    box-sizing: border-box;
    padding: 0 16px;
    font-size: 12.5px;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}
.btn-form-icon {
    height: 38px;
    width: 38px;
    box-sizing: border-box;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.item-tools {
    display: flex;
    gap: 4px;
    padding: 6px;
    background: #eee9e2;
    justify-content: center;
}
.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0; /* nigdy nie ma się ściskać w rzędzie flex, np. obok
                        długiego, nie zawijającego się tekstu — patrz naprawa
                        .chat-header-meta niżej, zgłoszenie użytkownika
                        09.09.2026 ze zrzutem ekranu (ikonka ustawień grupy
                        zmieniała się w wąski prostokąt na telefonie) */
    padding: 0;
    border-radius: 4px;
    background: #d8d2c8;
    color: #444;
    border: none;
    cursor: pointer;
    text-decoration: none;
    transition: background .15s;
}
.icon-btn:hover { background: #c8c0b3; }
.icon-btn.danger { background: #f3d2d2; color: #7a1f1f; }
.icon-btn.danger:hover { background: #eabcbc; }
.caption-edit {
    display: none;
    gap: 4px;
    padding: 0 6px 6px;
    background: #eee9e2;
    margin: 0;
}
.caption-edit.open { display: flex; }
/* font-size >=16px — blokada auto-zoomu iOS, ten sam powód co przy
   .modal-inner input[type=text] niżej. */
.caption-edit input[type=text] { flex: 1; padding: 5px 8px; font-size: 16px; border-radius: 4px; height: auto; }
/* Przenoszenie zdjęć/wideo między kategoriami Galerii — zgłoszenie
   użytkownika 14.09.2026. Jeden checkbox w rzędzie .item-tools (obok
   edycji/linku/usuwania) służy i do pojedynczego, i do zbiorczego
   przeniesienia — zaznaczenie choć jednego pokazuje pasek .gallery-bulk-bar
   nad siatką z wyborem kategorii docelowej. Wcześniejsza wersja miała
   dodatkowo osobny przycisk/panel tylko do pojedynczego przenoszenia —
   usunięty na prośbę użytkownika (uproszczenie, jeden mechanizm zamiast
   dwóch). */
.item-tools { align-items: center; }
.gallery-select-checkbox { width: 16px; height: 16px; flex-shrink: 0; cursor: pointer; accent-color: var(--accent); }
.gallery-bulk-bar {
    display: none;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
    border-radius: 6px;
    padding: 8px 10px;
    margin-bottom: 10px;
    font-size: 12px;
}
.gallery-bulk-bar select { padding: 5px 6px; font-size: 12px; border-radius: 4px; }

/* --- Zakładki (panel administracyjny) --- */
.tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    border-bottom: 1px solid #ccc;
    margin-bottom: 18px;
}
.tabs a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 10px 16px;
    font-size: 13px;
    font-weight: 500;
    color: #666;
    text-decoration: none;
    border-radius: 5px 5px 0 0;
    position: relative;
    top: 1px;
    transition: color .15s, background .15s;
}
.tabs a:hover { color: #111; background: rgba(0,0,0,0.04); }
.tabs a.active {
    color: #111;
    background: #fff;
    border: 1px solid #ccc;
    border-bottom-color: #fff;
}
.tabs a .count {
    display: inline-block;
    margin-left: 5px;
    background: var(--accent);
    color: #fff;
    font-size: 10px;
    padding: 1px 6px;
    border-radius: 6px;
}
/* --- Etykiety grup banerów w Narzędzia -> Wygląd (Ogólne/Nagłówki działów/
   Pulpit/Ogłoszenia/Materiały/ShadowManiak/Statki/Kod Mistrza/Konkursy) —
   kicker z ikoną i cienką linią pod spodem, żeby podział na sekcje było
   widać od razu, nie tylko jako pogrubiony akapit. Litery rozstrzelone tak
   jak .audycja-live-kicker, ale bez tła/plakietki — to nagłówek sekcji na
   stronie, nie etykieta na karcie. Zgłoszenie użytkownika 21.09.2026:
   "możesz ładniej zrobić tytuły tych działów - zaprojektuj ładnie". --- */
.wyglad-group-label {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 34px 0 14px;
    padding-bottom: 10px;
    border-bottom: 1px solid color-mix(in srgb, var(--text-chrome) 15%, transparent);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--text-chrome-muted);
}
.wyglad-group-label.first { margin-top: 0; }
.wyglad-group-label svg {
    width: 15px;
    height: 15px;
    color: var(--accent-light);
    flex-shrink: 0;
}

.subtabs {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 18px;
}
.subtabs a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 14px;
    font-size: 12.5px;
    font-weight: 600;
    color: #6b6560;
    text-decoration: none;
    border-radius: 20px;
    background: #eee9e2;
    transition: background .15s, color .15s;
}
.subtabs a:hover { background: #ddd5c8; color: #171512; }
.subtabs a.active {
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
}
/* --- Panel administracyjny: pasek boczny (zamiast poziomych .tabs) ---
   Przeprojektowanie 04.09.2026, patrz render_admin_tabs()/render_admin_tabs_end()
   w includes/layout.php. Karty (jak .card) są zawsze jasne niezależnie od
   motywu strony (patrz komentarz przy --card-bg w :root wyżej), więc kolory
   tu są celowo takie same, twarde wartości jak w .tabs/.subtabs powyżej —
   nie tokeny --text-chrome-*, które są dla treści leżącej BEZPOŚREDNIO na
   tle strony.

   Wersja 2 (ten sam dzień) — na telefonie ZAMIAST poziomego, przewijanego
   rzędu "pigułek" (pierwsza wersja) jest teraz natywny <details>/<summary>:
   zgłoszenie użytkownika — przewijanie palcem poziomego paska w Web App
   (PWA) na iOS czasem "porywało" całe okno/przesuwało stronę zamiast tylko
   scrollować pasek (znany konflikt gestów przeglądarki przy poziomym
   scrollu blisko krawędzi ekranu). <details> nie ma tego problemu wcale,
   bo nie przewija się poziomo w ogóle — tylko rozwija pionową listę,
   dokładnie tak jak reszta strony i tak już scrolluje. Zero JS potrzebne do
   samego rozwijania/zwijania (natywne zachowanie <details>), jedna mała
   synchroniczna linijka (patrz render_admin_tabs()/render_tools_tabs() w
   includes/layout.php) tylko po to, żeby na telefonie START był zwinięty
   (na desktopie <summary> jest ukryty, więc "open" i tak nie ma tam
   znaczenia — treść zawsze widoczna). */
.admin-shell {
    display: flex;
    align-items: flex-start;
    gap: 16px;
}
/* CELOWO bez position:sticky — z 10 grupami w nawigacji boczna lista bywa
   dłuższa niż sama treść karty obok, a "przyklejony" panel przy krótszej
   treści dawał wrażenie rozjechanego przewijania (treść już się skończyła,
   nawigacja dalej "jedzie" osobno). Zgłoszenie użytkownika 21.09.2026: "zrób
   żeby całość sie skrolowała razem bo teraz jest tak długa lewa nawigacja że
   musi być razem" — ten sam wniosek co wcześniej przy .ann-sidebar-col
   (panel boczny Ogłoszeń): CAŁA strona przewija się razem, bez żadnego
   "position:sticky". */
.admin-sidebar {
    width: 200px;
    flex-shrink: 0;
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    padding: 10px;
}
.admin-sidebar-summary {
    display: none;
    list-style: none;
    cursor: pointer;
    align-items: center;
    justify-content: space-between;
    padding: 4px 2px;
    font-size: 14px;
    font-weight: 600;
    color: #171512;
}
.admin-sidebar-summary::-webkit-details-marker { display: none; }
/* Strzałka rozwijająca pasek boczny na mobile — zgłoszenie użytkownika
   04-05.09.2026: mały, szary "▾" (14px) ginął w rogu, kolejne powiększenia
   samego znaku w kółku dalej wyglądały jak "kropka, która nic nie mówi".
   Od 05.09.2026 własna grafika (okrąg + trójkąt, patrz render_sidebar_chevron()
   w includes/layout.php) zamiast tekstowego znaku w tle-kółku — rysuje się
   sama, więc tu tylko rozmiar/kolor/obrót. */
.admin-sidebar-summary .chev {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--accent);
}
.admin-sidebar-summary .chev svg {
    width: 36px;
    height: 36px;
    transition: transform .15s;
}
.admin-sidebar[open] .admin-sidebar-summary .chev svg { transform: rotate(180deg); }
.admin-sidebar-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.admin-nav-group {
    font-size: 11px;
    color: #8a847d;
    text-transform: uppercase;
    letter-spacing: .03em;
    margin: 12px 10px 4px;
}
.admin-sidebar-list > .admin-nav-group-details:first-child > .admin-nav-group { margin-top: 2px; }
/* Akordeon głównych kategorii (Użytkownicy/Wygląd i Treści/Komunikacja/Gry/
   Statystyki/Ustawienia systemowe) — zadanie XII, 21.09.2026, zgłoszenie
   właściciela klubu: "dla urządzeń mobilnych każdą z głównych kategorii
   zrobić zwijaną, w taki sposób że jak jedną rozwinie to zwinie pozostałe".
   Każda grupa to teraz <details open> (patrz render_tools_tabs()/
   render_admin_tabs() w includes/layout.php) zamiast zwykłego <p> — na
   desktopie zostaje TAK JAK BYŁO: zawsze rozwinięta lista, <summary>
   celowo nieklikalny (pointer-events:none) i bez strzałki, więc wygląda
   dokładnie jak dawny nagłówek grupy. Dopiero w tym samym breakpoincie co
   reszta .admin-sidebar (<=720px) <summary> staje się klikalny i dostaje
   strzałkę — zwijanie/rozwijanie samo w sobie jest natywnym zachowaniem
   <details>, JS (render_admin_sidebar_collapse_script()) dogrywa tylko
   wzajemne wykluczanie (otwarcie jednej zwija pozostałe) i stan startowy. */
.admin-nav-group-details { margin: 0; }
.admin-nav-group-details > summary.admin-nav-group {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    cursor: default;
    pointer-events: none;
    list-style: none;
}
.admin-nav-group-details > summary.admin-nav-group::-webkit-details-marker { display: none; }
.admin-nav-group-details > summary.admin-nav-group .nav-group-chev { display: none; }
.admin-nav-group-links { display: flex; flex-direction: column; gap: 2px; }
@media (max-width: 720px) {
    .admin-nav-group-details > summary.admin-nav-group {
        cursor: pointer;
        pointer-events: auto;
        padding: 6px 10px 6px 0;
        margin: 8px 0 2px;
        border-radius: 6px;
    }
    .admin-nav-group-details > summary.admin-nav-group:hover { background: rgba(0,0,0,0.04); }
    .admin-nav-group-details > summary.admin-nav-group .nav-group-chev {
        display: inline-flex;
        flex-shrink: 0;
        color: var(--accent);
        transition: transform .15s;
    }
    .admin-nav-group-details > summary.admin-nav-group .nav-group-chev svg { width: 16px; height: 16px; }
    .admin-nav-group-details[open] > summary.admin-nav-group .nav-group-chev { transform: rotate(180deg); }
}
.admin-sidebar-list a {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    padding: 8px 10px;
    border-radius: 6px;
    color: #55504a;
    text-decoration: none;
    font-size: 13px;
    font-weight: 500;
}
.admin-sidebar-list a:hover { background: rgba(0,0,0,0.05); color: #171512; }
.admin-sidebar-list a.active {
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
}
.admin-sidebar-list a .count {
    /* margin-left:auto — dosuwa licznik do prawej w linkach, które go mają
       (np. "Oczekujący"), niezależnie od tego, że reszta linków (bez
       licznika) jest wyrównana do lewej (justify-content: flex-start
       wyżej) — zgłoszenie użytkownika 04.09.2026 ("ikonki z jednej a treść
       tytułu z drugiej"). */
    margin-left: auto;
    background: var(--accent);
    color: #fff;
    font-size: 11px;
    padding: 1px 7px;
    border-radius: 20px;
}
.admin-sidebar-list a.active .count { background: rgba(255,255,255,0.25); }
.admin-content { flex: 1; min-width: 0; }

@media (max-width: 720px) {
    .admin-shell { flex-direction: column; gap: 12px; }
    .admin-sidebar { width: 100%; position: static; padding: 12px; }
    .admin-sidebar-summary { display: flex; }
    .admin-sidebar-list { padding-top: 10px; margin-top: 8px; border-top: 1px solid var(--card-border); }
    .admin-sidebar:not([open]) .admin-sidebar-list { display: none; }
    /* Naprawa 19.09.2026 (zgłoszenie użytkownika: "okienko nie wyświetla się
       na szerokość 'Konta oczekujące na akceptację' w mobilnych") —
       .admin-shell ma align-items:flex-start (potrzebne na desktopie, żeby
       .admin-sidebar nie rozciągał się na wysokość .admin-content), ale w tym
       breakpoincie .admin-shell zmienia się na flex-direction:column, więc
       align-items:flex-start zaczyna działać na OSI POZIOMEJ — każdy element
       (więc i .admin-content, jedyny bez własnego "width") kurczy się do
       szerokości własnej treści zamiast rozciągać na całą szerokość, przez co
       WSZYSTKIE karty w Panelu admina/Aktualizacji serwisu (nie tylko lista
       "Oczekujący") wychodziły węższe niż ekran. .admin-sidebar miał już
       "width: 100%" wyżej, więc tego akurat nie było widać. */
    .admin-content { width: 100%; }
}

.gallery-cat-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 16px;
}
.gallery-cat-tile {
    position: relative;
    display: block;
    height: 200px;
    border-radius: 9px;
    overflow: hidden;
    text-decoration: none;
    background-color: #2c2c2c;
    transition: transform .18s;
}
.gallery-cat-tile:hover {
    transform: translateY(-3px);
}
/* Zdjęcie okładkowe kategorii jako osobna, w pełni wypełniająca warstwa
   (zamiast background-image bezpośrednio na kafelku) — dzięki temu powiększenie
   przy najechaniu robimy przez transform: scale(), a nie przez zmianę
   background-size (procentowe background-size liczy się inaczej niż "cover",
   więc zamiana cover -> 108% przy najechaniu potrafiła nagle pokazać zupełnie
   inny fragment/inne proporcje zdjęcia zamiast płynnego zbliżenia). */
.gallery-cat-tile-bg {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    transition: transform .4s;
}
.gallery-cat-tile:hover .gallery-cat-tile-bg {
    transform: scale(1.08);
}
.gallery-cat-tile::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,0) 35%, rgba(0,0,0,0.8) 100%);
}
.gallery-cat-tile.no-image { background-image: none !important; background: linear-gradient(135deg, #2c2c2c, #181818); }
.gallery-cat-tile-icon {
    position: absolute;
    top: 42%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #fff;
    opacity: .2;
}
.gallery-cat-tile-body {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 14px 16px;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.gallery-cat-tile-name {
    font-size: 16px;
    font-weight: 700;
    color: #fff;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-shadow: 0 1px 4px rgba(0,0,0,0.4);
}
.gallery-cat-tile-count {
    font-size: 11px;
    font-weight: 600;
    color: #fff;
    background: rgba(255,255,255,0.22);
    backdrop-filter: blur(2px);
    border-radius: 999px;
    padding: 3px 10px;
    flex-shrink: 0;
}
@media (max-width: 640px) {
    .gallery-cat-tiles { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
    .gallery-cat-tile { height: 140px; border-radius: 7px; }
    .gallery-cat-tile-name { font-size: 13px; }
}

.event-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 14px;
}
.event-card {
    display: flex;
    flex-direction: column;
    /* height:100% + align-self:stretch — bez tego karta w siatce CSS Grid
       (zwłaszcza w Safari/iOS, znany kwirk) nie zawsze rozciągała się do
       pełnej wysokości wiersza, gdy sąsiednia karta miała nieco dłuższy/
       krótszy tytuł. Krótsza karta kończyła się wcześniej niż wiersz siatki,
       a pod spodem, w tej samej komórce, prześwitywało czarne tło strony —
       zgłoszenie użytkownika 25.08.2026, ze screenshotem ("pasek na dole
       czarny"). Ten sam mechanizm co wcześniejsza naprawa .stat-card. */
    height: 100%;
    align-self: stretch;
    box-sizing: border-box;
    background: #232323;
    border-radius: 6px;
    overflow: hidden;
    text-decoration: none;
    color: #eee;
    transition: transform .15s;
}
.event-card:hover { transform: translateY(-2px); }
.event-card img { width: 100%; height: 140px; object-fit: cover; display: block; flex-shrink: 0; }
.event-card-placeholder {
    width: 100%;
    height: 140px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #6b6560;
    background: linear-gradient(135deg, #3a3a3a, #262626);
    position: relative;
    overflow: hidden;
}
/* Logo SOG jako blade "watermark" w tle zastępczej miniaturki (Ogłoszenia,
   Wydarzenia) — żeby karty bez własnego zdjęcia nie wyglądały na puste/gołe.
   Sama ikonka działu (announcement/calendar) zostaje na wierzchu, więc każdy
   dział wciąż wygląda inaczej, tylko oba mają teraz w tle to samo logo. */
.event-card-placeholder-logo {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 62%;
    max-height: 82%;
    opacity: 0.16;
    pointer-events: none;
}
.event-card-placeholder svg { position: relative; z-index: 1; }
.event-card-body { padding: 12px 14px; flex: 1; }
.event-card-body .title { margin: 0 0 5px; font-size: 13px; font-weight: 600; color: #fff; }
.event-card-body .meta { margin: 0; font-size: 11px; color: #a8a29a; line-height: 1.5; }

/* Kafelek audycji na liście wydarzeń (events.php) — wyróżniony tym samym
   czerwonym gradientem co kafelka ShadowManiaka na pulpicie/karta "na żywo"
   w dziale, żeby audycja rzucała się w oczy na pierwszy rzut oka, nie tylko
   przez małą plakietkę. Zgłoszenie użytkownika 25.08.2026: "super, jest
   ekstra, a możemy poprawić kolor kafelki z audycją... taka wyróżniona". */
.event-card.event-card-audycja .event-card-body { background: linear-gradient(150deg, #b8342c, #6e120e 75%); }
.event-card.event-card-audycja .event-card-body .meta { color: rgba(255,255,255,0.8); }
/* Sama plakietka "Audycja" (.badge-audycja) ma podobny czerwony gradient co
   teraz całe tło kafelka — bez tej zmiany plakietka by "znikała" na własnym
   tle. Jaśniejsza, półprzezroczysta biała pigułka (jak "NA ANTENIE" na
   karcie "na żywo") zamiast tego. */
.event-card.event-card-audycja .badge-audycja { background: rgba(255,255,255,0.22); }

/* Kafelki tras (routes.php) — .event-card z mapką jako "zdjęciem" +
   wyróżnienie tras "szuka towarzystwa", widoczne też na pulpicie, stąd też
   te same kolory/nazwa co plakietka .badge gdzie indziej w serwisie.
   Pomarańczowy pasek z boku
   karty (inset box-shadow, nie border — respektuje zaokrąglone rogi
   .event-card bez dodatkowej matematyki na promieniu) + plakietka na mapce,
   żeby było widać od razu przy przewijaniu listy, bez czytania treści. */
.route-card { position: relative; }
.route-card-companions { box-shadow: inset 4px 0 0 0 var(--accent); }
.route-card-badge {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 2;
    background: var(--accent);
    color: #fff;
    font-size: 10px;
    font-weight: 600;
    padding: 4px 9px;
    border-radius: 20px;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    box-shadow: 0 1px 4px rgba(0,0,0,.35);
}

/* Karta "Pytanie tygodnia" w siatce quizzes.php — ta sama wstążka co
   .route-card-badge (spójność z resztą serwisu) + akcentowa ramka na
   całej karcie, żeby wyraźnie odróżniała się od zwykłych quizów w tej
   samej siatce, nawet bez czytania treści. Zgłoszenie 11.08.2026. */
.quiz-week-featured {
    position: relative;
    box-shadow: 0 0 0 2px var(--accent);
}
/* Pasek z tytułem/wynikiem pod zdjęciem — pomarańczowy (jak wstążka i ramka
   wyżej), żeby karta "Pytanie tygodnia" wyróżniała się nie tylko obwódką, ale
   też kolorem, na pierwszy rzut oka, nawet przy szybkim przewijaniu siatki.
   Prośba użytkownika 11.08.2026. */
.quiz-week-featured .event-card-body {
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
}
.quiz-week-featured .event-card-body .meta { color: rgba(255,255,255,0.88); }
.quiz-week-ribbon {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 2;
    background: var(--accent);
    color: #fff;
    font-size: 10px;
    font-weight: 600;
    padding: 4px 9px;
    border-radius: 20px;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    box-shadow: 0 1px 4px rgba(0,0,0,.35);
}
/* Wyróżnienie KOLOREM ogłoszenia (moderator/admin, osobne od "kolejności" —
   patrz announcement_view.php) — ten sam zabieg co .quiz-week-featured wyżej
   (kolorowa kafelka + wstążka), ale z DOWOLNYM kolorem zamiast sztywnego
   pomarańczowego: kolor przychodzi jako zmienna CSS --announcement-highlight
   ustawiona inline na karcie (patrz announcements.php), z var(--accent) jako
   wartość domyślna, gdyby jednak inline stylu zabrakło. Dwa odcienie do
   gradientu (jaśniejszy/ciemniejszy) liczone na bieżąco przez color-mix()
   zamiast osobnych kolumn w bazie na "jasny"/"ciemny" wariant — ta sama
   technika co .badge (color-mix) już używana w tym pliku wyżej. Prośba
   użytkownika 03.09.2026: "dwa wyróżnienia — kolor i kolejność, a jak
   będzie potrzeba to tylko kolor". */
.announcement-featured {
    position: relative;
    box-shadow: 0 0 0 2px var(--announcement-highlight, var(--accent));
}
.announcement-featured .event-card-body {
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--announcement-highlight, var(--accent)) 85%, white),
        color-mix(in srgb, var(--announcement-highlight, var(--accent)) 85%, black));
}
.announcement-featured .event-card-body .meta { color: rgba(255,255,255,0.88); }
/* Plakietka kategorii w tytule (np. "Sprzedam") ginęłaby na kolorowym tle —
   jaśniejsza, półprzezroczysta wersja zamiast domyślnej (jak przy .badge-audycja). */
.announcement-featured .event-card-body .title .badge { background: rgba(255,255,255,0.25); color: #fff; }
.announcement-featured-ribbon {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 2;
    background: var(--announcement-highlight, var(--accent));
    color: #fff;
    font-size: 10px;
    font-weight: 600;
    padding: 4px 9px;
    border-radius: 20px;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    box-shadow: 0 1px 4px rgba(0,0,0,.35);
}

/* --- Ogłoszenia: feed w stylu Facebooka (przeprojektowanie 20.09.2026,
   zgłoszenie użytkownika "chciałbym aby wyglądały jak na Facebook") —
   zastępuje dawną siatkę kafelków .event-grid/.event-card. Każdy wpis to
   teraz własna karta z nagłówkiem autora, treścią, siatką zdjęć
   (.announcement-photo-grid — bez zmian, już istniała), paskiem reakcji,
   podglądem ostatniego komentarza i szybkim polem komentarza. Celowo JASNA
   paleta (#fff/--card-bg), tak samo jak .list-item/.card w całym serwisie —
   karty są zawsze jasne niezależnie od motywu strony (patrz komentarz przy
   --card-bg w :root), więc NIE kopiujemy ciemnych kolorów z podglądu
   ogloszenia_facebook_podglad.html (to był tylko szkic układu, nie
   docelowej kolorystyki). Stronicowanie zostaje klasyczne (bez
   nieskończonego przewijania) — patrz komentarz przy $announcementsPagination
   w announcements.php. */
.ann-page-layout { display: flex; align-items: flex-start; gap: 20px; }
/* Karta z listą NIE rozciąga się na całą dostępną szerokość (flex:1 dawało
   szeroką jasną kartę z węższym, wyśrodkowanym feedem w środku — dwa różne
   odcienie jasnego tła obok siebie wyglądały jak przypadkowa "poświata" po
   bokach, zgłoszenie użytkownika 20.09.2026 ze screenshotem). Zamiast tego
   karta ma stałą, docelową szerokość samego feedu (flex-basis, bez grow) —
   ewentualny nadmiar miejsca zostaje zwykłym ciemnym tłem strony, tak jak
   gdzie indziej w serwisie, zamiast jasnym "korytarzem" wewnątrz karty. */
.ann-page-layout > .card:first-child { flex: 0 1 740px; min-width: 0; }
/* Ten sam układ na announcement_view.php (pojedynczy post) — tam pierwsze
   dziecko .ann-page-layout NIE jest już pojedynczą .card (post/edycja/
   komentarze to kilka osobnych kart jedna pod drugą), tylko zwykły wrapper
   .ann-view-main, więc reguła ":first-child .card" wyżej by go nie objęła.
   Dodane 21.09.2026 razem z panelem bocznym na tej stronie — patrz
   render_announcements_sidebar() w includes/helpers.php. */
.ann-page-layout > .ann-view-main { flex: 0 1 740px; min-width: 0; }
/* Kolumna prawa (desktop) obok listy ogłoszeń — zgłoszenie użytkownika
   20.09.2026: wąska kolumna feedu w stylu Facebooka zostawiała na szerokim
   ekranie dużo pustki, więc dochodzi kolumna z kategoriami, wyróżnionym
   ogłoszeniem i skrótem do dodania nowego (realna treść, nie pusty
   "baner"). CELOWO bez position:sticky — pierwsza wersja miała przypięty
   panel .ann-sidebar, a baner pod nim w normalnym przepływie dokumentu, co
   przy skrolowaniu dawało efekt "chowanego" (panel stał w miejscu, baner
   znikał/pojawiał się osobno). Zgłoszenie użytkownika 20.09.2026: "obie
   kolumny są połączone i idą razem do góry, nie zabawa z banerem w
   chowanego... całość strony przewijamy, nie zostawiamy panelu
   nawigacyjnego, wszystko razem skrolujemy" — więc panel i baner są teraz
   zwykłymi elementami w normalnym przepływie i przewijają się razem z resztą
   strony, bez żadnego "position:sticky". */
.ann-sidebar-col { width: 300px; flex-shrink: 0; display: flex; flex-direction: column; gap: 14px; }
.ann-sidebar h3 {
    font-size: 12px;
    font-weight: 700;
    color: #8a847d;
    text-transform: uppercase;
    letter-spacing: .04em;
    margin: 16px 0 8px;
    padding-top: 14px;
    border-top: 1px solid rgba(0,0,0,0.07);
}
/* Skrót "Do akceptacji" (tylko moderator/admin, tylko gdy coś czeka) — te
   same kolory co karta "Do akceptacji" nad feedem (#fbeecf), żeby od razu
   było widać, że to ten sam temat. */
.ann-sidebar-pending {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 9px 12px;
    margin-top: 10px;
    border-radius: 6px;
    background: #fbeecf;
    color: #6b4a10;
    text-decoration: none;
    font-size: 13px;
    font-weight: 600;
}
.ann-sidebar-pending:hover { background: #f7e2ad; }
.ann-sidebar-cats { display: flex; flex-direction: column; gap: 2px; margin-bottom: 4px; }
.ann-sidebar-cats a { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 7px 10px; border-radius: 6px; font-size: 13px; color: #3a352f; text-decoration: none; }
.ann-sidebar-cats a:hover { background: rgba(0,0,0,0.04); }
.ann-sidebar-cats a.active { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent-dark); font-weight: 600; }
.ann-sidebar-cats a span { font-size: 11px; color: #8a847d; }
.ann-sidebar-cats a.active span { color: inherit; }
/* Wyróżnione ogłoszenia w panelu jako prawdziwe "banery" (zdjęcie w tle +
   gradient + tytuł na dole, jak plakat), nie tylko mała miniaturka z
   podpisem — zgłoszenie użytkownika 20.09.2026 o "pionowej bannerowni".
   Bez zdjęcia okładkowego spada na neutralne ciemne tło (nadal czytelny
   biały tekst), żeby nie zależeć od tego, czy ogłoszenie ma okładkę.
   MOŻE być ich kilka naraz (jeden pod drugim, do 5) — zgłoszenie
   użytkownika 20.09.2026: "zauważyłem że tylko jeden może być wyróżniony,
   a możemy w pasek nawigacyjny na desktop dodać jeden pod drugim
   wyróżnione posty, jeśli są w serwisie?". */
.ann-sidebar-featured-list { display: flex; flex-direction: column; gap: 9px; }
.ann-sidebar-banner {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: 150px;
    background-size: cover;
    background-position: center;
    background-color: #2a2a28;
    border-radius: 8px;
    padding: 10px 12px;
    text-decoration: none;
}
.ann-sidebar-banner-meta { font-size: 11px; font-weight: 600; color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.6); margin-bottom: 3px; }
.ann-sidebar-banner-title { font-size: 13.5px; font-weight: 700; color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.6); line-height: 1.35; }
/* "Najnowsze" — kilka ostatnich ogłoszeń jako kompaktowa lista miniatur, tak
   samo jak np. "Ostatnio dodane" gdzie indziej w serwisie. */
.ann-sidebar-recent { display: flex; flex-direction: column; gap: 9px; }
.ann-sidebar-recent-item { display: flex; gap: 9px; align-items: center; text-decoration: none; color: inherit; }
.ann-sidebar-recent-item img { width: 40px; height: 40px; border-radius: 6px; object-fit: cover; flex-shrink: 0; }
.ann-sidebar-recent-noimg { width: 40px; height: 40px; border-radius: 6px; background: #f3f0ea; display: flex; align-items: center; justify-content: center; color: #b0aaa1; flex-shrink: 0; }
.ann-sidebar-recent-text { min-width: 0; }
.ann-sidebar-recent-title { display: block; font-size: 12.5px; font-weight: 600; color: #171512; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ann-sidebar-recent-meta { display: block; font-size: 11px; color: #8a847d; margin-top: 1px; }
.ann-sidebar-recent-item:hover .ann-sidebar-recent-title { text-decoration: underline; }
/* Baner (300x600, Narzędzia -> Treści) POD panelem (.ann-sidebar), jako
   osobny element w .ann-sidebar-col — patrz obszerny komentarz przy
   .ann-sidebar-col wyżej o tym, dlaczego NIE jest wewnątrz samego panelu
   (i dlaczego cała kolumna jest teraz w normalnym przepływie, bez sticky).
   Skoro nie siedzi już w .card, dostaje własną, delikatną "ramkę" (jasne
   tło + cienka obwódka 1px), żeby nie wyglądać jak luźno wklejona grafika.
   Nazwa klasy CELOWO nie zawiera "ad"/"banner" — patrz komentarz w
   announcements.php o adblockach (pierwsza wersja z klasą "ann-sidebar-ad"
   i nagłówkiem "Reklama" była całkowicie zaślepiana). */
/* Kontener karuzeli (position:relative + stały format 1:2 = 300x600) —
   slajdy są przenikającymi się warstwami absolutnie pozycjonowanymi na
   PEŁNĄ szerokość/wysokość (inset:0 — obrazek styka się z cienką, 1px
   obwódką kontenera, BEZ dodatkowej "matowej" ramki wewnątrz), ten sam
   wzorzec przenikania/kropek co .dashboard-promo-carousel na Pulpicie,
   tylko bez treści tekstowej (te slajdy mają tylko obrazek + opcjonalny
   link) — patrz ann-sidebar-promo-slide niżej. Wcześniejsza wersja miała
   inset:8px (efekt passe-partout/mata) — zgłoszenie użytkownika
   20.09.2026: "bannerownia w delikatnej obwódce, zaszeroka ta ramka,
   pewnie skaluje się banner" — "delikatna ramka" z wcześniejszego
   zgłoszenia miała być tylko cienką 1px linią kontenera (tak jak jest),
   NIE dodatkowym grubym marginesem wokół obrazka. object-fit:cover
   wypełnia slajd całkowicie bez skalowania/niedopasowania — to nie
   "skalowanie banera" tylko właśnie ten wewnętrzny margines 8px. */
.ann-sidebar-promo-box {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 1 / 2;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.08);
    border-radius: 10px;
    box-sizing: border-box;
    overflow: hidden;
}
.ann-sidebar-promo-slide {
    position: absolute;
    inset: 0;
    display: block;
    opacity: 0;
    z-index: 1;
    transition: opacity 1s ease;
}
.ann-sidebar-promo-slide.active { opacity: 1; z-index: 2; }
.ann-sidebar-promo-slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ann-sidebar-promo-dots {
    position: absolute;
    bottom: 14px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
    display: flex;
    gap: 5px;
}
.ann-sidebar-promo-dots .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255,255,255,0.55);
    cursor: pointer;
    transition: transform .2s, background .2s;
}
.ann-sidebar-promo-dots .dot.active { background: #fff; transform: scale(1.3); }
/* Zakładki kategorii NAD feedem (.tabs w karcie listy) dublują się z listą
   kategorii w panelu bocznym, który jest widoczny na tej samej stronie na
   desktopie — zgłoszenie użytkownika 20.09.2026: "usuń w szerszej kolumnie
   na górze kategorie, skoro mamy je w panelu bocznym". Na mobilnym panel
   boczny ląduje pod całym feedem (patrz .ann-page-layout niżej), więc TAM
   .tabs zostaje jedynym wygodnym sposobem filtrowania — stąd ukrywamy je
   TYLKO od szerokości, przy której panel faktycznie stoi obok listy
   (zgłoszenie: "cały czas robimy tylko desktop, mobile wygląda nie
   ruszamy"). Druga reguła (".ann-view-main > .card:first-child") to
   DOKŁADNIE ten sam przypadek na events.php — tam kolumna główna jest
   ".ann-view-main" (bo mieści też kartę "Nowe wydarzenie" pod listą), nie
   sama karta wprost pod ".ann-page-layout" jak na Ogłoszeniach. Dodane
   21.09.2026, gdy panel boczny Wydarzeń dostał własną sekcję "Kategorie" —
   zgłoszenie użytkownika: "Dodajmy pod kalendarzem jak w ogłoszeniach
   kategorie, będzie spójny system". */
@media (min-width: 901px) {
    .ann-page-layout > .card:first-child > .tabs,
    .ann-page-layout > .ann-view-main > .card:first-child > .tabs,
    /* .tn-tabs-card (Konkursy, turnieje.php) — NIE polega na :first-child,
       bo nad nią czasem wskakuje osobna karta "Wstęp do działu Konkursy"
       (patrz komentarz w turnieje.php). Zgłoszenie użytkownika 28.09.2026:
       "w wersji desktop powielają się kategorie mamy je w prawym menu wiec
       nie ma potrzeby aby były nad postami". */
    .ann-page-layout > .ann-view-main > .tn-tabs-card > .tabs { display: none; }
}
/* Przycisk "Dodaj ogłoszenie"/"Dodaj wydarzenie" nad listą — TYLKO na mobile
   (na desktopie już jest w panelu bocznym). Zgłoszenie użytkownika
   20.09.2026 (Ogłoszenia); ".event-feed-mobile-add-btn" to ten sam wzorzec
   na Wydarzeniach, dodany 21.09.2026 dla spójności wyglądu obu list na
   mobile — zgłoszenie użytkownika: "na mobilnych musi być spójność
   wyglądu". */
.ann-feed-mobile-add-btn, .event-feed-mobile-add-btn, .route-feed-mobile-add-btn { display: none; }
/* Karta z mini-kalendarzem nad listą Wydarzeń — TYLKO na mobile (na
   desktopie ten sam kalendarz jest już w panelu bocznym, ukrytym poniżej
   razem z resztą .ann-sidebar-col). Wydarzenia, w przeciwieństwie do
   Ogłoszeń, mają kalendarz — stąd JEDYNA różnica względem mobilnego układu
   Ogłoszeń. Zgłoszenie użytkownika 21.09.2026: "tylko w wydarzeniach dodaj
   kalendarz na górę z Subskrybuj kalendarz". */
.event-mobile-calendar-card { display: none; }
/* Baner ShadowManiaka pod mini-kalendarzem — TYLKO na mobile (na desktopie
   ten sam baner jest już w panelu bocznym, ukrytym poniżej razem z resztą
   .ann-sidebar-col). Zgłoszenie użytkownika 21.09.2026: "jednak na
   mobilnych też musi być banner ShadowManiak, może pod kalendarzem". */
.event-mobile-shadowmaniak-banner { display: none; }
/* Odtwarzacz X-Stacji na żywo (shadowmaniak.php) — TYLKO na mobile (na
   desktopie ten sam odtwarzacz jest już w panelu bocznym, ukrytym poniżej
   razem z resztą .ann-sidebar-col, patrz render_shadowmaniak_sidebar() w
   includes/helpers.php). Ta sama technika co dwie mobilne karty-duplikaty
   Wydarzeń wyżej. Zgłoszenie użytkownika 21.09.2026: "dział ShadowManiak
   trzeba przerobić jak ogłoszenia i Wydarzenia... dajemy ten player co jest
   u góry" — na desktopie player wędruje do panelu bocznego, ale na mobile
   (gdzie ten panel znika) musi zostać widoczny gdzieś na stronie. */
.shadowmaniak-mobile-player-card { display: none; }
@media (max-width: 900px) {
    .ann-page-layout { flex-direction: column; }
    .ann-page-layout > .card:first-child { flex-basis: auto; width: 100%; }
    .ann-page-layout > .ann-view-main { flex-basis: auto; width: 100%; }
    /* Na mobile CAŁY panel boczny znika (Wyróżnione, kategorie, Do
       akceptacji, Najnowsze, oba bannery) — zamiast tego nad listą jest
       tylko przycisk "Dodaj ogłoszenie" (kategorie/.tabs w karcie z listą
       już tam były, bez zmian). Bez tego mobile pokazywał kartę z listą +
       kartę panelu + osobną kartę banera pod spodem — zbyt wiele "okien"
       jedno pod drugim. Zgłoszenie użytkownika 20.09.2026: "w mobilnych
       mozesz wyłaczyć bannery a nad ogłoszeniami zrobić przycisk dodaj
       ogłoszenie i kategorie, wtedy nie bedzie tyle okien". Ta sama reguła
       chowa panel boczny Wydarzeń (.ann-sidebar-col, ta sama klasa). */
    .ann-sidebar-col { display: none; }
    .ann-feed-mobile-add-btn, .event-feed-mobile-add-btn, .route-feed-mobile-add-btn { display: block; }
    .event-mobile-calendar-card { display: block; margin-bottom: 14px; }
    .event-mobile-shadowmaniak-banner { display: block; margin-bottom: 14px; }
    .shadowmaniak-mobile-player-card { display: block; margin-bottom: 14px; }
    /* Kategorie nad listą jako czytelne "chipsy" zamiast płaskich linków w
       tle karty — desktopowy styl .tabs (cienkie podkreślenie + biała
       aktywna "zakładka" łącząca się z linią pod spodem) rozjeżdża się przy
       zawijaniu do kilku wierszy na wąskim ekranie, bo nieaktywne linki nie
       mają żadnego własnego tła/obwódki. Początkowo TYLKO na Ogłoszeniach;
       21.09.2026 dołączone też Wydarzenia (".ann-view-main > .card:first-
       child > .tabs") dla spójności wyglądu obu list na mobile — zgłoszenie
       użytkownika: "na mobilnych musi być spójność wyglądu". Wcześniejsze
       zgłoszenie użytkownika 20.09.2026 ze screenshotem (Ogłoszenia):
       "mógłbyś pomyśleć nad ładniejszym układem na mobilnych z kategoriami,
       trochę nie pasuje... całość już ładnie wygląda a tu taka kicha". */
    .ann-page-layout > .card:first-child > .tabs,
    .ann-page-layout > .ann-view-main > .card:first-child > .tabs,
    .ann-page-layout > .ann-view-main > .tn-tabs-card > .tabs {
        border-bottom: none;
        gap: 8px;
        margin-bottom: 14px;
    }
    .ann-page-layout > .card:first-child > .tabs a,
    .ann-page-layout > .ann-view-main > .card:first-child > .tabs a,
    .ann-page-layout > .ann-view-main > .tn-tabs-card > .tabs a {
        background: #f6f3ef;
        border: 1px solid rgba(0,0,0,0.08);
        border-radius: 20px;
        padding: 8px 12px;
        color: #3a352f;
    }
    .ann-page-layout > .card:first-child > .tabs a.active,
    .ann-page-layout > .ann-view-main > .card:first-child > .tabs a.active,
    .ann-page-layout > .ann-view-main > .tn-tabs-card > .tabs a.active {
        background: var(--accent);
        border-color: var(--accent);
        color: #fff;
    }
    .ann-page-layout > .card:first-child > .tabs a.active .count,
    .ann-page-layout > .ann-view-main > .card:first-child > .tabs a.active .count,
    .ann-page-layout > .ann-view-main > .tn-tabs-card > .tabs a.active .count {
        background: rgba(255,255,255,0.3);
        color: #fff;
    }
}

.ann-feed { display: flex; flex-direction: column; gap: 14px; margin-top: 14px; }
.ann-feed-card {
    position: relative;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 10px;
    overflow: hidden;
}
/* "Stretched link" — TYLKO na kartach Wydarzeń (events.php dokłada ten
   element, announcements.php go nie renderuje, więc Ogłoszenia zostają bez
   zmian). Niewidoczny <a> pokrywa CAŁĄ kartę (inset:0) na z-index:1 — klik w
   zdjęcie/tytuł/opis/puste miejsce trafia w NIEGO i przenosi na
   event_view.php, zamiast w np. siatkę zdjęć (więc lightbox przestaje się
   otwierać, BEZ żadnych zmian w render_photo_grid()/announcement-gallery.js
   — po prostu nie da się już "dosięgnąć" zdjęcia klikiem). Elementy, które
   MAJĄ zostać klikalne osobno (awatar/nick autora, menu ⋯, "Zobacz
   więcej/mniej", pasek reakcji+komentarze — patrz ich z-index:2 niżej w tym
   pliku) leżą wizualnie NAD tym linkiem, więc przechwytują klik pierwsze.
   Zgłoszenie użytkownika 21.09.2026: "musi być cały post aktywnym linkiem,
   zdjęcie nie trzeba powiększać bo pod całym wydarzeniem jest całość
   ustawień" + "mamy czytaj więcej i to jest fajne, bo nie każdy [chce]
   przechodzić dalej" (więc "Zobacz więcej" zostaje wyjątkiem). */
.ann-feed-card-link { position: absolute; inset: 0; z-index: 1; }
.ann-feed-head { display: flex; align-items: flex-start; gap: 10px; padding: 14px 16px 8px; }
.ann-feed-head-text { flex: 1; min-width: 0; }
.ann-feed-head > a { position: relative; z-index: 2; }
.ann-feed-author { font-size: 13.5px; font-weight: 600; color: #171512; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ann-feed-author a { position: relative; z-index: 2; }
.ann-feed-meta { font-size: 11px; color: #7a746e; margin-top: 2px; }
/* Menu "⋯" na karcie posta (jak na Facebooku) — jedno miejsce na Edytuj/
   Usuń/Przypnij/Kolor wyróżnienia zamiast rozrzuconych osobno kontrolek.
   Zastąpiło samą wstążkę "Wyróżnione" w rogu karty, która nachodziła na
   "Edytuj" (zgłoszenie użytkownika 20.09.2026) — stan "wyróżnione" pokazuje
   się teraz jako zwykła plakietka w .ann-feed-author, nie jako nachodzący
   na coś element. */
.ann-feed-menu { position: relative; z-index: 2; flex-shrink: 0; }
.ann-feed-menu-trigger {
    background: none;
    border: none;
    cursor: pointer;
    color: #8a847d;
    font-size: 20px;
    line-height: 1;
    padding: 4px 8px;
    border-radius: 6px;
}
.ann-feed-menu-trigger:hover { background: rgba(0,0,0,0.06); color: #171512; }
.ann-feed-menu-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 4px;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.14);
    min-width: 200px;
    padding: 6px;
    z-index: 20;
}
.ann-feed-menu-dropdown.open { display: block; }
.ann-feed-menu-dropdown form { margin: 0; }
.ann-feed-menu-dropdown a,
.ann-feed-menu-dropdown button {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 10px;
    border-radius: 6px;
    font-size: 13px;
    text-align: left;
    color: #3a352f;
    text-decoration: none;
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
}
.ann-feed-menu-dropdown a:hover,
.ann-feed-menu-dropdown button:hover { background: rgba(0,0,0,0.05); }
.ann-feed-menu-dropdown button.danger { color: #a83a2e; }
.ann-feed-menu-dropdown button.danger:hover { background: rgba(168,58,46,0.08); }
.ann-feed-body { padding: 0 16px 4px; }
.ann-feed-title { margin: 0 0 4px; font-size: 15px; font-weight: 700; color: #17140f; }
.ann-feed-excerpt { font-size: 13.5px; line-height: 1.5; color: #3a352f; margin: 0 0 4px; }
/* --- Rozwijanie posta W MIEJSCU zamiast przejścia na announcement_view.php
   — zgłoszenie użytkownika 21.09.2026 ze screenshotem: "jak klikniesz w
   'zobacz więcej' to rozwinął się cały post a nie otwierał w nowym oknie".
   PIERWSZA wersja tej naprawy obcinała tekst czysto WIZUALNIE (CSS
   max-height + gradientowe wygaszenie na dole, pełny HTML renderowany
   zawsze w całości) — po przetestowaniu zgłoszenie: "ogranicz ilość znaków
   widocznych w postach Ogłoszenia i nie rób takiego przejścia, miało być
   jak na Facebook". Teraz PHP (truncate_rich_html() w includes/helpers.php)
   NAPRAWDĘ przycina treść do stałej liczby znaków i renderuje DWIE gotowe
   wersje obok siebie — .ann-feed-excerpt-short (przycięta, "Zobacz więcej"
   doklejone od razu za urwanym tekstem, jak na Facebooku) i
   .ann-feed-excerpt-full (pełna, domyślnie [hidden]) — JS w announcements.php
   tylko przełącza, która z dwóch jest widoczna, bez żadnego mierzenia
   wysokości/gradientu. --- */
.ann-feed-excerpt-short[hidden], .ann-feed-excerpt-full[hidden] { display: none; }
.ann-feed-more {
    position: relative;
    z-index: 2;
    display: inline-block;
    margin-left: 4px;
    color: var(--accent-dark);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    background: none;
    border: none;
    padding: 0;
    font-size: 13.5px;
    cursor: pointer;
    font-family: inherit;
}
.ann-feed-more:hover { text-decoration: underline; }
.ann-feed-excerpt-full .ann-feed-more { display: block; margin: 4px 0 0; }
.ann-feed-body .announcement-photo-grid { margin-top: 8px; }
/* Dolny pasek karty (reakcje + komentarze) w JEDNYM rzędzie, jak na
   Facebooku (screenshot użytkownika 20.09.2026: ikonka+liczba po lewej,
   bez osobnych podpisanych "przycisków") — subtelna linia nad rzędem
   oddziela go od zdjęć/treści, tak jak na Facebooku. Reakcje zostają BEZ
   zmian (dowolna emotka z pikera, ten sam współdzielony .reaction-bar co w
   całym serwisie — TYLKO układ/otoczenie w feedzie Ogłoszeń się zmienia,
   patrz odpowiedź na pytanie o zasięg). Zgłoszenie użytkownika 20.09.2026:
   "ten dół postów może zróbmy jak w przykładzie w Facebook". */
.ann-feed-stats {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 14px;
    padding: 8px 16px 14px;
    border-top: 1px solid rgba(0,0,0,0.06);
    margin-top: 4px;
}
.ann-feed-stats .reaction-bar { margin-top: 0; }
/* Komentarze rozwijane W MIEJSCU (jak na Facebooku) zamiast linku do
   osobnej podstrony announcement_view.php — natywny <details>/<summary>,
   ten sam wzorzec co .material-comments-details w materials.php. Siedzi
   teraz TUTAJ, w tym samym rzędzie co reakcje (nie osobnym pełnoszerokim
   paskiem pod spodem) — dopiero PO ROZWINIĘCIU ([open]) łapie flex-basis:
   100% i "spada" na nowy, pełnoszerokościowy wiersz (dzięki flex-wrap na
   .ann-feed-stats wyżej), więc wątek komentarzy ma tyle miejsca, ile
   potrzebuje. Zgłoszenie użytkownika 20.09.2026: "kaj klikam w komentarze
   zrób jak w Facebook, a nie otwiera się osobna strona". */
.ann-feed-comments-details { flex: 0 0 auto; }
.ann-feed-comments-details[open] { flex: 1 1 100%; }
.ann-feed-comments-details summary {
    cursor: pointer;
    font-size: 12px;
    font-weight: 600;
    color: #7a746e;
    display: flex;
    align-items: center;
    gap: 6px;
    list-style: none;
}
.ann-feed-comments-details summary:hover { color: var(--accent-dark); }
.ann-feed-comments-details summary::-webkit-details-marker { display: none; }
.ann-feed-comments-body { width: 100%; padding-top: 10px; border-top: 1px solid rgba(0,0,0,0.06); margin-top: 8px; }
/* "Udostępnij" w dolnym pasku karty feedu (obok reakcji/komentarzy) — ten
   sam wygląd co .ann-feed-comments-details summary obok. Zgłoszenie
   użytkownika 24.09.2026: przycisk na widoku pojedynczego posta jest, ale
   na liście/feedzie go zabrakło. */
.ann-feed-share-btn {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 600;
    color: #7a746e;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    margin-left: auto;
}
.ann-feed-share-btn:hover { color: var(--accent-dark); }
@media (max-width: 720px) {
    .ann-feed-head { padding: 12px 12px 6px; }
    .ann-feed-body { padding: 0 12px 4px; }
    .ann-feed-stats { padding: 8px 12px 12px; }
}

.comment {
    padding: 10px 0;
    border-top: 1px solid #cfcfcf;
    font-size: 13px;
}
.comment .who { font-weight: 500; margin-right: 6px; }
.comment .when { color: #888; font-size: 11px; }
.comment-head { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }

/* Odpowiedz/Edytuj/Usuń pod komentarzem — wcześniej to były te same
   szare/różowe "pigułki" co .btn-icon gdzie indziej w serwisie (np. w
   lightboxie galerii), co pod treścią komentarza wyglądało ciężko i
   nieporządnie. Zamiast tego zwykłe, drobne linki tekstowe w stylu
   Facebooka — bez tła, bez
   ramki, tylko pogrubienie/podkreślenie na hover. Celowo NOWA klasa (nie
   zmieniamy .btn-icon), żeby nie ruszać wyglądu przycisków w innych
   miejscach serwisu, które z tej samej klasy korzystają. */
.comment-actions { display: flex; align-items: center; gap: 12px; margin-top: 6px; }
.comment-action-btn {
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    color: #6b6560;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
}
.comment-action-btn:hover { color: var(--accent-dark); text-decoration: underline; }
.comment-action-btn.danger { color: #a83a2e; }
.comment-action-btn.danger:hover { color: #7a1f1f; }

/* Formularz odpowiedzi/edycji komentarza — dawniej całość (textarea +
   przycisk Wyślij + przycisk emotki) stała w jednym rzędzie flex, więc przy
   wyższej/rozciągniętej textarece przyciski "wisiały z boku", oderwane od
   dołu pola. Teraz textarea zawsze na pełną szerokość, przyciski w osobnym
   rzędzie pod spodem
   — dokładnie ten sam układ co w głównym formularzu "Dodaj komentarz". */
.comment-reply-form, .comment-edit-form { display: none; margin-top: 8px; }
.comment-edit-form textarea { width: 100%; }
.comment-edit-form .btn-row { margin-top: 6px; align-items: center; }

/* Formularz dodawania/odpowiedzi na komentarz — styl "Facebook" (21.09.2026,
   na prośbę użytkownika, na wzór zrzutu ekranu z Facebooka): awatar + owalna
   "pigułka" z polem tekstowym, przyciskiem emotki i przyciskiem wyślij w
   jednym rzędzie, dokładnie ten sam wzorzec co .chat-composer-row w czacie
   (patrz niżej w pliku) — tam gdzie już się sprawdził. .comment-reply-form
   ma teraz DWIE role: .comment-reply-form (samo pokazywanie/ukrywanie, JS
   przełącza na display:flex — patrz includes/comment_render.php) oraz
   .comment-pill-form (właściwy układ, wspólny też z głównym formularzem
   "Dodaj komentarz", który nie jest domyślnie ukryty). Główny formularz nie
   ma klasy .comment-reply-form, więc dostaje display:flex bezpośrednio z
   .comment-pill-form poniżej. */
.comment-pill-form { display: flex; align-items: flex-end; gap: 8px; margin-top: 8px; }
.comment-reply-form.comment-pill-form { display: none; } /* nadpisuje display:flex z reguły wyżej — dopóki JS nie otworzy (patrz komentarz przy .comment-reply-form, .comment-edit-form powyżej) */
.comment-pill-form > .avatar { margin-top: 2px; }
.comment-pill-row {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: flex-end;
    gap: 4px;
    background: #fff;
    border: 1px solid #e2dcd2;
    border-radius: 20px;
    padding: 6px 6px 6px 14px;
    transition: border-color .15s, box-shadow .15s;
}
.comment-pill-row:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent); }
.comment-pill-row textarea {
    flex: 1;
    min-width: 0;
    border: none;
    background: transparent;
    resize: none;
    padding: 6px 0;
    max-height: 120px; /* dalej rośnie JS (auto-grow), powyżej tej wysokości pojawia się własny scroll zamiast rozpychania strony — patrz assets/emoji-picker.js */
    font-family: inherit;
    /* MUSI być >=16px — poniżej tego iOS Safari łapie pole jako "mały tekst"
       i przy dotknięciu/pisaniu sam dopasowuje (zooma) widok całej strony,
       więc pigułka komentarza "wyjeżdżała" poza ekran. Zgłoszenie
       użytkownika 21.09.2026 ze zrzutami z telefonu. Reguła ".comment-
       pill-row textarea" ma wyższą specyficzność niż ogólna, ratunkowa
       "textarea { font-size:16px }" z media query niżej w pliku (patrz
       identyczny komentarz przy .chat-composer-row input[type=text]
       wyżej), więc ta wcześniejsza wartość 14px wygrywała i zoom mimo
       tamtej reguły dalej występował. */
    font-size: 16px;
    line-height: 1.4;
}
/* box-shadow/border-color: none — bez tego OGÓLNA reguła "textarea:focus"
   wyżej w pliku (border-color: var(--accent); box-shadow: 0 0 0 3px ...)
   dokłada WŁASNĄ czerwoną obwódkę wprost na textarea, NAKŁADAJĄC się na
   obwódkę .comment-pill-row:focus-within wyżej (ta jest na całej pigułce,
   tamta tylko na samym polu tekstowym w środku) — dwie różne, przesunięte
   względem siebie ramki naraz. Zgłoszenie użytkownika 21.09.2026 ze
   zrzutem ekranu: "co to sie robi za ramki". */
.comment-pill-row textarea:focus { outline: none; box-shadow: none; border-color: transparent; }
/* BEZ tła/kółka (21.09.2026, zgłoszenie: "nie chce tego kółka czerwonego,
   czy ty tego nie rozumiesz, chce tylko tą ikonkę co wysłałem") — sama
   ikonka, tak samo "goła" jak przycisk-emotka obok (.comment-emoji-wrap
   .icon-btn wyżej w pliku). Kolor przez color/currentColor, nie na sztywno w
   SVG (patrz comment_pill_send_icon() w includes/comment_render.php).
   ZNALEZIONY PRAWDZIWY POWÓD, dla którego ikonka od samego początku
   "znikała" mimo poprawnego fill/koloru (zgłoszenie 21.09.2026, użytkownik
   wkleił surowy atrybut d="..." z DOM-u, dowodząc że SVG BYŁ w kodzie
   strony): brakowało tu "padding: 0". Ogólna reguła "button, .btn" dużo
   wyżej w pliku ustawia KAŻDEMU <button> na stronie "padding: 11px 18px" —
   bez własnego override to i tak wygrywało (ten przycisk nie ma nic, co by
   to nadpisywało poza width/height). Przy globalnym "* { box-sizing:
   border-box }" (patrz góra pliku) padding 18px+18px=36px na boki NA
   32-pikselowym przycisku zostawiał ZEROWĄ (albo ujemną) przestrzeń na
   samą zawartość — ikonka nie miała się dosłownie gdzie zmieścić, więc
   znikała, niezależnie od koloru/fill, które i tak by nie pomogły. Przycisk-
   emotka (.icon-btn) NIGDY nie miał tego problemu, bo już wcześniej miał
   własne "padding: 0" (patrz .icon-btn wyżej w pliku) — stąd różnica.
   width/height 28px (nie 32px) — zgłoszenie 21.09.2026: "ikonkę 'dodaj
   kom...' ciutkę niżej aby były obie wyrównane w środek poziom" — rządek
   (.comment-pill-row) wyrównuje przyciski do DOŁU (align-items: flex-end,
   żeby zostawały przy dole rosnącej textarei), więc różne wysokości pudełek
   (32px vs 28px u .icon-btn obok) przesuwały środek ikonki "wyślij" wyżej
   niż środek ikonki emotki. Te same 28px co .icon-btn — teraz oba pudełka
   mają wspólny dół I wspólny środek. */
.comment-pill-send {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--accent);
    cursor: pointer;
}
/* .comment-pill-send-sm NIE dostaje już własnych width/height — pudełko
   jest teraz takie samo (28px) jak w głównym formularzu, właśnie żeby oba
   przyciski (wyślij/emotka) zawsze miały wspólny środek niezależnie od
   rozmiaru samej ikonki w środku (patrz komentarz przy .comment-pill-send
   wyżej). Mniejszy rozmiar w formularzu odpowiedzi zostaje TYLKO na samej
   ikonce (18px zamiast 20px, patrz comment_pill_send_icon(18) w
   includes/comment_render.php). */
.comment-pill-send svg { display: block; width: 20px; height: 20px; }
.comment-pill-send-sm svg { width: 18px; height: 18px; }

/* Przycisk-emotka pod formularzem "dodaj komentarz" (trasy/wydarzenia/
   ogłoszenia/materiały). Sam piker NIE jest już (21.09.2026) dzieckiem tego
   wrappera — jest jeden wspólny, doczepiony do <body> i pozycjonowany w JS
   (patrz .comment-emoji-shared-picker niżej w pliku oraz
   assets/emoji-picker.js) — reguły niżej dotyczą więc tylko układu samego
   przycisku, nie pozycji pikera. */
.comment-emoji-wrap { position: relative; display: inline-block; }
.comment-emoji-wrap .emoji-picker { bottom: 40px; }
/* Zgłoszenie użytkownika 21.09.2026: "nie chce żadnego tła w ikonce" — samo
   szare tło .icon-btn (patrz baza klasy wyżej w pliku) zdejmujemy TYLKO
   tutaj (przy przycisku-emotce pod komentarzem), żeby nie ruszać wyglądu
   .icon-btn gdzie indziej w serwisie, gdzie to tło jest potrzebne. Kolor też
   rozjaśniony (zgłoszenie: "ikonkę emotek rozjaśnij żeby nie była taka
   intensywna") — baza .icon-btn ma ciemnoszary #444, tu jaśniejszy #a39c92
   (ten sam ciepły szary co przygaszone podpisy/hinty gdzie indziej w
   serwisie), znowu tylko w tym jednym miejscu. */
.comment-emoji-wrap .icon-btn, .comment-emoji-wrap .icon-btn:hover { background: transparent; color: #a39c92; }
/* .btn-row, który OTACZA .comment-emoji-wrap (przycisk wyślij + przycisk
   emotki) — na telefonie .comment-emoji-wrap przestaje być "kotwicą" dla
   pozycjonowania okienka (patrz reguła position:static w @media niżej w
   pliku), więc TA szersza, pełnej szerokości linia MUSI być position:relative,
   żeby okienko emotek dostało prawidłową szerokość zamiast liczyć left/right
   od wąskiego przycisku — bez tego na telefonie okienko emotek pod
   komentarzem renderowało się jako wąski, wysoki biały pasek zamiast
   siatki emotek. */
.comment-form-actions { position: relative; }
/* left:0 (okienko rozwija się W PRAWO od przycisku) potrafiło wystawać poza
   prawą krawędź ekranu i zostać obcięte przez overflow-x:hidden na <body> —
   zależnie od tego, jak daleko w prawo stoi przycisk-emotka w danej karcie.
   right:0 (rozwija się W LEWO, w stronę wnętrza karty, gdzie zawsze jest
   miejsce) jest bezpieczniejsze
   — ale TYLKO na szerszych ekranach; na wąskich i tak przejmuje pełną-szerokość
   wersja z reguły .emoji-picker niżej w pliku (ta sama, co w czacie). */
@media (min-width: 721px) {
    .comment-emoji-wrap .emoji-picker { left: auto; right: 0; }
}

/* --- Edytor tekstu --- */
.rich-content { font-size: 14px; line-height: 1.6; }
.rich-content p { margin: 0 0 10px; }
.rich-content a { color: var(--accent-dark); }
.rich-content img { max-width: 100%; border-radius: 4px; }
.rich-content table { border-collapse: collapse; width: 100%; margin: 0 0 10px; }
.rich-content table td, .rich-content table th { border: 1px solid #ccc; padding: 6px 8px; }

/* --- Kalendarz --- */
.calendar {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 6px;
}
.calendar .day-name {
    font-size: 11px;
    color: #8a847d;
    text-align: center;
    padding-bottom: 8px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
}
.calendar .day-name:nth-child(7n+6), .calendar .day-name:nth-child(7n) { color: var(--accent-dark); }
.calendar .day {
    background: #fff;
    border: 1px solid var(--card-border);
    border-radius: 6px;
    min-height: 62px;
    min-width: 0;
    padding: 7px;
    font-size: 12px;
    position: relative;
    overflow: hidden;
    transition: box-shadow .15s, transform .15s, border-color .15s;
}
.calendar .day:not(.empty):hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); border-color: rgba(0,0,0,0.1); z-index: 2; }
.calendar .day:not(.empty):nth-child(7n+6), .calendar .day:not(.empty):nth-child(7n) { background: #faf6f0; }
.calendar .day.empty { background: transparent; border-color: transparent; }
.calendar .day .num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    color: #45403a;
    font-size: 12px;
}
.calendar .day.today .num {
    background: var(--accent);
    color: #fff;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: 999px;
}
.calendar .day.has-events { border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
.calendar .day.has-events .num { color: var(--accent-dark); }
.calendar .day.today.has-events .num { color: #fff; }
/* Mini-kalendarz w panelu bocznym Wydarzeń (.ann-sidebar, render_events_
   sidebar()) — ta sama siatka .calendar, tylko mniejsze komórki i BEZ
   treści wydarzeń w środku (żadnych linków .evt) — sam numer dnia + kropka,
   jeśli tego dnia jest jakieś wydarzenie. Pełny widok z klikalnymi
   wydarzeniami żyje w popupie #event-calendar-modal (events.php),
   otwieranym przyciskiem "Powiększ" pod tym mini-widokiem. Zgłoszenie
   użytkownika 21.09.2026: "w nawigatorze był (...) kalendarz (jeśli ktoś
   będzie chciał go powiększyć to wtedy uruchomi duży, może być popup)". */
.calendar.mini { gap: 3px; }
.calendar.mini .day-name { padding-bottom: 4px; font-size: 9px; }
.calendar.mini .day {
    min-height: 30px;
    padding: 3px 2px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
}
.calendar.mini .day .num { font-size: 10px; }
.calendar.mini .day.today .num { min-width: 16px; height: 16px; padding: 0; font-size: 9.5px; }
.calendar.mini .mini-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }
.calendar.mini .day.today .mini-dot { background: #fff; }
/* Dni z kropką w mini-kalendarzu są <button>, nie <div> (klikalne — patrz
   dymek niżej) — resetujemy domyślny wygląd przycisku przeglądarki, reszta
   wyglądu (tło/ramka/zaokrąglenie) i tak dziedziczy się z .calendar .day
   wyżej, bo to te same klasy. */
.calendar.mini button.day { appearance: none; -webkit-appearance: none; margin: 0; font: inherit; color: inherit; }

/* --- Dymek mini-kalendarza Wydarzeń (kliknięcie dnia z kropką) — lista
   wydarzeń tego dnia zamiast od razu ciągnięcia do pełnego popupu miesiąca.
   Tworzony/pozycjonowany w JS (assets/event-sidebar.js), position:fixed i
   POZA .calendar .day (który ma overflow:hidden) — inaczej dymek zostałby
   obcięty przez własną komórkę dnia. Wizualnie ten sam język co
   .ann-feed-menu-dropdown (biała karta, cień, zaokrąglenie). Zgłoszenie
   użytkownika 21.09.2026: "ten mały kalendarz, ma zaznaczone kropką
   wydarzenie, może jak w nie kliknie to wyświetli kaje to sa albo duży
   kalendarz". */
.mini-calendar-popover {
    position: fixed;
    z-index: 60;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.16);
    padding: 10px;
    width: 220px;
    max-width: calc(100vw - 20px);
}
.mini-calendar-popover-header { font-size: 12px; font-weight: 700; color: #171512; margin-bottom: 6px; }
.mini-calendar-popover-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 4px;
    border-radius: 6px;
    text-decoration: none;
    color: #3a352f;
    font-size: 12.5px;
}
.mini-calendar-popover-item:hover { background: rgba(0,0,0,0.05); }
.mini-calendar-popover-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.mini-calendar-popover-time { font-weight: 600; color: #7a746e; flex-shrink: 0; }
.mini-calendar-popover-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mini-calendar-popover-footer {
    display: block;
    width: 100%;
    margin-top: 6px;
    padding-top: 8px;
    border: none;
    border-top: 1px solid rgba(0,0,0,0.06);
    background: none;
    font-size: 12px;
    font-weight: 600;
    color: var(--accent-dark);
    text-align: left;
    cursor: pointer;
    font-family: inherit;
}
.mini-calendar-popover-footer:hover { text-decoration: underline; }
.year-label {
    font-size: 12px;
    font-weight: 700;
    color: #8a847d;
    margin: 16px 0 8px;
    text-transform: uppercase;
    letter-spacing: .04em;
}
.year-label:first-child { margin-top: 0; }
.year-archive {
    margin-top: 14px;
    border: 1px solid rgba(0,0,0,0.08);
    border-radius: 6px;
    padding: 2px 12px 12px;
}
.year-archive summary {
    cursor: pointer;
    padding: 10px 2px;
    font-weight: 700;
    font-size: 13px;
    color: #6b665f;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 6px;
}
.year-archive summary::-webkit-details-marker { display: none; }
.year-archive summary::before {
    content: '';
    width: 0; height: 0;
    border-left: 5px solid #8a847d;
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform .12s;
    margin-right: 2px;
}
.year-archive[open] summary::before { transform: rotate(90deg); }
.year-archive summary .count-badge {
    background: rgba(0,0,0,0.06);
    color: #6b665f;
    border-radius: 999px;
    padding: 1px 8px;
    font-size: 11px;
    font-weight: 700;
}
.year-archive[open] summary { margin-bottom: 2px; }

/* Strona Pomoc (help.php) — pasek wyszukiwania + akordeon pytań, wzorowane
   na .year-archive wyżej. Zgłoszenie użytkownika 05.09.2026: "brakuje nam
   takiego działu jak pomoc". */
.help-search-wrap { position: relative; display: flex; align-items: center; }
.help-search-wrap svg { position: absolute; left: 13px; color: #8a847d; pointer-events: none; }
.help-search-wrap input[type=text] { width: 100%; padding-left: 38px; margin: 0; }
.help-category h2 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Kategorie zwijalne (21.09.2026, zgłoszenie użytkownika: "Kategorie też
   zwinąć") — nagłówek kategorii to teraz <summary>, stylowany tak, żeby
   wyglądał identycznie jak dawny zwykły <h2> (ta sama waga/rozmiar), plus
   własna strzałka analogiczna do .faq-item niżej, tylko trochę większa,
   żeby wizualnie odróżnić poziom "kategoria" od poziomu "pytanie". */
.help-category-details { border: none; padding: 0; margin: 0; }
.help-category-details > summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 17px;
    font-weight: 700;
    margin: 0 0 4px;
}
.help-category-details > summary::-webkit-details-marker { display: none; }
.help-category-details > summary::before {
    content: '';
    width: 0; height: 0;
    border-left: 6px solid #8a847d;
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
    transition: transform .12s;
    flex-shrink: 0;
}
.help-category-details[open] > summary::before { transform: rotate(90deg); }
.help-category-details > .help-category-description { margin-top: 8px; }
/* Zwijanie kategorii w EDYTORZE Pomocy (Narzędzia/Panel -> Pomoc) —
   21.09.2026, patrz komentarz przy .help-cat-toggle-btn w tools.php.
   Zwykły <button> (nie <summary>), bo nagłówek kategorii ma też uchwyt do
   przeciągania (Sortable.js) — klik na <summary> przechwyciłby też kliki na
   uchwyt. Domyślnie rozwinięte; .is-collapsed dokłada JS po kliknięciu. */
.help-cat-header { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.help-cat-toggle-btn {
    background: none;
    border: none;
    padding: 4px;
    margin: 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.help-cat-toggle-arrow {
    display: inline-block;
    width: 0; height: 0;
    border-left: 6px solid #8a847d;
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
    transition: transform .12s;
}
.help-cat-card.is-collapsed .help-cat-toggle-arrow { transform: rotate(0deg); }
.help-cat-card:not(.is-collapsed) .help-cat-toggle-arrow { transform: rotate(90deg); }
.help-cat-card.is-collapsed .help-cat-body { display: none; }
.faq-item {
    border: 1px solid rgba(0,0,0,0.08);
    border-radius: 6px;
    padding: 2px 12px;
    margin-top: 8px;
}
.faq-item summary {
    cursor: pointer;
    padding: 10px 2px;
    font-weight: 600;
    font-size: 13.5px;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 8px;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::before {
    content: '';
    width: 0; height: 0;
    border-left: 5px solid #8a847d;
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform .12s;
    flex-shrink: 0;
}
.faq-item[open] summary::before { transform: rotate(90deg); }
.faq-answer { padding: 0 2px 12px 20px; font-size: 13.5px; color: #6b665f; line-height: 1.55; }
.help-contact-card { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }

.rsvp-details {
    margin-top: 8px;
    border: 1px solid rgba(0,0,0,0.08);
    border-radius: 6px;
    padding: 2px 12px 10px;
}
.rsvp-details summary {
    cursor: pointer;
    padding: 9px 2px;
    font-weight: 600;
    font-size: 13px;
    color: #6b665f;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 6px;
}
.rsvp-details summary::-webkit-details-marker { display: none; }
.rsvp-details summary::before {
    content: '';
    width: 0; height: 0;
    border-left: 5px solid #8a847d;
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform .12s;
    margin-right: 2px;
}
.rsvp-details[open] summary::before { transform: rotate(90deg); }
.rsvp-details summary .count-badge {
    background: rgba(0,0,0,0.06);
    color: #6b665f;
    border-radius: 999px;
    padding: 1px 8px;
    font-size: 11px;
    font-weight: 700;
}
.calendar .day .evt {
    display: block;
    margin-top: 4px;
    font-size: 10px;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    border-radius: 999px;
    padding: 2px 7px;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}
.calendar .day .evt-more {
    display: block;
    margin-top: 3px;
    font-size: 10px;
    color: var(--accent-dark);
    font-weight: 700;
    padding: 0 2px;
}
.calendar-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 16px;
    gap: 8px;
}
.calendar-nav h2 { margin: 0; font-size: 16px; text-transform: capitalize; }
.calendar-nav-title { display: flex; align-items: center; gap: 10px; }
.calendar-nav-today {
    font-size: 11px;
    font-weight: 700;
    color: var(--accent-dark);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    padding: 4px 10px;
    border-radius: 999px;
    text-decoration: none;
    white-space: nowrap;
}
.calendar-nav-today:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.calendar-nav-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    min-width: 34px;
    padding: 0;
    flex-shrink: 0;
    border-radius: 50%;
    background: #eee9e2;
    color: #45403a;
    text-decoration: none;
    flex-shrink: 0;
    transition: background .15s, transform .15s;
}
.calendar-nav-btn:hover { background: var(--accent); color: #fff; transform: translateY(-1px); }

/* --- Lightbox --- */
.lightbox {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.9);
    z-index: 999;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.lightbox.open { display: flex; }
.lightbox-body { display: flex; flex-direction: column; align-items: center; gap: 10px; max-width: 100%; }
.lightbox img, .lightbox video { max-width: 100%; max-height: 78vh; border-radius: 5px; display: block; }
/* PDF/TXT/CSV z czatu (assets/chat-lightbox.js) — przeglądarka renderuje je
   sama w iframe, więc w przeciwieństwie do img/video potrzebuje jawnej
   szerokości (domyślna szerokość <iframe> to raptem ~300px) i białego tła
   (biały dokument na czarnym tle lightboksa). */
.lightbox iframe { width: min(92vw, 900px); height: 78vh; max-width: 100%; border: none; border-radius: 5px; background: #fff; display: block; }
.lightbox .close {
    position: absolute;
    top: 18px;
    right: 24px;
    color: #fff;
    background: none;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
}
.lightbox-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 46px;
    height: 46px;
    min-width: 46px;
    padding: 0;
    border-radius: 50%;
    background: rgba(255,255,255,0.12);
    color: #fff;
    border: none;
    font-size: 26px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background .15s;
    z-index: 2;
}
.lightbox-nav:hover { background: var(--accent-dark); transform: translateY(-50%); }
.lightbox-nav.prev { left: 16px; }
.lightbox-nav.next { right: 16px; }
.lightbox-meta {
    display: flex;
    align-items: center;
    gap: 14px;
    color: #ddd;
    font-size: 13px;
}
.lightbox-meta .muted { color: #999; }
.lightbox-meta .btn-icon { background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; }
.lightbox-meta .btn-icon:hover { background: var(--accent-dark); }
/* Na telefonie zdjęcie (max-height:78vh) + odstęp + ten pasek z autorem
   razem potrafiły być wyższe niż faktycznie widoczna wysokość ekranu (np.
   pionowe zdjęcie + pasek adresu przeglądarki zjadający kawałek viewportu),
   a .lightbox nie miał żadnego przewijania — pasek z "Dodał: ..." wychodził
   poza widoczny obszar i był niewidoczny, mimo że działał na desktopie, gdzie
   jest więcej miejsca w pionie. Zgłoszenie użytkownika 25.08.2026: "nie ma w
   galerii (w lightboxie) autora zdjęcia, jest tylko na desktop". Mniejsze
   zdjęcie + możliwość przewinięcia jako zabezpieczenie na wszelki wypadek. */
@media (max-width: 720px) {
    /* align-items:flex-start (zamiast center z desktopu) — wyśrodkowana,
       przewijana zawartość, która nie mieści się w ekranie, w niektórych
       przeglądarkach nie daje się doprzewijać do samej góry (znany kwirk
       flexboxa) — od góry przewija się zawsze poprawnie, do samego dołu
       (czyli właśnie do paska z autorem). */
    .lightbox { padding: 12px; overflow-y: auto; align-items: flex-start; }
    .lightbox-body { margin: auto 0; }
    .lightbox img, .lightbox video { max-height: 60vh; }
    .lightbox iframe { width: 92vw; height: 60vh; }
    .lightbox-meta { flex-wrap: wrap; row-gap: 6px; font-size: 12px; }
}

/* Siatka zdjęć w ogłoszeniu, jak w poście na Facebooku — układ zależy od
   liczby zdjęć (1/2/3/4/5+), z nakładką "+N" na ostatnim kafelku, gdy jest
   ich więcej niż 5 widocznych. Stałe wysokości kontenera (zamiast aspect-ratio na
   pojedynczych kafelkach) trzymają całą siatkę w jednej, przewidywalnej
   wysokości niezależnie od proporcji wgranych zdjęć — dokładnie tak, jak
   robi to Facebook. Klikalne kafelki otwierają lekki lightbox (patrz
   assets/announcement-gallery.js), który używa tych samych klas .lightbox*
   co galeria (assets/style.css), więc bez dublowania stylów. */
.announcement-photo-grid { display: grid; gap: 4px; border-radius: 10px; overflow: hidden; margin-bottom: 14px; }
.announcement-photo-grid .apg-item { position: relative; display: block; overflow: hidden; background: #1a1816; cursor: pointer; }
.announcement-photo-grid .apg-item img,
.announcement-photo-grid .apg-item video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* count-1 (jedno dołączone zdjęcie) — ŚWIADOMIE bez aspect-ratio:16/9 +
   object-fit:cover, w przeciwieństwie do count-2 i wyżej. Przy siatce 2+
   zdjęć twarde przycięcie do wspólnej wysokości wygląda naturalnie (to
   wyraźnie "kafelki", jak na Facebooku) — ale przy JEDNYM zdjęciu, zwłaszcza
   pionowym (np. zbliżenie jednej części, telefonem), ten sam crop obcinał
   większość kadru i naciągał zdjęcie do szerokiego, poziomego pudełka, co
   przy niższej rozdzielczości źródła dawało dodatkowo widoczne rozmycie —
   zgłoszenie użytkownika 26.08.2026 (screen: tłok wyglądający "nieładnie").
   Zamiast tego: naturalne proporcje zdjęcia, żadnego sztucznego
   powiększania (brak wymuszonej szerokości na <img>), tylko górny limit
   wysokości, żeby bardzo pionowe zdjęcie nie zdominowało całej strony. */
.announcement-photo-grid.count-1 { grid-template-columns: 1fr; }
.announcement-photo-grid.count-1 .apg-item {
    aspect-ratio: auto;
    max-height: 480px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.announcement-photo-grid.count-1 .apg-item img {
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 480px;
    object-fit: contain;
}
/* count-2 i wyżej: WYSOKOŚĆ SIATKI liczona proporcjonalnie do jej własnej
   szerokości (aspect-ratio), a nie sztywnymi pikselami jak wcześniej —
   przy sztywnym "height: 240px" na szerokiej karcie (desktop) siatka
   wychodziła bardzo płaska i szeroka, jak spłaszczone prostokąty (zgłoszenie
   użytkownika 22.09.2026, screen: "Poszukuje tłoki z korbowodami"). Facebook
   trzyma dokładnie tę samą proporcję niezależnie od szerokości karty —
   aspect-ratio robi to samo, plus max-height jako górny limit, żeby na
   bardzo szerokich ekranach siatka też nie zrobiła się absurdalnie wysoka.
   Jawne "width: 100%" obok aspect-ratio + max-height jest tu KONIECZNE —
   Safari ma znany błąd, że przy jednoczesnym aspect-ratio i max-height
   zawęża SZEROKOŚĆ do wartości wynikającej z proporcji przy przyciętej
   wysokości, zamiast rozciągnąć się na całą szerokość karty i przyciąć
   tylko wysokość — stąd "dziura" po prawej stronie siatki na Safari
   (zgłoszenie użytkownika 22.09.2026, screen z postu "Blues zawsze
   towarzyszył motocyklistom"). Wymuszone width:100% to obchodzi. */
.announcement-photo-grid.count-2 { grid-template-columns: 1fr 1fr; aspect-ratio: 4 / 3; max-height: 420px; width: 100%; }
.announcement-photo-grid.count-3 { grid-template-columns: 1.4fr 1fr; grid-template-rows: 1fr 1fr; aspect-ratio: 4 / 3; max-height: 420px; width: 100%; }
.announcement-photo-grid.count-3 .apg-item:first-child { grid-row: 1 / span 2; }
.announcement-photo-grid.count-4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; aspect-ratio: 4 / 3; max-height: 420px; width: 100%; }
.announcement-photo-grid.count-5plus { grid-template-columns: repeat(6, 1fr); grid-template-rows: 1fr 1fr; aspect-ratio: 4 / 3; max-height: 420px; width: 100%; }
.announcement-photo-grid.count-5plus .apg-item:nth-child(1) { grid-column: span 3; }
.announcement-photo-grid.count-5plus .apg-item:nth-child(2) { grid-column: span 3; }
.announcement-photo-grid.count-5plus .apg-item:nth-child(3),
.announcement-photo-grid.count-5plus .apg-item:nth-child(4),
.announcement-photo-grid.count-5plus .apg-item:nth-child(5) { grid-column: span 2; }
.apg-more-overlay {
    position: absolute; inset: 0; background: rgba(0,0,0,0.55);
    color: #fff; display: flex; align-items: center; justify-content: center;
    font-size: 26px; font-weight: 700;
}
/* Ikona odtwarzania na kafelku wideo w siatce ogłoszenia — wyśrodkowane
   kółko, ten sam pomysł co .gallery-grid .play-circle, tylko nałożone na
   już wygenerowaną miniaturkę (klatkę z filmu) zamiast zastępować cały
   kafelek. */
.apg-play-overlay {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 42px; height: 42px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0,0,0,0.55); color: #fff;
    transition: opacity .2s ease;
}
/* Kafelek wideo w siatce cicho odtwarza się sam, kiedy wjedzie w widoczny
   obszar ekranu (assets/feed-video-autoplay.js dodaje/zdejmuje tę klasę na
   .apg-item, dokładnie jak w feedzie Facebooka) — ikonka "play" chowa się na
   czas odtwarzania, żeby nie zasłaniać podglądu, i wraca, kiedy wideo jest
   zatrzymane (poza ekranem albo jeszcze nie doszła jego kolej). */
.apg-item.is-playing .apg-play-overlay { opacity: 0; }

/* Kółko-spinner dopisywane do przycisku "Zapisz/Publikuj/Dodaj" na czas
   wysyłki formularza (assets/submit-loading.js, assets/video-thumb-form.js)
   — żeby było widać, że coś się dzieje, a nie że strona "zawiesiła się"
   (dotyczy zwłaszcza formularzy z załącznikami na słabszym zasięgu).
   currentColor = kolor tekstu przycisku, więc pasuje do każdego wariantu
   (btn / btn secondary itd.) bez osobnego malowania. 09.08.2026. */
.sog-btn-spinner {
    display: inline-block; width: 13px; height: 13px; margin-left: 8px;
    border: 2px solid currentColor; border-top-color: transparent;
    border-radius: 50%; vertical-align: -2px; opacity: 0.8;
    animation: sog-btn-spin 0.7s linear infinite;
}
@keyframes sog-btn-spin { to { transform: rotate(360deg); } }

/* Wizualnie ukryty <input type="file"> zastąpiony własnym przyciskiem
   (assets/video-thumb-form.js) — nie display:none, żeby .click() wywołane
   z JS dalej otwierało natywny picker w każdej przeglądarce. 10.08.2026. */
.sog-file-input-visually-hidden {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* Siatka podglądu zdjęć/wideo WYBRANYCH do wysłania (przed publikacją) —
   assets/video-thumb-form.js. Ten sam wzór co .announcement-gallery-edit-*
   (już wgrane zdjęcia w edycji), żeby wyglądały spójnie. 10.08.2026. */
.sog-picker-grid { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 4px; }
.sog-picker-grid:empty { margin: 0; }
.sog-picker-item { position: relative; width: 84px; }
.sog-picker-thumb {
    width: 84px; height: 84px; border-radius: 6px; overflow: hidden;
    background: var(--bg-2); display: flex; align-items: center; justify-content: center;
}
.sog-picker-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sog-picker-video-badge { font-size: 11px; color: var(--text-chrome-muted); text-align: center; padding: 4px; }
.sog-picker-remove {
    position: absolute; top: 3px; right: 3px; width: 20px; height: 20px; border-radius: 50%;
    border: none; cursor: pointer; background: rgba(0,0,0,0.65); color: #fff;
    font-size: 13px; line-height: 1; display: flex; align-items: center; justify-content: center; padding: 0;
}
.sog-picker-remove:hover { background: #a83a2e; }
.sog-picker-size {
    font-size: 10px; color: var(--text-chrome-muted); text-align: center; margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Plik przekraczający limit (8 MB zdjęcie / 50 MB wideo) — czerwona obwódka
   miniatury + czerwony, zawijający się opis zamiast wyszarzonego, żeby było
   widać PRZED wysyłką, że to i tak zostanie odrzucone. 10.08.2026. */
.sog-picker-item-over-limit .sog-picker-thumb { outline: 2px solid #a83a2e; outline-offset: 1px; }
.sog-picker-item-over-limit .sog-picker-size {
    color: #a83a2e; font-weight: 600; white-space: normal; overflow: visible; text-overflow: clip;
}
@media (max-width: 640px) {
    .announcement-photo-grid.count-1 .apg-item,
    .announcement-photo-grid.count-1 .apg-item img { max-height: 340px; }
    /* aspect-ratio z reguły wyżej skaluje się już sama do węższej karty na
       telefonie — tu tylko niższy górny limit, żeby nie było za wysoko. */
    .announcement-photo-grid.count-2, .announcement-photo-grid.count-3,
    .announcement-photo-grid.count-4, .announcement-photo-grid.count-5plus { max-height: 320px; }
}

/* Miniaturki galerii dodatkowych zdjęć w formularzu edycji ogłoszenia —
   każda z małym przyciskiem "usuń" w rogu. */
.announcement-gallery-edit-grid { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 4px; }
.announcement-gallery-edit-item { position: relative; width: 84px; height: 84px; border-radius: 6px; overflow: hidden; }
.announcement-gallery-edit-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.announcement-gallery-edit-item form { position: absolute; top: 3px; right: 3px; margin: 0; }
.announcement-gallery-edit-item button {
    width: 20px; height: 20px; border-radius: 50%; border: none; cursor: pointer;
    background: rgba(0,0,0,0.65); color: #fff; font-size: 12px; line-height: 1;
    display: flex; align-items: center; justify-content: center; padding: 0;
}
.announcement-gallery-edit-item button:hover { background: #a83a2e; }

/* Karta "Kolor motywu" w profile.php — zgłoszenie użytkownika 28.08.2026:
   "nie każdy lubi czarny i taki beżowy... żeby user mogli w profilu sobie
   wybrać dla całego serwisu". Zwykłe radio (ukryte, patrz input niżej) +
   dwukolorowe kółko-podgląd (ten sam gradient co w html[data-theme-color]
   wyżej w tym pliku — musi się zgadzać z theme_color_options() w
   includes/helpers.php, inaczej podgląd tu przestanie odpowiadać
   rzeczywistemu kolorowi po zapisaniu) + zaznaczenie obwódką akcentu, gdy
   wybrane — bez żadnego JS, czyste :checked + sąsiedztwo w CSS. */
.theme-color-options { display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 4px; }
.theme-color-option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    border-radius: 8px;
    border: 1px solid var(--card-border);
    cursor: pointer;
    font-size: 13px;
    font-weight: normal;
}
.theme-color-option:hover { border-color: rgba(0,0,0,0.2); }
/* :has() zamiast JS — obwódka reaguje na wybór OD RAZU (przed kliknięciem
   "Zapisz"), nie dopiero po przeładowaniu strony. */
.theme-color-option:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); font-weight: 600; }
.theme-color-option input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; }
.theme-color-swatch { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12); }

/* --- Kadrowanie zdjęcia --- */
.crop-modal {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.8);
    z-index: 1000;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.crop-modal.open { display: flex; }
.crop-modal-inner {
    background: var(--card-bg);
    border-radius: 8px;
    padding: 20px;
    max-width: 620px;
    width: 100%;
}
.crop-modal-inner h3 { margin: 0 0 12px; font-size: 15px; color: #17140f; }
.crop-canvas-wrap {
    max-height: 55vh;
    overflow: hidden;
    background: #111;
    border-radius: 5px;
}
.crop-canvas-wrap img { display: block; max-width: 100%; }

/* --- Materiały: podgląd całego gotowego posta (tekst + grafika bez
   przycięcia), zanim ktoś zdecyduje się skopiować/udostępnić. --- */
.material-preview-modal {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.75);
    z-index: 1000;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.material-preview-modal.open { display: flex; }
.material-preview-inner {
    position: relative;
    background: #fff;
    color: #171512;
    border-radius: 8px;
    max-width: 520px;
    width: 100%;
    max-height: 88vh;
    overflow-y: auto;
}
.material-preview-inner .close {
    /* position:sticky, NIE absolute — .material-preview-inner sam się
       przewija (overflow-y:auto, długi tekst posta potrafi być wyższy niż
       max-height:88vh). Przy position:absolute przycisk był POZYCJONOWANY
       WZGLĘDEM przewijanej zawartości, więc przy przewinięciu w dół (żeby
       przeczytać dłuższy tekst) wyjeżdżał poza widoczny obszar razem z
       resztą — zgłoszone jako "nie ma zamknij", z koniecznością zamykania
       całej aplikacji (PWA na pulpicie), żeby się stąd wydostać. sticky
       zostaje przyklejony do góry widocznego, przewijanego obszaru przez
       cały czas. */
    position: sticky;
    top: 10px;
    margin: 10px 10px -32px auto;
    z-index: 2;
    width: 32px;
    height: 32px;
    min-width: 32px;
    padding: 0;
    border-radius: 50%;
    background: rgba(0,0,0,0.55);
    color: #fff;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}
.material-preview-inner .close:hover { background: var(--accent-dark); }
/* Podgląd pliku (PDF/TXT/CSV) — ten sam modal co podgląd posta na FB, tylko
   szerszy (dokument potrzebuje więcej miejsca niż mockup posta) i z
   <iframe> zamiast siatki tekst+obrazek. */
.file-preview-inner { max-width: 900px; }
/* Popup pełnego kalendarza Wydarzeń (#event-calendar-modal, events.php) —
   ten sam modal co podgląd posta/pliku, tylko trochę szerszy, żeby siatka
   7 dni w tygodniu nie robiła się zbyt ciasna. */
.event-calendar-modal-inner { max-width: 640px; }
.file-preview-frame-wrap {
    margin: 8px 16px 0;
    width: calc(100% - 32px);
    height: 70vh;
    border: 1px solid #dddfe2;
    border-radius: 6px;
    overflow: hidden;
    background: #fff;
}
.file-preview-frame-wrap iframe { width: 100%; height: 100%; border: none; display: block; }
.file-preview-inner .btn-row { margin: 10px 16px 16px !important; }
@media (max-width: 640px) {
    .file-preview-frame-wrap { height: 60vh; }
}

/* Próba renderowania PDF przez PDF.js na <canvas> została wycofana — wynik
   był nieczytelny i bez żadnych narzędzi (bez powiększania, zaznaczania
   tekstu, drukowania). PDF-y teraz otwierają się od razu w nowej karcie
   przeglądarki (link
   target="_blank" w materials.php), co oddaje sterowanie natywnej
   przeglądarce PDF systemu — z pinch-zoom, drukowaniem itd. Stąd brak tu już
   żadnych reguł .file-preview-pdf-container.
*/

/* --- Materiały: przypięte pliki, licznik "przeczytano", komentarze pod
   plikami --- */
.list-item.material-pinned { border-left: 3px solid var(--accent); background: color-mix(in srgb, var(--accent) 5%, transparent); }

/* Bogatsza karta pliku — więcej "powietrza", zaokrąglenie, delikatny cień po
   najechaniu i kolorowa plakietka rozszerzenia zamiast płaskiego wiersza
   listy. Klasa .list-item zostaje na elemencie (typografia .title/.meta
   dziedziczona stamtąd), .material-file-card tylko dokłada layout. */
.list-item.material-file-card {
    display: block;
    padding: 14px 16px;
    border-radius: 10px;
    transition: box-shadow .12s, border-color .12s;
}
.list-item.material-file-card:hover { box-shadow: 0 2px 10px rgba(0,0,0,0.06); border-color: color-mix(in srgb, var(--accent) 25%, transparent); }

/* ShadowManiak, Etap 1 — lista redaktorów do zaznaczenia (checkboxy) przy
   tworzeniu/edycji audycji. Przewijana, żeby długa lista zatwierdzonych
   kont nie rozciągała formularza w nieskończoność (wyszukiwarka nad listą
   i tak filtruje po nicku/imieniu/e-mailu — patrz filterUserList() w JS). */
.host-picker-list { max-height: 260px; overflow-y: auto; padding-right: 2px; }
.host-picker-item { padding: 8px 12px; margin-bottom: 6px; }
.host-picker-item .meta { font-size: 11px; color: #7a746e; margin-left: 4px; }
.material-file-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}
.material-file-main { display: flex; align-items: center; gap: 10px; min-width: 0; }
.material-file-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.material-file-ext {
    display: inline-flex;
    align-items: center;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .03em;
    color: #fff;
    padding: 2px 7px;
    border-radius: 5px;
    margin-right: 6px;
    vertical-align: middle;
}
.material-pin-badge {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--accent-dark);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-radius: 20px;
    padding: 2px 8px;
    margin-right: 6px;
    vertical-align: middle;
}
.material-read-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    width: 100%;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid rgba(0,0,0,0.06);
}
.material-read-row .material-read-active { border-color: var(--accent); color: var(--accent-dark); font-weight: 600; }
.material-read-who { display: inline-block; }
.material-read-who summary { cursor: pointer; font-size: 12px; color: #8a847c; list-style: none; }
.material-read-who summary::-webkit-details-marker { display: none; }
.material-comments-details { width: 100%; margin-top: 6px; }
.material-comments-details summary {
    cursor: pointer;
    font-size: 12px;
    font-weight: 600;
    color: #6b655d;
    padding: 6px 0;
    display: flex;
    align-items: center;
    gap: 6px;
    list-style: none;
}
.material-comments-details summary::-webkit-details-marker { display: none; }
.material-comments-body {
    padding: 10px 0 4px;
    border-top: 1px solid rgba(0,0,0,0.06);
    margin-top: 4px;
}
.material-preview-hint {
    margin: 40px 16px 0;
    font-size: 11px;
    color: #8a847c;
}
.fb-mock {
    margin: 8px 16px 0;
    border: 1px solid #dddfe2;
    border-radius: 6px;
    background: #fff;
    overflow: hidden;
}
.fb-mock-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 12px 6px;
}
.fb-mock-name { margin: 0; font-size: 14px; font-weight: 700; color: #050505; }
.fb-mock-meta {
    margin: 1px 0 0;
    font-size: 12px;
    color: #65676b;
    display: flex;
    align-items: center;
    gap: 4px;
}
.fb-mock-meta .icon { width: 10px; height: 10px; }
.fb-mock-text {
    margin: 4px 0 10px;
    padding: 0 12px;
    font-size: 14px;
    line-height: 1.5;
    color: #050505;
    white-space: pre-line;
}
.fb-mock img {
    width: 100%;
    max-height: 42vh;
    object-fit: cover;
    display: block;
}
.fb-mock-actions {
    display: flex;
    gap: 18px;
    padding: 8px 12px;
    border-top: 1px solid #eee;
    font-size: 13px;
    color: #65676b;
    font-weight: 600;
}
.fb-mock-actions span { display: flex; align-items: center; gap: 5px; }
.material-preview-body { padding: 14px 16px 16px; }
.material-preview-inner .btn.secondary { color: #171512; border-color: #d8d3cc; }
.material-preview-inner .btn.secondary:hover { background: rgba(0,0,0,0.04); }

/* --- Pasek narzędzi nad treścią posta na Facebooka (emotikony, Unicode
   pogrubienie/kursywa, szybki hasztag) --- */
.fb-toolbar {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
}
.fb-toolbar-emoji-wrap { position: relative; display: inline-block; }
.emoji-popover {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 20;
    background: #fff;
    color: #171512;
    border: 1px solid #d8d3cc;
    border-radius: 5px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    padding: 8px;
    width: 220px;
    grid-template-columns: repeat(6, 1fr);
    gap: 2px;
}
.emoji-popover.open { display: grid; }
.emoji-popover-item {
    cursor: pointer;
    font-size: 18px;
    text-align: center;
    padding: 4px;
    border-radius: 4px;
    line-height: 1;
}
.emoji-popover-item:hover { background: #f0ece4; }

/* --- Delikatna ramka wokół map Google (trasa/jazda/wydarzenie) — inaczej
   mapa zlewa się wizualnie z tłem karty. --- */
.map-frame {
    border: 1px solid rgba(0,0,0,0.1);
    overflow: hidden;
}

/* --- Pulpit --- */
.dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 14px;
}
.stat-card {
    /* Delikatny ciepły, pomarańczowy gradient zamiast płaskiego kremu — na
       tyle subtelny, żeby liczby i etykiety zostały czytelne. */
    background: linear-gradient(150deg, #fdf1e4, var(--card-bg) 60%);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    padding: 18px;
    transition: transform .15s;
    text-decoration: none;
    color: #171512;
    display: block;
    min-width: 0;
    /* Safari (w tym iOS) ma znany problem z rozciąganiem <a> w gridzie do
       pełnej wysokości wiersza bez jawnego height/align-self — bez tego
       krótsza karta w tym samym rzędzie (np. "Ogłoszeń") kończyła się
       wyżej niż sąsiednia dłuższa (ShadowManiak z dodatkową treścią),
       zostawiając nierówne, "postrzępione" krawędzie na telefonie.
       Zgłoszenie użytkownika 25.08.2026. */
    height: 100%;
    align-self: stretch;
    box-sizing: border-box;
    /* Kontener na dużą, przyciętą ikonę w tle (patrz .stat-card-bgicon
       niżej) — position:relative daje jej punkt odniesienia,
       overflow:hidden przycina ją do zaokrąglonych rogów karty zamiast
       wystawać poza nie. Lifting 27.08.2026 (zgłoszenie użytkownika: "mało
       tu ilustracji i dynamiki... jakąś ikonkę powiększoną na całe
       okienko"). Naprawa tego samego dnia: BEZ z-index:0 tutaj ikona
       (z-index:-1) w ogóle nie była widoczna — bez własnego kontekstu
       sztaplowania na .stat-card, ujemny z-index "uciekał" poniżej całej
       karty (i całej reszty strony) zamiast zostać tylko za liczbą/
       podpisem. Dokładnie ten sam mechanizm, przed którym ostrzega
       komentarz przy .stat-card.stat-card-shadowmaniak niżej w tym pliku —
       przeoczony przy pierwszym wdrożeniu tej funkcji. */
    position: relative;
    z-index: 0;
    overflow: hidden;
    /* Wyższa karta na desktopie — bez tego duża ikona w tle (patrz
       .stat-card-bgicon niżej) nie miała gdzie "oddychać" i wychodziła
       jako mały, przycięty skrawek zamiast wyraźnej ilustracji na całą
       kafelkę. Zgłoszenie użytkownika 27.08.2026: "ikonki miały być na
       całą kafelkę... brzydko wygląda takie małe (na wysokość), miało być
       ładniej a nie zniknąć". */
    min-height: 160px;
}
.stat-card:hover { transform: translateY(-3px); }
.stat-card .stat-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 7px;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    margin-bottom: 10px;
}
.stat-card .num { font-size: 30px; font-weight: 800; color: #171512; line-height: 1.1; }
.stat-card .label { font-size: 12px; color: #6b6560; margin-top: 4px; }
/* Dwie animowane kafelki na Statystykach (tools.php, zakładka statystyki) —
   zgłoszenie użytkownika 09.09.2026 ("a może wymyślisz jakieś dwie kafelki z
   animacją :)"). Pierścień "zaangażowania" rysuje się przy wejściu na stronę
   (stroke-dashoffset animowany od pełnego obwodu do docelowej wartości,
   policzonej w PHP i przekazanej przez zmienną CSS --ring-offset), a
   gwiazdka przy kafelce "Nowych wpisów w tym miesiącu" delikatnie pulsuje
   cały czas, żeby ta kafelka odróżniała się żywością od reszty statycznych
   liczb w tym samym rzędzie. */
/* Pierścień powiększony z 68 na 84px i procent pogrubiony/powiększony
   09.09.2026 (zgłoszenie użytkownika: "może ten procent dać do środka tego
   pierścienia") — liczba już TECHNICZNIE siedziała w środku (position:
   absolute; inset:0 na .stat-ring-num względem .stat-ring-wrap), ale przy
   17px na małym 68px pierścieniu wyglądała na oderwaną/przypadkową zamiast
   czytelnie "w środku" — większy pierścień + większa, pogrubiona liczba
   naprawiają to wizualnie bez zmiany samej logiki pozycjonowania.

   15.09.2026: to załatwiało czytelność, ale NIE prawdziwe centrowanie —
   align-items:baseline (niżej) wyrównuje bazowe linie "92" i "%" względem
   siebie, ale nie centruje CAŁEJ tej pary w pionie w 84px kwadracie, tylko
   sadowi ją tam, gdzie wypadnie linia bazowa — w efekcie liczba wychodziła
   POZA górną krawędź pierścienia zamiast siedzieć w środku. Zgłoszenie
   użytkownika: "nachodzi wynik na okrąg, czy tak ma być czy powinien być w
   środku". Naprawa: align-items:center (prawdziwe centrowanie w pionie)
   zamiast baseline. */
.stat-ring-wrap { position: relative; width: 84px; height: 84px; margin-bottom: 10px; }
.stat-ring { transform: rotate(-90deg); }
.stat-ring-bg { fill: none; stroke: color-mix(in srgb, var(--accent) 15%, transparent); stroke-width: 7; }
.stat-ring-fg {
    fill: none;
    stroke: var(--accent);
    stroke-width: 7;
    stroke-linecap: round;
    stroke-dasharray: 214;
    stroke-dashoffset: 214;
    animation: stat-ring-draw 1.4s cubic-bezier(.2,.8,.2,1) .2s forwards;
}
@keyframes stat-ring-draw { to { stroke-dashoffset: var(--ring-offset); } }
.stat-ring-num {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1px;
    line-height: 1;
}
.stat-ring-num .num { font-size: 22px; font-weight: 800; }
.stat-ring-num span { font-size: 13px; font-weight: 700; color: #171512; }
.stat-icon-pulse { animation: stat-icon-pulse 1.8s ease-in-out infinite; }
@keyframes stat-icon-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } }
@media (prefers-reduced-motion: reduce) {
    .stat-ring-fg { animation: none; stroke-dashoffset: var(--ring-offset); }
    .stat-icon-pulse { animation: none; }
}

/* Duża ikona-ilustracja w rogu kafelka Pulpitu — zastępuje mały kwadracik
   .stat-icon na trzech głównych kartach (Trasy/Wydarzenia/Ogłoszenia).
   z-index:-1 gwarantuje, że zostaje POD liczbą i podpisem niezależnie od
   kolejności w DOM (ujemny z-index maluje się tuż nad tłem karty, ale pod
   każdą zwykłą, niepozycjonowaną treścią). Bardzo niska krycie (0.16-0.18)
   i lekki obrót — ma być "delikatna ilustracja", nie dominujący element.
   Zgłoszenie użytkownika 27.08.2026 (po odrzuceniu wcześniejszej wersji z
   ukośnymi paskami w tle: "ale może nie paseczki, może jakąś ikonkę
   powiększoną na całe okienko"). */
.stat-card-bgicon {
    position: absolute;
    right: -18px;
    bottom: -22px;
    z-index: -1;
    pointer-events: none;
    /* Biała, nie kolorowa — od 27.08.2026 karty same są kolorowe (patrz
       .stat-card-tint-* niżej, ten sam pomysł co karta ShadowManiak: kolor
       ikony trafia na TŁO kafelka, a ikona w rogu zostaje jasna). Wcześniej
       kolorowa ikona na jasnym, kremowym tle "gubiła się" w trybie jasnym
       — zgłoszenie użytkownika. */
    color: #fff;
    opacity: 0.2;
}
.stat-card-bgicon svg { width: 150px; height: 150px; display: block; }
.stat-card-bgicon-blue { transform: rotate(-8deg); }
.stat-card-bgicon-pink { transform: rotate(6deg); }
.stat-card-bgicon-amber { transform: rotate(-6deg); }
.stat-card-bgicon-teal { transform: rotate(8deg); }
.stat-card-bgicon-purple { transform: rotate(-5deg); }
.stat-card-bgicon-green { transform: rotate(7deg); }

/* Kolorowe tło kafelka zamiast kremowego — kolor przeniesiony z ikony na
   samo tło karty, tak jak od początku wyglądała karta ShadowManiak (celowo
   NIE ruszana, wzorzec do naśladowania). Zgłoszenie użytkownika 27.08.2026:
   "ładnie wygląda ShadowManiak... może w jego kierunku iść z pozostałymi".
   Gradient 150deg jasny->ciemny, taki sam kąt/schemat jak
   .stat-card.stat-card-shadowmaniak niżej, dla spójności całego rzędu. */
/* Selektory BEZ wymogu ".stat-card" z przodu (celowo, od 19.09.2026) — te
   same klasy .stat-card-tint-* są teraz używane też na malutkich "kółkach"
   podglądu koloru w Narzędziach (.tile-color-swatch, patrz niżej), które nie
   mają (i nie powinny mieć) klasy .stat-card na sobie. Wcześniejszy wariant
   ".stat-card.stat-card-tint-blue" nie łapał tych kółek wcale (były
   niekolorowe) — naprawione przy okazji dodania własnego koloru. */
.stat-card-tint-blue { background: linear-gradient(150deg, #2f74c4, #123457 75%); border-color: #5b9bdb; }
.stat-card-tint-blue .num { color: #fff; }
.stat-card-tint-blue .label { color: rgba(222, 236, 255, 0.9); }
.stat-card-tint-pink { background: linear-gradient(150deg, #c14a76, #611f37 75%); border-color: #e07ea3; }
.stat-card-tint-pink .num { color: #fff; }
.stat-card-tint-pink .label { color: rgba(255, 226, 238, 0.9); }
.stat-card-tint-amber { background: linear-gradient(150deg, #c9791a, #5f3705 75%); border-color: #f0b458; }
.stat-card-tint-amber .num { color: #fff; }
.stat-card-tint-amber .label { color: rgba(255, 238, 218, 0.9); }
/* Karta "Rozegranych partii Statków" — morski, zielono-turkusowy odcień
   (nie powtarza żadnego z powyższych), pasujący do tematyki gry, ten sam
   150deg gradient co reszta rzędu. Zgłoszenie użytkownika 18.09.2026. */
.stat-card-tint-teal { background: linear-gradient(150deg, #1f8f7a, #0c3a30 75%); border-color: #5cc9b0; }
.stat-card-tint-teal .num { color: #fff; }
.stat-card-tint-teal .label { color: rgba(214, 255, 244, 0.9); }
/* Dwa dodatkowe gotowe kolory do wyboru w kafelkach statystyk Pulpitu
   (Narzędzia -> Wspólne ustawienia -> "Kafelki statystyk Pulpitu") —
   zgłoszenie użytkownika 18.09.2026: "a możemy dodawać i edytować takie
   kafelki... kolor, kolejność, itd.". */
.stat-card-tint-purple { background: linear-gradient(150deg, #7c4fd1, #2f1a5e 75%); border-color: #b79ce8; }
.stat-card-tint-purple .num { color: #fff; }
.stat-card-tint-purple .label { color: rgba(232, 222, 255, 0.9); }
.stat-card-tint-green { background: linear-gradient(150deg, #3f9142, #16401a 75%); border-color: #7ecb82; }
.stat-card-tint-green .num { color: #fff; }
.stat-card-tint-green .label { color: rgba(222, 255, 224, 0.9); }
/* "Własny kolor" (color picker w Narzędziach, patrz dashboard_stat_tile_
   color_css() w includes/helpers.php) — TA klasa tylko ustawia kolor
   tekstu/ikony na biały, samo tło/obwódkę dostaje kafelek przez inline
   style="..." wyliczony w PHP (gradient jasny->ciemny z jednego wybranego
   koloru, ten sam wzorzec 150deg/75% co gotowe kolory wyżej). Zgłoszenie
   użytkownika 19.09.2026: "dodaj możliwość dodawania motywu kolorów".
   .stat-card-bgicon-custom dostaje tylko obrót, bez koloru (ikona w tle jest
   i tak zawsze biała, patrz .stat-card-bgicon). */
.stat-card-tint-custom .num { color: #fff; }
.stat-card-tint-custom .label { color: rgba(255, 255, 255, 0.9); }
.stat-card-bgicon-custom { transform: rotate(-7deg); }

/* Wybór koloru kafelka w formularzu dodawania/edycji (tools.php) — małe,
   klikalne kółka w kolorach dokładnie tych samych gradientów co
   .stat-card-tint-* wyżej, żeby admin widział NAPRAWDĘ to, co dostanie, a
   nie oderwaną od reszty nazwę koloru. Radio input schowany (.visually-
   hidden), zaznaczenie pokazane obwódką na sąsiadującym kółku/inputcie
   koloru. Ostatnia opcja to natywny <input type="color"> zamiast gotowego
   kółka — to i JEST wybór "własnego" koloru, nie tylko jego podgląd. */
.tile-color-picker { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; align-items: center; }
.tile-color-option { display: flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer; }
.tile-color-swatch {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    border: 2px solid transparent;
    box-shadow: 0 0 0 1px rgba(0,0,0,0.12);
    flex-shrink: 0;
}
.tile-color-option input[type="radio"]:checked + .tile-color-swatch { border-color: var(--accent); }
.tile-color-custom-input {
    width: 26px;
    height: 26px;
    padding: 0;
    border: 2px solid transparent;
    border-radius: 6px;
    cursor: pointer;
    box-shadow: 0 0 0 1px rgba(0,0,0,0.12);
    background: none;
}
.tile-color-option input[type="radio"]:checked + .tile-color-custom-input { border-color: var(--accent); }

/* Podgląd ikon do wyboru dla kafelka statystyk (tools.php) — ten sam
   wzorzec radio+swatch co wybór koloru wyżej, tylko swatch pokazuje
   PRAWDZIWĄ ikonę zamiast koloru, więc admin widzi, co wybiera, zamiast
   zgadywać po nazwie z listy rozwijanej. Zgłoszenie użytkownika 19.09.2026:
   "w ikonkach przydałby sie podglad ich do wyboru a nie lista". */
.tile-icon-picker { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
.tile-icon-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    width: 58px;
    font-size: 10px;
    text-align: center;
    color: var(--text-chrome-muted, #8a847d);
    cursor: pointer;
}
.tile-icon-swatch {
    width: 34px;
    height: 34px;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--card-border);
    color: #6b6560;
    border: 2px solid transparent;
}
.tile-icon-swatch-auto { background: transparent; border: 2px dashed var(--card-border); }
.tile-icon-option input:checked + .tile-icon-swatch {
    border-color: var(--accent);
    background: rgba(224, 18, 26, 0.1);
    color: var(--accent-dark, var(--accent));
}

/* ShadowManiak — piąta karta w rzędzie statystyk Pulpitu, celowo
   wyróżniona na czerwono (nie kolejny kafelek z karuzelą niżej), z małym
   odtwarzaczem "na żywo" wprost w karcie. Nie jest samo <a> (jak reszta
   .stat-card) — środek to osobny link do shadowmaniak.php, a przycisk
   play/pauza (render_listen_player()) leży obok, żeby kliknięcie w niego
   NIE przenosiło na dział (nie da się zagnieździć klikalnego przycisku w
   linku bez tego konfliktu). Zgłoszenie użytkownika 25.08.2026 ("bardziej
   na czerwono", "dodajmy taki mały player").*/
.stat-card.stat-card-shadowmaniak {
    background: linear-gradient(150deg, #b8342c, #6e120e 75%);
    border-color: #e2574f;
    padding: 14px 18px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    /* Element siatki (.dashboard-grid) domyślnie ma min-width: auto, czyli
       nie chce się zrobić węższy niż jego zawartość — przy dłuższym tekście
       (tytuł odcinka w .label, plakietka "na X-Stacji") to rozpychało kartę
       poza jej kolumnę na wąskich ekranach ("rozjechane kafelki" na
       telefonie, zgłoszenie 25.08.2026). */
    min-width: 0;
}
.stat-card-shadowmaniak-link {
    display: block;
    text-decoration: none;
    color: inherit;
    min-width: 0;
    flex: 1 1 auto;
}
.stat-card.stat-card-shadowmaniak .stat-icon {
    background: rgba(255, 255, 255, 0.2);
    margin-bottom: 0;
}
/* Ta sama duża ikona-ilustracja co na pozostałych trzech kartach (patrz
   .stat-card-bgicon wyżej), tylko biała — pasuje do czerwonego tła tej
   karty. Mały mikrofon w .stat-card-shadowmaniak-head ZOSTAJE (jest częścią
   napisu "Słuchaj nas na X-Stacji", nie samą dekoracją) — to dodatkowy,
   większy, przycięty do rogu. */
.stat-card.stat-card-shadowmaniak .stat-card-bgicon { color: #fff; opacity: 0.18; transform: rotate(10deg); }
.stat-card.stat-card-shadowmaniak .stat-card-title { font-size: 17px; font-weight: 800; color: #fff; line-height: 1.2; margin-top: 4px; }
/* Atrybucja stacji radiowej wprost na karcie — bez logo X-Stacji (patrz
   komentarz przy .audycja-info-box wyżej i decyzja użytkownika 25.08.2026:
   tekst zamiast hotlinkowania cudzego znaku graficznego bez zgody). Etykieta
   obok ikonki mikrofonu zamiast plakietki przy tytule — zgłoszenie
   użytkownika 25.08.2026 ("dodać obok ikonki mikrofonu tę etykietę"). */
.stat-card-shadowmaniak-head {
    display: flex;
    align-items: center;
    gap: 8px;
}
.stat-card-shadowmaniak-headline {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.2px;
    color: rgba(255, 255, 255, 0.82);
}
.stat-card.stat-card-shadowmaniak .label { color: rgba(255, 232, 229, 0.9); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Ikona w tle wraca na swoje pierwotne miejsce (dolny-prawy róg, jak na
   pozostałych trzech kartach) — zamiast jej przesuwać, na desktopie
   POWIĘKSZONY i mniej przezroczysty przycisk play sam wybija się na jej tle
   (mniejsza przezroczystość = bardziej kryjące, nieprzezroczyste tło).
   Zgłoszenie użytkownika 27.08.2026: "ikonka wcześniej była ok, przywróć, a
   zwiększ w desktop sam przycisk player... nadaj mu mniejszą
   transparetność". Mobilna wersja (mniejszy, bardziej przezroczysty
   przycisk) wraca niżej w sekcji @media (max-width: 720px) — tam
   użytkownik potwierdził, że już działa dobrze. */
.stat-card.stat-card-shadowmaniak .audycja-play-btn {
    flex-shrink: 0;
    align-self: center;
    background: rgba(255, 255, 255, 0.42);
    padding: 0;
    width: 46px;
    height: 46px;
    justify-content: center;
}
.stat-card.stat-card-shadowmaniak .audycja-play-btn:hover { background: rgba(255, 255, 255, 0.55); }
.stat-card.stat-card-shadowmaniak .audycja-play-label { display: none; }
.stat-card.stat-card-shadowmaniak .audycja-play-btn .audycja-play-icon,
.stat-card.stat-card-shadowmaniak .audycja-play-btn .audycja-pause-icon { background: transparent; width: 100%; height: 100%; }
/* Sam znaczek (trójkąt/pauza) jest narysowany na sztywno w rozmiarze 14px
   (patrz icon('play', 14) w index.php) — powiększenie samego kółka wyżej
   NIE powiększa znaczka w środku, bo SVG ma wpisane własne width/height.
   Trzeba wprost nadpisać wymiary <svg>. Ten sam mechanizm i naprawa co przy
   .audycja-live-card niżej w tym pliku. Zgłoszenie użytkownika 27.08.2026:
   "powiększyłeś przycisk i OK, ale jest mała strzałka". */
.stat-card.stat-card-shadowmaniak .audycja-play-btn .audycja-play-icon svg,
.stat-card.stat-card-shadowmaniak .audycja-play-btn .audycja-pause-icon svg {
    width: 22px;
    height: 22px;
}
.stat-card.stat-card-shadowmaniak .audycja-play-btn audio { display: none; }

/* --- Hero/powitanie na pulpicie: awatar + dynamiczne powitanie zależne od
   pory dnia + rząd szybkich akcji, żeby pulpit czuł się jak "centrum
   dowodzenia", a nie tylko nagłówek nad listami. --- */
.welcome-hero {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 26px 4px 6px;
}
.welcome-hero .avatar {
    width: 54px;
    height: 54px;
    font-size: 20px;
}
.welcome-hero .avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
.welcome h1 { font-size: 25px; margin: 0 0 3px; font-weight: 700; color: var(--text-hero); }
.welcome p { color: var(--text-hero-muted); font-size: 13px; margin: 0; }

/* Poziom/tytuł przy powitaniu na pulpicie — druga wersja, świadomie cicha
   i mniej rzucająca się w oczy niż wcześniejszy wariant. Zwykły, mały tekst
   pod powitaniem, bez tła/cienia/animacji, tylko emoji się lekko wyróżnia
   rozmiarem. */
.activity-badge-subtle {
    display: inline-block;
    margin-top: 5px;
    font-size: 12px;
    color: var(--text-hero-muted);
    text-decoration: none;
    opacity: .9;
    transition: opacity .15s;
}
.activity-badge-subtle:hover { opacity: 1; text-decoration: underline; }

/* Tytuł honorowy (Prezes/Zastępca itd.) doklejony przez member_link() do
   nazwiska wszędzie w serwisie — sam emoji, bez dodatkowego tła, żeby nie
   rywalizował wizualnie z treścią wokół (czat, komentarze, listy). */
.honorary-badge { font-size: .95em; }

/* --- Kamienie milowe: baner "nowe osiągnięcie" na pulpicie, żeby świętować
   zdobyte kamienie milowe. Ciepły, świąteczny akcent (podobny gradient co
   ranking), ale osobna, jednorazowa
   karta — znika po prostu przy następnym wejściu (nie jest zapisywana jako
   "trzeba pokazać znowu"), bo powiadomienie w dzwoneczku i tak zostaje. */
.milestone-banner {
    display: flex;
    flex-direction: column;
    gap: 6px;
    background: linear-gradient(135deg, #fff4e2, #ffe8c7);
    border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
    border-radius: 8px;
    padding: 12px 16px;
    margin-bottom: 16px;
    font-size: 13px;
    /* Ten div NIE ma klasy .card, więc bez jawnego koloru dziedziczyłby
       jasny kolor tekstu z body (--text-chrome, myślany pod ciemne tło) —
       na jasnym, kremowym tle banera wychodził wtedy prawie niewidoczny,
       "wyblakły" tekst. */
    color: #171512;
}
.milestone-banner-item { display: flex; align-items: center; gap: 8px; }
.milestone-banner-emoji { font-size: 20px; }
.milestone-banner-item .muted { color: #6b6560; }

/* --- Wybornik ikon (Narzędzia -> Kamienie milowe) — przycisk z podglądem +
   wyskakująca siatka ikon do klikania, patrz assets/icon-picker.js. Ten sam
   "przycisk-przełącznik + popup" wzorzec co .emoji-picker, ale mniejsza,
   jedna siatka bez zakładek (ikon jest tylko kilkanaście). Prośba
   użytkownika 02.08.2026: "jak mam wybierać ikonki skoro są na sztywno" —
   zwykły <select> z samym tekstem nie pokazywał, jak dana ikona wygląda. */
.icon-picker-field { position: relative; display: inline-block; }
.icon-picker-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: #fff;
    border: 1px solid #d8d3cc;
    border-radius: 5px;
    padding: 7px 10px;
    cursor: pointer;
    color: #171512;
}
.icon-picker-toggle:hover { background: rgba(0,0,0,0.03); }
.icon-picker-preview { display: inline-flex; }
.icon-picker-popup {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 5;
    grid-template-columns: repeat(5, 1fr);
    gap: 4px;
    background: #fff;
    border-radius: 7px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    padding: 8px;
    width: 210px;
    max-height: 260px;
    overflow-y: auto;
}
.icon-picker-popup.open { display: grid; }
.icon-picker-option {
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: 1px solid transparent;
    border-radius: 5px;
    padding: 8px 6px;
    cursor: pointer;
    color: #45403a;
}
.icon-picker-option:hover { background: rgba(0,0,0,0.06); }
.icon-picker-option.selected { background: color-mix(in srgb, var(--accent) 16%, transparent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent-dark); }

/* --- Checklist onboardingowy (uzupełnij profil / dodaj pierwszą trasę /
   dołącz do czatu, z małą nagrodą punktową za każdy krok) — znika z
   pulpitu, gdy wszystkie kroki są zrobione (patrz
   $showOnboardingChecklist w index.php). --- */
.onboarding-checklist-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.onboarding-checklist-head h2 { margin: 0; }
.onboarding-checklist-head form { margin: 0; }
.onboarding-dismiss {
    background: none;
    border: none;
    color: #8a847d;
    font-size: 12px;
    cursor: pointer;
    padding: 4px 6px;
    border-radius: 5px;
}
.onboarding-dismiss:hover { background: rgba(0,0,0,0.05); color: #171512; }
.onboarding-step {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 0;
    border-bottom: 1px solid rgba(0,0,0,0.06);
}
.onboarding-step:last-child { border-bottom: none; }
.onboarding-step-check {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 2px solid rgba(0,0,0,0.15);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: transparent;
}
.onboarding-step.done .onboarding-step-check {
    background: #3fb950;
    border-color: #3fb950;
    color: #fff;
}
.onboarding-step-label { flex: 1; font-size: 13px; }
.onboarding-step.done .onboarding-step-label { color: #6b6560; text-decoration: line-through; }

/* --- Cotygodniowe podsumowanie — zwykła karta na pulpicie, w tym samym
   stylu co reszta bocznej kolumny, żeby nie konkurowała wizualnie z
   rankingiem tuż pod spodem.
   Lifting 28.08.2026 (zgłoszenie użytkownika: "aby było spójne z
   całością", po odświeżeniu kolorowych kafelków statystyk i Rankingu
   aktywności wyżej na pulpicie). Kluczowe liczby (nowe trasy/wydarzenia/
   ogłoszenia/członkowie/aktywni) trafiają jako małe, kolorowe "kafelki" na
   górze karty (numer na górze, podpis pod spodem — dokładnie układ z
   zaakceptowanego podglądu "Wariant B", bez ikony w środku pigułki, żeby nie
   robiło się za gęsto) — te same kolory co .stat-card-tint-* wyżej w tym
   pliku, żeby cały pulpit mówił jednym językiem barw. Reszta (awanse,
   urodziny, kto jest zalogowany teraz) zostaje zwykłą, tekstową listą pod
   spodem — to pojedyncze zdarzenia z imionami, nie liczby, więc nie pasują
   do kafelka.
   Poprawka tego samego dnia: doszła też duża ikona w tle rogu karty (ten
   sam patent co .leaderboard-bgicon w Rankingu aktywności wyżej), żeby obie
   karty w tej kolumnie wyglądały spójnie, zgodnie z uwagą użytkownika ("zrób
   w tle też ikonkę aby było spójne do nowego design"). --- */
.card.weekly-digest {
    position: relative;
    z-index: 0;
    overflow: hidden;
}
.weekly-digest-bgicon {
    position: absolute;
    right: -34px;
    bottom: -36px;
    z-index: -1;
    pointer-events: none;
    /* Zgłoszenie użytkownika 28.08.2026: "ikonka w tle okna, wieksza i
       delikatniejszy kolor (ciut za ciemna)" — jaśniejszy --accent zamiast
       --accent-dark plus niższa krycie. */
    color: var(--accent);
    opacity: 0.12;
    transform: rotate(-8deg);
}
.weekly-digest-bgicon svg { width: 210px; height: 210px; display: block; }
.weekly-stats-row { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 12px; }
.weekly-stat-pill {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    /* Powiększone 28.08.2026 (zgłoszenie użytkownika: "ciutkę wieksze te
       okienka... wysze i wieksza czcionka z liczbą") — było 8px/6px
       paddingu i 17px na numer, teraz kafelek ma więcej "oddechu" i numer
       wyraźnie rzuca się w oczy. */
    padding: 14px 8px;
    border-radius: 8px;
    font-size: 11px;
    text-align: center;
    flex: 1 1 70px;
    min-width: 64px;
}
.weekly-stat-pill-num { font-size: 24px; font-weight: 800; line-height: 1; }
/* Kolory kafelków przyciemnione 28.08.2026 (zgłoszenie użytkownika:
   "okienka ciutkie ciemniejsze") — było zbyt blado, teraz wyraźniej widać
   kolor przy większym kafelku. Obwódka (tego samego dnia, kolejne
   zgłoszenie: "dodaj obry, ciutkę ciemniejszy niż ich własny kolor tła...
   bo teraz troche wyglada jak kreksy kolorowe") — ten sam wzorzec co
   .stat-card-tint-* wyżej w tym pliku (kolor obwódki wyraźnie odróżnia się
   od wypełnienia), tu tylko odwrotnie: jaśniejsze tło + ciemniejsza od
   niego obwódka, zamiast ciemnego tła i jaśniejszej obwódki. */
.weekly-stat-pill-blue { background: #cfe6f9; border: 1px solid #97c6ec; color: #185fa5; }
.weekly-stat-pill-pink { background: #f6d7e4; border: 1px solid #e6a8c3; color: #993556; }
.weekly-stat-pill-amber { background: #f5e1bd; border: 1px solid #e4bd7c; color: #854f0b; }
.weekly-stat-pill-green { background: #dcedc7; border: 1px solid #b6d792; color: #3b6d11; }
.weekly-stat-pill-teal { background: #cceee6; border: 1px solid #8ed6c7; color: #1a6e60; }
/* Cienkie linie między pozycjami — dodane 28.08.2026 z tego samego powodu
   co w .weekly-top-active-item/.leaderboard-compact-row niżej/wyżej
   (użytkownik: "łatwiej sie czyta wynik" z liniami) — dla spójności całej
   karty rozszerzone też na tę, środkową listę (status/awanse/urodziny),
   żeby wszystkie trzy sekcje karty czytały się tak samo. */
.weekly-digest-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; }
.weekly-digest-list li { font-size: 12.5px; display: flex; align-items: center; gap: 6px; padding: 6px 2px; border-bottom: 1px solid var(--card-border); }
.weekly-digest-list li:last-child { border-bottom: none; }
/* Urodziny wyróżnione cieplejszym tłem na całej szerokości wiersza — było
   nierozróżnialne od zwykłych awansów, ginęło w liście. Wariant "dziś"
   (a nie "w tym tygodniu") dostaje mocniejszy gradient + delikatny puls,
   żeby dzień urodzin był od razu widoczny bez czytania daty. Zgłoszenie
   użytkownika 18.09.2026: "możemy jakoś wyróżnić bardziej". */
.weekly-digest-list li.is-birthday {
    background: linear-gradient(90deg, rgba(255, 183, 3, .28), rgba(255, 183, 3, .05));
    border-radius: 6px;
    padding: 6px 8px;
    font-weight: 600;
    margin: 2px 0;
    border-bottom: none;
}
.weekly-digest-list li.is-birthday.is-today {
    background: linear-gradient(90deg, rgba(251, 133, 0, .38), rgba(255, 183, 3, .1));
    animation: birthday-row-pulse 2.2s ease-in-out infinite;
}
@keyframes birthday-row-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(251, 133, 0, 0); }
    50% { box-shadow: 0 0 0 3px rgba(251, 133, 0, .16); }
}
@media (prefers-reduced-motion: reduce) {
    .weekly-digest-list li.is-birthday.is-today { animation: none; }
}

/* "Najaktywniejsi w tym tygodniu" — było kilka gołych .badge obok siebie
   (zawijały się brzydko, nic nie mówiły o miejscu w rankingu). Przeprojekt
   28.08.2026 (zgłoszenie użytkownika: "jeszcze popracowałbym nad
   Najaktywniejsi w tym tygodniu"): mała, pionowa mini-lista z tą samą
   złotą/srebrną/brązową odznaką rangi co w karcie Rankingu wyżej
   (.leaderboard-compact-rank.rank-1/2/3, celowo reużyte 1:1, żeby obie
   karty mówiły tym samym językiem), zamiast płaskich pigułek. */
.weekly-top-active-label { margin: 14px 0 6px; font-size: 12px; }
.weekly-top-active { display: flex; flex-direction: column; gap: 0; }
/* Kreski pod każdą pozycją 28.08.2026 (zgłoszenie użytkownika: "zrób linie
   pod każdym jak w rankingu łatwiej sie czyta wynik") — dokładnie ten sam
   patent co .leaderboard-compact-row w karcie Rankingu wyżej. */
.weekly-top-active-item {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12.5px;
    padding: 6px 2px;
    border-bottom: 1px solid var(--card-border);
}
.weekly-top-active-item:last-child { border-bottom: none; }
.weekly-top-active-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.weekly-top-active-points { flex-shrink: 0; color: var(--accent-dark); font-size: 11px; font-weight: 600; }

/* --- Karta "Subskrybuj kalendarz" w Wydarzeniach — czytelny layout pod
   telefon: duży przycisk na górze, potem link do skopiowania, na końcu
   instrukcje rozbite na wiersze per platforma (zamiast jednego zbitego
   akapitu, który na wąskim ekranie robił się nieczytelny). 10.08.2026. --- */
/* Zwykły rozmiar przycisku (jak "Dodaj wydarzenie" niżej) na desktopie —
   pełna szerokość jest tylko na telefonie, patrz @media (max-width: 720px)
   niżej w pliku. */
.calendar-subscribe-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}
.calendar-link-label { font-size: 12px; color: #8a847d; font-weight: 600; margin: 18px 0 6px; }
.calendar-link-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.calendar-link-row input {
    flex: 1;
    min-width: 0;
    font-size: 16px; /* >=16px — blokada auto-zoomu iOS, patrz komentarz przy .modal-inner input[type=text] */
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.calendar-platform-list { list-style: none; margin: 12px 0 0; padding: 0; }
.calendar-platform-item {
    display: flex;
    gap: 10px;
    padding: 10px 0;
    border-bottom: 1px solid rgba(0,0,0,0.06);
}
.calendar-platform-item:last-child { border-bottom: none; }
.calendar-platform-icon {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    color: var(--accent-dark);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.calendar-platform-body { flex: 1; min-width: 0; }
.calendar-platform-name { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
.calendar-platform-steps { font-size: 12.5px; color: #6b6560; line-height: 1.55; }

/* --- Quizy (quizzes.php / quiz_take.php / quiz_admin.php) — patrz obszerny
   komentarz w includes/helpers.php. Karty w tym samym płaskim, kremowym
   stylu co reszta serwisu (.card), bez zdjęć w tle (w przeciwieństwie do
   .discover-tile) — quiz nie ma z czego wziąć miniaturki. 10.08.2026. --- */
/* Baner-zachęta do quizu na event_view.php, tuż po kliknięciu "Jadę" —
   patrz $_GET['rsvp'] w event_view.php. Zgłoszenie 10.08.2026. */
.quiz-rsvp-nudge {
    display: flex;
    align-items: center;
    gap: 10px;
    background: rgba(240,153,74,0.1);
    border: 1px solid rgba(240,153,74,0.35);
    border-radius: 10px;
    padding: 12px 14px;
    margin-bottom: 14px;
    text-decoration: none;
    color: inherit;
    font-size: 13.5px;
    transition: background .12s;
}
.quiz-rsvp-nudge:hover { background: rgba(240,153,74,0.18); }
.quiz-rsvp-nudge-icon {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
/* Bramka quizu przed zapisem "Jadę" (events.required_quiz_id) — ten sam
   ciepły, pomarańczowy język co .quiz-rsvp-nudge wyżej (to zachęta do nauki,
   nie kara/błąd, więc świadomie NIE czerwone). Zgłoszenie 10.08.2026. */
.quiz-gate-notice {
    display: flex;
    align-items: center;
    gap: 10px;
    background: rgba(240,153,74,0.1);
    border: 1px solid rgba(240,153,74,0.35);
    border-radius: 10px;
    padding: 12px 14px;
    margin-bottom: 14px;
    font-size: 13px;
    color: #7a5a2e;
}
.quiz-gate-notice .icon { color: var(--accent-dark); flex-shrink: 0; }
.quiz-gate-notice a { color: var(--accent-dark); font-weight: 600; text-decoration: underline; }
.quiz-gate-locked-btn {
    text-decoration: none;
    background: rgba(240,153,74,0.14);
    color: var(--accent-dark);
    border: 1px dashed rgba(240,153,74,0.5);
}
.quiz-gate-locked-btn:hover { background: rgba(240,153,74,0.22); }

/* ShadowManiak, Etap 1 — panel gość/redaktorzy/link przy wydarzeniu typu
   "audycja". Ten sam kompaktowy wzorzec co .quiz-gate-notice wyżej (jedna
   płaska ramka, nie "karta w karcie"), tylko w czerwonym akcencie audycji
   zamiast pomarańczowego. Zgłoszenie użytkownika 24.08.2026 — pierwsza
   wersja z zagnieżdżoną .card wyglądała ciężko i nieporządnie. */
.audycja-info-box {
    display: flex;
    flex-direction: column;
    gap: 10px;
    background: rgba(226,87,79,0.08);
    border: 1px solid rgba(226,87,79,0.3);
    border-radius: 10px;
    padding: 14px 16px;
    margin: 12px 0 16px;
    font-size: 13px;
    line-height: 1.5;
    color: #5c1616;
}
.audycja-info-box strong { color: #9c2a23; }
/* Osadzony player (Mixcloud/YouTube iframe albo zwykły <audio>) — patrz
   render_listen_player() w includes/helpers.php. */
/* Ciemne tło pod iframe'em (Mixcloud/YouTube) — dopóki widget się nie
   załaduje w pełni, widać TO tło w przerwach między jego własnymi etapami
   renderowania, zamiast jasnego tła karty. Zgłoszenie użytkownika
   25.08.2026: "przy wejściu do wydarzenia migocze ramka z playerem
   Mixcloud" — ciemne tło (zbliżone do samego widgetu) maskuje część tego
   migotania, reszta to już wnętrze obcego iframe'a, którego nie ostylujemy. */
.audycja-player { margin: 2px 0; border-radius: 8px; overflow: hidden; line-height: 0; background: #181818; }
.audycja-player iframe { display: block; border: 0; border-radius: 8px; }
.audycja-player-video iframe { aspect-ratio: 16/9; height: auto; }
/* Nagranie wgrane wprost na serwer (events.recording_video_path) — zwykły
   <video controls>, nie iframe, ale ten sam wrapper/rozmiar co embed z
   YouTube dla spójnego wyglądu. Zgłoszenie użytkownika 27.08.2026. */
.audycja-player-video video { display: block; width: 100%; aspect-ratio: 16/9; height: auto; background: #000; }
/* Komunikat po nieudanym ładowaniu wideo (patrz assets/episode-video.js) —
   ten sam rozmiar/proporcje co sam <video>, żeby layout nie "skakał" przy
   przełączaniu między odtwarzaczem a komunikatem błędu. Zgłoszenie
   użytkownika 28.08.2026 (stalowanie na niestabilnym łączu komórkowym, bez
   żadnego komunikatu). */
.audycja-player-video .video-error-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    aspect-ratio: 16/9;
    background: #000;
    color: rgba(255,255,255,.85);
    text-align: center;
    padding: 20px;
}
.audycja-player-video .video-error-state p { margin: 0; font-size: 13px; }
/* Własny przycisk play/pauza (zamiast surowego <audio controls>, którego
   nie da się spójnie ostylować w każdej przeglądarce) — patrz
   toggleListenPlayer() w event_view.php i render_listen_player() w
   helpers.php. Ten sam gradient co .stat-icon/.list-item-thumb-icon w
   reszcie serwisu, żeby wyglądał jak "u siebie", nie jak naklejka. */
.audycja-play-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    align-self: flex-start;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    border-radius: 999px;
    padding: 8px 16px 8px 8px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    user-select: none;
    transition: filter .12s;
}
.audycja-play-btn:hover { filter: brightness(1.08); }
.audycja-play-btn .audycja-play-icon,
.audycja-play-btn .audycja-pause-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: rgba(255,255,255,0.25);
    flex-shrink: 0;
}
.audycja-play-btn.playing { filter: brightness(1.05); }

/* ShadowManiak — wizytówki redaktorów w dziale (shadowmaniak.php). Wiersz
   zamiast małej siatki kafelków (Etap 2) — od kiedy doszło bio (Etap 4,
   shadowmaniak_hosts, zgłoszenie użytkownika 25.08.2026: "tak łyso... sam
   coś dopiszę"), tekst potrzebuje więcej miejsca niż wąska 110px kolumna.
   Awatar+imię+rola żyją teraz w OSOBNYM górnym pasku (.audycja-host-header,
   flex obok siebie, tylko na wysokości awatara), a bio jest osobnym blokiem
   POD spodem, na PEŁNĄ szerokość karty — bez lewego marginesu. Wcześniejsze
   podejście z float:left (awatar, tekst go "opływa") dawało nierówny,
   postrzępiony lewy brzeg akapitu (pierwsze linijki węższe obok awatara,
   kolejne pełnej szerokości) — zgłoszenie użytkownika 21.09.2026: "niech nie
   opływa tylko podnieś [awatar], a tekst idzie w całości do lewej". Jeszcze
   wcześniejsza, sztywna flexboxowa dwukolumnówka (awatar | tekst przez całą
   wysokość wiersza) zostawiała pod niskim awatarem pustą lukę z lewej —
   też odrzucone (to samo zgłoszenie). */
.audycja-host-row { padding: 14px 0; }
.audycja-host-row + .audycja-host-row { border-top: 1px solid var(--card-border); }
.audycja-host-header { display: flex; gap: 16px; align-items: center; }
.audycja-host-photo-link { flex-shrink: 0; display: block; position: relative; }
.audycja-host-photo-link .avatar,
.audycja-host-photo-link img.audycja-host-photo {
    width: 68px; height: 68px; font-size: 22px; border-radius: 50%; object-fit: cover; display: block;
}
.audycja-host-photo-badge {
    position: absolute; bottom: -2px; right: -2px;
    width: 22px; height: 22px; border-radius: 50%;
    background: #e2574f; border: 2px solid var(--card-bg);
    display: flex; align-items: center; justify-content: center; color: #fff;
}
.audycja-host-info { min-width: 0; }
.audycja-host-name { margin: 0 0 4px; font-size: 14px; font-weight: 700; color: #17140f; }
.audycja-host-role {
    display: inline-block; font-size: 10px; font-weight: 600; color: #9c2a23;
    background: rgba(226,87,79,0.12); padding: 2px 9px; border-radius: 20px;
}
.audycja-host-bio { margin: 12px 0 0; font-size: 13px; line-height: 1.5; color: #524d47; }
/* Stały odtwarzacz "na żywo" na shadowmaniak.php — zgłoszenie użytkownika
   25.08.2026 ("i miałeś poprawić... twoja propozycja"): ta karta miała
   dostać ten sam czerwony gradient/"na antenie" look co własna wcześniejsza
   propozycja podglądu działu, a wcześniej zostało tylko przy zwykłej
   białej karcie z czerwoną krechą — teraz dociągnięte do końca. Ten sam
   gradient co .stat-card-shadowmaniak na pulpicie, żeby oba miejsca
   trzymały spójny "brand" ShadowManiaka. */
.audycja-live-card {
    position: relative;
    z-index: 0; /* własny kontekst sztaplowania, żeby logo w tle (z-index:-1)
                   nie "uciekło" poniżej całej karty zamiast zostać za tekstem */
    overflow: hidden;
    background: linear-gradient(150deg, #b8342c, #6e120e 75%);
    border-color: #e2574f;
    color: #fff;
}
/* Logo X-Stacji w tle karty (opcjonalne, wgrywane w Narzędziach → Treści) —
   zgłoszenie użytkownika 25.08.2026: "brakuje mi tu w tle ich logo,
   przygotuj to, jak będę miał to wstawimy". Przygaszone i pod tekstem, żeby
   nie gryzło się z czytelnością. Jeśli logo nie jest ustawione, ten
   element po prostu nie jest renderowany (patrz shadowmaniak.php).
   Rozciągnięte na całą szerokość/wysokość karty (nie boks po prawej) —
   poprawka 25.08.2026: "rozciągnij na szerokość okna w tle, będę miał
   SVG więc nie będzie tego białego" (docelowe logo ma przezroczyste tło,
   więc cover na całej karcie nie da twardej krawędzi jak testowy plik). */
.audycja-live-logo-bg {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    opacity: 0.16;
    pointer-events: none;
}
/* .card h2 (bazowy styl nagłówków kart, patrz wyżej) ma własny, ciemny
   kolor tekstu ze specificity (klasa+element) wyższym niż sam .audycja-live-title
   (jedna klasa) — bez elementu "h2" w tym selektorze biały kolor tytułu
   przegrywał i tytuł wychodził ciemny na czerwonym tle (zgłoszenie
   użytkownika 25.08.2026: "zmień może na biały napis"). */
.card h2.audycja-live-title { margin: 0 0 6px; font-size: 22px; font-weight: 800; color: #fff; display: block; }
.audycja-live-kicker {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0 0 4px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: rgba(255,255,255,0.75);
}
.audycja-live-desc { margin: 0 0 16px; font-size: 14px; line-height: 1.5; color: rgba(255,255,255,0.85); max-width: 480px; }
.audycja-live-onair {
    position: absolute;
    top: 18px;
    right: 20px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: rgba(255,255,255,0.16);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 5px 12px;
    border-radius: 999px;
}
.audycja-live-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: #ff8a80;
    animation: audycja-live-pulse 1.8s infinite;
}
@keyframes audycja-live-pulse {
    0% { box-shadow: 0 0 0 0 rgba(255,138,128,0.55); }
    70% { box-shadow: 0 0 0 6px rgba(255,138,128,0); }
    100% { box-shadow: 0 0 0 0 rgba(255,138,128,0); }
}
.audycja-live-player-row { display: flex; align-items: center; gap: 14px; }
/* Przycisk play na ciemnym tle tej karty — okrągły, bez podpisu "Posłuchaj"
   (tekst już jest w nagłówku karty, powtarzanie go w przycisku było zbędne). */
.audycja-live-card .audycja-play-btn {
    width: 52px; height: 52px;
    padding: 0;
    justify-content: center;
    border-radius: 50%;
    background: rgba(255,255,255,0.18);
}
.audycja-live-card .audycja-play-btn:hover { background: rgba(255,255,255,0.28); }
.audycja-live-card .audycja-play-label { display: none; }
.audycja-live-card .audycja-play-btn .audycja-play-icon,
.audycja-live-card .audycja-play-btn .audycja-pause-icon {
    width: 100%; height: 100%;
    background: transparent;
}
/* Sam znaczek play/pauza w środku — render_listen_player() rysuje go na
   sztywno w rozmiarze 14px (dobrym dla małego 26px przycisku gdzie indziej
   na stronie), co w tym większym 52px kółku wyglądało drobno — kosmetyka
   25.08.2026: "znaczek w player trochę większy". */
.audycja-live-card .audycja-play-btn .audycja-play-icon svg,
.audycja-live-card .audycja-play-btn .audycja-pause-icon svg {
    width: 22px;
    height: 22px;
}
/* Dekoracyjny "korektor" obok przycisku — czysto wizualny sygnał "gra na
   żywo", bez podpięcia pod realny poziom audio (przeglądarki i tak nie dają
   do tego dostępu bez Web Audio API na zewnętrznym streamie). */
.audycja-eq { display: inline-flex; align-items: flex-end; gap: 3px; height: 22px; opacity: 0.75; }
.audycja-eq span { width: 3px; background: rgba(255,255,255,0.9); border-radius: 2px; animation: audycja-eq-bounce 1s ease-in-out infinite; }
.audycja-eq span:nth-child(1) { height: 40%; animation-delay: 0s; }
.audycja-eq span:nth-child(2) { height: 100%; animation-delay: 0.15s; }
.audycja-eq span:nth-child(3) { height: 65%; animation-delay: 0.3s; }
.audycja-eq span:nth-child(4) { height: 85%; animation-delay: 0.45s; }
@keyframes audycja-eq-bounce { 0%, 100% { transform: scaleY(0.4); } 50% { transform: scaleY(1); } }
/* Globalny pasek radia "na żywo" (patrz toggleLiveRadio() w layout.php) —
   widoczny na każdej stronie serwisu, dopóki transmisja gra. Zgłoszenie
   użytkownika 25.08.2026: włączenie radia i przechodzenie po serwisie ze
   słuchaniem, z możliwością zatrzymania po powrocie. Ten sam czerwony
   gradient co reszta ShadowManiaka, żeby było jasne, co to za pasek. */
.global-radio-bar {
    position: fixed;
    left: 50%;
    bottom: 18px;
    transform: translateX(-50%);
    z-index: 500;
    display: none;
    align-items: center;
    gap: 10px;
    background: linear-gradient(135deg, #b8342c, #6e120e);
    color: #fff;
    padding: 8px 10px 8px 14px;
    border-radius: 999px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.35);
    font-size: 12px;
    font-weight: 700;
}
.global-radio-bar .grb-label { white-space: nowrap; }
/* .grb-resume/.grb-stop to <button> — bez resetu przeglądarka dokłada
   własny domyślny padding (asymetryczny, więcej po bokach niż góra/dół),
   co przy sztywnym width/height:24px robiło z okrągłego przycisku ELIPSĘ
   (zgłoszenie użytkownika 25.08.2026, widoczne w Safari). box-sizing:
   border-box + padding:0 + font dziedziczony z paska naprawiają to. */
.global-radio-bar .grb-resume,
.global-radio-bar .grb-stop {
    box-sizing: border-box;
    padding: 0;
    margin: 0;
    border: none;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.global-radio-bar .grb-resume {
    display: flex;
    align-items: center;
    gap: 8px;
}
.global-radio-bar .grb-stop {
    display: flex; align-items: center; justify-content: center;
    width: 24px; height: 24px;
    flex-shrink: 0;
    border-radius: 50%;
    background: rgba(255,255,255,0.18);
}
.global-radio-bar .grb-stop:hover { background: rgba(255,255,255,0.3); }
.global-radio-bar .grb-eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; flex-shrink: 0; }
.global-radio-bar .grb-eq span { width: 2px; background: #fff; border-radius: 1px; animation: audycja-eq-bounce 1s ease-in-out infinite; }
.global-radio-bar .grb-eq span:nth-child(1) { height: 40%; animation-delay: 0s; }
.global-radio-bar .grb-eq span:nth-child(2) { height: 100%; animation-delay: .15s; }
.global-radio-bar .grb-eq span:nth-child(3) { height: 65%; animation-delay: .3s; }
.global-radio-bar .grb-eq span:nth-child(4) { height: 85%; animation-delay: .45s; }
/* Stan "zablokowane przez przeglądarkę, trzeba kliknąć" (patrz
   updateLiveRadioBar() w layout.php) — przygaszony korektor, żeby pasek nie
   wyglądał, jakby grało, skoro dźwięk faktycznie stoi. */
.global-radio-bar.paused .grb-eq span { animation-play-state: paused; opacity: 0.5; }
@media (max-width: 720px) {
    .global-radio-bar { bottom: 12px; font-size: 11px; padding: 7px 8px 7px 12px; }
}
/* Otwarta rozmowa czatu (body.chat-fullscreen, patrz chat.php) ma WŁASNY,
   sprawdzony pasek z polem do pisania przyklejony do dołu ekranu na
   mobilnych — pełny pływający pasek radia by go zasłaniał (zgłoszenie
   użytkownika 25.08.2026, ze screenshotem). Zamiast ruszać cokolwiek w
   samym czacie (działa dobrze wszędzie, nie chcemy ryzykować), chowamy tu
   pełny pasek — ale TYLKO gdy dźwięk faktycznie gra (nic do kliknięcia).
   Gdy Safari zablokuje wznowienie po wejściu na tę stronę (stan .paused —
   "kliknij, aby wznowić"), zamiast pełnego paska pokazujemy mały okrągły
   przycisk w rogu, wyżej niż pole do pisania, żeby dało się wznowić BEZ
   wychodzenia z rozmowy. Zgłoszenie użytkownika 25.08.2026: "wychodzę z
   czatu, włączam, wracam do czatu — już nie ma muzyki [i nie ma jak
   wznowić]". */
body.chat-fullscreen .global-radio-bar:not(.paused) { display: none !important; }
body.chat-fullscreen .global-radio-bar.paused {
    left: auto;
    right: 14px;
    bottom: 80px;
    transform: none;
    padding: 0;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    justify-content: center;
}
body.chat-fullscreen .global-radio-bar.paused .grb-stop { display: none; }
body.chat-fullscreen .global-radio-bar.paused .grb-resume {
    width: 100%;
    height: 100%;
    justify-content: center;
}
body.chat-fullscreen .global-radio-bar.paused .grb-label { display: none; }
body.chat-fullscreen .global-radio-bar.paused .grb-eq { height: 16px; }
.quiz-hero { margin-bottom: 16px; }
.quiz-hero h1 { display: flex; align-items: center; gap: 10px; margin: 0 0 4px; }
.quiz-week-card {
    display: flex;
    align-items: center;
    gap: 14px;
    background: linear-gradient(160deg, #fbe0c2, #f9ead9 55%, var(--card-bg) 100%);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
    border-radius: 12px;
    padding: 16px 18px;
    margin-bottom: 16px;
    text-decoration: none;
    color: #171512;
    transition: transform .15s;
}
.quiz-week-card:hover { transform: translateY(-2px); }
.quiz-week-badge {
    width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0;
    background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent-dark);
    display: flex; align-items: center; justify-content: center;
}
.quiz-week-body { flex: 1; min-width: 0; }
.quiz-week-label { font-size: 11px; font-weight: 600; color: var(--accent-dark); text-transform: uppercase; letter-spacing: .03em; }
.quiz-week-title { font-size: 16px; font-weight: 600; margin: 2px 0; }
.quiz-week-sub { font-size: 12.5px; color: #6b6560; }
.quiz-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.quiz-card {
    display: block;
    background: var(--card-bg);
    border: 1px solid rgba(0,0,0,0.08);
    border-radius: 12px;
    padding: 16px;
    text-decoration: none;
    color: #171512;
    transition: transform .15s;
}
.quiz-card:hover { transform: translateY(-3px); }
.quiz-card-icon {
    width: 34px; height: 34px; border-radius: 50%;
    background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-dark);
    display: flex; align-items: center; justify-content: center;
    margin-bottom: 10px;
}
.quiz-card-title { font-size: 14px; font-weight: 600; margin-bottom: 4px; line-height: 1.35; }
.quiz-card-meta { font-size: 12px; color: #8a847d; margin-bottom: 8px; }
.quiz-card-score { font-size: 12.5px; color: var(--accent-dark); font-weight: 600; }
.quiz-card-cta { font-size: 12.5px; color: var(--accent-dark); font-weight: 600; display: flex; align-items: center; gap: 2px; }

/* Nagłówek konkretnego quizu (quiz_take.php) — ta sama pełnoszerokościowa
   sztuczka i gradient co .section-banner (patrz komentarz przy tamtej
   klasie), tylko że źródłem zdjęcia jest grafika PRZYPISANA DO TEGO
   QUIZU (quizzes.cover_image_path), nie globalny baner działu. Bez
   własnej grafiki — to samo tło z logo SOG w tle co puste karty w
   Ogłoszeniach/Wydarzeniach (.event-card-placeholder), żeby wyglądało
   spójnie z resztą serwisu zamiast na puste/zepsute. Osobna klasa (nie
   współdzielona z .section-banner), żeby nie ruszać zachowania globalnych
   banerów działów gdzie indziej. Propozycja użytkownika 11.08.2026. */
.quiz-cover-banner {
    position: relative;
    z-index: 0;
    left: 50%;
    right: 50%;
    width: 100vw;
    margin-left: -50vw;
    margin-right: -50vw;
    margin-top: 0;
    margin-bottom: calc(-1 * var(--banner-overlap));
    padding-bottom: var(--banner-overlap);
    min-height: var(--banner-min-height);
    background-size: cover;
    background-position: center;
    background-color: #1a1712;
    display: flex;
    align-items: flex-end;
    overflow: hidden;
}
.quiz-cover-banner.no-cover {
    background-image: linear-gradient(135deg, #3a3a3a, #262626);
}
.quiz-cover-banner-logo {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 34%;
    max-width: 220px;
    opacity: 0.16;
    pointer-events: none;
}
.quiz-cover-banner .section-banner-inner {
    position: relative;
    z-index: 1;
}
.quiz-cover-banner .section-banner-inner h1 {
    display: flex;
    align-items: center;
    gap: 10px;
}
.quiz-cover-banner + * {
    position: relative;
    z-index: 1;
    margin-top: var(--banner-gap-after);
}
@media (max-width: 720px) {
    .quiz-cover-banner {
        min-height: var(--banner-min-height-mobile);
        margin-top: -14px;
        margin-bottom: calc(-1 * var(--banner-overlap-mobile));
        padding-bottom: var(--banner-overlap-mobile);
    }
}

/* .quiz-hidden — chowa .quiz-take-wrap do czasu kliknięcia "Rozpocznij
   quiz" na quiz_take.php (patrz #quiz-content-view tam) — reszta treści
   (opis/reakcje/komentarze) chowa się w tym samym momencie przez JS, żeby
   samo rozwiązywanie quizu wyglądało dokładnie tak jak na samym początku,
   bez żadnych dodatkowych warstw wokół. Propozycja użytkownika 11.08.2026. */
.quiz-hidden { display: none; }

/* --- quiz_take.php — cała interakcja pytanie-po-pytaniu robiona przez
   assets/quiz-take.js bez przeładowań, style tylko dla warstw, które JS
   pokazuje/podmienia. --- */
/* Wąska, czytelna kolumna (myśl mobile), bez wyśrodkowania w pionie — po
   staremu było wycentrowane na pełną wysokość ekranu z mocnym cieniem pod
   kartą (myślane pod ciemne tło, kiedy strona była z założenia czarna) —
   teraz strona ma zwykłe jasne tło (i tryb jasny/ciemny), więc zamiast tego
   zwykły, delikatny odstęp od góry i cień/obwódka taka jak przy .card na
   Pulpicie — spójnie z resztą serwisu. Poprawka 10.08.2026, 11.08.2026
   (zgłoszenie użytkownika: "niech nie centruje, delikatny odstęp od góry,
   bez podcienia"). */
.quiz-take-wrap {
    /* position:relative + z-index:1 na sztywno (nie tylko przez
       .quiz-cover-banner + *) — ten div jest DALEKIM siblingiem po banerze
       (karta treści, komentarze, link powrotu są przed nim w DOM, patrz
       quiz_take.php), nie bezpośrednim, więc samo ".quiz-cover-banner + *"
       by go nie objęło. Ten sam typ błędu co przy .section-banner, patrz
       komentarz tam. */
    position: relative;
    z-index: 1;
    max-width: 480px;
    margin: 0 auto;
    padding: 8px 0 40px;
}
.quiz-back-link { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: #8a847d; text-decoration: none; margin-bottom: 12px; flex: 0 0 auto; }
.quiz-back-link:hover { color: #171512; }
.quiz-progress-track { height: 6px; background: rgba(0,0,0,0.08); border-radius: 4px; margin-bottom: 16px; overflow: hidden; flex: 0 0 auto; }
.quiz-progress-fill { height: 100%; background: linear-gradient(135deg, var(--accent-light), var(--accent-dark)); transition: width .4s; }
.quiz-panel {
    background: var(--card-bg);
    border-radius: 16px;
    padding: 28px 24px;
    color: #171512;
    border: 1px solid var(--card-border);
}
.quiz-panel-center { text-align: center; }
/* Jasna karta na ciemnym tle strony — bez tej reguły przycisk "Jeszcze raz"
   dziedziczy jasny tekst (#eee) przewidziany pod ciemne tło i staje się
   praktycznie niewidoczny na kremowej karcie (ten sam błąd co gdzie indziej
   z ikonami/przyciskami wewnątrz .card, patrz reguła .card .btn.secondary
   wyżej). Poprawka 10.08.2026. */
.quiz-panel .btn.secondary { color: #171512; border-color: #d8d3cc; }
.quiz-panel .btn.secondary:hover { background: rgba(0,0,0,0.04); }
.quiz-result-icon {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 16px;
    box-shadow: 0 8px 20px rgba(201,112,31,.35);
}
/* Wejście karty wyniku "z przytupem" + konfetti (patrz spawnConfetti() w
   assets/quiz-take.js) — koniec quizu ma być momentem, a nie suchym
   wyświetleniem liczby. Zgłoszenie 10.08.2026. */
@keyframes quiz-result-pop {
    0% { transform: scale(.86); opacity: 0; }
    60% { transform: scale(1.04); opacity: 1; }
    100% { transform: scale(1); }
}
.quiz-result-pop { animation: quiz-result-pop .5s cubic-bezier(.34,1.56,.64,1); }
.quiz-confetti-wrap { position: fixed; inset: 0; pointer-events: none; z-index: 9999; overflow: hidden; }
.quiz-confetti-piece {
    position: absolute;
    top: -20px;
    border-radius: 2px;
    opacity: .95;
    animation-name: quiz-confetti-fall;
    animation-timing-function: cubic-bezier(.25,.46,.45,.94);
    animation-fill-mode: forwards;
}
@keyframes quiz-confetti-fall {
    0% { transform: translateY(0) rotate(0deg); opacity: 1; }
    100% { transform: translateY(100vh) rotate(680deg); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .quiz-result-pop { animation: none; }
    .quiz-confetti-wrap { display: none; }
}
/* Baner "Dziś urodziny" + spadające konfetti na member_profile.php — patrz
   $isBirthdayToday tam. Do 18.09.2026 to były długie, wąskie "serpentyny"
   (wstążki bujające się na boki) — świadomie inne niż kwadratowe konfetti
   quizu/statków. Zgłoszenie użytkownika 25.09.2026 ("miało być jak w
   wygranych wynikach, teraz to jeskieś paski") zmienia to z powrotem: teraz
   birthday-confetti-piece wygląda tak samo jak .quiz-confetti-piece (małe,
   kwadratowe kawałki z obrotem, bez bujania na boki), ale ma WŁASNY
   keyframe (birthday-confetti-fall, nie quiz-confetti-fall) — urodzinowe
   konfetti lecą znacznie dłużej (7-10s, patrz animationDuration w
   member_profile.php) niż krótki "błysk" quizu (1.8-3.2s), a przy dwóch
   krokach opacity (0%/100%) w quiz-confetti-fall wyblakały już w połowie
   spadania, więc WYGLĄDAŁO to, jakby znikały "za wcześnie/za wysoko",
   mimo że geometrycznie doleciałyby do dołu ekranu. Trzeci krok (85%,
   wciąż pełna opacity) w tym osobnym keyframe trzyma je w pełni widoczne
   PRAWIE do samego dołu, blaknięcie dopiero na ostatnim odcinku. Zgłoszenie
   użytkownika 25.09.2026: "za wcześnie znikają, niech spadają niżej". */
.birthday-banner {
    background: linear-gradient(135deg, #ffb703, #fb8500);
    color: #3a2200;
    font-weight: 700;
    font-size: 14px;
    text-align: center;
    padding: 10px 14px;
    border-radius: 8px;
    margin-bottom: 16px;
}
.birthday-confetti-wrap { position: fixed; inset: 0; pointer-events: none; z-index: 9999; overflow: hidden; }
.birthday-confetti-piece {
    position: absolute;
    top: -20px;
    border-radius: 2px;
    opacity: .95;
    animation-name: birthday-confetti-fall;
    animation-timing-function: cubic-bezier(.25,.46,.45,.94);
    animation-fill-mode: forwards;
}
@keyframes birthday-confetti-fall {
    0%   { transform: translateY(0) rotate(0deg); opacity: 1; }
    85%  { opacity: 1; }
    100% { transform: translateY(100vh) rotate(680deg); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .birthday-confetti-wrap { display: none; }
}
.quiz-q-image { width: 100%; max-height: 220px; object-fit: contain; background: #e9e4da; border-radius: 9px; margin-bottom: 14px; }
.quiz-q-text { font-size: 15px; font-weight: 600; margin-bottom: 14px; line-height: 1.4; }
.quiz-option {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
    border: 1.5px solid #e2ddd3;
    border-radius: 9px;
    margin-bottom: 8px;
    cursor: pointer;
    font-size: 13.5px;
}
.quiz-option-check { width: 19px; height: 19px; border-radius: 5px; border: 2px solid #c9c2b6; flex-shrink: 0; }
.quiz-option.selected .quiz-option-check { background: #c9701f; border-color: #c9701f; }
.quiz-option.correct { border-color: #3fb950; background: rgba(63,185,80,.08); }
.quiz-option.wrong { border-color: #e24b4a; background: rgba(226,75,74,.08); }
.quiz-feedback { font-size: 12.5px; border-radius: 8px; padding: 10px 12px; margin: 10px 0; }
.quiz-feedback.correct { background: rgba(63,185,80,.12); color: #1d7a34; }
.quiz-feedback.wrong { background: rgba(226,75,74,.1); color: #a32d2d; }
.quiz-result-score { font-size: 40px; font-weight: 700; color: #c9701f; margin-bottom: 4px; }
.quiz-result-sub { font-size: 13.5px; color: #6b6560; margin-bottom: 18px; }
/* Wytłumaczenie, dlaczego ranking pokazuje inny wynik niż ten, który ktoś
   właśnie zobaczył u góry (liczy się tylko pierwsza próba) — patrz
   quiz-take.js renderResult(). Bez tego wygląda na błąd, nie na zasadę. */
.quiz-rank-note {
    font-size: 12px;
    color: #7a5a2e;
    background: rgba(240,153,74,0.12);
    border-radius: 8px;
    padding: 8px 12px;
    margin: -6px 0 16px;
    line-height: 1.4;
}
.quiz-leaderboard { text-align: left; margin-top: 18px; border-top: 1px solid rgba(0,0,0,0.08); padding-top: 14px; }
.quiz-leaderboard-row { display: flex; justify-content: space-between; font-size: 12.5px; padding: 5px 0; color: #45403a; }
.quiz-leaderboard-row.is-you { font-weight: 700; color: var(--accent-dark); }

/* Ranking aktywności na pulpicie — świadomie stonowany i wąski (mieści się w
   kolumnie bocznej razem z innymi kartami), nie osobny, krzykliwy "widget"
   na pół strony — ważny element, ale ma zostać czymś ładnym i delikatnym,
   nie dominować układu. Zwykłe, ciche wiersze zamiast
   kolorowych kółek/pasków postępu — tylko delikatne podświetlenie własnego
   wiersza i mała, cienka kreska pod spodem oddzielająca pozycje.
   Lifting 28.08.2026 (zgłoszenie użytkownika: stary pomarańczowy gradient
   karty przestał pasować do reszty po odświeżeniu kafelków statystyk wyżej
   na pulpicie). Karta wraca do zwykłego, kremowego tła — tak jak "Trasy
   społeczności" — ale świadomie NIE traci motywacyjnego/gamifikowanego
   charakteru (użytkownik wprost o to prosił: "ludzie starają się zdobywać
   punkty i to ich podbudowuje... to ważne okienko, oni tym żyją"). Ten
   charakter zostaje przeniesiony w kilka miejsc: bardzo delikatny złoty
   odcień tła (subtelniejszy niż stary pomarańczowy gradient, ale wciąż
   odróżnia kartę od reszty), złota obwódka zamiast zwykłej --card-border,
   dużą ikonę pucharu w tle rogu karty (ten sam patent co .stat-card-bgicon
   wyżej w tym pliku, tu spora — użytkownik wprost poprosił o większą:
   "wieksza ta ikonka w tle... ona troche musi sie wybija") i złotą/
   srebrną/brązową odznakę przy pierwszych trzech miejscach zamiast gołego
   numerka. */
.card.leaderboard-compact {
    position: relative;
    z-index: 0;
    overflow: hidden;
    background: linear-gradient(160deg, #fbf2dc, var(--card-bg) 65%);
    border: 1px solid rgba(217,165,32,.3);
}
.leaderboard-bgicon {
    position: absolute;
    right: -30px;
    bottom: -34px;
    z-index: -1;
    pointer-events: none;
    color: #d9a520;
    opacity: 0.22;
    transform: rotate(8deg);
}
.leaderboard-bgicon svg { width: 210px; height: 210px; display: block; }

/* Pełny ranking (ranking.php) — osobna strona, nie 2-kolumnowy pulpit, więc
   bez ograniczenia .leaderboard-compact rozciągało się na całą szerokość
   .wrap (1180px), zostawiając dużą pustą przestrzeń między nazwiskiem a
   punktami po prawej stronie. Zwężamy listę do czytelnej szerokości i
   powiększamy/przesuwamy ikonkę w tle, żeby wypełnić pustkę zamiast jej
   dodatkowo pogłębiać powiększoną kartą. Zgłoszenie użytkownika 09.09.2026
   (zrzut ekranu): "może większa ikonka w tle na 3/4 - wypełni nam pustkę". */
.ranking-page-wrap { max-width: 640px; margin: 0 auto; }
/* Większa, celowo PRZYCIĘTA przez overflow:hidden karty (patrz .card.leaderboard-compact
   wyżej) — po prawej i od dołu, zamiast w całości mieścić się w środku. Poprawka
   09.09.2026 na prośbę użytkownika: "może być większa i przycięta w rogu po
   prawej i przycieta - ładnie to wygląda". */
.ranking-page-wrap .leaderboard-bgicon { right: -140px; bottom: -90px; opacity: 0.16; }
.ranking-page-wrap .leaderboard-bgicon svg { width: 460px; height: 460px; }
/* Karty rankingu gier (Statki/Kod Mistrza) po wtopieniu bilansu — ten sam
   patent "większa i przycięta" co wyżej (.ranking-page-wrap), na wyraźną
   prośbę użytkownika przy akceptacji wykresu: "ikonka w tle, jak wszedzie
   wieksza i moze być przycieta". */
.battleship-ranking-card .leaderboard-bgicon,
.mm-ranking-card .leaderboard-bgicon { right: -46px; bottom: -50px; }
.battleship-ranking-card .leaderboard-bgicon svg,
.mm-ranking-card .leaderboard-bgicon svg { width: 270px; height: 270px; }
.leaderboard-compact-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 2px;
    border-bottom: 1px solid var(--card-border);
    font-size: 12.5px;
}
.leaderboard-compact-row:last-child { border-bottom: none; }
.leaderboard-compact-row.is-me { background: color-mix(in srgb, var(--accent) 10%, transparent); border-radius: 6px; padding-left: 6px; padding-right: 6px; }
/* Odznaka rangi — okrągła, z numerem; pierwsze trzy miejsca dostają
   złoty/srebrny/brązowy gradient (patrz .rank-1/.rank-2/.rank-3 niżej),
   pozostałe zostają neutralne, żeby nie rozpraszać od podium. */
.leaderboard-compact-rank {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 10.5px;
    background: #e9e4d9;
    color: #6b6560;
}
.leaderboard-compact-rank.rank-1 { background: linear-gradient(135deg, #f7d774, #d9a520); color: #5c4400; }
.leaderboard-compact-rank.rank-2 { background: linear-gradient(135deg, #e2e2e2, #b0b0b0); color: #3a3a3a; }
.leaderboard-compact-rank.rank-3 { background: linear-gradient(135deg, #e3a86c, #b06d34); color: #3f2408; }
.leaderboard-compact-row .avatar { width: 22px; height: 22px; font-size: 9px; flex-shrink: 0; }
.leaderboard-compact-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.leaderboard-compact-points { flex-shrink: 0; color: var(--accent-dark); font-size: 11px; font-weight: 600; }
/* Admini/moderatorzy w rankingach GRY (Statki/Kod Mistrza) grają na
   równych zasadach co reszta (normalny wpis, z numerem miejsca i
   punktami) — zgłoszenie użytkownika 20.09.2026, patrz komentarz przy
   bship_compute_ranking()/mm_compute_ranking(). Osobna, wyciszona lista bez
   punktów (wcześniejsza wersja tej poprawki) została wycofana na wyraźną
   prośbę: "w rankingach gier jest to ważne nich sie bawią wszyscy". */

.quick-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 14px 4px 20px;
}
.quick-action {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 9px 15px;
    border-radius: 20px;
    background: var(--chrome-fill);
    border: 1px solid var(--chrome-border-strong);
    color: var(--text-chrome);
    font-size: 12px;
    font-weight: 600;
    text-decoration: none;
    transition: background .15s, border-color .15s, transform .15s;
}
.quick-action:hover { background: var(--accent); border-color: var(--accent); color: #fff; transform: translateY(-2px); }
.quick-action .icon { width: 13px; height: 13px; }

/* --- Kafelki "odkryj" na pulpicie (galeria/wydarzenia/ogłoszenia ze zdjęciem) --- */
.discover-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 14px;
    margin-bottom: 18px;
}
.discover-tile {
    position: relative;
    display: block;
    height: 230px;
    border-radius: 9px;
    overflow: hidden;
    text-decoration: none;
    background-color: #232323;
    transition: transform .18s;
}
.discover-tile:hover { transform: translateY(-4px); }
.discover-tile::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,0) 35%, rgba(0,0,0,0.78) 100%);
}
.discover-tile.no-image { background: linear-gradient(135deg, #2c2c2c, #181818); }

/* Pasek stronicowania (Poprzednia / Strona X z Y / Następna) — patrz
   paginate() + render_pagination() w includes/helpers.php, używane na
   każdej liście, która z czasem może urosnąć (użytkownicy, galeria,
   materiały, trasy...). */
.pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 16px;
    flex-wrap: wrap;
}
.pagination-info {
    font-size: 12px;
    color: var(--text-chrome-muted, #8a847d);
    text-align: center;
    flex: 1;
}
/* Grupuje "Pierwsza"+"Poprzednia" (lewa strona) i "Następna"+"Ostatnia"
   (prawa strona) — pusty <span> (0 przycisków) na skrajnej stronie, gdy już
   jesteśmy na pierwszej/ostatniej stronie, zachowuje się tak samo jak
   dawny pojedynczy pusty <span> (wyśrodkowuje .pagination-info między
   dwiema stronami). Zgłoszenie użytkownika 20.09.2026 o szybszym
   przechodzeniu na koniec/początek listy. */
.pagination-side { display: flex; align-items: center; gap: 6px; }

/* Kafelek Galerii na pulpicie: kilka nałożonych na siebie warstw tła,
   przełączanych klasą .active (przenikanie zamiast twardej zmiany) —
   patrz JS w index.php. Każda warstwa ma własne background-size/position,
   żeby zoom na hover (przejęty tu z .discover-tile:hover) też działał. */
.discover-tile-slide {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    opacity: 0;
    transition: opacity 1s ease, transform .4s;
}
.discover-tile-slide.active { opacity: 1; }
.discover-tile-carousel:hover .discover-tile-slide.active { transform: scale(1.08); }
.discover-tile-dots {
    position: absolute;
    left: 0; right: 0;
    bottom: 10px;
    z-index: 2;
    display: flex;
    justify-content: center;
    gap: 5px;
}
.discover-tile-dots .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255,255,255,0.45);
    transition: background .3s, transform .3s;
    cursor: pointer;
}
.discover-tile-dots .dot.active { background: #fff; transform: scale(1.3); }
.discover-tile-icon-lg {
    position: absolute;
    top: 46%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #fff;
    opacity: .18;
}
.discover-tile-body {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 14px 16px;
    z-index: 2;
    color: #fff;
}
.discover-tile-title {
    font-size: 15px;
    font-weight: 700;
    margin: 0 0 3px;
    display: flex;
    align-items: center;
    gap: 6px;
    color: #fff;
}
.discover-tile-arrow {
    position: absolute;
    top: 14px;
    right: 14px;
    z-index: 2;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: rgba(255,255,255,0.16);
    backdrop-filter: blur(3px);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transform: translateX(-4px);
    transition: opacity .18s, transform .18s;
}
.discover-tile:hover .discover-tile-arrow { opacity: 1; transform: translateX(0); }
.discover-tile-meta {
    font-size: 11px;
    color: rgba(255,255,255,0.82);
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Karuzela promocyjna Pulpitu (dashboard_promo_slides, Narzędzia -> Treści)
   — między kafelkami Galeria/Wydarzenia/Ogłoszenia i resztą Pulpitu.
   Dowolna liczba slajdów, admin dodaje/usuwa (patrz tools.php); JS w
   index.php przenika między nimi co 4s, pauza pod kursorem, kropki na
   dole. Ta sama wysokość/zaokrąglenie co .discover-tile (spójność z
   karuzelą tuż nad nią), ale WEWNĄTRZ .wrap — celowo NIE pełnoszerokościowa
   jak banery działów/Statków, bo to tylko jeden element wśród wielu na
   Pulpicie, a nie nagłówek całej strony. Cała sekcja w ogóle się nie
   renderuje, gdy nie ma żadnego aktywnego slajdu (patrz index.php).
   Zgłoszenie użytkownika 18.09.2026 ("widziałbym taka karuzele... bannery
   graficzne dodane w narzedziach"), zaakceptowany podgląd-mockup przed
   wdrożeniem ("petarda, super to wyglada"). */
.dashboard-promo-carousel {
    position: relative;
    height: 230px;
    border-radius: 9px;
    overflow: hidden;
    margin-bottom: 18px;
    background-color: #232323;
}
.dashboard-promo-slide {
    position: absolute;
    inset: 0;
    display: block;
    opacity: 0;
    z-index: 1;
    text-decoration: none;
    transition: opacity 1s ease;
}
.dashboard-promo-slide.active { opacity: 1; z-index: 2; }
.dashboard-promo-slide-bg {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    transition: transform .4s;
}
/* Podkład mobilny domyślnie UKRYTY (widoczny tylko poniżej 720px, patrz
   .has-mobile-image w @media niżej) — bez tego nakładałby się na desktopowy
   w tym samym miejscu (oba position:absolute; inset:0). */
.dashboard-promo-slide-bg-mobile { display: none; }
.dashboard-promo-carousel:hover .dashboard-promo-slide.active .dashboard-promo-slide-bg { transform: scale(1.05); }
.dashboard-promo-slide.no-image { background: linear-gradient(135deg, #2c2c2c, #181818); }
.dashboard-promo-slide::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,0) 30%, rgba(0,0,0,0.8) 100%);
}
/* Slajd BEZ tytułu/podtytułu (28.09.2026) — grafika ma już wpisany tekst,
   więc ciemniejący gradient u dołu (::after wyżej, dodany z myślą o
   czytelności BIAŁEGO tekstu podpisu) jest tu zbędny i tylko przyciemnia
   część własnej grafiki użytkownika. Zgłoszenie: "mam gotową kolekcję z
   napisami [...] tytuł jest obowiązkowy". */
.dashboard-promo-slide.no-caption::after { content: none; }
.dashboard-promo-slide-body {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    z-index: 2;
    padding: 20px 24px;
    max-width: 560px;
    color: #fff;
}
.dashboard-promo-slide-title {
    font-size: 20px;
    font-weight: 700;
    margin: 0 0 5px;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0,0,0,0.35);
}
.dashboard-promo-slide-subtitle {
    font-size: 13px;
    margin: 0;
    color: rgba(255,255,255,0.88);
}
.dashboard-promo-dots {
    position: absolute;
    right: 18px;
    bottom: 14px;
    z-index: 3;
    display: flex;
    gap: 6px;
}
.dashboard-promo-dots .dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgba(255,255,255,0.45);
    transition: background .3s, transform .3s;
    cursor: pointer;
}
.dashboard-promo-dots .dot.active { background: #fff; transform: scale(1.3); }

/* --- Dwukolumnowy układ list na pulpicie (trasy/ogłoszenia/wydarzenia/czat)
   zamiast jednej długiej kolumny kart jedna pod drugą — na szerokich
   ekranach wygląda jak prawdziwy dashboard, nie lista.
   Dwie NIEZALEŻNE kolumny (każda to własny, pionowy stos kart), a nie
   wspólny grid par w rzędach — karty nie muszą mieć tej samej wysokości
   (np. jedno ogłoszenie obok trzech tras), każda jest tak wysoka, ile
   potrzebuje jej własna treść, i zaczyna się równo od góry kolumny. Wymuszone
   rozciąganie do wysokości sąsiada (poprzednia wersja) tylko dokładało pustej
   przestrzeni w krótszej karcie. */
.dashboard-columns {
    display: flex;
    align-items: flex-start;
    gap: 16px;
}
.dashboard-col {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 16px;
}
@media (max-width: 900px) {
    /* align-items:flex-start (wyżej) był POTRZEBNY w układzie poziomym
       (dwie kolumny obok siebie), żeby kolumny nie rozciągały się do
       wysokości dłuższej sąsiadki. Ale flex-direction:column obraca osie —
       ten sam align-items zaczyna wtedy działać na szerokość zamiast
       wysokości i kurczy każdą kolumnę/kartę do szerokości jej własnej
       treści (stąd wąskie karty z czarnym marginesem po bokach na
       telefonie). Trzeba go tu jawnie nadpisać na stretch. */
    .dashboard-columns { flex-direction: column; align-items: stretch; }

    /* Na telefonie dwie kolumny łączą się w jedną, w kolejności dokumentu
       (lewa kolumna cała, potem prawa cała) — cokolwiek trafi do prawej
       kolumny zawsze ląduje POD całą lewą, niezależnie od miejsca w tej
       kolumnie. Do 19.09.2026 był tu hardkodowany order:-1 na Rankingu i "W
       tym tygodniu" (łatał dawny SZTYWNY podział na kolumny — ranking był
       zawsze w drugiej), potem krótko wystarczało samo przeciąganie do
       lewej kolumny skoro podział stał się konfigurowalny — ale to nadal
       wiązało kolejność na telefonie z podziałem kolumn na desktopie, a to
       dwie różne rzeczy. Zgłoszenie użytkownika 19.09.2026: "trzeba dodac
       jedna opcje dla mobilnych która wyrózni kolejnosc bo tak rankingi
       spadna na dół". Rozwiązanie: OSOBNA, w pełni niezależna kolejność na
       telefon (dashboard_list_mobile_default_order() w includes/helpers.php,
       ustawiana w Narzędzia -> Ustawienia -> "Kolejność na telefonie") —
       display:contents "rozpuszcza" oba .dashboard-col (ich własne div-y
       znikają z layoutu, ale karty w środku zostają) w jeden wspólny stos,
       w którym `order` może przestawiać karty MIĘDZY dawnymi kolumnami, nie
       tylko w obrębie jednej. Bez tego poniższe klasy mobile-order-N
       działałyby tylko wewnątrz własnej kolumny, bo .dashboard-col byłby
       nadal osobnym kontenerem flex. Poniższe klasy (wstrzykiwane w
       index.php przez dashboard_inject_mobile_order_class()) nadają każdej
       karcie jej WŁASNĄ pozycję, niezależną od tego, w której kolumnie
       desktopowej się znajduje. */
    .dashboard-col { display: contents; }
    .dashboard-columns .mobile-order-0 { order: 0; }
    .dashboard-columns .mobile-order-1 { order: 1; }
    .dashboard-columns .mobile-order-2 { order: 2; }
    .dashboard-columns .mobile-order-3 { order: 3; }
    .dashboard-columns .mobile-order-4 { order: 4; }
    .dashboard-columns .mobile-order-5 { order: 5; }
}

/* --- Miniatury zdjęć wydarzeń --- */
.event-cover {
    width: 100%;
    height: 160px;
    object-fit: cover;
    border-radius: 5px;
    display: block;
    margin-bottom: 12px;
}
/* --- Stopka --- */
.site-footer {
    margin-top: 36px;
    padding-top: 20px;
    border-top: 1px solid var(--chrome-border);
    text-align: center;
}
.site-footer-links {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 18px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}
.site-footer-links a {
    color: var(--text-chrome-muted);
    text-decoration: none;
    font-size: 12px;
    transition: color .15s;
}
.site-footer-links a:hover { color: var(--text-chrome); }
/* Rządek "Znajdziesz nas również" — okrągłe, szare, delikatne ikonki mediów
   społecznościowych, celowo stonowane, nie duże i krzykliwe. Osobny wiersz
   pod linkami stopki, wypełniony kolor dopiero po najechaniu. */
.site-footer-social {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}
.site-footer-social span {
    color: var(--text-chrome-faint);
    font-size: 11px;
    margin-right: 2px;
}
.site-footer-social a {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1.3px solid var(--chrome-border);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-chrome-muted);
    text-decoration: none;
    font-size: 13px;
    transition: color .15s, border-color .15s, background-color .15s;
}
.site-footer-social a:hover {
    color: #fff;
    background: var(--accent);
    border-color: var(--accent);
}
.site-footer-meta { color: var(--text-chrome-faint); font-size: 11px; margin: 0; }

/* --- Komunikator (czat) --- */
.chat-layout {
    display: flex;
    height: 72vh;
    min-height: 480px;
    background: var(--card-bg);
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--card-border);
}
.chat-sidebar {
    width: 270px;
    flex-shrink: 0;
    background: #eee9e2;
    border-right: 1px solid rgba(0,0,0,0.08);
    display: flex;
    flex-direction: column;
}
/* Przewijana lista grup, OSOBNO od .chat-sidebar-head nad nią (ten sam
   wzorzec co .chat-header/.chat-messages po prawej) — nagłówek "Czat" +
   przyciski archiwum/nowej grupy mają zostać na stałym miejscu, przewija
   się tylko sama lista pod nimi. min-height:0
   jest konieczne, żeby flex-item w ogóle chciał się skurczyć poniżej
   wysokości swojej treści (ten sam powód co przy .chat-messages niżej). */
.chat-sidebar-list { flex: 1; min-height: 0; overflow-y: auto; }
.chat-sidebar-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 14px 12px;
    color: #17140f;
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 10%, transparent), color-mix(in srgb, var(--accent) 0%, transparent) 90%);
    border-bottom: 1px solid rgba(0,0,0,0.06);
}
.chat-sidebar-head h2 { margin: 0; font-size: 14px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
.chat-sidebar-head h2 .icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    vertical-align: middle;
}
.chat-sidebar .empty-state { padding: 20px 14px; color: #6b6560; }
.chat-sidebar .empty-state p { font-size: 12px; }
.chat-group-item {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 4px 8px;
    padding: 9px 10px;
    border-radius: 7px;
    text-decoration: none;
    color: #17140f;
    transition: background .15s, box-shadow .15s, transform .15s;
}
.chat-group-item:hover { background: rgba(0,0,0,0.05); }
.chat-group-item.active {
    background: #f5f3ef;
    box-shadow: inset 3px 0 0 var(--accent);
}
.chat-group-item.active .chat-group-item-name { color: var(--accent-dark); }
.chat-group-item.active .chat-group-item-preview { color: var(--accent-dark); opacity: .75; }
.chat-group-avatar { flex-shrink: 0; box-shadow: 0 0 0 2px #fff, 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent); }
/* Osobna ikona + kolor dla automatycznych czatów wycieczek/wydarzeń (zamiast
   inicjałów) — pozwala odróżnić je na pierwszy rzut oka od zwykłych,
   ręcznie tworzonych grup (regiony itp.) i skrócić widoczny tekst na liście
   (patrz chat_group_visual() w includes/helpers.php, usuwa powtarzający się
   prefiks "Wycieczka:"/"Wydarzenie:" ze skróconej etykiety, skoro ikona już
   to mówi). Kolory celowo inne niż --accent (pomarańcz zwykłych awatarów),
   żeby faktycznie się wyróżniały, nie tylko ikoną. Zgłoszenie 18.08.2026. */
.chat-group-avatar--route { background: linear-gradient(135deg, #3fae90, #227a63); box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(34,122,99,0.25); }
.chat-group-avatar--event { background: linear-gradient(135deg, #7c7ce8, #4b46b8); box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(75,70,184,0.25); }
/* Trzeci kolor pary Wycieczka(zielony)/Wydarzenie(fiolet) — czat audycji
   ShadowManiak. Patrz chat_group_visual() w helpers.php.
   14.09.2026: był to ten sam czerwony co reszta modułu ShadowManiaka —
   po przejściu całego serwisu (i logowania) na czerwień jako domyślny
   --accent ten sam czerwony zaczął zlewać się z własnym dymkiem wiadomości
   (.chat-bubble.own, też czerwony) na liście grup czatu — zgłoszenie
   użytkownika ("w kolorach chat teraz zlewa się ShadowManiak"). Reszta
   brandu ShadowManiaka (kafelka na pulpicie, plakietka, baner, karta "na
   żywo", pasek radia) ZOSTAJE czerwona, jak było — to jest jedyna, punktowa
   zmiana: TYLKO ta jedna ikonka na liście grup dostaje pomarańcz, żeby dało
   się ją odróżnić od własnego dymku obok, bez przebarwiania całej marki
   ShadowManiaka gdzie indziej w serwisie. */
.chat-group-avatar--audycja { background: linear-gradient(135deg, #f0994a, #8a4a12); box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(138,74,18,0.25); }
/* Czwarty wariant — ręcznie wybrana ikonka grupy (chat_groups.icon_key,
   patrz CHAT_GROUP_ICON_CHOICES w helpers.php), celowo neutralny szary/
   niebieski zamiast kolorów zarezerwowanych dla automatycznych typów
   wyżej (Wycieczka/Wydarzenie/Audycja), żeby nie sugerować, że to kolejny
   systemowy typ grupy. Zgłoszenie użytkownika 05.09.2026: "możemy takiemu
   czat Pomocy dodać ikonkę?". */
.chat-group-avatar--manual-icon { background: linear-gradient(135deg, #6b7480, #3f4650); box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(63,70,80,0.25); }
.chat-group-item-body { min-width: 0; }
.chat-group-item-name { display: block; font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-group-item-preview { display: block; font-size: 11px; color: #8a847d; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.chat-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    position: relative;
    background: #efebe4;
    background-image: radial-gradient(rgba(0,0,0,0.025) 1px, transparent 1px);
    background-size: 18px 18px;
}
.chat-main .empty-state { color: #6b6560; }
.chat-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 18px;
    background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 14%, transparent), color-mix(in srgb, var(--accent) 2%, transparent) 55%), var(--card-bg);
    border-bottom: 2px solid var(--accent);
    color: #17140f;
    flex-shrink: 0; /* nagłówek rozmowy nigdy nie ma się ściskać — na telefonie
                        z otwartą klawiaturą to .chat-messages (flex:1,
                        min-height:0 wyżej) ma oddawać miejsce, nie nagłówek
                        ani pole do pisania (patrz też .chat-composer niżej) */
}
/* align-items zmienione z center na flex-start 09.09.2026 (zgłoszenie
   użytkownika ze zrzutem ekranu: "ikonka w tytule... i pod nią dopiero ilość
   i opis") — przy dłuższym opisie grupy .chat-header-meta zawija się do 2-3
   linii (patrz flex-wrap niżej), a z align-items:center awatar grupy był
   centrowany względem CAŁEGO bloku (tytuł+opis), więc "pływał" w dół i wyglądał
   na przesunięty względem samego tytułu. flex-start przypina awatar do góry,
   na wysokości tytułu — tak jak w typowych komunikatorach. */
.chat-header-info { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.chat-header-avatar { flex-shrink: 0; box-shadow: 0 0 0 2px #fff, 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
.chat-header-name { margin: 0; font-size: 14px; font-weight: 700; color: var(--accent-dark); }
/* flex-wrap:wrap dodane 09.09.2026 (zgłoszenie użytkownika ze zrzutem ekranu
   na telefonie) — bez tego flexbox traktował ikonkę+liczbę członków+opis
   grupy (długi tekst) jako jeden nie-zawijający się rząd "flex items", więc
   długi opis wypychał layout w bok zamiast się zawinąć, a przy okazji
   ściskał sąsiedni przycisk ustawień (patrz .icon-btn flex-shrink:0 wyżej). */
.chat-header-meta { margin: 2px 0 0; font-size: 11px; color: #8a847d; display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }

.chat-messages {
    flex: 1;
    min-height: 0; /* bez tego flex-item domyślnie nie chce się skurczyć
                      poniżej wysokości swojej treści — przy długiej historii
                      czatu wypychało to pole do pisania poza widoczny obszar
                      zamiast pozwolić TEJ liście przewijać się wewnętrznie */
    overflow-y: auto;
    padding: 18px 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.chat-messages::-webkit-scrollbar, .chat-sidebar::-webkit-scrollbar { width: 8px; }
.chat-messages::-webkit-scrollbar-thumb, .chat-sidebar::-webkit-scrollbar-thumb {
    background: rgba(0,0,0,0.15);
    border-radius: 20px;
}
.chat-messages::-webkit-scrollbar-track, .chat-sidebar::-webkit-scrollbar-track { background: transparent; }
.chat-day-divider {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 4px 0;
    color: #a8a29a;
    font-size: 11px;
    font-weight: 600;
}
.chat-day-divider::before, .chat-day-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: rgba(0,0,0,0.08);
}
.chat-day-divider span { white-space: nowrap; }
.chat-bubble {
    display: flex;
    gap: 8px;
    max-width: 72%;
    align-self: flex-start;
}
.chat-bubble.own { align-self: flex-end; flex-direction: row-reverse; }
/* align-self:flex-start jest tu KLUCZOWE — .chat-bubble (rodzic, display:flex)
   nie ustawia align-items, więc domyślne "stretch" rozciągało ten link na
   pełną wysokość całej wiadomości (dymek + reakcje + rządek akcji pod spodem,
   czyli sąsiedni flex-item .chat-bubble-stack). Sam <img> nie rósł (obrazki
   nie rozciągają się domyślnie), ale KONTENER .chat-bubble-avatar owszem —
   więc .online-dot (position:absolute; bottom:-1px) lądował na dole TEGO
   rozciągniętego, niewidzialnego pudełka, czyli praktycznie przy rządku
   przycisków odpowiedz/emotka, a nie przy samym awatarze. */
.chat-bubble-avatar { flex-shrink: 0; align-self: flex-start; position: relative; display: inline-block; }
.chat-bubble-content {
    background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 9%, transparent), color-mix(in srgb, var(--accent) 3%, transparent)), #fff;
    color: #17140f;
    border: 1px solid color-mix(in srgb, var(--accent) 12%, transparent);
    border-radius: 14px 14px 14px 4px;
    padding: 9px 13px;
    min-width: 0;
}
.chat-bubble.own .chat-bubble-content {
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    border: none;
    border-radius: 14px 14px 4px 14px;
}
.chat-bubble-author { font-size: 11px; font-weight: 700; margin-bottom: 2px; }
.chat-bubble-author a { color: var(--accent-dark); text-decoration: none; }
.chat-bubble-author a:hover { text-decoration: underline; }
.chat-bubble-avatar:hover .avatar { opacity: 0.85; }
.chat-bubble-text { font-size: 13px; line-height: 1.5; word-wrap: break-word; white-space: pre-wrap; }
.chat-bubble-text a { color: var(--accent-dark); text-decoration: underline; }
.chat-bubble.own .chat-bubble-text a { color: #fff; }
.chat-mention {
    font-weight: 700;
    text-decoration: none !important;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border-radius: 4px;
    padding: 1px 4px;
}
.chat-bubble.own .chat-mention { background: rgba(255,255,255,0.22); color: #fff; }
.chat-mention-me { background: color-mix(in srgb, var(--accent) 35%, transparent); }
.chat-bubble.own .chat-mention-me { background: rgba(255,255,255,0.4); }
/* "@wszyscy" — wywołuje naraz wszystkich (patrz build_mention_lookup() w
   includes/helpers.php), nie jest linkiem do niczyjego profilu, więc
   wyróżniona innym kolorem zamiast zwykłego .chat-mention. */
.chat-mention-all { background: color-mix(in srgb, #c0392b 18%, transparent); color: #c0392b; }
.chat-bubble.own .chat-mention-all { background: rgba(255,255,255,0.3); color: #fff; }
.chat-bubble-image { display: block; max-width: 220px; max-height: 220px; border-radius: 5px; margin-bottom: 4px; object-fit: cover; }
.chat-bubble-file {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: rgba(0,0,0,0.05);
    border-radius: 5px;
    text-decoration: none;
    color: inherit;
    font-size: 12px;
    margin-bottom: 4px;
}
.chat-bubble.own .chat-bubble-file { background: rgba(255,255,255,0.18); color: #fff; }
.chat-bubble-time { font-size: 10px; color: #a8a29a; margin-top: 3px; text-align: right; }
.chat-bubble.own .chat-bubble-time { color: rgba(255,255,255,0.75); }
.chat-bubble-edited { font-style: italic; opacity: .8; }

/* Edycja własnej, ostatniej wiadomości w oknie czasowym — patrz
   startEditMessage() w assets/chat.js i chat_edit.php. Textarea zastępuje
   .chat-bubble-text w miejscu, więc trzyma tę samą szerokość dymka. */
.chat-edit-form { display: flex; flex-direction: column; gap: 6px; margin-top: 2px; }
.chat-edit-textarea {
    width: 100%;
    min-width: 180px;
    max-width: 100%;
    min-height: 44px;
    resize: vertical;
    font: inherit;
    font-size: 16px; /* >=16px — blokada auto-zoomu iOS, patrz komentarz przy .modal-inner input[type=text] */
    line-height: 1.5;
    padding: 6px 8px;
    border-radius: 6px;
    border: 1px solid rgba(0,0,0,0.15);
    background: #fff;
    color: #17140f;
    box-sizing: border-box;
}
.chat-bubble.own .chat-edit-textarea { border-color: rgba(255,255,255,0.4); }
.chat-edit-actions { display: flex; gap: 6px; justify-content: flex-end; }
.chat-edit-save, .chat-edit-cancel {
    font-size: 12px;
    padding: 4px 10px;
    border-radius: 20px;
    border: none;
    cursor: pointer;
}
.chat-edit-save { background: var(--accent); color: #fff; font-weight: 600; }
.chat-edit-cancel { background: rgba(0,0,0,0.08); color: #45403a; }
.chat-bubble.own .chat-edit-cancel { background: rgba(255,255,255,0.22); color: #fff; }

.chat-bubble-stack { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
.chat-bubble.own .chat-bubble-stack { align-items: flex-end; }

.chat-reply-quote {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 5px 8px;
    margin-bottom: 5px;
    background: rgba(0,0,0,0.05);
    border-left: 3px solid var(--accent);
    border-radius: 4px;
    font-size: 11.5px;
    max-width: 260px;
}
.chat-bubble.own .chat-reply-quote { background: rgba(255,255,255,0.18); border-left-color: rgba(255,255,255,0.7); }
.chat-reply-quote-author { font-weight: 700; color: var(--accent-dark); }
.chat-bubble.own .chat-reply-quote-author { color: #fff; }
.chat-reply-quote-text { color: #6b6560; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; }
.chat-bubble.own .chat-reply-quote-text { color: rgba(255,255,255,0.85); }

.chat-reactions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.chat-reactions:empty { display: none; margin-top: 0; }
.chat-reaction-pill {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 11.5px;
    padding: 2px 8px;
    border-radius: 20px;
    border: 1px solid rgba(0,0,0,0.08);
    background: #fff;
    color: #45403a;
    cursor: pointer;
    line-height: 1.5;
    transition: transform .1s, border-color .12s;
}
.chat-reaction-pill:hover { transform: translateY(-1px); }
.chat-reaction-pill.mine { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-dark); font-weight: 600; }
.chat-reaction-pill span { font-size: 10.5px; }

.chat-bubble-actions {
    display: flex;
    gap: 2px;
    margin-top: 3px;
    opacity: .5;
    transition: opacity .12s;
}
.chat-bubble:hover .chat-bubble-actions, .chat-bubble-actions:focus-within { opacity: 1; }
.chat-bubble-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    min-width: 22px;
    padding: 0;
    flex-shrink: 0;
    border-radius: 50%;
    border: none;
    background: rgba(0,0,0,0.06);
    color: #6b6560;
    cursor: pointer;
}
.chat-bubble-action:hover { background: var(--accent); color: #fff; }
.chat-bubble-action.danger:hover { background: #c0392b; color: #fff; }
/* "Oznacz jako rozwiązane"/"Cofnij" (chat.php: resolve_message/
   unresolve_message) — zwykły <form> owijający jeden przycisk zamiast <button
   type="button"> jak reszta akcji w .chat-bubble-actions, bo to jedyna akcja
   tutaj bez obsługi JS/AJAX (patrz komentarz przy handlerze w chat.php).
   display:inline-flex, żeby <form> (domyślnie display:block) nie psuł
   wyrównania w rzędzie .chat-bubble-actions (display:flex). Zielony odcień na
   hover (zamiast --accent jak reszta) — celowo "pozytywny" kolor, odróżniający
   od czerwonego "danger" (usuwanie) i pomarańczowego akcentu reszty przycisków.
   Zgłoszenie użytkownika 08.09.2026. */
.chat-bubble-resolve-form { display: inline-flex; }
.chat-bubble-action-resolve:hover { background: #3f8f4f; color: #fff; }
/* Sekcja "Rozwiązane pytania" (chat.php, <details class="year-archive
   chat-resolved-archive">) — .chat-messages daje już pionowy układ dymków z
   odstępami (patrz reguła wyżej), tu tylko dopisujemy górny odstęp od
   przycisku rozwijania i pułap wysokości WŁASNYM przewijaniem, żeby grupa z
   setkami rozwiązanych pytań nie rozciągała całej strony w nieskończoność. */
.chat-resolved-archive { margin-top: 12px; }
.chat-resolved-list { max-height: 420px; padding: 4px 4px 0; }

/* Placeholder "Wiadomość została usunięta" (usuwanie u wszystkich, jak w
   WhatsApp) — bez zdjęcia/pliku/cytatu/reakcji/akcji, tylko wyszarzony,
   kursywą opisany dymek w tym samym miejscu co oryginał. */
.chat-bubble.deleted .chat-bubble-content { opacity: 0.75; }
.chat-bubble-deleted-text {
    display: flex;
    align-items: center;
    gap: 5px;
    font-style: italic;
    color: #8a8378;
}
.chat-bubble.own .chat-bubble-deleted-text { color: rgba(255,255,255,0.85); }

.chat-quick-react-popover {
    display: none;
    position: absolute;
    background: #fff;
    border-radius: 20px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    padding: 5px 7px;
    gap: 3px;
    z-index: 25;
}
.chat-quick-react-popover.open { display: flex; }
.chat-quick-react-popover button {
    background: none;
    border: none;
    font-size: 18px;
    cursor: pointer;
    border-radius: 50%;
    padding: 3px;
    line-height: 1;
    transition: transform .1s, background .1s;
}
.chat-quick-react-popover button:hover { background: rgba(0,0,0,0.06); transform: scale(1.2); }

.chat-more-menu {
    display: none;
    position: absolute;
    background: #fff;
    border-radius: 8px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    padding: 4px;
    z-index: 25;
    min-width: 170px;
}
.chat-more-menu.open { display: block; }
.chat-more-menu-item {
    display: flex;
    align-items: center;
    gap: 7px;
    width: 100%;
    background: none;
    border: none;
    border-radius: 5px;
    padding: 8px 9px;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
    color: #17140f;
}
.chat-more-menu-item:hover { background: rgba(0,0,0,0.06); }
.chat-more-menu-item.danger { color: #c0392b; }
.chat-more-menu-item.danger:hover { background: rgba(192,57,43,0.1); }

.chat-reply-preview {
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 7px 10px;
    margin-bottom: 8px;
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    border-left: 3px solid var(--accent);
    border-radius: 5px;
    font-size: 12px;
}
.chat-reply-preview-body { display: flex; flex-direction: column; gap: 0; min-width: 0; }
.chat-reply-preview-author { font-weight: 700; color: var(--accent-dark); }
.chat-reply-preview-text { color: #6b6560; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-reply-preview-close {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    min-width: 22px;
    padding: 0;
    border-radius: 50%;
    border: none;
    background: rgba(0,0,0,0.06);
    color: #6b6560;
    cursor: pointer;
}
.chat-reply-preview-close:hover { background: rgba(0,0,0,0.12); }

.chat-composer {
    border-top: 1px solid rgba(0,0,0,0.06);
    background: var(--card-bg);
    padding: 12px 16px;
    position: relative;
    flex-shrink: 0; /* pole do pisania nigdy nie ma się ściskać/znikać, gdy na
                        telefonie zabraknie miejsca po otwarciu klawiatury —
                        bez tego domyślny flex-shrink:1 potrafił je razem z
                        nagłówkiem ściskać w stronę zera, zamiast oddać
                        brakujące miejsce WYŁĄCZNIE liście wiadomości
                        (.chat-messages ma flex:1 + min-height:0 właśnie po to) */
}
.chat-composer-row {
    display: flex;
    align-items: center;
    gap: 4px;
    background: #fff;
    border: 1px solid #e2dcd2;
    border-radius: 24px;
    padding: 4px 6px 4px 8px;
    transition: border-color .15s, box-shadow .15s;
}
.chat-composer-row:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent); }
.chat-composer-row input[type=text] {
    flex: 1;
    padding: 9px 6px;
    border-radius: 20px;
    border: none;
    background: transparent;
    /* MUSI być >=16px — poniżej tego iOS Safari łapie ten input jako "mały
       tekst" i przy dotknięciu/pisaniu sam dopasowuje (zooma) widok strony,
       co wygląda jak "rozjeżdżające się" okno czatu w trakcie pisania.
       Ta reguła (klasa+atrybut) ma wyższą specyficzność niż ogólna
       "input { font-size:16px }" z media query niżej, więc jej wcześniejsza
       wartość 13px wygrywała i zoom mimo tamtej reguły dalej występował. */
    font-size: 16px;
    height: auto; /* reset wspólnego "input[type=text] { height:44px }" — patrz identyczny komentarz przy .topbar-search input[type=text] */
}
.chat-composer-row input[type=text]:focus { outline: none; }
.chat-composer .icon-btn { background: rgba(0,0,0,0.045); }
.chat-composer .icon-btn.primary {
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
}
.chat-composer .icon-btn.primary:hover { filter: brightness(1.05); }
.chat-readonly-notice {
    border-top: 1px solid rgba(0,0,0,0.06);
    background: var(--card-bg);
    color: #6b655d;
    padding: 12px 14px;
    font-size: 12px;
    display: flex;
    align-items: center;
    gap: 6px;
}
.chat-attachment-preview:empty { display: none; }
.chat-attachment-preview {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    margin-bottom: 8px;
    background: rgba(0,0,0,0.04);
    border-radius: 5px;
    font-size: 12px;
    color: #45403a;
}
.chat-attachment-preview img { width: 34px; height: 34px; object-fit: cover; border-radius: 4px; }
.chat-attachment-preview .remove-attachment {
    margin-left: auto;
    background: none;
    border: none;
    cursor: pointer;
    color: #a33;
    display: flex;
    align-items: center;
}

/* Podpowiedzi @wzmianki nad polem wiadomości — patrz initMentionAutocomplete()
   w assets/chat.js. Puste :empty { display:none } zapasowo, gdyby JS nie
   dodał klasy .open na czas. */
.chat-mention-suggestions {
    display: none;
    flex-direction: column;
    position: absolute;
    bottom: 58px;
    left: 14px;
    right: 14px;
    background: #fff;
    border-radius: 8px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    max-height: 190px;
    overflow-y: auto;
    z-index: 21;
}
.chat-mention-suggestions.open { display: flex; }
.chat-mention-suggestion {
    padding: 8px 12px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    color: #45403a;
}
.chat-mention-suggestion:hover, .chat-mention-suggestion.active { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent-dark); }

/* Piker emotek z kategoriami (jak w WhatsApp) — zakładki kategorii u góry,
   przewijana siatka poniżej. Używany zarówno przy polu wiadomości
   (#emoji-picker), jak i w rozszerzonym pikerze reakcji
   (#chat-extended-react-picker). */
/* PODEJŚCIE UPROSZCZONE — zamiast walczyć z rozmiarem okienka, po prostu
   scrolluje się całość. Wcześniejsze podejście z flex:1/min-height:0 na
   wewnętrznej siatce + osobnym scrollu TAM
   było kruche i nadal "obcinało" widok bez wyraźnego sygnału, że jest czego
   szukać niżej. Teraz CAŁE okienko (zakładki + siatka razem) to JEDEN,
   zwykły, przewijany kontener — prościej i niezawodnie. Zakładki kategorii są
   "sticky" u góry, więc nie znikają w trakcie przewijania niżej. */
.emoji-picker {
    display: none;
    flex-direction: column;
    position: absolute;
    bottom: 58px;
    left: 14px;
    background: #fff;
    border-radius: 7px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    padding: 8px;
    width: 288px;
    max-width: calc(100vw - 28px);
    max-height: 320px;
    overflow-y: auto;
    overflow-x: hidden;
    /* Suwak od razu widoczny (nie tylko po najechaniu myszką) — bez tego
       duże kategorie (np. "Emocje" — prawie 100 emotek) i tak nie mieszczą
       się na ekranie, a bez śladu suwaka wygląda to jak ucięcie, nie jak
       "przewiń dalej". */
    scrollbar-width: thin;
    scrollbar-color: rgba(0,0,0,0.28) transparent;
    z-index: 20;
}
.emoji-picker::-webkit-scrollbar { width: 6px; }
.emoji-picker::-webkit-scrollbar-track { background: transparent; }
.emoji-picker::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.28); border-radius: 3px; }
.emoji-picker.open { display: flex; }
.emoji-picker-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 1px;
    padding-bottom: 6px;
    margin-bottom: 6px;
    border-bottom: 1px solid rgba(0,0,0,0.08);
    position: sticky;
    top: 0;
    background: #fff;
    z-index: 1;
}
.emoji-picker-tabs button {
    background: none;
    border: none;
    font-size: 16px;
    padding: 4px 7px;
    border-radius: 5px;
    cursor: pointer;
    flex-shrink: 0;
    line-height: 1;
}
.emoji-picker-tabs button.active { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.emoji-picker-tabs button:hover { background: rgba(0,0,0,0.06); }
.emoji-picker-grid {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    align-content: flex-start;
    gap: 3px;
}
.emoji-picker-grid button {
    background: none;
    border: none;
    font-size: 18px;
    cursor: pointer;
    border-radius: 4px;
    padding: 4px;
    line-height: 1;
}
.emoji-picker-grid button:hover { background: rgba(0,0,0,0.06); }
/* Wspólny piker pod formularzami komentarzy (assets/emoji-picker.js) —
   JEDEN egzemplarz doczepiony do <body>, zamiast osobnego pikera "na
   sztywno" wewnątrz każdego formularza (patrz .comment-emoji-wrap wyżej w
   pliku). Naprawia ucinanie pikera przez overflow:hidden ciasnych kart (np.
   .ann-feed-card) — pozycja (top/left) liczona w JS przez
   getBoundingClientRect(), więc trzeba wyzerować bottom/left, które
   .emoji-picker zakłada domyślnie (kotwiczenie nad polem tekstowym), tak
   samo jak przy .reaction-quick-popover niżej. */
.comment-emoji-shared-picker { bottom: auto; left: auto; z-index: 60; }

/* Podpowiedzi @wzmianek pod polami komentarzy — patrz assets/comment-mentions.js
   (23.09.2026, zgłoszenie: "a możemy w komentarzach wywoływać @user_xxx").
   Wygląd 1:1 jak .chat-mention-suggestions niżej w pliku, ale
   position:absolute + top/left liczone w JS (getBoundingClientRect() +
   window.scrollX/scrollY) zamiast stałego "bottom: 58px" — komentarze, w
   odróżnieniu od czatu, NIE mają jednego stałego, zadokowanego u dołu pola,
   tylko wiele rozrzuconych po stronie pól (główne + po jednym na każdą
   odpowiedź/edycję), więc pozycja musi być liczona względem KONKRETNEGO,
   aktywnego pola za każdym razem. */
.comment-mention-suggestions {
    display: none;
    flex-direction: column;
    position: absolute;
    background: #fff;
    border-radius: 8px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    max-height: 190px;
    overflow-y: auto;
    z-index: 60;
}
.comment-mention-suggestions.open { display: flex; }
.comment-mention-suggestion {
    padding: 8px 12px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    color: #45403a;
}
.comment-mention-suggestion:hover, .comment-mention-suggestion.active { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent-dark); }

/* Rządek 6 "szybkich" emotek + "+" pod przyciskiem-emotką w formularzu
   komentarza (21.09.2026, na wzór zrzutu z czatu) — pierwsze, co się otwiera;
   "+" dopiero otwiera pełny, kategoryzowany .comment-emoji-shared-picker
   wyżej. Wygląd celowo 1:1 jak .chat-quick-react-popover (patrz niżej w
   pliku) — osobna klasa (nie ten sam selektor), żeby dało się zmieniać
   niezależnie od czatu. */
.comment-emoji-quick-popover {
    display: none;
    position: absolute;
    background: #fff;
    border-radius: 20px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    padding: 5px 7px;
    gap: 3px;
    z-index: 60;
}
.comment-emoji-quick-popover.open { display: flex; }
.comment-emoji-quick-popover button {
    background: none;
    border: none;
    font-size: 18px;
    cursor: pointer;
    border-radius: 50%;
    padding: 3px;
    line-height: 1;
    transition: transform .1s, background .1s;
}
.comment-emoji-quick-popover button:hover { background: rgba(0,0,0,0.06); transform: scale(1.2); }

/* --- Reakcje emoji na ogłoszeniach/wydarzeniach/postach (Materiały) --- */
.reaction-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 10px; }
.reaction-pill {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 12px;
    padding: 3px 9px;
    border-radius: 20px;
    border: 1px solid rgba(0,0,0,0.1);
    background: #fff;
    color: #45403a;
    cursor: pointer;
    line-height: 1.5;
    transition: transform .1s, border-color .12s;
    /* Przytrzymanie palcem pokazuje chmurkę "kto zareagował" (assets/reactions.js)
       — bez tego telefon (głównie iOS Safari) po ok. pół sekundy sam próbuje
       pokazać swoje menu "Kopiuj"/zaznaczanie tekstu, które wygrywało wyścig
       z naszym licznikiem i przerywało gest, zanim zdążył się dokończyć
       (23.09.2026, zgłoszenie: "teraz po kliknięciu wyłącza moją ikonkę i
       już chmurki nie było"). */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    touch-action: manipulation;
}
.reaction-pill:hover { transform: translateY(-1px); }
.reaction-pill.mine { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-dark); font-weight: 600; }
/* Pigułka jest wizualnie zbudowana z dwóch <span>-ów, ale klikanie/dotykanie
   NIE zależy od tego, w który trafisz (patrz dłuższy komentarz przy
   render_reactions_bar() w includes/reactions.php) — <span> tu wyłącznie dla
   rozmiaru czcionki liczby. */
.reaction-pill-count { font-size: 11px; }
/* Chmurka "kto zareagował" po najechaniu na pigułkę reakcji — jak na
   Facebooku. Zgłoszenie użytkownika 22.09.2026: "jak najedzie się na ikonkę
   w lajku na Facebook pojawia się chmurka kto lajkował". Tekst przychodzi
   gotowy z serwera w data-users (patrz render_reactions_bar() w
   includes/reactions.php) — assets/reactions.js tylko go pokazuje.
   ŚWIADOMIE position:fixed + JEDEN wspólny element w <body> (nie
   position:absolute wewnątrz pigułki) — karty ogłoszeń/wydarzeń
   (.ann-feed-card) mają overflow:hidden (zaokrąglone rogi), więc dymek
   renderowany blisko krawędzi karty byłby przez nie przycinany. To ten sam
   trik co .reaction-quick-popover/.reaction-fb-popover wyżej. 23.09.2026,
   zgłoszenie użytkownika: "super, tylko przycina chmurkę". Na telefonie
   (bez :hover, patrz assets/reactions.js) się nie pokazuje — świadomie,
   kliknięcie zostaje bez zmian (przełącza własną reakcję). */
.reaction-users-tooltip {
    position: fixed;
    z-index: 80;
    background: rgba(20,18,16,0.94);
    color: #fff;
    font-size: 11.5px;
    line-height: 1.4;
    padding: 6px 10px;
    border-radius: 8px;
    max-width: 220px;
    text-align: center;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .12s ease;
    box-shadow: 0 3px 10px rgba(0,0,0,0.22);
}
.reaction-users-tooltip.open { opacity: 1; visibility: visible; }
.reaction-add-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    min-width: 24px;
    padding: 0;
    flex-shrink: 0;
    border-radius: 50%;
    border: 1px dashed rgba(0,0,0,0.18);
    background: transparent;
    color: #8a847d;
    cursor: pointer;
}
.reaction-add-btn:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
/* Pod "+" otwiera się TEN SAM kategoryzowany piker co pod komentarzami
   (.emoji-picker z assets/emoji-picker.js) — dawniejsze 6 emotek szybkiej
   reakcji dawało zbyt ubogi wybór, więc jest ich teraz tyle, ile w pełnym
   pikerze. .reaction-quick-popover dokłada tylko to, czego
   .emoji-picker nie ma z automatu: pozycjonowanie liczone w JS (top/left na
   sztywno, patrz assets/reactions.js), więc trzeba wyzerować bottom/left,
   które .emoji-picker zakłada domyślnie (kotwiczenie nad polem tekstowym). */
.reaction-quick-popover { bottom: auto; left: auto; z-index: 60; }
/* Mały, stały rządek 7 emotek jak na Facebooku (Ogłoszenia — posty i
   komentarze pod nimi, patrz fb_style_reaction_types() w
   includes/reactions.php) — ZAMIAST pełnego, kategoryzowanego .emoji-picker
   wyżej, żeby dodawanie reakcji było tak szybkie jak na Facebooku (jeden
   rządek, bez zakładek/przewijania). Pozycjonowanie liczone w JS (top/left
   na sztywno, ten sam wzorzec co .reaction-quick-popover), stąd position:
   fixed-jak-absolute przez position:absolute + z-index. Zgłoszenie
   użytkownika 21.09.2026: "ograniczmy emotki w lajkach w postach i przy
   komentarzach do takich jak na Facebook i tym samym ułatwmy ich dodawanie
   jak w Facebook". */
/* Wygląd okienka dopasowany (21.09.2026, na prośbę użytkownika, ze
   screenshotem z czatu jako wzorem) do .chat-quick-react-popover niżej w
   pliku — ta sama ciasna "pigułka" i hover — sama LISTA emotek zostaje bez
   zmian (7 emotek jak na Facebooku, patrz fb_style_reaction_palette()), bo
   to osobna, wcześniejsza decyzja. */
.reaction-fb-popover {
    display: none;
    position: absolute;
    z-index: 60;
    background: #fff;
    border-radius: 20px;
    border: 1px solid rgba(0,0,0,0.08);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    padding: 5px 7px;
    gap: 3px;
}
.reaction-fb-popover.open { display: flex; }
.reaction-fb-option {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    padding: 3px;
    border-radius: 50%;
    transition: transform .1s, background .1s;
}
.reaction-fb-option:hover { background: rgba(0,0,0,0.06); transform: scale(1.2); }
@media (max-width: 480px) {
    .reaction-fb-popover { max-width: calc(100vw - 16px); overflow-x: auto; }
}

/* --- Nakładka "Wgrywanie plików..." (Galeria, Ogłoszenia — nowe i edycja)
   — patrz assets/upload-progress.js. z-index wyżej niż .modal (1000), bo ma
   przykryć WSZYSTKO, łącznie z otwartym modalem, jeśli formularz akurat w
   takim się znajduje. Kolory stałe (nie zależą od jasny/ciemny motyw —
   .card i tak zawsze ma jasne tło, patrz komentarz w :root wyżej), tak jak
   reszta elementów kart. Zgłoszenie użytkownika 19.08.2026. */
.upload-progress-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.7);
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.upload-progress-box {
    background: var(--card-bg);
    border-radius: 12px;
    padding: 28px 32px;
    max-width: 320px;
    width: 100%;
    text-align: center;
    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
.upload-progress-spinner {
    width: 34px;
    height: 34px;
    margin: 0 auto 14px;
    border: 3px solid color-mix(in srgb, var(--accent) 20%, transparent);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: upload-progress-spin 0.8s linear infinite;
}
@keyframes upload-progress-spin { to { transform: rotate(360deg); } }
.upload-progress-text { font-size: 13px; color: #4a453f; margin: 0 0 14px; line-height: 1.5; }
.upload-progress-track { height: 6px; background: rgba(0,0,0,0.08); border-radius: 4px; overflow: hidden; }
.upload-progress-fill {
    height: 100%;
    width: 40%;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    border-radius: 4px;
    animation: upload-progress-indeterminate 1.3s ease-in-out infinite;
}
@keyframes upload-progress-indeterminate {
    0% { margin-left: -40%; }
    100% { margin-left: 100%; }
}

/* Pasek "Miejsce na serwerze" w Narzędziach -> Ustawienia — DETERMINOWANY
   (dokładna szerokość w %, w przeciwieństwie do .upload-progress-fill
   wyżej, który tylko animuje się w kółko bez znanego procentu), żeby dało
   się na oko ocenić zajętość względem progu ostrzegawczego. Przeprojekt
   28.08.2026 (zgłoszenie użytkownika: "nie rozumiem tych wartości...
   powinno być chyba ile z czego"). */
.storage-usage-bar { height: 8px; background: rgba(0,0,0,0.08); border-radius: 4px; overflow: hidden; }
.storage-usage-bar-fill { height: 100%; background: linear-gradient(135deg, var(--accent-light), var(--accent-dark)); border-radius: 4px; transition: width .3s ease; }

/* --- Modal ogólny (np. ustawienia grupy czatu) --- */
.modal {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.8);
    z-index: 1000;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.modal.open { display: flex; }
.modal-inner {
    position: relative;
    background: var(--card-bg);
    color: #17140f;
    border-radius: 8px;
    padding: 20px;
    max-width: 440px;
    width: 100%;
    max-height: 85vh;
    overflow-y: auto;
}
/* Jasne tło modala potrzebuje ciemnego wariantu przycisku .btn.secondary
   — domyślny wariant jest dobrany pod ciemne tło strony i na jasnym tle
   był praktycznie niewidoczny (wyglądał jak wyłączony/nieklikalny). */
.modal-inner .btn.secondary { color: #171512; border-color: #d8d3cc; }
.modal-inner .btn.secondary:hover { background: rgba(0,0,0,0.04); }
/* Popup "pytanie tygodnia" na Pulpicie (index.php) — używa ogólnego
   .modal/.modal-inner, ale z własnym wejściem "z przytupem" (ten sam styl
   co ekran wyniku quizu, quiz-result-pop w assets/quiz-take.js) zamiast
   suchego display:flex. Zgłoszenie 10.08.2026. */
@keyframes quiz-week-modal-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes quiz-week-modal-pop {
    0% { transform: scale(.82) translateY(10px); opacity: 0; }
    60% { transform: scale(1.03); opacity: 1; }
    100% { transform: scale(1) translateY(0); }
}
.quiz-week-modal-overlay { animation: quiz-week-modal-fade .25s ease; }
.quiz-week-modal-inner {
    text-align: center;
    max-width: 380px;
    background: linear-gradient(160deg, var(--card-bg) 0%, var(--card-bg) 55%, #fbead6 100%);
    animation: quiz-week-modal-pop .5s cubic-bezier(.34,1.56,.64,1);
}
.quiz-week-modal-icon {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 4px auto 14px;
}
.quiz-week-modal-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--accent-dark); margin-bottom: 6px; }
.quiz-week-modal-title { font-size: 19px; margin: 0 0 8px; }
.quiz-week-modal-sub { font-size: 13px; color: #6b6560; margin: 0 0 18px; }
@media (prefers-reduced-motion: reduce) {
    .quiz-week-modal-overlay, .quiz-week-modal-inner { animation: none; }
}
.modal-inner .close {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 30px;
    height: 30px;
    min-width: 30px;
    padding: 0;
    border-radius: 50%;
    background: rgba(0,0,0,0.08);
    color: #45403a;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}
.modal-inner .close:hover { background: rgba(0,0,0,0.15); }
.modal-inner h3 { margin: 0 0 12px; font-size: 15px; display: flex; align-items: center; gap: 8px; }
.modal-inner label { display: block; font-size: 12px; font-weight: 600; margin: 12px 0 4px; color: #45403a; }
.modal-inner label:first-of-type { margin-top: 0; }
/* font-size MUSI być >=16px — ta reguła obowiązuje w KAŻDYM modalu w
   serwisie (np. "Nowe ogłoszenie", "Nowe wydarzenie" itd. — wszystkie
   popupy używają .modal-inner), więc przy 13px każdy tytuł/pole tekstowe w
   każdym popupie łapało auto-zoom na iOS Safari. Zgłoszenie użytkownika
   22.09.2026, dokładnie ten sam mechanizm co .comment-pill-row textarea /
   .chat-composer-row input[type=text] (patrz komentarze przy nich). */
.modal-inner input[type=text] { width: 100%; padding: 8px 10px; border-radius: 5px; border: 1px solid #ccc; font-size: 16px; }
.member-picker {
    max-height: 180px;
    overflow-y: auto;
    border: 1px solid #ddd;
    border-radius: 5px;
    padding: 8px 10px;
    background: #fff;
}
.member-check { display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 4px 0; cursor: pointer; }
.member-check input { margin: 0; }

/* Picker regionów jako tagi + przycisk "dodaj" (zamiast listy checkboxów) */
.tag-picker.member-picker {
    max-height: none;
    overflow: visible;
    border: none;
    padding: 0;
    background: transparent;
}
.tag-picker-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.tag-picker-empty { font-size: 12px; color: var(--text-chrome-dim, #888); margin: 0 0 8px; }
.tag-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 500;
    color: var(--accent-dark);
    background: color-mix(in srgb, var(--accent) 15%, transparent);
    padding: 4px 6px 4px 10px;
    border-radius: 20px;
    white-space: nowrap;
}
.tag-pill-remove {
    border: none;
    background: none;
    color: inherit;
    cursor: pointer;
    font-size: 15px;
    line-height: 1;
    padding: 2px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    opacity: 0.75;
}
.tag-pill-remove:hover { opacity: 1; }
.tag-picker-add-wrap { display: flex; align-items: center; }
.tag-picker-add-btn {
    font-size: 12px;
    font-weight: 500;
    color: var(--accent-dark);
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    border: 1px dashed color-mix(in srgb, var(--accent) 40%, transparent);
    padding: 5px 12px;
    border-radius: 20px;
    cursor: pointer;
}
.tag-picker-add-btn:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); }
.tag-picker-add-wrap { position: relative; }
.tag-picker-dropdown {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 20;
    width: 260px;
    max-width: 80vw;
    background: #fff;
    border: 1px solid #ddd;
    border-radius: 8px;
    box-shadow: 0 6px 18px rgba(0,0,0,0.12);
    padding: 8px;
}
.tag-picker-filter {
    width: 100%;
    font-size: 13px;
    padding: 6px 8px;
    border-radius: 5px;
    border: 1px solid #ccc;
    box-sizing: border-box;
    margin-bottom: 6px;
}
.tag-picker-options { max-height: 220px; overflow-y: auto; }
.tag-picker-option {
    font-size: 13px;
    padding: 7px 8px;
    border-radius: 5px;
    cursor: pointer;
}
.tag-picker-option:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.tag-picker-options-empty { font-size: 12px; color: var(--text-chrome-dim, #888); margin: 4px 2px; }

/* Picker ikonki grupy czatu (chat.php, okno tworzenia/edycji grupy) — mała,
   stała lista (CHAT_GROUP_ICON_CHOICES w helpers.php), więc zwykłe radio w
   siatce zamiast pełnego tag-pickera z wyszukiwarką jak przy osobach/
   regionach/rolach wyżej. Zgłoszenie użytkownika 05.09.2026: "możemy takiemu
   czat Pomocy dodać ikonkę?". */
.icon-choice-grid { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px 8px; margin-bottom: 4px; }
/* display:flex + align-items/justify-content na SAMEJ etykiecie dodane
   09.09.2026 (zgłoszenie użytkownika ze zrzutem ekranu: "pierwsze jest
   wyżej") — .icon-choice-grid wyżej domyślnie STRETCHOWAŁ każdą etykietę do
   wysokości rzędu, a etykieta bez własnego centrowania czasem renderowała
   swoją zawartość (ukryty input + kółko) przy górnej krawędzi zamiast na
   środku, co przy nierównych wysokościach rzędu wyglądało jak przesunięcie
   pierwszej ikonki w górę.
   Stały rozmiar 44x44 dodany 09.09.2026 (kolejny zrzut ekranu użytkownika:
   "zobacz jak to jest rozjechane") — PRAWDZIWA przyczyna: zaznaczone kółko
   (.icon-choice-swatch niżej) dostaje obwódkę (box-shadow) 2px+3px, czyli
   realnie ok. 10px więcej średnicy niż kółka bez zaznaczenia. Box-shadow NIE
   liczy się do rozmiaru w layoucie, więc zaznaczone kółko po prostu
   "wystawało" poza swoją komórkę siatki bardziej niż sąsiednie — stąd rząd
   wyglądał na nierówny/rozjechany, mimo że same kółka są tej samej
   wielkości. Naprawa: etykieta .icon-choice ma teraz STAŁY rozmiar 44x44 (36px
   kółko + zapas na obwódkę z każdej strony), więc zaznaczenie NIGDY nie
   zmienia efektywnego rozmiaru komórki w siatce — obwódka mieści się w
   zarezerwowanym miejscu i rząd zawsze wygląda równo, niezależnie które
   kółko jest akurat zaznaczone. Gap zmniejszony z 14/12 na 8/8, bo teraz
   wolne miejsce na obwódkę jest już wliczone w rozmiar samej etykiety. */
/* PRAWDZIWA przyczyna "pierwsza ikonka jest wyżej" znaleziona 09.09.2026 (po
   kilku nieskutecznych próbach wyżej — flex-wrap, box-shadow inset, stały
   rozmiar — żadna nie pomogła, bo poprawiały złą właściwość): w tym samym
   pliku jest OGÓLNA reguła ".modal-inner label { margin: 12px 0 4px; }" oraz
   ".modal-inner label:first-of-type { margin-top: 0; }" (linijka ok. 5423),
   które dotyczą KAŻDEGO <label> w oknie modalnym, więc też każdej etykiety
   .icon-choice. ".modal-inner label" ma WYŻSZĄ specyficzność CSS (klasa+tag)
   niż sama ".icon-choice" (tylko klasa), więc mimo że .icon-choice była
   zdefiniowana NIŻEJ w pliku, przegrywała z regułą wyżej. Efekt: pierwsza
   ikonka w całym pickerze (jako pierwszy <label> w oknie) dostawała
   margin-top:0, a WSZYSTKIE pozostałe dostawały odziedziczone margin-top:12px
   — stąd zawsze wyglądała, jakby siedziała wyżej niż reszta rzędu, niezależnie
   od tego, która ikonka była akurat zaznaczona. Selektor
   ".icon-choice-grid .icon-choice" (dwie klasy) ma specyficzność WYŻSZĄ niż
   ".modal-inner label:first-of-type" (klasa+tag+pseudoklasa), więc teraz
   wygrywa gwarantowanie i jawnie zeruje margines dla WSZYSTKICH ikonek. */
.icon-choice-grid .icon-choice { display: flex; align-items: center; justify-content: center; cursor: pointer; position: relative; width: 44px; height: 44px; flex-shrink: 0; margin: 0; }
.icon-choice input { position: absolute; opacity: 0; width: 1px; height: 1px; }
/* NAPRAWA 05.09.2026 (zgłoszenie użytkownika ze zrzutem ekranu: "nie widać
   ikonek"): pierwsza wersja używała --chrome-fill/--text-chrome, które są
   dobrane pod CIEMNY górny pasek (.notif-trigger i podobne) — w ciemnym
   motywie strony --text-chrome to prawie biały (#eee), a --chrome-fill to
   prawie przezroczysty biały odcień. Ten picker siedzi jednak w .modal-inner,
   które ZAWSZE ma jasne, kremowe tło niezależnie od motywu strony (patrz
   komentarz przy .modal-inner wyżej) — stąd niemal biała ikona na niemal
   białym tle była praktycznie niewidoczna w spoczynku (widać ją było tylko
   po zaznaczeniu/najechaniu, bo te stany dodawały kontrastowe tło). Naprawa:
   --accent/--accent-dark zamiast chrome-*, tak jak reszta pickerów w tym
   samym modalu (.tag-pill, .tag-picker-add-btn niżej) — te zmienne są
   dobrane pod czytelność NA JASNYM tle w każdym z motywów kolorystycznych. */
.icon-choice-swatch {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    color: var(--accent-dark);
    transition: background .15s, color .15s, box-shadow .15s;
}
/* NAPRAWA 05.09.2026 (zgłoszenie użytkownika ze zrzutem ekranu: "jedna sie
   rozjechała"): box-shadow NIE liczy się do rozmiaru elementu w layoucie,
   więc przy zbyt małym odstępie między kółkami (poprzednio gap:8px) "obwódka"
   zaznaczonego kółka (dawniej 2px+4px = do 6px na stronę) wystawała poza
   swój kwadrat i nachodziła na sąsiednie kółka, wyglądając jak zniekształcenie/
   przesunięcie siatki. Naprawa dwutorowa: mniejsza obwódka (2px+3px zamiast
   2px+4px) ORAZ większy gap w .icon-choice-grid wyżej (14px/12px) — teraz
   nawet z obwódką jest zapas miejsca do sąsiadów w każdą stronę. */
/* box-shadow zamieniony na INSET 09.09.2026 (kolejne zrzuty ekranu
   użytkownika, nawet z czystym cache: "zobacz jak to jest rozjechane",
   zaznaczone czerwonym kółkiem na ekranie że zaznaczona ikonka wystaje ponad
   i pod rząd pozostałych) — zwykły (zewnętrzny) box-shadow, nawet przy
   zarezerwowanym stałym rozmiarze etykiety (44x44 wyżej), nadal wizualnie
   "urósł" poza sąsiadów w niektórych przeglądarkach/silnikach renderowania.
   INSET box-shadow rysuje się ZAWSZE do wewnątrz elementu, więc fizycznie
   nie ma prawa wystawać poza własne 36x36 kółko — to eliminuje możliwość
   "urośnięcia" zaznaczonej ikonki niezależnie od przeglądarki czy cache. */
.icon-choice input:checked + .icon-choice-swatch {
    background: var(--accent);
    color: #fff;
    box-shadow: inset 0 0 0 2px #fff;
}
.icon-choice input:focus-visible + .icon-choice-swatch { outline: 2px solid var(--accent); outline-offset: 2px; }
.icon-choice-swatch:hover { background: color-mix(in srgb, var(--accent) 25%, transparent); }

/* =========================================================
   RESPONSYWNOŚĆ — telefony i tablety
   ========================================================= */
@media (max-width: 720px) {
    .wrap { padding: 14px 12px 48px; }

    .topbar { gap: 0; padding-bottom: 10px; }

    /* Pełnoekranowa nakładka po tapnięciu hamburgera — wcześniej wysuwane
       menu było zwykłym paskiem pod nagłówkiem, więc treść strony (np.
       powitanie na Pulpicie) było widać tuż pod ostatnią pozycją menu, co
       przy dłuższej liście (Rozrywka/Materiały rozwinięte) i tak wymagało
       przewijania, a jednocześnie NIE dawało wrażenia "prawdziwego" menu na
       cały ekran. .topbar.menu-open staje się teraz fixed na 100% okna,
       kryje całą resztę strony własnym tłem (--bg-1, to samo co tło strony —
       auto dopasowuje się do jasnego/ciemnego motywu i presetów koloru) i
       samo przewija się w pionie, jeśli lista pozycji nie mieści się na
       ekranie. Górny pasek (logo + przełącznik motywu + hamburger) zostaje
       PRZYklejony do góry (position:sticky) wewnątrz tej nakładki, żeby
       przycisk zamykający menu był zawsze pod ręką, nawet po przewinięciu
       w dół. document.body dostaje overflow:hidden na czas otwarcia (patrz
       includes/layout.php), żeby strona POD spodem się nie przewijała razem
       z menu. Zgłoszenie użytkownika 25.09.2026 ("miało być menu na cały
       ekran"), zaakceptowane po wizualizacji. */
    .topbar.menu-open {
        position: fixed;
        inset: 0;
        z-index: 300;
        background: var(--bg-1);
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        /* padding-top 28px, NIE 14px — to jest właściwa przyczyna
           "skakania" logo, którego poprzednia poprawka (.topbar-row1 o
           stałej wysokości) nie usuwała, bo problem był gdzie indziej:
           position:fixed całkowicie IGNORUJE padding WSZYSTKICH przodków,
           nie tylko poziomy. W stanie zamkniętym logo siedzi 14px (.wrap)
           + 14px (.topbar) = 28px od góry ekranu; w otwartym .topbar sam
           staje się position:fixed i traci tamte 14px z .wrap, więc przy
           padding-top:14px logo nagle wskakiwało 14px WYŻEJ w momencie
           otwarcia. 28px tutaj odtwarza dokładnie tę samą odległość, więc
           nic już nie przeskakuje w pionie. Padding poziomy (16px) już
           wcześniej wychodził na to samo, co zamknięty stan (12px .wrap +
           4px .topbar = 16px), więc w poziomie nie było jumpu. Zgłoszenie
           użytkownika 25.09.2026 ("menu z logo skacze, zmienia
           położenie"). */
        padding: 28px 16px 32px;
        margin-bottom: 0;
        border-bottom: none;
    }
    /* padding-bottom/margin-bottom/border-bottom na .topbar-row1 są STAŁE,
       niezależnie od menu-open — wcześniej doklejały się TYLKO w stanie
       otwartym, więc logo/hamburger nagle "podskakiwały" o te ~13px w dół w
       chwili otwarcia (box rzędu zmieniał rozmiar dokładnie w momencie, gdy
       .topbar jednocześnie stawało się position:fixed). Teraz rozmiar rzędu
       jest identyczny zamknięty i otwarty — nic nie skacze, zmienia się
       tylko position:sticky (który poniżej dotyczy WYŁĄCZNIE stanu
       otwartego, bo tylko wtedy jest co przykleić — w stanie zamkniętym
       nie ma czego scrollować). .topbar traci więc swój globalny
       border-bottom na mobile (niżej), bo tę samą linię rysuje teraz
       .topbar-row1 zawsze, więc wygląd zamkniętego paska się nie zmienił.
       Zgłoszenie użytkownika 25.09.2026 ("menu z logo podskakuje"). */
    .topbar { border-bottom: none; }
    .topbar.menu-open .topbar-row1 {
        position: sticky;
        top: 0;
        background: var(--bg-1);
        z-index: 1;
    }

    /* Krótki, zawsze widoczny pasek: logo (większe niż wcześniej — to był
       jeden z powodów tej zmiany) po lewej, hamburger po prawej. Reszta
       (szukajka/dzwonek/użytkownik/menu główne) chowa się domyślnie i
       wysuwa dopiero po tapnięciu hamburgera — patrz .topbar.menu-open
       niżej i skrypt w includes/layout.php. padding-bottom/margin-bottom/
       border-bottom NIE są w osobnej regule pod .menu-open (patrz komentarz
       wyżej) — muszą być tu, w JEDYNEJ regule .topbar-row1 na mobile, żeby
       skrócony zapis "padding: 2px 0 10px" faktycznie wygrał (druga reguła
       .topbar-row1 z samym "padding: 2px 0" nadpisałaby padding-bottom). */
    .topbar-row1 { align-items: center; gap: 10px; flex-wrap: wrap; padding: 2px 0 10px; margin-bottom: 4px; border-bottom: 1px solid var(--chrome-border); }
    .topbar .brand { justify-content: flex-start; font-size: 16px; }
    .topbar .brand img { height: 50px; }
    /* Motyw + hamburger razem, dosunięte do prawej krawędzi (margin-left:auto
       na PIERWSZYM z nich wystarczy — flex sam dosunie oba, patrz kolejność w
       includes/layout.php). Oba powiększone do ok. 44px — 38px to za mało
       jak na wygodny cel dotykowy (standard Apple/Google to ok. 44–48px),
       a to jest teraz jedyny ZAWSZE widoczny
       przycisk otwierający resztę menu na telefonie. */
    #theme-toggle-btn { margin-left: auto; width: 44px; height: 44px; }
    .topbar-menu-toggle { display: inline-flex; width: 44px; height: 44px; }

    .topbar-row1-extras {
        display: none;
        flex-direction: column;
        align-items: stretch;
        gap: 10px;
        width: 100%;
        order: 5;
        margin-top: 4px;
        padding-top: 10px;
        border-top: 1px solid var(--chrome-border);
    }
    .topbar.menu-open .topbar-row1-extras { display: flex; }
    .topbar-search-wrap { max-width: none; }
    /* Pomoc + dzwonek obok siebie w jednym, wycentrowanym wierszu zamiast
       każda na swojej pełnej linii — zgłoszenie użytkownika 20.09.2026
       ("zabiera trochę miejsca jak jest jedna pod drugą"). align-self:center
       (zamiast stretch odziedziczonego z rodzica) trzyma ten wiersz w
       naturalnej, wąskiej szerokości zamiast rozciągać go na 100%. */
    /* align-items: flex-start (NIE center) — inaczej po otwarciu chmurki
       powiadomień .notif-wrap robi się wysoki (przycisk + rozwinięty
       panel), a przy center cały wiersz centrowałby się względem TEJ
       nowej wysokości i wizualnie "zjeżdżałby" ikonkę Pomocy w dół, w
       okolice środka panelu, zamiast zostawić ją przy dzwonku na górze.
       flex-start trzyma oba górne krawędzie (dzwonek i Pomoc, oba 34px)
       w jednej linii niezależnie od tego, czy panel jest otwarty — panel
       i tak rośnie tylko w dół, w obrębie .notif-wrap. Gap zwiększony na
       prośbę użytkownika ("trochę bym je rozsunął"). Zgłoszenie
       użytkownika 20.09.2026 (zrzut ekranu z telefonu). */
    .topbar-icon-row { display: flex; flex-direction: row; align-items: flex-start; justify-content: center; gap: 20px; align-self: center; width: auto; }
    /* width:34px + overflow:visible — bez tego otwarcie panelu poniżej
       (patrz .notif-panel.open { position: static } niżej) sprawiało, że
       .notif-wrap "puchł" do szerokości panelu (320px), bo w kolumnowym
       flex bez wymuszonej szerokości rodzic dopasowuje się do najszerszego
       dziecka. .topbar-icon-row ma justify-content:center, więc spuchnięcie
       notif-wrap przesuwało środek CAŁEGO wiersza (Pomoc+dzwonek) w lewo
       dokładnie w momencie kliknięcia dzwonka — ikonka Pomocy "skakała"
       w bok, mimo że sama się nie zmieniła. Sztywna szerokość 34px
       (dokładnie tyle, co przycisk dzwonka) + overflow:visible pozwala
       panelowi normalnie rozwinąć się WIZUALNIE poza tę wąską kolumnę, nie
       zmieniając już jej wkładu do szerokości .topbar-icon-row — dzwonek i
       Pomoc stoją więc w miejscu bez względu na to, czy panel jest otwarty.
       Zgłoszenie użytkownika 20.09.2026 ("przesuwa ją w lewo, powinna stać
       w miejscu po kliknięciu dzwoneczka"). */
    .topbar .notif-wrap { display: flex; flex-direction: column; align-items: center; width: 34px; overflow: visible; }
    /* align-items:stretch (NIE center jak wcześniej) — z "center" przycisk
       awatar+nazwa+rola kurczył się do szerokości własnej treści i stał na
       środku, więc wyglądał jak wąska, mała "pigułka" pływająca pośrodku
       ekranu zamiast pełnego rzędu jak w zaakceptowanej wizualizacji (tam
       awatar po lewej, strzałka rozwijania przy prawej krawędzi). To NIE
       był efekt nieodświeżonego cache — ten sam kod siedział tu od dawna.
       Zgłoszenie użytkownika 25.09.2026. */
    .topbar .user-menu-wrap { display: flex; flex-direction: column; align-items: stretch; }
    /* border-radius 20px (pigułka) z desktopu, rozciągnięty na pełną
       szerokość, wyglądał jak "banan" — na małym, kompaktowym przycisku
       desktopowym duże zaokrąglenie jest OK, ale przy pełnej szerokości i
       niewielkiej wysokości robi się z niego owal zamiast prostokątnej
       karty. 10px pasuje do reszty kart w tym menu (.topbar-nav-row ma
       7px, pozycje w nim 5px) — trochę większe, bo to wyższy element, ale
       już nie pigułka. padding:12px (zamiast desktopowego 4px/12px)
       dorównuje wysokością pozycjom menu głównego niżej (tam 14px), które
       wcześniej były zauważalnie niższe. Zgłoszenie użytkownika 25.09.2026
       ("pierwsze jest innej wielkości... za bardzo zaokrąglone, że wygląda
       jak banan"). */
    .topbar .user-menu-trigger { width: 100%; padding: 12px; border-radius: 10px; }
    /* Awatar odrobinę większy TYLKO tu (nie globalna .avatar — ta sama
       klasa jest w dziesiątkach miejsc w serwisie) — na pełnoszerokościowym,
       wyższym przycisku bazowe 26px wyglądało nieproporcjonalnie małe.
       Zgłoszenie użytkownika 25.09.2026. */
    .topbar .user-menu-trigger .avatar { width: 35px; height: 35px; font-size: 14px; }
    /* Nazwa/nick rośnie i wypycha odznakę roli + strzałkę do prawej
       krawędzi — bez tego (samo width:100% wyżej) przycisk owszem robił się
       pełnej szerokości, ale cała jego zawartość zostawała stłoczona po
       lewej, z pustym miejscem po prawej zamiast strzałki przy krawędzi. */
    .topbar .user-menu-trigger > span:not(.avatar):not(.badge):not(.chevron) { flex: 1; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; }
    /* UWAGA: reguła align-self:center dla #help-trigger, która kiedyś tu
       była (zgłoszenie 05.09.2026, gdy Pomoc leżała jeszcze bezpośrednio w
       .topbar-row1-extras) została USUNIĘTA 20.09.2026 — od czasu, gdy
       Pomoc i dzwonek są razem w .topbar-icon-row (patrz wyżej), ta reguła
       była bardziej specyficzna (selektor po #id) niż align-items:flex-start
       na rodzicu i po cichu go nadpisywała, więc Pomoc mimo wszystko
       centrowała się względem CAŁEJ wysokości dzwonka + rozwiniętego
       panelu i "skakała" w dół przy otwartej chmurce powiadomień — dokładnie
       to, co align-items:flex-start na .topbar-icon-row miało naprawić.
       Wyrównanie Pomoc/dzwonek teraz w 100% steruje .topbar-icon-row.
       Zgłoszenie użytkownika 20.09.2026 (drugi zrzut ekranu, z odręcznym
       zaznaczeniem — pierwsza próba naprawy nie zadziałała przez tę regułę). */
    .user-menu-dropdown { left: 0; right: 0; margin: 0 auto; }
    /* Na telefonie dropdown profilu NIE może zostać "position: absolute"
       jak na desktopie — nakładałby się jako pływająca warstwa NA wysuwane
       menu główne poniżej (.topbar-nav-row), zasłaniając "Pulpit"/"Trasy":
       po otwarciu menu profilu na telefonie dwa pierwsze linki menu
       głównego znikały, bo dropdown profilu leżał dosłownie na nich, w tym
       samym miejscu na ekranie. Zamiast nakładki: zwykły element w
       przepływie dokumentu, więc tylko odpycha menu główne niżej, nic nie
       zasłaniając. */
    .user-menu-dropdown.open {
        position: static;
        margin: 8px auto 0;
        /* Bez jawnej szerokości dropdown kurczył się do treści (tylko
           min-width:170px z bazowej reguły) — mimo "left:0; right:0;
           margin:0 auto" wyżej, które przy position:static (ustawionym
           właśnie w TEJ regule) nic już nie dają, left/right działają
           tylko na pozycjonowane elementy. Stąd "wąskie menu profilowe"
           zgłoszone przez użytkownika 25.09.2026 — teraz zajmuje pełną
           szerokość, tak jak reszta wysuwanego menu (.topbar-nav-row). */
        width: 100%;
    }
    /* Wygląd menu profilu na telefonie dopasowany do zaakceptowanej
       wizualizacji — WYRAŹNIE drugoplanowy względem głównej nawigacji
       poniżej (mniejszy, przygaszony, z cienkimi liniami między pozycjami),
       a nie ten sam rozmiar co .topbar-nav-row. Poprzednia poprawka
       (25.09.2026, "można troszeczkę powiększyć") ujednoliciła oba rozmiary
       do 14px/14px, ale to zgubiło tę hierarchię — użytkownik przesłał zrzut
       swojej zaakceptowanej wizualizacji jako punkt odniesienia. 12px/12px
       to wciąż wyraźnie więcej niż bazowe desktopowe 9px/10px, więc cel
       dotykowy jest nadal wygodniejszy niż był pierwotnie, tylko subtelnie
       mniejszy niż menu główne (14px/14px). */
    .user-menu-dropdown a { font-size: 13px; padding: 12px; }
    /* Ikony w menu profilu przygaszone (var(--text-chrome-dim)) — na
       desktopie dziedziczą kolor tekstu linku, tu celowo odrębny, stonowany
       odcień, żeby cała sekcja "Konto" wizualnie ustępowała menu głównemu. */
    .user-menu-dropdown a svg { color: var(--text-chrome-dim); flex-shrink: 0; }
    /* Cienkie linie między pozycjami (nie przed pierwszą — stąd "a + a", nie
       ":not(:first-child)", bo pierwszym dzieckiem .user-menu-dropdown jest
       teraz etykieta "Konto", nie link). */
    .user-menu-dropdown a + a { border-top: 1px solid var(--chrome-border); margin-top: 2px; padding-top: 13px; }
    .topbar-section-label {
        display: block;
        font-size: 10px;
        letter-spacing: .04em;
        text-transform: uppercase;
        color: var(--text-chrome-dim);
        padding: 2px 6px 6px;
    }
    /* .notif-panel na telefonie: KILKA podejść "w normalnym przepływie
       dokumentu" (jak dropdown profilu wyżej) po kolei zawodziło w praktyce
       (zgłoszenia użytkownika 20.09.2026, kolejne zrzuty ekranu — raz panel
       obcięty z prawej, raz przesunięty w lewo względem ekranu, raz
       względem ikonki) — bo jego pozioma pozycja w przepływie zależy od
       tego, GDZIE w dokumencie siedzi (wąski 34px .notif-wrap), a próby
       skompensowania tego marginesem, liczonym czy to w CSS, czy w JS na
       podstawie zmierzonej pozycji, za każdym razem gdzieś się rozjeżdżały.
       Ostateczne rozwiązanie: "position: fixed" — position:fixed jest
       ZAWSZE liczone względem widocznego okna (viewport), więc
       left:50% + transform:translateX(-50%) wyśrodkowuje panel pod
       dzwonkiem w sposób odporny na WSZYSTKIE te niuanse na 100% (bez
       żadnej zależności od tego, gdzie w dokumencie faktycznie siedzi
       .notif-wrap). top ustawiany jest inline w JS
       (assets/notifications.js, positionNotifPanelMobile) na podstawie
       zmierzonej pozycji przycisku dzwonka (żeby panel pojawił się tuż pod
       nim, a nie w stałym miejscu niezależnym od tego, gdzie akurat jest
       przycisk). Ponieważ position:fixed wyjmuje panel z normalnego
       przepływu, sam z siebie NIE odepchnąłby menu głównego w dół — dlatego
       dochodzi #notif-panel-spacer (includes/layout.php), pusty element w
       przepływie, któremu JS ustawia wysokość otwartego panelu specjalnie
       w tym celu. */
    .notif-panel.open {
        position: fixed;
        /* top:60px to tylko bezpieczny domyślny fallback (na wypadek gdyby
           JS z jakiegoś powodu nie zdążył/nie mógł zadziałać) — normalnie
           JS (assets/notifications.js, positionNotifPanelMobile) od razu
           nadpisuje to inline stylem dokładną pozycją pod przyciskiem
           dzwonka. Bez tego panel dziedziczyłby "top:calc(100% + 8px)" z
           bazowej reguły .notif-panel — dla position:fixed to 100%
           WYSOKOŚCI CAŁEGO OKNA, czyli panel wylądowałby prawie całkowicie
           poza widocznym ekranem, u samego dołu. */
        top: 60px;
        left: 50%;
        right: auto;
        transform: translateX(-50%);
        margin: 0;
    }

    .topbar-nav-row {
        display: none;
        flex-direction: column;
        align-items: stretch;
        gap: 6px;
        margin-top: 8px;
    }
    .topbar.menu-open .topbar-nav-row { display: flex; }
    /* Harmonijka: gdy menu profilu (.user-menu-dropdown) jest rozwinięte na
       telefonie, menu główne poniżej chowa się — i odwrotnie, po zamknięciu
       menu profilu menu główne wraca (patrz toggleUserMenu()/closeAccountMenu()
       w includes/layout.php, które przełączają klasę "account-open" na
       .topbar). Wcześniej oba dały się rozwinąć naraz, co na wąskim ekranie
       robiło z telefonu długą "wieżę" do przewijania, zanim w ogóle było
       widać właściwą nawigację. Reguła musi stać PO ".topbar.menu-open
       .topbar-nav-row" wyżej (ta sama specyficzność selektora — wygrywa
       kolejność w pliku). Zgłoszenie użytkownika 25.09.2026 (zrzut ekranu z
       telefonu + prośba o wizualizację przed wdrożeniem, zaakceptowana). */
    .topbar.account-open .topbar-nav-row { display: none; }
    /* Odstęp między pozycjami powiększony z 2px na 8px, a same rzędy trochę
       wyższe (padding 14px, czcionka 14px zamiast 12px/13px) — łatwiej
       trafić palcem we właściwą pozycję, mniej ryzyka kliknięcia sąsiedniej.
       Zgłoszenie użytkownika 25.09.2026 ("można troszeczkę powiększyć menu,
       lepiej trafiać palcem") — druga tura powiększenia po wcześniejszej
       (2px→6px/10px→12px). */
    .topbar-nav-row { gap: 8px; }
    .topbar-nav-row a, .topbar-nav-row .nav-dropdown-trigger { font-size: 14px; padding: 14px; justify-content: flex-start; }
    /* Submenu "Rozrywka" na telefonie: przycisk na całą szerokość jak reszta
       pozycji, a panel — tak samo jak .user-menu-dropdown/.notif-panel wyżej
       — NIE może zostać "position:absolute", bo nałożyłby się pływającą
       warstwą na resztę wysuwanego menu. Zamiast tego zwykły element w
       przepływie dokumentu (position:static), więc tylko odpycha kolejne
       pozycje niżej. */
    .topbar-nav-row .nav-dropdown-wrap { width: 100%; }
    .topbar-nav-row .nav-dropdown-trigger { width: 100%; }
    .nav-dropdown-menu.open { position: static; margin: 2px 0 0; padding-left: 10px; }

    .welcome-hero { flex-direction: column; text-align: center; padding-top: 8px; }
    .quick-actions { justify-content: center; }
    .dashboard-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
    .discover-tiles { grid-template-columns: 1fr; gap: 10px; }
    /* Wyższe niż domyślne 180px — zgłoszenie użytkownika 27.08.2026:
       kafelki-karuzele (Galeria/Wydarzenia/Ogłoszenia) na telefonie miały
       być wyższe, w parze z minimalnie obniżonymi kartami statystyk
       wyżej (patrz .stat-card min-height niżej). */
    .discover-tile { height: 220px; }
    /* Ta sama wysokość co kafelki-karuzele tuż wyżej na telefonie, plus
       nieco węższy tytuł/podtytuł, żeby zmieściły się bez zawijania na
       trzy linie na wąskim ekranie. */
    .dashboard-promo-carousel { height: 220px; }
    .dashboard-promo-slide-body { max-width: 100%; padding: 16px 18px; }
    .dashboard-promo-slide-title { font-size: 17px; }
    /* Grafika MOBILNA slajdu (opcjonalna, 28.09.2026) — .dashboard-promo-slide-bg
       to background-size:cover o stałej wysokości i płynnej szerokości, więc
       na wąskim ekranie przycina lewą/prawą krawędź grafiki 4:1 (tam gdzie
       często jest wrysowany tekst). Zgłoszenie użytkownika: "w grafice mam
       obcinane treści [...] czy jedynym rozwiąniem jest inny banner na
       mobilne a inny na desktop". Klasa "has-mobile-image" (dopisywana w
       index.php TYLKO gdy admin faktycznie wgrał osobną grafikę mobilną)
       przełącza, który z dwóch podkładów jest widoczny — bez niej slajd
       nie ma nawet elementu .dashboard-promo-slide-bg-mobile w DOM, więc
       nic się nie zmienia (mobile pokazuje tę samą grafikę co desktop,
       jak dotychczas). */
    .dashboard-promo-slide.has-mobile-image .dashboard-promo-slide-bg-desktop { display: none; }
    .dashboard-promo-slide.has-mobile-image .dashboard-promo-slide-bg-mobile { display: block; }
    .stat-card { padding: 16px; }
    /* Zamiast dociągać krótsze karty do wysokości ShadowManiaka samym
       "height: 100%" (co zostawiało u dołu pustą, martwą przestrzeń) —
       powiększone cyfry/podpisy naturalnie wypełniają tę wysokość treścią.
       Zgłoszenie użytkownika 25.08.2026 ("powiększmy cyfry i te podpisy"). */
    .stat-card .num { font-size: 30px; }
    .stat-card .label { font-size: 13px; }
    /* Mniejsza wersja dużej ikony w tle na wąskich kartach (siatka 2x2 na
       telefonie) — bez tego 110px ikona potrafiła wystawać na sam środek
       węższej karty i przygniatać liczbę/podpis zamiast być dyskretnym
       akcentem w rogu. */
    .stat-card-bgicon { right: -14px; bottom: -18px; }
    .stat-card-bgicon svg { width: 128px; height: 128px; }
    /* Ujednolicona wysokość WSZYSTKICH kart w siatce 2x2 — bez tego górny
       rząd (Trasy/Wydarzenia, tylko liczba+podpis) wychodził wyraźnie
       niższy niż dolny (Ogłoszeń/ShadowManiak, gdzie ShadowManiak ma
       dodatkowo nagłówek i przycisk play), bo CSS Grid liczy wysokość
       KAŻDEGO wiersza osobno, niezależnie od pozostałych — dwa kwadratowe
       rzędy o różnej wysokości wyglądały nierówno. min-height wyższy niż
       naturalna treść którejkolwiek karty (łącznie z ShadowManiak)
       wyrównuje oba rzędy do tej samej wysokości. Zgłoszenie użytkownika
       27.08.2026 ("może je powiększyć na wysokość aby ładnie równo
       wyglądały"). */
    /* Lekko obniżone (190px -> 172px) 27.08.2026 — zgłoszenie użytkownika:
       "minimalnie niższe", w parze z podwyższeniem kafelków-karuzel niżej
       (.discover-tile). */
    .stat-card { min-height: 172px; }
    /* Na telefonie karta ShadowManiak (link + odtwarzacz obok siebie w
       rzędzie) nie mieściła się w wąskiej kolumnie 2-kolumnowej siatki —
       rozjeżdżała kartę poza jej komórkę. Zamiast ciasnego rzędu: treść i
       przycisk play jeden pod drugim.
       Poprawka 25.08.2026: pierwsza wersja tej reguły miała
       "align-items: flex-start", co w kontenerze flex-direction:column
       oznacza "szerokość dziecka = szerokość jego treści" (oś poprzeczna),
       więc tytuł/plakietka/opis wcale nie zawijały się do szerokości karty,
       tylko wystawały poza jej prawą krawędź — dokładnie to, co było widać
       na zrzucie ekranu. "stretch" (wartość domyślna, tu wpisana wprost dla
       jasności) każe dzieciom wypełnić całą szerokość karty, więc tekst
       zawija się normalnie zamiast wypływać poza nią. */
    .stat-card.stat-card-shadowmaniak { flex-direction: column; align-items: stretch; gap: 8px; }
    /* Na telefonie przycisk jest w osobnym wierszu pod tekstem (nie obok
       dużej ikony w rogu), więc nie potrzeba go powiększać ani dociemniać
       jak na desktopie (patrz .stat-card.stat-card-shadowmaniak
       .audycja-play-btn wyżej w pliku) — wraca do pierwotnych, mniejszych
       wymiarów. */
    .stat-card.stat-card-shadowmaniak .audycja-play-btn {
        align-self: flex-start;
        background: rgba(255, 255, 255, 0.18);
        width: 34px;
        height: 34px;
    }
    .stat-card.stat-card-shadowmaniak .audycja-play-btn:hover { background: rgba(255, 255, 255, 0.28); }
    .stat-card.stat-card-shadowmaniak .audycja-play-btn .audycja-play-icon,
    .stat-card.stat-card-shadowmaniak .audycja-play-btn .audycja-pause-icon { width: 20px; height: 20px; }

    .card { padding: 14px; border-radius: 6px; }

    /* Kalendarz w Wydarzeniach: przycisk "jednym dotknięciem" na całą
       szerokość tylko na telefonie — na desktopie to zwykły przycisk (patrz
       .calendar-subscribe-cta wyżej w pliku). */
    .calendar-subscribe-cta { display: flex; width: 100%; padding: 14px 16px; font-size: 15px; text-align: center; }

    /* Quiz na telefonie — węższe marginesy karty, reszta (.quiz-take-wrap)
       jest teraz tak samo "od góry" na telefonie i na desktopie (patrz baza
       wyżej w pliku), więc nic tu już nie trzeba nadpisywać poza tym. */
    .quiz-panel { padding: 20px 16px; }

    /* Trasy: przystanki muszą być użyteczne na telefonie */
    .stop-row {
        grid-template-columns: 1fr;
        gap: 6px;
        background: #d3d3d3;
        padding: 8px;
        border-radius: 5px;
        margin-bottom: 10px;
    }
    .stop-row .remove-stop { width: 100%; }
    .stop-actions { width: 100%; }
    .stop-actions .stop-move, .stop-actions .remove-stop, .stop-actions .stop-map-btn {
        flex: 1;
        padding: 12px 8px;
        font-size: 15px;
        min-height: 44px;
    }

    .list-item { flex-direction: column; align-items: flex-start; gap: 6px; }
    .list-item .badge { align-self: flex-start; }

    .btn-row { flex-direction: column; }
    .btn-row .btn, .btn-row button { width: 100%; text-align: center; }
    /* Wyjątek: przyciski akcji na kartach postów promocyjnych (Materiały) —
       na telefonie pełna szerokość
       zamieniała małe "pigułki" (Podgląd/Kopiuj/Grafika, potem Edytuj/Usuń)
       w wysokie, puste na oko belki, zwłaszcza przy przyciskach z samą
       ikonką. Zostają zwartym, zawijanym rzędem — tak jak .material-file-actions
       przy plikach do pobrania, ten sam wzorzec co tam. */
    .btn-row.materials-post-actions { flex-direction: row; flex-wrap: wrap; }
    .btn-row.materials-post-actions .btn, .btn-row.materials-post-actions button { width: auto; }
    /* .emoji-picker (zakładki kategorii + siatka emotek) leży fizycznie
       WEWNĄTRZ .comment-form-actions/.btn-row (patrz .comment-emoji-wrap
       wyżej w pliku), więc reguła ".btn-row button { width:100% }" tuż nad
       tą — myślana dla głównych przycisków akcji — bez tego wygrywała też
       na malutkich przyciskach zakładek kategorii w pikerze, rozciągając
       każdy do pełnej szerokości i spychając je jeden pod drugim zamiast w
       rządek (zakładki 😀🖐️🐾... każda na osobnym wierszu).
       Wyższa specyficzność (dwie klasy) wygrywa niezależnie od kolejności
       w pliku, więc same przyciski emotek (zakładki i siatka) wracają do
       naturalnej szerokości treści. */
    .btn-row .emoji-picker button { width: auto; text-align: inherit; }
    /* Ten sam problem co wyżej (przy .emoji-picker button), ale dotyczy
       samego PRZYCISKU-EMOTKI (uśmiechnięta buźka), który otwiera piker pod
       komentarzem — to mały, kwadratowy .icon-btn (28×28px), ale leży
       wewnątrz .btn-row razem z przyciskiem "Wyślij"/"Dodaj komentarz",
       więc ogólna reguła ".btn-row button { width:100% }" wygrywała nad
       .icon-btn (wyższa specyficzność: klasa+element kontra sama klasa) i
       rozciągała go na całą szerokość wiersza — zamiast małej kwadratowej
       ikonki wychodził szeroki, płaski "pasek" (zgłoszone jako "przycisk
       emotki wygląda dziwnie"). */
    .btn-row .icon-btn { width: 28px; flex-shrink: 0; }
    /* Przyciski akcji pod ogłoszeniem (announcement_view.php, "Edytuj" /
       "Odepnij z góry listy" / kolor wyróżnienia / "Usuń ogłoszenie") —
       zgłoszenie użytkownika 13.09.2026 ze zrzutem ekranu: na telefonie tylko
       "Edytuj" (bo to <a class="btn">, bezpośrednie dziecko .btn-row) było
       na pełną szerokość, a "Odepnij"/"Usuń ogłoszenie" i rząd z kolorem
       wyglądały jak wąskie, wyśrodkowane "wysepki". Przyczyna: te trzy
       przyciski/rząd siedzą KAŻDY we własnym <form> (osobne żądania POST), a
       to WŁAŚNIE <form> jest bezpośrednim dzieckiem .btn-row (kolumna na
       telefonie) — reguła ".btn-row button { width:100% }" niżej trafiała w
       SAM przycisk, ale 100% z czego? Z szerokości <form>, który przez
       align-items:center (ustawione inline w PHP na .btn-row, dla
       wyrównania w pionie na DESKTOPIE) sam kurczył się do szerokości
       swojej zawartości zamiast rozciągać na cały wiersz — więc "100%"
       przycisku i tak wychodziło tyle, ile przycisk już zajmował. Naprawa:
       same <form> też dostają pełną szerokość. */
    .btn-row form { width: 100%; }
    /* Rząd z kolorem wyróżnienia to jednak WEWNĄTRZ siebie osobny, poziomy
       flex (kwadracik koloru + dwa przyciski obok) — gdyby zostawić samą
       regułę wyżej, oba przyciski w środku dostałyby PO 100% szerokości tego
       (teraz już pełnego) wiersza i szarpałyby się o miejsce. Zamiast tego:
       przyciski dzielą między siebie dostępne miejsce po równo (flex:1), a
       kwadracik koloru zachowuje swój stały rozmiar (flex-shrink:0). */
    .btn-row .announcement-color-form button { width: auto; flex: 1; }
    .btn-row .announcement-color-form input[type=color] { flex-shrink: 0; }

    .calendar { gap: 3px; }
    .calendar .day { min-height: 46px; padding: 4px; font-size: 10px; }
    .calendar .day .evt { font-size: 8px; padding: 1px 5px; }
    .calendar-nav h2 { font-size: 13px; }
    .calendar-nav-title { gap: 6px; }
    .calendar-nav-today { font-size: 10px; padding: 3px 8px; }
    .calendar-nav-btn { width: 30px; height: 30px; }

    /* Na desktopie kafelki postów promocyjnych mają celowo ograniczoną,
       stałą szerokość (190-220px, patrz .materials-post-grid wyżej w pliku)
       — chodziło o to, żeby POJEDYNCZY post nie rozciągał się na całą
       szerokość karty. Na telefonie i tak wychodzi zwykle jeden kafelek w
       rzędzie (za wąsko na dwa), więc ten sam limit szerokości zostawiał
       kafelek węższy niż dostępne miejsce — widoczny, niewykorzystany pas
       pustego miejsca z boku (zgłoszone jako "powinien być dostosowany do
       szerokości"). Na mobile wracamy więc do pełnej szerokości. */
    .materials-post-grid { grid-template-columns: 1fr; }
    .gallery-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; }
    .gallery-grid img, .gallery-grid video { height: 100px; }

    .event-grid { grid-template-columns: 1fr; }

    /* Na telefonie lista grup (u góry) i sama rozmowa (nagłówek + wiadomości
       + pole do pisania) to dwa NIEZALEŻNE, osobno ograniczone obszary —
       każdy ma własną, stałą wysokość i własne przewijanie. Dzięki temu
       pole do pisania zawsze stoi w tym samym miejscu, tuż pod wiadomościami
       — nie trzeba przewijać setek wiadomości ani całej strony, żeby je
       zobaczyć, niezależnie od tego, ile osób/wiadomości jest w grupie.
       (Wcześniejsza wersja dawała stronie rosnąć w nieskończoność wraz
       z historią czatu — dobre dla kilku wiadomości, fatalne dla grupy
       ~100 osób z długą historią.) */
    .chat-layout { flex-direction: column; height: auto; max-height: none; overflow: visible; border-radius: 6px; }
    .chat-sidebar { width: 100%; max-height: 200px; border-right: none; border-bottom: 1px solid rgba(0,0,0,0.08); }
    .chat-main { height: 62vh; min-height: 400px; overflow: hidden; }
    .chat-messages { flex: 1; overflow-y: auto; max-height: none; }
    .chat-bubble { max-width: 88%; }
    .emoji-picker { left: 8px; right: 8px; width: auto; max-width: none; }
    /* Pod komentarzami (trasy/wydarzenia/ogłoszenia) okienko emotek musi na
       telefonie liczyć left/right od SZEROKIEGO wiersza przycisków
       (.comment-form-actions, patrz wyżej w pliku), nie od wąskiego,
       "owijającego się w rozmiar przycisku" .comment-emoji-wrap — inaczej
       left:8px/right:8px liczone od kilkudziesięciopikselowego przycisku
       dają w efekcie wąski, wysoki pasek zamiast czytelnej siatki emotek. */
    .comment-emoji-wrap { position: static; }

    /* Dwa „ekrany" jak w typowych komunikatorach: albo lista rozmów, albo
       otwarta rozmowa na całą dostępną wysokość — nigdy oba naraz, więc
       lista grup (mogąca urosnąć wraz z liczbą grup) nigdy nie ściska
       otwartej rozmowy. Przełącznikiem jest zwykły link (?group=... vs
       chat.php bez parametru) — działa bez JS, każdy stan ma swój URL. */
    .chat-layout--list .chat-main { display: none; }
    .chat-layout--list .chat-sidebar { max-height: none; }
    .chat-layout--conversation .chat-sidebar { display: none; }
    .chat-back-link {
        display: none;
        align-items: center;
        justify-content: center;
        width: 30px;
        height: 30px;
        margin-right: 2px;
        border-radius: 50%;
        color: #17140f;
        flex-shrink: 0;
    }
    .chat-layout--conversation .chat-back-link { display: inline-flex; }
    .chat-back-link:hover { background: rgba(0,0,0,0.06); }

    /* Otwarta rozmowa na telefonie = pełny ekran, jak w prawdziwym
       komunikatorze — nie kawałek strony pod headerem/bannerem serwisu.
       body.chat-fullscreen dodawane jest server-side w chat.php dokładnie
       wtedy, gdy pokazujemy konkretną, wybraną rozmowę (a nie listę grup).
       Reszta chrome strony (topbar, baner sekcji, stopka) chowa się na czas
       trwania rozmowy — wraca po kliknięciu "←" (zwykły link, nie JS). */
    body.chat-fullscreen .topbar,
    body.chat-fullscreen .section-banner,
    body.chat-fullscreen .site-footer {
        display: none;
    }
    body.chat-fullscreen .wrap { padding: 0; max-width: none; }
    /* Zabezpieczenie na wypadek, gdyby .chat-layout--conversation przez
       ułamek sekundy (np. w trakcie animacji otwierania klawiatury) było
       niższe niż realnie widoczny obszar — zamiast goły, ciemny motyw
       strony (czarne tło) prześwitujący spod spodu, widać ten sam jasny
       kolor co samo okno czatu (.chat-layout), więc nawet chwilowa
       nieszczelność nie rzuca się w oczy jako "coś się zepsuło". */
    body.chat-fullscreen { overflow: hidden; background: var(--card-bg); }
    .chat-layout--conversation {
        position: fixed;
        inset: 0;
        z-index: 200;
        /* ".section-banner + *" (wyżej w pliku) dokłada margin-top:
           var(--banner-gap-after) do KAŻDEGO elementu zaraz po banerze w
           DOM — także do tego, mimo że sam baner jest tu ukryty
           (body.chat-fullscreen .section-banner{display:none}). To
           selektor po sąsiedztwie w znacznikach, nie po widoczności, więc
           display:none na banerze nic nie zmienia. Z position:fixed +
           inset:0 ten odziedziczony margines nie centruje/przesuwa ładnie,
           tylko zsuwa cały kontener w dół o tyle samo, ile wynosi
           --banner-gap-after — realny błąd zgłoszony przez usera: pasek do
           pisania chowający się pod paskiem przeglądarki, widoczny tylko
           w motywie/ustawieniu, gdzie --banner-gap-after > 0. Zerowanie
           tutaj, żeby ten kontener był zawsze NIEZALEŻNY od wartości
           tokenu banera w obu motywach. */
        margin-top: 0;
        /* Nadpisuje overflow:visible z ogólnej reguły .chat-layout na telefonie
           (potrzebne TAM dla widoku listy grup, żeby nic się nie ucinało przy
           height:auto) — tu, w widoku otwartej rozmowy, overflow musi być
           hidden, żeby żaden element (np. pole do pisania, gdyby przez
           chwilę nie zmieścił się w nowej, mniejszej wysokości po otwarciu
           klawiatury) nie mógł "wystawać" poza fizycznie widoczny ekran
           zamiast zostać ściśnięty/przewinięty w środku. */
        overflow: hidden;
        height: 100vh;
        /* 100vh na iOS Safari to wysokość "dużego" viewportu — licząc w to
           obszar zasłonięty przez (chowający się) pasek adresu/przyciski na
           dole. Efekt: pole do pisania wypadało pod paskiem przeglądarki i
           nie dało się do niego doscrollować (overflow:hidden na body).
           100dvh liczy realnie widoczny obszar (łącznie z otwartą klawiaturą
           na telefonie, patrz interactive-widget=resizes-content w meta
           "viewport", includes/layout.php) i nadpisuje wartość powyżej w
           przeglądarkach, które to wspierają — starsze po prostu ją
           zignorują i zostaną przy 100vh. Świadomie NIE liczymy tego już w
           JS przez window.visualViewport (patrz historia zmian w chat.js) —
           dvh jest natywne, aktualizowane przez przeglądarkę samo, bez
           ryzyka rozjazdu, na jakie trafialiśmy z ręcznym liczeniem. */
        height: 100dvh;
        max-height: none;
        border-radius: 0;
        border: none;
        box-shadow: none;
        padding-top: env(safe-area-inset-top);
        /* env(keyboard-inset-height) to część VirtualKeyboard API (Chrome/
           Android — patrz navigator.virtualKeyboard.overlaysContent w
           chat.js). Gdy ta API jest dostępna, WŁĄCZAMY overlaysContent, co
           oznacza, że przeglądarka PRZESTAJE w ogóle zmieniać dvh/viewport
           po otwarciu klawiatury (my musimy sami zarezerwować dla niej
           miejsce) — stąd zsumowane z safe-area-inset-bottom, nie zamiast
           niego. Na przeglądarkach bez tej API (iOS Safari, Firefox) ta
           zmienna po prostu liczy się jako 0px — bez zmiany zachowania,
           zostaje sam dvh + interactive-widget jak dotychczas. */
        padding-bottom: calc(env(safe-area-inset-bottom) + env(keyboard-inset-height, 0px));
    }
    .chat-layout--conversation .chat-main { height: auto; flex: 1; min-height: 0; }

    input, textarea, select, button, .btn { font-size: 16px; } /* blokuje auto-zoom na iOS */
}

@media (max-width: 420px) {
    .dashboard-grid { grid-template-columns: 1fr; }
    .gallery-grid { grid-template-columns: repeat(2, 1fr); }
    .event-grid { grid-template-columns: 1fr; }
}

/* --- Blokujący komunikat od moderatora/admina (patrz includes/layout.php,
   pending_system_notice_for + notice_ack.php) — celowo bez przycisku
   zamknięcia w rogu ani zamykania kliknięciem w tło, jedyne wyjście to
   "Rozumiem", żeby faktycznie liczyło się jako potwierdzenie odczytania. --- */
.notice-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.75);
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.notice-modal {
    background: var(--card-bg);
    border-radius: 10px;
    padding: 22px;
    max-width: 440px;
    width: 100%;
    box-shadow: 0 20px 60px rgba(0,0,0,0.4);
}
.notice-modal h3 { margin: 0 0 12px; font-size: 16px; color: #17140f; display: flex; align-items: center; gap: 8px; }
.notice-modal-text { font-size: 14px; color: #33302b; line-height: 1.5; margin-bottom: 18px; }
.notice-modal form { margin: 0; }

/* ===========================================================================
   Gra "Statki" (statki.php i okolice) — piracka plansza. Tło planszy to
   prawdziwy obraz (assets/battleship/board.jpg na desktopie,
   board-mobile.jpg na telefonie), a siatka 10x10 nałożona jest na niego jako
   .battleship-grid, pozycjonowana procentowo — pozycja zostaje poprawna
   niezależnie od tego, w jakiej szerokości ostatecznie renderuje się
   obrazek. Dokładne wartości % są przy .battleship-grid niżej. Kapitan
   (assets/battleship/captain.png, karykatura prezesa stowarzyszenia jako
   pirata) zajmuje miejsce klasycznej róży wiatrów w rogu mapy.
   =========================================================================== */
/* Komunikat "czyja tura" w statki_game.php (#bship-turn-banner, ustawiany z
   assets/battleship.js) — pigułka z kolorem zależnym od stanu: czerwony/
   accent i większy tekst gdy to Twoja tura (wezwanie do akcji), stonowana
   szara pigułka gdy czekamy na przeciwnika/bota, zielona/czerwona jak
   badge-ok/badge-warn po zakończeniu gry — te same kolory wygranej/
   przegranej co w "Ostatnie wyniki" na statki.php. */
.bship-turn-banner {
    display: inline-block;
    margin-top: 6px;
    padding: 6px 16px;
    border-radius: 20px;
    font-size: 14px;
    font-weight: 700;
}
.bship-turn-banner.bship-turn-mine { background: var(--accent); color: #fff; }
/* Jasna, półprzezroczysta pigułka (nie ciemna) — banner siedzi teraz na
   .battleship-game-banner, czyli na zdjęciu/ciemnym tle, nie na jasnej
   karcie jak wcześniej; ciemny tekst na prawie przezroczystym tle byłby
   nieczytelny. */
.bship-turn-banner.bship-turn-waiting { background: rgba(255, 255, 255, 0.16); color: #fff; }
.bship-turn-banner.bship-turn-win { background: #3f8f4f; color: #fff; }
.bship-turn-banner.bship-turn-lose { background: #a32d2d; color: #fff; }

.battleship-mode-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
@media (max-width: 780px) { .battleship-mode-grid { grid-template-columns: 1fr; } }

/* Dwie kolumny pod siatką trybów gry (Twoje gry + Ostatnie wyniki | Ranking
   graczy — Statki, patrz statki.php). Ten sam wzorzec co
   .dashboard-columns/.dashboard-col na Pulpicie: kolumny niezależne (flex,
   nie grid par), każda tak wysoka, ile potrzebuje jej treść — karta
   rankingu przy 1-2 graczach nie musi rozciągać się do wysokości sąsiedniej
   kolumny. Świadomie własna klasa (.battleship-columns), a nie recykling
   .dashboard-columns — tamta ma reguły mobilne specyficzne dla widgetów
   Pulpitu, które złapałyby też kartę rankingu Statków (ta sama klasa
   .leaderboard-compact) i wepchnęły ją nie tam, gdzie trzeba.
   Kolejność mobilna: karta rankingu (z wtopionym bilansem, patrz sekcja
   "Bilans wygranych/przegranych" niżej w tym pliku) zaraz pod "Twoje gry" —
   stąd display:contents na .battleship-col poniżej breakpointu (ten sam
   patent co .dashboard-col na Pulpicie) rozpuszcza kolumny, a explicit
   order na jawnych klasach kart (.battleship-mygames-card/-ranking-card,
   odpornych na to, że karty są warunkowe) ustawia kolejność w powstałym,
   wspólnym stosie. */
.battleship-columns { display: flex; align-items: flex-start; gap: 16px; }
.battleship-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.battleship-mygames-card { order: 1; }
.battleship-ranking-card { order: 2; }
@media (max-width: 780px) {
    /* align-items:stretch — bez tego, na mobilnych, rodzic dziedziczy
       desktopowe align-items:flex-start (potrzebne tam na osi poprzecznej,
       żeby karta rankingu przy 1-2 graczach nie rozciągała się na wysokość
       sąsiada), które po zmianie na flex-direction:column staje się osią
       POZIOMĄ — każda karta kurczyłaby się do szerokości własnej treści
       zamiast rozciągać na 100%. Ten sam patent co .dashboard-columns na
       Pulpicie.
       gap:0 — NAPRAWA 20.09.2026 (zgłoszenie użytkownika: "na mobilnych
       okienko rankingu jest z większym odstępem niż pozostałe okienka").
       Na desktopie ten gap rozdziela dwie kolumny OBOK siebie (poziomo), ale
       .battleship-col rozpuszcza się w display:contents, więc po zmianie na
       flex-direction:column ten sam gap zaczyna DODATKOWO rozsuwać karty w
       pionie — a każda .card ma już WŁASNY margin-bottom:16px (patrz .card
       wyżej), więc bez wyzerowania gap odstęp między "Twoje gry" a kartą
       rankingu wychodził podwójny (16px marginesu + 16px gap = 32px) —
       zauważalnie większy niż zwykłe 16px między pozostałymi kartami na
       stronie, które o gap nie mają pojęcia. */
    .battleship-columns { flex-direction: column; align-items: stretch; gap: 0; }
    .battleship-col { display: contents; }
}

/* Trzy kafelki trybów gry — kolorowe gradienty + duża, przyblakła ikona w
   rogu, ten sam wzorzec co .stat-card/.stat-card-tint-* na Pulpicie
   (index.php), świadomie skopiowany 1:1 (te same trzy odcienie
   niebieski/różowy/bursztynowy) dla spójności z resztą serwisu. */
.battleship-mode-card {
    margin: 0;
    padding: 18px;
    border: 1px solid var(--card-border);
    border-radius: 10px;
    position: relative;
    z-index: 0;
    /* overflow:visible (nie hidden) — karta ma pływające okienko szukajki
       osoby do wyzwania (.tag-picker-dropdown, position:absolute), które
       overflow:hidden by przycinało na zaokrąglonym dolnym rogu. Ikona w
       tle i zaokrąglone rogi paska przycisku (własny border-radius:9px,
       patrz niżej) wyglądają identycznie bez przycinania karty. */
    overflow: visible;
    min-height: 172px;
    display: flex;
    flex-direction: column;
    background: linear-gradient(150deg, #fdf1e4, var(--card-bg) 60%);
    color: #171512;
}
.battleship-mode-card .title { color: inherit; }
.battleship-mode-card-icon {
    position: absolute;
    right: -18px;
    bottom: -22px;
    z-index: -1;
    pointer-events: none;
    color: #fff;
    opacity: 0.2;
}
.battleship-mode-card-icon svg { width: 140px; height: 140px; display: block; }
.battleship-mode-card-icon-blue { transform: rotate(-8deg); }
.battleship-mode-card-icon-pink { transform: rotate(6deg); }
.battleship-mode-card-icon-amber { transform: rotate(-6deg); }

.battleship-mode-card.tint-blue { background: linear-gradient(150deg, #2f74c4, #123457 75%); border-color: #5b9bdb; color: #fff; }
.battleship-mode-card.tint-blue p.muted { color: rgba(222, 236, 255, 0.9); }
.battleship-mode-card.tint-pink { background: linear-gradient(150deg, #c14a76, #611f37 75%); border-color: #e07ea3; color: #fff; }
.battleship-mode-card.tint-pink p.muted { color: rgba(255, 226, 238, 0.9); }
.battleship-mode-card.tint-amber { background: linear-gradient(150deg, #c9791a, #5f3705 75%); border-color: #f0b458; color: #fff; }
.battleship-mode-card.tint-amber p.muted { color: rgba(255, 238, 218, 0.9); }

/* Tag-picker (wybór osoby do wyzwania, patrz statki.php) osadzony na
   kolorowej karcie — domyślny styl tego komponentu jest pomyślany pod jasne
   tło (Email, Narzędzia), więc półprzezroczyste akcentowe tło i ciemny
   tekst gubiłyby się na nasyconym gradiencie tej karty. Rozwijana lista
   wyników (.tag-picker-dropdown) zostaje biała bez zmian — to i tak
   pływające okienko nad kartą, nie część jej tła. Kolor tekstu w dropdownie
   trzeba zresetować jawnie: .tint-blue/.tint-pink/.tint-amber (niżej)
   ustawiają na całej karcie color:#fff, co bez tego spływałoby też do
   białego okienka rozwijanego (biały tekst na białym tle). */
.battleship-mode-card .tag-picker-dropdown { color: #171512; }
.battleship-mode-card .tag-picker-empty { color: rgba(255, 255, 255, 0.75); }
.battleship-mode-card .tag-pill { color: #171512; background: rgba(255, 255, 255, 0.92); }
.battleship-mode-card .tag-picker-add-btn {
    color: #fff;
    background: rgba(255, 255, 255, 0.14);
    border: 1px dashed rgba(255, 255, 255, 0.5);
}
.battleship-mode-card .tag-picker-add-btn:hover { background: rgba(255, 255, 255, 0.24); }

/* Przyciski w tych kolorowych kartach — przezroczysty pasek, tylko lekko
   przyciemniający tło karty (rgba(0,0,0,.05), nie nowy kontrastowy kolor),
   przyklejony na pełną szerokość do dolnej krawędzi karty (ujemne marginesy
   znoszące padding karty, 18px). Zaokrąglone dolne rogi paska zapewnia
   własny border-radius przycisku (niżej, 0 0 9px 9px) — karta ma
   overflow:visible (patrz wyżej), więc nie można liczyć na przycinanie
   przez kartę samą; 9px vs 10px karty jest wystarczająco bliskie, żeby
   wizualnie nie było widać różnicy.

   margin-top:auto (nie stały odstęp) — karta "Wyzwij konkretną osobę" ma
   więcej treści niż pozostałe dwie, więc siatka (grid, stretch) rozciąga
   wszystkie trzy karty do wysokości tej najwyższej; auto każe paskowi
   wchłonąć całą wolną przestrzeń nad sobą i przykleić się do dołu w
   każdej karcie, zamiast "unosić się" w połowie krótszych kart. Stały
   odstęp min. 14px między treścią a paskiem, na wypadek gdyby auto nie
   miał wolnej przestrzeni do wchłonięcia, zapewnia padding-top na samym
   pasku (dodaje się zawsze, niezależnie od marginesu). */
.battleship-mode-card > .btn-row { margin: auto -18px -18px; padding-top: 14px; }
.battleship-mode-card .btn-row button {
    width: 100% !important;
    background: rgba(0, 0, 0, 0.18);
    color: #fff;
    border: none;
    box-shadow: none;
    border-radius: 0 0 9px 9px;
    padding: 14px 18px;
    font-weight: 600;
}
.battleship-mode-card .btn-row button:hover { background: rgba(0, 0, 0, 0.28); }
.battleship-mode-card .btn-row button.secondary { border-top: 1px solid rgba(255, 255, 255, 0.18); }

/* Plansza 780px szeroka (panel floty zwężony) — przy mniejszej szerokości
   na szerokim desktopie zostawało dużo pustego, jasnego tła karty obok
   stosunkowo małej mapy. Gap 40px daje kapitanowi (right:-6%, patrz
   .battleship-captain-badge niżej) trochę miejsca na delikatne wystawanie
   poza ramkę planszy bez dotykania panelu floty obok — bez tego jego
   broda/kapelusz nachodziły na panel, zasłaniając początek tekstu statusu. */
.battleship-place-layout { display: grid; grid-template-columns: minmax(0, 780px) minmax(210px, 260px); gap: 40px; align-items: start; }
/* Rozgrywka (statki_game.php) — obie plansze jedna pod drugą (nie obok
   siebie), zawsze w pełnej szerokości 780px, dokładnie ten sam układ i
   procenty siatki co na ekranie rozstawiania floty — .battleship-grid/
   .battleship-captain-badge nie mają żadnej reguły specyficznej dla dwóch
   plansz obok siebie, więc wychodzi identycznie automatycznie. Mobilny
   breakpoint niżej został tylko dla
   .battleship-place-layout (plansza+panel), .battleship-game-layout jest już
   jednokolumnowa bez warunku szerokości. */
.battleship-game-layout { display: grid; grid-template-columns: 1fr; gap: 60px; align-items: start; }
@media (max-width: 780px) {
    .battleship-place-layout { grid-template-columns: 1fr; }
}

.battleship-place-panel { position: sticky; top: 14px; }
/* Tytuły dwóch okienek plansz na ekranie rozgrywki ("Plansza przeciwnika —
   strzelaj tutaj" / "Twoja plansza") — zwykły systemowy font (bez własnej
   deklaracji font-family, dziedziczony z <body>), kolor #17140f (ta sama
   "atramentowa" czerń co reszta nagłówków kart w serwisie, .card h2 i inne)
   i cienka linia pod spodem. */
.battleship-board-title {
    font-size: 15px;
    font-weight: 700;
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 10px;
    padding-bottom: 7px;
    color: #17140f;
    border-bottom: 2px solid rgba(23, 20, 15, .18);
}
.battleship-board-title svg { flex-shrink: 0; opacity: .85; }

/* justify-content:center — bez tego chipy floty (mniej niż 4, w miarę
   stawiania statków) trzymały się lewej krawędzi karty, zostawiając sporo
   pustego miejsca po prawej. Karta i tak jest wąska (plansza obok), więc
   wyśrodkowanie nie psuje niczego przy pełnych 4 chipach na starcie. */
.battleship-fleet-list { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 10px 0; min-height: 28px; }
/* Chipy floty są klikalne <button> — kliknięcie chipa wybiera konkretny
   statek (ważne przy dwóch Fregatach — inaczej nie dałoby się rozróżnić,
   którą z dwóch identycznych wybieramy) jako "aktywny", podświetlony złotą
   obwódką, a dopiero potem klik na planszy go stawia. */
.battleship-fleet-chip {
    display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600;
    color: #fff; background: linear-gradient(135deg, #1b6b6b, #0e3f3f);
    padding: 4px 12px 4px 4px; border-radius: 20px; white-space: nowrap;
    box-shadow: 0 0 0 2px transparent;
}
.battleship-fleet-chip--active {
    box-shadow: 0 0 0 2px #f0b458;
    background: linear-gradient(135deg, #2a9d8f, #10534c);
}
/* Miniaturka prawdziwego zdjęcia statku na chipie (te same pliki co na
   planszy) — okrągła "plakietka" tak jak awatary w serwisie (.avatar),
   object-fit:contain (nie cover), bo statki są wydłużone/poziome — cover
   ucinałby dziób/rufę w okrągłym kadrze. */
.battleship-fleet-chip-thumb {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
    padding: 3px;
    object-fit: contain;
    flex-shrink: 0;
}

/* .battleship-board-frame jest celowo oddzielone od .battleship-board:
   ramka mapy (obrazek + siatka) ma overflow:hidden (żeby zaokrąglone rogi
   ładnie przycinały obrazek), ale kapitan w rogu (.battleship-captain-badge)
   musi móc wystawać poza tę ramkę jako dekoracyjny akcent — stąd jest jej
   rodzeństwem w osobnym, nieprzyciętym kontenerze, a nie dzieckiem
   .battleship-board. */
.battleship-board-frame {
    position: relative;
    width: 100%;
    max-width: 780px;
    margin: 0 auto;
}
.battleship-board {
    position: relative;
    width: 100%;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 10px 30px rgba(0,0,0,0.35), 0 0 0 3px #caa24a, 0 0 0 5px #2c1c10;
    background: #0e3540;
}
/* <picture> jest domyślnie inline — bez tego jednego wiersza potrafi zostawić
   ledwo widoczną szparę pod obrazkiem (typowy problem inline-elementów),
   mimo że sam .battleship-board-img ma już display:block. Dodane razem z
   przejściem na <picture>/<source media> (dwie grafiki planszy, patrz
   bship_render_board_img() w includes/battleship.php), 18.09.2026. */
.battleship-board picture { display: block; }
.battleship-board-img { display: block; width: 100%; height: auto; user-select: none; -webkit-user-drag: none; }

/* Siatka 10x10 nałożona procentowo na obrazek planszy — nie na cały
   obrazek, tylko na zmierzony obszar czystej wody wewnątrz ramki, z
   miejscem zostawionym celowo na dekoracyjnego pirata i oznaczenia A-J/1-10
   w marginesie. Wartości width/height są tak dobrane, żeby wyszły KWADRATOWE
   pola mimo różnych proporcji desktopowego (1600x900) i mobilnego (900x1600)
   obrazka: width%*obrazekWidth == height%*obrazekHeight dla obu orientacji.
   left:25.3% = (100%-49.4%)/2 na desktopie — siatka wyśrodkowana względem
   planszy, kapitan (patrz .battleship-captain-badge niżej) dostaje miejsce
   na wystawanie poza ramkę zamiast zajmować je ręcznym przesunięciem
   siatki w bok. */
.battleship-grid {
    position: absolute;
    left: 25.3%;
    top: 6%;
    width: 49.4%;
    height: 88%;
    display: grid;
    grid-template-columns: repeat(10, 1fr);
    grid-template-rows: repeat(10, 1fr);
}
@media (max-width: 780px) {
    .battleship-grid { left: 6%; top: 27%; width: 89%; height: 50%; }
}

/* Oznaczenia A-J / 1-10 — żywy HTML/CSS, przylegają do krawędzi siatki (nie
   do krawędzi obrazka, siatka stoi w głębi ramki), więc mają osobny zestaw
   procentów na desktop i telefon, tak jak sama siatka. clamp() na
   font-size, żeby dwucyfrowe "10" zmieściło się w wąskim pasku bocznym
   nawet na małym ekranie. */
.battleship-coord-cols, .battleship-coord-rows {
    position: absolute;
    display: grid;
    color: #e8d9b8;
    font-weight: 700;
    text-shadow: 0 1px 2px rgba(0,0,0,0.6);
    pointer-events: none;
    user-select: none;
}
.battleship-coord-cols {
    left: 25.3%;
    width: 49.4%;
    height: 3.5%;
    grid-template-columns: repeat(10, 1fr);
    align-items: center;
    justify-items: center;
    /* Górny pułap celowo niski — na szerokim desktopie wyższa wartość
       dobijała do maksimum i litery wyglądały grubo/duże obok delikatnych
       na telefonie. */
    font-size: clamp(9px, 1.2vw, 12px);
}
.battleship-coord-cols-top { top: 2.2%; }
.battleship-coord-cols-bottom { top: 94.3%; }
.battleship-coord-rows {
    top: 6%;
    height: 88%;
    width: 2.2%;
    grid-template-rows: repeat(10, 1fr);
    align-items: center;
    justify-items: center;
    font-size: clamp(8px, 1vw, 11px);
}
.battleship-coord-rows-left { left: 22.8%; }
.battleship-coord-rows-right { left: 75%; }
@media (max-width: 780px) {
    .battleship-coord-cols { left: 6%; width: 89%; height: 2.5%; }
    .battleship-coord-cols-top { top: 24.3%; }
    .battleship-coord-cols-bottom { top: 77.2%; }
    .battleship-coord-rows { top: 27%; height: 50%; width: 4%; }
    .battleship-coord-rows-left { left: 1.7%; }
    .battleship-coord-rows-right { left: 95.3%; }
}

.battleship-cell {
    position: relative;
    border: 1px solid rgba(255,255,255,0.16);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color 0.15s ease;
}
/* Delikatny podkład pod grafiką statku (.battleship-ship-piece niżej) —
   zostaje widoczny na brzegach, gdzie sylwetka statku (elipsa-w-elipsie,
   nie prostokąt) nie pokrywa całego kwadratowego pola. */
.bship-cell-ship { background: rgba(20, 20, 15, 0.22); }
.bship-cell-clickable { cursor: pointer; }
.bship-cell-clickable:hover { background: rgba(255, 255, 255, 0.28); }
.bship-cell-invalid { background: rgba(224, 18, 26, 0.55) !important; }

/* Prawdziwe sylwetki statków (zdjęcia, patrz BSHIP_SHIP_ASSETS w
   assets/battleship.js) zamiast płaskiego prostokąta. Pozycjonowanie
   (left/top/width/height/transform) ustawiane w JS jako procenty — patrz
   komentarz przy bshipMakeShipPieceEl() w assets/battleship.js po
   wyjaśnienie sztuczki z obrotem dla pionowych statków. */
.battleship-ship-piece {
    position: absolute;
    pointer-events: none;
    z-index: 1;
    filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
}
.battleship-ship-piece img { display: block; width: 100%; height: 100%; }

/* Pudło = plusk wody, trafiony = szary dym/wybuch, zatopiony = ognisty
   wybuch (mocniejszy, dla ostatniego trafienia w statek) — dwa różne
   warianty wybuchu, żeby zatopienie wyglądało "mocniejsze" niż zwykłe
   trafienie. Obrazek celowo większy niż samo pole (szerokość/wysokość >
   100%), żeby "wybuchał"/"pryskał" poza kratkę zamiast być ciasno wciśnięty
   w jedno pole — ten sam efekt wystawania co plakietka kapitana. z-index
   nad sąsiednimi polami, żeby się nie chowało pod nimi. */
.bship-cell-miss::after {
    content: '';
    position: absolute;
    width: 165%;
    height: 165%;
    background-image: url('battleship/splash.png');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    pointer-events: none;
    z-index: 1;
}
.bship-cell-hit {
    background: rgba(224, 18, 26, 0.35);
}
/* "Trafiony" ma skromniejszy wybuch (120%, blisko własnej komórki) niż
   "zatopiony" (190%, patrz niżej) — przy większym rozmiarze dym z jednej
   trafionej komórki zalewał wizualnie sąsiednie, jeszcze całe komórki tego
   samego statku, sprawiając wrażenie zatopienia całości po jednym
   trafieniu, mimo że logika w bazie zawsze liczyła poprawnie (sunk dopiero
   gdy hits >= size). */
.bship-cell-hit::after {
    content: '';
    position: absolute;
    width: 120%;
    height: 120%;
    background-image: url('battleship/explosion-hit.png');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    pointer-events: none;
    z-index: 2;
}
.bship-cell-sunk {
    background: rgba(80, 10, 12, 0.75);
}
.bship-cell-sunk::after {
    content: '';
    position: absolute;
    width: 190%;
    height: 190%;
    background-image: url('battleship/explosion.png');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    pointer-events: none;
    z-index: 2;
    filter: saturate(1.3);
}

/* Prawdziwe zdjęcie prezesa stowarzyszenia (karykatura jako pirat, tło już
   usunięte z PNG) — świadomie bez okrągłej ramki/przycinania: wystaje ponad
   górną krawędź mapy jak figurka wyskakująca zza rogu, żeby wyglądało na
   żywą maskotkę, z którą się gra, a nie sztywną plakietkę wciśniętą w
   kółko. Brak filter:drop-shadow/obrotu — PNG ma sam w sobie miękko
   rozmyty zanik przezroczystości pod brodą, więc broda "wtapia się" w wodę
   planszy naturalnie bez potrzeby dodatkowych efektów. */
.battleship-captain-badge {
    position: absolute;
    right: -6%;
    top: -15%;
    width: 40%;
    z-index: 3;
    pointer-events: none;
}
/* right:-6% (nie więcej) — statki_place.php ma tuż obok rzeczywistego
   sąsiada, panel floty bez własnego paddingu startujący zaraz za gapem;
   przy większym wystawaniu broda/kapelusz zasłaniały początek tekstu
   statusu. Ta sama reguła bazowa jest używana też w statki_game.php, gdzie
   plansze stoją jedna pod drugą — kapitan ma nad/pod sobą tylko drugą
   planszę, nigdy panel z tekstem, więc ten sam bezpieczny wariant wygląda
   tam równie dobrze. */
.battleship-captain-badge img {
    display: block;
    width: 100%;
    height: auto;
}
/* Telefon (≤780px, ten sam próg co <picture>/<source media> dla
   board-mobile.jpg w bship_render_board_img()) — większa plakietka (62%) i
   top:-8% zamiast -15%, żeby kapelusz/pióro nie wystawały poza kartę
   planszy na kartę "Rozstaw swoją flotę" nad nią. */
@media (max-width: 780px) {
    .battleship-captain-badge { width: 62%; top: -8%; right: -2.5%; }
}

/* Znaczek wodny (duża kotwica) w tle karty rozgrywki, wypełniający puste,
   jasne pasy po bokach planszy. .battleship-game-bgicon-clip to osobny
   kontener-przycinacz (position:absolute; inset:0 względem
   .battleship-game-card; overflow:hidden), który przykrywa całą kartę i
   przycina TYLKO samą ikonę w środku — .battleship-captain-badge (wystaje
   poza swoją ramkę planszy) nie jest jego potomkiem (żyje w innej gałęzi
   drzewa, wewnątrz .battleship-board-frame), więc zostaje nietknięty. */
.battleship-game-card { position: relative; z-index: 0; }
.battleship-game-bgicon-clip {
    position: absolute;
    inset: 0;
    overflow: hidden;
    z-index: -1;
    pointer-events: none;
    border-radius: 8px;
}
.battleship-game-bgicon {
    position: absolute;
    left: -220px;
    top: 50%;
    transform: translateY(-50%) rotate(-8deg);
    color: #d9a520;
    opacity: 0.16;
}
.battleship-game-bgicon svg { width: 900px; height: 900px; display: block; }

/* Rozrzucone kołki-ozdoby na ekranie rozgrywki Kod Mistrza
   (kod_mistrza_game.php) — 4 gotowe grafiki kulek przesłane przez
   użytkownika 20.09.2026 ("udekoruj ładnie smutną i ciężką grę... rozmiary
   dostosuj kulek przypadkowo nie muszą być wszystkie identyczne"). Żyją w
   .mm-decor-balls, absolutnym kontenerze na całą wysokość/szerokość
   .battleship-game-card (position:relative wyżej) — same kulki mają
   pointer-events:none i różne rozmiary/obroty, większość każdej wystaje
   POZA kartę w pusty margines strony (karta świadomie NIE ma
   overflow:hidden, w przeciwieństwie do .leaderboard-compact — poziomy
   nadmiar ucina i tak overflow-x:hidden na <body>, patrz komentarz przy tej
   regule). z-index:2, żeby leżeć NAD tłem karty i nad przyćmioną ikonką
   .battleship-game-bgicon (z-index:-1), ale to bez znaczenia dla
   klikalności — pointer-events:none przepuszcza kliknięcia do przycisków/
   plansz pod spodem, nawet tam gdzie się wizualnie nakładają. */
.mm-decor-balls { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.mm-decor-ball { position: absolute; display: block; height: auto; }
.mm-decor-ball-1 { width: 72px; top: -24px; right: 14%; transform: rotate(-14deg); }
.mm-decor-ball-2 { width: 58px; top: 36%; left: -22px; transform: rotate(11deg); }
.mm-decor-ball-3 { width: 46px; bottom: 64px; right: -18px; transform: rotate(-9deg); }
/* Kulka nr 4 (fioletowa) — zgłoszenie użytkownika 20.09.2026: na desktopie
   leżała na przycisku "Poddaj się" w .btn-row na dole karty (ten przycisk
   ląduje mniej więcej w tym samym miejscu co poprzednie left:16%/bottom, bo
   .btn-row nie jest wyśrodkowany, tylko pakuje przyciski od lewej).
   Przeniesiona pod "Twój tajny kod" — pierwsza próba (top:-30px) wylądowała
   za wysoko, na samym nagłówku "Twój tajny kod" (zasłaniała tekst) —
   poprawka: NIŻEJ, w pustym jasnym tle POD małą, wąską planszą kodu, a nad
   "Moje próby" (użytkownik: "daj tą kulkę niżej zobacz ile tam światła").
   Reguła w @media niżej (widok mobilny, gdzie użytkownik potwierdził, że
   jest już dobrze) zostaje CELOWO nietknięta. */
.mm-decor-ball-4 { width: 108px; top: 150px; left: 6%; transform: rotate(15deg); }
@media (max-width: 780px) {
    .mm-decor-ball-1 { width: 52px; right: 8%; }
    .mm-decor-ball-2 { width: 40px; top: 30%; }
    .mm-decor-ball-3 { width: 34px; bottom: 44px; }
    .mm-decor-ball-4 { width: 48px; top: auto; bottom: -22px; left: 10%; }
}

.battleship-result-overlay {
    position: fixed;
    inset: 0;
    background: rgba(10, 8, 6, 0.72);
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.battleship-result-card {
    background: var(--card-bg);
    border-radius: 14px;
    padding: 34px 30px;
    max-width: 420px;
    width: 100%;
    text-align: center;
    box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 3px #caa24a, 0 0 0 5px #2c1c10;
}
.battleship-result-card h2 { margin: 0 0 8px; font-size: 26px; }
/* Złoty (ten sam odcień co ramka mapy i akcenty kapitana/plakietek)
   zamiast zielonego dla wygranej — zielony nagłówek obok czerwonych
   przycisków (.btn) wyglądał jak zderzenie sygnalizacji świetlnej. */
.battleship-result-card--won h2 { color: #c9971f; }
.battleship-result-card--lost h2 { color: var(--accent-dark); }
.battleship-result-card p { margin: 0 0 18px; color: #6b6560; font-size: 14px; }
/* Ten sam błąd co .modal-inner/.card wyżej: jasna karta (var(--card-bg))
   + domyślny .btn.secondary (kolor pod ciemne tło strony) = niewidoczny,
   "pusty" przycisk. */
.battleship-result-card .btn.secondary { color: #171512; border-color: #d8d3cc; }
.battleship-result-card .btn.secondary:hover { background: rgba(0,0,0,0.04); }
.notice-modal button { width: 100%; }

/* Baner powitalny na ekranie rozgrywki (statki_game.php), edytowalny w
   Narzędzia > Treść — ta sama pełnoszerokościowa sztuczka i gradient co
   .section-banner/.quiz-cover-banner (100vw + ujemne marginesy, żeby
   wyjść poza wyśrodkowany .wrap). Osobna klasa (nie współdzielona), żeby
   nie ruszać zachowania tamtych banerów gdzie indziej. Bez wgranej
   grafiki — własne, ciemne morskie tło zamiast zdjęcia (.no-cover), ze
   znaczkiem kotwicy zamiast logo klubu. */
.battleship-game-banner {
    position: relative;
    z-index: 0;
    left: 50%;
    right: 50%;
    width: 100vw;
    margin-left: -50vw;
    margin-right: -50vw;
    margin-top: 0;
    margin-bottom: calc(-1 * var(--banner-overlap));
    padding-bottom: var(--banner-overlap);
    min-height: var(--banner-min-height);
    background-size: cover;
    background-position: center;
    background-color: #0e3540;
    display: flex;
    align-items: flex-end;
    overflow: hidden;
}
.battleship-game-banner.no-cover { background-image: linear-gradient(135deg, #123a46, #081b21); }
.battleship-game-banner-icon {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    opacity: 0.14;
    color: #fff;
    pointer-events: none;
}
.battleship-game-banner .section-banner-inner { position: relative; z-index: 1; }
.battleship-game-banner .section-banner-inner h1 { display: flex; align-items: center; gap: 10px; }
.battleship-game-banner + * { position: relative; z-index: 1; margin-top: var(--banner-gap-after); }
@media (max-width: 720px) {
    .battleship-game-banner {
        min-height: var(--banner-min-height-mobile);
        margin-top: -14px;
        margin-bottom: calc(-1 * var(--banner-overlap-mobile));
        padding-bottom: var(--banner-overlap-mobile);
    }
}

/* ===========================================================================
   Gra "Kod Mistrza" (kod_mistrza.php i okolice) — pojedynek dedukcyjny w
   stylu Mastermind, klubowy reskin (6 ikon zamiast kolorowych kołków, patrz
   mm_peg_defs() w includes/mastermind.php). Banery/karta gry/pigułka tury/
   overlay wygranej celowo REUŻYWAJĄ klas Statków (.battleship-game-banner,
   .battleship-game-card, .battleship-board-title, .bship-turn-banner,
   .battleship-result-overlay/card) — to czysto wizualna otoczka bez
   ship-specyficznej treści, więc duplikowanie jej tutaj tylko rozjeżdżałoby
   dwa identyczne style z czasem. Poniżej TYLKO to, co faktycznie unikalne
   dla tej gry: dwukolumnowe lobby, sloty/paleta kołków i lista historii
   prób. Zgłoszenie użytkownika 19.09.2026: "wypaliła nam gra w statki, coś
   takiego w akcji i interakcji".
   =========================================================================== */

/* Lobby: "Twoje gry"+"Ostatnie wyniki" | Ranking — WŁASNA klasa (nie
   .battleship-columns), z tego samego powodu co Statki nie recyklingują
   .dashboard-columns: żeby nie odziedziczyć mobilnych reguł kolejności
   należących do innego widżetu. Na telefonie kolumny znikają
   (display:contents) i wszystko wraca do jednej listy w kolejności DOM. */
.mm-columns { display: flex; align-items: flex-start; gap: 16px; }
.mm-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
@media (max-width: 900px) {
    /* gap:0 — ta sama naprawa co .battleship-columns wyżej (patrz obszerny
       komentarz tam): po display:contents na .mm-col ten gap dokłada się do
       WŁASNEGO margin-bottom:16px każdej .card, dając podwójny (32px)
       odstęp między "Twoje gry" a kartą rankingu zamiast zwykłych 16px. */
    .mm-columns { flex-direction: column; align-items: stretch; gap: 0; }
    .mm-col { display: contents; }
}

/* Ciemna "plansza" w stylu oryginalnego pudełka Mastermind (zdjęcie
   fizycznej planszy przesłane przez użytkownika 19.09.2026: rzędy z dużymi
   "dziurkami" na zgadywane kołki + mniejsze "wskaźniki" trafień obok, oraz
   osobny, też ciemny panel z paletą kolorów i legendą) — w całości na CSS,
   bez żadnych grafik: okrągłe "dziurki" to zwykłe div-y z inset box-shadow
   (wgłębienie), a wypełniony kołek to ten sam gradient co reszta palety.
   Używana w trzech miejscach: ustawianie własnego kodu (kod_mistrza_set.php,
   małe dziurki .mm-hole-compact), przypomnienie własnego kodu na ekranie
   gry, i pełna plansza próby+historii w kod_mistrza_game.php (pełny
   rozmiar dziurek, patrz .mm-board-full niżej). */
.mm-board {
    background: linear-gradient(160deg, #2a2a2a, #0c0c0c 80%);
    border-radius: 14px;
    padding: 16px;
    border: 1px solid #000;
}
/* Wiersz próby jako DWIE osobne "tacki" (kołki + wskaźniki) zamiast płaskiej
   linii z cienkim separatorem — dokładnie jak na planszy-mockupie
   przesłanej przez użytkownika 20.09.2026 ("plansza_mastermind.png" /
   "..._przeciwnika.png"): pełna, zawsze widoczna plansza (bez przewijania,
   patrz .mm-board-full niżej), bez numerów wierszy — kolejność (najnowsza
   próba tuż pod aktywnym wierszem) mówi wystarczająco dużo sama. */
.mm-board-row { display: flex; align-items: stretch; gap: 14px; }
.mm-board-row + .mm-board-row { margin-top: 10px; }
.mm-row-holes {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    width: 100%;
    box-sizing: border-box;
    background: rgba(255, 255, 255, 0.035);
    border-radius: 12px;
    padding: 10px 16px;
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}
/* "Twój tajny kod" reużywa TĘ SAMĄ tackę co plansze prób (.mm-row-holes),
   żeby kołki miały identyczny rozstaw/rozmiar — zgłoszenie użytkownika
   20.09.2026: "utrzymaj standard rozstawu kulek jak Moje próby". Poza
   flex-em (tu nie ma rodzica display:flex jak w .mm-board-row) width:100%
   wyżej robi realną robotę — tacka rozciąga się na całą szerokość
   poszerzonego pola "Twój tajny kod" (patrz .mm-top-row-code), zamiast
   kurczyć się do samej zawartości. */
.mm-row-indicators {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    flex-shrink: 0;
    flex-wrap: wrap;
    width: 92px;
    background: rgba(255, 255, 255, 0.035);
    border-radius: 12px;
    padding: 10px 8px;
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}

/* Aktywny wiersz (Twoja próba do ułożenia, gdy jest Twoja tura) — tacka z
   kołkami podświetlona na niebiesko, ten sam pomysł co niebieski wiersz na
   mockupie. Sloty w środku (.mm-slot) są klikalne — patrz
   mmSetupPegPicker() w assets/kod-mistrza.js. Ten wiersz nie ma
   .mm-row-indicators (patrz .mm-row-logo-slot niżej), więc podświetlamy
   tylko tackę z kołkami. */
/* NAPRAWA 20.09.2026: brakująca 10px przerwa między aktywnym wierszem a
   pierwszym wierszem historii w planszy "Moje próby" — zgłoszenie
   użytkownika "czemu wysokości się różnią w tych kolumnach skoro są
   identyczne". Przyczyna: #mm-guess-input-row (aktywny wiersz) jest
   osobnym, STAŁYM elementem DOM poza #mm-my-history (celowo — JS re-renderuje
   #mm-my-history całkowicie przy każdym pollu/ruchu, gdyby aktywny wiersz
   był w środku, znikałaby właśnie klikana próba, patrz komentarz przy
   guessInputWasVisible w assets/kod-mistrza.js), więc selektor rodzeństwa
   ".mm-board-row + .mm-board-row" nie widzi pierwszego wiersza historii
   jako "następnego" po aktywnym — są w różnych kontenerach. Efekt: plansza
   "Moje próby" wychodziła o dokładnie jedną przerwę (10px) krótsza niż
   "Próby przeciwnika", mimo identycznej liczby wierszy. */
#mm-guess-input-row { margin-bottom: 10px; }

.mm-board-row-active .mm-row-holes {
    background: linear-gradient(160deg, #0f3a63, #08213b 85%);
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(90, 170, 255, 0.35), 0 0 0 1px rgba(90, 170, 255, 0.25);
}

/* Pełna plansza (Moje/Przeciwnika próby) — WSZYSTKIE wiersze na raz, bez
   przewijania (patrz komentarz przy .mm-board-row wyżej); dopełniana do
   min. 10 wierszy pustymi "dziurkami" przez mmPadHistoryRows() w JS, żeby
   plansza od razu wyglądała na kompletną, tak jak w fizycznym pudełku, a
   nie na obciętą listę. Jeśli gra realnie przekroczy 10 prób, plansza po
   prostu rośnie dalej w dół (gra nie ma twardego limitu prób, patrz
   includes/mastermind.php) zamiast urywać się na 10-tym wierszu. */
.mm-board-full { padding: 20px; }

/* NAPRAWA 20.09.2026 (trzecia — wg dokładnego opisu/zaznaczeń użytkownika
   na screenie): dwie pierwsze próby (osobny nagłówek z podpisami, potem
   logo doczepiane do tego nagłówka) wciąż nie zgadzały się z mockupem
   "plansza_mastermind.png", który NIE MA żadnych podpisów "Twoje
   zgadnięcie"/"Wskaźniki" — nagłówek usunięty całkowicie (patrz
   kod_mistrza_game.php). Logo nie jest już osobno pozycjonowane
   (position:absolute) — siedzi jako zwykły flex-child w miejscu, gdzie w
   INNYCH wierszach jest pole wskaźników, tylko w AKTYWNYM wierszu na
   ułożenie próby (.mm-row-logo-slot zamiast .mm-row-indicators — próba
   jeszcze nierozstrzygnięta, więc wskaźniki i tak nie mają tam czego
   pokazywać). Zero position:absolute, zero sztucznych odstępów — wysokość
   wiersza sama nadaje się z .mm-row-holes obok. */
.mm-row-logo-slot { display: flex; align-items: center; justify-content: center; flex-shrink: 0; width: 92px; }
.mm-board-logo { width: 68px; height: auto; pointer-events: none; }
@media (max-width: 480px) {
    /* Zgłoszenie użytkownika 20.09.2026: "w mobilnych delikatnie powieksz
       logo jest troche za małe" — 48px ginęło w 92px szerokim slocie. */
    .mm-board-logo { width: 60px; }
}

/* "Dziurka" na kołek — puste = wgłębienie w planszy (inset shadow, jak w
   prawdziwym pudełku), wypełnione pokazuje prawdziwe zdjęcie szklanej kulki
   (assets/kod-mistrza/peg-*.png, przesłane przez użytkownika 19.09.2026:
   "dołączam pionki") zamiast płaskiego koloru CSS. Te same klasy
   (.mm-slot/.mm-slot-filled) obsługuje JS przy klikaniu (mmSetupPegPicker()
   w assets/kod-mistrza.js) — tu tylko wygląd. */
.mm-set-slots { display: flex; gap: 10px; }
.mm-hole, .mm-slot {
    width: 52px;
    height: 52px;
    border-radius: 50%;
    background: #050505;
    box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.85), inset 0 0 0 1px rgba(255, 255, 255, 0.06);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
}
.mm-hole-compact { width: 34px; height: 34px; }
.mm-hole-compact img { width: 28px; height: 28px; }
.mm-slot-filled, .mm-hole-filled {
    background: transparent;
    box-shadow: none;
    cursor: pointer;
}
.mm-hole-filled { cursor: default; }
.mm-slot img, .mm-hole img { width: 44px; height: 44px; object-fit: contain; pointer-events: none; }

/* Panel palety (te same kołki co dziurki na planszy, ale w jasnym,
   zaokrąglonym boksie obok — dokładnie jak panel z kolorami na oryginalnym
   pudełku). Legenda tłumaczy wskaźniki tak samo jak w zdjęciu, które
   przesłał użytkownik: BIAŁY = dobry kolor na dobrym miejscu, CZARNY =
   dobry kolor, złe miejsce. */
.mm-palette-panel {
    background: linear-gradient(160deg, #2a2a2a, #0c0c0c 80%);
    border-radius: 14px;
    padding: 16px;
    border: 1px solid #000;
}
.mm-palette { display: flex; flex-wrap: wrap; gap: 10px; }
.mm-peg {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    border: none;
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: transform 0.12s ease;
}
.mm-peg:hover { transform: scale(1.08); }
.mm-peg img { width: 44px; height: 44px; object-fit: contain; pointer-events: none; }

.mm-legend { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(255, 255, 255, 0.12); }
.mm-legend-item { display: flex; align-items: center; gap: 10px; color: rgba(255, 255, 255, 0.75); font-size: 12px; }
.mm-legend-dot { width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0; }
.mm-legend-dot-white { background: #f6f3ef; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.3); }
.mm-legend-dot-black { background: #8a8a8a; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.4), inset 0 1px 2px rgba(0, 0, 0, 0.35); }

/* Przypomnienie własnego kodu na ekranie rozgrywki — teraz w tej samej
   tacce co próby (.mm-row-holes, patrz wyżej), więc BEZ własnych
   display/gap tutaj — dublowałyby (i psuły, przez kolejność w kaskadzie)
   ustawienia .mm-row-holes. Klasa zostaje tylko jako "hak" do JS
   (getElementById('mm-own-code')). */

/* Pasek "Twój tajny kod" + paleta kolorów DO ułożenia próby, OBOK siebie —
   zgłoszenie użytkownika 20.09.2026: "to musi być nad planszami bo sie
   robi bałagan w rozegraniu (skrolowanie w dół), a miejsca nawet obok Twój
   tajny kod jest dużo". Zwykły flex-wrap zamiast media query z osobnym
   progiem — obie kolumny rosną (flex-grow) i dzielą się wolnym miejscem,
   zamiast "Twój tajny kod" trzymać się sztywno rozmiaru samej zawartości
   (NAPRAWA 20.09.2026: "za ciasno, masz masę miejsca a ściskasz" — flex:0 0
   auto ignorowało dostępną szerokość). Paleta i tak dostaje więcej (flex:2),
   bo ma więcej treści (6 kołków + legenda); kod dostaje uczciwy, wyraźny
   kawałek (flex:1) zamiast garbnąć się do minimum. */
.mm-top-row { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-start; }
.mm-top-row-code { flex: 1 1 260px; }
.mm-top-row-palette { flex: 2 1 320px; min-width: 280px; }

/* Dwie pełne plansze (Moje próby / Próby przeciwnika) OBOK siebie na
   desktopie, jedna pod drugą na telefonie — zwykły grid, bez potrzeby
   display:contents (to nie jest kwestia kolejności `order`, tylko zwykłego
   zawijania). Zgłoszenie użytkownika 20.09.2026: "na desktop można zrobić
   obok plansze w mobilnych zostaje układ jedna pod drugą". */
.mm-history-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@media (max-width: 720px) {
    .mm-history-columns { grid-template-columns: 1fr; }
}
/* Tekst "Jeszcze żadnej próby." (mmRenderHistoryList() w
   assets/kod-mistrza.js) dziedziczy zwykłą, ciemnoszarą klasę .muted —
   nieczytelną na prawie czarnym tle planszy, stąd nadpisanie tu na jasny
   szary. */
.mm-board .muted, .mm-palette-panel .muted { color: rgba(255, 255, 255, 0.55); }

/* Wskaźniki trafień — pojedynczy rząd 4 małych "dziurek" obok dużych
   kołków, DOKŁADNIE jak na zdjęciu pudełka przesłanym przez użytkownika
   19.09.2026. UWAGA na zamianę znaczenia: mm_score_guess() w
   includes/mastermind.php nazywa je (z klasyczną, anglojęzyczną
   terminologią algorytmu) "black_pegs" = dobry kolor NA dobrym miejscu i
   "white_pegs" = dobry kolor, złe miejsce — ale NA TEJ KONKRETNEJ planszy
   referencyjnej użytkownika kolory wskaźników są odwrotne (biały = dobre
   miejsce, czarny = złe miejsce), więc renderowanie (mmRenderHistoryRow() w
   assets/kod-mistrza.js) świadomie zamienia kolory kropek przy wyświetlaniu,
   żeby zgadzało się z tym, co widzi użytkownik na pudełku — nazwy pól w
   bazie/API zostają bez zmian, to czysto kosmetyczna zamiana w warstwie UI.

   NAPRAWA 20.09.2026: wskaźnik "dobry kolor, złe miejsce" (mm-indicator-
   dot-color) miał wcześniej TO SAMO niemal czarne tło (#050505) co pusty
   wskaźnik "brak trafienia" (mm-indicator-dot-empty) — różniła je tylko
   1px obwódka, praktycznie niewidoczna na ciemnej planszy (a już zupełnie
   na zdjęciu z telefonu). Zgłoszenie użytkownika: "sprawdź czy jest coś
   dziwnego w moich działaniach w grze... albo nie rozumiem zasad białą
   czarna kropka albo źle działa gra" — po przeliczeniu ręcznie WSZYSTKICH
   prób z przesłanych zrzutów ekranu algorytm liczył poprawnie, problem był
   wyłącznie w nieczytelnym kolorze tego wskaźnika. Teraz wyraźny, średnio-
   szary kołek zamiast prawie-czarnego, żeby dało się go odróżnić od
   pustego miejsca na pierwszy rzut oka. */
.mm-indicator-dot {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #050505;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.8);
    flex-shrink: 0;
}
.mm-indicator-dot-exact { background: #f6f3ef; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25); }
.mm-indicator-dot-color { background: #8a8a8a; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.4), inset 0 1px 2px rgba(0, 0, 0, 0.35); }
.mm-indicator-dot-empty { background: #050505; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.8); }

@media (max-width: 480px) {
    .mm-hole, .mm-slot { width: 44px; height: 44px; }
    .mm-hole img, .mm-slot img { width: 36px; height: 36px; }
    .mm-peg { width: 42px; height: 42px; }
    .mm-peg img { width: 36px; height: 36px; }
}

/* ===========================================================================
   Bilans wygranych/przegranych (Statki + Kod Mistrza) — wtopiony w kartę
   "Ranking graczy" jako jedno okno zamiast dwóch osobnych kart (zgłoszenie
   użytkownika 20.09.2026: "nie połaczyć jednego okienka razem z rankingeim
   i wpierw ładny wykres kołowy dynamiczny a poniżej ładny ranking" —
   zaakceptowane po kilku rundach podglądu: "petarda, teraz cieżkie zadanie
   żeby ładnei wygladało w systemie - akceptuje, jedno okno dla rakingu gier
   z bilansem"). Wykres rysowany przez Chart.js (ta sama biblioteka/wersja co
   zakładka Statystyki w Narzędziach) — gruby pierścień wygrane/przegrane +
   cienki pierścień liczby rozegranych gier (patrz inline <script> w
   statki.php/kod_mistrza.php), tekst procentowy na środku przez
   .tally-donut-center. Obok wykresu trzy obramowane, delikatnie podbarwione
   kafelki jako legenda — zgłoszenie: "troche dużo światła po prawej stronie
   za legendą... delikatne obrysy do legendy i to wypełni na szerokość
   okienko". Poniżej pozioma linia (.tally-divider) i istniejąca lista
   rankingu (.leaderboard-compact-rows, bez zmian). Karta renderuje się
   ZAWSZE, nawet przy 0 rozegranych gier i pustym rankingu — patrz
   .game-tally-empty i komunikat zastępczy nad listą rankingu w PHP. */
.tally-donut-row { display: flex; align-items: center; gap: 20px; margin: 14px 0 16px; flex-wrap: wrap; }
.tally-donut-wrap { position: relative; flex: 0 0 auto; width: 150px; height: 150px; }
.tally-donut-wrap canvas { display: block; width: 150px; height: 150px; }
.tally-donut-center {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    pointer-events: none;
}
.tally-donut-pct { display: block; font-size: 24px; font-weight: 800; line-height: 1.1; color: #17140f; }
.tally-donut-sub { display: block; margin-top: 2px; font-size: 10px; font-weight: 600; color: #6b6560; }
.tally-stat-tiles { flex: 1 1 160px; display: flex; flex-direction: column; gap: 8px; min-width: 150px; }
.tally-stat-tile {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: 10px;
    border: 1px solid rgba(23, 20, 15, 0.1);
    background: rgba(23, 20, 15, 0.02);
}
.tally-stat-icon {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.tally-stat-win { border-color: rgba(63, 143, 79, 0.35); background: rgba(63, 143, 79, 0.06); }
.tally-stat-win .tally-stat-icon { background: rgba(63, 143, 79, 0.18); color: #3f8f4f; }
.tally-stat-win .tally-stat-num { color: #3f8f4f; }
.tally-stat-loss { border-color: rgba(163, 45, 45, 0.35); background: rgba(163, 45, 45, 0.06); }
.tally-stat-loss .tally-stat-icon { background: rgba(163, 45, 45, 0.18); color: #a32d2d; }
.tally-stat-loss .tally-stat-num { color: #a32d2d; }
.tally-stat-played { border-color: rgba(63, 127, 209, 0.35); background: rgba(63, 127, 209, 0.06); }
.tally-stat-played .tally-stat-icon { background: rgba(63, 127, 209, 0.18); color: #3f7fd1; }
.tally-stat-played .tally-stat-num { color: #3f7fd1; }
.tally-stat-num { font-size: 16px; font-weight: 800; line-height: 1; }
.tally-stat-label { font-size: 11px; color: rgba(23, 20, 15, 0.55); text-transform: uppercase; letter-spacing: 0.03em; margin-left: auto; }
.tally-divider { border: none; border-top: 1px solid var(--card-border); margin: 0 0 16px; }
.game-tally-empty { margin: 12px 0 16px; }

/* ===========================================================================
   Konkursy (turnieje.php + tools.php zakładka Konkursy) — drabinka
   pojedynczej eliminacji, patrz includes/tournaments.php. Kolumny rund w
   poziomym pasku (przewijanym na wąskich ekranach zamiast łamania układu —
   drabinka z natury robi się szeroka przy więcej niż kilku uczestnikach),
   łączone liniami między rundami. Sztuczka z wyrównaniem "widełek": każda
   kolumna rundy ma justify-content:space-around, a align-items:stretch na
   .tn-bracket wymusza na WSZYSTKICH kolumnach TĘ SAMĄ wysokość co kolumna
   rundy 1. (ma najwięcej meczów, więc jest naturalnie najwyższa) — dzięki
   temu mecz rundy 2. sam ląduje dokładnie w połowie wysokości między swoimi
   dwoma meczami-rodzicami z rundy 1., bez żadnego ręcznego liczenia
   pikseli w JS. Odstęp między kolumnami (--tn-gap) i połówka tego odstępu
   używana w łącznikach MUSZĄ się zgadzać z liczbami w .tn-match-slot::before/
   ::after i .tn-match::before niżej. */
.tn-bracket-card { overflow: visible; }
.tn-bracket-scroll { overflow-x: auto; overflow-y: hidden; padding: 4px 4px 12px; }
.tn-bracket { display: flex; align-items: stretch; gap: 50px; min-width: max-content; padding: 10px 8px; }
/* .tn-branch = wspólna klasa KAŻDEJ kolumny drabinki, WŁĄCZNIE z kolumną
   pucharu na końcu (.tn-trophy-col) — dzięki temu ta sama sztuczka z
   wyrównaniem "widełek" (justify-content:space-around + align-items:stretch
   na .tn-bracket wymuszają na wszystkich kolumnach TĘ SAMĄ wysokość co
   kolumna rundy 1., więc każda kolejna kolumna sama centruje się dokładnie
   między swoimi "rodzicami") i te same łączniki działają identycznie dla
   przejścia runda->runda ORAZ dla ostatniego przejścia finał->puchar.
   Zgłoszenie użytkownika 20.09.2026: "musi być drzewko nawet jak jest tylko
   dwóch to kończy sie jednym z przodu i można dodać tam puchar". Odstęp
   między kolumnami (gap na .tn-bracket) i połówka tego odstępu w łącznikach
   niżej MUSZĄ się zgadzać. */
.tn-branch { display: flex; flex-direction: column; justify-content: space-around; }
.tn-round { min-width: 210px; }
.tn-trophy-col { min-width: 150px; }
.tn-round-title {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    text-align: center;
    font-weight: 700;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: rgba(23, 20, 15, 0.5);
    margin: 0 0 12px;
}
.tn-round-final .tn-round-title { color: var(--accent); }
.tn-match-slot { position: relative; flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 10px 0; }
/* Karta meczu — obramowanie i cień WYRAŹNIE widoczne na tle karty (jasne
   --card-bg), bo poprzednia wersja korzystała z var(--card-border) (bardzo
   subtelna wartość dobrana do zwykłych, cienkich linii list) — na jasnym tle
   karty praktycznie znikała, więc cała drabinka wyglądała jak zwykła lista
   bez żadnych "pudełek meczów". Zgłoszenie użytkownika 20.09.2026: "z tego
   to nie wynika... ładnie to zaprojektuj". */
.tn-match {
    position: relative;
    width: 100%;
    border: 1px solid rgba(23, 20, 15, 0.16);
    border-radius: 10px;
    background: #fff;
}
.tn-round-final .tn-match { border-color: rgba(224, 18, 26, 0.35); border-width: 2px; }
.tn-match-player { display: flex; align-items: center; gap: 8px; padding: 9px 12px; font-size: 13px; }
.tn-match-player:first-child { border-radius: 10px 10px 0 0; }
.tn-match-player:last-child { border-radius: 0 0 10px 10px; }
.tn-match-player + .tn-match-player { border-top: 1px solid rgba(23, 20, 15, 0.1); }
.tn-match-player-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tn-match-player.is-winner { font-weight: 700; background: rgba(63, 143, 79, 0.13); }
.tn-match-player.is-empty { color: rgba(23, 20, 15, 0.38); font-style: italic; }
/* Awatar gracza w drabince — zdjęcie profilowe albo inicjały na czerwonawym
   tle klubowym, ten sam wzorzec co .avatar w rankingach gier, tylko
   mniejszy rozmiar dopasowany do wąskiej karty meczu. */
.tn-avatar { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.tn-avatar-initials {
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(224, 18, 26, 0.12);
    color: var(--accent);
    font-size: 9px;
    font-weight: 700;
}
/* Awatar zwycięzcy w węźle pucharu — wyraźnie większy niż w wierszach meczów, zgłoszenie użytkownika 20.09.2026: "zwycięzca trochę większy awatar". */
.tn-avatar-lg { width: 46px; height: 46px; }
.tn-avatar-lg.tn-avatar-initials { font-size: 15px; }
.tn-match-winner-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #3f8f4f;
    color: #fff;
    flex-shrink: 0;
}
/* "vs" — mała plakietka na styku dwóch wierszy gracza, żeby nawet finał
   dwuosobowy (jedna runda, bez żadnych łączników z wcześniejszych rund)
   czytał się jak POJEDYNEK, a nie jak zwykła lista dwóch nazwisk. */
.tn-match-vs {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background: var(--card-bg);
    border: 1px solid rgba(23, 20, 15, 0.16);
    border-radius: 50%;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 9px;
    font-weight: 800;
    text-transform: uppercase;
    color: rgba(23, 20, 15, 0.45);
    z-index: 1;
}
/* min-height REZERWUJE miejsce na dwie linijki (status + licznik czasu na
   ruch) NAWET gdy druga linijka się nie pokazuje (mecz nie w toku / bez
   limitu czasu) — bez tego drabinka "rozjeżdżała się" wizualnie: kolumny
   są wyrównywane flexboxem (.tn-bracket { align-items:stretch }, patrz
   niżej), więc mecze W TEJ SAMEJ rundzie muszą mieć jednakową wysokość,
   żeby łączniki drzewka trafiały dokładnie w środek każdej pary. Zgłoszenie
   użytkownika 28.09.2026 (zrzut ekranu z żywego konkursu): "troche nam sie
   rozjechały drzewka, pewnie z powodu tych doatkowych tekstów". */
.tn-match-status { text-align: center; font-size: 11px; color: rgba(23, 20, 15, 0.55); margin: 6px 0 0; min-height: 30px; }
.tn-match-status a { color: var(--accent); font-weight: 600; }
/* Licznik czasu na ruch przy meczu w toku (28.09.2026, patrz komentarz w
   turnieje.php) — trochę drobniejszy/przygaszony niż link "mecz w toku",
   żeby nie konkurował z nim wizualnie; sama ikonka zegara wystarczy jako
   akcent. */
.tn-match-timer { display: inline-flex; align-items: center; gap: 3px; font-size: 10.5px; color: rgba(23, 20, 15, 0.45); margin-top: 2px; }
/* Kropki progresu serii "do dwóch wygranych" (runda 1.) — jedna grupka kropek
   przy KAŻDYM nazwisku w .tn-match-player, tyle kropek ile trzeba wygranych,
   wypełnione = już wygrane partie. Zastąpiło suchy tekst "wynik serii: 2:1"
   pod meczem — zgłoszenie użytkownika 28.09.2026: "może zachęcić do
   spoglądania na wyniki". */
.tn-series-dots { display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0; }
.tn-series-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgba(23, 20, 15, 0.14);
    border: 1px solid rgba(23, 20, 15, 0.22);
}
.tn-series-dot.is-filled { background: var(--accent); border-color: var(--accent); }
/* Węzeł pucharu — ostatnia kolumna drabinki (.tn-trophy-col), w którą
   zbiegają się linie z finału. Wyszarzony, dopóki finał się nie rozstrzygnął
   (is-decided dopisywane dopiero, gdy jest zwycięzca konkursu). */
.tn-trophy-node { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; padding: 4px 6px; opacity: 0.45; }
.tn-trophy-node.is-decided { opacity: 1; }
.tn-trophy-circle {
    width: 54px;
    height: 54px;
    border-radius: 50%;
    background: rgba(224, 18, 26, 0.1);
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(23, 20, 15, 0.35);
}
.tn-trophy-node.is-decided .tn-trophy-circle { color: var(--accent); background: rgba(224, 18, 26, 0.14); }
.tn-trophy-circle svg { width: 26px; height: 26px; }
.tn-trophy-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: rgba(23, 20, 15, 0.5); font-weight: 700; margin: 0; }
.tn-trophy-name { font-size: 13px; font-weight: 700; color: #171512; margin: 0; }
.tn-trophy-name-pending { color: rgba(23, 20, 15, 0.35); font-weight: 400; }
/* Łącznik wychodzący z prawej strony każdej kolumny (poza ostatnią — puchar
   niczego dalej nie karmi). Zaczepiony o .tn-match (kartę samego meczu), NIE
   o .tn-match-slot — .tn-match-slot to slot PLUS .tn-match-status pod spodem
   (link "mecz w toku" i licznik czasu, patrz wyżej), więc top:50% liczone od
   CAŁEGO slotu ląduje NIŻEJ niż środek karty ("vs"), tym niżej, im dłuższy
   tekst statusu. .tn-match ma własne position:relative i wysokość TYLKO
   dwóch wierszy graczy, więc top:50% od niego zawsze trafia dokładnie w
   "vs" — niezależnie od tego, co pokazuje się pod kartą. Zgłoszenie
   użytkownika 28.09.2026 (zrzut ekranu z żywego konkursu + adnotacja
   strzałką): "kreska pozioma powinna być na równo miedzy graczami".
   Sprawdzone wizualnie przed wdrożeniem (por./po poprawce) i potwierdzone
   przez użytkownika jako poprawne. */
.tn-branch:not(:last-child) .tn-match::after {
    content: '';
    position: absolute;
    right: -25px;
    top: 50%;
    width: 25px;
    height: 2px;
    background: rgba(23, 20, 15, 0.2);
    transform: translateY(-1px);
}
/* Pionowa "klamra" łącząca dwa węzły-rodzice z poprzedniej kolumny (poza pierwszą rundą — ta nie ma rodziców). */
.tn-branch:not(:first-child) .tn-match-slot::before {
    content: '';
    position: absolute;
    left: -25px;
    top: 0;
    bottom: 0;
    width: 2px;
    background: rgba(23, 20, 15, 0.2);
}
/* Łącznik wchodzący od klamry do samej karty meczu / węzła pucharu (poza pierwszą rundą). */
.tn-branch:not(:first-child) .tn-match::before,
.tn-branch:not(:first-child) .tn-trophy-node::before {
    content: '';
    position: absolute;
    left: -25px;
    top: 50%;
    width: 25px;
    height: 2px;
    background: rgba(23, 20, 15, 0.2);
    transform: translateY(-1px);
}

.tn-winner-banner {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-top: 14px;
    padding: 14px 16px;
    border-radius: 10px;
    background: linear-gradient(135deg, rgba(224, 18, 26, 0.12), rgba(224, 18, 26, 0.04));
    border: 1px solid rgba(224, 18, 26, 0.25);
}
.tn-winner-banner svg { color: var(--accent); }
.tn-winner-title { margin: 0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: rgba(23, 20, 15, 0.6); }
.tn-winner-name { margin: 2px 0 0; font-size: 20px; font-weight: 700; }

/* Opis konkursu (pełny edytor, zgłoszenie użytkownika 20.09.2026) i pozioma
   kreska rozdzielająca scaloną kartę nagłówka od karty drabinki niżej
   (dawniej dwie osobne karty — "okna łączymy z tabelą"). */
.tn-description { margin-top: 14px; }
.tn-card-divider { border: none; border-top: 1px solid var(--card-border); margin: 18px 0 14px; }

/* Linijka "Gra: ... · Status: ..." — zgłoszenie użytkownika 20.09.2026:
   "ciutkę bardziej wyróznić bo to chyba ważne", potem "to w zasadzie
   główny tytuł bardziej go wyróżnij i może dodaj mu ikonkę na początku"
   (gdy jest baner, h2 z nazwą konkursu jest ukryty — patrz turnieje.php —
   więc ta linijka faktycznie PEŁNI rolę głównego nagłówka karty). Teraz
   rozmiar i grubość jak .card h2, z ikoną 'play' (gra w toku) przed
   tekstem, zamiast dawnego małego, szarego .muted. */
.tn-meta-line {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 8px;
    margin: 0 0 6px;
    font-size: 15px;
    font-weight: 700;
    color: #17140f;
    letter-spacing: .01em;
}
.tn-meta-line svg { color: var(--accent); flex-shrink: 0; }

/* Banery PER KONKURS — NAPRAWA 20.09.2026 ("to miało wyglądać tak jak na
   drugim screen czyli bannery u góry jak wszędzie i kadrowaniem, nie w
   okienkach"): pierwsza wersja robiła mały obrazek oprawiony w kartę
   (ujemny margines na krawędzie .card). Teraz oba bannery (nagłówek i
   drabinka) to PEŁNA SZEROKOŚĆ strony, ten sam wzorzec co baner gry —
   patrz .battleship-game-banner (statki_game.php/kod_mistrza_game.php) i
   .section-banner (includes/layout.php), reużyte wprost w turnieje.php,
   bez własnej, osobnej klasy. */

/* Zarządzanie banerami PER KONKURS w Narzędziach (tools.php, zakładka
   Konkursy) — zwinięte <details> pod każdym wierszem listy konkursów, ten
   sam wzorzec strzałki co .rsvp-details wyżej. */
.tn-banner-details { margin: -6px 0 14px; }
.tn-banner-details summary {
    cursor: pointer;
    padding: 6px 2px;
    font-weight: 600;
    font-size: 12px;
    color: rgba(23, 20, 15, 0.6);
    list-style: none;
    display: flex;
    align-items: center;
    gap: 6px;
}
.tn-banner-details summary::-webkit-details-marker { display: none; }
.tn-banner-details summary::before {
    content: '';
    width: 0; height: 0;
    border-left: 5px solid rgba(23, 20, 15, 0.4);
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform .12s;
}
.tn-banner-details[open] summary::before { transform: rotate(90deg); }
/* Pod sobą (baner nad opisem), NIE obok siebie — zgłoszenie użytkownika
   20.09.2026: "niech to okno edytora bedzie pod". Wcześniejszy grid
   auto-fit dawał dwie kolumny, gdy oba pola mieściły się obok siebie na
   szerokim ekranie — z pełnym edytorem TinyMCE (420px wysokości) obok
   wąskiego pola banera to wyglądało niechlujnie. */
.tn-banner-details-inner {
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: 4px 2px 10px;
}
.tn-banner-preview-row { display: flex; align-items: center; gap: 10px; }
.tn-banner-preview-thumb { width: 100px; height: 42px; object-fit: cover; border-radius: 6px; border: 1px solid var(--card-border); }

/* Panel "Ręczny walkower" (tools.php, zakładka Konkursy) — dwa przyciski
   walkoweru + "Unieważnij mecz" niżej wcześniej wisiały w jednym
   flex-wrap:wrap wierszu, więc przy dłuższych nickach zawijały się w różnych
   miejscach w różnych meczach (raz wszystkie trzy w linii, raz "Unieważnij"
   samo niżej) — wyglądało chaotycznie. Teraz "Unieważnij" ZAWSZE jest na
   własnej linii pod dwoma przyciskami walkoweru, oddzielone cienką kreską, i
   wyróżnione stonowanym "ostrzegawczym" kolorem (ten sam #c0392b co inne
   akcje danger w serwisie, np. .chat-more-menu-item.danger) — jasny sygnał,
   że to poważniejsza/nieodwracalna decyzja niż zwykły walkower. Zgłoszenie
   użytkownika 30.09.2026: "możesz poprawić ukłąd button, bo tak troche
   chaotycznie wyglada". */
.tn-walkover-item { gap: 10px; }
.tn-walkover-actions { width: 100%; display: flex; flex-direction: column; gap: 10px; }
.tn-void-form { margin: 0; padding-top: 10px; border-top: 1px dashed var(--card-border); }
.tn-void-btn { color: #c0392b; border-color: #e8c3bb; display: inline-flex; align-items: center; gap: 5px; }
.tn-void-btn:hover { background: rgba(192, 57, 43, 0.08); }

@media (max-width: 480px) {
    .tn-round { min-width: 170px; }
    .tn-trophy-col { min-width: 120px; }
    .tn-bracket { gap: 34px; }
    .tn-branch:not(:last-child) .tn-match::after { right: -17px; width: 17px; }
    .tn-branch:not(:first-child) .tn-match-slot::before { left: -17px; }
    .tn-branch:not(:first-child) .tn-match::before,
    .tn-branch:not(:first-child) .tn-trophy-node::before { left: -17px; width: 17px; }
}


/* =========================================================
   MOTYW AlkoCzwartek
   Domyślny wygląd serwisu: jasne, kremowe tło, bursztyn z logo jako kolor
   główny, ciemny "atrament" (#20201e — napisy w logo) jako kolor tekstu na
   bursztynie. Reguły z :where(html:not([data-theme-color])) dotyczą WYŁĄCZNIE
   motywu domyślnego — ktoś, kto w profilu wybrał inny kolor (niebieski,
   różowy...), dostaje dotychczasowe przyciski w kolorze swojego motywu.
   :where() zeruje specyficzność prefiksu — reguła ma tę samą wagę co
   oryginalna (np. samo "button"), a wygrywa tylko dlatego, że stoi na końcu
   pliku. Dzięki temu przyciski z własną klasą (ikonki w topbarze, czat,
   gry) zachowują swój wygląd.
   ========================================================= */
:where(html:not([data-theme-color])) button,
:where(html:not([data-theme-color])) .btn {
    background: linear-gradient(135deg, #ebbb55, var(--brand-amber-strong));
    color: var(--brand-ink);
    box-shadow: 0 1px 0 rgba(32,32,30,0.12), inset 0 1px 0 rgba(255,255,255,0.35);
}
:where(html:not([data-theme-color])) .btn.secondary {
    background: transparent;
    color: var(--text-chrome);
    border-color: var(--chrome-border-strong);
    box-shadow: none;
}
:where(html:not([data-theme-color])) .card .btn.secondary { color: #171512; border-color: #d8d3cc; }
:where(html:not([data-theme-color])) .topbar-nav-row a.active,
:where(html:not([data-theme-color])) .nav-dropdown-trigger.active,
:where(html:not([data-theme-color])) .nav-dropdown-menu a.active,
:where(html:not([data-theme-color])) .user-menu-dropdown a.active,
:where(html:not([data-theme-color])) .quick-action:hover,
:where(html:not([data-theme-color])) .chat-edit-save,
:where(html:not([data-theme-color])) .calendar-nav-btn:hover,
:where(html:not([data-theme-color])) .bship-turn-banner.bship-turn-mine {
    background: var(--brand-amber);
    border-color: var(--brand-amber);
    color: var(--brand-ink);
}
/* Cienki pasek w kolorach logo pod górnym paskiem — bursztyn z czerwonym
   akcentem (czerwień ze "2014" i gwiazdek w logo). */
:where(html:not([data-theme-color])) .topbar {
    border-bottom: 0;
    background-image: linear-gradient(90deg, var(--brand-amber) 0%, var(--brand-amber) 86%, var(--brand-red) 86%, var(--brand-red) 100%);
    background-size: 100% 3px;
    background-repeat: no-repeat;
    background-position: bottom left;
}
.topbar .brand img { border-radius: 50%; }

/* Ekrany logowania/rejestracji/resetu hasła (auth-*) — w SOG ciemne
   ("Moto Editorial"), tu jasne, w kolorach logo. Te strony nie mają
   atrybutu data-theme (brak zalogowanej osoby), więc reguły są bezwarunkowe.
   Przy wgranym tle logowania (Narzędzia, .has-bg) zostaje ciemna nakładka
   na zdjęciu, a karta formularza i tak jest jasna i czytelna. */
.auth-shell { background-color: #fbf7ee; background-image: radial-gradient(ellipse at top, #fdf9f1 0%, #f5eedd 60%, #eee2c8 100%); }
.auth-shell.has-bg { background-color: #2a2620; }
.auth-shell::before { background: radial-gradient(circle, rgba(228,173,60,0.28) 0%, rgba(228,173,60,0) 62%); }
.auth-shell::after { background: radial-gradient(circle, rgba(193,0,22,0.08) 0%, rgba(193,0,22,0) 65%); }
.auth-logo img { filter: drop-shadow(0 6px 18px rgba(32,32,30,0.18)); }
.auth-ticker { background: rgba(255,255,255,0.78); color: #45403a; border-left-color: var(--brand-amber); box-shadow: 0 2px 10px rgba(32,32,30,0.06); }
.auth-ticker-dot { background: var(--brand-red); }
.auth-card { background: #ffffff; color: #20201e; border: 1px solid rgba(32,32,30,0.08); box-shadow: 0 10px 34px rgba(32,32,30,0.10); }
.auth-card::before { background: linear-gradient(90deg, var(--brand-amber) 0%, var(--brand-amber) 82%, var(--brand-red) 82%); }
.auth-card h2 { color: #20201e; }
.auth-card .auth-sub, .auth-footer-link, .auth-check { color: #6b6560; }
.auth-card label { color: #45403a; }
.auth-field .auth-field-icon { color: #8a847d; }
.auth-field input {
    background: #fbf8f2 !important;
    border-color: rgba(32,32,30,0.16) !important;
    color: #20201e !important;
}
.auth-field input:-webkit-autofill,
.auth-field input:-webkit-autofill:focus {
    -webkit-text-fill-color: #20201e;
    -webkit-box-shadow: 0 0 0 1000px #fbf8f2 inset;
    caret-color: #20201e;
}
.auth-submit { background: linear-gradient(135deg, #ebbb55, var(--brand-amber-strong)) !important; color: var(--brand-ink) !important; }
.auth-submit:hover { background: var(--brand-amber) !important; }
.auth-footer-link a { color: var(--accent); }
.auth-card .btn.secondary { color: #20201e; border-color: #d8d3cc; }

/* Jasny bursztyn (#e4ad3c) jako kolor TEKSTU na jasnym tle jest
   nieczytelny — w jasnym motywie domyślnym te miejsca dostają ciemniejszy
   --accent. */
:where(html[data-theme="light"]:not([data-theme-color])) .topbar .user-menu-trigger .badge,
:where(html[data-theme="light"]:not([data-theme-color])) .topbar-search:focus-within { color: var(--accent); }


/* --- Kafelek Radia na Pulpicie (AlkoCzwartek) — układ dawnego kafelka
   ShadowManiak (.stat-card-shadowmaniak: przycisk play, nagłówek, tytuł),
   a kolor jak pozostałe kolorowe kafelki statystyk (inline style z
   radio_tile_style(), wybór w Narzędzia → Treści → Radio). */
.stat-card-radio-logo { width: 30px; height: 30px; border-radius: 8px; object-fit: contain; background: #fff; padding: 2px; flex-shrink: 0; box-shadow: 0 1px 3px rgba(0,0,0,0.25); }
.stat-card.stat-card-radio .label { color: rgba(255, 255, 255, 0.85); }
/* Na telefonie (2 kolumny) radio jako 5. kafelek zostawałoby samo w
   ostatnim rzędzie na pół szerokości — rozciągamy je na cały rząd. */
@media (max-width: 720px) {
    .dashboard-grid .stat-card.stat-card-radio { grid-column: 1 / -1; }
}

/* Kafelek radia: treść od góry, na tej samej wysokości co liczby w
   pozostałych kolorowych kafelkach (dawny ShadowManiak centrował w pionie). */
.stat-card.stat-card-radio { align-items: flex-start; padding: 18px; }
.stat-card.stat-card-radio .audycja-play-btn { align-self: flex-start; }
.stat-card.stat-card-radio .stat-card-shadowmaniak-head { min-height: 30px; }
