/* WRSM3 — style aplikacji.
 *
 * Tokeny są WIĄŻĄCE i pochodzą z design/README.md. Artboard, który wpisuje
 * własny odcień, rozjeżdża płótno; tak samo arkusz stylów, który wpisuje
 * odcień zamiast tokenu.
 *
 * CSP jest bez `unsafe-inline` (patrz internal/web/serwer.go), więc każdy
 * styl musi być TUTAJ — żaden szablon nie może mieć atrybutu style="".
 * To jest powód, dla którego ten plik istnieje od pierwszego wydania,
 * a nie „kiedy będzie co stylować".
 */

:root {
  --tlo: #FBF8F3;
  --powierzchnia: #FFFFFF;
  --obwodka: #E7DFD3;
  --tekst: #241F2B;
  --tekst-slaby: #6B6478;
  --glowny: #3F51A8;
  --glowny-ciemny: #2C3A7D;
  --glowny-jasny: #E8ECFA;
  --uwaga: #9A3412;
  --uwaga-jasny: #FDEBDD;
  --zgoda: #1B6E4B;
  --zgoda-jasny: #E4F1EA;

  --skala-tekstu: 1;
}

/*
  STOPIEŃ PISMA — po jednej regule na krok drabiny (REQ-014, do 200%).

  Reguł jest sześć, a nie jedna z wartością z bazy, bo polityka bezpieczeństwa
  nie dopuszcza `unsafe-inline`: skala nie może przyjść atrybutem `style`.
  Arkusz statyczny z sześcioma regułami przeglądarka trzyma w pamięci
  podręcznej; arkusz generowany dla każdego konta osobno — nie.

  Skala mnoży `font-size` na ELEMENCIE GŁÓWNYM, a stamtąd wszystko liczone
  w `rem` i `em` idzie za nią samo. To dlatego w arkuszu nie ma rozmiarów
  w pikselach poza tym jednym miejscem i poza celami dotykowymi, które mają
  zostać wielkości palca niezależnie od stopnia pisma.

  NA `html`, A NIE NA `body` — i to jest cała usterka #107. Do wydania 0.29.0
  mnożenie stało na `body`, a komentarz obiecywał dokładnie to, co jest wyżej.
  `rem` liczy się jednak od elementu GŁÓWNEGO, nie od `body`, więc `<html>`
  zostawało przy 16 px na każdym stopniu drabiny i za skalą szło wyłącznie to,
  co rozmiar dziedziczy albo liczy w `em`. Czterdzieści rozmiarów zapisanych
  w `rem` było głuchych na drabinę.

  Widać to było na pieśni: wariant „z akordami" rozmiaru nie ustawia, więc
  dziedziczył i rósł, a wariant „sam tekst" ma własne `1.05rem` i stał
  w miejscu. Jeden guzik, dwa różne skutki, zależnie od zakładki.

  Oba testy stopnia pisma były przy tym ZIELONE, bo pytały o atrybut
  `data-skala` na `<html>` — a atrybut dochodził poprawnie. Atrybut ustawiony
  nie znaczy tekst większy; mierzy to teraz `TestStopienPismaPowiekszaTekstPiesni`.
*/
:root[data-skala="115"] { --skala-tekstu: 1.15; }
:root[data-skala="130"] { --skala-tekstu: 1.3; }
:root[data-skala="150"] { --skala-tekstu: 1.5; }
:root[data-skala="175"] { --skala-tekstu: 1.75; }
:root[data-skala="200"] { --skala-tekstu: 2; }

/*
  TRYB CIEMNY — atrybut ma trzy położenia, przełącznik w pasku dwa (REQ-004).

  BRAK ATRYBUTU ZNACZY „AUTO", czyli „za ustawieniem systemu, NA ŻYWO"
  (rozstrzygnięcie właściciela produktu z 7 września 2026, issue #120). Tak
  wygląda dokument, gdy tryb domyślny konta to „auto" i nikt nie kliknął
  guzika w pasku — i to jest jedyny powód, dla którego „auto" nie potrzebuje
  ani jednego nasłuchu w skrypcie: przejście o zmierzchu robi ta reguła sama.
  Wyliczenie „auto" w skrypcie albo na serwerze zamroziłoby je na moment
  żądania, a właściciel produktu odrzucił i to, i liczenie pory dnia własnym
  kodem.

  `light` i `dark` to WYBÓR: tryb domyślny „dzień"/„noc" z `/ustawienia`
  (atrybut stawia serwer w `baza.tmpl`) albo doraźne przełączenie ikoną
  (atrybut stawia `wyglad.js` z `sessionStorage`). Sam guzik przełącza od
  issue #118 wyłącznie dzień ↔ noc, a wybór trybu domyślnego mieszka
  w „Ustawieniach" (issue #120).

  Domyślnie idzie za ustawieniem systemu. Wybór użytkownika stawia
  `data-theme` na `<html>` — tym samym atrybutem, którym steruje się Bulmą i MUSI wygrać w obie strony: kto na ciemnym
  systemie wybrał dzień, ma dostać jasny motyw, a nie ciemny. Dlatego
  regułą jest osobny selektor, a nie tylko `@media`.
  
  Wartości są wypisane dwa razy i to jest cena tej reguły w czystym CSS:
  bez preprocesora nie da się użyć jednej listy w dwóch selektorach. Lista
  ciemna jest jedna — poniżej `--ciemny-*` — a oba selektory tylko ją
  przepisują, więc przy zmianie barwy poprawia się jedno miejsce.
*/
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --tlo: #17141C;
    --powierzchnia: #211D29;
    --obwodka: #3A3345;
    --tekst: #F2EFE9;
    --tekst-slaby: #B3ACC0;
    --glowny: #9FB0F0;
    --glowny-ciemny: #C6D0F7;
    --glowny-jasny: #2A2C4A;
    /* Stanowe pary barw MAJĄ WŁASNE WARTOŚCI CIEMNE (issue #118). Bez nich
       ciemna zieleń #1B6E4B stała na ciemnym tle wszędzie tam, gdzie niesie
       sam tekst: `.stan-zaakceptowana` na powierzchni karty dawało 2,0 : 1,
       a `.powod` w czerwieni #9A3412 na tle strony 2,5 : 1. Wskaźnik sieci
       jest tu drugim powodem, ważniejszym od wydania 0.36: to jego para
       (kreska ikony plus tło plakietki) niesie stan zamiast napisu, więc
       nadpisanie w motywie ciemnym JEDNEGO jej ramienia — tak jak w usterce
       #110 — zdjęłoby kontrast do 2,1 : 1. Pilnują tego dwa testy naraz:
       `TestKontrastParBarwWObuMotywach` i
       `TestKontrastWskaznikaSieciWeWszystkichStanach`. */
    --uwaga: #F2A88C;
    --uwaga-jasny: #3A1F14;
    --zgoda: #6FD3A2;
    --zgoda-jasny: #14361F;
  }
}

:root[data-theme="dark"] {
  --tlo: #17141C;
  --powierzchnia: #211D29;
  --obwodka: #3A3345;
  --tekst: #F2EFE9;
  --tekst-slaby: #B3ACC0;
  --glowny: #9FB0F0;
  --glowny-ciemny: #C6D0F7;
  --glowny-jasny: #2A2C4A;
  --uwaga: #F2A88C;
  --uwaga-jasny: #3A1F14;
  --zgoda: #6FD3A2;
  --zgoda-jasny: #14361F;
}

/* >>> SCHEMATY KOLORÓW — POCZĄTEK
 *   Sekcja WYgenerowana programowo z `store.CSSSchematow()`
 *   (internal/store/schematy_kolorow.go). Ręczne poprawki znikną
 *   przy najbliższym wygenerowaniu: zmiany wpisujesz w tabeli
 *   `SchematyKolorow`, a potem `go run scripts/schematy-css.go`.
 */
/* Schemat: Domyślny (Domyślny) */
:root[data-schemat=""] {
  --tlo: #FBF8F3;
  --powierzchnia: #FFFFFF;
  --obwodka: #E7DFD3;
  --tekst: #241F2B;
  --tekst-slaby: #6B6478;
  --glowny: #3F51A8;
  --glowny-ciemny: #2C3A7D;
  --glowny-jasny: #E8ECFA;
  --uwaga: #9A3412;
  --uwaga-jasny: #FDEBDD;
  --zgoda: #1B6E4B;
  --zgoda-jasny: #E4F1EA;
}
:root[data-schemat=""][data-theme="dark"] {
  --tlo: #17141C;
  --powierzchnia: #211D29;
  --obwodka: #3A3345;
  --tekst: #F2EFE9;
  --tekst-slaby: #B3ACC0;
  --glowny: #9FB0F0;
  --glowny-ciemny: #C6D0F7;
  --glowny-jasny: #2A2C4A;
  --uwaga: #F2A88C;
  --uwaga-jasny: #3A1F14;
  --zgoda: #6FD3A2;
  --zgoda-jasny: #14361F;
}
@media (prefers-color-scheme: dark) {
  :root[data-schemat=""]:not([data-theme="light"]) {
    --tlo: #17141C;
    --powierzchnia: #211D29;
    --obwodka: #3A3345;
    --tekst: #F2EFE9;
    --tekst-slaby: #B3ACC0;
    --glowny: #9FB0F0;
    --glowny-ciemny: #C6D0F7;
    --glowny-jasny: #2A2C4A;
    --uwaga: #F2A88C;
    --uwaga-jasny: #3A1F14;
    --zgoda: #6FD3A2;
    --zgoda-jasny: #14361F;
  }
}
[data-klucz-schematu=""] .plamka-tlo { background-color: #FBF8F3; }
[data-klucz-schematu=""] .plamka-powierzchnia { background-color: #FFFFFF; }
[data-klucz-schematu=""] .plamka-obwodka { background-color: #E7DFD3; }
[data-klucz-schematu=""] .plamka-glowny { background-color: #3F51A8; }
[data-klucz-schematu=""] .plamka-glowny-jasny { background-color: #E8ECFA; }
[data-klucz-schematu=""] .plamka-tekst { background-color: #241F2B; }
/* Schemat: Leśny (lesny) */
:root[data-schemat="lesny"] {
  --tlo: #F2F6F1;
  --powierzchnia: #FFFFFF;
  --obwodka: #D9E2D6;
  --tekst: #1F2B23;
  --tekst-slaby: #55625A;
  --glowny: #2E6B4F;
  --glowny-ciemny: #1F5340;
  --glowny-jasny: #DCEBE2;
  --uwaga: #9A3412;
  --uwaga-jasny: #FDEBDD;
  --zgoda: #1B6E4B;
  --zgoda-jasny: #E4F1EA;
}
:root[data-schemat="lesny"][data-theme="dark"] {
  --tlo: #121816;
  --powierzchnia: #1C251F;
  --obwodka: #33413A;
  --tekst: #EFF5F0;
  --tekst-slaby: #A9B8AE;
  --glowny: #74CDA0;
  --glowny-ciemny: #97E0BB;
  --glowny-jasny: #1D392C;
  --uwaga: #F2A88C;
  --uwaga-jasny: #3A1F14;
  --zgoda: #6FD3A2;
  --zgoda-jasny: #14361F;
}
@media (prefers-color-scheme: dark) {
  :root[data-schemat="lesny"]:not([data-theme="light"]) {
    --tlo: #121816;
    --powierzchnia: #1C251F;
    --obwodka: #33413A;
    --tekst: #EFF5F0;
    --tekst-slaby: #A9B8AE;
    --glowny: #74CDA0;
    --glowny-ciemny: #97E0BB;
    --glowny-jasny: #1D392C;
    --uwaga: #F2A88C;
    --uwaga-jasny: #3A1F14;
    --zgoda: #6FD3A2;
    --zgoda-jasny: #14361F;
  }
}
[data-klucz-schematu="lesny"] .plamka-tlo { background-color: #F2F6F1; }
[data-klucz-schematu="lesny"] .plamka-powierzchnia { background-color: #FFFFFF; }
[data-klucz-schematu="lesny"] .plamka-obwodka { background-color: #D9E2D6; }
[data-klucz-schematu="lesny"] .plamka-glowny { background-color: #2E6B4F; }
[data-klucz-schematu="lesny"] .plamka-glowny-jasny { background-color: #DCEBE2; }
[data-klucz-schematu="lesny"] .plamka-tekst { background-color: #1F2B23; }
/* Schemat: Morski (morski) */
:root[data-schemat="morski"] {
  --tlo: #F0F5F8;
  --powierzchnia: #FFFFFF;
  --obwodka: #D3E0E8;
  --tekst: #1B2633;
  --tekst-slaby: #4E5F70;
  --glowny: #1F6FB2;
  --glowny-ciemny: #13558C;
  --glowny-jasny: #DBEAF4;
  --uwaga: #9A3412;
  --uwaga-jasny: #FDEBDD;
  --zgoda: #1B6E4B;
  --zgoda-jasny: #E4F1EA;
}
:root[data-schemat="morski"][data-theme="dark"] {
  --tlo: #10161C;
  --powierzchnia: #1A222B;
  --obwodka: #2E3D4C;
  --tekst: #EDF3F8;
  --tekst-slaby: #A9B8C7;
  --glowny: #6FBFFF;
  --glowny-ciemny: #9ED4FF;
  --glowny-jasny: #1B3A54;
  --uwaga: #F2A88C;
  --uwaga-jasny: #3A1F14;
  --zgoda: #6FD3A2;
  --zgoda-jasny: #14361F;
}
@media (prefers-color-scheme: dark) {
  :root[data-schemat="morski"]:not([data-theme="light"]) {
    --tlo: #10161C;
    --powierzchnia: #1A222B;
    --obwodka: #2E3D4C;
    --tekst: #EDF3F8;
    --tekst-slaby: #A9B8C7;
    --glowny: #6FBFFF;
    --glowny-ciemny: #9ED4FF;
    --glowny-jasny: #1B3A54;
    --uwaga: #F2A88C;
    --uwaga-jasny: #3A1F14;
    --zgoda: #6FD3A2;
    --zgoda-jasny: #14361F;
  }
}
[data-klucz-schematu="morski"] .plamka-tlo { background-color: #F0F5F8; }
[data-klucz-schematu="morski"] .plamka-powierzchnia { background-color: #FFFFFF; }
[data-klucz-schematu="morski"] .plamka-obwodka { background-color: #D3E0E8; }
[data-klucz-schematu="morski"] .plamka-glowny { background-color: #1F6FB2; }
[data-klucz-schematu="morski"] .plamka-glowny-jasny { background-color: #DBEAF4; }
[data-klucz-schematu="morski"] .plamka-tekst { background-color: #1B2633; }
/* Schemat: Słoneczny (sloneczny) */
:root[data-schemat="sloneczny"] {
  --tlo: #FAF4E6;
  --powierzchnia: #FFFFFF;
  --obwodka: #E5D8BC;
  --tekst: #2D2516;
  --tekst-slaby: #6B5E42;
  --glowny: #8A5B00;
  --glowny-ciemny: #6B4600;
  --glowny-jasny: #F3E7CF;
  --uwaga: #9A3412;
  --uwaga-jasny: #FDEBDD;
  --zgoda: #1B6E4B;
  --zgoda-jasny: #E4F1EA;
}
:root[data-schemat="sloneczny"][data-theme="dark"] {
  --tlo: #1B160E;
  --powierzchnia: #262015;
  --obwodka: #3D3628;
  --tekst: #F8F2E4;
  --tekst-slaby: #C2B89E;
  --glowny: #FFC14D;
  --glowny-ciemny: #FFD988;
  --glowny-jasny: #3A2F1A;
  --uwaga: #F2A88C;
  --uwaga-jasny: #3A1F14;
  --zgoda: #6FD3A2;
  --zgoda-jasny: #14361F;
}
@media (prefers-color-scheme: dark) {
  :root[data-schemat="sloneczny"]:not([data-theme="light"]) {
    --tlo: #1B160E;
    --powierzchnia: #262015;
    --obwodka: #3D3628;
    --tekst: #F8F2E4;
    --tekst-slaby: #C2B89E;
    --glowny: #FFC14D;
    --glowny-ciemny: #FFD988;
    --glowny-jasny: #3A2F1A;
    --uwaga: #F2A88C;
    --uwaga-jasny: #3A1F14;
    --zgoda: #6FD3A2;
    --zgoda-jasny: #14361F;
  }
}
[data-klucz-schematu="sloneczny"] .plamka-tlo { background-color: #FAF4E6; }
[data-klucz-schematu="sloneczny"] .plamka-powierzchnia { background-color: #FFFFFF; }
[data-klucz-schematu="sloneczny"] .plamka-obwodka { background-color: #E5D8BC; }
[data-klucz-schematu="sloneczny"] .plamka-glowny { background-color: #8A5B00; }
[data-klucz-schematu="sloneczny"] .plamka-glowny-jasny { background-color: #F3E7CF; }
[data-klucz-schematu="sloneczny"] .plamka-tekst { background-color: #2D2516; }
/* Schemat: Różany (rozany) */
:root[data-schemat="rozany"] {
  --tlo: #FAF1F2;
  --powierzchnia: #FFFFFF;
  --obwodka: #E4D0D4;
  --tekst: #301F24;
  --tekst-slaby: #6D5A61;
  --glowny: #A63A5C;
  --glowny-ciemny: #852A49;
  --glowny-jasny: #F3DFE5;
  --uwaga: #9A3412;
  --uwaga-jasny: #FDEBDD;
  --zgoda: #1B6E4B;
  --zgoda-jasny: #E4F1EA;
}
:root[data-schemat="rozany"][data-theme="dark"] {
  --tlo: #191214;
  --powierzchnia: #241C1F;
  --obwodka: #3A2C31;
  --tekst: #F6EFF1;
  --tekst-slaby: #C0AEB5;
  --glowny: #F08FAD;
  --glowny-ciemny: #F7A9C1;
  --glowny-jasny: #3A2230;
  --uwaga: #F2A88C;
  --uwaga-jasny: #3A1F14;
  --zgoda: #6FD3A2;
  --zgoda-jasny: #14361F;
}
@media (prefers-color-scheme: dark) {
  :root[data-schemat="rozany"]:not([data-theme="light"]) {
    --tlo: #191214;
    --powierzchnia: #241C1F;
    --obwodka: #3A2C31;
    --tekst: #F6EFF1;
    --tekst-slaby: #C0AEB5;
    --glowny: #F08FAD;
    --glowny-ciemny: #F7A9C1;
    --glowny-jasny: #3A2230;
    --uwaga: #F2A88C;
    --uwaga-jasny: #3A1F14;
    --zgoda: #6FD3A2;
    --zgoda-jasny: #14361F;
  }
}
[data-klucz-schematu="rozany"] .plamka-tlo { background-color: #FAF1F2; }
[data-klucz-schematu="rozany"] .plamka-powierzchnia { background-color: #FFFFFF; }
[data-klucz-schematu="rozany"] .plamka-obwodka { background-color: #E4D0D4; }
[data-klucz-schematu="rozany"] .plamka-glowny { background-color: #A63A5C; }
[data-klucz-schematu="rozany"] .plamka-glowny-jasny { background-color: #F3DFE5; }
[data-klucz-schematu="rozany"] .plamka-tekst { background-color: #301F24; }
/* Schemat: Lawendowy (lawendowy) */
:root[data-schemat="lawendowy"] {
  --tlo: #F5F1FA;
  --powierzchnia: #FFFFFF;
  --obwodka: #DFD6EC;
  --tekst: #272030;
  --tekst-slaby: #5F5570;
  --glowny: #5B3F9E;
  --glowny-ciemny: #452C7D;
  --glowny-jasny: #EAE2F8;
  --uwaga: #9A3412;
  --uwaga-jasny: #FDEBDD;
  --zgoda: #1B6E4B;
  --zgoda-jasny: #E4F1EA;
}
:root[data-schemat="lawendowy"][data-theme="dark"] {
  --tlo: #16121C;
  --powierzchnia: #201A2A;
  --obwodka: #352D45;
  --tekst: #F1EDF7;
  --tekst-slaby: #B3A8C6;
  --glowny: #A98BF4;
  --glowny-ciemny: #C0A9FA;
  --glowny-jasny: #2E1F44;
  --uwaga: #F2A88C;
  --uwaga-jasny: #3A1F14;
  --zgoda: #6FD3A2;
  --zgoda-jasny: #14361F;
}
@media (prefers-color-scheme: dark) {
  :root[data-schemat="lawendowy"]:not([data-theme="light"]) {
    --tlo: #16121C;
    --powierzchnia: #201A2A;
    --obwodka: #352D45;
    --tekst: #F1EDF7;
    --tekst-slaby: #B3A8C6;
    --glowny: #A98BF4;
    --glowny-ciemny: #C0A9FA;
    --glowny-jasny: #2E1F44;
    --uwaga: #F2A88C;
    --uwaga-jasny: #3A1F14;
    --zgoda: #6FD3A2;
    --zgoda-jasny: #14361F;
  }
}
[data-klucz-schematu="lawendowy"] .plamka-tlo { background-color: #F5F1FA; }
[data-klucz-schematu="lawendowy"] .plamka-powierzchnia { background-color: #FFFFFF; }
[data-klucz-schematu="lawendowy"] .plamka-obwodka { background-color: #DFD6EC; }
[data-klucz-schematu="lawendowy"] .plamka-glowny { background-color: #5B3F9E; }
[data-klucz-schematu="lawendowy"] .plamka-glowny-jasny { background-color: #EAE2F8; }
[data-klucz-schematu="lawendowy"] .plamka-tekst { background-color: #272030; }
/* Schemat: Grafitowy (grafitowy) */
:root[data-schemat="grafitowy"] {
  --tlo: #F2F2F4;
  --powierzchnia: #FFFFFF;
  --obwodka: #D9D9DF;
  --tekst: #23232A;
  --tekst-slaby: #5E5E6A;
  --glowny: #3A3A46;
  --glowny-ciemny: #26262F;
  --glowny-jasny: #E4E4EA;
  --uwaga: #9A3412;
  --uwaga-jasny: #FDEBDD;
  --zgoda: #1B6E4B;
  --zgoda-jasny: #E4F1EA;
}
:root[data-schemat="grafitowy"][data-theme="dark"] {
  --tlo: #131316;
  --powierzchnia: #1D1D23;
  --obwodka: #33333B;
  --tekst: #F0F0F3;
  --tekst-slaby: #ADADB8;
  --glowny: #B9B9C8;
  --glowny-ciemny: #D2D2DD;
  --glowny-jasny: #2A2A33;
  --uwaga: #F2A88C;
  --uwaga-jasny: #3A1F14;
  --zgoda: #6FD3A2;
  --zgoda-jasny: #14361F;
}
@media (prefers-color-scheme: dark) {
  :root[data-schemat="grafitowy"]:not([data-theme="light"]) {
    --tlo: #131316;
    --powierzchnia: #1D1D23;
    --obwodka: #33333B;
    --tekst: #F0F0F3;
    --tekst-slaby: #ADADB8;
    --glowny: #B9B9C8;
    --glowny-ciemny: #D2D2DD;
    --glowny-jasny: #2A2A33;
    --uwaga: #F2A88C;
    --uwaga-jasny: #3A1F14;
    --zgoda: #6FD3A2;
    --zgoda-jasny: #14361F;
  }
}
[data-klucz-schematu="grafitowy"] .plamka-tlo { background-color: #F2F2F4; }
[data-klucz-schematu="grafitowy"] .plamka-powierzchnia { background-color: #FFFFFF; }
[data-klucz-schematu="grafitowy"] .plamka-obwodka { background-color: #D9D9DF; }
[data-klucz-schematu="grafitowy"] .plamka-glowny { background-color: #3A3A46; }
[data-klucz-schematu="grafitowy"] .plamka-glowny-jasny { background-color: #E4E4EA; }
[data-klucz-schematu="grafitowy"] .plamka-tekst { background-color: #23232A; }
SCHEMATY KOLORÓW — KONIEC */

/* GALERIA SCHEMATÓW (REQ-005) — statyczna szata; same barwy plamek stawiają
   reguły `[data-klucz-schematu="…"] .plamka-*` z sekcji wyżej, WYgenerowane
   z tej samej tabeli, z której stawiane są tokeny. */
.galeria-schematow {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: 0.75rem;
}

.kafel-schemat {
  position: relative;
  display: block;
  padding: 0.5rem;
  cursor: pointer;
  border: 1px solid var(--obwodka);
  border-radius: 0.5rem;
  background: var(--powierzchnia);
}

.kafel-schemat:hover {
  border-color: var(--glowny);
}

.kafel-schemat:focus-within {
  outline: 2px solid var(--glowny);
  outline-offset: 2px;
}

.kafel-schemat.wybrany {
  border-color: var(--glowny);
  box-shadow: 0 0 0 2px var(--glowny-jasny);
}

/* Radio jest niewidoczne wzrokowo, ale stoi w drzewie i reaguje z klawiatury
   (strzałki przełączają kafle, `change` jedzie do `wyglad.js`). */
.wybor-schematu {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.podglad-schematu {
  display: flex;
  gap: 2px;
}

.podglad-schematu .plamka {
  flex: 1 1 0;
  height: 1.4rem;
  border-radius: 0.25rem;
}

.nazwa-schematu {
  display: block;
  margin-top: 0.4rem;
  text-align: center;
  font-size: 0.85rem;
  color: var(--tekst);
}

.opis-schematow {
  font-size: 0.9rem;
  color: var(--tekst-slaby);
}

* { box-sizing: border-box; }

/* Podstawa drabiny stopnia pisma — patrz komentarz przy `--skala-tekstu`.
   Musi stać na `html`, bo `rem` liczy się od elementu głównego (#107). */
html { font-size: calc(16px * var(--skala-tekstu)); }

body {
  margin: 0;
  background: var(--tlo);
  color: var(--tekst);
  font-family: 'Inter', ui-sans-serif, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: 'Bitter', 'Inter', ui-serif, serif;
  letter-spacing: -0.01em;
  margin: 0 0 12px;
}

a { color: var(--glowny); }

/* ---- pasek aplikacji ---------------------------------------------------- */

.pasek {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  background: var(--powierzchnia);
  border-bottom: 1px solid var(--obwodka);
}

.znak {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  text-decoration: none;
  /* Bulma daje `.navbar-item` 8 px pionowego odstępu — dla znaku, którego
     wysokość wyznacza sam rysunek (issue #148), ten odstęp podwajałby
     wiersz. Poziomy odstęp pozostaje z Bulmy i powiększa obszar kliknięcia. */
  padding-top: 0;
  padding-bottom: 0;
}

/*
  `max-height` znosi limit Bulmy `.navbar-item img` (1.75rem = 28 px przy
  skali 1). Znak wspólnoty jest znakiem, a nie ikoną pozycji menu, więc jego
  rozmiar deklarują atrybuty `width`/`height` w pasku (REQ-052, issue #148).
  Selektor jest dwa razy dłuższy niż Bulmy, bo tamten ma większą specyficzność
  (.navbar-item + img) i bez tego przegrałby.
*/
.pasek .znak-ikona { max-height: none; }

/*
  Znak wspólnoty (`znak.svg`) NIE DZIEDZICZY koloru: ma własne barwy —
  granat, złoto i kremowe pole (REQ-052). Stała tu wcześniej reguła
  `color: var(--glowny)`, która niczego nie robiła, bo rysunek nie używa
  `currentColor`. Znak jest JEDEN dla obu motywów — decyzja w REQ-052.
*/

/*
  OZNACZENIE TESTOWE ŚRODOWISKA STAGE (REQ-079, issue #354).
  Tło znaku na czerwono odróżnia środowisko testowe od produkcji widocznie
  i natychmiast — zanim ktokolwiek przeczyta pierwszy napis na stronie.
  Selektor idzie OD `<html>` przez `data-stage`, a nie od klasy, żeby
  ani jedna strona renderowana bez tego pola nie dostała czerwonych pleców
  przypadkiem.
*/
[data-stage] .znak {
  background-color: #cc0000;
  border-radius: 4px;
}

/*
  KONTROLKI PASKA STOJĄ PRZY PRAWEJ KRAWĘDZI — na każdej szerokości
  (zgłoszenie właściciela produktu, 2026-09-05).

  Bulma odsuwa `navbar-end` do prawej dopiero powyżej 1024 px, bo na wąskim
  ekranie zakłada, że `navbar-menu` jest osobnym, rozwijanym blokiem pod
  paskiem. U nas `navbar-end` jest bezpośrednim dzieckiem paska i ma zostać
  rzędem także na telefonie — więc odsunięcie deklarujemy sami.
  Bez tego przełącznik motywu kończył się na 180 px z 390 px szerokości
  ekranu, czyli w połowie paska.

  Awatar jest OSTATNI, a przełącznik motywu przed nim: awatar prowadzi na
  własny profil (i dalej do wylogowania), więc to on jest kontrolką „moje
  konto" — a taka stoi na końcu paska w każdej aplikacji, którą wspólnota zna
  skądinąd.
*/
.pasek .navbar-end {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/*
  `rozpychacz` zniknął razem z własnym paskiem — `navbar-end` Bulmy odsuwa
  kontrolki do prawej krawędzi samym układem.
*/

/* Cele dotykowe od 44 px, akcje główne 56–64 px (reguła 1 z design/README.md,
 * REQ-016). Ten próg dotyczy TAKŻE przycisków ikonowych — ikona rysowana na
 * 20 px nie zwalnia z rozmiaru celu. */
.kontrolka {
  width: 44px;
  height: 44px;
  /* `padding: 0` jest tu potrzebne, a nie ozdobne: `.button` Bulmy daje
   * `padding: … 1em`, czyli 32 px w poziomie z 44 px szerokości — znak
   * zostałby ściśnięty do dwunastu. */
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--obwodka);
  border-radius: 12px;
  background: var(--powierzchnia);
  color: var(--tekst);
  cursor: pointer;
}

/*
  PRZEŁĄCZNIK MOTYWU — dwa tryby, dwa rysunki (REQ-004, issue #118).

  Wybór idzie atrybutem `data-motyw` na elemencie głównym — stawia go serwer
  (`baza.tmpl`, tryb domyślny „dzień"/„noc") albo `wyglad.js` (zawieszenie
  z `sessionStorage`), zawsze w tej samej chwili co `data-theme`, czyli PRZED
  PIERWSZYM RYSOWANIEM.

  W TRYBIE „AUTO" ATRYBUTU NIE MA i wtedy rysunek idzie za
  `prefers-color-scheme` dokładnie tak, jak reszta motywu: samym arkuszem, na
  żywo, bez ani jednego nasłuchu w skrypcie. Skrypt nasłuchuje wyłącznie po
  to, żeby przepisać ETYKIETĘ SŁOWNĄ (`aria-label`) — arkusz stylów nie ma
  jak zmienić tekstu, a stan nie może być niesiony samym kolorem
  (reguła 2 `design/README.md`).
*/
.ikona-motywu { display: none; }
:root[data-motyw="dzien"] .ikona-motywu[data-motyw="dzien"],
:root[data-motyw="noc"] .ikona-motywu[data-motyw="noc"] { display: block; }
:root:not([data-motyw]) .ikona-motywu[data-motyw="dzien"] { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-motyw]):not([data-theme="light"]) .ikona-motywu[data-motyw="dzien"] { display: none; }
  :root:not([data-motyw]):not([data-theme="light"]) .ikona-motywu[data-motyw="noc"] { display: block; }
}

/*
  SELEKTOR `.pasek a.awatar`, A NIE `.awatar` — i to nie jest ozdoba
  specyficzności. Bulma daje `a.navbar-item { background-color: hsla(…, 0) }`,
  czyli tło PRZEZROCZYSTE, a selektor klasa+typ bije jednoczłonową `.awatar`.
  Reguła `background: var(--glowny-jasny)` stała tu od pierwszego wydania
  i nie robiła NIC: kółko awatara nie miało wypełnienia w ogóle, a wyglądało
  na działające, bo pasek pod nim jest jasny. Znalezione przy zgłoszeniu #148,
  gdy obwódka też nie chciała wejść z tego samego powodu.
*/
.pasek a.awatar,
.awatar {
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--glowny-jasny);
  border: 2px solid var(--glowny);
  color: var(--glowny-ciemny);
  font-weight: 700;
  font-size: 0.85rem;
  text-decoration: none;
  overflow: hidden;
}

/* Zdjęcie w awatarze (REQ-065) wypełnia kółko i nie wystaje poza obwódkę. */
.pasek a.awatar img.awatar-zdjecie {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 999px;
  display: block;
}

.wyloguj { margin: 0; display: inline-flex; }

/* ---- treść -------------------------------------------------------------- */

main { padding: 20px 16px 32px; }

/*
  `.pomocniczy` NIE NIESIE JUŻ WYGLĄDU — dają go klasy Bulmy
  `is-size-7 has-text-grey`, dopisane obok w szablonach. Nazwa zostaje, bo
  mówi, CZYM ten tekst jest (informacja drugorzędna), a nie jak wygląda;
  po niej szukają go też kontrole spójności i reguły wydruku.
*/
.pomocniczy { }

/* ---- wskaźnik stanu sieci ----------------------------------------------- */

/*
  Stan NIGDY nie jest samym kolorem (reguła 2) — ale od 6 września 2026 nie
  jest też niesiony napisem w pasku: właściciel produktu zdjął go stamtąd we
  wszystkich stanach (issue #118, REQ-002). Nośniki są odtąd dwa i działają
  RAZEM, nie zamiennie:

    - KSZTAŁT — cztery różne rysunki w `wskaznik-sieci.tmpl`, wybierane niżej
      po `data-stan`; zostaje czytelny w odcieniach szarości, w trybie
      wysokiego kontrastu i na wydruku, gdzie tło potrafi zniknąć;
    - BARWA — para „kreska ikony + tło plakietki", osobna dla każdego stanu;
      tło widać kątem oka z dalej niż cienką linię.

  Każda z tych par ma kontrast co najmniej 4,5 : 1 w OBU motywach. Stan,
  w którym wskaźnik akurat stoi, mierzy `TestKontrastParBarwWObuMotywach`
  na kilkunastu ekranach; WSZYSTKIE cztery stany —
  `TestKontrastWskaznikaSieciWeWszystkichStanach`. Wartości ciemne tokenów
  `uwaga` i `zgoda` (oraz ich odpowiedników jasnych) są na górze tego pliku.

  Napis zostaje w dokumencie i w `title`; w pasku jest ukryty regułą niżej.
*/
.wskaznik-sieci {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--obwodka);
  background: var(--powierzchnia);
  font-size: 0.8rem;
  color: var(--tekst-slaby);
}

/* Rysunek stanu — jeden z czterech. Reszta czeka w dokumencie. */
.wskaznik-ikona { display: none; }
.wskaznik-sieci[data-stan="sprawdzam"] .wskaznik-ikona[data-stan="sprawdzam"],
.wskaznik-sieci[data-stan="online"] .wskaznik-ikona[data-stan="online"],
.wskaznik-sieci[data-stan="synchronizacja"] .wskaznik-ikona[data-stan="synchronizacja"],
.wskaznik-sieci[data-stan="offline"] .wskaznik-ikona[data-stan="offline"] { display: block; }

.wskaznik-sieci[data-stan="online"] {
  color: var(--zgoda);
  background: var(--zgoda-jasny);
  border-color: var(--zgoda);
}
.wskaznik-sieci[data-stan="offline"] {
  color: var(--uwaga);
  background: var(--uwaga-jasny);
  border-color: var(--uwaga);
}
.wskaznik-sieci[data-stan="synchronizacja"] {
  color: var(--glowny-ciemny);
  background: var(--glowny-jasny);
  border-color: var(--glowny);
}
/* Stan przejściowy tuż po wczytaniu strony — cichy i BEZ własnego tła, bo za
   chwilę rozstrzygnie się w „połączono" albo „brak sieci" (issue 1333).
   Brak barwnego tła jest tu tak samo rozpoznawalny jak tło u sąsiadów. */
.wskaznik-sieci[data-stan="sprawdzam"] {
  color: var(--tekst-slaby);
  background: var(--powierzchnia);
  border-color: var(--obwodka);
}

/*
  NAPIS ZNIKA WYŁĄCZNIE W PASKU (REQ-002, issue #118). Zajmował 62 px ze 215
  px całego `navbar-end` — ponad połowę miejsca, które w pasku mają kontrolki
  konta. W TREŚCI ekranu logowania ten sam napis zostaje widoczny: tam nie
  konkuruje o miejsce, a jest jedyną rzeczą odróżniającą brak łączności od
  złego hasła (rozbieżność 25).

  UKRYCIE, A NIE USUNIĘCIE: `display: none` zabrałby ten tekst także
  czytnikowi ekranu i `aria-live` nie miałoby czego ogłosić.
*/
.pasek .wskaznik-sieci { gap: 0; padding: 6px; }
.pasek .wskaznik-sieci .wskaznik-tekst {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/*
  WYBÓR PLIKU ZDJĘCIA (REQ-065): pole `<input type="file">` jest UKRYTE
  PRZED OKIEM (ten sam wzorzec `clip-path`, co tekst wskaźnika sieci), ale
  ZOSTAJE W STRUKTURZE — etykieta `label` z klasą-guzikiem otacza pole, więc
  klawiatura dociera do niego, a czytnik czyta nazwę z `label`. Formularz
  wgrywa po zmniejszeniu do wersji ekranowej (`kwadrat.js`); pole bez
  `productive` przeglądarki obsługuje `capture` dla aparatu (migawka prosto
  z telefonu).
*/
.wybor-zdjecia input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/*
  LICZNIK AKCJI OCZEKUJĄCYCH (REQ-003, FEAT-002 scenariusz 5, issue #139).

  Stoi tuż przy plakietce sieci i nosi TĘ SAMĄ PARĘ BARW co stan „brak
  sieci" (`--uwaga` na `--uwaga-jasny`) — nie z oszczędności, tylko dlatego,
  że mówi o tej samej sprawie: coś jeszcze nie dotarło do wspólnoty. Para
  jest już zmierzona w obu motywach, a jej kontrast pilnuje
  `TestKontrastLicznikaOczekujacych`.

  TREŚCIĄ JEST LICZBA, więc znaczenie nie wisi na kolorze (reguła 2 makiet):
  kto nie rozróżnia barw, czyta cyfrę, a czytnik ekranu dostaje pełne zdanie
  w `aria-label` (`oczekujace.js`).

  `min-width` z `text-align`, bo licznik zmienia się przy każdej wysłanej
  akcji: bez tego pasek drgałby o kilka pikseli przy przejściu z 10 na 9.

  Element nie ma stanu „zero" — przy pustej kolejce `oczekujace.js` stawia na
  nim `hidden`, czyli zdejmuje go także czytnikowi ekranu.
*/
.licznik-oczekujacych {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.6rem;
  margin-left: 4px;
  padding: 2px 6px;
  border-radius: 999px;
  border: 1px solid var(--uwaga);
  background: var(--uwaga-jasny);
  color: var(--uwaga);
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.2;
}
.licznik-oczekujacych[hidden] { display: none; }

/*
  PLAKIETKA NOWEJ WERSJI (REQ-053, FEAT-048, issue #149).

  Rozstrzygnięcie właściciela produktu z 7 września 2026: sam badge z numerem
  nowej wersji, ewentualnie z ikonką aktualizacji — żadnych zdań.

  ZNACZENIA NIE NOSI KOLOR (reguła 2 makiet). Poza parą barw plakietka niesie
  IKONĘ strzałki odświeżania oraz `aria-label` i `title` ze zdaniem
  „Zaktualizuj aplikację do wersji X" (wpisuje je `wersja.js`) — bo sam numer
  nie mówi czytnikowi ekranu, że to aktualizacja. Kontrast pary mierzy test
  przeglądarkowy w OBU motywach.

  PARA `--glowny` NA `--glowny-jasny`, a nie `--uwaga` jak licznik
  oczekujących. Rozróżnienie jest treścią, nie ozdobą: licznik i wskaźnik
  sieci mówią „coś nie doszło", a nowa wersja jest OFERTĄ, nie kłopotem.
  Barwa uwagi obok nich dawałaby w pasku trzy pomarańczowe plakietki, z których
  żadna nie mówiłaby, która z nich wymaga reakcji. Kontrast tej pary: 6,03 : 1
  w motywie jasnym, 6,38 : 1 w ciemnym.

  CEL DOTYKOWY 44 px (REQ-014) daje `min-height`, a nie sam `padding`: numer
  wersji bywa krótki („1.3.0") i przy samym marginesie wewnętrznym plakietka
  wychodziła na telefonie niższa niż palec.
*/
.plakietka-wersji {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 44px;
  margin-left: 6px;
  padding: 2px 10px;
  border-radius: 999px;
  border: 1px solid var(--glowny);
  background: var(--glowny-jasny);
  color: var(--glowny);
  font-size: 0.82rem;
  font-weight: 700;
  line-height: 1.2;
  cursor: pointer;
}

.plakietka-wersji[hidden] { display: none; }

/*
  GUZIK ZABLOKOWANY NA CZAS AKTUALIZACJI. Kursor „nie wolno" i przygaszenie
  razem, bo samo przygaszenie na plakietce tej wielkości bywa nierozpoznawalne,
  a kursor widzi tylko ten, kto ma mysz.
*/
.plakietka-wersji[disabled] {
  cursor: progress;
  opacity: 0.65;
}

.plakietka-wersji .ikona-aktualizacji { flex: none; }

/*
  OZNACZENIE „czeka na synchronizację" PRZY ELEMENCIE (REQ-003).

  Stoi POD formularzem, którego dotyczy, i jest zdaniem, a nie ikoną: mówi
  wprost, że efekt nie dotarł jeszcze do pozostałych. Barwa jest tą samą
  barwą uwagi co licznik; sam napis wystarczy bez niej.
*/
.znacznik-oczekuje {
  display: block;
  margin: 6px 0 0;
  padding: 6px 10px;
  border-left: 3px solid var(--uwaga);
  background: var(--uwaga-jasny);
  color: var(--uwaga);
  font-size: 0.85rem;
}

/* ---- ekran logowania ---------------------------------------------------- */

.ekran-logowania {
  max-width: 380px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.formularz-logowania {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.formularz-logowania label {
  font-size: 0.85rem;
  color: var(--tekst-slaby);
}

/*
  Pola logowania mają klasę `input` Bulmy (skill `bulma`), więc obwódka, tło,
  ognisko i zachowanie w trybie ciemnym pochodzą stamtąd. Zostaje WYŁĄCZNIE
  to, czego Bulma nie ustala, a czego wymaga REQ-016: cel dotykowy akcji
  głównej. Powtarzanie tu obwódki i tła dawało dwa źródła prawdy o wyglądzie
  pola — a rozjazd widać dopiero w trybie ciemnym.
*/
.formularz-logowania .input { min-height: 48px; }

.akcja-glowna {
  min-height: 56px;
  margin-top: 8px;
  border: 0;
  border-radius: 16px;
  background: var(--glowny);
  color: #FFFFFF;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* Komunikat błędu logowania. Kolor `uwaga` jest tu na miejscu — coś się nie
 * udało. To odróżnia go od wakatu, który jest zaproszeniem i dlatego NIGDY
 * nie dostaje czerwieni (reguła 3). */
.komunikat-bledu {
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--uwaga);
  border-radius: 14px;
  background: var(--uwaga-jasny);
  color: var(--uwaga);
  font-size: 0.9rem;
}

.stopka-wersji {
  margin: 8px 0 0;
  color: var(--tekst-slaby);
  font-size: 0.75rem;
}

/* ---- pulpit ------------------------------------------------------------- */

.kafle {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin: 16px 0;
}

.kafel {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 92px;
  padding: 14px;
  border: 1px solid var(--obwodka);
  border-radius: 16px;
  background: var(--powierzchnia);
}

.kafel-etykieta { font-size: 0.8rem; color: var(--tekst-slaby); }
.kafel-wartosc {
  font-family: 'Bitter', 'Inter', ui-serif, serif;
  font-size: 1.6rem;
  font-weight: 700;
}
.kafel-podpis { font-size: 0.78rem; color: var(--tekst-slaby); }

.os-dnia {
  list-style: none;
  margin: 16px 0;
  padding: 0 0 0 18px;
  border-left: 2px solid var(--obwodka);
}

.os-dnia li {
  display: flex;
  gap: 12px;
  padding: 10px 0;
}

.os-godzina {
  min-width: 64px;
  color: var(--tekst-slaby);
  font-size: 0.8rem;
}

.os-teraz .os-godzina { color: var(--glowny-ciemny); font-weight: 700; }

/* ---- ekran błędu -------------------------------------------------------- */

.ekran-bledu { max-width: 480px; margin: 0 auto; }

/* ---- nawigacja ---------------------------------------------------------- */

/*
  NAWIGACJA STOI NA KOMPONENCIE `navbar` BULMY (skill `bulma`) — cały układ,
  chowanie na wąskim ekranie i burger dostajemy gotowe. Zostaje tu WYŁĄCZNIE
  to, czego Bulma nie ma albo co u nas znaczy co innego.

  Poprzednia wersja miała tu siedemdziesiąt linii własnego paska: przewijaną
  listę z `overflow-x`, własne rozwijane menu na `<details>` i własny stan
  aktywnej pozycji. Menu było obcinane przez `overflow` rodzica i na telefonie
  wyglądało jak „hamburger nie działa" (usterka wydania 0.7.0). Komponent
  z biblioteki ma ten przypadek rozwiązany, bo pozycje nie przewijają się
  w poziomie, tylko układają w pionie.
*/

/*
  WOREK IKON NIE ZAJMUJE MIEJSCA W UKŁADZIE (REQ-001, #157).

  `<svg class="ikony-nawigacji">` w `nawigacja.tmpl` trzyma same `<symbol>`,
  które z definicji niczego nie rysują, ale jako element wpycha się w układ
  przed paskiem nawigacji. Zerujemy go jak ukryty nośnik treści pomocniczej:
  poza strumieniem (`position: absolute`) i bez wymiarów. Atrybutu `style`
  być tu nie może — `style-src 'self'` bez `unsafe-inline` nie wykonałby go —
  więc rozmiar jest sprawą arkusza, nie znacznika.
*/
.ikony-nawigacji {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/*
  POZYCJA AKTYWNA jest u nas WYRAŹNIEJSZA niż domyślna w Bulmie: to jedyna
  informacja o tym, gdzie się stoi, a nawigacja ma kilkanaście pozycji.
  Kolor idzie ze zmiennej Bulmy, więc zmienia się razem z motywem.
*/
.nawigacja .navbar-item.is-active {
  font-weight: 600;
  color: var(--bulma-link-text);
  border-bottom: 2px solid var(--bulma-link);
}

/*
  POZYCJE STAŁE ŁAMIĄ SIĘ DO DRUGIEGO WIERSZA na wąskim ekranie.

  `navbar-brand` Bulmy jest rzędem, który się NIE ZAWIJA — przy sześciu
  pozycjach moderatora treść miała 770 px na ekranie 390 px, czyli strona
  przewijała się w poziomie. Test go-rod złapał to na przebiegu telefonu
  natychmiast po przejściu na komponent Bulmy.

  Zawijanie, a nie przewijanie w poziomie: przewijana lista jest dokładnie
  tym wzorcem, który w wydaniu 0.7.0 obcinał rozwinięte menu (`overflow`
  na rodzicu). Dwa wiersze pozycji są brzydsze i działają.
*/
.nawigacja .navbar-brand {
  flex-wrap: wrap;
  row-gap: 0;
  /*
    ZAWIJANIE MUSI BYĆ DOZWOLONE TAKŻE NA KOMPUTERZE. Bulma daje
    `navbar-brand` twarde `flex-shrink: 0`, więc powyżej 1024 px — gdzie sam
    pasek jest kontenerem giętkim — pozycje nie zawijały się, tylko wychodziły
    poza jego szerokość. Samo `flex-wrap` nic wtedy nie robi: element o
    szerokości treści zawsze mieści swoją treść w jednym wierszu.

    Bez tej pary strona z za dużą liczbą pozycji przewija się w poziomie —
    czyli robi dokładnie to, przed czym REQ-016 ostrzega. Z nią pozycja, która
    się nie zmieściła, ląduje w drugim wierszu i pozostaje klikalna.
  */
  flex-shrink: 1;
  min-width: 0;
}

/*
  BURGER NA KOMPUTERZE TEŻ. Bulma chowa go powyżej 1024 px, zakładając, że
  wszystkie pozycje mieszczą się w pasku — u nas nie mieszczą się WSZYSTKIE,
  bo moderator ma ich kilkanaście. Zamiast wypychać stronę poza ekran, reszta
  zostaje w menu na każdej szerokości.

  „Nie wszystkie" to nie to samo co „tyle samo na każdej szerokości", a przez
  długi czas znaczyło u nas właśnie to: pasek kończył się na tej samej pozycji
  przy 1024 px i przy 2560 px, mimo że zostawało w nim miejsce na kilka
  kolejnych. Ile ich dokładnie wchodzi, rozstrzyga PONIŻSZA drabinka progów,
  a po niej — pomiar realnego układu w `nawigacja.js` (REQ-016).
*/
@media screen and (min-width: 1024px) {
  .nawigacja .navbar-burger { display: block; }
  .nawigacja .navbar-menu { display: none; }
  .nawigacja .navbar-menu.is-active {
    display: block;
    position: absolute;
    right: 0;
    top: 100%;
    z-index: 30;
    min-width: 240px;
    box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
    /*
      MENU PRZEWIJA SIĘ WEWNĄTRZ SIEBIE, a nie wypycha strony.

      Pozycji jest kilkanaście i przy każdej nowej funkcji dochodzi kolejna
      — dołożenie „Nagrań" (REQ-038) zepchnęło „Śpiewnik" poniżej krawędzi
      ekranu 1440×900 i pozycja przestała być klikalna, choć była w drzewie
      dokumentu. Dokładnie ta klasa usterki, o której mówi reguła „element
      widoczny nie znaczy klikalny" — złapał ją test nawigacji.
    */
    max-height: calc(100vh - 72px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /*
    POZYCJE W MENU STOJĄ JEDNA POD DRUGĄ, a nie w rzędzie.

    Powyżej 1024 px Bulma układa `navbar-start` i `navbar-end` poziomo, bo
    zakłada, że to pasek — a u nas to menu rozwijane. Przy kilkunastu
    pozycjach rząd był SZERSZY od menu i część pozycji lądowała poza nim
    (ujemna współrzędna lewej krawędzi): były w drzewie dokumentu, miały
    rozmiar i nie dało się ich kliknąć. Złapał to test nawigacji po
    dołożeniu pozycji „Nagrania" (REQ-038).
  */
  .nawigacja .navbar-menu.is-active .navbar-start,
  .nawigacja .navbar-menu.is-active .navbar-end {
    display: block;
    margin: 0;
  }
  /*
    NA KOMPUTERZE MENU ZOSTAJE LISTĄ, NIE SIATKĄ (#157). Siatka powyżej
    jest rzeczą PANELU, który otwiera „Więcej" na telefonie; tutaj ten sam
    węzeł jest rozwijanym menu burgera z przewijaniem w środku (`max-height`
    wyżej) — wąska kolumna nie nadaje się na siatkę z podpisami obok ikon,
    a lista ma tu pilnować tego, co pilnowała dotąd. Ikona jest ukryta,
    bo sens niesie podpis (reguła 2 makiet), a w listę wraca dokładnie ten
    wygląd, który menu miało przed #157.
  */
  .nawigacja .navbar-menu.is-active .navbar-start .navbar-item {
    flex-direction: row;
    justify-content: flex-start;
    gap: 0.75rem;
    min-height: 0;
    padding: 0.375rem 1rem;
    text-align: left;
  }
  .nawigacja .navbar-menu.is-active .navbar-start .ikona-pozycji-menu {
    display: none;
  }
  .nawigacja { position: relative; }
}

/*
  ILE POZYCJI STOI W PASKU — DWIE WARSTWY (REQ-016)
  ─────────────────────────────────────────────────

  Serwer wypisuje kilka pierwszych pozycji menu DWA RAZY: w `navbar-brand`
  (klasa `nadmiar`, domyślnie ukryte) i w `navbar-menu`. Zadaniem tych reguł
  jest pokazać ZAWSZE DOKŁADNIE JEDNĄ z dwóch kopii.

  Warstwa 1 — PROGI SZEROKOŚCI. Stan wyjściowy, ustawiony zanim wykona się
  jakikolwiek skrypt, i jedyny, gdy skryptu nie ma. Progi są zgadywane
  i wolno im się mylić w jedną stronę: `navbar-brand` ma wyżej `flex-wrap`,
  więc pozycja, która się nie zmieściła, zawija się do drugiego wiersza,
  a nie wypycha strony poza ekran.

  Warstwa 2 — POMIAR. `nawigacja.js` dopisuje na nawigacji klasę `mierzona`
  (czyli „skrypt przejął sterowanie", co wyłącza progi przez `:not`)
  i oznacza klasą `w-pasku` te pozycje, które naprawdę się zmieściły. Pomiar
  jest rozstrzygający, bo progi nie widzą powiększonej czcionki (REQ-014):
  skalowanie do 200% zmienia szerokość pozycji, a nie szerokość okna.
*/

/*
  Kopia w pasku jest domyślnie ukryta — na telefonie zostaje ukryta na
  zawsze, bo tam nie mieści się nic ponad pozycje stałe. `display: none`,
  a nie przezroczystość: ukryta kopia ma zniknąć również z drzewa dostępności,
  inaczej czytnik ekranu wymieniałby każdą taką pozycję dwa razy.
*/
.nawigacja .navbar-brand .nadmiar { display: none; }

@media screen and (min-width: 1024px) {
  .nawigacja:not(.mierzona) .navbar-brand .nadmiar-1,
  .nawigacja:not(.mierzona) .navbar-brand .nadmiar-2 { display: flex; }
  .nawigacja:not(.mierzona) .navbar-menu .nadmiar-1,
  .nawigacja:not(.mierzona) .navbar-menu .nadmiar-2 { display: none; }
}
@media screen and (min-width: 1216px) {
  .nawigacja:not(.mierzona) .navbar-brand .nadmiar-3 { display: flex; }
  .nawigacja:not(.mierzona) .navbar-menu .nadmiar-3 { display: none; }
}
@media screen and (min-width: 1408px) {
  .nawigacja:not(.mierzona) .navbar-brand .nadmiar-4 { display: flex; }
  .nawigacja:not(.mierzona) .navbar-menu .nadmiar-4 { display: none; }
}
@media screen and (min-width: 1600px) {
  .nawigacja:not(.mierzona) .navbar-brand .nadmiar-5 { display: flex; }
  .nawigacja:not(.mierzona) .navbar-menu .nadmiar-5 { display: none; }
}
@media screen and (min-width: 1800px) {
  .nawigacja:not(.mierzona) .navbar-brand .nadmiar-6 { display: flex; }
  .nawigacja:not(.mierzona) .navbar-menu .nadmiar-6 { display: none; }
}
@media screen and (min-width: 2000px) {
  .nawigacja:not(.mierzona) .navbar-brand .nadmiar-7 { display: flex; }
  .nawigacja:not(.mierzona) .navbar-menu .nadmiar-7 { display: none; }
}
@media screen and (min-width: 2200px) {
  .nawigacja:not(.mierzona) .navbar-brand .nadmiar-8 { display: flex; }
  .nawigacja:not(.mierzona) .navbar-menu .nadmiar-8 { display: none; }
}

/*
  Warstwa 2 wygrywa specyficznością (dwie klasy na `.nawigacja`), a nie
  kolejnością — reguły progów siedzą w zapytaniach medialnych, które
  specyficzności nie podnoszą, więc porządek pliku nic by tu nie rozstrzygnął.
*/
.nawigacja.mierzona .navbar-brand .nadmiar.w-pasku { display: flex; }
.nawigacja.mierzona .navbar-menu .nadmiar.w-pasku { display: none; }

/*
  PRZYCISK MENU ZNIKA DOPIERO PRZY PUSTYM MENU (REQ-016). Przycisk otwierający
  pustą listę uczy ignorowania przycisku — a to ten sam przycisk, który na
  telefonie jest jedyną drogą do połowy ekranów. Klasę nadaje skrypt, bo tylko
  on umie policzyć widoczne pozycje; bez skryptu menu nie może opustoszeć,
  o co dba `zKolejkaDoPaska` w `internal/web/nawigacja.go`.
*/
.nawigacja.mierzona .navbar-burger.menu-puste { display: none; }

/* ==========================================================================
   GÓRNA NAWIGACJA ZNIKA NA TELEFONIE (REQ-016, zgłoszenie #151,
   rozbieżność 55)

   Od #100 dolny pasek niesie na telefonie te same cztery pozycje, co
   `navbar-brand` górnej nawigacji, a jego pozycja „Więcej" prowadzi do tego
   samego menu, co burger. Dwie warstwy nawigacji jedna nad drugą zabierały
   więc na 390 px dwa wiersze ekranu i dawały dwa miejsca na to samo.

   CO DOKŁADNIE ZNIKA, A CO ZOSTAJE — i to jest tu cała trudność:

     • ZNIKA `navbar-brand` NAWIGACJI: pozycje stałe, ukryte kopie pozycji
       nadmiaru i burger. To jest ten duplikat.
     • ZOSTAJE `#menu-nawigacji`, czyli PEŁNE MENU. Nie wolno schować całego
       elementu `nav`: rodzic z `display: none` zabiera prostokąt także
       dziecku, więc `#menu-nawigacji:target` przestałby cokolwiek pokazywać
       — a to jest jedyna droga do ośmiu ekranów na telefonie i jedyna, która
       działa bez JavaScriptu.
     • ZOSTAJE CAŁY PASEK APLIKACJI (`.pasek`): znak firmowy, wskaźnik sieci
       (REQ-002), licznik akcji oczekujących (REQ-003) i przełącznik motywu
       (REQ-004). To pasek STANU I TOŻSAMOŚCI, nie nawigacji — usunięcie go
       zabrałoby trzy nośniki stanu naraz.

   ROBI TO ZAPYTANIE MEDIALNE, NIE SKRYPT (REQ-001, REQ-016): awaria
   `nawigacja.js` nie ma zmieniać UKŁADU, a `unsafe-inline` jest wykluczone.
   Zapytanie medialne nie usuwa węzłów z dokumentu — usuwa PROSTOKĄT i wypis
   w drzewie dostępności, więc kryterium „na telefonie górnej nawigacji nie
   ma" jest sprawdzalne POMIAREM, a nie pytaniem o drzewo.

   PRÓG 768 px — ten sam, na którym włącza się dolny pasek i na którym Bulma
   zwija swój `navbar`. Dwie różne liczby dałyby szerokość, na której obu
   nawigacji nie ma wcale albo są obie.
   ========================================================================== */

/*
  Tytuł ekranu i odnośnik zamykający menu są DOMYŚLNIE NIEOBECNE i pokazują
  się dopiero poniżej progu telefonu. Kolejność jest taka, a nie odwrotna, bo
  stan bez arkusza ma być stanem KOMPUTERA: niewczytany `app.css` daje wtedy
  stronę z nawigacją i bez dwóch elementów, które mają sens tylko wtedy, gdy
  tej nawigacji nie widać.
*/
.tytul-ekranu { display: none; }
.zamknij-pelne-menu { display: none; }

@media screen and (max-width: 768px) {
  /* TO JEST CAŁE USUNIĘCIE GÓRNEJ NAWIGACJI — jedna reguła na `navbar-brand`,
     czyli na rzędzie pozycji i burgerze. Element `nav` zostaje wyświetlany,
     żeby `#menu-nawigacji:target` miał gdzie się rozwinąć (patrz wyżej). */
  .nawigacja .navbar-brand { display: none; }
  /*
    `navbar` Bulmy ma `min-height: 3.25rem`, więc sam element `nav` bez
    zawartości zostawiałby na telefonie 52 px pustego pasa pod paskiem
    aplikacji — czyli tyle samo miejsca, ile zabierała nawigacja, tylko bez
    ani jednej pozycji. Zerujemy też obwódkę i tło, żeby zwinięta nawigacja
    nie rysowała kreski pod paskiem.
  */
  .nawigacja {
    min-height: 0;
    background: none;
    border: 0;
  }

  /*
    TYTUŁ BIEŻĄCEGO EKRANU (REQ-016, #151) — zamiennik podświetlonej pozycji.

    SKRACA SIĘ, A NIE ZAWIJA PASKA, i to jest warunek zgłoszenia. Tytuł jest
    JEDYNYM giętkim elementem paska: `navbar-brand` Bulmy ma `flex-shrink: 0`,
    a `navbar-end` dostaje je niżej — więc cały brak miejsca odbiera się
    tytułowi, nigdy wskaźnikowi sieci ani licznikowi oczekujących. `min-width:
    0` jest tu konieczne, a nie ozdobne: bez niego element giętki nie schodzi
    poniżej szerokości swojej treści i „Subskrypcja kalendarza" wypchnęłaby
    kontrolki paska za krawędź ekranu.

    `white-space: nowrap` RAZEM z `text-overflow`: bez zakazu zawijania długi
    tytuł złamałby się na dwie linie i to on rozepchnąłby pasek w pionie,
    zamiast zostać uciętym.
  */
  .tytul-ekranu {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-weight: 600;
    font-size: 0.95rem;
    color: var(--tekst);
  }
  /*
    KONTROLKI PASKA NIE KURCZĄ SIĘ NIGDY. Domyślne `flex-shrink: 1` pozwoliłoby
    ścisnąć wskaźnik sieci i licznik oczekujących na rzecz tytułu — czyli
    zamienić informację o stanie na napis, który i tak wolno uciąć.
  */
  .pasek .navbar-end { flex: 0 0 auto; }

  /*
    ZAMKNIĘCIE PEŁNEGO MENU (patrz `nawigacja.tmpl`). Burger, który tę robotę
    wykonuje na komputerze, nie ma tu prostokąta — bez tego odnośnika menu
    otwarte kotwicą „Więcej" nie miałoby czym się zamknąć.

    Cel dotykowy 44 px (REQ-014, REQ-016) i podpis SŁOWEM, a nie krzyżykiem
    (reguła 2 makiet: nigdy sam obrazek).
  */
  .zamknij-pelne-menu {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    min-height: 44px;
    padding: 0 1rem;
    color: var(--bulma-link-text);
    font-weight: 600;
    text-decoration: none;
  }
}

/* ==========================================================================
   DOLNY PASEK NAWIGACJI — TYLKO NA TELEFONIE (REQ-016, zgłoszenie #100)

   TRZY RZECZY ROZSTRZYGA TU ARKUSZ, A NIE SZABLON I NIE SKRYPT:

   1. CZY PASEK JEST. Domyślnie NIE MA GO WCALE — `display: none` stoi poza
      zapytaniem medialnym, a pasek włącza się dopiero poniżej progu telefonu.
      Kolejność jest taka, a nie odwrotna („widoczny, chowany na komputerze"),
      bo stan bez arkusza ma być stanem BEZPIECZNYM: niewczytany `app.css`
      daje wtedy stronę bez zdublowanej nawigacji, a nie pasek telefonu
      rozciągnięty na 1440 px.

      NIE ROBI TEGO SKRYPT (REQ-016, punkt 1 zgłoszenia): awaria
      `nawigacja.js` ma nie zmieniać UKŁADU, a `unsafe-inline` jest
      wykluczone (REQ-001). Zapytanie medialne nie usuwa węzłów z dokumentu
      — usuwa PROSTOKĄT i wypis w drzewie dostępności, więc kryterium „na
      komputerze paska nie ma" jest sprawdzalne POMIAREM, a nie pytaniem
      o drzewo (rozbieżność 54).

   2. ŻE PASEK NIE ZASŁANIA TREŚCI. Pasek jest OSTATNIM elementem strony
      i zostaje W UKŁADZIE — `position: sticky`, nie `fixed`. Zajmuje więc
      swoją wysokość na końcu strony i to jest cały zapas: nie ma osobnego
      `padding-bottom`, który trzeba by trzymać w zgodzie z wysokością paska
      i który rozjeżdżałby się przy każdej zmianie kroju (REQ-014 skaluje
      podpisy, więc wysokość paska NIE JEST stała).

      Ta sama reguła obsługuje klawiaturę ekranową: element w układzie nie ma
      czego zasłonić, a `interactive-widget=resizes-content` w `baza.tmpl`
      sprawia, że klawiatura zmniejsza obszar układu i pasek zostaje nad nią.

      Powód, dla którego to jest tu wypisane tak długo: element pod
      przyklejonym paskiem jest w drzewie dokumentu, ma rozmiar i NIE DA SIĘ
      go trafić palcem. Na tym padło CI przy zgłoszeniu #16 i tego pilnuje
      pomocnik `DaSieKliknac` w testach przeglądarkowych.

   3. ILE JEST KOLUMN. `grid-auto-flow: column` bez wyliczonej liczby kolumn,
      więc pasek układa się na tyle równych części, ile pozycji przyszło
      z serwera I MA W TEJ SZEROKOŚCI PROSTOKĄT. Sztywne `repeat(5, 1fr)`
      zostawiłoby PUSTĄ KOLUMNĘ na instalacji z wyłączonym modułem kalendarza
      — a REQ-016 mówi, że pozycja niewidoczna nie zostawia po sobie ani
      przerwy, ani wyszarzonego wiersza. `display: none` wyjmuje pozycję
      z siatki razem z jej kolumną, więc kolumny są równe zawsze.

   4. ILE POZYCJI SIĘ MIEŚCI — progi niżej. Wzorzec to „4 pozycje + »Więcej«",
      a piąta i szósta wchodzą wtedy, gdy jest na nie miejsce
      (rozstrzygnięcie właściciela produktu, rozbieżność 54).
   ========================================================================== */

.dolny-pasek { display: none; }

/* Rysunki stoją raz, jako `<symbol>` — patrz komentarz w `dolny-pasek.tmpl`.
   Element `<svg>` sam nic nie rysuje, ale bez tego zajmowałby miejsce
   w układzie paska jako pierwsza „kolumna". Atrybut `style` na znaczniku jest
   wykluczony przez `style-src 'self'`. */
.ikony-dolnego-paska { width: 0; height: 0; position: absolute; }

@media screen and (max-width: 768px) {
  /*
    PRÓG 768 px, czyli granica `mobile` w Bulmie (skill `bulma`). Ta sama
    liczba, na której Bulma zwija swój `navbar` — dwa różne progi dla dwóch
    warstw tej samej nawigacji dawałyby szerokość, na której pasek dolny już
    jest, a górny jeszcze się nie zwinął.
  */

  /*
    STRONA JEST KOLUMNĄ NA CAŁĄ WYSOKOŚĆ EKRANU — tak samo jak na makietach
    telefonowych, które składają ekran z trzech części: pasek u góry, treść
    rosnąca i pasek u dołu (`flex-shrink: 0`).

    Bez tego pasek przyklejony przez `sticky` stawałby na KRÓTKIEJ stronie tam,
    gdzie kończy się treść — czyli w połowie ekranu, z pustym miejscem pod
    sobą. `sticky` przykleja element wtedy, gdy jego miejsce w układzie
    wypadłoby poniżej krawędzi; na krótkiej stronie nie wypada.

    `100dvh`, a nie `100vh`: na telefonie pasek adresu przeglądarki zwija się
    przy przewijaniu i `100vh` jest wtedy o kilkadziesiąt pikseli za wysokie,
    czyli strona dostaje pasek przewijania w pionie bez treści, która by go
    uzasadniała.

    ZAPYTANIE MEDIALNE ZAMYKA TO NA TELEFONIE. Zamiana `body` w kolumnę
    giętką na komputerze byłaby zmianą układu wszystkich ekranów naraz przy
    okazji dokładania paska, którego tam nie ma.
  */
  body {
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
  }

  /*
    PRZEWIJANIE „DO ELEMENTU" ZATRZYMUJE SIĘ NAD PASKIEM, a nie pod nim.
    ─────────────────────────────────────────────────────────────────────
    Pasek jest przyklejony, więc DOPÓKI TRWA PRZEWIJANIE treść przesuwa się
    POD nim — swoje miejsce w układzie zajmuje dopiero na samym końcu strony.
    Przeglądarka, proszona o pokazanie elementu („przewiń do", skupienie pola
    formularza klawiaturą, wejście pod adres z kotwicą), stawia go przy
    NAJBLIŻSZEJ krawędzi — czyli u dołu, dokładnie za paskiem. Element ma
    wtedy rozmiar, jest w drzewie dokumentu i nie da się go trafić palcem.

    TO NIE JEST TEORIA: bez tej reguły `TestLogowanieIWylogowanie/telefon`
    WISIAŁ do końca czasu testu. `MustClick` z rod-a przewija do elementu
    i ponawia kliknięcie, dopóki w punkcie środka leży coś innego — guzik
    „Wyloguj się" ląduje po takim przewinięciu za paskiem, więc pętla nie
    kończyła się nigdy. To ta sama klasa usterki co przy zgłoszeniu #16,
    tylko widziana od strony przewijania, a nie od strony układu.

    `scroll-padding-bottom` na kontenerze przewijania mówi przeglądarce:
    „przy pokazywaniu elementu traktuj dolne N pikseli jak zajęte".
    W `rem`, bo pasek rośnie razem ze stopniem pisma (REQ-014) — 5rem to
    80 px przy 100% i 160 px przy 200%, czyli z zapasem nad wysokość paska
    w obu skrajach drabiny.
  */
  html { scroll-padding-bottom: 5rem; }
  /* Treść zabiera całą wolną wysokość, więc stopka i pasek idą na dół.
     `1 0 auto`, a nie `1`: `flex-shrink: 1` pozwoliłby ścisnąć długą treść
     poniżej jej wysokości, zamiast dać stronie się przewijać. */
  body > #tresc { flex: 1 0 auto; }

  .dolny-pasek {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    position: sticky;
    bottom: 0;
    /*
      NAD TREŚCIĄ, ALE POD PEŁNYM EKRANEM PIEŚNI. `.pelny-ekran` ma 40
      i przykrywa stronę w całości (REQ-044) — pasek nawigacji nie ma prawa
      przez niego przebić, bo prowadzący trzyma wtedy telefon jako pulpit.
    */
    z-index: 30;
    padding: 6px 4px;
    /*
      ZAPAS NA PASEK SYSTEMOWY TELEFONU (kreska „home" na iPhonie). Bez tego
      podpisy ostatniego wiersza wchodzą pod niego i palec trafia w system,
      a nie w pozycję. `env()` daje 0 tam, gdzie takiego paska nie ma.
    */
    padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
    background: var(--powierzchnia);
    border-top: 1px solid var(--obwodka);
  }

  .dolny-pasek-pozycja {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    /*
      CEL DOTYKOWY: 3,5rem wysokości (= 56 px z makiet telefonowych) przy
      pięciu kolumnach na 390 px, czyli 78 px szerokości — obie liczby powyżej
      progu 44 px z REQ-016.

      W `rem`, NIE W PIKSELACH: drabina stopnia pisma (REQ-014) stoi na
      elemencie głównym, więc pasek rośnie razem z podpisami i wysokość
      `scroll-padding-bottom` wyżej trzyma z nim krok. Ta sama zasada, co
      w trybie pełnoekranowym pieśni. `min-height`, a nie `height`: przy 200%
      podpis łamie się w dwie linie i pozycja ROŚNIE, zamiast uciąć napis.
      Skrótu etykiety nie ma — „Moje grupy" to reguła, nie napis
      (rozbieżność 15).
    */
    min-height: 3.5rem;
    padding: 4px 2px;
    color: var(--tekst-slaby);
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1.15;
    text-align: center;
    text-decoration: none;
  }

  /*
    POZYCJA OTWARTEJ STRONY. Kolor i grubość, a nie sama kreska pod spodem:
    kreska pod pozycją stojącą przy dolnej krawędzi ekranu wchodzi w obwódkę
    paska i nie widać jej wcale. Znak `aria-current="page"` niesie tę samą
    informację dla czytnika ekranu i stawia go szablon.
  */
  .dolny-pasek-pozycja.is-active {
    color: var(--bulma-link-text);
    font-weight: 700;
  }

  /* Rysunek nie kurczy się razem z podpisem łamiącym się w dwie linie. */
  .dolny-pasek-ikona { flex: 0 0 auto; }

  /*
    PODPIS ŁAMIE SIĘ, A NIE ZNIKA. `overflow-wrap`, bo „Moje grupy" na
    najwęższym obiecanym ekranie (390 px / 5 kolumn = 78 px) nie mieści się
    w jednym wierszu i ma się złamać między słowami.
  */
  .dolny-pasek-podpis { overflow-wrap: anywhere; }

  /* ====================================================================
     „ILE SIĘ ZMIEŚCI" — PROGI SZEROKOŚCI, NIE POMIAR W SKRYPCIE.
     ====================================================================
     Wzorzec z rozstrzygnięcia właściciela produktu (rozbieżność 54) brzmi
     „4 pozycje + »Więcej« (ew. więcej niż 4, jeśli się zmieści)". Serwer
     wypisuje kolejkę ważności PO ODSIEWIE UPRAWNIEŃ i nadaje jej rangi
     (`dolny-pasek-ranga-N`); tutaj rozstrzyga się, które rangi mają
     prostokąt.

     DLACZEGO PROGI, A NIE POMIAR — trzy powody, każdy sam by wystarczył:

       1. Polityka bezpieczeństwa nie dopuszcza `unsafe-inline` (REQ-001),
          a awaria `nawigacja.js` nie ma zmieniać UKŁADU. Pasek, który bez
          skryptu ma inną liczbę pozycji, łamie tę samą zasadę, dla której
          całego paska nie chowa się skryptem.
       2. Pomiar rozstrzyga PO WCZYTANIU, więc pasek zmieniałby liczbę
          pozycji na oczach użytkownika — a to jest dolny pasek, w który
          celuje się palcem w chwili, gdy się pojawia.
       3. Test byłby niedeterministyczny: zależny od tego, czy pomiar zdążył
          przed pytaniem. Progi są sprawdzalne w obu układach testowych
          i nie migają.

     GÓRNY PASEK MIERZY SIĘ SKRYPTEM I TO NIE JEST NIEKONSEKWENCJA: jego
     pozycje to same napisy o RÓŻNEJ szerokości („Rodziny — zarządzanie"
     obok „Osoby"), więc z szerokości okna nie wynika, ile ich wejdzie.
     Dolny pasek ma kolumny RÓWNE (`1fr`), więc liczba, która się mieści,
     wynika z samej szerokości okna i z minimalnego celu dotykowego.

     SKĄD TE LICZBY. Cel dotykowy ma mieć co najmniej 44 px (REQ-016),
     a podpis dwuwyrazowy („Moje grupy") potrzebuje w praktyce ~64 px, żeby
     złamać się na dwie linie, a nie na trzy. Stąd:

       • do 429 px  → 5 kolumn: 4 pozycje + „Więcej".
         390 px (układ testowy telefonu) / 5 = 78 px; najwęższy obsługiwany
         ekran 320 px / 5 = 64 px, wciąż powyżej 44 px.
       • od 430 px  → 6 kolumn: 5 pozycji + „Więcej".
         430 px to szerokość najszerszego typowego telefonu w pionie
         (iPhone 14 Pro Max); 430 / 6 = 71 px. Próg jest DOPIERO tutaj,
         a nie przy 390 px, bo 390 / 6 = 65 px zostawiałoby „Moje grupy"
         bez zapasu na skalowanie kroju (REQ-014 dopuszcza 200%).
       • od 600 px  → 7 kolumn: 6 pozycji + „Więcej".
         600 px to szerokość, od której zaczynają się tablety w pionie
         i telefony w poziomie; 600 / 7 = 85 px. Górną granicą paska jest
         768 px (granica `mobile` Bulmy), więc siódmej pozycji nie ma gdzie
         postawić — i serwer jej nie wypisuje (`MaksPozycjiDolnegoPaska`).

     „Więcej" nie ma rangi i dlatego nie ma go w żadnej z tych reguł:
     jest pozycją STAŁĄ i widać ją na każdej szerokości.
  */
  .dolny-pasek-ranga-5,
  .dolny-pasek-ranga-6 { display: none; }

  /*
    MENU BOCZNE ROZWIJA SIĘ BEZ SKRYPTU, gdy „Więcej" wskaże je kotwicą.
    Pseudoklasa `:target` jest jedynym sposobem, w jaki kliknięcie może
    zmienić wygląd bez JavaScriptu — a „Więcej", które bez `nawigacja.js`
    nic nie robi, byłoby ślepą pozycją paska (REQ-016: „nawigacja ma
    działać także wtedy, gdy skrypt się nie wczyta").

    Burger Bulmy działa jak dotąd, przez `.is-active` dopisywane skryptem;
    to druga droga do tej samej listy. Selektor ma większą wagę
    (identyfikator) niż `.navbar-menu` Bulmy, więc wygrywa bez `!important`.
  */
  #menu-nawigacji:target { display: block; }

  /*
    BURGER MUSI UMIEĆ ZAMKNĄĆ MENU OTWARTE KOTWICĄ. Adres z `#menu-nawigacji`
    zostaje w pasku przeglądarki po kliknięciu „Więcej", więc `:target` pasuje
    dalej — bez tej reguły burger wyglądałby na zepsuty. Klasę dopisuje
    `nawigacja.js`; selektor ma tę samą wagę co reguła wyżej i stoi PO niej,
    więc wygrywa kolejnością, a nie `!important`.
  */
  #menu-nawigacji.zwiniete { display: none; }
}

/* ==========================================================================
   PANEL „WIĘCEJ" — POZYCJE MENU JAKO SIATKA Z IKONAMI (REQ-016, #157)

   `#menu-nawigacji .navbar-start` — to, co otwiera „Więcej" dolnego paska
   (i burger) — nie jest już listą pozycji jedna pod drugą, tylko SIATKĄ
   komórek z ikoną NAD podpisem. Ta sama para, którą niesie dolny pasek,
   i ta sama reguła 2 makiet: nigdy sama ikona.

   DLACZEGO SIATKA, A NIE DRUGA LISTA: pozycji jest kilkanaście i ma być ich
   więcej; komórki mają równe szerokości (`1fr`) i łamią się do kolejnych
   wierszy CAŁYMI RZĘDAMI. `flex-wrap` do tego samego dociskałby komórki do
   lewej krawędzi i zostawiał w ostatnim wierszu puste miejsce.

   DWIE KOLUMNY, A NIE TRZY, z tego samego powodu, dla którego dolny pasek ma
   przy 390 px pięć: 390 / 2 = 195 px na komórkę zostawia „Najbliższe
   wydarzenia" w jednym wierszu na najwęższym obsługiwanym ekranie, a trzy
   kolumny (130 px) łamałyby większość dwuwyrazowych podpisów — czyli zmuszały
   do skrótów (rozbieżność 15) albo do czytania w dwóch liniach każdej drugiej
   pozycji.

   WYSOKOŚĆ W `rem`, NIE W PIKSELACH — drabina stopnia pisma (REQ-014) stoi na
   elemencie głównym i komórka rośnie wraz z podpisem; `min-height`, a nie
   `height`, bo dwuwyrazowy podpis na 200% lamie się i komórka ma urosnąć,
   a nie uciąć napis. Próg `overflow-wrap` na podpisie trzyma ten sam obietnicę
   co `.dolny-pasek-podpis`: napis łamie się między słowami, zamiast wyjechać
   poza komórkę.
   ========================================================================== */

.nawigacja .navbar-menu .navbar-start {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.nawigacja .navbar-menu .navbar-start .navbar-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 4px;
  /* cel dotykowy ≥ 44 px z REQ-016; dolny pasek bierze 3,5rem, a tu jest
     więcej miejsca, więc progu pilnuje się z zapasem, nie na styk */
  min-height: 3.5rem;
  padding: 8px 4px;
  text-align: center;
}

/* Rysunek nie kurczy się razem z podpisem łamiącym się w dwie linie. */
.nawigacja .navbar-menu .navbar-start .ikona-pozycji-menu { flex: 0 0 auto; }

.nawigacja .navbar-menu .navbar-start .podpis-pozycji-menu {
  overflow-wrap: anywhere;
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.2;
}

/*
  PIĄTA POZYCJA — od 430 px (patrz uzasadnienie progów wyżej). Zapytanie jest
  OSOBNE, a nie zagnieżdżone w tym powyżej: zagnieżdżanie `@media` wymaga
  zapisu z CSS Nesting, a ten arkusz jedzie bez narzędzi budujących.
*/
@media screen and (min-width: 430px) and (max-width: 768px) {
  .dolny-pasek-ranga-5 { display: flex; }
}

/* SZÓSTA POZYCJA — od 600 px. */
@media screen and (min-width: 600px) and (max-width: 768px) {
  .dolny-pasek-ranga-6 { display: flex; }
}

/* ---- ekrany „kto jest kim" ---------------------------------------------- */

/*
  `.ekran` zostaje SAMĄ NAZWĄ: szerokość i wyśrodkowanie daje `container`
  Bulmy w układzie strony. Dwa miejsca ustawiające tę samą szerokość
  rozjeżdżają się przy pierwszej zmianie.
*/
.ekran { }

.szukajka {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 12px 0;
}

.szukajka label {
  /*
    Etykieta jest dla czytnika ekranu, ale NIE ZNIKA z układu przez
    `display: none` — element ukryty w ten sposób jest pomijany także przez
    czytnik, czyli traci się dokładnie to, po co go dodano.
  */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.szukajka input { flex: 1 1 240px; }

.tabela { width: 100%; border-collapse: collapse; }
.tabela th, .tabela td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--obwodka); }
.tabela th { font-size: 0.8rem; color: var(--tekst-slaby); font-weight: 600; }
.tabela td.obecnosc { color: var(--tekst-slaby); font-size: 0.85rem; }

.osoba { display: inline-flex; gap: 8px; align-items: center; text-decoration: none; color: inherit; }
.osoba:hover span:last-child { text-decoration: underline; }

.awatar-mini,
.awatar-duzy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  font-weight: 700;
  flex: none;
}

.awatar-mini { width: 28px; height: 28px; font-size: 0.75rem; }
.awatar-duzy { width: 56px; height: 56px; font-size: 1.1rem; }

/*
  KÓŁKO KONTA BEZ ŻYCIA (REQ-051, REQ-059). Puste — ani zdjęcia, ani
  inicjałów — i WYCISZONE, żeby pozycja anonimowa nie przyciągała wzroku
  mocniej niż osoby, które są. Kółko ZOSTAJE na swoim miejscu: gdyby znikało,
  po samym układzie listy dałoby się policzyć, które pozycje są anonimowe.
*/
.awatar-mini.awatar-nieznany,
.awatar-duzy.awatar-nieznany {
  background: var(--tlo);
  border: 1px dashed var(--obwodka);
}

/*
  ZDJĘCIA W MIEJSCU AWATARÓW (REQ-065). Te same wymiary co `awatar-mini`
  i `awatar-duzy`, więc twarz wypełnia kółko jak inicjał — miniatura siedzi
  już w bazie (96 px), pełne zdjęcie leży warstwę niżej.
*/
.awatar-mini-zdjecie,
.awatar-duzy-zdjecie {
  object-fit: cover;
  border-radius: 999px;
  display: block;
  flex: none;
  border: 1px solid var(--glowny-ciemny);
}
.awatar-mini-zdjecie { width: 28px; height: 28px; }
.awatar-duzy-zdjecie { width: 56px; height: 56px; }
.awatar-pelne { max-width: 100%; height: auto; border-radius: 12px; }

.naglowek-osoby { display: flex; gap: 14px; align-items: center; margin-bottom: 16px; }
.naglowek-osoby h1 { margin: 0; }

.dane { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; margin: 0 0 20px; }
.dane dt { color: var(--tekst-slaby); font-size: 0.85rem; }
.dane dd { margin: 0; }

/*
  LISTA OPISOWA „ETYKIETA — WARTOŚĆ" NA GÓRZE DRABINY STOPNIA PISMA
  (REQ-014, zgłoszenie #145).

  DWIE KOLUMNY NIE MIESZCZĄ SIĘ NA TELEFONIE PRZY 150% I WYŻEJ — to jest
  liczba, nie przypuszczenie. Ekran 390 px daje treści 358 px. Kolumna
  etykiet jest `max-content`, więc bierze szerokość najdłuższej etykiety
  („Role we wspólnocie"), a ta rośnie razem z drabiną:

      100%  147 px etykiet + 193 px wartości  → `.dane` 358 / 358  mieści się
      130%  191 px + 149 px                   → 358 / 358  mieści się
      150%  220 px + 120 px                   → 368 / 358  PRZEWIJA
      175%  257 px +  83 px                   → 426 / 358  PRZEWIJA
      200%  294 px +  46 px                   → 484 / 358, dokument 501 / 390

  Wartość obok jest wtedy węższa od SWOJEGO NAJDŁUŻSZEGO SŁOWA („Moderator"
  to przy 200% 173 px), więc nie ma czego zawinąć — i dlatego samo
  `minmax(0, …)` na obu ścieżkach tego NIE NAPRAWIA. Sprawdzone pomiarem:
  po zamianie kolumn na `minmax(0, max-content) minmax(0, 1fr)` siatka nadal
  wychodziła 484 px, bo `1fr` oddaje kolumnie etykiet całą resztę miejsca,
  a nie odwrotnie. Jedyne, co mieści treść bez przewijania w poziomie, to
  ustawienie etykiety NAD wartością.

  ROZSTRZYGA STOPIEŃ PISMA, A NIE SAMA SZEROKOŚĆ, bo przy 100% dwie kolumny
  na telefonie mieszczą się z zapasem i zabranie ich byłoby zmianą wyglądu
  bez powodu. Drabina jest skończona (REQ-014: „każdy stopień musi mieć własną
  regułę w arkuszu" przy zakazie `unsafe-inline` z REQ-039), więc stopnie
  wypisuje się tu tak samo jak przy `--skala-tekstu` na początku pliku.
  Progu 150% nie ma jak wyrazić zapytaniem medialnym: `em` w `@media` liczy
  się od rozmiaru POCZĄTKOWEGO, a nie od `font-size` ustawionego na `html`.

  PRÓG SZEROKOŚCI 768 px — ten sam, na którym znika górna nawigacja i włącza
  się dolny pasek. Na komputerze reguły nie ma i być nie ma: tam kontener
  `is-max-desktop` daje ~960 px, czyli miejsce na obie kolumny nawet przy 200%.
*/
@media screen and (max-width: 768px) {
  :root[data-skala="150"] .dane,
  :root[data-skala="175"] .dane,
  :root[data-skala="200"] .dane {
    grid-template-columns: minmax(0, 1fr);
    gap: 0 0;
  }
  /*
    Etykieta i wartość stają się PARĄ W PIONIE, więc odstęp musi rozdzielać
    PARY, a nie wszystkie wiersze po równo: `gap` liczony jednakowo odsuwałby
    wartość od swojej etykiety dokładnie tak samo, jak od następnej — i lista
    przestałaby mówić, co do czego należy.
  */
  :root[data-skala="150"] .dane dd,
  :root[data-skala="175"] .dane dd,
  :root[data-skala="200"] .dane dd {
    margin: 0 0 10px;
  }
}

.sklad, .karty, .notatki, .lista-przynaleznosci { list-style: none; padding: 0; margin: 0 0 20px; }
.sklad li, .karty li, .notatki li, .lista-przynaleznosci li {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: baseline;
  padding: 8px 0;
  border-bottom: 1px solid var(--obwodka);
}

/*
  „Moja" grupa albo rodzina jest wyróżniona PASKIEM z lewej, a nie samym
  pogrubieniem — wyróżnienie ma być widoczne w skanowaniu listy wzrokiem,
  a nie po przeczytaniu każdej pozycji.
*/
.sklad li.moja, .karty li.moja {
  border-left: 3px solid var(--glowny);
  padding-left: 10px;
}

/*
  PARA BARW JEST NIEROZDZIELNA — tło i tekst z jednego motywu (issue #110).

  Komunikat stanu pustego na `/nagrania` miał w motywie ciemnym kontrast
  1,72 : 1 przy progu WCAG AA 4,5 : 1, czyli był nieczytelny; w jasnym
  4,44 : 1, czyli też poniżej progu, tylko mniej dramatycznie. Klasy w
  szablonie były przy tym poprawne — zepsuta była PARA BARW.

  Skąd się bierze. Bulma dla modyfikatora `is-light` ustawia jasne tło
  i dobrany do niego ciemny tekst, ale gdy `is-light` jest JEDNOCZEŚNIE
  kolorem i odcieniem (`notification is-light`, `tag is-light`), trafia we
  własną regułę `.notification.is-light.is-light`, która kieruje kolor tekstu
  na `--bulma-light-light-invert-l` — zmienną, której arkusz Bulmy NIGDY nie
  definiuje (sprawdzone w `web/static/bulma.min.css`: zero deklaracji, jedno
  użycie). Wartość jest wtedy nieprawidłowa, `color` wraca do dziedziczenia,
  więc tekst idzie za motywem, a tło zostaje jasne w obu. W motywie ciemnym
  to jest jasnoszary napis na jasnym tle.

  Nasz arkusz to pogłębiał: `.pusto` nadpisywał z tej pary WYŁĄCZNIE tekst.

  Dlatego te komponenty dostają WŁASNĄ, PEŁNĄ parę z naszej palety — obie
  barwy naraz i obie idące za motywem. Nadpisanie samego tekstu albo samego
  tła jest tu usterką z definicji, niezależnie od tego, jak ładnie wygląda
  w motywie, w którym się je pisało.

  `:where()` wokół wykluczeń trzyma swoistość na `0,2,0`: reguła ma wygrać
  z deklaracjami Bulmy stojącymi na `.notification` i `.tag`, a przegrać
  z naszymi własnymi parami niżej (`.tag-piosenki`). Wykluczenia są po to,
  żeby `notification is-warning is-light` i reszta kolorowych wariantów
  zostały przy swoich barwach — one mają parę kompletną i czytelną.
*/
.notification.is-light:where(:not(.is-white):not(.is-black):not(.is-dark):not(.is-text):not(.is-primary):not(.is-link):not(.is-info):not(.is-success):not(.is-warning):not(.is-danger)),
.tag.is-light:where(:not(.is-white):not(.is-black):not(.is-dark):not(.is-text):not(.is-primary):not(.is-link):not(.is-info):not(.is-success):not(.is-warning):not(.is-danger)) {
  background-color: var(--powierzchnia);
  color: var(--tekst);
  border: 1px solid var(--obwodka);
}

/*
  Stan pusty jest CICHSZY od zwykłej treści — i to niesie kolor ORAZ krój,
  tak jak akordy pieśni (reguła 2 `design/README.md`): kursywa zostaje
  czytelna w odcieniach szarości i przy zaburzeniu widzenia barw, gdy sama
  barwa przestaje odróżniać. Kolor jest tu SŁABSZY, a nie jaśniejszy od tła:
  `--tekst-slaby` na `--powierzchnia` to 5,65 : 1 w motywie jasnym
  i 7,53 : 1 w ciemnym.
*/
.notification.pusto { color: var(--tekst-slaby); font-style: italic; }

/*
  KOMUNIKAT O SKUTKU CZYNNOŚCI (REQ-063, FEAT-055, issue #121).

  ANI JEDNEJ DEKLARACJI KOLORU. Para barw przychodzi w całości z komponentu
  Bulmy (`notification is-success` / `is-danger`) — i to jest tu regułą, nie
  oszczędnością: nadpisanie połowy pary dało w issue #110 kontrast 1,04 : 1,
  a zepsute było każde `is-light` naraz, nie jedno miejsce. Ten arkusz ustala
  wyłącznie UKŁAD: odstęp, rozmieszczenie ikony i słowa, oraz przejście przy
  gaśnięciu.
*/
.obszar-komunikatu { margin-top: 12px; }

.komunikat-skutku {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 0;
  /*
    Miejsce z prawej na guzik zamknięcia, który dokłada `komunikat.js`.
    Bulma stawia `.delete` w `notification` absolutnie w prawym górnym rogu,
    więc bez tego zapasu krzyżyk kładłby się na ostatnim słowie długiego
    zdania — widoczne tylko przy komunikacie zawijającym się na telefonie.
  */
  padding-right: 40px;
}

/*
  Ikona nie kurczy się przy zawijaniu tekstu i nie rozjeżdża się w pionie
  z pierwszym wierszem zdania.
*/
.komunikat-skutku .ikona-komunikatu { flex: 0 0 auto; margin-top: 2px; }

/*
  SŁOWO OBOK KOLORU (reguła 2 `design/README.md`): „Gotowe" albo „Nie udało
  się". Wytłuszczenie jest tu drugim, niezależnym od barwy nośnikiem — kto
  pracuje w odcieniach szarości, rozstrzyga rodzaj komunikatu z samego napisu.
*/
.komunikat-skutku .slowo-komunikatu { font-weight: 700; flex: 0 0 auto; }

.komunikat-skutku .tresc-komunikatu { flex: 1 1 auto; }

/*
  GAŚNIĘCIE SUKCESU idzie klasą z arkusza, a nie atrybutem `style`: polityka
  bezpieczeństwa nie dopuszcza `unsafe-inline` (REQ-001).
*/
.komunikat-skutku.komunikat-gasnie { opacity: 0; transition: opacity 350ms ease-out; }

@media (prefers-reduced-motion: reduce) {
  /*
    Kto poprosił system o mniej ruchu, dostaje zniknięcie bez przejścia —
    a nie przejście „na wszelki wypadek wolniejsze".
  */
  .komunikat-skutku.komunikat-gasnie { transition: none; }
}

@media (max-width: 640px) {
  /*
    Na telefonie tabela osób i rodzin zwija się do wierszy: kolumny „Login"
    i „Rodzina" schodzą pod nazwisko. Poziome przewijanie tabeli byłoby tu
    gorsze — telefon jest urządzeniem PODSTAWOWYM, a nie wersją zapasową.
  */
  .tabela thead { display: none; }
  .tabela, .tabela tbody, .tabela tr, .tabela td { display: block; width: 100%; }
  .tabela tr { padding: 10px 0; border-bottom: 1px solid var(--obwodka); }
  .tabela td { border: 0; padding: 2px 0; }
}

/*
  PRZEŁĄCZNIK „wszyscy" pod listą osób (REQ-047, REQ-009, REQ-014).

  Wysokość w PIKSELACH, a nie w `em`: cel dotykowy ma mieć 44 px niezależnie
  od stopnia pisma ustawionego w przeglądarce (reguła 1 z design/README.md).
  Przy rozmiarze w `em` powiększenie tekstu powiększa guzik razem z nim, a
  ZMNIEJSZENIE tekstu zabiera mu palec.
*/
.wszyscy-przelacznik { display: flex; justify-content: center; margin: 8px 0 0; }
.wszyscy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  padding: 0 16px;
}
/* Ucięta lista mówi o tym słowem — akapit stoi NAD stronicowaniem. */
.uciete { margin-top: 12px; }

/* ---- kalendarz ---------------------------------------------------------- */

.kalendarz-naglowek {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin-bottom: 12px;
}
.kalendarz-naglowek h1 { margin: 0; flex: 1 1 auto; }

/*
  ZAKŁADKI RODZAJU WIDOKU („Wydarzenia", „Rocznice", „Rocznice bez pełnej
  daty") — Bulma `tabs is-boxed`, ale z dwoma nadpisaniami (REQ-041,
  issue #101):

  Bulma domyślnie NIE ZAWIJA wiersza zakładek (`white-space: nowrap`,
  `overflow-x: auto` na `.tabs`) — a trzy zakładki przy stopniu pisma 200%
  byłyby szersze niż 390 px i kalendarz przewijał się w poziomie, dokładnie
  tak jak robił to rząd zakresów przed zgłoszeniem #145. Owijanie, a nie
  przewijanie rzędu: zakładka schowana za krawędzią poziomego pasa
  przewijania jest zakładką, której nie widać, że istnieje.

  Każda zakładka zostaje celem DOTYKOWYM co najmniej 44 px (REQ-008),
  a licznik przy „Rocznice bez pełnej daty" stoi w Bulma `tag` obok nazwy —
  `gap` na odnośniku oddziela nazwę od plakietki.
*/
.taby-kalendarza.tabs {
  overflow: visible;
  white-space: normal;
}
.taby-kalendarza ul {
  flex-wrap: wrap;
  gap: 4px;
}
.taby-kalendarza a {
  min-height: 44px;
  gap: 6px;
}

/*
  Zakładki zakresu i strzałki przewijania są celami DOTYKOWYMI: co najmniej
  44 px wysokości i szerokości (design/README.md, REQ-008). Telefon jest
  urządzeniem podstawowym, a nie wersją zapasową.
*/
/*
  ZAKŁADKI ZAKRESU OWIJAJĄ SIĘ (REQ-014, zgłoszenie #145).

  Bez `flex-wrap` trzy zakładki („Dzień", „Tydzień", „Miesiąc") stały w jednym
  rzędzie NIEZALEŻNIE od tego, ile jest miejsca. Przy stopniu pisma 200%
  sama „Miesiąc" ma 160 px, więc rząd rósł do 451 px na ekranie 390 px
  i kalendarz przewijał się w poziomie — czego REQ-014 zakazuje wprost.

  OWIJANIE, A NIE PRZEWIJANIE RZĘDU (`overflow-x: auto`): to jest nawigacja,
  a zakładka schowana za krawędzią poziomego pasa przewijania jest zakładką,
  której nie widać, że istnieje. Dokładnie ten wzorzec dał usterkę wydania
  0.7.0 — menu obcinane przez `overflow-x` rodzica.

  Cele dotykowe zostają 44 px (`min-width`/`min-height` niżej): owinięty rząd
  robi się wyższy, a nie ciaśniejszy.
*/
.zakresy, .przewijanie { display: flex; flex-wrap: wrap; gap: 4px; }
.zakresy a, .przewijanie a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--obwodka);
  border-radius: 8px;
  color: var(--tekst-slaby);
  text-decoration: none;
}
.zakresy a.aktywna {
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  border-color: var(--glowny);
  font-weight: 600;
}

.filtr-kalendarzy fieldset {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  border: 1px solid var(--obwodka);
  border-radius: 10px;
  padding: 10px 14px;
  margin: 0 0 12px;
}
.filtr-kalendarzy legend { font-size: 0.8rem; color: var(--tekst-slaby); }
.kalendarz-wybor { display: inline-flex; gap: 6px; align-items: center; min-height: 44px; }

/*
  Znacznik kalendarza bierze kolor ze zmiennej ustawianej przez
  /style/kalendarze.css. Kolor NIGDY nie jest jedynym nośnikiem informacji —
  obok stoi skrót kalendarza jako tekst (REQ-008, REQ-039).
*/
.znacznik-kalendarza {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--kolor-kalendarza, var(--tekst-slaby));
  flex: none;
}
.skrot-kal {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--tekst-slaby);
}

.siatka-miesiac .naglowki-dni {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  font-size: 0.75rem;
  color: var(--tekst-slaby);
  text-align: center;
  padding: 4px 0;
}
.siatka-miesiac {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.siatka-miesiac .naglowki-dni { grid-column: 1 / -1; }
.siatka-miesiac .skrot-dnia { display: none; }

.siatka-dzien, .siatka-tydzien { display: flex; flex-direction: column; gap: 8px; }

.dzien {
  border: 1px solid var(--obwodka);
  border-radius: 8px;
  padding: 6px;
  min-height: 88px;
  background: var(--powierzchnia);
}
.dzien.dzisiaj { border-color: var(--glowny); border-width: 2px; }
.dzien.obcy-miesiac { opacity: 0.55; }

/*
  Pusta komórka miesiąca POZOSTAJE klikalna i osiągalna z klawiatury
  (REQ-008): z widoku miesiąca wchodzi się w dzień właśnie po to, żeby coś
  w nim zaplanować. Cel dotykowy ma pełne 44 px.
*/
.numer-dnia {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  color: inherit;
  text-decoration: none;
  font-weight: 600;
}
.numer-dnia:hover { text-decoration: underline; }

.pas-calodniowy, .wydarzenia { list-style: none; padding: 0; margin: 4px 0 0; }
.pas-calodniowy li, .wydarzenia li {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: baseline;
  font-size: 0.85rem;
  padding: 2px 0;
}
/* Pas całodniowy jest ODDZIELONY od wydarzeń z godziną (REQ-008). */
.pas-calodniowy { border-bottom: 1px dashed var(--obwodka); padding-bottom: 4px; }

/*
  ROZWINIĘCIE SPOTKANIA W WIDOKU DZIENNYM (FEAT-007, scenariusze 4 i 5).

  `flex-basis: 100%` bierze się stąd, że `.wydarzenia li` jest wierszem
  flex z zawijaniem: bez tego rozwinięcie wciskałoby się obok tytułu
  i program spotkania czytałoby się w poprzek. Wcięcie mówi, że to jest
  treść POD nagłówkiem wydarzenia, a nie kolejne wydarzenia dnia.
*/
.rozwiniecie-spotkania {
  flex-basis: 100%;
  margin: 4px 0 0 12px;
  padding-left: 10px;
  border-left: 2px solid var(--obwodka);
}
.czesci-w-dniu, .grupy-w-dniu { list-style: none; padding: 0; margin: 0; }
.czesci-w-dniu li, .grupy-w-dniu li {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: baseline;
  font-size: 0.85rem;
  /*
    44 px celu dotykowego nie wymuszamy: to są WIERSZE DO CZYTANIA, nie
    kontrolki — żaden z nich nie jest odnośnikiem ani przyciskiem (REQ-008).
  */
  padding: 2px 0;
}
.czesci-w-dniu .czesc-nazwa { min-width: 0; }
.podpis-rozwiniecia {
  margin: 6px 0 2px;
  font-size: 0.72rem;
  color: var(--tekst-slaby);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.odwolane { text-decoration: line-through; color: var(--tekst-slaby); }
/*
  Plakietka „teraz" jest parą: tło `--glowny` i tekst NA NIM. Biel działała
  tylko w motywie jasnym — w ciemnym `--glowny` jest jasną lawendą i biały
  napis dawał 2,04 : 1. `--tlo` idzie za motywem w drugą stronę niż
  `--glowny`, więc para trzyma się w obu: 6,71 : 1 i 8,62 : 1.
*/
.teraz {
  background: var(--glowny);
  color: var(--tlo);
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 0.72rem;
  font-weight: 700;
}

/* ---- spotkanie ---------------------------------------------------------- */

.terminy, .czesci, .lista-wydarzen, .lista-rocznic, .lista-wakatow, .lista-brakow,
.lista-testow {
  list-style: none;
  padding: 0;
  margin: 0 0 20px;
}
.terminy li, .czesci li, .lista-wydarzen li, .lista-rocznic li,
.lista-wakatow li, .lista-brakow li, .lista-testow li {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: baseline;
  padding: 10px 0;
  border-bottom: 1px solid var(--obwodka);
  min-height: 44px;
}
.terminy li.minione { opacity: 0.6; }
.czesc.biezaca {
  border-left: 3px solid var(--glowny);
  padding-left: 10px;
  background: var(--glowny-jasny);
}
.czesc-nazwa { font-weight: 600; min-width: 8em; }
.czesc-godzina { color: var(--tekst-slaby); }

.karta-spotkania {
  /*
    Pełnoszeroki blok wyrównany z kafelkami: ta sama lewa i prawa krawędź,
    nie węższy i nienachodzący na żaden kafelek (REQ-010).
  */
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 14px 16px;
  margin: 0 0 16px;
  border: 1px solid var(--obwodka);
  border-radius: 12px;
  background: var(--powierzchnia);
  color: inherit;
  text-decoration: none;
  min-height: 64px;
}
.karta-spotkania.trwa { border-color: var(--glowny); border-width: 2px; }
/*
  SPOTKANIE ZAPOWIEDZIANE — wyróżnione tło (REQ-035, REQ-010).

  Trzy karty muszą się od siebie różnić: zwykła (tło powierzchni), trwająca
  (gruba obwódka w kolorze głównym) i zapowiedziana (tło i obwódka
  przerywana). Sam kolor NIE NIESIE tu informacji — obok stoi zdanie
  „zaczynamy za 4:31", czytelne w odcieniach szarości i dla czytnika ekranu.
*/
.karta-spotkania.zapowiedziane {
  background: var(--glowny-jasny);
  border-color: var(--glowny);
  border-width: 2px;
  border-style: dashed;
}
/*
  Cyfry o stałej szerokości: bez tego napis „zaczynamy za 4:59" skacze w bok
  przy każdej sekundzie, bo „1" jest węższa od „8".
*/
.karta-spotkania .odliczanie { font-variant-numeric: tabular-nums; }

/* Zapowiedź rozpoczęcia — etykieta i trzy guziki w jednym wierszu. */
.zapowiedz { align-items: center; gap: 8px; flex-wrap: wrap; }
.zapowiedz .etykieta-zapowiedzi { margin: 0 4px 0 0; }
.zapowiedz button { min-height: 44px; }
.karta-spotkania span { display: block; }
.karta-tytul { font-weight: 700; }
.karta-data, .karta-czesc { color: var(--tekst-slaby); font-size: 0.9rem; }
.karta-stan { color: var(--glowny-ciemny); font-weight: 700; }

.prowadzenie { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-end; }
.prowadzenie .z-powodem { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.prowadzenie button { min-height: 44px; }
.prowadzenie .glowna { min-height: 56px; padding: 0 20px; }
.powod { color: var(--uwaga); }

/* ---- pulpit ------------------------------------------------------------- */

.sekcja { margin: 0 0 24px; }
.sekcja h2 { display: flex; gap: 8px; align-items: center; }

/*
  Licznik niesie SŁOWEM, co liczy — opis dla czytnika ekranu stoi obok
  w `.tylko-dla-czytnika`, bo sama liczba przy ikonie nic mu nie mówi
  (REQ-010).
*/
.licznik {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  padding: 0 7px;
  border-radius: 999px;
  background: var(--glowny);
  color: #fff;
  font-size: 0.78rem;
  font-weight: 700;
}
.tylko-dla-czytnika {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.pulpit-kafelki .kafle {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}
.pulpit-kafelki .kafle .sekcja {
  margin: 0;
  padding: 14px 16px;
  border: 1px solid var(--obwodka);
  border-radius: 12px;
  background: var(--powierzchnia);
}

/*
  ZAPROSZENIA AUTOMATYCZNE NA PULPICIE (REQ-010, REQ-062).

  Adres wygląda inaczej niż zwykły akapit — ramka i krój o stałej szerokości —
  z tego samego powodu co adres subskrypcji: ma się czytać jak coś do
  PRZEPISANIA, a nie jak zdanie. Łamiemy go w dowolnym miejscu, bo na telefonie
  390 px pełny adres z nazwą hosta nie mieści się w jednym wierszu, a wyjście
  poza krawędź daje poziome przewijanie całej strony.
*/
.adres-zaproszen {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 10px;
  margin: 0 0 12px;
  border: 1px solid var(--obwodka);
  border-radius: 12px;
  background: var(--tlo);
}
.adres-zaproszen code { font-size: 0.85rem; word-break: break-all; }

/*
  ZDANIE O ODSŁONIĘTEJ LIŚCIE jest PRZYPOMNIENIEM, nie błędem — więc nie
  dostaje czerwieni ani ikony ostrzeżenia (reguła 3 `design/README.md`; ta sama
  decyzja co przy wakacie). Barwy przychodzą z `notification is-light`, które
  nasz arkusz wyżej nadpisuje PEŁNĄ parą z palety, idącą za motywem.
  Kolor nie jest tu jedynym nośnikiem: informacja stoi zdaniem.
*/
.ostrzezenie-zaproszen { font-size: 0.9rem; }

/*
  Wakat jest ZAPROSZENIEM DO OBSADZENIA, nie błędem: kolor główny, bez ikony
  ostrzeżenia i bez czerwieni (REQ-030, REQ-010).
*/
.wakat {
  color: var(--glowny-ciemny);
  font-weight: 600;
  font-size: 0.85rem;
}

/*
  Wakat stoi w szablonach jako `notification is-warning is-light wakat`,
  więc do wydania 0.29.0 brał żółte tło Bulmy — nieruchome przy zmianie
  motywu — i nasz kolor główny, który za motywem idzie. W ciemnym dawało to
  1,35 : 1 (issue #110). Tło musi więc przyjść z TEJ SAMEJ palety co tekst.

  Przy okazji znika sprzeczność z regułą 3 `design/README.md`: wakat jest
  ZAPROSZENIEM do obsadzenia, a nie ostrzeżeniem, więc żółć komunikatu
  ostrzegawczego nigdy nie była tu na miejscu. Para `--glowny-jasny` +
  `--glowny-ciemny` to 8,87 : 1 w jasnym i 8,84 : 1 w ciemnym.
*/
.notification.wakat {
  background-color: var(--glowny-jasny);
  color: var(--glowny-ciemny);
}

/* ---- grupy dzieci ------------------------------------------------------- */

.grupa-dzieci { margin: 0 0 24px; }
.grupa-dzieci h2 { display: flex; gap: 8px; align-items: center; }

/*
  Kolory grup dzieci są WYJĄTKIEM od palety kalendarzy — biorą wartości wprost
  z wypełnienia komórek arkusza organizacyjnego, bo wspólnota rozpoznaje po
  nich grupy od lat (REQ-031, REQ-039). Trzy wartości są zamknięte, więc mogą
  stać tutaj; kalendarzy jest otwarcie wiele i te idą osobnym arkuszem.

  Tekst NIE STOI na tym tle — plamka jest sama, a nazwa grupy obok, na tle
  strony. Dzięki temu kontrast tekstu nie zależy od koloru grupy.
*/
.plamka-grupy {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 4px;
  border: 1px solid var(--obwodka);
  flex: none;
}
.grupa-1 { background: #FFE599; }
.grupa-2 { background: #B6D7A8; }
.grupa-3 { background: #A4C2F4; }

@media (max-width: 640px) {
  /*
    Przy wąskim ekranie siatka miesiąca ZWIJA SIĘ DO LISTY dni, zamiast
    ściskać siedem kolumn poniżej szerokości słowa (REQ-008, REQ-015).
  */
  .siatka-miesiac { grid-template-columns: 1fr; }
  .siatka-miesiac .naglowki-dni { display: none; }
  .siatka-miesiac .skrot-dnia { display: inline; color: var(--tekst-slaby); }
  .siatka-miesiac .dzien.obcy-miesiac { display: none; }
  .dzien { min-height: 0; }
}

/* ---- decyzje, ankiety, projekty ---------------------------------------- */

/*
  „NOWA ANKIETA" ZGASZONA u osoby, która nie ma do kogo skierować ankiety
  (zgłoszenie #343). `span` bez `href` nie jest odnośnikiem, więc nie da się
  w niego ani kliknąć, ani wejść tabulatorem; wygląd ma to powiedzieć oczom.
  Kursor `not-allowed` bez zmiany barwy byłby podpowiedzią wyłącznie dla myszy,
  a na telefonie myszy nie ma.
*/
.ekran.ankiety span.glowna.wylaczony {
  color: var(--tekst-slaby);
  opacity: 0.55;
  cursor: not-allowed;
}

.lista-prosb, .lista-ankiet, .lista-powiadomien, .propozycje, .odpowiedzi-tekstowe {
  list-style: none;
  padding: 0;
  margin: 0 0 20px;
}
/*
  KLIKALNE POWIADOMIENIE NA PULPICIE (REQ-010, issue #128).

  Celem kliknięcia jest CAŁA POZYCJA, nie słowo „otwórz" na jej końcu: pulpit
  czyta się na telefonie w biegu, a próg celu dotyku to 44 px (reguła 1
  makiet). Odnośnik dziedziczy kolor tekstu, bo pozycja nie jest odsyłaczem
  w zdaniu — podkreślenie każdego wiersza zamieniłoby listę w plątaninę linii.
  Że da się kliknąć, mówi kursor i podświetlenie tła, a nie sam kolor.
*/
.lista-powiadomien a.powiadomienie {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: baseline;
  flex-grow: 1;
  min-height: 44px;
  padding: 4px 6px;
  margin: -4px -6px;
  border-radius: 8px;
  color: inherit;
  text-decoration: none;
}
.lista-powiadomien a.powiadomienie:hover,
.lista-powiadomien a.powiadomienie:focus-visible {
  background: var(--glowny-jasny);
  text-decoration: underline;
}

.lista-prosb li, .lista-ankiet li, .lista-powiadomien li, .propozycje li {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: baseline;
  padding: 10px 0;
  border-bottom: 1px solid var(--obwodka);
  min-height: 44px;
}

/*
  Powiadomienie NIEPRZECZYTANE jest wyróżnione paskiem i wagą pisma, nie samym
  kolorem — odróżnienie wyłącznie barwą znika w druku i dla części
  użytkowników (REQ-004).
*/
.lista-powiadomien li.nieprzeczytane {
  border-left: 3px solid var(--glowny);
  padding-left: 10px;
  font-weight: 600;
}
.lista-powiadomien .rodzaj { color: var(--glowny-ciemny); font-weight: 700; font-size: 0.85rem; }
.lista-powiadomien .tresc { flex: 1 1 260px; }

/*
  MOMENT ZDARZENIA — „12 września, 19:05" (REQ-008, issue #99).

  `white-space: nowrap` — data i godzina nie łamią się między sobą. Zawinięcie
  po przecinku daje na telefonie „12 września," w jednym wierszu i „19:05"
  w następnym, czyli dwie wartości wyglądające na dwie osobne informacje.

  `font-weight: 400` cofa pogrubienie, które wiersz nieprzeczytany narzuca
  całej swojej treści (`li.nieprzeczytane { font-weight: 600 }`). Moment jest
  metryką wpisu, nie jego treścią — pogrubiony konkuruje wzrokowo z tym, co
  wpis mówi.
*/
.lista-powiadomien .kiedy,
.lista-prosb .kiedy {
  flex: 0 0 auto;
  white-space: nowrap;
  font-weight: 400;
}

/*
  POTWIERDZENIE „OK" PRZY WPISIE NA PULPICIE (REQ-010, issue #102).

  Formularz jest RODZEŃSTWEM odnośnika `a.powiadomienie`, nie jego wnętrzem, i
  ten kawałek stylu jest tym, co utrzymuje ten podział NA EKRANIE.

  Odnośnik ma `flex-grow: 1` i UJEMNE marginesy (−4 px / −6 px), żeby jego tło
  obejmowało cały wiersz — czyli jego prostokąt sięga 6 px dalej, niż widać.
  Odstęp trzyma `gap: 10px` z reguły wiersza wyżej, a `margin-left` poniżej
  jest drugą warstwą tego samego: bez niej zapas wynosi 4 px i pierwsza zmiana
  wypełnienia odnośnika zjada go w całości.

  Sprawdziliśmy to ZEPSUCIEM: `margin-left: −20px` zamienia odstęp
  w PRZEKRYCIE i test przeglądarkowy zapala się na tym w układzie komputera.
  W układzie telefonu wiersz się zawija, więc prostokąty rozjeżdżają się
  w pionie i przekrycia nie ma — to jeden z powodów, dla których każdy test
  idzie w obu układach.

  `flex-shrink: 0` — guzik nie zwęża się przy długiej treści. Ściśnięty do
  szerokości litery „O" przestaje być celem dotykowym i zaczyna być pomyłką.
*/
.lista-powiadomien .potwierdz-powiadomienie {
  flex: 0 0 auto;
  margin: 0 0 0 12px;
}
.lista-powiadomien .potwierdz-powiadomienie .button.potwierdzenie {
  min-width: 44px;
  min-height: 44px;
}

.komentarz {
  flex: 1 1 100%;
  margin: 4px 0 0;
  padding: 6px 10px;
  background: var(--tlo);
  border-radius: 8px;
  font-style: italic;
}

.decyzja { display: inline-flex; gap: 6px; align-items: center; }
.decyzja button { min-height: 44px; }
.decyzja.z-powodem { flex-wrap: wrap; }

/*
  Stany prośby SŁOWEM — nazwa stanu stoi w treści, a kolor tylko ją wzmacnia.
*/
.stan-oczekuje { color: var(--glowny-ciemny); }
.stan-zaakceptowana { color: var(--zgoda); }
.stan-odrzucona, .stan-wygasla { color: var(--tekst-slaby); }

/* ---- głosowanie --------------------------------------------------------- */

.pytanie {
  border: 1px solid var(--obwodka);
  border-radius: 10px;
  padding: 12px 14px;
  margin: 0 0 16px;
}
.pytanie legend { font-weight: 600; }
.odpowiedz { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; min-height: 44px; }
.odpowiedz label { display: inline-flex; gap: 8px; align-items: center; }

.oceny { display: flex; flex-wrap: wrap; gap: 12px; }
.oceny label { display: inline-flex; gap: 6px; align-items: center; min-height: 44px; }

/*
  Trzy stopnie oceny są rozróżniane SŁOWEM — „pasuje", „może", „nie pasuje" —
  a kolor tylko pomaga je odnaleźć wzrokiem. Nazwy są dokładnie te same
  w dokumentacji, na ekranie, w powiadomieniu i w audycie (REQ-043).
*/
.wynik { flex: 1 1 100%; display: flex; flex-direction: column; gap: 2px; font-size: 0.85rem; }
.wynik .pasuje { color: var(--zgoda); }
.wynik .moze { color: var(--tekst-slaby); }
.wynik .nie-pasuje { color: var(--uwaga); }
.wynik .brak { color: var(--tekst-slaby); font-style: italic; }

.propozycje li.zwycieska {
  border-left: 3px solid var(--glowny);
  padding-left: 10px;
  background: var(--glowny-jasny);
}
.rozstrzygniecie {
  padding: 10px 14px;
  border: 1px solid var(--glowny);
  border-radius: 10px;
  background: var(--glowny-jasny);
}

/* ---- formularze --------------------------------------------------------- */

.formularz {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-width: 640px;
}
.formularz label { margin-top: 10px; font-size: 0.85rem; color: var(--tekst-slaby); }
.formularz input, .formularz textarea, .formularz select { min-height: 44px; }
.formularz fieldset {
  border: 1px solid var(--obwodka);
  border-radius: 10px;
  padding: 10px 14px;
  margin: 12px 0 0;
}
.formularz .wybor {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  margin-top: 12px;
  color: var(--tekst);
  font-size: 1rem;
}
.formularz .wybor .pomocniczy { flex: 1 1 100%; }
.formularz button.glowna { margin-top: 16px; min-height: 56px; }
.propozycja-wiersz {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--obwodka);
}

/* ---- śpiewnik i playlisty ---------------------------------------------- */

.lista-piosenek, .pozycje-playlisty, .lista-modulow, .pozycje-zmian {
  list-style: none;
  padding: 0;
  margin: 0 0 20px;
}
.lista-piosenek li, .pozycje-playlisty li, .lista-modulow li {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: baseline;
  padding: 10px 0;
  border-bottom: 1px solid var(--obwodka);
  min-height: 44px;
}

/* ---- moduły: trzy guziki przed nazwą (issue #122) ------------------------ */

.lista-modulow li.modul-wiersz {
  align-items: center;
  gap: 14px;
}

/* Formularz sterowania trzyma TYLKO guziki; stoi PRZED nazwą, w stałej
   kolumnie. Szerokość trzech guzików jest wspólna, bo wszystkie wiersze niosą
   te same trzy etykiety. `flex: 0 1 auto` pozwala wierszowi ścisnąć formę
   poniżej jej naturalnej szerokości (np. przy stopniu pisma 200% guziki nie
   mieszczą się obok siebie), a wtedy `flex-wrap` w `.guziki-stanu` zawija
   guziki w kolumnę — bez tego szerokość pojedynczego guzika popychałaby
   wiersz w poziom. */
.stan-modulu {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
}

/* Grupa trzech guzików Bulmy. `buttons has-addons` skleja guziki w jeden
   wiersz; kolumna ma w każdym wierszu tę samą szerokość, bo wszystkie wiersze
   niosą te same trzy etykiety — strona nie skacze przy przewijaniu (issue
   #122). Cel dotykowy 44 × 44 px liczony w pikselach, niezależnie od stopnia
   pisma (REQ-014): `min-height` i `min-width` stoją w jednostkach stałych,
   bo na `em` rosłyby i malały wraz z napisem. `flex-wrap` pozwala zawinąć
   na wąskim ekranie — strona nie przewija się w poziomie. */
.guziki-stanu { flex-wrap: wrap; }
.guziki-stanu .button {
  flex: 1 1 auto;
  min-height: 44px;
  min-width: 44px;
  white-space: nowrap;
}
.guziki-stanu .button.is-selected {
  font-weight: 700;
  box-shadow: inset 0 0 0 2px var(--glowny);
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
}

/* Opis modułu (nazwa + opis) stoi PO PRAWEJ guziki, elastycznie rozciąga
   resztę wiersza. */
.modul-opis {
  flex: 1 1 200px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.nazwa-modulu {
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

/* IKONA MODUŁU przy jego nazwie (REQ-026) — ta sama, którą moduł ma
   w nawigacji. Rysunek nie kurczy się razem z nazwą łamiącą się w dwie linie
   (`flex: 0 0 auto`), bo ściśnięta ikona liniowa przestaje być rozpoznawalna,
   a wtedy przestaje cokolwiek mówić.

   BEZ WŁASNEJ BARWY: `currentColor` w symbolu bierze kolor tekstu wiersza,
   więc rysunek chodzi za motywem dziennym i nocnym razem z nazwą, a barwa
   nie niesie tu żadnego znaczenia (reguła 2 makiet). */
.ikona-modulu { flex: 0 0 auto; }
.nazwa-modulu .napis-modulu { min-width: 0; overflow-wrap: anywhere; }

/* Legenda trzech stanów nad listą — pełne zdanie o stanie, którego skrót
   na guziku nie niesie (issue #122). */
.legenda-modulow { margin: 0 0 8px; line-height: 1.6; }
.legenda-modulow span { font-weight: 700; }
.legenda-wlaczony { color: var(--glowny-ciemny); }
.legenda-testowanie { color: var(--glowny-ciemny); }
.legenda-wylaczony { color: var(--tekst-slaby); }

/* ---- dostęp indywidualny: ekran Moduły, widok modułu, Tabela (REQ-071) -- */

/* Narzędzie przy legendzie ekranu Moduły: guzik do Tabeli dostępów. Ekran
   i tak jest oczami administratora instalacji, więc instrument stoi wprost,
   a nie chowa się w menu. */
.narzedzia-dostepow {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  align-items: center;
  margin: 0 0 14px;
}
.narzedzia-dostepow .button { min-height: 44px; }

/* Liczba przy module na ekranie Moduły prowadzi do widoku użytkowników.
   Cel dotykowy 44 × 44 px (REQ-014), jak wszystkie linki-czynności. */
.licznik-dostepu {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--obwodka);
  border-radius: 999px;
  color: var(--glowny-ciemny);
  font-weight: 600;
  font-size: 0.85rem;
  text-decoration: none;
  white-space: nowrap;
}
.licznik-dostepu:hover { border-color: var(--glowny); background: var(--glowny-jasny); }

/* Adnotacja modułu widocznego dla każdego — napis, nie sam kolor (reguła 2
   makiet): słowo niesie sens, wygląd go tylko wyróżnia. */
.tag.tag-dostepny-wszystkim {
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  font-weight: 700;
  border: 1px solid var(--glowny);
}
.tag.tag-w-testach {
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  font-weight: 700;
}

/* Powrót z obu nowych widoków do ekranu Moduły — wzorzec „← Wszystkie role". */
.powrot-do-moduly {
  margin-bottom: 6px;
  font-size: 0.9rem;
}
.powrot-do-moduly a { text-decoration: none; color: var(--glowny-ciemny); }

/* Grupy osób przy module (widok użytkowników i Tabela): każda grupa ma
   nagłówek, wyjaśnienie i wykaz o stałej wysokości wiersza. */
.grupa-osob-dostepu { margin: 12px 0; }
.grupa-osob-dostepu .title { margin-bottom: 2px; }
.wykaz-osob-dostepu {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
}
.wykaz-osob-dostepu li {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  min-height: 44px;
  padding: 6px 0;
  border-bottom: 1px solid var(--obwodka);
}
.wykaz-osob-dostepu li:last-child { border-bottom: 0; }
.wykaz-osob-dostepu form { margin-left: auto; }

/* Formularz nadania dla wielu naraz: pola wyboru w wierszu o stałej
   wysokości, wypełnienie tylko na labelu, żeby cel dotykowy był cały. */
.dodaj-dostep-modulu fieldset {
  border: 1px solid var(--obwodka);
  border-radius: 8px;
  padding: 12px;
  margin: 0;
}
.kandydaci-dostepu {
  list-style: none;
  margin: 6px 0 12px;
  padding: 0;
}
.kandydaci-dostepu li label {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  min-height: 44px;
  padding: 4px 0;
  cursor: pointer;
}
.kandydaci-dostepu input { flex: 0 0 auto; }

/* Tabela dostępów: moduł to CAŁY wiersz z dwiema grupami. Wiersz modułu
   globalnie włączonego ma być widoczny z listy już ze słów, bez czytania
   zawartości — dlatego dostaje tło wyróżnienia i jedną adnotację zamiast
   grup. */
.lista-wierszy-dostepow { list-style: none; margin: 0 0 20px; padding: 0; }
.wiersz-dostepu {
  padding: 12px 0;
  border-bottom: 1px solid var(--obwodka);
}
.wiersz-dostepu.globalny { background: var(--glowny-jasny); }
.naglowek-wiersza-dostepu {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  align-items: baseline;
}

/* Sekcja „Moduły" na profilu osoby (widok administratora). Wiersz niesie
   nazwę, opis modułu, ŹRÓDŁO widoczności i — tylko przy parze — guzik
   „Usuń". Źródło jest tagiem, żeby administrator jednym rzutem oka
   rozróżnił „Dostępny wszystkim" od „Tester". */
.moduly-osoby { margin-top: 18px; }
.moduly-osoby h2 { margin-bottom: 2px; }
.lista-dostepow-osoby {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
}
.lista-dostepow-osoby li {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  align-items: center;
  min-height: 44px;
  padding: 8px 0;
  border-bottom: 1px solid var(--obwodka);
}
.lista-dostepow-osoby form { margin-left: auto; }
.tag.zrodlo-dostepu {
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  font-weight: 700;
}
.dodaj-modul-osoby { margin-top: 6px; }

/* ---- „O Aplikacji": sekcja Moduły (REQ-070) ------------------------------
   Lista przeglądarki-opisu dzieli z listami ekranu administratora tę samą
   bazę wiersza (odstępy, przerywnik, `flex`). Różnica: tu wiersz niesie
   WYŁĄCZNIE opis i plakietkę „Testowany" — nie ma guzików stanu, bo lista
   nie jest narzędziem instalacji, tylko odpowiedzią na „co ja mam". */
.lista-modulow-ogladajacego {
  list-style: none;
  padding: 0;
  margin: 0;
}
.lista-modulow-ogladajacego li {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: baseline;
  padding: 10px 0;
  border-bottom: 1px solid var(--obwodka);
  min-height: 44px;
}
.lista-modulow-ogladajacego li:last-child { border-bottom: 0; }
.sekcja-modulow h2.title { margin-bottom: 4px; }

/* WYRÓŻNIENIE MODUŁU TESTOWANEGO (REQ-070): adnotacja jest cechą
   WIDOCZNOŚCI — osoba z dostępem ma wiedzieć, że sprawdza rzecz nienormowaną.
   Swoistość `0,2,0`, wzorem `.tag.tag-piosenki`, żeby własna para wygrała
   z ogólną regułą `.tag.is-light`. */
.tag.jest-testowany {
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  font-size: 0.78rem;
  font-weight: 700;
}

.pozycje-playlisty { counter-reset: piesn; }
.pozycje-playlisty li.biezaca {
  border-left: 3px solid var(--glowny);
  padding-left: 10px;
  background: var(--glowny-jasny);
}

.warianty-piosenki { display: flex; align-items: center; gap: 4px; margin: 0 0 16px; flex-wrap: wrap; }
.warianty-piosenki a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--obwodka);
  border-radius: 8px;
  color: var(--tekst-slaby);
  text-decoration: none;
}
.warianty-piosenki a.aktywna {
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  border-color: var(--glowny);
  font-weight: 600;
}

/*
  DWA WARIANTY WIDOKU PIOSENKI = DWA KROJE, nie ten sam układ z ukrytymi
  akordami (REQ-044, design/PiosenkaKom.dc.html).

  Krój o STAŁEJ SZEROKOŚCI znaku jest potrzebny WYŁĄCZNIE po to, żeby akord
  wypadł nad właściwą sylabą. Bez akordów staje się gorszy od
  proporcjonalnego: zwęża wiersz i psuje interlinię. Dlatego wariant „sam
  tekst" ma własny krój, własną interlinię i ograniczoną miarę wiersza —
  tekst pieśni czyta się jak wiersz, a nie jak akapit prozy.
*/
.tekst-piosenki { margin: 0 0 20px; }

/* --- wariant Z AKORDAMI: nakładka nad sylabą (układ WRSM2026) --- */

/*
  AKORD JEST NAKŁADKĄ, nie osobnym wierszem. Rysuje go `::before` z atrybutu
  `data-chord`, uniesiony o wiersz do góry i o ZEROWEJ SZEROKOŚCI — dzięki
  temu nie rozpycha tekstu i stoi dokładnie nad tą literą, przy której zapisał
  go redaktor. To jest układ, w którym wspólnota czyta te pieśni od lat.

  Konsekwencja, dla której warto: tekst zawija się normalnie, więc pieśń mieści
  się na telefonie bez przewijania w poziomie, a krój może być proporcjonalny —
  wiersz akordów o stałej szerokości znaku był potrzebny wyłącznie po to, żeby
  akord wypadł nad właściwą kolumną.

  Interlinia MUSI być duża: w tym wierszu, nad tekstem, stoją akordy.
*/
.song { line-height: 2.1; }
.song span[data-chord] { position: relative; }
.song span[data-chord]::before {
  position: relative;
  top: -1.15em;
  display: inline-block;
  width: 0;
  content: attr(data-chord);
  /* Kolor ORAZ pogrubienie i kursywa — w odcieniach szarości i przy zaburzeniu
     widzenia barw akord musi zostać odróżnialny od tekstu (reguła 2). */
  color: var(--glowny-ciemny);
  font-style: italic;
  font-weight: 700;
  white-space: pre;
}
/* Linia akordów całej zwrotki — stoi w treści, nie nad sylabą. */
.song span[data-chordbase]::before {
  content: attr(data-chordbase);
  color: var(--glowny-ciemny);
  font-style: italic;
  font-weight: 700;
  letter-spacing: 0.08em;
}
.song h1, .song h2, .song h3, .song h4, .song h5, .song h6 {
  font-size: 1rem;
  margin: 16px 0 6px;
}
/* Notatka dla grających — plakietka Bulmy, ale cicha: to instrukcja
   wykonawcza, nie treść pieśni. */
.song .znacznik { font-style: italic; }

/* --- wariant SAM TEKST --- */

/*
  Bez akordów krój proporcjonalny i węższa miara wiersza: tekst pieśni czyta
  się jak wiersz, a nie jak akapit prozy. Interlinia wraca do normalnej — nad
  wierszem nic już nie stoi.
*/
.tekst-piosenki.sam-tekst {
  max-width: 42ch;
  line-height: 1.75;
  font-size: 1.05rem;
}

.powtorzenie {
  margin-left: 6px;
  font-family: system-ui, sans-serif;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--tekst-slaby);
}

.tagi-piosenki { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
/* Swoistość `0,2,0`, żeby własna para plakietki wygrała z ogólną regułą
   `.tag.is-light` wyżej — obie są parami pełnymi, ta jest bardziej
   szczegółowa. */
.tag.tag-piosenki {
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  font-size: 0.78rem;
}

/* ---- ikonki przy tytule pieśni ----------------------------------------- */

/*
  TYTUŁ I DWIE IKONKI W JEDNYM RZĘDZIE (REQ-044): „Play" dla każdego,
  edycja tylko dla osoby z uprawnieniem. Rząd się ZAWIJA — na telefonie przy
  200% stopnia pisma tytuł zajmuje całą szerokość, a ikonki mają wtedy zejść
  pod niego, a nie zostać wypchnięte poza ekran.

  Cel dotykowy 44 px (REQ-016) jest tu wpisany w PIKSELACH świadomie: guziki
  mają zostać wielkości palca niezależnie od stopnia pisma, tak samo jak
  pozostałe cele dotykowe w tym arkuszu.
*/
.tytul-piesni {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}
.tytul-piesni .title { margin-bottom: 0; }
.ikony-piesni { display: flex; gap: 8px; }
.ikona-piesni {
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tekst);
}

/* ---- edycja pieśni: źródło i podgląd obok siebie ------------------------ */

/*
  DWIE KOLUMNY DOPIERO OD 900 px. Na telefonie podgląd stoi POD polem treści:
  dwie kolumny na 390 px dałyby dwie kolumny po 180 px, w których ani zapisu
  źródłowego, ani złożonej pieśni nie da się przeczytać.
*/
.kolumny-edycji { display: grid; gap: 20px; }
.kolumna-zrodla, .kolumna-podgladu { min-width: 0; }
.podglad-skladu, .dokument-podglad { overflow-x: auto; }
.guziki-edycji { margin-top: 16px; }
.blad-formularza { margin: 0 0 16px; }
@media (min-width: 900px) {
  .kolumny-edycji { grid-template-columns: 1fr 1fr; }
}

/* ---- tryb pełnoekranowy pieśni ----------------------------------------- */

/*
  EKRAN PRZYKRYWA STRONĘ W CAŁOŚCI (REQ-044), a nie udaje pełnego ekranu przez
  ukrycie kilku elementów: pasek aplikacji, nawigacja i stopka zostają
  w dokumencie, ale są pod spodem. Dzięki temu wyjście wraca do strony, która
  nigdzie się nie podziała, a tryb nie wymaga uprawnień przeglądarki do
  prawdziwego pełnego ekranu (te wymagają gestu i bywają zablokowane).

  ROZMIARY SĄ W `rem`, NIE W PIKSELACH. Drabina stopnia pisma (REQ-014) stoi
  na elemencie głównym, więc tekst w pełnym ekranie idzie za nią sam. Własna
  skala w pikselach byłaby drugą drabiną — i wywracałaby wybór, który część
  wspólnoty ustawiła świadomie, żeby móc czytać.
*/
.pelny-ekran {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  background: var(--tlo);
  color: var(--tekst);
  padding: 8px 12px 14px;
}
.pasek-pelnego-ekranu {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 10px;
}
.tytul-pelnego-ekranu {
  flex-grow: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 700;
  color: var(--tekst-slaby);
}
.numer-strony { color: var(--tekst-slaby); font-size: 0.9rem; }

/*
  Strona pieśni stoi w PIONOWYM ŚRODKU: bywa krótka, a pieśń czytana z pulpitu
  ma stać tam, gdzie pada wzrok.

  `overflow-y: auto` jest ostatnią deską ratunku, a nie sposobem na układ —
  przy stopniu 175% i 200% strona może nie zmieścić się w pionie mimo podziału
  co dwanaście wierszy. Tekst ucięty jest gorszy niż tekst przewijany.
*/
.strona-piesni {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 12px 4px;
}
.ciag-dalszy {
  margin: 0 0 8px;
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tekst-slaby);
}
.tekst-piesni-pelny { font-size: 1.35rem; }
.tekst-piesni-pelny.sam-tekst { max-width: 42ch; line-height: 1.75; }
@media (min-width: 900px) {
  .tekst-piesni-pelny { font-size: 1.9rem; }
}
.przejscia-stron {
  flex-shrink: 0;
  display: flex;
  gap: 10px;
}
/*
  Cele 56 px — akcja główna (REQ-016). Naciska się je seriami i nie patrząc,
  więc kraniec ZOSTAJE wyszarzony zamiast znikać: gdyby znikał, drugi guzik
  zmieniałby położenie na pierwszej i ostatniej stronie.
*/
.przejscia-stron .button {
  flex: 1;
  min-height: 56px;
  font-weight: 600;
}
.przejscia-stron .strona-dalej { flex: 2; }

/* ---- infoekran -------------------------------------------------------------- */

/*
  Infoekran wisi na ścianie i jest czytane Z SALI, nie z ręki — stąd skala pisma
  zupełnie inna niż na pozostałych ekranach. Nazwa bieżącej części jest
  najmocniejszym elementem, bo to ona odpowiada na pytanie zadawane od progu.
*/
.infoekran { max-width: none; text-align: center; }
.infoekran-etykieta {
  margin: 0;
  font-size: 1rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--tekst-slaby);
}
.infoekran-czesc {
  margin: 4px 0 20px;
  font-size: clamp(2rem, 7vw, 5rem);
  line-height: 1.1;
}
.infoekran-piesn h2 { font-size: clamp(1.4rem, 4vw, 2.6rem); }
/*
  TEKST PIEŚNI NA RZUTNIKU (REQ-059, zgłoszenie #270).

  Treść jest złożona TYM SAMYM rendererem, co widok pieśni (`.song`), więc
  wersy rozdziela `<br/>`, a nie osobne akapity. Stopień pisma rośnie
  z SZEROKOŚCIĄ OKNA (`3.4vw`) i to on, a nie zawijanie, trzyma regułę „wers
  nie łamie się w połowie": przy stałym stopniu pisma ten sam wers mieściłby
  się na rzutniku, a łamał na tablecie.

  Interlinia nadpisuje `.song { line-height: 2.1 }` — tamta jest duża, bo nad
  wierszem stoją akordy, a tutaj akordów nie ma (wariant „sam tekst").
*/
.infoekran-tekst {
  margin: 0;
  font-size: clamp(1.2rem, 3.4vw, 2.4rem);
  line-height: 1.35;
}
.infoekran-dzieci .grupy-dzieci {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 24px;
  list-style: none;
  padding: 0;
}
.infoekran-dzieci .grupa-dzieci { display: flex; gap: 8px; align-items: center; }
/*
  IMIĘ OPIEKUNA OBOK AWATARA (REQ-059, reguła 4 makiet; artboard `Infoekran`).
  Ten sam stopień pisma co nazwa grupy, wyróżniony grubością — na ścianie sali
  szara linijka pomocnicza obok kółka 56 px jest nieczytelna, a kółko bez
  czytelnego imienia nie jest tożsamością: samych „Ań" jest we wspólnocie kilka.
*/
.infoekran-dzieci .nazwa-opiekuna { font-weight: 600; }
.infoekran-wydarzenia .lista-wydarzen { display: inline-block; text-align: left; }

/*
  SYGNALIZACJA POCZĄTKU PIEŚNI (REQ-046, zgłoszenie #279).

  NIE SAMYM KOLOREM — reguła 2 makiet mówi to o każdym nośniku informacji,
  a REQ-046 powtarza to o tej sygnalizacji wprost („nie może polegać wyłącznie
  na dźwięku ani wyłącznie na kolorze"). Nośnikiem jest SŁOWO „zaczynamy";
  obwódka i stopień pisma tylko je wzmacniają. Ekran w sali bywa oglądany
  z ośmiu metrów przez kogoś, kto właśnie podniósł wzrok — sam odcień tła nie
  doniesie tam niczego.

  Zdanie stoi w dokumencie ZAWSZE i jest schowane poza stanem
  `stan-zaczynamy`. Skrypt zapala stan na sekcji i nie dopisuje ani jednego
  słowa: polszczyzna zostaje w szablonie, a strona bez skryptu pokazuje po
  prostu pieśń — bez sygnału, ale i bez zdania wiszącego na okrągło.

  REGUŁY SĄ WSPÓLNE DLA OBU EKRANÓW ŚLEDZĄCYCH (`/infoekran`
  i `/playlisty/{id}`) i tak mają zostać: drugi zestaw reguł rysujących ten
  sam stan rozjechałby się przy pierwszej poprawce.
*/
.piesn-zaczynamy { display: none; }
.stan-zaczynamy .piesn-zaczynamy {
  display: block;
  margin: 0 0 4px;
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--glowny);
}
.stan-zaczynamy {
  outline: 3px solid var(--glowny);
  outline-offset: 6px;
  border-radius: 6px;
}

/*
  NA KIEDY DANE SĄ AKTUALNE (REQ-059, FEAT-052, zgłoszenie #278).

  Napis jest NAJSŁABSZYM elementem ekranu i tak ma zostać: odpowiada na
  pytanie, które pada dopiero wtedy, gdy ktoś już podejrzewa, że ekran stoi.
  Nazwa bieżącej części zostaje najmocniejsza.
*/
.infoekran-aktualnosc {
  margin: 0 0 8px;
  font-size: 0.9rem;
  color: var(--tekst-slaby);
}
/*
  OSTRZEŻENIE MUSI DAĆ SIĘ SCHOWAĆ ATRYBUTEM `hidden` — a `.tag` Bulmy ustawia
  `display: inline-flex` i WYGRYWA z regułą przeglądarki dla `hidden` (tamta
  stoi w arkuszu agenta i ma zerową specyficzność). Bez tego wiersza
  ostrzeżenie o nieudanej aktualizacji wisiałoby na ekranie od pierwszego
  wyrenderowania, zawsze — czyli mówiłoby nieprawdę przy sprawnym serwerze,
  dokładnie odwrotnie niż ma.
*/
.infoekran-nieaktualne[hidden] { display: none; }
.infoekran-nieaktualne { margin-left: 8px; }

/* Tryb pełnoekranowy chowa pasek i nawigację — ekran ma pokazywać treść. */
.infoekran-pelny { padding-top: 0; }
.infoekran-pelny-przelacznik { margin-top: 32px; }

/* ---- lista zmian -------------------------------------------------------- */

/*
  WYDANIE JAKO ELEMENT ROZWIJANY (issue 1328). `details` w pudełku `card`
  Bulmy — bez skryptu, bo CSP nie dopuszcza `unsafe-inline` (REQ-001), a ekran
  ma działać także wtedy, gdy skrypt się nie wczyta.
*/
.wydanie { margin: 0 0 14px; }

/*
  CAŁY NAGŁÓWEK JEST CELEM KLIKNIĘCIA — nie sam trójkącik. Trójkącik ma
  kilkanaście pikseli, a to jest jedyny sposób otwarcia wydania (reguła 1
  makiet: cel dotyku ma co najmniej 44 px).
*/
.wydanie > summary.naglowek-wydania {
  display: block;
  cursor: pointer;
  padding: 10px 14px;
  min-height: 44px;
}
.wydanie > summary.naglowek-wydania::marker,
.wydanie > summary.naglowek-wydania::-webkit-details-marker { color: var(--tekst-slaby); }
.wydanie > summary .card-header-title {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: baseline;
  padding: 0;
}
/* Podsumowanie ZAWIJA SIĘ, a nie ucina: na telefonie zdanie nie zmieści się
   w jednym wierszu, a ucięte przestaje odpowiadać na pytanie „które to". */
.wydanie > summary .podsumowanie { display: block; margin-top: 2px; }
.wydanie.biezace > summary .card-header-title { color: var(--glowny-ciemny); }
/*
  BEZ `color` — i to jest sedno usterki #110 w drugim komponencie.

  Ta reguła stoi w sekcji listy zmian, ale selektor `.tag` jest globalny:
  kolor tekstu z niej szedł na KAŻDĄ plakietkę w aplikacji i wygrywał
  z parami Bulmy (`tag is-info is-light` — 2,04 : 1 w motywie ciemnym) oraz
  z naszą własną parą plakietki „teraz" (1,26 : 1 w jasnym, 1,04 : 1
  w ciemnym: szary tekst na kolorze głównym). Krój i stopień pisma parą barw
  nie są, więc zostają.
*/
.tag {
  font-family: ui-monospace, monospace;
  font-size: 0.85rem;
}
.pozycje-zmian li { margin: 0 0 10px; }
.pozycje-zmian ul { margin: 4px 0 0 18px; }
/* Pierwsze zdanie („co") wyróżnione, uzasadnienia ciszej — ekran skanuje się
   po punktach pierwszego poziomu. */
.pozycje-zmian .co { font-weight: 600; }
.pozycje-zmian .szczegoly { color: var(--tekst-slaby); }

/* ---- pomoc -------------------------------------------------------------- */

.pomoc section { margin: 0 0 28px; max-width: 62ch; }
.pomoc h2 { margin-bottom: 6px; }
.pomoc p { margin: 0 0 10px; }
.pomoc code {
  font-family: ui-monospace, monospace;
  font-size: 0.9em;
  background: var(--tlo);
  padding: 1px 5px;
  border-radius: 4px;
}

/* ---- WYDRUK A4 (REQ-009) ------------------------------------------------ */

@media print {
  /*
    WYDRUK JEST OSOBNYM DOKUMENTEM, nie stroną z nawigacją ukrytą stylem —
    ale skoro reguły druku i tak muszą tu być, chowamy wszystko, co nie jest
    treścią, w jednym miejscu. Nawigacja, pasek, przyciski i wskaźniki stanu
    nie mają na kartce czego szukać.
  */
  .pasek, .nawigacja, .dolny-pasek, .zakladki, .filtr-kalendarzy, .zakresy,
  .taby-kalendarza, .przewijanie, .prowadzenie, .formularz, .szukajka,
  .decyzja, .oznacz,
  .oznacz-jedno, .warianty-piosenki, .infoekran-pelny-przelacznik, form, button {
    display: none !important;
  }

  /*
    TŁO BIAŁE, TEKST CZARNY niezależnie od motywu — wydruk nie zużywa tuszu
    na ciemne tło (REQ-009).
  */
  :root, :root[data-theme="dark"] {
    --tlo: #fff;
    --powierzchnia: #fff;
    --tekst: #000;
    --tekst-slaby: #333;
    --obwodka: #999;
    --glowny: #000;
    --glowny-ciemny: #000;
    --glowny-jasny: #fff;
  }
  body { background: #fff; color: #000; }

  @page { size: A4; margin: 10mm; }

  /* Minimalny rozmiar tekstu: 10 pt pomocniczy, 12 pt treść (REQ-009).
     Podstawa schodzi na `html` z tego samego powodu, co na ekranie: gdyby
     została na `body`, rozmiary w `rem` szłyby w druku za stopniem pisma
     wybranym na ekranie, a wydruk ma być ten sam dla każdego konta. */
  html { font-size: 12pt; }
  body { font-size: 12pt; }
  .pomocniczy { font-size: 10pt; }

  /*
    Widok TYGODNIOWY na jednej stronie A4 POZIOMEJ, miesięczny na jednej
    stronie — bez ucinania treści i bez poziomego przewijania.
  */
  .siatka-miesiac { grid-template-columns: repeat(7, 1fr); }
  .siatka-miesiac .naglowki-dni { display: grid; }
  .dzien { break-inside: avoid; min-height: 0; }

  /*
    Osoby i grupy rozróżnialne przez IMIĘ i OBRAMOWANIE, nie przez sam kolor:
    wydruk jest czytany w odcieniach szarości (REQ-009).
  */
  .plamka-grupy, .znacznik-kalendarza {
    background: #fff !important;
    border: 2px solid #000;
  }
  .grupa-2 { border-style: dashed; }
  .grupa-3 { border-style: dotted; }

  a { color: #000; text-decoration: none; }
}

/*
  STEROWANIE STOPNIEM PISMA NA EKRANIE PIEŚNI (design/PiosenkaTel.dc.html).

  Guziki mają 44 px także wtedy, gdy tekst jest powiększony do 200% — cel
  dotykowy to wielkość palca, nie wielkość pisma, i skalowanie go razem
  z tekstem zabrałoby miejsce samej pieśni.
*/
.rozmiar-tekstu {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}
.rozmiar-tekstu .stopien {
  font-size: 0.75rem;
  color: var(--tekst-slaby);
  min-width: 5.5ch;
  text-align: right;
}
.rozmiar-tekstu a,
.rozmiar-tekstu span.wylaczony {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  padding: 0;
  border: 1px solid var(--obwodka);
  background: var(--powierzchnia);
  font-weight: 700;
  text-decoration: none;
  color: var(--tekst);
}
/*
  KRANIEC DRABINY jest WYSZARZONY, a nie usunięty — inaczej para guzików
  zmieniałaby szerokość przy dojściu do 200% i drugi guzik przeskakiwałby pod
  palcem. Zostaje właśnie po to, żeby nic się nie przesunęło.

  Drugie takie miejsce to „Nowa ankieta" u osoby bez żadnej grupy (niżej,
  zgłoszenie #343) — i oba mają wspólne uzasadnienie: element zostaje na
  ekranie WYŁĄCZNIE wtedy, gdy jego nieaktywność jest stanem przejściowym,
  a nie odmową po roli. Odmowa po roli nie zostawia po sobie śladu (REQ-025).
*/
.rozmiar-tekstu span.wylaczony { color: var(--tekst-slaby); opacity: 0.45; }

.czynnosci-filtra { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }

/*
  CZYNNOŚCI ANKIETY ZAPLANOWANEJ — „Zmień pytania" i „Usuń" (zgłoszenie #341).
  Zawijanie, bo na 390 px dwa guziki obok siebie razem z paddingiem Bulmy nie
  mieszczą się w wierszu, a wypchnięta strona to usterka, której pomiar
  szerokości nie łapie, gdy tekst da się złamać.
*/
.czynnosci-zaplanowanej { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }

/*
  ZAKŁADKI stron automatycznych (REQ-049). Kształt ten sam co warianty
  pieśni — bo to jest ta sama czynność: przełączenie widoku tego samego
  zbioru. Dwa różne kształty dla jednej czynności każą uczyć się dwa razy.
*/
.zakladki { display: flex; align-items: center; gap: 4px; margin: 0 0 16px; flex-wrap: wrap; }
.zakladki a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--obwodka);
  border-radius: 8px;
  color: var(--tekst-slaby);
  text-decoration: none;
}
.zakladki a.aktywna {
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  border-color: var(--glowny);
  font-weight: 600;
}
.miejsce-druku { margin: 0 0 12px; }

/*
  STEROWANIE PLAYLISTĄ — cele dotykowe większe od zwyczajowych 44 px, bo
  naciska się je w półmroku, jedną ręką, nie patrząc na ekran.
*/
.prowadzenie .przesuniecie button { min-height: 56px; padding: 0 22px; font-size: 1rem; }

/*
  EKRAN RÓL — konto zwinięte do jednego wiersza. Osiem uprawnień razy
  dwadzieścia kont to sto sześćdziesiąt kontrolek; rozwinięte naraz nie dają
  się przeczytać, a szukanie idzie po osobie, nie po uprawnieniu.
*/
.konto { border: 1px solid var(--obwodka); border-radius: 12px; margin-bottom: 8px; background: var(--powierzchnia); }
.konto > summary { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 12px; cursor: pointer; flex-wrap: wrap; }
.uprawnienia { list-style: none; margin: 0; padding: 4px 12px 12px; }
.uprawnienia li { display: flex; align-items: center; gap: 10px; justify-content: space-between; min-height: 44px; border-top: 1px solid var(--obwodka); }
.uprawnienia button { min-height: 36px; }
/* Stan jest KOLOREM I SŁOWEM (reguła 2 design/README.md) — „nadane" stoi w treści guzika. */
.uprawnienia button.nadane { background: var(--glowny-jasny); color: var(--glowny-ciemny); border-color: var(--glowny); }

/*
  ZAKŁADKA „ROLE" — WIDOK ODWRÓCONY (issue #115). Lista ról, przy każdej opis
  i wykaz osób.

  Wykaz osób jest OWIJANY, a nie ustawiony w kolumnę o szerokości z treści:
  kolumna `max-content` obok tekstu łamie na telefonie nazwisko po jednej
  literze, a szerokość strony przy tym się nie zmienia, więc pomiar tego nie
  łapie. To był w tym projekcie ten sam błąd trzy razy na infoekranie.
*/
.lista-rol { list-style: none; margin: 0; padding: 0; }
.pozycja-roli {
  border: 1px solid var(--obwodka);
  border-radius: 12px;
  margin-bottom: 8px;
  padding: 10px 12px;
  background: var(--powierzchnia);
}
.pozycja-roli .title { margin-bottom: 2px; }
.rodzaj-roli { margin-bottom: 4px; }
.opis-roli { margin-bottom: 6px; }
.wykaz-roli { list-style: none; margin: 0; padding: 0; }
.wykaz-roli li {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 44px;
  border-top: 1px solid var(--obwodka);
}
.wykaz-roli.zwiniety li { min-height: 0; border-top: 0; }
/* Kontekst niesie SŁOWO („diakonia Muzyczna"), nie sam kolor — reguła 2 makiet. */
.kontekst-roli {
  padding: 2px 8px;
  border-radius: 8px;
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  font-weight: 600;
}
.pusta-rola { margin: 0; }
.gdzie-nadawana { margin-bottom: 6px; }
.notification.gdzie-nadawana { padding: 8px 12px; }
.powrot-do-rol { display: inline-flex; align-items: center; min-height: 44px; margin-bottom: 8px; }

/*
  ROLE NA PROFILU I EKRAN „ROLE" WŁAŚCICIELA PROFILU (zgłoszenie #129).

  Wiersz ról jest LISTĄ, a nie tekstem rozdzielonym przecinkami, bo od #129
  każda pozycja niesie DWIE rzeczy: nazwę roli i kontekst, w którym rola
  obowiązuje. Przecinki między nimi dawały na telefonie ciąg, w którym nie
  było widać, gdzie kończy się jedna rola, a zaczyna druga.

  Pozycje OWIJAJĄ SIĘ (`flex-wrap`) — nazwa roli z kontekstem to najdłuższy
  napis na tym ekranie („Animator grupy formacyjnej" + „grupa formacyjna
  Rodziny z Woli"), a bez owijania rozpychał stronę w poziomie na 390 px.
*/
.role-w-profilu { list-style: none; margin: 0; padding: 0; }
.role-w-profilu li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  min-height: 28px;
}
.odnosnik-rol { display: inline-flex; align-items: center; min-height: 44px; }

/* Guziki sekcji „Twoje konto": cel dotykowy 44 px, akcja główna 56 px
   (design/README.md, reguła 1). „Role" jest guzikiem zwykłym — dwie akcje
   główne obok siebie każą wybierać, zamiast prowadzić. */
.wlasne-konto .button { min-height: 44px; }
.wlasne-konto .button.glowna { min-height: 56px; }
.dodaj-do-roli { margin-top: 12px; }
.dodaj-do-roli .select, .dodaj-do-roli select { max-width: 100%; }
.zakladki-rol { margin-bottom: 12px; }

/*
  OBSADA GRUPY DZIECI. Wiersz opiekuna niesie imię, rolę SŁOWEM i czynności —
  bez ikon bez podpisu: przy obsadzie chodzi o odpowiedzialność za dzieci
  i pomyłka „usuń" kontra „ustaw jako głównego" ma inną cenę niż zwykle.
*/
.obsada { margin: 0 0 12px; }
.lista-obsady { list-style: none; margin: 0 0 8px; padding: 0; }
.lista-obsady li { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-height: 44px; }
.rola-obsady {
  padding: 2px 8px;
  border-radius: 8px;
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  font-size: 0.75rem;
  font-weight: 600;
}
.dodaj-opiekuna { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/*
  SUBSKRYPCJA KALENDARZA (REQ-040). Adres jest POŚWIADCZENIEM, więc jego
  blok wygląda inaczej niż zwykły akapit: ramka, krój o stałej szerokości
  i guzik kopiowania — żeby nie dało się go wziąć za zwykły odnośnik.
*/
.uwaga-tresc {
  border: 1px solid var(--obwodka);
  border-left: 4px solid var(--uwaga);
  border-radius: 12px;
  padding: 10px 14px;
  background: var(--powierzchnia);
}
.nowy-token { border: 1px solid var(--glowny); border-radius: 14px; padding: 12px 14px; margin: 0 0 16px; }
.adres-subskrypcji {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 10px;
  border: 1px solid var(--obwodka);
  border-radius: 12px;
  background: var(--tlo);
}
.adres-subskrypcji code { font-size: 0.8rem; word-break: break-all; }
.etykieta-adresu { font-size: 0.7rem; font-weight: 700; color: var(--tekst-slaby); text-transform: uppercase; }
.kopiuj { min-height: 32px; margin-left: auto; }
.lista-tokenow { list-style: none; margin: 0; padding: 0; }
.lista-tokenow li {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 44px;
  border-top: 1px solid var(--obwodka);
}
.nazwa-tokenu { font-weight: 600; }
.zakres-tokenu { border: 1px solid var(--obwodka); border-radius: 12px; margin: 12px 0; }
.zakres-tokenu legend { font-size: 0.8rem; color: var(--tekst-slaby); }

/* ---- stopka z wersją (REQ-053, REQ-054) --------------------------------- */

/*
  Stopka stoi na KAŻDEJ stronie i jest cicha: numer wersji to informacja,
  po którą się sięga, a nie treść, która ma zabierać uwagę. Odnośnik wygląda
  jak odnośnik dopiero pod kursorem i przy skupieniu klawiaturą — ale ma pełny
  rozmiar celu dotykowego, bo na telefonie klika się w niego palcem.
*/
.stopka {
  margin: 32px 0 0;
  padding: 12px 16px 20px;
  text-align: center;
}
.stopka .stopka-wersji {
  display: inline-block;
  min-height: 44px;
  line-height: 44px;
  padding: 0 12px;
  color: var(--tekst-slaby);
  font-size: 0.85rem;
  text-decoration: none;
}
a.stopka-wersji:hover, a.stopka-wersji:focus { text-decoration: underline; }

/* Sekcja instalacji — stany rozdzielone, każdy własnym zdaniem (REQ-054). */
.instalacja ol { margin: 8px 0 8px 20px; }
.instalacja button { min-height: 44px; }

/* ==========================================================================
   EKRAN PLAYLISTY W TRAKCIE ODTWARZANIA (REQ-046, issue #92)

   Na telefonie wszystko stoi w jednej kolumnie w kolejności czytania: co
   teraz, tekst pieśni, dopiero potem lista i sterowanie — bo prowadzący
   trzyma telefon w jednej ręce i patrzy w górę ekranu.

   Na komputerze makieta `PlaylistaKom` rysuje DWIE kolumny: lista po lewej,
   tekst bieżącej pieśni po prawej. Powód, dla którego nie zostawiamy jednej
   kolumny także tu: na 1440 px lista zajmuje ćwierć szerokości, a tekst
   zepchnięty pod nią wymaga przewijania przy każdej zmianie pieśni — czyli
   dokładnie tego, czego podgląd ma oszczędzić.
   ========================================================================== */
.ekran.playlista .podglad-piesni { overflow-x: auto; }

@media (min-width: 1024px) {
  /*
    Dwie kolumny bez nazywania obszarów: podgląd zajmuje prawą kolumnę na
    całą wysokość, WSZYSTKO POZOSTAŁE idzie do lewej w swojej kolejności.
    Nazwane obszary wymagałyby wyliczenia każdego dziecka ekranu z osobna,
    a ekran ma ich różną liczbę zależnie od uprawnień patrzącego — wtedy
    element bez przypisanego obszaru trafiałby, gdzie popadnie.
  */
  .ekran.playlista.z-podgladem {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
    gap: 0 22px;
  }
  .ekran.playlista.z-podgladem > * { grid-column: 1; }
  .ekran.playlista.z-podgladem > #oob-playlista-podglad {
    grid-column: 2;
    grid-row: 2 / span 50;
    position: sticky;
    top: 12px;
  }
}

/*
  ZAPROSZENIA AUTOMATYCZNE (REQ-062, FEAT-054, issue #104).

  Ekran bez sesji, więc bez paska nawigacji i bez menu — treść dostaje własną
  szerokość maksymalną, tak samo jak ekran logowania. Bez niej lista
  osiemdziesięciu pozycji rozciągałaby się na całą szerokość laptopa,
  a wiersz z jednym słowem („Michał K.") i guzikiem po drugiej stronie ekranu
  jest wierszem, w którym nie widać, co z czym się łączy.

  Wiersz jest elementem `details`, tak samo jak konto na ekranie użytkowników
  — i celowo ma tę samą wysokość celu dotykowego (48 px, REQ-016): to jest
  ta sama czynność w innym miejscu.
*/
.ekran.zaproszenie { max-width: 560px; margin: 0 auto; }

.lista-zaproszenia { list-style: none; margin: 12px 0 0; padding: 0; }

.pozycja-zaproszenia {
  border: 1px solid var(--obwodka);
  border-radius: 12px;
  margin-bottom: 8px;
  background: var(--powierzchnia);
}
.pozycja-zaproszenia > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: space-between;
  min-height: 48px;
  padding: 0 12px;
  cursor: pointer;
  flex-wrap: wrap;
}
.pozycja-zaproszenia .podpis { font-weight: 600; }
/* Dwie osoby o identycznym imieniu i nazwisku (REQ-062): podpis ich nie
   rozróżnia i rozróżnić nie może, więc pozycja mówi o tym słowem. Zdanie łamie
   się na wąskim ekranie razem z całym wierszem — `flex-wrap` stoi wyżej. */
.pozycja-zaproszenia .podpis-dwuznaczny {
  white-space: normal;
  height: auto;
  text-align: left;
}
/* Guzik „To ja!" jest w środku `summary`, więc klika go samo otwarcie wiersza
   — nie jest osobną kontrolką i nie może udawać, że nią jest. */
.pozycja-zaproszenia .to-ja { pointer-events: none; }
.pozycja-zaproszenia .formularz { padding: 4px 12px 14px; }
.pozycja-zaproszenia .formularz .input { min-height: 48px; }

.czekam-na-potwierdzenie { margin: 12px 0; }
/* Login wyliczył system, a nie człowiek — stąd krój o stałej szerokości:
   ma się dać przepisać z ekranu bez zgadywania, czy to „l" czy „1". */
.login-zgloszenia { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

.klucz-zgloszenia { margin: 8px 0; }

/*
  ZGŁOSZENIA PO KONTO na ekranie „Użytkownicy i role". Sekcja stoi NAD listą
  kont, bo jest czynnością do zrobienia, a lista kont jest do przeglądania.
*/
.zgloszenia-po-konto { margin-bottom: 18px; }
.lista-zgloszen { list-style: none; margin: 8px 0 0; padding: 0; }
.lista-zgloszen .zgloszenie {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 48px;
  padding: 6px 12px;
  border: 1px solid var(--obwodka);
  border-radius: 12px;
  margin-bottom: 8px;
  background: var(--powierzchnia);
}
.lista-zgloszen .osoba { font-weight: 600; }

/* ---- kod QR z adresem strony zgłoszeń (REQ-062, issue #140) --------------- */

/*
  PEŁNY EKRAN OTWIERA I ZAMYKA CSS PRZEZ `:target`, BEZ JAVASCRIPTU (REQ-001):
  odnośnik z sufiksem `#sek-qr-zaproszenia`/`#qr-zaproszenia` włącza regułę
  `:target`, odnośnik „Zamknij" (goły `href="#"`) ją wyłącza. Zamykanie MUSI
  działać bez skryptu — warstwa, której nie da się zamknąć, blokowałaby całą
  aplikację. To odstępstwo od trybu pełnoekranowego pieśni jest celowe: pieśń
  wchodzi w ten tryb z przycisku i z powrotem z przycisku, a tu droga jest
  zwykłym odnośnikiem i nie wolno jej zależeć od JavaScriptu.
*/
.qr-przycisk {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--tekst);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.95rem;
}
.qr-przycisk img { flex-shrink: 0; }
.qr-obszar { margin: 12px 0; }
.qr-obszar .pomocniczy { margin: 4px 0 0; }

.qr-pelny-ekran {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.qr-pelny-ekran:target { display: flex; }
.qr-tlo {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  cursor: default;
}
.qr-karta {
  position: relative;
  max-width: 420px;
  width: 100%;
  background: var(--powierzchnia);
  color: var(--tekst);
  border: 1px solid var(--obwodka);
  border-radius: 14px;
  padding: 20px;
  text-align: center;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
}
.qr-obraz {
  width: 100%;
  max-width: 320px;
  height: auto;
  margin: 8px auto 12px;
  border: 1px solid var(--obwodka);
  border-radius: 10px;
}
.qr-karta code {
  word-break: break-all;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.qr-karta .zamknij { margin-top: 14px; }

/* ==========================================================================
   EKRAN BRAK KOPII (REQ-064, etap a, issue #160)

   Powłoka całkowicie offline, którą oddaje pracownik usługi przy nawigacji
   bez sieci. Serwowana przez `web/static/brak-kopii.html`; bez CELNEGO
   `<style>` i `onclick` — polityka bezpieczeństwa nie dopuszcza
   `unsafe-inline` (REQ-001), a pre-cache nie podstawia treści serwerem,
   więc ten ekran musi działać samym arkuszem.
   ========================================================================== */

/* Samodzielna strona: znak u góry jak w pasku aplikacji, komunikat na
   środku, niczego nie trzeba przewijać. */
.brak-kopii { min-height: 100vh; display: flex; flex-direction: column; }

.brak-kopii-tresc {
  margin: auto;
  padding: 0 16px;
  text-align: center;
  max-width: 30rem;
}

.brak-kopii-tytul { font-size: 1.35rem; line-height: 1.35; margin: 0 0 10px; }

.brak-kopii-opis { color: var(--tekst-slaby); margin: 0 0 20px; }

/* Droga powrotu na pulpit to jedna wyraźna kontrola (cel dotykowy 44 px,
   design/README.md, reguła 1) — to jest JEDYNA czynność, jaką ten ekran
   oferuje, i ma jej nie umykać. */
.brak-kopii .brak-kopii-powrot { min-height: 44px; }

/* ==========================================================================
   ADNOTACJA POCHODZENIA Z PAMIĘCI (REQ-064, etap b+c, issue #161/#162)

   Wstrzykuje ją pracownik usługi (`sw.js`) do strony wydanej z pamięci
   konta. Element jest wstrzykiwany DO DOKUMENTU, więc zdanie o pochodzeniu
   stoi także wtedy, gdy łączność już wróciła, a ekran nie został odświeżony
   (REQ-064). Barwa jest INFORMACYJNA, nie alarmowa: „dane z pamięci telefonu"
   to fakt, nie błąd — a mocny napis przy każdym otwarciu z pamięci wykrzykiwałby
   to, co ma być spokojną informacją.
   ========================================================================== */

.pochodzenie-pamieci {
  background: var(--glowny-jasny);
  color: var(--glowny-ciemny);
  border-bottom: 1px solid var(--obwodka);
  padding: 6px 12px;
  text-align: center;
  font-size: 0.85rem;
  line-height: 1.4;
}

.pochodzenie-pamieci strong { font-weight: 600; }
