/* DOM — system tokenów: schematy × (light|dark|auto), skalowanie rem,
   animacje wyłącznie transform/opacity (kompozycja GPU). */

/* ---------- 1. Tokeny: schematy kolorów ---------- */
:root {
  color-scheme: light dark;

  /* Grafit (domyślny): neutralna szarość + indygo. */
  --bg:      light-dark(#f4f4f6, #131316);
  --bg-2:    light-dark(#ffffff, #1c1c21);
  --bg-3:    light-dark(#ececf0, #26262d);
  --tekst:   light-dark(#1b1b20, #ececf1);
  --tekst-2: light-dark(#5c5c66, #9d9da8);
  --linia:   light-dark(#dcdce2, #34343c);
  --akcent:       light-dark(#4f46e5, #818cf8);
  --akcent-tekst: light-dark(#ffffff, #131316);
  --akcent-tlo:   light-dark(#e7e6fb, #2a2a45);
  --ok:    light-dark(#15803d, #4ade80);
  --blad:  light-dark(#b91c1c, #f87171);
  --uwaga: light-dark(#b45309, #fbbf24);
  --orb-a: #6366f1; --orb-b: #a855f7;

  --promien: .75rem;
  --cien:   0 1px 2px rgb(0 0 0 / .06), 0 4px 16px rgb(0 0 0 / .06);
  --cien-2: 0 2px 4px rgb(0 0 0 / .08), 0 12px 32px rgb(0 0 0 / .12);
  --przejscie: 180ms cubic-bezier(.2, .8, .2, 1);
  /* Minimalny cel dotykowy — design/README: pozycje list i przełączniki
     ≥44 px NIEZALEŻNIE od trybu (audyt 390×844 mierzy tryb domyślny, więc
     próg musi być w wartości bazowej, nie dopiero w trybie „dotyk"). */
  --dotyk: 2.75rem;        /* 44 px */
  --dotyk-glowny: 3.5rem;  /* 56 px — akcja główna (design/README: 56–64 px) */
}
:root[data-scheme="ocean"] {
  --bg:      light-dark(#eef4f6, #0c1a20);
  --bg-2:    light-dark(#ffffff, #12242c);
  --bg-3:    light-dark(#e2edf1, #1a323c);
  --tekst:   light-dark(#122a33, #dbeef5);
  --tekst-2: light-dark(#4e6d79, #8fb0bc);
  --linia:   light-dark(#d3e2e8, #24404c);
  --akcent:       light-dark(#0e7490, #22d3ee);
  --akcent-tekst: light-dark(#ffffff, #0c1a20);
  --akcent-tlo:   light-dark(#d9f0f6, #123a46);
  --orb-a: #06b6d4; --orb-b: #3b82f6;
}
:root[data-scheme="zorza"] {
  --bg:      light-dark(#f2f2f8, #12101d);
  --bg-2:    light-dark(#ffffff, #1a1729);
  --bg-3:    light-dark(#e9e7f3, #262138);
  --tekst:   light-dark(#221d33, #eae6f7);
  --tekst-2: light-dark(#635c7d, #a49bc4);
  --linia:   light-dark(#dcd8ea, #362f4e);
  --akcent:       light-dark(#7c3aed, #34d399);
  --akcent-tekst: light-dark(#ffffff, #12101d);
  --akcent-tlo:   light-dark(#ece3fb, #223a35);
  --orb-a: #8b5cf6; --orb-b: #34d399;
}
:root[data-scheme="lesny"] {
  --bg:      light-dark(#f2f6f1, #101812);
  --bg-2:    light-dark(#ffffff, #18211a);
  --bg-3:    light-dark(#e6ede4, #212b23);
  --tekst:   light-dark(#16201a, #e6f0e8);
  --tekst-2: light-dark(#54685a, #9db3a3);
  --linia:   light-dark(#d6e0d4, #2e3a31);
  --akcent:       light-dark(#2f7a4f, #5cc98a);
  --akcent-tekst: light-dark(#ffffff, #101812);
  --akcent-tlo:   light-dark(#dcecdf, #1d3a29);
  --ok:    light-dark(#15803d, #4ade80);
  --blad:  light-dark(#b91c1c, #f87171);
  --uwaga: light-dark(#a15c00, #fbbf24);
  --orb-a: #2f7a4f; --orb-b: #7fc9a0;
}
:root[data-scheme="malina"] {
  --bg:      light-dark(#faf2f5, #180f13);
  --bg-2:    light-dark(#ffffff, #21151a);
  --bg-3:    light-dark(#f2e3e9, #2c1c23);
  --tekst:   light-dark(#24131a, #f4e6ec);
  --tekst-2: light-dark(#6d5460, #bb9aa7);
  --linia:   light-dark(#e6d2da, #3a2730);
  --akcent:       light-dark(#a8325c, #f28ab0);
  --akcent-tekst: light-dark(#ffffff, #180f13);
  --akcent-tlo:   light-dark(#f7dee8, #3d1d2b);
  --ok:    light-dark(#15803d, #4ade80);
  --blad:  light-dark(#b91c1c, #f87171);
  --uwaga: light-dark(#a15c00, #fbbf24);
  --orb-a: #a8325c; --orb-b: #f28ab0;
}
:root[data-scheme="bursztyn"] {
  --bg:      light-dark(#f8f4ec, #1b150d);
  --bg-2:    light-dark(#fffdf8, #241c12);
  --bg-3:    light-dark(#f1e9da, #322718);
  --tekst:   light-dark(#2b2113, #f3e9d8);
  --tekst-2: light-dark(#77664c, #bda88a);
  --linia:   light-dark(#e6dcc8, #443623);
  --akcent:       light-dark(#b45309, #fbbf24);
  --akcent-tekst: light-dark(#ffffff, #1b150d);
  --akcent-tlo:   light-dark(#fbe9d2, #453214);
  --orb-a: #f59e0b; --orb-b: #ef4444;
}
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/* ---------- 2. Baza ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; min-height: 100dvh;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", sans-serif;
  font-size: 1rem; line-height: 1.55;
  background: var(--bg); color: var(--tekst);
  padding-bottom: env(safe-area-inset-bottom);
}
code { background: var(--bg-3); border-radius: .3em; padding: .05em .35em; font-size: .92em; }
a { color: var(--akcent); }
.male { font-size: .8em; } .pusto { color: var(--tekst-2); font-style: italic; }
.ukryty { display: none !important; }
.inline { display: inline; }
.mb-1 { margin-bottom: 1rem; }
.opis { color: var(--tekst-2); }
.blad { color: var(--blad); }
.wiersz { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; }

/* ---------- 3. Dekoracja tła (orbs — czysty kompozytor) ---------- */
.tlo-dekor { position: fixed; inset: 0; overflow: hidden; z-index: -1; pointer-events: none; }
.orb {
  position: absolute; width: 42vmax; height: 42vmax; border-radius: 50%;
  filter: blur(90px); opacity: .16; will-change: transform;
}
.orb-a { background: var(--orb-a); top: -18vmax; right: -12vmax; animation: dryf 26s ease-in-out infinite alternate; }
.orb-b { background: var(--orb-b); bottom: -20vmax; left: -14vmax; animation: dryf 32s ease-in-out infinite alternate-reverse; }
@keyframes dryf {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-6vmax, 5vmax, 0) scale(1.15); }
}

/* ---------- 4. Nagłówek i nawigacja ---------- */
.naglowek {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: 1rem;
  padding: .55rem clamp(.8rem, 3vw, 1.6rem);
  padding-top: max(.55rem, env(safe-area-inset-top)); /* iOS notch */
  background: color-mix(in srgb, var(--bg-2) 82%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--linia);
}
/* min-height: logo z nazwą jest linkiem do pulpitu, więc jest celem dotykowym. */
.marka { display: flex; align-items: center; gap: .5rem; min-height: var(--dotyk); font-weight: 700; letter-spacing: .04em; color: var(--tekst); text-decoration: none; }
.marka-logo { width: 1.6rem; height: 1.6rem; }
.nawigacja-obszar { display: none; position: relative; align-items: center; gap: .3rem; margin-inline: auto; min-width: 0; flex: 1 1 auto; justify-content: center; }
.nawigacja { display: flex; gap: .25rem; overflow: hidden; min-width: 0; flex: 1 1 auto; }
/* Tryb "przewijanie" — alternatywa dla chowania do menu hamburger: pasek
   zakładek przewijalny poziomo zamiast ukrywania nadmiaru do menu. */
.nawigacja[data-overflow="scroll"] { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; }
.nawigacja a {
  padding: .4rem .8rem; border-radius: .55rem; text-decoration: none; color: var(--tekst-2);
  white-space: nowrap; flex-shrink: 0; border-bottom: 2px solid transparent;
  transition: color var(--przejscie), background var(--przejscie), border-color var(--przejscie);
}
.nawigacja a:hover { color: var(--tekst); background: var(--bg-3); }
/* border-bottom niezależny od tła — patrz komentarz przy .sidebar-linki. */
.nawigacja a[aria-current="page"] { color: var(--akcent); background: var(--akcent-tlo); font-weight: 600; border-bottom-color: var(--akcent); }
#nav-wiecej-btn { flex-shrink: 0; position: relative; }
/* Plakietka schowana w menu „☰" — liczba na przycisku (zgłoszenie #77).
   Kropka `ma-aktywna` mówi „aktywna strona jest w menu"; ta plakietka mówi
   „w menu jest N nieprzeczytanych". Dwa różne fakty, więc dwa różne znaki:
   kropka w akcencie w prawym górnym rogu, liczba nad prawą krawędzią.
   Liczba, nie kropka, bo „masz coś" bez liczby zmusza do rozwinięcia menu,
   żeby dowiedzieć się, ile — czyli nie oszczędza ani jednego kliknięcia. */
#nav-wiecej-btn.ma-plakietke::before {
  content: attr(data-plakietka);
  position: absolute; top: -.15rem; right: -.35rem; z-index: 1;
  min-width: 1.05rem; padding: 0 .2rem;
  border-radius: 999px; background: var(--akcent); color: var(--akcent-tekst);
  font-size: .65rem; line-height: 1.05rem; font-weight: 700; text-align: center;
}
/* Gdy aktywna strona TEŻ jest w menu, kropka ustępuje liczbie — dwa znaki
   w jednym narożniku nakładałyby się na siebie. */
#nav-wiecej-btn.ma-plakietke.ma-aktywna::after { display: none; }

#nav-wiecej-btn.ma-aktywna::after {
  content: ""; position: absolute; top: .3rem; right: .3rem;
  width: .4rem; height: .4rem; border-radius: 50%; background: var(--akcent);
}
.nav-wiecej-menu {
  position: absolute; top: 100%; left: 0; margin-top: .3rem; z-index: 20;
  display: flex; flex-direction: column; min-width: 10rem;
  background: var(--bg-2); border: 1px solid var(--linia); border-radius: var(--promien);
  box-shadow: var(--cien-2); padding: .35rem;
}
.nav-wiecej-menu a {
  padding: .5rem .8rem; border-radius: .45rem; text-decoration: none; color: var(--tekst-2);
  white-space: nowrap; border-left: 3px solid transparent;
  transition: color var(--przejscie), background var(--przejscie), border-color var(--przejscie);
}
.nav-wiecej-menu a:hover { color: var(--tekst); background: var(--bg-3); }
.nav-wiecej-menu a[aria-current="page"] { color: var(--akcent); background: var(--akcent-tlo); font-weight: 600; border-left-color: var(--akcent); }
.naglowek-akcje { display: flex; align-items: center; gap: .5rem; margin-left: auto; flex-shrink: 0; }

.kropka { width: .65rem; height: .65rem; border-radius: 50%; background: var(--uwaga); transition: background var(--przejscie); }
.kropka.online { background: var(--ok); } .kropka.offline { background: var(--blad); }
.badge {
  border: none; font: inherit; font-size: .8rem; border-radius: 999px;
  background: var(--bg-3); color: var(--tekst-2); padding: .15rem .6rem;
}
.badge.akcent { background: var(--akcent); color: var(--akcent-tekst); cursor: pointer; }
/* Cztery generyczne klasy szkieletu (`.badge-ok/-degraded/-error/-info`)
   USUNIĘTO w zgłoszeniu #90 razem z funkcją `web.BadgeKlasa`, która je
   nadawała: nie odwoływał się do nich ani jeden szablon ani plik JS. Nowy
   enum stanu dostaje własną rodzinę `badge-<dziedzina>-*` — wzór poniżej. */

/* --- Plakietki STANU ZADANIA (REQ-013/REQ-015, zgłoszenie #51) ---
   Rodzina powstała osobno od czterech generycznych klas szkieletu (usuniętych
   w #90), bo generyczne nie znały ani jednego stanu zadania: wszystkie cztery
   stany wpadały w `default` → `badge-info`, więc plakietka miała JEDEN kolor
   niezależnie od stanu. Klasę wybiera `web.KlasaStanuZadania`
   (internal/web/badges.go); prefiks
   `badge-stan-` sprawdzony grepem przed nadaniem — `tablo-stan-*` (tablo,
   REQ-033) to inna rodzina i celowo się z tą nie styka.

   TRZY NOŚNIKI, NIE JEDEN. Kolor jest tu ostatni, nie pierwszy:
   1. SŁOWO — plakietka zawsze zawiera nazwę stanu („zadanie-stan-etykieta");
   2. ZNAK — ○ ● ✓ ⚠ ✕, rozpoznawalny bez koloru i bez czytania;
   3. kolor — dopiero jako wzmocnienie.
   Nie jest to ozdoba: schemat „Bursztyn" ma `--akcent` i `--uwaga` w tym samym
   odcieniu (oba amber), a „Zorza" w trybie ciemnym ma `--akcent` i `--ok`
   w dwóch zieleniach — bez znaku i słowa trzy stany zlewałyby się w jednym
   schemacie w jeden.

   KONTRAST (pułapka 12 w docs/HANDOFF.md — ta pomyłka zdarzyła się tu trzy
   razy). Wzór generyczny wyżej („kolor 18% na bg-3, tekst w pełnym kolorze")
   NIE przechodzi 4.5:1 w trybie jasnym — policzone dla wszystkich sześciu
   schematów wychodzi 3.2–4.1, a plakietka ma .8rem, więc nie liczy się jako
   duży tekst. Dlatego tekst jest tu mieszany z `--tekst`: w trybie jasnym
   ciemnieje, w ciemnym jaśnieje, czyli ODJEŻDŻA od tła w obie strony naraz.
   Przy 65% udziału koloru najgorsza para z 6 schematów × 2 tryby daje 4.95.
   Zmieniasz procent — przelicz, a nie „na oko": tło i tekst poruszają się
   wtedy w tę samą stronę. */
.badge-stan-oczekuje  { background: var(--bg-3); color: var(--tekst-2); }
.badge-stan-zgloszone {
  background: color-mix(in srgb, var(--akcent) 18%, var(--bg-3));
  color: color-mix(in srgb, var(--akcent) 65%, var(--tekst));
}
.badge-stan-przyjete {
  background: color-mix(in srgb, var(--ok) 18%, var(--bg-3));
  color: color-mix(in srgb, var(--ok) 65%, var(--tekst));
}
.badge-stan-uwaga {
  background: color-mix(in srgb, var(--uwaga) 18%, var(--bg-3));
  color: color-mix(in srgb, var(--uwaga) 65%, var(--tekst));
}
/* Anulowane jest WYGASZONE, nie kolorowe: zdejmujemy tło (a nie tylko
   przygaszamy tekst — pułapka 12), zostawiamy przerywaną obwódkę i przekreślone
   słowo. Przekreślenie jest tu czwartym nośnikiem i jedynym, który działa nawet
   na wydruku czarno-białym. */
.badge-stan-anulowane {
  background: none; box-shadow: inset 0 0 0 1px var(--linia);
  color: var(--tekst-2); text-decoration: line-through;
}
.badge-stan-oczekuje::before  { content: "○"; }
.badge-stan-zgloszone::before { content: "●"; }
.badge-stan-przyjete::before  { content: "✓"; }
.badge-stan-uwaga::before     { content: "⚠"; }
.badge-stan-anulowane::before { content: "✕"; }
.badge-stan-oczekuje::before,
.badge-stan-zgloszone::before,
.badge-stan-przyjete::before,
.badge-stan-uwaga::before,
.badge-stan-anulowane::before { margin-inline-end: .3rem; }
.przycisk-ikona {
  border: none; background: transparent; color: var(--tekst-2); font-size: 1.1rem;
  width: var(--dotyk); height: var(--dotyk); border-radius: .55rem; cursor: pointer;
  transition: background var(--przejscie), color var(--przejscie), transform var(--przejscie);
}
.przycisk-ikona:hover { background: var(--bg-3); color: var(--tekst); }
.przycisk-ikona:active { transform: scale(.92); }
/* Ikona wylogowania w nagłówku (#107) — SVG, nie znak. Rozmiar w `rem`, żeby
   szedł za skalowaniem czcionki (REQ-025) tak samo jak `font-size` samego
   `.przycisk-ikona`; `display: block` + `margin: auto` zamiast domyślnego
   `inline`, bo inline SVG siadałby na linii bazowej i stał niżej od środka
   przycisku. Kolor bierze się z `currentColor` przycisku, więc motyw ciemny
   nie potrzebuje własnej reguły. Cel dotykowy to nadal 44 px `--dotyk`
   przycisku, nie rozmiar rysunku. */
.ikona-wyloguj { display: block; width: 1.15rem; height: 1.15rem; margin: 0 auto; }
/* inline-flex + min-height: .przycisk-mikro bywa <a> (strzałki okresu i
   „Drukuj A4" na /plan), a link jest domyślnie inline — wtedy wysokość
   wynikałaby z samego tekstu (24 px) i cel był poniżej progu 44 px. */
.przycisk-mikro {
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; color: var(--tekst-2); font: inherit; font-size: .75rem;
  padding: .3rem .6rem; min-height: var(--dotyk); min-width: var(--dotyk);
  border-radius: .55rem; cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: background var(--przejscie), color var(--przejscie);
}
.przycisk-mikro:hover { background: var(--bg-3); color: var(--tekst); }
.zakres-szybki-pasek { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .6rem; }
.zakres-szybki-pasek .przycisk-mikro { border: 1px solid var(--bg-3); }

/* Modal — wzorzec natywnego <dialog> (SREQ-033). */
.modal {
  background: var(--bg-2); color: var(--tekst); border: 1px solid var(--linia);
  border-radius: var(--promien); box-shadow: var(--cien-2); padding: 1.25rem;
  max-width: min(30rem, calc(100vw - 2rem));
}
.modal::backdrop { background: rgb(0 0 0 / .45); }
.modal h2 { margin-top: 0; }

/* Wykresy — chart-mini.js (SREQ-028). */
.wykres-legenda { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .5rem; }
.wykres-legenda-poz {
  display: inline-flex; align-items: center; gap: .35rem; border: 1px solid var(--linia);
  background: var(--bg-2); color: var(--tekst); font: inherit; font-size: .8rem;
  padding: .25rem .55rem; border-radius: 999px; cursor: pointer;
}
.wykres-legenda-kolor { width: .6rem; height: .6rem; border-radius: 50%; display: inline-block; }
.wykres-legenda-ukryta { opacity: .4; text-decoration: line-through; }
.wykres-kontener canvas { width: 100%; height: 16rem; display: block; }

/* Zakładki (SFEAT-017) — pasek nad tabelą/wykresem, filtrujący/przełączający
   ten sam widok, nie osobne równoległe strony. */
/* flex-wrap: zakładka z plakietką licznika („Zaległe 1") jest szersza od
   zakładki bez niej, więc na 320 px trzy zakładki nie mieszczą się w wierszu
   i BEZ zawijania wypychały ostatnią („Rodzina") poza ekran razem z całą
   stroną (zgłoszenie #89). Zawijanie, nie `overflow-x: auto`: przewijany pasek
   ukrywa zakładkę zamiast ją pokazać, a to ona jest tu nawigacją. */
.zakladki { display: flex; flex-wrap: wrap; gap: .3rem; margin-bottom: .8rem; border-bottom: 1px solid var(--linia); }
.zakladki a {
  display: inline-flex; align-items: center; justify-content: center;
  padding: .5rem .9rem; min-height: var(--dotyk); text-decoration: none; color: var(--tekst-2);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.zakladki a:hover { color: var(--tekst); }
.zakladki a[aria-current="page"] { color: var(--akcent); border-bottom-color: var(--akcent); font-weight: 600; }

/* Przełącznik zakresu planu (plan.tmpl: <nav class="zakres"> Dzień/Tydzień/
   Miesiąc). Bez własnej reguły linki zostawały inline i miały 17 px wysokości
   — poniżej progu 44 px. Warianty (wariant-osdnia.css) nadpisują ten wygląd
   swoim, bardziej szczegółowym selektorem. */
.zakres { display: flex; flex-wrap: wrap; gap: .3rem; }
.zakres a {
  display: inline-flex; align-items: center; justify-content: center;
  padding: .4rem .9rem; min-height: var(--dotyk); border-radius: .55rem;
  text-decoration: none; color: var(--tekst-2); background: var(--bg-2);
  border: 1px solid var(--linia); white-space: nowrap;
  transition: color var(--przejscie), background var(--przejscie), border-color var(--przejscie);
}
.zakres a:hover { color: var(--tekst); background: var(--bg-3); }
.zakres a[aria-current="page"] { color: var(--akcent); background: var(--akcent-tlo); border-color: var(--akcent); font-weight: 600; }

.tabela th a { color: inherit; text-decoration: none; }
.tabela th a:hover { text-decoration: underline; }
/* KOLIZJA NAZW (zgłoszenie #44): `.paginacja` była zdefiniowana DWA RAZY w tym
   pliku — tu (pasek numerowanych stron demo-tabel) i w sekcji 22 (wspólny panel
   „Strona X z Y” + skok). Druga deklaracja wygrywała jako późniejsza, więc
   `gap: .3rem` i `margin-top` stąd były martwe, a pasek numerów dostawał
   `justify-content: space-between` i rozjeżdżał cyfry 1…7 na całą szerokość.
   Reguła bazowa jest teraz JEDNA i jedyna: wspólny panel szkieletu odszedł
   w 0.2.4 razem z partialem, a jego reguły `.paginacja-*` w 0.4.x
   (zgłoszenie #66), więc `.paginacja` obsługuje wyłącznie pasek demo-tabel. */
.paginacja { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: .6rem 0; }
.paginacja .przycisk-mikro { border: 1px solid var(--bg-3); }
.paginacja .przycisk-mikro.aktywna { background: var(--akcent); color: var(--akcent-tekst); border-color: var(--akcent); }

.awatar-nag {
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; text-decoration: none; border: 2px solid transparent;
  transition: border-color var(--przejscie); flex-shrink: 0;
}
.awatar-nag[aria-current="page"] { border-color: var(--akcent); }

/* ---------- Awatar (REQ-022): partial "awatar" wspólny dla nagłówka,
   profilu i wszystkich list z osobami. .awatar-mini/-male/-duzy to trzy
   rozmiary tej samej zaślepki; .awatar-tlo-N to osiem deterministycznych
   odcieni tła placeholdera (kolorAwatara w render.go), dokładanych TYLKO
   gdy nie ma własnego zdjęcia — zdjęcie samo w sobie już się różni. ---------- */
.awatar {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; overflow: hidden; flex-shrink: 0; font-weight: 700;
  vertical-align: middle; line-height: 1;
  /* OBWÓDKA jest tym, co odróżnia AWATAR od dwuliterowego prefiksu przed
     imieniem (zgłoszenie #57: właściciel produktu zobaczył „Pi Piotr" i nie
     odczytał tego jako awatara). Idzie przez inset box-shadow, nie border:
     border zabiera z pola 2×1 px, więc przy 1.6rem miniatury zauważalnie
     ścisnąłby litery, a przy zdjęciu wciągnąłby je pod obwódkę.
     `currentColor` trzyma obwódkę w kolorze skrótu, czyli automatycznie
     w odcieniu przypisanym danej osobie i automatycznie poprawnie w obu
     motywach — żadnej drugiej listy kolorów do zapomnienia. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 45%, transparent);
}
/* Zdjęcie wypełnia kółko w całości; obwódka zostaje nad nim, ale liczona z tła
   kółka, a nie z fotografii — dlatego dla zdjęcia ją wyciszamy do linii
   neutralnej: silna obwódka w kolorze losowym nad twarzą wygląda jak błąd. */
.awatar img { width: 100%; height: 100%; object-fit: cover; }
.awatar:has(img) { box-shadow: inset 0 0 0 1px var(--linia); }
/* Na listach awatar zawsze poprzedza imię jako tekst (REQ-022: towarzyszy,
   nie zastępuje) — mały odstęp, żeby nie sklejały się w jedno słowo. */
.awatar-mini, .awatar-duzy { margin-inline-end: .4rem; }
/* Skrót jest MNIEJSZY od kółka o połowę, nie o trzy czwarte: przy .65rem
   w kółku 1.6rem litery ginęły i całość czytała się jak kropka z szumem.
   Wartości trzymają proporcję ~0.45 średnicy dla dwóch liter. */
.awatar-mini { width: 1.6rem; height: 1.6rem; font-size: .72rem; }
.awatar-male { width: var(--dotyk); height: var(--dotyk); font-size: .95rem; }
.awatar-duzy { width: 5rem; height: 5rem; font-size: 2rem; }
/* Rozmiar TABLOWY (REQ-033): jedyna rzecz, którą tablo dokłada do wspólnego
   awatara — reszta (kółko, kolory, zdjęcie, anonimizacja) jest wspólna.
   Skaluje się `--tablo-skala`, tą samą pokrętką co cała typografia tabla, więc
   na telewizorze awatar rośnie razem z imieniem, a nie zostaje plamką. */
.awatar-tablo {
  width: calc(2.1rem * var(--tablo-skala)); height: calc(2.1rem * var(--tablo-skala));
  font-size: calc(.95rem * var(--tablo-skala));
  margin-inline-end: calc(.45rem * var(--tablo-skala));
}

/* Kolory zaślepki. Tła są jasne/ciemne w zależności od motywu, a napis ma
   kontrast co najmniej 5.4:1 do swojego tła w OBU motywach — sprawdzone
   liczbowo, nie na oko (pułapka 12 w docs/HANDOFF.md: ta sama pomyłka
   z kontrastem zdarzyła się w tym projekcie trzy razy). Wcześniejsze wartości
   dla motywu jasnego dawały 3.7–4.5:1, czyli poniżej minimum dla małego
   tekstu — przy skrócie w kółku 1.6rem to dokładnie ten przypadek. */
.awatar-tlo-0 { background: light-dark(#e7e6fb, #2a2a45); color: light-dark(#4338ca, #b4bcfb); }
.awatar-tlo-1 { background: light-dark(#fde3e3, #452323); color: light-dark(#a62f22, #f0a8a3); }
.awatar-tlo-2 { background: light-dark(#ddf1e6, #1d3a29); color: light-dark(#1a6b43, #7fd9a8); }
.awatar-tlo-3 { background: light-dark(#fdecd0, #402c12); color: light-dark(#8a4e07, #f2bd6c); }
.awatar-tlo-4 { background: light-dark(#dceefb, #12324a); color: light-dark(#0c5f95, #7fc4ef); }
.awatar-tlo-5 { background: light-dark(#f3ddf6, #3a1f42); color: light-dark(#7f2890, #e0a0ec); }
.awatar-tlo-6 { background: light-dark(#e4ecd7, #29331d); color: light-dark(#48611a, #b7d17a); }
.awatar-tlo-7 { background: light-dark(#fbdde8, #3d1f2b); color: light-dark(#a82259, #f2a0c2); }

.pasek-offline { background: var(--uwaga); color: #1b150d; text-align: center; padding: .35rem .8rem; font-size: .9rem; }

/* ---------- Menu boczne (tryb nawigacji "sidebar") ----------
   Trzeci tryb nawigacji obok Menu/Przewijanie: .nawigacja-obszar (pasek
   górny) jest całkowicie ukryty, a wszystkie pozycje żyją w #sidebar —
   panel nakładkowy z lewej strony, z sekcjami accordion (natywny
   <details>, bez JS). #sidebar-toggle w nagłówku i #sidebar-zamknij w
   panelu przełączają klasę "sidebar-zwiniety" na <html> (ta sama zasada
   co data-theme/data-mode: ustawiana PRZED pierwszym renderem w
   security.go, żeby uniknąć FOUC — panel nie "mignie" otwarty przy
   starcie, gdy user go zwinął). */
.sidebar-toggle-header { display: none; flex-shrink: 0; }
html[data-nav-mode="sidebar"] .sidebar-toggle-header { display: inline-flex; }
html[data-nav-mode="sidebar"] .nawigacja-obszar { display: none !important; }
html:not([data-nav-mode="sidebar"]) .sidebar,
html:not([data-nav-mode="sidebar"]) .sidebar-backdrop { display: none !important; }

.sidebar {
  position: fixed; top: 0; left: 0; height: 100dvh; width: min(20rem, 85vw);
  z-index: 45; background: var(--bg-2); border-right: 1px solid var(--linia);
  display: flex; flex-direction: column; overflow-y: auto;
  padding-top: env(safe-area-inset-top);
  transform: translateX(-100%); transition: transform var(--przejscie);
  box-shadow: var(--cien-2);
}
html.sidebar-zwiniety .sidebar { transform: translateX(-100%); }
html:not(.sidebar-zwiniety)[data-nav-mode="sidebar"] .sidebar { transform: translateX(0); }
html[data-anim="off"] .sidebar { transition: none; }

.sidebar-backdrop {
  position: fixed; inset: 0; z-index: 44; background: rgba(0, 0, 0, .4);
}
html.sidebar-zwiniety .sidebar-backdrop { display: none !important; }

.sidebar-naglowek {
  display: flex; align-items: center; justify-content: space-between;
  padding: .8rem 1rem; border-bottom: 1px solid var(--linia); flex-shrink: 0;
}
.sidebar-tytul { font-weight: 700; }

.sidebar-sekcje { padding: .5rem; }
.sidebar-sekcja { border-bottom: 1px solid var(--linia); }
.sidebar-sekcja:last-child { border-bottom: none; }
.sidebar-sekcja summary {
  padding: .7rem .6rem; cursor: pointer; font-weight: 600; list-style: none;
  display: flex; align-items: center; justify-content: space-between;
}
.sidebar-sekcja summary::-webkit-details-marker { display: none; }
.sidebar-sekcja summary::after { content: '▾'; color: var(--tekst-2); transition: transform var(--przejscie); }
.sidebar-sekcja[open] summary::after { transform: rotate(180deg); }
.sidebar-linki { display: flex; flex-direction: column; padding: 0 .5rem .6rem 1rem; }
.sidebar-linki a {
  padding: .5rem .6rem; border-radius: .5rem; color: var(--tekst-2);
  text-decoration: none; border-left: 3px solid transparent;
  transition: background var(--przejscie), color var(--przejscie), border-color var(--przejscie);
}
.sidebar-linki a:hover { background: var(--bg-3); color: var(--tekst); }
/* Aktywna pozycja: border-left NIEZALEŻNY od koloru tła — odporność na
   motyw, w którym --akcent-tlo wypadnie zbliżony do --bg-3 (hover); bez
   tego stan aktywny i hover byłyby nieodróżnialne w takim motywie. */
.sidebar-linki a[aria-current="page"] { color: var(--akcent); background: var(--akcent-tlo); font-weight: 600; border-left-color: var(--akcent); }
/* Zagnieżdżone <details> (SREQ-032): gdy sekcja najwyższego poziomu
   rozrasta się, jej podpoziomy grupuje się we WŁASNY <details> wewnątrz
   .sidebar-linki — rozwijanie/zwijanie natywne, bez JS. Węższy padding i
   mniejszy krój odróżniają podsekcję od sekcji najwyższego poziomu. */
.sidebar-linki details { border: none; }
.sidebar-linki details summary {
  padding: .4rem .6rem; cursor: pointer; font-weight: 500; font-size: .85rem;
  color: var(--tekst-2); list-style: none; display: flex; align-items: center; justify-content: space-between;
}
.sidebar-linki details summary::-webkit-details-marker { display: none; }
.sidebar-linki details summary::after { content: '▾'; transition: transform var(--przejscie); }
.sidebar-linki details[open] summary::after { transform: rotate(180deg); }
.sidebar-linki details .sidebar-linki { padding: 0 0 .3rem 1rem; }

/* Desktop: panel otwarty PRZESUWA treść (nagłówek + #page) zamiast ją
   zasłaniać nakładką — na wąskich ekranach zostaje nakładka z tłem
   (.sidebar-backdrop), bo nie ma miejsca na dosunięcie treści. */
@media (min-width: 900px) {
  html[data-nav-mode="sidebar"]:not(.sidebar-zwiniety) .naglowek,
  html[data-nav-mode="sidebar"]:not(.sidebar-zwiniety) #page {
    margin-left: min(20rem, 85vw); transition: margin-left var(--przejscie);
  }
  html[data-anim="off"][data-nav-mode="sidebar"] .naglowek,
  html[data-anim="off"][data-nav-mode="sidebar"] #page { transition: none; }
  html[data-nav-mode="sidebar"] .sidebar-backdrop { display: none !important; }
}

/* TELEFON: menu boczne jest JEDYNĄ drogą do pełnego zestawu stron (REQ-027),
   więc jego przycisk musi być widoczny w KAŻDYM trybie nawigacji, a nie tylko
   w trybie „menu boczne" wybranym ręcznie w ustawieniach.

   Zgłoszenie #61: do 0.3.1 na telefonie nie było jak otworzyć menu. Pasek
   nawigacji głównej razem z przyciskiem „☰" włącza się od 769 px, menu boczne
   było wyłączone regułą `html:not([data-nav-mode="sidebar"])`, a dolny pasek
   ma świadomie tylko pięć pozycji — więc „Do sprawdzenia", „Dyżury", „Tablo",
   „Powiadomienia", „Ustawienia" i „Pomoc" były z nawigacji NIEOSIĄGALNE.
   Rodzic nie miał dojścia do ekranu weryfikacji, czyli do swojej głównej
   funkcji.

   `!important` jest tu wymuszone przez regułę wyłączającą szufladę
   (`display: none !important` wyżej) — nie ozdoba, tylko jedyny sposób jej
   przebicia bez przebudowy tamtych reguł, których używa tryb desktopowy. */
@media (max-width: 768px) {
  .sidebar-toggle-header { display: inline-flex; }
  /* Szuflada pokazuje się WYŁĄCZNIE w stanie otwartym (klasa na <html>).
     Pierwsza wersja tej poprawki dawała jej `display: flex` na stałe i strona
     zaczęła przewijać się w poziomie przy 390 px — element odsunięty
     transformacją poza lewą krawędź nadal powiększał szerokość przewijania,
     a przygaszenie tła wisiało nad treścią. Wyłapał to test wiersza zadania
     (`TestWierszZadaniaNieLamieSieNaLiterach`), nie ja. */
  html.szuflada-otwarta .sidebar {
    display: flex !important; transform: translateX(0) !important;
  }
  html.szuflada-otwarta .sidebar-backdrop { display: block !important; }

  /* Miejsce na przycisk szuflady. Nagłówek stał na granicy szerokości:
     dołożenie jednego przycisku dawało 1 px przewijania w poziomie (wyłapał to
     `TestWierszZadaniaNieLamieSieNaLiterach` przy 390 px, nie ja). Zamiast
     chować przycisk, ścieśniamy odstępy — nagłówek na telefonie i tak jest
     ciasny, a jego przepełnienie przy 320 px jest znane i opisane osobno
     (pozycja 64 w docs/ROZBIEZNOSCI.md). */
  .naglowek { gap: .5rem; }
  .naglowek-akcje { gap: .35rem; }
  /* Znak aplikacji oddaje miejsce, ale NIE WYLEWA napisu (zgłoszenie #93).
     `min-width: 0` samo w sobie było błędem połowicznym: pozwalało elementowi
     zwinąć się poniżej szerokości własnej treści, a bez `overflow` napis
     wychodził poza swój prostokąt — przy 320 px marka miała 6 px szerokości
     i litery „dom" leżały pod kropką stanu połączenia. Pomiar strony
     przechodził na czysto (`scrollWidth == clientWidth`), prostokąty sąsiadów
     się nie nachodziły; objaw był widoczny wyłącznie na zrzucie.
     `min-width: 0` ZOSTAJE — bez niego wraca brak z #61, w którym marka
     wypychała awatar i wylogowanie poza ekran. */
  .marka { min-width: 0; overflow: hidden; }
  /* Znak firmowy nie zwija się nigdy: to on jest odnośnikiem do pulpitu,
     więc gdy miejsca brakuje, ustępuje NAPIS, a nie znak. */
  .marka-logo { flex-shrink: 0; }
  .marka span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* Najwęższe telefony (≤360 px): napis „dom" schowany JAWNIE, nie ścięty.
   Wielokropek nie miał tu czego skracać — przy 320 px na napis zostawało
   0–13 px, czyli „d…" albo sama kropka, a to gorsze od braku napisu. Znak
   firmowy zostaje odnośnikiem do pulpitu, a nazwa aplikacji nie ginie dla
   czytnika ekranu: niesie ją `aria-label` odnośnika („dom — pulpit"). */
@media (max-width: 360px) {
  .marka { flex-shrink: 0; }
  .marka span { display: none; }
  /* Po schowaniu napisu marka przestała być gąbką na brakujące piksele
     i deficyt nagłówka wyszedł na wierzch: strona zaczęła się przewijać
     w poziomie (311 przy 305 w „Liście", 323 w „Kafelkach", gdzie awatar jest
     większy). Wcześniej tego przewijania NIE BYŁO tylko dlatego, że marka
     w ciszy oddawała swoją szerokość i ścinała własny napis — objaw wyglądał
     na brak jednego elementu, a był deficytem całej belki.
     Odzyskujemy ~23 px na odstępach i wyściółce, a NIE na celach dotykowych:
     przycisk szuflady, odświeżanie, motyw, awatar i wylogowanie zostają
     w rozmiarze 44 px z REQ-026. Kropka stanu sieci (REQ-002) też zostaje —
     schowanie informacji o połączeniu byłoby wymianą braku układu na brak
     funkcjonalny. */
  .naglowek { gap: .35rem; padding-inline: .5rem; }
  .naglowek-akcje { gap: .25rem; }
}

/* ---------- 5. Strona i tabbar ---------- */
.strona { max-width: 64rem; margin-inline: auto; padding: clamp(.9rem, 3vw, 2rem); padding-bottom: 5.5rem; }
.tabbar {
  position: fixed; inset-inline: 0; bottom: 0; z-index: 10;
  display: grid; grid-auto-flow: column;
  background: color-mix(in srgb, var(--bg-2) 88%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--linia); padding-bottom: env(safe-area-inset-bottom);
}
.tabbar a {
  display: flex; flex-direction: column; align-items: center; gap: .1rem;
  padding: .45rem 0 .55rem; font-size: .72rem; text-decoration: none; color: var(--tekst-2);
  transition: color var(--przejscie);
}
.tab-ikona { font-size: 1.25rem; line-height: 1; transition: transform var(--przejscie); }
.tabbar a[aria-current="page"] { color: var(--akcent); }
.tabbar a[aria-current="page"] .tab-ikona { transform: translateY(-2px) scale(1.12); }
@media (min-width: 769px) {
  .nawigacja-obszar { display: flex; }
  .tabbar { display: none; }
  .strona { padding-bottom: 2rem; }
}

/* ---------- 6. Karty, formularze, przyciski ---------- */
.karta {
  background: var(--bg-2); border: 1px solid var(--linia); border-radius: var(--promien);
  box-shadow: var(--cien); padding: 1.1rem 1.25rem; margin-block: .9rem;
}
.tytul-strony { font-size: 1.45rem; margin: .4rem 0 .8rem; }
.tytul-sekcji { font-size: 1.05rem; color: var(--tekst-2); margin: 1.4rem 0 .4rem; }
.karta h2 { font-size: 1.02rem; margin: 0 0 .6rem; }

.formularz { display: grid; gap: .9rem; }
.formularz label { display: grid; gap: .3rem; font-size: .9rem; color: var(--tekst-2); }
/* textarea razem z input/select: bez tego pole „Pozycje checklisty" miało
   domyślny krój i ramkę przeglądarki, wyraźnie inne niż pola obok. */
input, select, textarea {
  font: inherit; color: var(--tekst); background: var(--bg); border: 1px solid var(--linia);
  border-radius: .55rem; padding: .5rem .7rem; min-height: var(--dotyk);
  transition: border-color var(--przejscie), box-shadow var(--przejscie);
}
input:focus-visible, textarea:focus-visible { outline: none; border-color: var(--akcent); box-shadow: 0 0 0 3px var(--akcent-tlo); }
/* Pola wyboru mają własną wewnętrzną szerokość (13 px), której min-height z
   reguły wyżej nie rusza — cel wychodził 13×36. Ustawiamy oba wymiary jawnie,
   żeby zaznaczenie osoby w rotacji dyżuru dało się trafić palcem. */
input[type="checkbox"], input[type="radio"] {
  width: var(--dotyk); height: var(--dotyk); min-height: 0;
  accent-color: var(--akcent); cursor: pointer; flex-shrink: 0;
}
/* Etykieta z polem wyboru jest właściwym celem kliknięcia — trzymamy ją
   w jednym wierszu i na wysokości progu. */
label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) {
  display: flex; align-items: center; gap: .5rem; min-height: var(--dotyk); cursor: pointer;
}
/* display: inline-flex — .przycisk bywa <a> (np. „Zmień hasło" w profilu),
   a link inline ignoruje min-height, więc cel schodził do 41 px. */
.przycisk {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  font: inherit; border: 1px solid var(--linia); background: var(--bg-2); color: var(--tekst);
  border-radius: .55rem; padding: .45rem .95rem; min-height: var(--dotyk); cursor: pointer;
  text-decoration: none;
  transition: background var(--przejscie), transform var(--przejscie), box-shadow var(--przejscie);
}
.przycisk:hover { background: var(--bg-3); }
.przycisk:active { transform: scale(.97); }
/* Akcja główna jest wyższa od zwykłego przycisku (design/README: 56–64 px). */
.przycisk.glowny {
  background: var(--akcent); border-color: var(--akcent); color: var(--akcent-tekst); font-weight: 600;
  min-height: var(--dotyk-glowny); padding-inline: 1.25rem;
}
.przycisk.glowny:hover { box-shadow: var(--cien-2); transform: translateY(-1px); }
.przycisk.glowny:active { transform: translateY(0) scale(.97); }
.przycisk.niebezpieczny { background: var(--blad); border-color: var(--blad); color: light-dark(#fff, #2b0b0b); font-weight: 600; }
.przycisk.niebezpieczny:hover { box-shadow: var(--cien-2); }
.przycisk:disabled { opacity: .6; cursor: default; }

.spinner {
  display: inline-block; width: 1em; height: 1em; margin-right: .4em;
  border: 2px solid var(--linia); border-top-color: var(--akcent);
  border-radius: 50%; vertical-align: -.15em;
  animation: spinner-obrot .7s linear infinite;
}
@keyframes spinner-obrot { to { transform: rotate(360deg); } }
html[data-anim="off"] .spinner { animation: none; }

/* Globalny wskaźnik postępu (SREQ-029): widoczny automatycznie przy KAŻDYM
   żądaniu htmx (nawigacja hx-boost, formularz, akcja) — bez ręcznego
   pokazywania/chowania per handler, patrz app.js (init htmxProgres). */
.htmx-progres {
  position: fixed; top: 0; left: 0; height: 3px; width: 0; z-index: 9999;
  background: var(--akcent); opacity: 0; pointer-events: none;
  transition: width .4s ease, opacity .2s ease;
}
.htmx-progres.aktywny { opacity: 1; }
html[data-anim="off"] .htmx-progres { transition: none; }

.diagnostyka { display: grid; grid-template-columns: auto 1fr; gap: .3rem 1rem; margin: 0; }
.diagnostyka dt { color: var(--tekst-2); }
.diagnostyka dd { margin: 0; font-family: monospace; word-break: break-all; }

.profil-glowka { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
.profil-nazwa { font-size: 1.15rem; font-weight: 700; margin: 0 0 .2rem; }
.awatar-aparat, .awatar-kadrowanie { margin-top: .8rem; }
.awatar-aparat video {
  width: 100%; max-width: 20rem; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--promien); background: var(--bg-3); display: block; margin-bottom: .6rem;
}
#awatar-kadrowanie-viewport {
  width: 220px; height: 220px; border-radius: 50%; overflow: hidden; position: relative;
  background: #000; margin-inline: auto; touch-action: none; cursor: move;
}
#awatar-kadrowanie-img { position: absolute; max-width: none; user-select: none; }
#awatar-kadrowanie-zoom { width: 220px; display: block; margin: .6rem auto; }

/* ---------- 7. Panel logowania ---------- */
.panel-auth { max-width: 24rem; margin: 8vh auto; text-align: center; }
.panel-auth .formularz { text-align: left; margin-top: 1rem; }
.auth-logo { width: 3.2rem; height: 3.2rem; }
.panel-auth h1 { margin: .5rem 0 .2rem; }
.rozdzielacz { display: flex; align-items: center; gap: .7rem; margin: 1rem 0; color: var(--tekst-2); font-size: .85rem; }
.rozdzielacz::before, .rozdzielacz::after { content: ""; flex: 1; height: 1px; background: var(--linia); }

/* ---------- 8. Pulpit: kontener strony i kafle ---------- */
/* Pulpit domowy (REQ-020) — kontener CAŁEGO ekranu, wspólny dla trzech
   wariantów. Do 0.5.2 `.pulpit`, `.pulpit-naglowek` i `.pulpit-sekcja` nie
   miały ani jednej reguły bazowej (`.pulpit-sekcja` tylko wariantową
   `.pulpit-sekcja.kafle` w „Osi dnia"), a Bulma zeruje marginesy nagłówków —
   więc „Cześć, …", „Mój dyżur", „Na dziś" i „Luki w harmonogramie" stały
   bezpośrednio na swojej treści i pierwszy ekran po zalogowaniu czytał się jak
   jeden akapit. Widać to tylko na zrzucie: znaczniki, identyfikatory i testy
   struktury były i są poprawne (zgłoszenie #96).
   Wzorzec ten sam, co `.podsumowanie-strona`: siatka jednokolumnowa z jednym
   odstępem, a nie odstęp sklejany z marginesów sąsiadów. */
.pulpit { display: grid; gap: .8rem; }
.pulpit-naglowek h1 { margin: 0; font-size: 1.35rem; }
/* Odstęp nagłówka sekcji od jej treści. Celowo `> h2`, nie `display` na samej
   sekcji: `.kafle` ustawia własną siatkę kolumn, a druga deklaracja `display`
   o tej samej specyficzności rozstrzygałaby się kolejnością w pliku. */
.pulpit-sekcja > h2 { margin: 0 0 .5rem; font-size: 1.05rem; }
.kafle { display: grid; gap: .9rem; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
.kafel {
  display: grid; justify-items: center; gap: .2rem; text-align: center;
  color: var(--tekst-2); text-decoration: none; margin-block: 0;
  transition: transform var(--przejscie), box-shadow var(--przejscie);
}
.kafel:hover { transform: translateY(-3px); box-shadow: var(--cien-2); }
.kafel-liczba { font-size: 2rem; color: var(--akcent); line-height: 1.1; } /* stary pulpit.tmpl */

/* Zawartość kafla pulpitu domowego (pulpit-domowy.tmpl): duża liczba,
   podpis nad nią i zamiennik na wypadek zera. */
.kafel-licznik { font-size: 2rem; color: var(--akcent); line-height: 1.1; font-weight: 700; }
/* Zaległości czytamy jako ostrzeżenie, nie jako zwykły licznik — globalna
   reguła .blad stoi wcześniej w pliku, więc trzeba ją powtórzyć tutaj. */
.kafel-licznik.blad { color: var(--blad); }
.kafel-etykieta { font-size: .85rem; color: var(--tekst-2); }
.kafel-pusto { color: var(--tekst-2); font-style: italic; font-size: .9rem; }

/* Karta „Mój dyżur" na pulpicie domowym. Element jest <a class="karta">, a link
   bez własnego display zostaje inline: padding i margin-block .karty nie wchodzą
   wtedy do przepływu, więc karta nachodziła na kafle poniżej i była węższa od
   nich. Blokowy grid naprawia i nakładkę, i szerokość. */
.dyzur-kafel {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: .1rem .7rem; min-height: var(--dotyk);
  color: var(--tekst); text-decoration: none;
  transition: transform var(--przejscie), box-shadow var(--przejscie);
}
.dyzur-kafel:hover { transform: translateY(-3px); box-shadow: var(--cien-2); }
.dyzur-nazwa { grid-column: 1; font-weight: 700; font-size: 1.05rem; }
.dyzur-okres { grid-column: 1; color: var(--tekst-2); font-size: .85rem; }
/* Plakietka z liczbą zadań stoi z prawej, przez oba wiersze tekstu. */
.dyzur-kafel .badge { grid-column: 2; grid-row: 1 / -1; justify-self: end; }
.wskazowka ul { margin: .3rem 0 0; padding-left: 1.2rem; }
.wskazowka li { margin-block: .3rem; }

/* ---------- Zwijany formularz tworzenia (ukryty domyślnie, JS-free toggle) ----------
   <details class="zwijka">: <summary> jest przyciskiem "Dodaj/Utwórz"; formularz
   pokazuje się po otwarciu, chowa na "Anuluj" (data-zwijka-anuluj, app.js). */
/* Link powrotu do listy na widokach szczegółów. */
.powrot-link { display: inline-block; margin-bottom: .6rem; color: var(--tekst-2); font-weight: 600; }
.powrot-link:hover { color: var(--akcent); }

.zwijka { margin-bottom: 1rem; }
.zwijka > summary {
  display: inline-flex; align-items: center; gap: .4rem; cursor: pointer;
  list-style: none; width: fit-content; min-height: var(--dotyk); /* cel dotykowy */
  padding: .5rem 1rem; border-radius: var(--promien); border: 1px solid var(--akcent);
  background: var(--akcent); color: var(--akcent-tekst); font-weight: 600;
  transition: box-shadow var(--przejscie), transform var(--przejscie);
}
.zwijka > summary::-webkit-details-marker { display: none; }
.zwijka > summary::before { content: '+'; font-weight: 700; }
.zwijka > summary:hover { box-shadow: var(--cien-2); transform: translateY(-1px); }
/* Zmiennej --obwodka nigdy nie zdefiniowano, więc border-color cofał się do
   wartości początkowej i rozwinięta zwijka nie zmieniała obwódki. */
.zwijka[open] > summary { background: var(--bg-2); color: var(--tekst); border-color: var(--linia); }
.zwijka[open] > summary::before { content: '×'; }
.zwijka .zwijka-tresc { margin-top: .8rem; }
/* Regułę `.formularz-akcje` (rząd przycisków formularza ze szkieletu PWASKEL)
   usunięto w 0.4.x, zgłoszenie #66 — klasy nie nosił ani jeden element w żadnym
   szablonie ani w JS. Rzędy akcji w formularzach tej aplikacji mają własne
   klasy przy swoich ekranach; wspólnego kontenera nie potrzebowały. */

/* ---------- 9. Notatki ---------- */
.formularz-notatki { display: flex; gap: .7rem; align-items: center; flex-wrap: wrap; }
.formularz-notatki input[name="tresc"] { flex: 1; min-width: 8rem; }
.zalacznik-wybierz { cursor: pointer; }
#zalacznik-status { flex-basis: 100%; }
.zalacznik-miniatura { display: inline-block; margin-block: .4rem; }
.zalacznik-miniatura img { max-width: 8rem; max-height: 8rem; border-radius: .5rem; display: block; }
.zalacznik-plik { display: inline-block; margin-block: .4rem; color: var(--tekst-2); }
.szukaj { width: 100%; margin-block: .2rem .8rem; }
.lista-notatek { display: grid; gap: .6rem; }
.notatka { display: flex; align-items: center; gap: .8rem; margin-block: 0; padding: .7rem .9rem; }
.check {
  flex: none; width: 1.55rem; height: 1.55rem; border-radius: .45rem;
  border: 2px solid var(--linia); background: var(--bg); cursor: pointer; padding: 0;
  transition: background var(--przejscie), border-color var(--przejscie), transform var(--przejscie);
}
.check:active { transform: scale(.85); }
.check svg { width: 100%; height: 100%; fill: none; stroke: var(--akcent-tekst); stroke-width: 3;
  stroke-linecap: round; stroke-linejoin: round; opacity: 0; transform: scale(.5);
  transition: opacity var(--przejscie), transform var(--przejscie); }
.zrobiona .check { background: var(--akcent); border-color: var(--akcent); }
.zrobiona .check svg { opacity: 1; transform: scale(1); }
.notatka-tresc { flex: 1; min-width: 0; }
.notatka-tresc p { margin: 0; overflow-wrap: anywhere; transition: opacity var(--przejscie); }
.zrobiona .notatka-tresc p { text-decoration: line-through; opacity: .55; }
.stempel { color: var(--tekst-2); }
.usun::before { content: "✕"; }
.usun { opacity: 0; }
.notatka:hover .usun, .notatka:focus-within .usun { opacity: 1; }
html[data-resolved-mode="touch"] .usun { opacity: .7; }

/* Stan „czeka na synchronizację" (optymistyczny UI offline). */
.oczekuje-info { display: none; color: var(--uwaga); }
.oczekuje { border-style: dashed; border-color: var(--uwaga); }
.oczekuje .notatka-tresc p { font-style: italic; }
.oczekuje .oczekuje-info { display: inline; }

/* ---------- 10. Ustawienia ---------- */
/* max-width + zawijanie: grupa przełączników (motyw, tryb, nawigacja) miała
   stałą szerokość treści i na telefonie 390 px wypychała stronę w poziomie.
   Zawijamy zamiast przewijać — to ustawienia, więc ukrycie części opcji poza
   krawędzią byłoby gorsze niż drugi rząd przycisków. */
.segmenty { display: inline-flex; flex-wrap: wrap; max-width: 100%; border: 1px solid var(--linia); border-radius: .6rem; overflow: hidden; }
.segmenty button {
  font: inherit; border: none; background: transparent; color: var(--tekst-2);
  padding: .45rem 1rem; min-height: var(--dotyk); cursor: pointer;
  transition: background var(--przejscie), color var(--przejscie);
}
.segmenty button + button { border-left: 1px solid var(--linia); }
.segmenty button[aria-checked="true"] { background: var(--akcent); color: var(--akcent-tekst); font-weight: 600; }
.schematy { display: flex; flex-wrap: wrap; gap: .6rem; }
.schematy button {
  font: inherit; display: flex; align-items: center; gap: .5rem;
  border: 2px solid var(--linia); border-radius: .6rem; background: var(--bg);
  color: var(--tekst); padding: .45rem .8rem; min-height: var(--dotyk); cursor: pointer;
  transition: border-color var(--przejscie), transform var(--przejscie);
}
.schematy button:hover { transform: translateY(-2px); }
.schematy button[aria-checked="true"] { border-color: var(--akcent); }
.probka { width: 1.1rem; height: 1.1rem; border-radius: 50%; }
.probka[data-scheme="grafit"]   { background: linear-gradient(135deg, #4f46e5, #a5b4fc); }
.probka[data-scheme="ocean"]    { background: linear-gradient(135deg, #0e7490, #22d3ee); }
.probka[data-scheme="zorza"]    { background: linear-gradient(135deg, #7c3aed, #34d399); }
.probka[data-scheme="bursztyn"] { background: linear-gradient(135deg, #b45309, #fbbf24); }
.probka[data-scheme="lesny"]    { background: linear-gradient(135deg, #2f7a4f, #7fc9a0); }
.probka[data-scheme="malina"]   { background: linear-gradient(135deg, #a8325c, #f28ab0); }
/* Wybór wariantu interfejsu (ustawienia-sekcje.tmpl, radiogroup): gołe
   <button> bez klasy — bez tej reguły miały 36 px, poniżej progu. */
.warianty { display: grid; gap: .5rem; }
.warianty button {
  font: inherit; text-align: left; cursor: pointer;
  display: flex; align-items: center; gap: .5rem; min-height: var(--dotyk);
  padding: .5rem .9rem; border: 1px solid var(--linia); border-radius: .6rem;
  background: var(--bg); color: var(--tekst);
  transition: border-color var(--przejscie), background var(--przejscie);
}
.warianty button:hover { background: var(--bg-3); }
.warianty button[aria-checked="true"], .warianty button.wybrany {
  border-color: var(--akcent); background: var(--akcent-tlo); color: var(--akcent); font-weight: 600;
}
.suwak-czcionki { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }
.suwak-czcionki input[type="range"] { flex: 1; min-width: 8rem; accent-color: var(--akcent); }
/* Przełącznik = pozycja listy ustawień, więc cały wiersz ma wysokość progu,
   a samo pole wyboru zostaje na rozmiarze z sekcji 6 (var(--dotyk)). */
.przelacznik { display: flex; gap: .6rem; align-items: center; min-height: var(--dotyk); }
.przelacznik input { accent-color: var(--akcent); }
.lista-kluczy { display: grid; gap: .6rem; margin-bottom: .9rem; }
.klucz-webauthn { display: flex; align-items: center; justify-content: space-between; gap: .8rem; padding: .7rem .9rem; }
/* Zawijanie z tego samego powodu co w .segmenty: pole obok przycisku
   („Wygeneruj token") nie mieściło się na telefonie 390 px i wypychało stronę
   w poziomie. `min-width: 0` na polu jest konieczne — domyślna wewnętrzna
   szerokość <input> nie pozwala mu zejść poniżej rozmiaru zawartości, więc
   samo flex-wrap by nie wystarczyło. */
.formularz-inline { display: flex; flex-wrap: wrap; gap: .7rem; }
.formularz-inline input { flex: 1; min-width: 0; }
.lista-grup { display: grid; gap: .6rem; }
.grupa-kafel { display: block; text-decoration: none; color: var(--tekst); font-weight: 600; padding: .8rem 1rem; }
.grupa-kafel:hover { border-color: var(--akcent); }
.lista-czlonkow { display: grid; gap: .6rem; }
.czlonek-grupy { display: flex; align-items: center; justify-content: space-between; gap: .8rem; padding: .7rem .9rem; }
.lista-tokenow { display: grid; gap: .6rem; margin-bottom: .9rem; }
.token-api { display: flex; align-items: center; justify-content: space-between; gap: .8rem; padding: .7rem .9rem; }
.token-jawny { display: inline-block; margin-top: .3rem; padding: .4rem .6rem; background: var(--bg-3); border-radius: .4rem; word-break: break-all; user-select: all; }

/* ---------- 11. Tabele ---------- */
.tabela-zwijana { overflow-x: auto; }
.tabela { border-collapse: collapse; width: 100%; font-size: .88rem; }
.tabela th, .tabela td { text-align: left; padding: .45rem .6rem; border-bottom: 1px solid var(--linia); }
.tabela th { color: var(--tekst-2); font-weight: 600; }
.wiersz-blad td { color: var(--blad); }

/* ---------- 12. Animacje wejścia i htmx ---------- */
.wejscie { animation: wejscie .45s cubic-bezier(.2, .8, .2, 1) both; animation-delay: calc(var(--i, 0) * 60ms); }
/* Kaskada opóźnień przez data-i (nie inline style="") — CSP bez unsafe-inline w style-src. */
[data-i="1"] { --i: 1; } [data-i="2"] { --i: 2; } [data-i="3"] { --i: 3; } [data-i="4"] { --i: 4; }
[data-i="5"] { --i: 5; } [data-i="6"] { --i: 6; } [data-i="7"] { --i: 7; } [data-i="8"] { --i: 8; }
[data-i="9"] { --i: 9; }
@keyframes wejscie {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.htmx-added { animation: wejscie .3s cubic-bezier(.2, .8, .2, 1) both; }
.htmx-swapping { opacity: 0; transform: scale(.98); transition: opacity .2s, transform .2s; }

/* ---------- 13. Demo (kompozytor + GPU) ---------- */
.demo-flip { perspective: 700px; }
.flip-scena { width: 7rem; height: 4.6rem; }
.flip-obiekt {
  position: relative; width: 100%; height: 100%;
  transform-style: preserve-3d; transition: transform .7s cubic-bezier(.2, .8, .2, 1); will-change: transform;
}
.demo-flip:hover .flip-obiekt, .demo-flip:focus-visible .flip-obiekt { transform: rotateY(180deg); }
.flip-przod, .flip-tyl {
  position: absolute; inset: 0; display: grid; place-items: center;
  border-radius: .6rem; backface-visibility: hidden; font-weight: 700;
}
.flip-przod { background: var(--akcent); color: var(--akcent-tekst); font-size: 1.6rem; }
.flip-tyl { background: var(--bg-3); transform: rotateY(180deg); font-size: .8rem; padding: .4rem; }
.pierscien {
  width: 4.6rem; height: 4.6rem; border-radius: 50%;
  background: conic-gradient(from 0deg, var(--orb-a), var(--orb-b), var(--orb-a));
  -webkit-mask: radial-gradient(farthest-side, transparent 62%, #000 64%);
          mask: radial-gradient(farthest-side, transparent 62%, #000 64%);
  animation: wiruj 2.6s linear infinite; will-change: transform;
}
@keyframes wiruj { to { transform: rotate(360deg); } }
.pulsar { position: relative; width: 4.6rem; height: 4.6rem; }
.pulsar i {
  position: absolute; inset: 0; border-radius: 50%; border: 2px solid var(--akcent);
  animation: puls 2.4s ease-out infinite; will-change: transform, opacity;
}
.pulsar i:nth-child(2) { animation-delay: .8s; } .pulsar i:nth-child(3) { animation-delay: 1.6s; }
@keyframes puls { from { transform: scale(.25); opacity: .9; } to { transform: scale(1); opacity: 0; } }
#gl-demo { width: 100%; height: auto; border-radius: .6rem; background: var(--bg-3); }

/* ---------- 13b. Wgrywanie zdjęć do raportu ---------- */
/* Widget [data-uploads] buduje uploads.js z DOM API (CSP nie dopuszcza inline).
   Wcześniej te klasy nie miały ŻADNEJ reguły — ta sama klasa błędu co
   .dyzur-kafel i .nawigacja-dolna: znacznik istniał, wyglądu nie było. */
.uploads-lista { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0 0; padding: 0; list-style: none; }
.uploads-pozycja {
  display: flex; align-items: center; gap: .4rem;
  padding: .3rem .5rem; border: 1px solid var(--linia); border-radius: .5rem;
  background: var(--bg-2); font-size: .85rem; max-width: 100%;
}
.uploads-pozycja span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uploads-usun {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--dotyk); min-height: var(--dotyk);
  border: none; background: transparent; color: var(--tekst-2);
  font: inherit; cursor: pointer; border-radius: .4rem;
}
.uploads-usun:hover { color: var(--blad); background: var(--bg-3); }
.uploads-status { display: block; margin-top: .4rem; color: var(--tekst-2); font-size: .85rem; }
.uploads-status.blad { color: var(--blad); }

/* Dowody wykonania przy wpisie historii zadania (REQ-014). Obraz idzie jako
   miniatura, PDF jako odnośnik z nazwą — reguły dla .zalacznik-miniatura
   i .zalacznik-plik są wspólne z notatkami (sekcja 9), tu zostaje tylko układ
   listy. */
.historia-zalaczniki {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin: .3rem 0 0; padding: 0; list-style: none;
}
.historia-zalaczniki > li { max-width: 100%; min-width: 0; }
.historia-zalaczniki .zalacznik-plik {
  display: inline-flex; align-items: center; gap: .3rem;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: .25rem .5rem; border: 1px solid var(--linia); border-radius: .5rem;
  background: var(--bg-2); font-size: .85rem;
}

/* Checklista zamrożona po zgłoszeniu (REQ-014): znacznik zostaje, przycisku
   nie ma. Wyszarzenie mówi „to już nie jest do ruszania", zamiast udawać
   kontrolkę, która zawsze odmówi. */
.checklista-blok { margin-top: 1rem; }
.checklista-pozycja.zamrozona { opacity: .75; }
.check-zamrozony { cursor: default; }
.pozycja-autor { margin-left: .4rem; }

/* ---------- 13c. Szablony zadań dyżuru ---------- */
/* Znaczniki szablonów istniały od 0.2.3 BEZ ani jednej reguły — trzeci taki
   przypadek w tym projekcie (po .dyzur-kafel i .uploads-*), więc wygląd
   dopisujemy razem z audytem listy rozbieżności. Bulma stylizuje same pola
   formularza, nie układ listy, dlatego bez tego szablony wyglądały jak goła
   lista punktowana. */
.szablony-dyzuru { margin-top: 1rem; }
.szablony-dyzuru h3 { margin-bottom: .4rem; font-size: 1rem; }
.szablony { margin: 0; padding: 0; list-style: none; display: grid; gap: .5rem; }
.szablon {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  padding: .5rem .6rem; border: 1px solid var(--linia); border-radius: .5rem;
  background: var(--bg-2); min-height: var(--dotyk);
}
.szablon-tytul { font-weight: 600; }
/* Sterowanie idzie na koniec wiersza, żeby tytuł i plakietki czytały się
   pierwsze, a „Usuń" nie wypadało tuż obok „Edytuj" na wąskim ekranie. */
.szablon-edycja { margin-left: auto; }
.szablon-usun { margin: 0; }
.szablon-edycja, .szablon-nowy { width: 100%; }
.szablon-edycja > summary, .szablon-nowy > summary { min-height: var(--dotyk); }
.szablon-nowy { margin-top: .75rem; }
.formularz-szablonu { display: grid; gap: .6rem; margin-top: .5rem; }
.formularz-szablonu label { display: grid; gap: .2rem; }
/* Pola wyboru: etykieta w jednym wierszu z kontrolką, bez rozjeżdżania grida. */
.formularz-szablonu label:has(> input[type="checkbox"]),
.formularz-szablonu label:has(> input[type="radio"]) {
  display: flex; align-items: center; gap: .4rem; min-height: var(--dotyk);
}
.cykl-rodzaj, .cykl-dni {
  display: flex; flex-wrap: wrap; gap: .5rem 1rem;
  margin: 0; padding: .5rem .6rem; border: 1px solid var(--linia); border-radius: .5rem;
}
.cykl-rodzaj legend, .cykl-dni legend { padding: 0 .3rem; color: var(--tekst-2); font-size: .85rem; }
.cykl-rodzaj label, .cykl-dni label {
  display: flex; align-items: center; gap: .3rem; min-height: var(--dotyk);
}

/* ---------- 13d. Ekran dyżurów: skład rodziny, rotacja, okresy ---------- */
/* Piąty przypadek tej samej klasy błędu (po .dyzur-kafel, .nawigacja-dolna,
   .uploads-* i .szablon-*): dyzury.tmpl + partial "domownik" używały
   kilkunastu klas BEZ ani jednej reguły, więc cały ekran renderował się jako
   goły HTML. Kolory wyłącznie ze zmiennych sekcji 1 — tryb nocny i sześć
   schematów działają bez osobnych reguł.

   Luka vs wyjątek (design/{A,B,C}Dyzury): oba stany są rozpoznawalne BEZ
   czytania tekstu i NIE tylko kolorem (design/README: „stan nigdy nie zależy
   wyłącznie od koloru"):
   - luka  = brak obsady, coś do naprawienia → ciągła listwa i tło w --uwaga
             plus plakietka o PRZERYWANEJ obwódce (ten sam znak co .druk-luka
             na wydruku planu),
   - wyjątek = ręczne zastępstwo, informacja neutralna → listwa KROPKOWANA
             w --tekst-2, bez tła alarmowego; sama plakietka „wyjątek" zostaje
             na neutralnym .badge. */

/* Skład rodziny. Sama karta wygląd bierze z .karta — tu tylko zwężamy odstęp
   pod nagłówkiem, bo wiersze listy mają własny padding. */
.domownicy-karta h2 { margin-bottom: .2rem; }
.domownicy { display: grid; margin: 0; padding: 0; list-style: none; }
.domownik {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  min-height: var(--dotyk); padding: .4rem 0;
}
/* Separator sąsiedztwem (jak .segmenty button + button) — pierwszy wiersz nie
   dostaje kreski i nie trzeba zerować jej na ostatnim. */
.domownik + .domownik { border-top: 1px solid var(--linia); }
.domownik-imie { font-weight: 600; }
.domownik-rola { font-size: .85rem; }
/* Moderacja awatara idzie na koniec wiersza; <form> ma domyślny margines. */
.domownik-moderacja { margin: 0 0 0 auto; }
/* Mniejszy krój i węższy padding, ale min-height z .przycisk zostaje —
   próg 44 px obowiązuje też przyciski poboczne. */
.przycisk.maly { font-size: .85rem; padding-inline: .7rem; }

/* Rotacja: kolejność jest treścią, więc numer zostaje widoczny (licznik CSS),
   a pozycje układają się w plakietki jak w makietach. */
.rotacja {
  display: flex; flex-wrap: wrap; gap: .4rem;
  margin: 0 0 .6rem; padding: 0; list-style: none; counter-reset: rotacja;
}
.rotacja > li {
  counter-increment: rotacja;
  display: inline-flex; align-items: center; min-height: var(--dotyk);
  padding: .25rem .75rem; border: 1px solid var(--linia); border-radius: 999px;
  background: var(--bg-2); font-size: .9rem; font-weight: 600;
}
.rotacja > li::before { content: counter(rotacja) "."; margin-inline-end: .35rem; color: var(--tekst-2); font-weight: 400; }
.ostrzezenie {
  display: flex; align-items: center; gap: .45rem;
  margin-block: .6rem; padding: .5rem .7rem;
  border: 1px solid var(--uwaga); border-radius: .5rem;
  background: color-mix(in srgb, var(--uwaga) 12%, transparent); color: var(--uwaga);
}
.ostrzezenie::before { content: "⚠"; flex-shrink: 0; }
/* Komunikat odrzuconej walidacji (422) — pierwszy element `#page`, więc bez
   górnego marginesu, żeby nie odklejał się od nagłówka strony. Wygląd bierze
   z .ostrzezenie: odrzucenie ma wyglądać tak samo w całej aplikacji. */
.komunikat-walidacji { margin-block-start: 0; }

/* Formularz kolejności dyżurów — układ jak .cykl-dni (flex, nie grid: legend
   w fieldsecie z display:grid renderuje się różnie w silnikach). */
.formularz-rotacji { display: grid; gap: .7rem; justify-items: start; margin-block: .8rem; }
/* Siatka, nie flex-wrap: każdy uczestnik zajmuje CAŁY wiersz swojej kolumny,
   więc numer trzyma się swojego imienia. Przy flex-wrap wiersze o różnej
   długości imion rozjeżdżały się i „Nr" lądowało pod cudzym nazwiskiem. */
.formularz-rotacji fieldset {
  /* `min(17rem, 100%)`, nie samo `17rem`: minimum ścieżki siatki jest
     TWARDE i na 320 px kolumna 17rem (272 px) razem z wyściółką fieldsetu nie
     mieszczą się w karcie dyżuru — `width: 100%` tego nie ratuje, bo minimum
     ścieżki wygrywa i przewijała się CAŁA strona (zgłoszenie #89). Z `min()`
     wąski ekran dostaje jedną kolumnę o szerokości karty, a szeroki nadal
     tyle kolumn, ile zmieści po 17rem. */
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr));
  gap: .3rem 1.2rem; width: 100%;
  margin: 0; padding: .5rem .7rem; border: 1px solid var(--linia); border-radius: .5rem;
}
.formularz-rotacji legend { padding: 0 .3rem; color: var(--tekst-2); font-size: .85rem; }

/* Najbliższe okresy — wiersze listy na progu dotyku, rozdzielone kreską,
   z listwą stanu po stronie początku wiersza (przezroczysta w stanie
   zwykłym, żeby tekst nie skakał w poziomie). */
.okresy { display: grid; margin: 0; padding: 0; list-style: none; }
.okres {
  display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .6rem;
  min-height: var(--dotyk); padding: .45rem .6rem;
  border-inline-start: 3px solid transparent;
}
.okres + .okres { border-top: 1px solid var(--linia); }
.okres-daty { font-weight: 600; }
.okres-osoba { display: inline-flex; align-items: center; min-width: 0; color: var(--tekst-2); }
.okres .badge { margin-inline-start: auto; }
/* Luka: alarm — ciągła listwa, tło w --uwaga i plakietka „brak osoby"
   z przerywaną obwódką (pusty slot, nie osoba). */
.okres.luka {
  border-inline-start-color: var(--uwaga);
  background: color-mix(in srgb, var(--uwaga) 10%, transparent);
}
.okres-brak {
  display: inline-flex; align-items: center;
  padding: .1rem .55rem; border: 1px dashed var(--uwaga); border-radius: 999px;
  color: var(--uwaga); font-size: .85rem; font-weight: 600;
}
/* Wyjątek: informacja, nie alarm — inny KSZTAŁT listwy (kropkowana) w kolorze
   drugoplanowym; bez tła, żeby nie konkurował wzrokowo z luką. */
.okres.wyjatek { border-inline-start-style: dotted; border-inline-start-color: var(--tekst-2); }

/* Nowy dyżur: etykiety nad polami, akcja główna na końcu. */
.formularz-dyzuru { display: grid; gap: .25rem; justify-items: start; }
.formularz-dyzuru label { margin-top: .5rem; color: var(--tekst-2); font-size: .9rem; }
.formularz-dyzuru input { width: 100%; max-width: 22rem; }
.formularz-dyzuru button { margin-top: .9rem; }

/* Rotacja: wiersz „osoba + numer w kolejce". Numer jest kontrolką, nie
   ozdobą — to on decyduje o kolejności dyżurów, więc stoi obok imienia,
   a nie pod spodem. */
.rotacja-wiersz {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  min-height: var(--dotyk);
}
.rotacja-wiersz > label:first-child { display: flex; align-items: center; gap: .4rem; min-width: 9rem; }
/* Numer stoi przy imieniu, nie na końcu wiersza: w siatce wielokolumnowej
   „auto" odsuwałoby go do granicy kolumny, czyli w sąsiedztwo obcego imienia. */
.rotacja-pozycja { display: flex; align-items: center; gap: .3rem; }
.rotacja-wiersz > label:first-child { flex: 1 1 auto; }
.rotacja-pozycja input { width: 4.5rem; }

/* Zastępstwo: zwijka, żeby formularz nie zaśmiecał karty dyżuru na co dzień. */
.formularz-zastepstwa { display: grid; gap: .6rem; margin-top: .5rem; }
.formularz-zastepstwa label { display: grid; gap: .2rem; font-size: .9rem; color: var(--tekst-2); }
.formularz-zastepstwa input, .formularz-zastepstwa select { max-width: 22rem; }

/* Awatar konta usuniętego (REQ-017): neutralny, bez koloru przypisanego do
   identyfikatora — kolor zaślepki jest funkcją ID, więc sam w sobie
   rozróżniałby byłych domowników między sobą. */
.awatar-anonim { background: var(--bg-3); color: var(--tekst-2); }

/* ---------- 13d2. Akcje na definicji dyżuru i na zadaniu ---------- */
/* Strefy nieodwracalne i administracyjne trzymamy wizualnie NIŻEJ niż treść:
   „Zakończ dyżur" i „Anuluj zadanie" nie mogą konkurować z akcją, po którą
   użytkownik przyszedł („Zgłoś wykonanie", „Zapisz rotację"). Kolor
   ostrzegawczy dostaje sam przycisk (`.przycisk.niebezpieczny`), nie całe tło:
   czerwony blok na pół karty czyta się jak komunikat o awarii. */
.definicja-dyzuru, .zadanie-anulowanie { margin-top: 1rem; }
/* Sam `color` NIE WYSTARCZAŁ i był wręcz szkodliwy: `.zwijka > summary` daje
   pełne tło akcentem, więc nadpisanie tylko koloru tekstu dawało szary napis
   na akcencie — nieczytelny, obok poprawnych białych zwijek na tym samym
   ekranie. Strefa administracyjna ma być CISZEJ niż akcja główna, więc
   zdejmujemy tło i zostawiamy obwódkę: to ta sama hierarchia, tylko wyrażona
   wagą wizualną, a nie kolorem tekstu. */
.definicja-dyzuru > summary, .zadanie-anulowanie > summary {
  font-size: .9rem; color: var(--tekst-2);
  background: transparent; border-color: var(--linia);
}
.definicja-dyzuru > summary:hover, .zadanie-anulowanie > summary:hover {
  background: var(--bg-3); color: var(--tekst);
}
.formularz-edycji-dyzuru { display: grid; gap: .6rem; margin-top: .5rem; }
.formularz-edycji-dyzuru label { display: grid; gap: .2rem; font-size: .9rem; color: var(--tekst-2); }
.formularz-edycji-dyzuru input, .formularz-edycji-dyzuru select { max-width: 22rem; }
.formularz-edycji-dyzuru .opis { max-width: 44rem; }
/* Zakończenie odcięte kreską od edycji — inaczej „Zapisz definicję"
   i „Zakończ dyżur" wyglądają jak dwa przyciski jednego formularza. */
.formularz-zakonczenia {
  display: flex; margin-top: .75rem; padding-top: .75rem;
  border-top: 1px solid var(--linia);
}
.zadanie-anulowanie .zwijka-tresc { display: grid; gap: .5rem; margin-top: .5rem; }
.zadanie-anulowanie label { display: grid; gap: .2rem; font-size: .9rem; color: var(--tekst-2); }
.zadanie-anulowanie textarea, .zadanie-anulowanie input { max-width: 32rem; }

/* Zmiana roli w wierszu domownika: pole i przycisk w jednej linii, przy
   prawej krawędzi wiersza (obok moderacji awatara), zawijane na wąskim
   ekranie — wiersz domownika ma trzymać próg dotyku, nie rosnąć w słupek. */
.domownik-rola-zmiana {
  display: flex; align-items: center; gap: .3rem; flex-wrap: wrap;
  margin: 0 0 0 auto;
}
.domownik-rola-zmiana select { max-width: 10rem; }

/* Pasek filtrów (historia): etykieta i pole to para, więc odstęp między parami
   musi być większy niż wewnątrz pary — inaczej „Od" przykleja się do pola
   poprzedniej pary i czyta się jak jej podpis. */
.filtry { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem 1rem; margin-bottom: .6rem; }
.filtry label { color: var(--tekst-2); font-size: .9rem; margin-inline-end: -.65rem; }

/* ---------- 13g. Kolejka weryfikacji: dowody i samo-akceptacja ---------- */
/* Dowody na karcie kolejki: rodzic ma widzieć, co akceptuje, BEZ wchodzenia
   w zadanie. Miniatury i odnośniki do PDF idą tym samym partialem co historia
   zadania, więc styl jest wspólny — tu zostaje sam odstęp nad blokiem akcji. */
.dowody-weryfikacji { margin: .5rem 0; }

/* Rodzic przyjmuje własne zgłoszenie sam (REQ-015, zgłoszenie #68), a karta mu
   to mówi. Ton NEUTRALNY, nie alarmowy — to dozwolona droga, a nie wyjątek ani
   ostrzeżenie: obramowanie akcentem robiło z niej podejrzaną operację. Klasa
   `.samoweryfikacja-blokada` zniknęła razem z zakazem; nosił ją komunikat
   w miejscu przycisków, których teraz nikt nie traci. */
.samoakceptacja-uwaga {
  padding: .4rem .6rem; border-radius: .5rem; background: var(--bg-3);
  color: var(--tekst-2); font-size: .85rem;
}

/* Pominięty filtr historii: informacja, nie błąd — dane są pokazane, tylko
   szerzej, niż prosił adres. */
.historia-filtr-pominiety {
  margin: .3rem 0 0; padding: .35rem .6rem; border-radius: .5rem;
  background: color-mix(in srgb, var(--uwaga) 10%, transparent);
  color: var(--uwaga); font-size: .85rem;
}

/* ---------- 13f. Historia okresów dyżurów ---------- */
/* Ten sam fakt („kto pełnił okres") ma wyglądać jak na ekranie dyżurów, więc
   układ wiersza jest wspólny z `.okres`; tutaj dochodzą wyłącznie liczby
   zadań i wyróżnienie okresu trwającego. */
.historia-okresy { display: grid; margin: .5rem 0 0; padding: 0; list-style: none; }
.historia-okres {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .6rem;
  min-height: var(--dotyk); padding: .4rem .2rem; border-top: 1px solid var(--linia);
}
.historia-okres.luka { color: var(--tekst-2); }
.historia-okres.wyjatek { border-inline-start: 3px dotted var(--tekst-2); padding-inline-start: .5rem; }
/* Ten sam fakt („to trwa teraz") ma wyglądać tak samo na ekranie dyżurów
   i w historii — jedna reguła dla obu, żeby nie rozjechały się przy pierwszej
   zmianie. Treść niesie plakietka ze słowem „teraz", więc tło jest tylko
   wzmocnieniem (design/README: stan nigdy nie zależy wyłącznie od koloru). */
.historia-okres.trwajacy, .okres.trwajacy { background: var(--akcent-tlo); border-radius: .4rem; }
.badge.okres-teraz {
  background: var(--akcent); color: var(--akcent-tekst);
  font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
}
.historia-okres-dyzur { font-weight: 600; }
.historia-okres-liczby { display: flex; flex-wrap: wrap; gap: .3rem; margin-inline-start: auto; }
/* Liczby są plakietkami, nie tekstem: trzy wartości pod rząd czyta się wzrokiem
   po kolorze („ile zrobione / ile zaległe / ile odwołane"), nie po etykietach,
   na które w wierszu nie ma miejsca. Kolor NIESIE znaczenie, więc każda ma też
   tytuł dla czytników ekranu (ustawiony w szablonie). */
.liczba-zaakceptowane, .liczba-zalegle, .liczba-anulowane {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.6rem; padding: .1rem .35rem; border-radius: 999px;
  font-size: .78rem; font-weight: 700;
}
.liczba-zaakceptowane { background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok); }
.liczba-zalegle { background: color-mix(in srgb, var(--uwaga) 16%, transparent); color: var(--uwaga); }
.liczba-anulowane { background: var(--bg-3); color: var(--tekst-2); }
.historia-ograniczona {
  margin: .4rem 0 0; padding: .4rem .6rem; border-radius: .5rem;
  background: var(--bg-3); color: var(--tekst-2); font-size: .85rem;
}
.historia-widocznosc { display: grid; gap: .6rem; }

/* Podsumowanie pod listą okresów (zgłoszenie #44). Obejrzane na /historia przy
   390 px we wszystkich trzech wariantach: blok był CZTEREMA identycznymi
   linijkami zwykłego tekstu — „Razem 0 zadań w 4 okresach” ważyło dokładnie
   tyle samo co rozbicie pod nim, `<ul>` schodził w pionie na pełną szerokość
   (361 px na pozycję) zamiast zbić się w rządek liczników, a `<footer>` nie był
   niczym odcięty od listy okresów nad nim. Liczby są tu treścią, więc dostają
   wagę; etykiety schodzą na drugi plan. */
.historia-suma { margin-top: .8rem; padding-top: .6rem; border-top: 1px solid var(--linia); }
.historia-suma-razem { margin: 0 0 .5rem; font-weight: 600; }
.historia-suma-liczby {
  display: flex; flex-wrap: wrap; gap: .5rem;
  margin: 0; padding: 0; list-style: none;
}
/* Kolory te same co przy plakietkach `.liczba-*` w wierszu okresu wyżej — ten
   sam fakt nie może mieć dwóch barw na jednym ekranie. Stan NIE zależy tu od
   koloru: każda pozycja ma obok liczby pełną etykietę słowną
   („zaakceptowane” / „zaległe” / „anulowane”), a kolor tylko ją wzmacnia. */
.historia-suma-liczby > li {
  display: flex; flex-direction: column; align-items: flex-start;
  min-width: 5.5rem; padding: .35rem .6rem;
  border-inline-start: 3px solid var(--linia); border-radius: .3rem;
  background: var(--bg-3);
}
.suma-liczba { font-size: 1.35rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.suma-etykieta { font-size: .78rem; color: var(--tekst-2); }
.suma-zaakceptowane { border-inline-start-color: var(--ok); }
.suma-zaakceptowane .suma-liczba { color: var(--ok); }
.suma-zalegle { border-inline-start-color: var(--uwaga); }
.suma-zalegle .suma-liczba { color: var(--uwaga); }
.suma-anulowane { border-inline-start-color: var(--tekst-2); }
.suma-anulowane .suma-liczba { color: var(--tekst-2); }

/* Nazwa dyżuru w wierszu luki na pulpicie administratora: odnośnik, ale ma
   czytać się jako etykieta wiersza — bez podkreślenia i bez koloru linku,
   bo cały wiersz i tak prowadzi w to samo miejsce. */
.okres-dyzur { font-weight: 600; color: var(--tekst); text-decoration: none; }
.okres-dyzur:hover { text-decoration: underline; }

/* ---------- 13e. Plan (dzień / tydzień / miesiąc) ---------- */
/* Szósty przypadek „znacznik jest, stylu nie ma" w tym projekcie: cały ekran
   planu jeździł na domyślnym wyglądzie przeglądarki, w tym siatka tygodnia
   złożona z divów jeden pod drugim. Wariantowe arkusze stylizowały wyłącznie
   nagłówek i oś dnia, więc brak reguł tutaj był widoczny we wszystkich trzech
   wariantach naraz.
   Luka używa TEJ SAMEJ konwencji co wydruk (druk.css: przerywana obwódka) i co
   ekran dyżurów — brak obsady ma wyglądać jednakowo na ekranie i na papierze. */
.plan { display: flex; flex-direction: column; gap: 1rem; }
.plan-naglowek { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.plan-naglowek h1 { margin: 0; flex: 1 1 100%; }
/* Strzałki okresu i „Drukuj A4" są odnośnikami, więc bez obwódki gubiły się
   w tekście — a to jedyna nawigacja po planie. Obwódka jak w pasku paginacji,
   żeby czytały się jako kontrolki, i „Drukuj" odsunięty na koniec wiersza. */
.plan-naglowek .przycisk-mikro { border: 1px solid var(--linia); }
.plan-naglowek .przycisk-mikro:last-child { margin-inline-start: auto; }

/* Mobile-first: jedna kolumna. Siatka siedmiu kolumn ma sens dopiero wtedy,
   gdy kolumna zmieści imię — poniżej tego rozmiaru tydzień czyta się jako
   lista dni, tak jak w makiecie wariantu „Lista". */
.plan-siatka { display: grid; gap: .5rem; grid-template-columns: 1fr; }
@media (min-width: 48rem) {
  .plan-tydzien .plan-siatka,
  .plan-miesiac .plan-siatka { grid-template-columns: repeat(7, minmax(0, 1fr)); }
}
/* Wariant „Kafelki" pokazuje plan jako listę kart, a „Oś dnia" jako os —
   obie nadpisują siatkę własnym układem i nie mogą dostać siedmiu kolumn. */
.plan-siatka.plan-lista, .plan-siatka.os { grid-template-columns: 1fr; }

.plan-dzien {
  display: flex; flex-direction: column; gap: .4rem;
  padding: .5rem; border: 1px solid var(--linia); border-radius: var(--promien);
  background: var(--bg-2); min-height: var(--dotyk);
}
/* Dzisiaj: obwódka akcentem, nie samo tło — tło znika przy druku i przy
   wysokim kontraście. */
.plan-dzien.dzis { border-color: var(--akcent); box-shadow: 0 0 0 1px var(--akcent); }
.plan-dzien.obcy-miesiac { opacity: .55; }
.plan-data { font-size: .8rem; font-weight: 700; color: var(--tekst-2); text-transform: uppercase; }

.plan-dyzur {
  display: flex; align-items: center; gap: .35rem; flex-wrap: wrap;
  font-size: .85rem; padding: .25rem .4rem; border-radius: .5rem; background: var(--bg-3);
}
.plan-dyzur.luka {
  background: color-mix(in srgb, var(--uwaga) 10%, transparent);
  border: 1px dashed var(--uwaga); color: var(--uwaga); font-weight: 600;
}
.plan-osoba { display: inline-flex; align-items: center; gap: .3rem; font-weight: 600; }

.plan-zadanie {
  display: flex; align-items: center; gap: .3rem; flex-wrap: wrap;
  min-height: var(--dotyk); padding: .35rem .4rem; border-radius: .5rem;
  font-size: .85rem; color: var(--tekst); text-decoration: none;
  border: 1px solid var(--linia);
}
.plan-zadanie:hover { background: var(--bg-3); }
.plan-zadanie.zalegle { border-color: var(--blad); }

.plan-legenda {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  padding-top: .5rem; border-top: 1px solid var(--linia);
  font-size: .85rem; color: var(--tekst-2);
}
.legenda-osoba { display: inline-flex; align-items: center; gap: .3rem; }

/* ---------- 13h. Audyt „znacznik jest, reguły nie ma" (#41, pozycja 32) ----------
   Dziewiąty i dziesiąty przypadek tego wzorca w projekcie (po .dyzur-kafel,
   .nawigacja-dolna, .uploads-*, .szablon-*, ekranie dyżurów, /plan, liczniku
   checklisty i martwej plakietce powiadomień). Wszystko poniżej sprawdzone
   ZRZUTAMI przy 390 i 1200 px, nie samą obecnością reguł — klasy, które nic
   nie niosą wizualnie, zostały świadomie bez reguły (lista w opisie zgłoszenia).
   Kolory wyłącznie ze zmiennych sekcji 1; każdy stan ma poza kolorem także
   kształt, obwódkę albo znak (design/README). */

/* --- Lista powiadomień (/powiadomienia) ---
   Karty listy noszą `powiadomienie-wpis`, a NIE `powiadomienie` — ta druga
   nazwa należy do pływającego toasta z sekcji 14 (position: fixed, tworzony
   przez app.js). Zanim nazwy rozdzielono, ekran /powiadomienia był PUSTY,
   a wszystkie karty leżały jedna na drugiej przy dolnej krawędzi okna.
   Dziewiąty przypadek wzorca „znacznik jest, wyglądu nie ma" w tym projekcie
   i najgorszy: nie „brzydko", a dwie trzecie danych niewidoczne. */
.lista-powiadomien { display: grid; gap: .6rem; }
.powiadomienie-wpis {
  padding: .8rem 1rem;
  /* Listwa stanu przezroczysta w stanie zwykłym — tekst nie skacze w poziomie
     po oznaczeniu powiadomienia jako przeczytane (ta sama zasada co .okres). */
  border-inline-start: 3px solid transparent;
}
.powiadomienie-wpis .powiadomienie-tresc { font-size: 1rem; overflow-wrap: anywhere; }
/* Nieprzeczytane: listwa akcentem + tło, a treść pogrubiona — do tego szablon
   dokłada słowo „nowe", więc stan czyta się także bez koloru. */
.powiadomienie-wpis.nieprzeczytane {
  border-inline-start-color: var(--akcent); background: var(--akcent-tlo);
}
.powiadomienie-wpis.nieprzeczytane .powiadomienie-tresc { font-weight: 600; }
/* Powiadomienie o błędzie: inny KSZTAŁT listwy (przerywana) plus znak przed
   treścią — kolor jest tu trzecim, nie jedynym nośnikiem informacji. */
.powiadomienie-wpis.powiadomienie-blad {
  border-inline-start-style: dashed; border-inline-start-color: var(--blad);
}
.powiadomienie-wpis.powiadomienie-blad .powiadomienie-tresc::before { content: "⚠ "; }

/* --- Checklista zadania ---
   Bulma (minireset) zeruje listom padding i znaczniki, więc <ul class="checklista">
   nie miała ŻADNEGO układu: przycisk odhaczenia stał w osobnym wierszu nad
   treścią pozycji. Wiersz na progu dotyku, przycisk i tekst w jednej linii. */
.checklista { display: grid; gap: .1rem; margin: .3rem 0 0; padding: 0; list-style: none; }
.checklista-pozycja {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  min-height: var(--dotyk); padding: .2rem 0;
}
.checklista-pozycja + .checklista-pozycja { border-top: 1px solid var(--linia); }
.checklista-pozycja form { display: flex; margin: 0; }
/* Ptaszek w .check pokazuje się po klasie .zrobiona (sekcja 9 — notatki),
   a pozycja checklisty nosi .zaznaczona. Bez tej reguły pozycja odhaczona
   w bazie wyglądała DOKŁADNIE jak nieodhaczona: zrzut /zadania/N pokazywał
   licznik „1/2" nad dwoma pustymi kwadratami. Stan niesie też przekreślenie
   treści, więc nie zależy wyłącznie od koloru wypełnienia. */
.checklista-pozycja.zaznaczona .check { background: var(--akcent); border-color: var(--akcent); }
.checklista-pozycja.zaznaczona .check svg { opacity: 1; transform: scale(1); }
.checklista-pozycja.zaznaczona > span:not([class]) { text-decoration: line-through; opacity: .7; }
/* Licznik przy nagłówku „Checklista" — plakietka, nie goły tekst: „1/2"
   w rozmiarze nagłówka czytało się jak część tytułu. */
.licznik {
  display: inline-block; margin-inline-start: .35rem; vertical-align: middle;
  padding: .05rem .5rem; border-radius: 999px;
  background: var(--bg-3); color: var(--tekst-2);
  font-size: .8rem; font-weight: 700;
}

/* --- Karta zadania: tytuł, metryczka, plakietki ---
   Wiersz karty układa `.lista-wiersz`, ale WYŁĄCZNIE w wariancie „Lista"
   (wariant-lista.css), więc te klasy muszą nieść własną typografię i odstępy
   — inaczej w pozostałych wariantach metryczka zlewa się w jeden ciąg
   („Kuchnia Antonina 1/2 wymaga dowodu 31 sierpnia 2026, 23:59”, tak jak na
   zrzucie /zadania i /historia). Sam UKŁAD wiersza zostaje w arkuszach
   wariantów — tutaj tylko zawartość. */
/* Bez `min-width: 0` świadomie: tytuł jest odnośnikiem z długimi wyrazami,
   a ściśnięty poniżej min-content wylewałby TEKST poza swoje pudełko i wchodził
   pod metryczkę (rect się nie nachodzą, więc widać to tylko na zrzucie).
   `break-word` (NIE `anywhere`) jest zabezpieczeniem na tytuł z jednego bardzo
   długiego słowa: `anywhere` wchodzi do wyliczania min-content i ścisnąłby
   kolumnę tytułu do czterech liter, `break-word` łamie tylko wtedy, gdy słowo
   naprawdę nie ma gdzie się zmieścić. */
.zadanie-tytul { flex: 1 1 40%; font-weight: 600; overflow-wrap: break-word; }
.zadanie-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: .2rem .5rem;
  margin: .15rem 0; color: var(--tekst-2); font-size: .85rem;
  min-width: 0; overflow-wrap: anywhere;
}
.zadanie-dyzur { color: var(--tekst); font-weight: 600; }
.zadanie-osoba { display: inline-flex; align-items: center; min-width: 0; }
/* Checklista i wymóg dowodu to liczby/warunki, nie zdania — plakietka
   oddziela je od daty stojącej w tym samym wierszu. */
.zadanie-checklista {
  display: inline-flex; align-items: center; gap: .2rem;
  padding: .05rem .45rem; border-radius: 999px;
  background: var(--bg-3); font-weight: 700; font-size: .78rem;
}
.zadanie-checklista::before { content: "✓"; font-weight: 400; }
/* Wymóg dowodu: obwódka i znak, nie samo słowo w kolorze ostrzeżenia. */
.zadanie-wymog {
  display: inline-flex; align-items: center; gap: .2rem;
  padding: .05rem .45rem; border-radius: 999px;
  border: 1px dashed var(--uwaga); color: var(--uwaga);
  font-weight: 600; font-size: .78rem;
}
.zadanie-wymog::before { content: "⚠"; }
/* Zadanie po terminie — REQ-013 wymaga wyraźnego oznaczenia „na wszystkich
   listach", a klasa `zalegle` na karcie nie miała dotąd ani jednej reguły
   (zgłoszenie #51): jedynym śladem zaległości była plakietka, identyczna
   w kolorze ze wszystkimi pozostałymi stanami. Pasek robimy WEWNĘTRZNYM
   cieniem, nie grubszą ramką: ramka przesunęłaby treść karty o 2 px, więc
   wiersz zaległy byłby węższy od sąsiadów (widać to na liście, gdzie stoją
   jeden pod drugim). `var(--cien)` trzeba powtórzyć, bo `box-shadow` nie
   dokłada się do wartości z `.karta`, tylko ją zastępuje.
   Kolor to `--blad`, spójnie z `.plan-zadanie.zalegle` i `.tablo-zalegle` —
   trzy ekrany, jedno znaczenie, jeden odcień. */
.zadanie.zalegle { box-shadow: inset 3px 0 0 var(--blad), var(--cien); }

/* --- Strona zadania: okruszki, opis, wymogi, formularz raportu, historia --- */
/* Okruszki miały rozmiar tekstu podstawowego i konkurowały z <h1> pod nimi. */
.okruszki { margin: 0 0 .2rem; color: var(--tekst-2); font-size: .85rem; }
.zadanie-opis { max-width: 44rem; overflow-wrap: anywhere; }
/* „To zadanie wymaga dowodu" to warunek przyjęcia raportu, nie ozdoba opisu —
   ta sama konwencja co .ostrzezenie, tylko ciszej (stoi wewnątrz formularza). */
.wymog {
  display: flex; align-items: center; gap: .4rem;
  margin: 0 0 .5rem; padding: .35rem .6rem; border-radius: .5rem;
  border: 1px solid var(--uwaga); color: var(--uwaga);
  background: color-mix(in srgb, var(--uwaga) 10%, transparent);
  font-size: .85rem;
}
.wymog::before { content: "⚠"; flex-shrink: 0; }
/* Oznaczenie raportu składanego za dziecko (REQ-014, #82). Wymog z koloru
   akcentu, nie uwagi — to informacja, nie ostrzeżenie: rodzic widzi, że
   podpisuje raport cudzego zadania. */
.wymog.w-imieniu { border-color: var(--akcent); color: var(--akcent);
  background: color-mix(in srgb, var(--akcent) 10%, transparent); }
.wymog.w-imieniu::before { content: "✎"; }
/* Formularz zgłoszenia wykonania — układ jak .formularz (etykieta nad polem). */
.raport { display: grid; gap: .5rem; justify-items: start; }
.raport label { color: var(--tekst-2); font-size: .9rem; }
.raport textarea { width: 100%; max-width: 44rem; }
.raport-zdjecia { width: 100%; }

/* Historia zadania. <ol> po minirezecie Bulmy nie ma paddingu, więc numery
   pozycji wypadały POZA kontener — na zrzucie 390 px widać ucięty znacznik
   przy samej krawędzi ekranu. Numer jest tu treścią (kolejność podejść),
   więc zostaje, tylko wraca do środka. */
.historia { display: grid; gap: .4rem; margin: .3rem 0 0; padding-inline-start: 1.6rem; }
.historia-wpis { padding-block: .25rem; }
.historia-wpis + .historia-wpis { border-top: 1px solid var(--linia); }
/* Rodzaj zdarzenia jest etykietą wpisu, autor i czas — przypisem do niej. */
.historia-typ { font-weight: 600; margin-inline-end: .35rem; }
.historia-autor { color: var(--tekst-2); margin-inline-end: .35rem; }
.historia-komentarz {
  margin: .2rem 0 0; padding-inline-start: .6rem;
  border-inline-start: 2px solid var(--linia);
  color: var(--tekst-2); overflow-wrap: anywhere;
}
/* Przesunięcie terminu przy odesłaniu do poprawki (REQ-015). Stoi POD
   komentarzem rodzica, bo jest jego decyzją, nie osobnym zdarzeniem — i musi
   być czytelne bez koloru, więc niesie słowo „Termin przesunięty na", a nie
   samą datę wyróżnioną odcieniem. */
.historia-termin { margin: .2rem 0 0; color: var(--tekst-2); font-size: .9rem; }
.historia-termin time { font-weight: 600; color: var(--tekst); }

/* Pozycja GPS zapisana przy zdarzeniu (REQ-007, zgłoszenie #71) — przy wpisie
   historii zadania I na karcie kolejki weryfikacji, ten sam partial i ten sam
   wygląd, bo mówią to samo zdanie.

   Wiersz ZAWIJA się (`flex-wrap`), a nie skraca: przy 390 px wiek pomiaru,
   dokładność i odnośnik nie mieszczą się w jednej linii, a żadnej z tych
   trzech rzeczy nie wolno uciąć — dokładność ucięta w połowie („dokładn…")
   znaczyłaby dla czytelnika tyle samo, co jej brak. */
.zdarzenie-pozycja {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .5rem;
  margin: .25rem 0 0; font-size: .9rem;
}
/* Etykieta „Pozycja:" — bez niej z karty kolejki nie było widać, CO zmierzono
   (zrzut 390 px). Cichsza od samej liczby, bo to podpis, nie odpowiedź. */
.zdarzenie-pozycja-etykieta { color: var(--tekst-2); }
.zdarzenie-pozycja-wiek { font-weight: 600; }
.zdarzenie-pozycja-czas { color: var(--tekst-2); }
/* Dokładność ma być widoczna jako LICZBA, nie jako szept — plakietka, tak samo
   jak na ekranie „Gdzie są domownicy": pozycja o dokładności 900 m nie znaczy
   tego samego co o dokładności 8 m (REQ-035). */
.zdarzenie-pozycja-dokladnosc {
  padding: .1rem .45rem; border-radius: .4rem; background: var(--bg-3);
  font-size: .85rem; font-variant-numeric: tabular-nums;
}
.zdarzenie-pozycja-wspolrzedne {
  color: var(--tekst-2); font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.zdarzenie-pozycja-mapa { flex-shrink: 0; }
/* Brak pozycji jest FAKTEM, nie awarią: ten sam ton, co „brak zapisanej
   pozycji" na ekranie domowników — cichy, ale nie ostrzegawczy. Kolor uwagi
   sugerowałby, że dziecko czegoś nie dopełniło, a odmowa zgody na lokalizację
   jest jego prawem (REQ-007). */
.zdarzenie-pozycja-brak {
  margin: .25rem 0 0; color: var(--tekst-2); font-size: .85rem; font-style: italic;
}

/* --- Kolejka weryfikacji (/sprawdzanie) ---
   „Akceptuję" i „Do poprawki" wyglądały IDENTYCZNIE (oba gołe .przycisk)
   i stały jeden pod drugim — na zrzucie 390 px nie da się rozpoznać, która
   z dwóch decyzji jest którą. Kolor nie może być jedynym nośnikiem, więc
   każda dostaje też znak: ✓ dla przyjęcia, ↩ dla odesłania. */
/* Kolumna kart kolejki. Oba warianty z własnym arkuszem dają jej jawny odstęp
   (`flex` + `gap`), a wariant „Lista" nie miał ANI JEDNEJ reguły — odstęp brał
   się tam z marginesu `.karta`. To dokładnie klasa błędu ze zgłoszenia #81:
   wygląd ma jeden wariant, a pozostałe jadą na czymkolwiek. Reguła bazowa
   ustawia ten sam układ dla wszystkich i zdejmuje margines karty, żeby odstęp
   miał JEDNO źródło (zgłoszenie #96). */
.lista-sprawdzanie { display: flex; flex-direction: column; gap: .9rem; margin-block: .9rem; }
.lista-sprawdzanie > .karta { margin-block: 0; }
.akcje-weryfikacji { display: inline-block; margin: .4rem .5rem .4rem 0; }
.poprawka { display: inline-block; vertical-align: top; margin: .4rem 0; }
.przycisk.ok, .przycisk.uwaga { font-weight: 600; }
.przycisk.ok {
  border-color: var(--ok); color: var(--ok);
  background: color-mix(in srgb, var(--ok) 12%, var(--bg-2));
}
.przycisk.ok::before { content: "✓"; }
.przycisk.ok:hover { background: color-mix(in srgb, var(--ok) 20%, var(--bg-2)); }
.przycisk.uwaga {
  border-color: var(--uwaga); color: var(--uwaga);
  background: color-mix(in srgb, var(--uwaga) 12%, var(--bg-2));
}
.przycisk.uwaga::before { content: "↩"; }
.przycisk.uwaga:hover { background: color-mix(in srgb, var(--uwaga) 20%, var(--bg-2)); }
/* <summary class="przycisk uwaga"> — bez tego przeglądarka dokłada własny
   trójkąt obok znaku „↩" i przycisk rozjeżdża się na dwie ikony. */
.poprawka > summary { list-style: none; cursor: pointer; }
.poprawka > summary::-webkit-details-marker { display: none; }
.poprawka > form { display: grid; gap: .4rem; justify-items: start; margin-top: .5rem; }
.poprawka > form label { color: var(--tekst-2); font-size: .9rem; }
.poprawka > form textarea, .poprawka > form input { width: 100%; max-width: 32rem; }
/* Karta kolejki: odstęp między metryczką a strefą decyzji — przyciski nie mogą
   przyklejać się do tekstu, który opisuje, co się właśnie akceptuje. */
.sprawdzanie h2 { margin-bottom: .2rem; }
.sprawdzanie > .zadanie-meta { margin-bottom: .5rem; }

/* --- Profil ---
   .profil-glowka jest flexem, a .profil-dane nie miała `min-width: 0`, więc
   nie mogła zejść poniżej szerokości najdłuższej treści: na zrzucie 390 px
   adres e-mail i formularz zmiany imienia WYCHODZĄ ZA prawą krawędź karty
   (pole „Imię" ściśnięte do trzech znaków, przycisk „Zapisz imię" poza
   kartą). Sam .formularz-inline miał już flex-wrap — nie miał jak zadziałać. */
.profil-dane { min-width: 0; flex: 1 1 auto; }
.profil-dane p { margin-block: .15rem; overflow-wrap: anywhere; }
.profil-podpis { font-size: .9rem; }
/* Karta profilu bywa wąska, a awatar „duzy" ma 5rem — na telefonie głowka
   ma się złożyć w dwa wiersze, nie ścisnąć danych do zera. */
.profil-karta { overflow-wrap: anywhere; }
@media (max-width: 26rem) { .profil-glowka { flex-wrap: wrap; } }

/* --- Ustawienia: kanały powiadomień ---
   Oba wiersze były kontenerami bez układu: nazwa kanału i jego przełącznik
   stały w osobnych wierszach, a kolejne rodzaje zdarzeń zlewały się w jedną
   kolumnę bez granic (zrzut /ustawienia, 390 i 1200 px). */
.kanal-wiersz {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
  min-height: var(--dotyk); padding: .4rem 0;
}
.kanal-wiersz + .kanal-wiersz { border-top: 1px solid var(--linia); }
.kanal-wiersz p { margin: 0; }
.kanal-wiersz .formularz-inline { margin-inline-start: auto; }
/* Rodzaj zdarzenia = jedna grupa: obwódka zamiast samego odstępu, bo trzy
   pola wyboru bez granicy nie mówią, do którego zdarzenia należą. */
.kanal-zdarzenie-wiersz {
  display: flex; flex-wrap: wrap; align-items: center; gap: .3rem 1rem;
  margin: .5rem 0 0; padding: .4rem .6rem;
  border: 1px solid var(--linia); border-radius: .5rem;
}
.kanal-zdarzenie-wiersz > p { flex: 1 1 100%; margin: 0; font-weight: 600; }
.kanal-zdarzenie-wiersz label { min-height: var(--dotyk); }
.kanal-zdarzenie-wiersz button { margin-inline-start: auto; border: 1px solid var(--linia); }

/* Komentarz przy akceptacji: zwijka o najmniejszej wadze wizualnej, bo
   najczęstsza akceptacja jest bez słów. Przycisk zwijki NIE może wyglądać jak
   „Akceptuję" obok — dlatego bez tła akcentu (ta sama zasada co przy strefach
   administracyjnych: ciszej znaczy zdjąć tło, nie tylko zmienić kolor tekstu). */
.pochwala > summary { background: transparent; border-color: var(--linia); color: var(--tekst-2); font-size: .85rem; }
.pochwala > summary:hover { background: var(--bg-3); color: var(--tekst); }
.pochwala textarea { width: 100%; max-width: 32rem; }

/* Formularz nowego zadania (REQ-012): etykieta nad polem, pola wyboru
   w jednym wierszu z opisem. Bez tych reguł formularz renderowałby się jako
   goły stos elementów — dziesiąty raz w tym projekcie ten sam wzorzec
   („znacznik jest, wyglądu nie ma"), więc reguły idą razem ze szablonem. */
.nowe-zadanie { margin-block: .8rem; }
.formularz-zadania { display: grid; gap: .5rem; margin-top: .5rem; }
.formularz-zadania label { font-size: .9rem; color: var(--tekst-2); }
.formularz-zadania input, .formularz-zadania select, .formularz-zadania textarea { max-width: 32rem; }
.formularz-zadania .opis { max-width: 44rem; }

/* Fieldset terminu — dwie pary etykieta+pole (data i godzina) w formularzu
   „Nowe zadanie" (REQ-011) i w odesłaniu do poprawki (REQ-015). Rozdzielenie
   terminu na dwa pola przyszło ze zgłoszeniem #79, a fieldset został wtedy BEZ
   ANI JEDNEJ REGUŁY. Skutek widać dopiero na zrzucie: pola `date`/`time` mają
   szerokość z treści, więc w przepływie inline etykieta „Godzina" stawała OBOK
   pola daty, a samo pole godziny spadało do następnego wiersza — para
   etykieta–pole rozjeżdżała się i nie było widać, co do czego należy. Do tego
   przeglądarka rysowała fieldsetowi własną ramkę, jedyną taką w formularzu.
   Siatka jednokolumnowa układa pary tak samo jak resztę formularza
   (`.formularz label` też jest siatką), `justify-items: start` zostawia polom
   szerokość z treści, a `border: 0` zdejmuje ramkę przeglądarki. */
.pole-terminu {
  display: grid; gap: .3rem; justify-items: start;
  margin: 0; padding: 0; border: 0;
}
.pole-terminu > legend { padding: 0; font-size: .9rem; color: var(--tekst-2); }

/* Onboarding rodziny (REQ-008, FEAT-028). Ten sam układ siatki co formularz
   zadania — oba są formularzami „kilka pól pod sobą" w karcie, a dwa różne
   wyglądy dla tej samej roli tylko dodałyby wrażenie, że robią coś innego. */
.zaloz-rodzine { margin-block: 1rem; }
.formularz-rodziny { display: grid; gap: .5rem; margin-top: .6rem; }
.formularz-rodziny label { font-size: .9rem; color: var(--tekst-2); }
.formularz-rodziny input { max-width: 24rem; }
.nowy-domownik { margin-top: .8rem; }
.formularz-domownika { display: grid; gap: .5rem; margin-top: .5rem; }
.formularz-domownika label { font-size: .9rem; color: var(--tekst-2); }
.formularz-domownika input, .formularz-domownika select { max-width: 24rem; }
.formularz-domownika .opis { max-width: 44rem; }
.pole-wyboru {
  display: flex; align-items: center; gap: .4rem;
  min-height: var(--dotyk); color: var(--tekst);
}

/* Odzyskiwanie hasła: potwierdzenie wysyłki i podpowiedzi. Komunikat jest
   `role="status"`, więc musi się wyróżniać także dla kogoś, kto go nie szuka. */
.odzyskiwanie-info {
  padding: .5rem .7rem; border-radius: .5rem;
  background: var(--akcent-tlo); color: var(--tekst);
}
.odzyskiwanie-podpowiedz, .logowanie-zapomniane { font-size: .85rem; color: var(--tekst-2); }
.logowanie-zapomniane { margin-top: .6rem; text-align: center; }
.odzyskiwanie-powrot { margin-top: .8rem; }

/* Sekcja przesuwania dat demonstracyjnych (REQ-029): narzędzie do pokazów, nie
   funkcja domowa — więc wyraźnie oddzielona od reszty ustawień i utrzymana
   w tonie ostrzegawczym, bo zmienia WSZYSTKIE dane instalacji. */
.demo-daty { border: 1px dashed var(--uwaga); }
.demo-daty-akcje { display: flex; flex-wrap: wrap; gap: .5rem; align-items: end; }
.demo-daty-akcje form { margin: 0; }

/* ---------- 14. Tryby interakcji i dostępność ---------- */
/* Baza ma już 44 px (patrz --dotyk w sekcji 1); tryb „dotyk" idzie wyżej,
   do 48 px, bo tam palec jest jedynym wskaźnikiem. */
html[data-resolved-mode="touch"] { --dotyk: 3rem; --dotyk-glowny: 3.75rem; }
html[data-resolved-mode="touch"] .notatka { padding: .95rem 1rem; }
:focus-visible { outline: 2px solid var(--akcent); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
  .orb { display: none; }
}
html[data-anim="off"] .orb, html[data-anim="off"] .pierscien, html[data-anim="off"] .pulsar { animation: none; display: none; }
html[data-anim="off"] .wejscie, html[data-anim="off"] .htmx-added { animation: none; }

/* Banner trybu testowego/deweloperskiego (PageData.Dev) */
.pasek-testowy {
  position: sticky; top: 0; z-index: 60;
  text-align: center; font-size: .8rem; font-weight: 600;
  padding: 2px 8px; letter-spacing: .03em;
  color: #1a1204; background: #f0b429;
}

/* Reguły `.paginacja-info`, `.paginacja-numer`, `.paginacja-skok`,
   `.paginacja-input` i `.paginacja-linki` (części wspólnego panelu paginacji
   ze szkieletu, SREQ-063) usunięto w 0.4.x, zgłoszenie #66. Sam partial
   `paginacja` szkieletu odszedł już w 0.2.4 razem z obsługą skoku w `app.js`
   (zgłoszenie #44, pozycja 43 w docs/ROZBIEZNOSCI.md) — arkusza wtedy nie
   posprzątano, więc pięć reguł zostało bez ani jednego elementu w szablonach.
   Reguła bazowa `.paginacja` ZOSTAJE (sekcja 6): jej nadal używa pasek
   numerowanych stron w `demo_tabele.tmpl`. */

/* SREQ-068 — guzik odświeżenia treści i powiadomienie o błędzie/timoucie. */
.odswiez-btn { position: relative; }
.odswiez-btn.htmx-request { background: var(--akcent-tlo); color: var(--akcent); pointer-events: none; }
.odswiez-btn.htmx-request .odswiez-etykieta { visibility: hidden; }
.odswiez-btn.htmx-request::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 1em; height: 1em; border: 2px solid var(--linia); border-top-color: var(--akcent);
  border-radius: 50%; animation: spinner-obrot .7s linear infinite;
}
.odswiez-btn.blad { background: var(--blad); color: light-dark(#fff, #1a0505); }
.odswiez-btn.timeout { background: var(--uwaga); color: #1b150d; }
.powiadomienie {
  position: fixed; bottom: 5rem; left: 50%; transform: translateX(-50%);
  max-width: 24rem; padding: .7rem 1rem; border-radius: var(--promien);
  background: var(--bg-2); border: 1px solid var(--linia); box-shadow: var(--cien-2);
  display: flex; align-items: center; gap: .6rem; z-index: 50;
  animation: wejscie .3s cubic-bezier(.2, .8, .2, 1) both;
}
.powiadomienie.blad { border-color: var(--blad); }
/* Selektor był MARTWY: app.js buduje <span class="powiadomienie-tresc">, a nie
   element z dwiema klasami .powiadomienie.tresc — treść toasta nie dostawała
   rozmiaru. Po rozdzieleniu nazw (lista używa .powiadomienie-wpis) nie trzeba
   już zawężać przez :not(.karta). */
.powiadomienie > .powiadomienie-tresc { flex: 1; font-size: .9rem; }
.powiadomienie-zamknij {
  border: none; background: transparent; color: var(--tekst-2); cursor: pointer;
  font-size: 1.1rem; width: var(--dotyk); height: var(--dotyk);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.powiadomienie-zamknij:hover { color: var(--tekst); }

/* Stopka z wersją i datą kompilacji (#9) */
.stopka {
  text-align: center; padding: 12px 8px 20px;
  font-size: .72rem; color: var(--tekst-wyciszony, #8a8a94);
}

/* Przewodnik „Pomoc" (REQ-028 / FEAT-026).

   Strona jest tekstem do czytania, a nie interfejsem, więc reguły dotyczą
   głównie długości wiersza i rytmu akapitów. Bez `style=""` w szablonie — CSP
   bez unsafe-inline i tak by go wyciął (TestNoInlineStyle).

   Klasy wariantowe (`pomoc-kafelki`, `pomoc-osdnia`) stoją TUTAJ, a nie
   w wariant-*.css, bo tamte pliki nie są częścią tej zmiany, a selektor
   z klasą wariantu i tak nie działa nigdzie poza stroną tego wariantu. */
.pomoc { max-width: 46rem; }
.pomoc-sekcja { margin-bottom: 1rem; }
.pomoc-sekcja h3 {
  font-size: .95rem; margin: 1rem 0 .35rem; color: var(--tekst);
}
.pomoc-sekcja p { margin: 0 0 .6rem; line-height: 1.55; }
.pomoc-sekcja p:last-child { margin-bottom: 0; }

/* Spis treści: cele dotykowe co najmniej 44 px w pionie (REQ-027), zawijane,
   więc mieszczą się na ekranie 390 px bez przewijania w poziomie. */
.pomoc-spis {
  display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 1rem;
}
.pomoc-spis a {
  display: flex; align-items: center; min-height: var(--dotyk, 44px);
  padding: .35rem .7rem; border: 1px solid var(--linia);
  border-radius: var(--promien); background: var(--bg-2);
  color: var(--tekst); font-size: .85rem; text-decoration: none;
}
.pomoc-spis a:hover { border-color: var(--akcent); color: var(--akcent); }

/* Kroki i listy. Numeracja kroków jest treścią (kolejność ma znaczenie), więc
   zostaje <ol> z widoczną liczbą; wyliczenia bez kolejności dostają kropkę. */
.pomoc-kroki, .pomoc-lista { margin: 0 0 .7rem 1.2rem; padding: 0; }
.pomoc-kroki { list-style: decimal; }
.pomoc-lista { list-style: disc; }
.pomoc-kroki li, .pomoc-lista li { margin-bottom: .35rem; line-height: 1.5; }
.pomoc-kroki li::marker, .pomoc-lista li::marker { color: var(--tekst-2); }

/* Wariant „Kafelki": spis treści jako siatka dużych celów, po dwa w rzędzie
   na telefonie — ten sam język co kafle pulpitu. */
:root[data-wariant="kafelki"] .pomoc-kafelki .pomoc-spis {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}
:root[data-wariant="kafelki"] .pomoc-kafelki .pomoc-spis a {
  justify-content: center; text-align: center; min-height: 56px;
}

/* Wariant „Oś dnia": proste kanty i linia rozdzielająca zamiast obramowania
   karty — sekcje czytają się wtedy jak kolejne wpisy, nie jak osobne kafle. */
:root[data-wariant="osdnia"] .pomoc-osdnia .pomoc-sekcja {
  border-radius: var(--promien); border-left: 2px solid var(--linia);
}
:root[data-wariant="osdnia"] .pomoc-osdnia .pomoc-spis a { border-radius: var(--promien); }

/* ── Walidator obciążenia dyżurami (REQ-030, FEAT-029) ─────────────────────
   Nazwy klas sprawdzone gerpem przed nadaniem: prefiks `obciazenie-` nie
   występował w projekcie. Pułapka 13 w docs/HANDOFF.md — kolizja nazw jest tu
   najczęstszą klasą błędu i objawia się tym, że OBIE strony są poprawne,
   a ekran wygląda nie tak. */
.obciazenie .obciazenie-sprawdz { margin-bottom: .6rem; }

/* Wynik dociągany fragmentem — dopóki administrator nie kliknie, kontener jest
   pusty i nie zajmuje miejsca. */
.obciazenie-wynik:empty { display: none; }
.obciazenie-liczby { margin-bottom: .6rem; }
.obciazenie-tabela td:not(:first-child), .obciazenie-tabela th:not(:first-child) {
  text-align: right; font-variant-numeric: tabular-nums;
}
.obciazenie-tabela td:first-child { font-weight: 600; }

/* Szczyt równoległości jest OSTRZEŻENIEM, więc dostaje kolor uwagi, a nie sam
   pogrubiony napis — liczba „3 ×" ma się rzucać w oczy w tabeli liczb. */
.badge.obciazenie-szczyt {
  background: color-mix(in srgb, var(--uwaga) 18%, var(--bg-3)); color: var(--uwaga);
}
/* Nazwy kolidujących dyżurów (zgłoszenie #66) idą do NOWEGO WIERSZA pod
   plakietką, a nie obok niej: kolumna „Jednocześnie" jest ostatnia i najwęższa,
   a przy 390 px trzy nazwy w jednej linii rozpychały tabelę w poziom, czego
   REQ-026 zabrania. Mniejszy stopień pisma i kolor drugoplanowy, bo to
   uszczegółowienie ostrzeżenia, nie drugie ostrzeżenie. */
.obciazenie-szczyt-dyzury {
  display: block; margin-top: .2rem;
  font-size: .8rem; font-weight: 400; color: var(--tekst-2);
}

.obciazenie-problemy { list-style: none; margin: .8rem 0 0; padding: 0; }
.obciazenie-problem {
  border: 1px solid var(--linia); border-left: 3px solid var(--uwaga);
  border-radius: var(--promien); background: var(--bg-3);
  padding: .7rem .8rem; margin-bottom: .6rem;
}
/* Luka jest przyczyną, nie winą kogokolwiek — stąd kolor akcentu zamiast
   ostrzeżenia; jej propozycja nikomu nic nie odbiera. */
.obciazenie-problem.obciazenie-luka { border-left-color: var(--akcent); }
.obciazenie-opis { margin: 0 0 .5rem; line-height: 1.5; }

.obciazenie-propozycja {
  border-top: 1px solid var(--linia); padding-top: .5rem;
  display: flex; flex-direction: column; gap: .35rem; align-items: flex-start;
}
.obciazenie-propozycja-tresc { margin: 0; font-weight: 600; }
.obciazenie-uzasadnienie { margin: 0; }
.obciazenie-propozycja .przycisk { min-height: var(--dotyk, 44px); }

.obciazenie-rowno {
  margin: .8rem 0 0; padding: .6rem .8rem; line-height: 1.5;
  border: 1px solid color-mix(in srgb, var(--ok) 40%, var(--linia));
  border-radius: var(--promien);
  background: color-mix(in srgb, var(--ok) 10%, var(--bg-3)); color: var(--tekst);
}

/* Wariant „Kafelki": panel diagnozy jest AKCJĄ, więc przycisk dostaje rozmiar
   celu głównego jak reszta kafelkowych akcji. Wariant „Oś dnia": płaskie kanty
   i lewa linia, tak jak inne bloki osi. */
:root[data-wariant="kafelki"] .obciazenie .obciazenie-sprawdz {
  width: 100%; min-height: var(--dotyk-glowny, 56px);
}
:root[data-wariant="osdnia"] .obciazenie-problem {
  background: none; border: none; border-left: 2px solid var(--uwaga);
  border-radius: 0; padding-left: .7rem;
}
/* UWAGA, tu brakowało jednej klamry (znalezione przy #55). Reguła powyżej była
   ostatnią w pliku, gdy powstawała (walidator obciążenia, #47), więc niczego
   nie psuła — ale każda sekcja DOPISANA POD NIĄ wpadała już do środka tego
   bloku deklaracji. Parser CSS zjada wtedy kolejne reguły jako niepoprawne
   deklaracje (razem z ich klamrami) i nie odzyskuje się do końca pliku: martwe
   były WSZYSTKIE reguły od tego miejsca w dół — kreator dyżurów (REQ-031),
   oceny kciukiem (REQ-032) i całe tablo (REQ-033). Nic się nie zapaliło na
   czerwono: HTML poprawny, testy struktury zielone, ekrany po prostu jechały
   na domyślnym wyglądzie przeglądarki. To ten sam wzorzec co „znacznik bez
   kodu" z docs/HANDOFF.md, tylko od drugiej strony — i dlatego łatka zawijania
   z sekcji 16 „naprawiona przy ocenach" nigdy nie działała. */
/* --- Kreator dyżurów (REQ-031, FEAT-030) ---------------------------------- */
/* Cały ekran kreatora. Prefiks `kreator-` na KAŻDEJ klasie jest świadomy:
   kolizje nazw są w tym projekcie najczęstszą klasą błędu (jedenaście
   przypadków — `.dyzur-kafel` z pulpitu zwinęła kartę dyżuru w słupek), więc
   nowy ekran nie pożycza ani jednej nazwy od sąsiadów. Sprawdzone gregiem
   przed nadaniem: przed tą zmianą ciąg „kreator" nie występował w repozytorium
   ani raz. */
.kreator { display: block; }

/* Wejście z ekranu „Dyżury": akcja i zdanie wyjaśniające obok siebie, na
   telefonie jedno pod drugim. */
.kreator-wejscie {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  margin: 0 0 1rem;
}
.kreator-wejscie .opis { margin: 0; }

/* Wskaźnik postępu. Numer kroku jest TREŚCIĄ (kolejność ma znaczenie), więc
   zostaje widoczną liczbą, a nie kropką; krok bieżący ma dodatkowo aria-current
   w znaczniku, bo stan nie może zależeć wyłącznie od koloru (design/README). */
.kreator-postep {
  display: flex; flex-wrap: wrap; gap: .4rem;
  margin: 0 0 1rem; padding: 0; list-style: none;
}
.kreator-etap {
  display: flex; align-items: center; gap: .4rem;
  padding: .3rem .7rem; min-height: var(--dotyk);
  border: 1px solid var(--linia); border-radius: var(--promien);
  background: var(--bg-2); color: var(--tekst-2); font-size: .85rem;
}
.kreator-etap-numer {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; flex-shrink: 0;
  border-radius: 50%; border: 1px solid var(--linia);
  font-size: .75rem; font-weight: 700;
}
/* Nazwa kroku na wąskim ekranie ma prawo się zawinąć, ale nie łamać wyrazów
   w połowie — „Podsumowanie" jest najdłuższe i wyznacza szerokość. */
.kreator-etap-nazwa { overflow-wrap: normal; }
.kreator-etap.zrobiony { color: var(--tekst); }
.kreator-etap.zrobiony .kreator-etap-numer { border-color: var(--akcent); color: var(--akcent); }
.kreator-etap.biezacy {
  border-color: var(--akcent); color: var(--tekst); font-weight: 600;
}
.kreator-etap.biezacy .kreator-etap-numer {
  background: var(--akcent); border-color: var(--akcent); color: var(--bg-1);
}

/* Karta jednego kroku. Wygląd bierze z .karta — tu tylko układ pól w kolumnie,
   jak .formularz na innych ekranach. */
.kreator-krok { display: grid; gap: .8rem; }
.kreator-krok label { display: grid; gap: .3rem; font-size: .9rem; color: var(--tekst-2); }
.kreator-krok textarea { width: 100%; max-width: 44rem; }
/* Komunikat odmowy: wygląd bierze z .ostrzezenie (ikona ⚠ i obramowanie),
   tu tylko odstęp — nadpisywanie samego koloru tekstu na kolorowym tle było
   w tym projekcie już trzy razy błędem kontrastu (pułapka 12 w HANDOFF). */
.kreator-blad { margin: 0; }

/* Propozycje obszarów i wiersze obszaru: każdy cel dotykowy co najmniej 44 px
   (REQ-027), zawijane, więc mieszczą się na ekranie 390 px. */
.kreator-propozycje, .kreator-obszar {
  display: grid; gap: .5rem; margin: 0; padding: .7rem .8rem;
  border: 1px solid var(--linia); border-radius: var(--promien);
}
.kreator-propozycje legend, .kreator-obszar legend {
  padding-inline: .35rem; color: var(--tekst); font-weight: 600; font-size: .95rem;
}
.kreator-propozycja, .kreator-zadanie, .kreator-uczestnik {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  min-height: var(--dotyk);
}
.kreator-propozycja > label, .kreator-zadanie > label, .kreator-uczestnik > label {
  display: flex; flex-wrap: wrap; align-items: center; gap: .45rem;
  min-height: var(--dotyk); color: var(--tekst); font-size: .95rem;
}
.kreator-propozycja-nazwa, .kreator-zadanie-tytul { color: var(--tekst); }
.kreator-propozycja-opis, .kreator-zadanie-cykl {
  color: var(--tekst-2); font-size: .8rem;
}
/* Numer w kolejce stoi po prawej i nie rozpycha wiersza — to samo pole co
   w formularzu rotacji na ekranie „Dyżury". */
.kreator-uczestnik { justify-content: space-between; }
.kreator-pozycja { flex: 0 0 auto; font-size: .8rem; }
.kreator-pozycja input { width: 4.5rem; }

/* Podsumowanie: lista dyżurów, które powstaną. Bez numeracji — kolejność
   dyżurów niczego nie znaczy, w odróżnieniu od kolejności kroków i rotacji. */
.kreator-podsumowanie { display: grid; gap: .7rem; margin: 0; padding: 0; list-style: none; }
.kreator-podsumowanie-dyzur {
  padding: .7rem .8rem; border: 1px solid var(--linia); border-radius: var(--promien);
  border-left: 3px solid var(--akcent);
}
.kreator-podsumowanie-dyzur h3 { font-size: 1rem; margin: 0 0 .3rem; }
.kreator-podsumowanie-dyzur p { margin: 0 0 .35rem; }
.kreator-podsumowanie-rotacja { color: var(--tekst); font-size: .9rem; }
.kreator-podsumowanie-zadania {
  display: grid; gap: .25rem; margin: .35rem 0 0; padding-inline-start: 1.2rem;
  list-style: disc; font-size: .9rem;
}
.kreator-podsumowanie-zadania li::marker { color: var(--tekst-2); }

/* Nawigacja kroków: akcja główna i cofnięcie w jednym rzędzie, rezygnacja
   CISZEJ — bez tła, nie tylko innym kolorem tekstu (pułapka 12 w HANDOFF:
   ta sama pomyłka z kontrastem zdarzyła się już trzy razy). */
.kreator-nawigacja {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  margin-top: .4rem;
}
.kreator-rezygnacja {
  display: inline-flex; align-items: center; min-height: var(--dotyk);
  background: none; border: 0; padding-inline: .3rem;
  color: var(--tekst-2); font-size: .85rem;
}
.kreator-rezygnacja:hover { color: var(--akcent); }

/* Wariant „Kafelki": wskaźnik postępu jako siatka dużych celów, po dwa-trzy
   w rzędzie na telefonie — ten sam język co kafle pulpitu i spis treści
   przewodnika. */
:root[data-wariant="kafelki"] .kreator-kafelki .kreator-postep {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
}
:root[data-wariant="kafelki"] .kreator-kafelki .kreator-etap { min-height: 56px; }
:root[data-wariant="kafelki"] .kreator-kafelki .kreator-nawigacja .przycisk.glowny {
  min-height: var(--dotyk-glowny);
}

/* Wariant „Oś dnia": kreator JEST sekwencją, więc wskaźnik postępu dostaje
   pion z lewą szyną — tutaj podziałka mówi prawdę o porządku kroków, inaczej
   niż na profilu i w przewodniku, gdzie oś świadomie nie występuje. */
:root[data-wariant="osdnia"] .kreator-osdnia .kreator-postep {
  display: grid; gap: 0; border-left: 2px solid var(--linia); padding-left: .7rem;
}
:root[data-wariant="osdnia"] .kreator-osdnia .kreator-etap {
  border: 0; border-radius: 0; background: none; padding-inline: 0;
}
:root[data-wariant="osdnia"] .kreator-osdnia .kreator-etap + .kreator-etap {
  border-top: 1px solid var(--linia);
}
:root[data-wariant="osdnia"] .kreator-osdnia .kreator-podsumowanie-dyzur,
:root[data-wariant="osdnia"] .kreator-osdnia .kreator-obszar {
  border-radius: var(--promien);
}
/* ---------- 16. Ocena wykonania kciukiem (REQ-032) ---------- */
/* Nazwy `.ocena*`, `.kciuk-*` i `.ikona-kciuk*` sprawdzone grepem PRZED
   nadaniem — w projekcie nie było ani jednej takiej klasy. Kolizje nazw są tu
   najczęstszą klasą błędu (.dyzur-kafel zwinęła kartę dyżuru w słupek,
   .powiadomienie wywróciło całą listę powiadomień).

   Blok to wiersz dwóch kciuków z licznikiem w środku, więc wygląda tak samo
   w karcie zadania i pod kartą dyżuru.

   ROZMIAR (zgłoszenie #61). Kciuk jest akcją DRUGORZĘDNĄ i musi czytać się jak
   plakietka stojąca obok, nie jak przycisk główny wiersza. Punkt odniesienia
   jest jawny: `.zadanie-checklista` / `.zadanie-wymog` (font .78rem, padding
   .05–.15rem) i metryczka `.zadanie-meta` (.85rem). Pierwotne 34 px wysokości
   przy ikonie 1.05rem dawało kontrolkę wyższą niż dwie trzecie wiersza listy
   (`--gestosc-wiersza: 3.25rem` = 52 px) — właściciel produktu zobaczył ją
   jako „za duże ikonki". Dziś malowane pudełko ma ~20 px, czyli mniej niż
   40 % wiersza, i tego pilnuje TestKciukNieDominujeWiersza.

   CEL DOTYKOWY zostaje przy 44 px (design/README: przełączniki ≥44 px), ale
   NIE przez powiększanie malowanego pudełka — powiększa go przezroczysty
   `::after`. Rozstrzygnięcie geometryczne, nie na wyczucie: 44 < 52 px wiersza,
   więc pole dotyku mieści się w SWOIM wierszu i nie kradnie kliknięć
   odnośnikowi tytułu w wierszu sąsiednim.

   KOLOR I WŁASNY GŁOS (zgłoszenie #61). Kciuk w górę bierze `--ok`, kciuk
   w dół `--blad` — istniejące tokeny semantyczne, nie nowe barwy, bo aplikacja
   ma sześć schematów × dwa tryby. Poprzednia wersja malowała kciuk w dół
   `--uwaga`, a w schemacie „Bursztyn" `--uwaga` to DOKŁADNIE ta sama wartość
   co `--akcent` (light-dark(#b45309, #fbbf24)) — kciuk „nie" wyglądał tam jak
   akcja główna. `--blad` tej kolizji nie ma w żadnym schemacie.

   Własny głos NIE jest niesiony kolorem. Tło wybranego kciuka różni się od
   niewybranego tylko 1.47:1 w luminancji (najgorzej: Malina/ciemny), czyli
   w skali szarości jest praktycznie niewidoczne — samo tło jako nośnik stanu
   BY NIE WYSTARCZYŁO. Nośnikiem jest KSZTAŁT IKONY: kciuk możliwy do oddania
   jest OBRYSOWANY (`fill: none; stroke: currentColor`), kciuk już oddany jest
   WYPEŁNIONY (`fill: currentColor`). Ta sama jedna ścieżka, dwa rysunki — bez
   drugiej kopii kształtu, o którą prosi komentarz w partialu. Do tego pogrubiony
   licznik, obwódka w barwie semantycznej i — niezależnie od wyglądu —
   aria-pressed, bo kolor nie jest informacją (design/README.md). */
.ocena { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; margin-top: .35rem; }
.ocena-glos { margin: 0; }
.ocena-kciuk {
  position: relative;
  display: inline-flex; align-items: center; gap: .25rem;
  padding: .15rem .4rem;
  border: 1px solid var(--linia); border-radius: var(--promien);
  background: transparent; color: var(--tekst-2); cursor: pointer;
  font: inherit; font-size: .78rem; line-height: 1;
  transition: background var(--przejscie), border-color var(--przejscie), color var(--przejscie);
}
/* Pole dotyku szersze niż rysunek — patrz akapit „CEL DOTYKOWY" wyżej.
   Przezroczyste, więc nie zmienia wyglądu; wyśrodkowane przez 50 % + translate,
   żeby rosło symetrycznie w obie strony niezależnie od rozmiaru pudełka. Tylko
   dla PRZYCISKU: wersja bierna (`.ocena-kciuk-bierny`) jest <span>-em, nie ma
   czego klikać, a pole dotyku kradłoby zaznaczanie tekstu obok. */
button.ocena-kciuk::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 100%; min-width: var(--dotyk); height: var(--dotyk);
}
.ocena-kciuk:active { transform: scale(.94); }
/* Ikona: rozmiar bliżej plakietki niż przycisku. `stroke-width` w jednostkach
   viewBoxa (24), więc przy ~14 px rysunku obrys wychodzi ~0,9 px. */
.ikona-kciuk {
  width: .85rem; height: .85rem; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linejoin: round; stroke-linecap: round;
}
/* Kciuk w dół to ten sam kształt obrócony — jedna ścieżka, dwa znaczenia.
   Reguła dla `gore` jest JAWNA, mimo że nic nie zmienia: klasa bez ani jednej
   reguły to w tym projekcie osobna klasa błędu (cały ekran dyżurów jeździł
   kiedyś na domyślnym wyglądzie przeglądarki), więc symetria jest tu tańsza
   niż zastanawianie się, czy o czymś nie zapomniano. */
.ikona-kciuk-gore { transform: none; }
.ikona-kciuk-dol { transform: rotate(180deg); }
.ocena-liczba { font-variant-numeric: tabular-nums; min-width: .8rem; text-align: center; }

/* Barwa semantyczna niesie ZNACZENIE kciuka (w górę / w dół) i jest widoczna
   już w stanie „mogę zagłosować" — o to prosiło zgłoszenie #61. Barwę bierze
   IKONA; licznik zostaje w --tekst-2, żeby dwie liczby obok siebie nie
   krzyczały na siebie kolorami. Obrys czystą barwą na --bg-2 daje najgorzej
   4.93:1 (Bursztyn/jasny, --ok) — grafika potrzebuje 3:1, więc z zapasem. */
.kciuk-gore { --kciuk-barwa: var(--ok); }
.kciuk-dol  { --kciuk-barwa: var(--blad); }
.ocena-kciuk .ikona-kciuk { stroke: var(--kciuk-barwa); }

/* WYBRANY = mój głos. Trzy nośniki naraz, z czego DWA bezbarwne:
     1. ikona WYPEŁNIONA zamiast obrysowanej (kształt — działa w skali szarości
        i przy każdym rodzaju daltonizmu),
     2. pogrubiony licznik (waga),
     3. tło i obwódka w barwie semantycznej (kolor — dodatek, nie nośnik).
   Licznik przechodzi na barwę mieszaną z --tekst w 65 %: czysta barwa na
   tle przytłumionym własną barwą schodzi do 3.22:1 (Malina/jasny), a mieszanka
   65 % daje najgorzej 4.94:1 (Ocean/jasny, --ok) — próg 4.5:1 dla małego tekstu
   spełniony we WSZYSTKICH sześciu schematach × dwóch trybach. Nie podnoś tego
   udziału bez przeliczenia: przy 80 % najgorsza para spada do 4.16:1. */
.ocena-kciuk.wybrany {
  background: color-mix(in srgb, var(--kciuk-barwa) 18%, var(--bg-3));
  border-color: var(--kciuk-barwa);
  color: color-mix(in srgb, var(--kciuk-barwa) 65%, var(--tekst));
  font-weight: 700;
}
.ocena-kciuk.wybrany .ikona-kciuk { fill: currentColor; stroke: none; }

/* Wersja bez przycisków (własne wykonanie, zadanie przed zgłoszeniem):
   ZDEJMUJEMY tło, obwódkę i kursor, zamiast tylko przygaszać tekst — ma być
   ciszej, nie mniej czytelnie. Ikona zostaje obrysowana i w barwie
   semantycznej: to nadal informacja, w którą stronę liczy się liczba obok. */
.ocena-kciuk-bierny { background: none; border-color: transparent; cursor: default; }
/* Powód ZAWSZE w osobnej linii bloku — na jednej linii z kciukami ścisnąłby je
   do kilku pikseli w karcie zadania. */
.ocena-powod { color: var(--tekst-2); font-size: .8rem; flex: 1 1 100%; }
/* W KARCIE zadania wiersz jest flexem BEZ zawijania (wariant „Lista"), więc
   bez własnej szerokości blok kurczył się do słupka: kciuk pod kciukiem,
   podwójna wysokość wiersza. Widać to wyłącznie na zrzucie przy 390 px —
   struktura jest wtedy nadal poprawna, więc żaden test tego nie łapie.

   Szerokość przeliczona razem ze zmniejszeniem kciuków (#61) i to jest tu
   WARUNEK, nie kosmetyka: min-width musi przekraczać sumę szerokości PARY
   kciuków, inaczej `flex-wrap` (potrzebny dla `.ocena-powod`) zrzuca drugi
   kciuk pod pierwszy. Pomiar: pudełko kciuka 45 px, przerwa .35rem, więc para
   zajmuje ~96 px — 6.5rem (104 px) wystarcza z zapasem. Przy poprzednich
   34-px kciukach para miała ~113 px i te same 6.5rem były ZA MAŁE: blok stał
   słupkiem. Zmieniasz rozmiar kciuka — przelicz i to. */
.zadanie .ocena { flex: none; min-width: 6.5rem; max-width: 9rem; margin-top: 0; }
.zadanie .ocena-kciuk { flex: none; }
/* Na telefonie wiersz karty nie ma już miejsca na trzecią kolumnę: metryczka
   („Kuchnia", imię, termin) ściskała się wtedy do jednej litery na linię.
   Kciuki schodzą więc do WŁASNEJ linii karty, wyrównane do prawej. Sprawdzone
   zrzutem przy 390 px — pomiar tego nie pokazuje, bo struktura jest poprawna
   w obu układach (design/README.md: wygląd oglądamy obrazem).
   Samo ZAWIJANIE wiersza przeniesione stąd do `wariant-lista.css`: należy do
   wariantu, który ten wiersz układa, i obowiązuje wszystkie jego wiersze,
   nie tylko kartę zadania (zgłoszenie #55). Tu zostaje wyłącznie to, co
   dotyczy bloku ocen. */
@media (max-width: 34rem) {
  .zadanie .ocena {
    flex: 1 1 100%; min-width: 0; max-width: none;
    justify-content: flex-end; margin-top: .3rem;
  }
}

/* Wariant „Kafelki": trochę więcej powietrza, jak w reszcie tego wariantu —
   ale JUŻ NIE 44 px malowanej wysokości. Cel dotykowy niesie `::after`
   wspólny dla wszystkich wariantów, więc powiększanie samego pudełka nie
   kupowało tu ergonomii, tylko robiło z kciuka drugą akcję główną kafla. */
:root[data-wariant="kafelki"] .ocena-kciuk { padding: .25rem .55rem; font-size: .82rem; }
:root[data-wariant="kafelki"] .ikona-kciuk { width: .95rem; height: .95rem; }
/* Wariant „Oś dnia": proste kanty, spójnie z resztą wariantu. */
:root[data-wariant="osdnia"] .ocena-kciuk { border-radius: 0; }
/* Nawigacja konta TYLKO DO ODCZYTU (REQ-033) — pusta, ale zajmująca to samo
   miejsce co nawigacja zwykła. Bez tej reguły nagłówek na tablu składałby się
   inaczej niż na pozostałych ekranach: marka i wylogowanie skakałyby do siebie,
   a wysokość paska zmieniałaby się z rolą oglądającego. */
.nawigacja-tablo { flex: 1 1 auto; min-height: var(--dotyk); }

/* Ustawienie hasła konta tylko do odczytu w składzie rodziny (REQ-033).
   Zwijka i jej treść dziedziczą wygląd z `.zwijka`; własne reguły są tylko po to,
   żeby pole nie rozpychało wiersza na całą szerokość karty — wzorem
   `.formularz-zastepstwa`. */
.domownik-haslo { margin-bottom: 0; }
.formularz-haslo-domownika { display: grid; gap: .6rem; }
.formularz-haslo-domownika label { display: grid; gap: .2rem; font-size: .9rem; color: var(--tekst-2); }
.formularz-haslo-domownika input { max-width: 22rem; }

/* ---------- Tablo — ekran „co dzisiaj" (REQ-033) ---------- */

/* JEDNA POKRĘTKA NA CAŁY EKRAN: `--tablo-skala`. Wszystkie rozmiary są z niej
   wyliczane, więc klasa rozmiaru ustawia JEDNĄ liczbę, a nie trzydzieści
   wartości na każdy breakpoint. Bez tego pierwsza zapomniana reguła daje ekran,
   na którym nazwa dyżuru rośnie, a imię obok zostaje małe.

   Prefiks `tablo-` jest w tym projekcie nowy — sprawdzony gerpem przed nadaniem
   (kolizje nazw klas są tu najczęstszą klasą błędu: `.dyzur-kafel`,
   `.powiadomienie` — docs/HANDOFF.md pkt 13). Świadomie NIE używamy `.karta`,
   `.badge` ani `.lista-wiersz`: tablo ma własną typografię, a wpięcie się
   w klasy aplikacji znaczyłoby, że zmiana wyglądu listy zadań po cichu
   przestawia ekran wiszący na ścianie. */
.tablo-strona { --tablo-skala: 1; }
.tablo-tytul { margin: 0 0 .2rem; font-size: calc(1.45rem * var(--tablo-skala)); line-height: 1.15; }
.tablo { display: flex; flex-direction: column; gap: calc(.85rem * var(--tablo-skala)); }
.tablo-dzien { margin: 0; color: var(--tekst-2); font-size: calc(.95rem * var(--tablo-skala)); }

/* Głowica: tytuł po lewej, przełącznik trybu pełnoekranowego po prawej.
   `flex-wrap`, bo przy 390 px przełącznik nie zmieści się w rzędzie z tytułem
   — a ściśnięty do jednej litery na linię byłby dokładnie tym błędem, który
   w tym projekcie widać wyłącznie na zrzucie. */
.tablo-glowica {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: .4rem .8rem; margin-bottom: .2rem;
}

/* CZAS AKTUALNOŚCI DANYCH (REQ-033) — cicho, ale czytelnie: to metryczka,
   nie treść. Rośnie razem z resztą tabla, bo na telewizorze ma dać się
   przeczytać z tej samej odległości co imiona. */
.tablo-aktualnosc {
  margin: 0; color: var(--tekst-2);
  font-size: calc(.8rem * var(--tablo-skala));
}
/* Ostrzeżenie o nieudanym odświeżeniu: kolor UWAGI plus SŁOWO (nigdy sam
   kolor — wymóg dostępności z design/README.md). Tła nie dostaje, żeby na
   ekranie wiszącym nie krzyczało bardziej niż zaległe zadanie. */
.tablo-ostrzezenie { color: var(--uwaga); font-weight: 700; }

/* PRZEŁĄCZNIK TRYBU PEŁNOEKRANOWEGO (REQ-033, zgłoszenie #56).
   Wejście wygląda jak zwykła, cicha akcja tekstowa. Wyjście musi być WIDOCZNE
   (na tablecie to jedyna droga powrotu, bo klawisza Esc tam nie ma), ale
   nienachalne: ma nie konkurować z treścią, po którą ekran postawiono.
   Ciszej robimy ZDEJMUJĄC TŁO, a nie przygaszając tekst na kolorze — pułapka
   12 z docs/HANDOFF.md (ta pomyłka zdarzyła się tu trzy razy). */
.tablo-pelny-wlacz, .tablo-wyjscie {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .35rem .6rem; min-height: var(--dotyk);
  border: 1px solid var(--linia); border-radius: var(--promien);
  background: none; color: var(--tekst-2); text-decoration: none;
  font-size: calc(.85rem * var(--tablo-skala)); white-space: nowrap;
}
.tablo-pelny-wlacz:hover, .tablo-wyjscie:hover,
.tablo-pelny-wlacz:focus-visible, .tablo-wyjscie:focus-visible {
  color: var(--tekst); border-color: var(--akcent);
}

/* TRYB PEŁNOEKRANOWY — wygląd. Stanu tu nie ma: nagłówka, nawigacji i stopki
   układ bazowy w tym trybie w ogóle nie renderuje (base.tmpl), więc nie ma
   czego ukrywać. Zostaje odzyskanie miejsca po nich i wyciszenie tego, co na
   ścianie jest szumem. */
body.pelny-ekran #page { padding: clamp(.6rem, 2vw, 1.4rem); padding-bottom: clamp(.6rem, 2vw, 1.4rem); }
/* Tytuł „Co dzisiaj" schodzi z ekranu: treść odpowiada na to pytanie sama,
   a wysokość jest tu zasobem deficytowym. Nadbrewka wariantu „Oś dnia" — tak
   samo. Zostaje sam przełącznik wyjścia. */
body.pelny-ekran .tablo-tytul, body.pelny-ekran .os-nadbrewka { display: none; }
/* Wyjście do prawego górnego róg treści, bez rozpychania wiersza. */
body.pelny-ekran .tablo-glowica { justify-content: flex-end; margin-bottom: 0; }
/* Na ekranie od tabletu w górę wyjście idzie do NAROŻNIKA okna i przestaje
   zajmować wiersz: cały odzyskany po nagłówku pas wysokości ma iść na treść,
   a nie na jeden przycisk. Zostaje tło kafla, żeby napis dał się przeczytać,
   gdyby wypadł nad wierszem dyżuru.
   Poniżej 700 px zostaje w układzie: przy 390 px narożnik przykryłby nagłówek
   dnia, czyli informację — a to gorsze niż zajęty wiersz. */
@media (min-width: 700px) {
  body.pelny-ekran .tablo-glowica {
    position: fixed; top: .6rem; right: .8rem; z-index: 5; margin: 0;
    background: var(--bg-2); border-radius: var(--promien);
  }
}
/* SKALI TYPOGRAFII TRYB PEŁNOEKRANOWY NIE ZMIENIA i to jest rozstrzygnięcie:
   zysk z trybu to ~120 px wysokości po nagłówku i stopce, a podniesienie skali
   zjadłoby go z nawiązką (wiersze rosną szybciej niż odzyskane miejsce)
   i wprowadziłoby pionowe przewijanie na ekranie, którego nikt nie przewinie.
   Klasy rozmiaru (700 px / 1500 px / niskie okno poziome) obowiązują dalej,
   te same co poza trybem. */

.tablo-pusto-dzien {
  margin: 0; padding: calc(1.3rem * var(--tablo-skala));
  background: var(--bg-2); border: 1px solid var(--linia); border-radius: var(--promien);
  color: var(--tekst-2); font-size: calc(1.1rem * var(--tablo-skala)); line-height: 1.45;
}

/* Podział na kolumny (poziomo) albo na wiersze (pionowo) robi WYŁĄCZNIE ten
   grid — znacznik jest jeden dla obu orientacji. Domyślnie jedna kolumna:
   pionowo i na telefonie to układ właściwy, dwie kolumny dochodzą warunkiem. */
.tablo-kolumny {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: calc(1rem * var(--tablo-skala)); align-items: start;
}
.tablo-sekcja {
  min-width: 0; padding: calc(.85rem * var(--tablo-skala));
  background: var(--bg-2); border: 1px solid var(--linia); border-radius: var(--promien);
}
.tablo-sekcja h2 {
  margin: 0 0 calc(.65rem * var(--tablo-skala));
  font-size: calc(.95rem * var(--tablo-skala)); letter-spacing: .09em;
  text-transform: uppercase; color: var(--tekst-2);
}
.tablo-sekcja-dyzury { order: 1; }
.tablo-sekcja-zadania { order: 2; }
.tablo-lista {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: calc(.5rem * var(--tablo-skala));
}

/* Obszary NAZWANE, nie automatyczne rozmieszczanie: wiersz dyżuru ma inne pola
   niż wiersz zadania (dyżur nie ma stanu, zadanie nie ma okresu), a przy
   auto-placement brakujące pole przesuwa sąsiada do innej komórki. */
.tablo-wiersz {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "nazwa osoba" "meta stan";
  gap: .15rem calc(.9rem * var(--tablo-skala));
  align-items: baseline; min-height: var(--dotyk);
  padding: calc(.6rem * var(--tablo-skala)) calc(.75rem * var(--tablo-skala));
  background: var(--bg-3); border-radius: .6rem;
  border-left: .35rem solid var(--linia);
}
.tablo-nazwa {
  /* break-word, NIE anywhere: „anywhere" pozwala przeglądarce łamać wyraz, żeby
     zminimalizować szerokość minimalną kolumny — czyli wprost zachęca ją do
     rozerwania „Kuchnia" na „Kuchn / ia", gdy miejsca jest mało. „break-word"
     łamie wyraz dopiero wtedy, gdy naprawdę nie ma jak go zmieścić. Na tablu
     łamany w połowie wyraz jest nieczytelny z odległości, czyli kasuje cały cel
     ekranu (REQ-033). */
  grid-area: nazwa; min-width: 0; overflow-wrap: break-word;
  font-size: calc(1.15rem * var(--tablo-skala)); font-weight: 650; color: var(--tekst);
}
.tablo-nazwa a { color: var(--tekst); text-decoration: none; }
.tablo-nazwa a:hover, .tablo-nazwa a:focus-visible { text-decoration: underline; }
.tablo-okres { grid-area: meta; color: var(--tekst-2); font-size: calc(.85rem * var(--tablo-skala)); }
.tablo-osoba {
  grid-area: osoba; justify-self: end; display: flex; align-items: center;
  gap: calc(.4rem * var(--tablo-skala)); min-width: 0;
}
.tablo-imie { font-weight: 600; font-size: calc(1.05rem * var(--tablo-skala)); overflow-wrap: break-word; }
/* Awatar na tablu to WSPÓLNY `.awatar` w rozmiarze `.awatar-tablo` (sekcja
   awatarów wyżej) — zdjęcie, gdy konto je ma, zaślepka gdy nie (REQ-033,
   zgłoszenie #57). Dawna, osobna klasa `.tablo-skrot` została USUNIĘTA:
   rysowała drugie, słabsze kółko z `--akcent-tlo`, więc każdy domownik miał na
   tablu ten sam kolor (na pozostałych ekranach — własny), a zdjęcia nie było
   nigdy. Dwa miejsca rysujące ten sam element rozjeżdżają się przy pierwszej
   zmianie i dokładnie to się stało. */
.tablo-brak { color: var(--blad); font-style: italic; font-size: calc(1rem * var(--tablo-skala)); }
.tablo-stan {
  grid-area: stan; justify-self: end; white-space: nowrap;
  font-size: calc(.8rem * var(--tablo-skala)); font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--tekst-2);
}
.tablo-pusta-sekcja {
  margin: 0; color: var(--tekst-2); font-style: italic;
  font-size: calc(1rem * var(--tablo-skala));
}

/* Kolor krawędzi niesie stan, ale NIGDY sam: obok stoi słowo („Zaległe",
   „Zgłoszone") — wymóg dostępności z design/README.md. */
.tablo-dyzur { border-left-color: var(--akcent); }
.tablo-stan-do_wykonania { border-left-color: var(--akcent); }
.tablo-stan-zaraportowane { border-left-color: var(--uwaga); }
.tablo-stan-zaraportowane .tablo-stan { color: var(--uwaga); }
.tablo-stan-zaakceptowane { border-left-color: var(--ok); }
.tablo-stan-zaakceptowane .tablo-stan { color: var(--ok); }
/* Zaległe i luka PO stanach, bo mają je nadpisywać przy tej samej swoistości. */
.tablo-dyzur-luka { border-left-color: var(--blad); }
.tablo-zalegle { border-left-color: var(--blad); }
.tablo-zalegle .tablo-stan { color: var(--blad); }

/* TELEFON — wiersz tabla w JEDNEJ kolumnie. Kolumna „osoba" jest `auto`, więc
   bierze max-content („(An) Antonina Kołodziejczyk" to ~200 px), a nazwa dostaje
   resztę: przy 320–390 px zostaje jej kilkadziesiąt pikseli i przeglądarka łamie
   tytuł PO JEDNEJ LITERZE („O / d / k / u / r / z / a / n / i / e"). To trzeci
   raz ten sam błąd na tablu — dwa poprzednie (kolumna przy 844 px i przy
   1920 px) opisują komentarze niżej. Tu wychodzi od strony WĄSKIEGO ekranu
   i znowu wyłącznie ze zrzutu: strona się nie przewija w poziomie, więc pomiar
   `scrollWidth` przechodził na czysto (zgłoszenie #89).
   Kolejność bloków to kolejność czytania: co → kiedy → kto → w jakim stanie. */
@media (max-width: 34rem) {
  .tablo-wiersz {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "nazwa" "meta" "osoba" "stan";
  }
  .tablo-osoba, .tablo-stan { justify-self: start; }
}

/* KLASA ŚREDNIA (tablet, ~820×1180 i ~1180×820). Typografia wyraźnie większa
   niż w aplikacji — tablet stoi na blacie albo na ścianie, nie w ręce. */
@media (min-width: 700px) {
  .tablo-strona { --tablo-skala: 1.3; }
}

/* …ale NISKIE okno poziome to telefon w dłoni, nie ekran na ścianie: pionowego
   miejsca jest tam kilkaset pikseli, więc powiększona typografia zjadałaby całą
   listę zadań. Reguła stoi PO tej wyżej, bo ma ją nadpisywać. */
@media (orientation: landscape) and (max-height: 520px) {
  .tablo-strona { --tablo-skala: 1; }
}

/* POZIOMO — przypadek PODSTAWOWY (tablet na ścianie stoi poziomo): dyżury
   i zadania obok siebie. Dyżury po lewej i węższe, bo jest ich mniej i są
   odpowiedzią na pierwsze pytanie („kto dziś"); zadania dostają więcej
   szerokości, bo mają dłuższe tytuły.
   Próg 900 px, nie 700: przy 844 px (telefon obrócony poziomo) dwie kolumny
   MIESZCZĄ się arytmetycznie, ale kolumna jest wtedy za wąska na nazwę dyżuru
   obok imienia i przeglądarka łamie ją w POŁOWIE WYRAZU („Kuchn / ia").
   Wyszło to ze zrzutu ekranu, nie z pomiaru — układ nie przewijał się w poziomie
   i każdy test struktury przechodził (docs/HANDOFF.md: wygląd sprawdzaj
   obrazem). Poniżej progu zostaje jedna kolumna, w której wiersz ma ~800 px
   i nazwa mieści się w całości. */
@media (orientation: landscape) and (min-width: 900px) {
  .tablo-kolumny { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); }
}

/* KLASA DUŻA (komputer, telewizor, ~1920×1080). Cel to czytelność Z ODLEGŁOŚCI:
   MNIEJ pozycji, WIĘKSZA typografia. Skala 1.85 jest jedyną potrzebną do tego
   pokrętką — pozycji mieści się wtedy mniej same z siebie. */
@media (min-width: 1500px) {
  .tablo-strona { --tablo-skala: 1.85; }
}

/* Tablo jest JEDYNYM ekranem, który ma prawo wyjść poza kolumnę treści.
   `.strona` ma `max-width: 64rem`, więc na 1920 px tablo dostawało niecałe
   1000 px, z tego kolumna ~430 px — a przy skali 1.85 sama „Antonina ZALEGŁE"
   zajmuje tam prawie tyle, więc na nazwę zostawało 100 px i przeglądarka łamała
   ją PO JEDNEJ LITERZE („K / u / c / h / n / i / a"). Wyszło to wyłącznie ze
   zrzutu ekranu: strona nie przewijała się w poziomie i wszystkie testy
   struktury przechodziły.
   Celujemy w `#page` przez atrybut `data-path`, który układ bazowy i tak już
   wypisuje — bez tego trzeba by parent-selektora albo klasy na `<main>`, czyli
   zmiany w layoucie wspólnym dla wszystkich ekranów.
   Świadomie odrzucone: układ wiersza w JEDNEJ linii („nazwa | osoba | stan”
   w trzech kolumnach) na dużym ekranie. Kolumny `auto` zabierały tam
   max-content, a nazwa dostawała resztę, czyli nic — to był dokładnie ten
   sam błąd, tylko wewnątrz wiersza. */
#page[data-path="/tablo"] { max-width: none; }

/* Wariant „Kafelki": wiersze jako duże, wyraźnie oddzielone bloki — ten sam
   język co kafle pulpitu. */
:root[data-wariant="kafelki"] .tablo-kafelki .tablo-wiersz {
  border-radius: var(--promien); border-left-width: .5rem; box-shadow: var(--cien);
}
/* Wariant „Oś dnia": bez wypełnienia tła wiersza — sekcje czytają się wtedy jak
   kolejne wpisy na osi, nie jak osobne kafle. */
:root[data-wariant="osdnia"] .tablo-osdnia .tablo-wiersz {
  background: none; border-radius: 0; border-bottom: 1px solid var(--linia);
}

/* REQ-034 — oznaczenie trybu „działaj jako". Pasek stoi na samej górze każdego
   ekranu (base.tmpl, poza `#page`), więc jego zadaniem jest być NIEPRZEOCZALNY,
   a nie ładny: bez koloru i bez wagi wygląda jak stopka i po minucie przestaje
   być widziany, a wtedy administrator zaczyna działać cudzym imieniem nie
   pamiętając o tym.
   `position: sticky` z `top: 0` i z-index NIŻSZYM niż pasek wersji testowej
   (.pasek-testowy ma 60) — gdy oba są na ekranie, ostrzeżenie o wersji stoi
   wyżej i się nie przenikają.
   Kolory z palety ostrzeżeń, nie akcentu: akcent oznacza w tej aplikacji „akcja
   główna", a to nie jest akcja, tylko stan. Kontrast liczony na obu motywach —
   ciemny tekst na pomarańczowym tle wypada tak samo w dziennym i nocnym, bo tło
   jest własne, a nie dziedziczone (ta sama decyzja co przy .pasek-testowy).
   Wypełnione tło pilnuje przy okazji pułapki nr 12 z docs/HANDOFF.md: przycisk
   powrotu MUSI mieć własne tło, inaczej dziedziczy szary tekst na pomarańczu. */
.pasek-impersonacja {
  position: sticky; top: 0; z-index: 55;
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  padding: .45rem .75rem;
  font-size: .85rem; line-height: 1.35;
  color: #2a1400; background: #ffab5e;
  border-bottom: 2px solid #b45309;
}
.pasek-impersonacja-tresc { flex: 1 1 16rem; min-width: 0; }
/* Powrót idzie na koniec wiersza, a przy 390 px — pod treść (flex-wrap wyżej).
   <form> ma domyślny margines, więc zerujemy go jawnie. */
.impersonacja-powrot { margin: 0 0 0 auto; }
/* Własne tło i własny kolor tekstu: przycisk na wypełnionym tle paska
   dziedziczyłby kolor akcentu i zniknął (pułapka nr 12 — ten sam błąd zdarzył
   się w tym projekcie trzy razy). */
.impersonacja-powrot .przycisk {
  background: #2a1400; color: #ffe8cf; border-color: #2a1400;
}
/* Wejście w tryb w sekcji „Domownicy" — akcja poboczna, ale nie cicha: to
   najwrażliwsza akcja w aplikacji, więc obramowanie ostrzegawcze odróżnia ją od
   „Zmień rolę" i „Usuń zdjęcie" obok. Bez `margin-left: auto` — ten koniec
   wiersza należy do .domownik-moderacja i dwa `auto` rozjechałyby wiersz. */
.domownik-dzialaj-jako { margin: 0; }
.domownik-dzialaj-jako .przycisk { border-color: #b45309; }
/* ─── Ekran „Gdzie są domownicy" (REQ-035 / FEAT-034) ──────────────────────
   Prefiks `lokalizac-` jest w warstwie CSS tego projektu NOWY — sprawdzony
   grepem przed nadaniem (kolizje nazw to tu najczęstsza klasa błędu,
   czternaście przypadków, docs/HANDOFF.md pkt 13). Świadomie nie wpinamy się
   w `.lista-wiersz` ani w `.domownik` z ekranu dyżurów: tamte wiersze mają
   inne pola i inną liczbę kolumn, a wspólna klasa znaczyłaby, że zmiana
   wyglądu składu rodziny przypadkiem przestawia ten ekran.

   Wiersz jest GRIDEM z nazwanymi obszarami, nie rzędem flexa. Powód jest
   z doświadczenia, nie z gustu: przy `flex` i długim imieniu odnośnik „Pokaż
   na mapie" wypycha się poza kartę przy 390 px — dokładnie ten błąd, który już
   raz wyszedł na profilu i wyłącznie ze ZRZUTU EKRANU, bo strona nie
   przewijała się w poziomie i wszystkie testy struktury przechodziły. */
.lokalizacje-strona > .opis { max-width: 44rem; }
.lokalizacje { display: flex; flex-direction: column; gap: .75rem; }

/* Cała rodzina bez pozycji: ZDANIE, nie puste miejsce (REQ-035). Wygląda jak
   informacja, nie jak ostrzeżenie — brak pozycji jest stanem normalnym, więc
   nie maluje się kolorem błędu. */
.lokalizacje-pusto {
  margin: 0; padding: .85rem 1rem;
  background: var(--bg-2); border: 1px solid var(--linia); border-radius: var(--promien);
  color: var(--tekst-2); line-height: 1.5;
}

.lokalizacje-lista {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: .6rem;
}

.lokalizacja-wiersz {
  display: grid; min-width: 0;
  grid-template-areas: "osoba" "wiek" "czas" "dokladnosc" "wspolrzedne" "mapa";
  gap: .3rem; padding: .7rem .85rem;
  background: var(--bg-2); border: 1px solid var(--linia);
  border-left: .25rem solid var(--akcent); border-radius: var(--promien);
}
/* Wiersz bez pozycji jest CISZEJ, ale nadal jest wierszem: zdejmujemy akcent
   z krawędzi, nie treść. „Ciszej" robimy TŁEM albo krawędzią, nigdy samym
   kolorem tekstu — ta pomyłka zdarzyła się tu już trzy razy (pułapka 12). */
.lokalizacja-wiersz-pusty { border-left-color: var(--linia); }

.lokalizacja-osoba {
  grid-area: osoba; display: flex; align-items: center; gap: .5rem; min-width: 0;
}
.lokalizacja-imie { font-weight: 620; overflow-wrap: break-word; min-width: 0; }

/* Wiek pomiaru jest ODPOWIEDZIĄ na pytanie tego ekranu, więc po imieniu jest
   jego najmocniejszym elementem — nie przypisem. */
.lokalizacja-wiek { grid-area: wiek; font-weight: 600; }
.lokalizacja-czas { grid-area: czas; }
/* Dokładność musi być widoczna jako LICZBA, nie jako szept: pozycja
   z dokładnością 900 m nie znaczy tego samego co 8 m (REQ-035). Dlatego ma
   własną plakietkę, a nie samo `.opis male` jak współrzędne. */
.lokalizacja-dokladnosc {
  grid-area: dokladnosc; justify-self: start; padding: .1rem .45rem;
  background: var(--bg-3); border-radius: .4rem;
  font-size: .85rem; font-variant-numeric: tabular-nums;
}
.lokalizacja-wspolrzedne {
  grid-area: wspolrzedne; font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.lokalizacja-mapa { grid-area: mapa; justify-self: start; }
.lokalizacja-brak { grid-area: wiek; color: var(--tekst-2); font-style: italic; }

/* Stan „prośba o pozycję czeka na odpowiedź" (REQ-039). Był JEDYNYM stanem
   wiersza bez ani jednej reguły: sąsiednie komunikaty w tym samym miejscu
   („to Ty", „nie udostępnia pozycji") biorą wygląd z `opis male`, więc
   czekająca prośba krzyczała pełnym rozmiarem tekstu treści — na zrzucie
   390 px była najgłośniejszym napisem w karcie i dobijała do jej krawędzi,
   choć mówi o czekaniu, a nie o zdarzeniu. Ma ten sam ciężar co sąsiedzi.
   Bez znaku graficznego świadomie: informację niesie tu całe ZDANIE („prośba
   wysłana, czeka na odpowiedź"), nie kolor, więc wymóg REQ-026 jest spełniony
   treścią — a znak z bloku emoji dokładałby tylko ryzyko pustego prostokąta
   na urządzeniu bez tego kroju. */
.lokalizacja-prosba-czeka {
  display: inline-block; font-size: .85rem; color: var(--tekst-2);
  overflow-wrap: anywhere;
}

/* Od 34rem (~544 px) wiersz mieści się w dwóch kolumnach: osoba i liczby po
   lewej, odnośnik do mapy po prawej. Kolumna liczb ma `minmax(0, 1fr)`, więc
   długie imię łamie się, a nie rozpycha wiersza. */
@media (min-width: 34rem) {
  .lokalizacja-wiersz {
    grid-template-areas:
      "osoba       mapa"
      "wiek        mapa"
      "czas        mapa"
      "dokladnosc  mapa"
      "wspolrzedne mapa";
    grid-template-columns: minmax(0, 1fr) auto;
  }
  /* Odnośnik do mapy centruje się względem BLOKU LICZB, a nie całego wiersza:
     `align-self: center` na obszarze rozciągniętym o poprzednie pozycje
     zsuwałoby go na ich wysokość, czyli obok czegoś, czego nie dotyczy. */
  .lokalizacja-mapa { align-self: center; justify-self: end; }
}

/* ─── Poprzednie pozycje domownika (REQ-040 / FEAT-038) ─────────────────────
   Historia dziesięciu ostatnich pomiarów. Sekcja jest WYRAŹNIE PODRZĘDNA
   względem pozycji najnowszej i to nie jest kwestia gustu: ekran odpowiada
   przede wszystkim na „gdzie ktoś jest teraz", a poprzednie pozycje mówią
   „gdzie był przedtem". Gdyby dziesięć wpisów miało ciężar pierwszego, wiersz
   przestałby mieć odpowiedź na pytanie ze swojego tytułu.

   Podrzędność robimy WCIĘCIEM I KRESKĄ, nie zmniejszeniem liczb: dokładność
   i wiek pomiaru muszą zostać czytelne w każdym wpisie (REQ-035 — bez tych
   liczb czytelnik wyciąga fałszywy wniosek), a zszeptanie ich do rozmiaru
   przypisu jest tym samym co ich ukrycie. */
.lokalizacja-poprzednie {
  /* BEZ nazwanego obszaru siatki — świadomie. Nazwany obszar „poprzednie"
     stawiał tę sekcję w wierszu siatki PRZED przyciskiem „Lokalizuj", bo
     przycisk jest rozmieszczany automatycznie, a obszary nazwane zajmują
     wiersze wcześniejsze niż jakikolwiek wiersz domyślny — i zmiana kolejności
     w SZABLONIE tego nie ruszała (sprawdzone na zrzucie: przycisk nadal
     lądował dziewięć wpisów pod imieniem). Rozmieszczenie automatyczne idzie
     kolejnością w DOM, więc sekcja trafia tam, gdzie stoi w szablonie: na
     końcu wiersza. `1 / -1` daje jej pełną szerokość także w układzie
     dwukolumnowym. */
  grid-column: 1 / -1; min-width: 0;
  margin-top: .45rem; padding-top: .45rem;
  border-top: 1px solid var(--linia);
}
.lokalizacja-poprzednie-naglowek { display: block; margin-bottom: .3rem; }
.lokalizacja-poprzednie-lista {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: .35rem;
}
/* Wpis jako jeden zawijający się wiersz, nie jako druga siatka: dziesięć
   siatek w siatce dałoby dziesięć razy tę samą kolumnę pustego miejsca po
   krótkim „przed chwilą". `border-inline-start` jest KRESKĄ ciągu, dzięki
   której widać, że wpisy należą do siebie, a nie do wiersza wyżej. */
.lokalizacja-poprzednia {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .5rem;
  min-width: 0; padding-inline-start: .6rem;
  border-inline-start: 2px solid var(--linia);
}
/* Wewnątrz wpisu obszary siatki wiersza nie obowiązują — `grid-area`
   odziedziczone z reguł wyżej ustawiłoby elementy na obszarach RODZICA
   i posypało układ. Zerujemy je jawnie, bo to ten sam zestaw klas. */
.lokalizacja-poprzednia .lokalizacja-wiek,
.lokalizacja-poprzednia .lokalizacja-czas,
.lokalizacja-poprzednia .lokalizacja-dokladnosc,
.lokalizacja-poprzednia .lokalizacja-wspolrzedne,
.lokalizacja-poprzednia .lokalizacja-mapa { grid-area: auto; justify-self: auto; }
.lokalizacja-poprzednia .lokalizacja-wiek { font-weight: 560; }

/* Wariant „Kafelki": wiersze jako duże, wyraźnie oddzielone bloki — ten sam
   język co kafle pulpitu i wiersze tabla. */
:root[data-wariant="kafelki"] .lokalizacje-kafelki .lokalizacja-wiersz {
  border-left-width: .5rem; box-shadow: var(--cien); padding: .9rem 1rem;
}
/* Wariant „Oś dnia": bez wypełnienia tła wiersza — pozycje czytają się wtedy
   jak kolejne wpisy na osi, a nie jak osobne kafle. */
:root[data-wariant="osdnia"] .lokalizacje-osdnia .lokalizacja-wiersz {
  background: none; border-radius: 0; border-inline: none; border-top: none;
  border-bottom: 1px solid var(--linia);
}

/* ─── Obecność domowników (REQ-006 / FEAT-005) ──────────────────────────────
   Stan obecności w wierszu składu rodziny: „teraz w aplikacji" / „ostatnio
   20 minut temu" / „nigdy w aplikacji" / „ekran na ścianie".

   Wiersz `.domownik` jest `flex` z `flex-wrap`, więc plakietka wchodzi w niego
   jako kolejny element i na wąskim ekranie przechodzi do drugiej linii razem
   z datą — bez zmiany układu wiersza i bez ruszania sąsiadów.

   KOLOR NIE JEST TU JEDYNYM NOŚNIKIEM INFORMACJI. Każdy stan mówi to samo
   SŁOWEM w treści elementu, a kolor tylko przyspiesza czytanie listy: wymóg
   dostępności (REQ-023) i zarazem odporność na to, co w tym projekcie zdarzyło
   się trzy razy — regułę zjadał niedomknięty komentarz albo klamra, a ekran
   dalej musiał być zrozumiały.

   Klasa `.obecnosc` NIE nazywa się `.domownik-obecnosc`, choć stoi w wierszu
   domownika: to ten sam byt, który kiedyś stanie przy autorze zgłoszenia, więc
   nazwa jest opisem stanu, nie miejsca. Grep przed nadaniem: żadnego
   `.obecnos*` w projekcie nie było (pułapka 13 w docs/HANDOFF.md — jedenaście
   kolizji nazw). */
.obecnosc {
  display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: .3rem;
  font-size: .85rem;
}
.obecnosc-opis { white-space: nowrap; }
/* Data obok opisu, nie pod kursorem: na telefonie „po najechaniu" nie istnieje
   (REQ-006, to samo rozstrzygnięcie co w REQ-035). Cyfry tabelaryczne, żeby
   kolumna dat w liście nie tańczyła. */
.obecnosc-czas { color: var(--tekst-2); font-variant-numeric: tabular-nums; }
/* „Teraz w aplikacji" — jedyny stan z tłem. Wyróżniony, bo jest jedyną
   odpowiedzią, która zmienia decyzję czytającego (nie ma sensu pisać wiadomości
   do kogoś, kto był tu trzy dni temu).

   TŁO SIEDZI NA `.obecnosc-opis`, NIE na `.obecnosc`, i to jest poprawka
   znaleziona WYŁĄCZNIE NA ZRZUCIE: w wariancie „Kafelki" plakietka stoi
   w osobnej linii wiersza, a element rozpięty na całą szerokość linii miał tło
   ciągnące się przez cały wiersz — pasek szarości od imienia do prawej krawędzi
   karty. Znacznik był poprawny, klasa miała regułę, test struktury zielony.
   Tło na wewnętrznym napisie przylega do TEKSTU niezależnie od tego, ile miejsca
   dostał element nadrzędny. */
.obecnosc-teraz { color: var(--ok); font-weight: 600; }
.obecnosc-teraz .obecnosc-opis {
  padding: .1rem .45rem; border-radius: .4rem; background: var(--bg-3);
}
.obecnosc-ostatnio { color: var(--tekst-2); }
/* „Nigdy w aplikacji" i „ekran na ścianie" — kursywa, bo oba są opisem SYTUACJI
   konta, a nie pomiarem czasu. Bez koloru ostrzegawczego: konto, na które nikt
   się nie zalogował, nie jest błędem ani zaniedbaniem. */
.obecnosc-nigdy, .obecnosc-ekran { color: var(--tekst-2); font-style: italic; }

/* Wariant „Kafelki": wiersze składu rodziny są w nim większe i wyraźniej
   oddzielone, więc stan dostaje osobną linię pod imieniem — nie walczy wtedy
   o miejsce z selektorem roli i przyciskiem „Działaj jako". */
:root[data-wariant="kafelki"] .domownicy .obecnosc { flex-basis: 100%; }
/* Wariant „Oś dnia" czyta się jak strumień wpisów, więc stan zostaje w linii
   imienia, ale traci tło — plakietka z tłem wyglądałaby w nim jak wpis osi. */
:root[data-wariant="osdnia"] .domownicy .obecnosc-teraz .obecnosc-opis {
  padding: 0; background: none;
}

/* ── Lista zmian (REQ-036, FEAT-035, zgłoszenie #70) ─────────────────────────
   Wykaz wydań ma jedną chorobę: jest ŚCIANĄ TEKSTU. Ponad dwanaście wydań,
   w każdym po kilka sekcji, w każdej po kilkanaście akapitów — bez rytmu
   nie da się na tym ekranie niczego znaleźć. Trzy zabiegi robią tu całą pracę:
   numer wydania jest DUŻY i ma własną linię z datą i tagiem, nazwa sekcji jest
   krótką etykietą w akcencie (a nie kolejnym akapitem tej samej wagi),
   a długość wiersza jest ograniczona miarą znakową — na 1200 px pełna szerokość
   karty dawała wiersze, w których wzrok gubił początek następnego. */
.zmiany-wstep { max-width: 68ch; }
.zmiany-lista { display: flex; flex-direction: column; gap: 1rem; }

/* Karta wydania. Lewa krawędź zamiast obwódki: przy kilkunastu kartach pod sobą
   pełna ramka na każdej daje siatkę linii, a pasek prowadzi wzrok w dół. */
.wydanie { border-left: 3px solid var(--linia); }
/* Wydanie uruchomione — jedyne z akcentem. Wyróżnienie jest tu odpowiedzią na
   pytanie „na czym stoję", a nie ozdobą, więc dostaje je DOKŁADNIE jedna karta. */
.wydanie[data-biezace] { border-left-color: var(--akcent); }
/* Wpis niewydany — krawędź przerywana, bo to zapowiedź, nie fakt. Sam napis
   „Niewydane" w miejscu numeru wersji łatwo przeczytać jako nazwę wydania. */
.wydanie[data-niewydane] { border-left-style: dashed; }

.wydanie-naglowek {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .35rem .75rem; margin-bottom: .5rem;
}
.wydanie-wersja { font-size: 1.5rem; line-height: 1.1; margin: 0; }
.wydanie-data { color: var(--tekst-2); font-variant-numeric: tabular-nums; }
/* Tag do przepisania do gita — monospace i tło, bo jest komendą do skopiowania,
   a nie opisem. */
.wydanie-tag {
  padding: .1rem .4rem; border-radius: .4rem;
  background: var(--bg-3); color: var(--tekst-2); font-size: .85rem;
}
.wydanie-plakietka {
  padding: .1rem .5rem; border-radius: 999px;
  font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
}
/* Pełne tło akcentem WRAZ z kolorem tekstu na akcencie — nadpisanie samego
   koloru na akcentowym tle dało w tym projekcie trzy razy szary napis na
   akcencie (pułapka 12 w docs/HANDOFF.md). */
.wydanie-biezaca { background: var(--akcent); color: var(--akcent-tekst); }

.wydanie-wstep { max-width: 68ch; color: var(--tekst-2); margin: 0 0 .75rem; }
.wydanie-sekcja { margin-top: .9rem; }
/* Etykieta, nie nagłówek akapitu: to nazwa kubełka („Dodane"), a nie treść. */
.wydanie-sekcja-tytul {
  margin: 0 0 .35rem; font-size: .8rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--akcent);
}
.wydanie-lista { margin: 0; padding-left: 1.1rem; list-style: disc; }
.wydanie-podlista {
  margin: .3rem 0 0; padding-left: 1.1rem; list-style: circle;
  color: var(--tekst-2);
}
.wydanie-pozycja { max-width: 72ch; margin-bottom: .45rem; }
.wydanie-pozycja code { font-size: .9em; }

/* Dwa stany brzegowe ekranu: kompilacja bez wykazu i wersja bez wpisu w pliku.
   Oba są komunikatem o stanie instalacji, więc czytają się jak ostrzeżenie,
   a nie jak treść wydania. */
.zmiany-brak { color: var(--tekst-2); }
.zmiany-ostrzezenie {
  max-width: 68ch; margin: 0 0 1rem; padding: .6rem .8rem;
  border-left: 3px solid var(--uwaga); border-radius: var(--promien);
  background: var(--bg-3); color: var(--tekst);
}

/* Telefon 390 px: numer wydania schodzi o stopień, bo w jednej linii z datą
   i tagiem zawijał się w środku plakietki. */
@media (max-width: 30rem) {
  .wydanie-wersja { font-size: 1.25rem; }
  .wydanie-pozycja { margin-bottom: .6rem; }
}

/* ------------------------------------------------------------------------- */
/* Katalog zadań w formularzu nowego zadania (REQ-011 / REQ-012, #74).       */
/* ------------------------------------------------------------------------- */
/* Reguły idą RAZEM ze szablonem, nie „potem": jedenasty raz w tym projekcie
   wzorzec „znacznik jest, wyglądu nie ma" (docs/HANDOFF.md), a przy tym
   pułapka 19 — brakująca klamra unieważnia arkusz DO KOŃCA PLIKU, więc ten
   blok jest domknięty świadomie, regułą po regule. */

/* Pole wyszukiwania dziedziczy `.szukaj`, ale w formularzu nie może zabierać
   dolnego marginesu przeznaczonego na listę wyników. */
.katalog-szukaj { margin-block: .2rem .3rem; }

/* Pojemnik podpowiedzi istnieje ZAWSZE, także pusty — jest celem fragmentu
   htmx. Pusty nie może jednak zostawiać dziury w formularzu, więc bez
   własnego marginesu i bez tła; wysokość daje mu dopiero zawartość. */
.katalog-podpowiedzi { display: grid; gap: .3rem; max-width: 32rem; }

/* Podpowiedź jest PRZYCISKIEM `submit`, więc trzeba mu odebrać wygląd
   przycisku: wygląda i czyta się jak wiersz listy, nie jak akcja główna.
   Ta sama zasada, na której trzy razy przejechano się z kontrastem
   (pułapka 12): ciszej znaczy zdjąć TŁO, nie tylko zmienić kolor tekstu. */
.katalog-podpowiedz {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  width: 100%; min-height: var(--dotyk);
  padding: .4rem .6rem; text-align: start;
  border: 1px solid var(--linia); border-radius: .5rem;
  background: var(--bg-2); color: var(--tekst); cursor: pointer;
  font: inherit;
}
.katalog-podpowiedz:hover, .katalog-podpowiedz:focus-visible {
  background: var(--bg-3); border-color: var(--akcent);
}
.katalog-nazwa { font-weight: 600; }

/* Plakietka KATEGORII (dyżuru). Tło na samym napisie, nie na elemencie
   rozpiętym na wiersz — inaczej przy zawinięciu wiersza ciągnie się szarym
   pasem do prawej krawędzi (dokładnie ta pomyłka wyszła przy obecności
   domowników i była widoczna WYŁĄCZNIE na zrzucie). */
.katalog-kategoria {
  padding: .1rem .45rem; border-radius: .4rem;
  background: var(--akcent-tlo); color: var(--tekst);
  font-size: .8rem; white-space: nowrap;
}
/* Wymogi dowodu i długość checklisty — informacja drugorzędna, więc bez tła
   i mniejszym stopniem; plakietka kategorii ma zostać jedyną rzeczą, która
   w wierszu przyciąga wzrok. */
.katalog-wymog { color: var(--tekst-2); font-size: .8rem; white-space: nowrap; }
.katalog-pusto { margin: 0; }

/* Przełącznik „poza dyżurem" (REQ-012). Blok adresata odsłania się CZYSTYM
   CSS-em — bez skryptu, bo CSP nie ma `unsafe-inline`.
   Pole wyboru siedzi w <label>, dokładnie jak dwa wymogi dowodu nad nim, więc
   `.pole-wyboru` daje mu ten sam układ i ten sam rozmiar. Przy pierwszym
   podejściu stało OSOBNO od etykiety (żeby zadziałał selektor rodzeństwa `~`)
   i w jednokolumnowej siatce formularza spadło do własnego wiersza:
   przerośnięty kwadrat, a podpis pod nim. Wyszło z ZRZUTU przy 390 px — HTML
   był poprawny, klasy miały reguły, testy struktury zielone. */
.zadanie-adresat { gap: .4rem; }
.etykieta-adresata { color: var(--tekst); }
/* `:has()` na formularzu, nie `~` na polu wyboru — patrz komentarz wyżej.
   KIERUNEK AWARII jest tu wybrany: reguła ukrywająca stoi WEWNĄTRZ @supports,
   więc przeglądarka bez `:has()` pokazuje blok adresata ZAWSZE. Odwrotnie
   (ukrycie domyślne + odsłanianie w @supports) brak obsługi odbierałby całą
   zdolność, a tak odbiera tylko zwięzłość formularza. */
@supports selector(:has(*)) {
  .formularz-zadania:has(.przelacznik-adresata) .zadanie-adresat { display: none; }
  .formularz-zadania:has(.przelacznik-adresata:checked) .zadanie-adresat { display: grid; }
}

/* Wariant „Kafelki": formularz jest w nim szerszy, więc podpowiedzi mogą stać
   w dwóch kolumnach — lista ośmiu pozycji nie odpycha wtedy pól niżej. */
:root[data-wariant="kafelki"] .katalog-podpowiedzi {
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  max-width: 44rem;
}
/* Wariant „Oś dnia" czyta się jak strumień wpisów, więc podpowiedź traci
   obramowanie i zostaje samym wierszem z plakietką. */
:root[data-wariant="osdnia"] .katalog-podpowiedz {
  border-color: transparent; background: none;
}
:root[data-wariant="osdnia"] .katalog-podpowiedz:hover,
:root[data-wariant="osdnia"] .katalog-podpowiedz:focus-visible {
  background: var(--bg-3);
}
/* ── 17. Podsumowanie tygodnia (REQ-037 / FEAT-036) ─────────────────────────
   Ekran jest ZESTAWIENIEM LICZB, więc jego jedyne realne ryzyko wizualne to
   ściana cyfr: pięć kafli i siedem par w wierszu osoby bez wyraźnej hierarchii
   czyta się jak tabela z arkusza. Reguły niżej robią więc trzy rzeczy —
   nadają liczbom SKALĘ (liczba duża, nazwa mała), rozbijają kafle na siatkę,
   która sama się zawija, i pozwalają wierszom osób łamać się w pionie na
   telefonie.

   CZYTELNOŚĆ BEZ KOLORU (REQ-037): kolor kafla jest DODATKIEM. Każdy kafel
   niesie nazwę kategorii słowem, więc odebranie wszystkich kolorów nie odbiera
   ani jednej informacji. Nie ma tu ani jednej kolorowej kropki bez podpisu.

   Nazwy klas: prefiks `podsumowanie-`. W arkuszu istnieje już rodzina
   `.kreator-podsumowanie*` (ostatni krok kreatora dyżurów, REQ-031) — to inny
   prefiks i inny ekran, sprawdzone grepem przed nadaniem (pułapka 13
   w docs/HANDOFF.md). */
/* Sama strona: nagłówek ekranu i treść pod nim, z tym samym rytmem odstępów co
   pozostałe ekrany. */
.podsumowanie-strona { display: grid; gap: .8rem; }
.podsumowanie { display: grid; gap: 1rem; }

/* Nawigacja po tygodniach: „poprzedni — zakres — następny" w jednym rzędzie.
   Środkowa kolumna jest elastyczna, a skrajne mają stałe miejsce, żeby zakres
   nie skakał w lewo, gdy „Następny tydzień" nie istnieje (tydzień bieżący).
   Dokładnie temu służy `.podsumowanie-krok-brak` — pusty element trzymający
   miejsce. */
.podsumowanie-nawigacja {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
  justify-content: space-between;
}
.podsumowanie-krok {
  min-height: var(--dotyk); display: inline-flex; align-items: center;
  padding: 0 .6rem; border-radius: var(--promien); color: var(--akcent);
  white-space: nowrap;
}
.podsumowanie-krok:hover { background: var(--bg-3); }
.podsumowanie-krok-brak { display: inline-block; min-width: 9rem; }
.podsumowanie-zakres {
  margin: 0; font-size: 1.15rem; font-weight: 600; text-align: center; flex: 1 1 auto;
}
.podsumowanie-biezacy { color: var(--tekst-2); font-weight: 400; font-size: .9rem; }
/* PRZY 390 px trzy elementy w jednym rzędzie zawijały się każdy w swoją linię,
   a zakres — wyśrodkowany — stawał pomiędzy dwoma odnośnikami wyrównanymi do
   lewej. Wyglądało to jak trzy niepowiązane napisy, a nie jak nawigacja.
   Znaleziono WYŁĄCZNIE NA ZRZUCIE (SKILL.md: „wygląd sprawdzaj obrazem"):
   znacznik był poprawny, wszystkie testy struktury zielone.
   Na wąskim ekranie zakres idzie więc do PIERWSZEJ linii na całą szerokość,
   a oba odnośniki stają pod nim w jednym rzędzie, rozepchnięte do krawędzi. */
@media (max-width: 30rem) {
  /* `minmax(0, 1fr)`, nie `auto`: kolumny `auto` biorą szerokość TREŚCI, więc
     dwa długie napisy („← Poprzedni tydzień" i „Następny tydzień →") rozepchnęły
     siatkę PONAD szerokość ekranu i wypchnęły w poziomie CAŁĄ stronę — kafle
     wychodziły za prawą krawędź. Znalezione na zrzucie po pierwszej wersji tej
     reguły; `minmax(0, …)` odbiera kolumnie prawo do bycia szerszą od udziału. */
  .podsumowanie-nawigacja {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .podsumowanie-krok { white-space: normal; }
  /* WIERSZE WPROST, nie z autoukładania: w znaczniku kolejność jest „poprzedni,
     zakres, następny" (bo taka jest kolejność czytania na szerokim ekranie),
     więc samo `grid-column: 1 / -1` na zakresie zepchnęłoby go do DRUGIEGO
     wiersza — między dwa odnośniki. Podanie wiersza jawnie odwraca kolejność
     wizualną bez ruszania znacznika, czyli bez zmiany kolejności dla czytnika
     ekranu i klawiatury. */
  .podsumowanie-zakres { grid-row: 1; grid-column: 1 / -1; }
  .podsumowanie-krok:first-of-type {
    grid-row: 2; grid-column: 1; justify-self: start; padding-left: 0;
  }
  .podsumowanie-krok[rel="next"] {
    grid-row: 2; grid-column: 2; justify-self: end; padding-right: 0; text-align: right;
  }
  /* Pusty element trzymający miejsce jest na wąskim ekranie zbędny: zakres ma
     własną linię, więc nie ma czego przesuwać, a niewidoczna kolumna 9rem
     rozpychałaby siatkę. */
  .podsumowanie-krok-brak { display: none; }
}

/* Uwagi ekranu: pominięty parametr adresu, zawężona widoczność, dni bez obsady.
   Jeden wygląd dla wszystkich trzech, bo wszystkie są tym samym rodzajem
   zdania — wyjaśnieniem, dlaczego widzisz to, co widzisz. Bez ikony
   ostrzeżenia: żadna z tych sytuacji nie jest błędem. */
.podsumowanie-uwaga {
  margin: 0; padding: .55rem .7rem; border-radius: var(--promien);
  background: var(--bg-3); color: var(--tekst-2); font-size: .9rem;
}

/* Pusty tydzień to NORMALNY stan (REQ-037), więc zdanie jest zwykłym akapitem
   karty, nie komunikatem błędu — nic tu nie jest czerwone. */
.podsumowanie-pusto {
  margin: 0; padding: 1rem; border-radius: var(--promien);
  background: var(--bg-2); box-shadow: var(--cien); color: var(--tekst-2);
}

/* Kafle liczb. `auto-fit` z minimum 7.5rem: przy 390 px wychodzą dwie kolumny,
   przy 1200 px pięć — bez ani jednego zapytania medialnego i bez przewijania
   w poziomie (REQ-037). */
.podsumowanie-kafle {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: .7rem; margin: 0; padding: 0; list-style: none;
}
.podsumowanie-kafel {
  display: flex; flex-direction: column; gap: .15rem;
  min-height: var(--dotyk); padding: .7rem .8rem;
  border-radius: var(--promien); background: var(--bg-2); box-shadow: var(--cien);
  border-left: 3px solid var(--linia);
}
.podsumowanie-liczba {
  font-size: 1.6rem; font-weight: 700; line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.podsumowanie-etykieta { color: var(--tekst-2); font-size: .85rem; }
/* „w tym N po terminie" — podliczba WEWNĄTRZ kafla niezrobionych. Zaległe są
   podzbiorem, nie piątą kategorią, więc nie dostają własnego kafla: suma
   przestałaby się zgadzać, a czytelnik dodawałby je dwa razy. */
.podsumowanie-podliczba { color: var(--uwaga); font-size: .8rem; }

/* Kolor jest DODATKIEM do nazwy, nigdy jej zamiennikiem — lewa krawędź, nie
   wypełnienie: pełne tło akcentem wymusiłoby nadpisywanie koloru tekstu, a to
   ta sama pomyłka z kontrastem, która w tym projekcie zdarzyła się trzy razy
   (pułapka 12 w docs/HANDOFF.md). */
.podsumowanie-kafel-zrobione   { border-left-color: var(--ok); }
.podsumowanie-kafel-czekajace  { border-left-color: var(--akcent); }
.podsumowanie-kafel-niezrobione { border-left-color: var(--uwaga); }
.podsumowanie-kafel-anulowane  { border-left-color: var(--tekst-2); }
.podsumowanie-kafel-wszystkie  { background: var(--bg-3); box-shadow: none; }

.podsumowanie-sekcja { display: grid; gap: .6rem; }
/* Pary „nazwa: liczba" w dwóch kolumnach. Liczba wyrównana do prawej i cyframi
   tabelarycznymi, żeby kolumna nie tańczyła między wierszami. */
.podsumowanie-pary {
  display: grid; grid-template-columns: 1fr auto; gap: .3rem .8rem; margin: 0;
}
.podsumowanie-pary dt { color: var(--tekst-2); }
.podsumowanie-pary dd {
  margin: 0; text-align: right; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Rozbicie na domowników. Wiersz łamie się w pionie na wąskim ekranie
   (`flex-wrap`), a blok liczb zawija się wewnętrznie — dzięki temu siedem par
   przy 390 px układa się w kilka linii zamiast wychodzić za krawędź karty.
   To jest ten punkt ekranu, który przy 390 px pęka najłatwiej. */
.podsumowanie-lista { display: grid; gap: .5rem; margin: 0; padding: 0; list-style: none; }
.podsumowanie-osoba {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .8rem;
  min-height: var(--dotyk); padding: .5rem .6rem;
  border-radius: var(--promien); background: var(--bg-3);
}
.podsumowanie-kto {
  display: inline-flex; align-items: center; gap: .5rem;
  flex: 1 1 9rem; min-width: 0;
}
.podsumowanie-imie { font-weight: 600; overflow-wrap: anywhere; }
.podsumowanie-osoba-liczby {
  display: flex; flex-wrap: wrap; gap: .25rem .8rem;
  font-size: .85rem; color: var(--tekst-2);
}
.podsumowanie-para { white-space: nowrap; }
.podsumowanie-para b { color: var(--tekst); font-variant-numeric: tabular-nums; }

/* Wariant „Kafelki": język tego wariantu to większe bloki, więc kafle dostają
   więcej powietrza i wyższą liczbę, a wiersz osoby — pełną szerokość dla bloku
   liczb (liczby pod imieniem, nie obok). */
/* Selektory celują przez klasę-dowód `.podsumowanie-kafelki` / `.podsumowanie-osdnia`
   ze strony TEGO wariantu, a nie tylko przez `:root[data-wariant=…]`. Dwa warunki
   naraz są tu celowe: gdyby wariant kiedyś przestał mieć własny plik strony
   (i odziedziczył wspólny), reguły wyglądu przestałyby obowiązywać RAZEM z klasą,
   zamiast zostać jako martwy kod stosowany do szablonu, dla którego ich nie
   pisano. */
:root[data-wariant="kafelki"] .podsumowanie-kafelki .podsumowanie-kafel { padding: 1rem; }
:root[data-wariant="kafelki"] .podsumowanie-kafelki .podsumowanie-liczba { font-size: 2rem; }
:root[data-wariant="kafelki"] .podsumowanie-kafelki .podsumowanie-osoba-liczby { flex-basis: 100%; }

/* Wariant „Oś dnia" czyta się jak strumień wpisów, więc kafle tracą cień
   i stają się wierszami z linią po lewej — a nawigacja tygodni przywiera do
   lewej krawędzi, jak nadbrewki tego wariantu. */
:root[data-wariant="osdnia"] .podsumowanie-osdnia .podsumowanie-kafel {
  box-shadow: none; background: none; border-left-width: 2px;
  padding: .35rem .7rem;
}
:root[data-wariant="osdnia"] .podsumowanie-osdnia .podsumowanie-zakres { text-align: left; }

/* ---------- Podkład mapy (REQ-041, FEAT-039) ----------
   Dwie klasy i ani jednej „na zapas": kontener mapy i baner o braku podkładu.
   Reguł dla klas Leafleta tu NIE MA i być nie może — jego arkusz leży
   w `css/vendor/leaflet.css` i jest nietykalny (następna aktualizacja
   biblioteki nadpisałaby każdą naszą poprawkę bez śladu), a strażnice CSS
   katalogu `vendor/` nie czytają, więc jego klasy nie stykają się z naszymi
   w ogóle. Wygląd samej mapy stroimy WYŁĄCZNIE przez kontener. */
.mapa-podklad {
  /* Wysokość jest obowiązkowa: Leaflet mierzy kontener, a element o zerowej
     wysokości daje mapę niewidoczną i BEZ ani jednego żądania o kafel —
     czyli objaw wyglądający jak „podkład nie działa". */
  height: min(60vh, 30rem);
  border: 1px solid var(--linia);
  border-radius: var(--promien);
  /* Kafle są prostokątne i wychodzą za zaokrąglenie narożnika. */
  overflow: hidden;
  /* Tło pod kaflami: zanim pierwszy dojedzie, kontener nie ma być białą
     dziurą w karcie. */
  background: var(--bg-3);
}
.mapa-brak-podkladu {
  margin-bottom: .5rem;
  padding: .5rem .7rem;
  border-left: 3px solid var(--uwaga);
  border-radius: var(--promien);
  background: var(--bg-2);
  color: var(--tekst-2);
  font-size: .9rem;
}

/* ---------- Trasa na mapie (REQ-042, FEAT-040) ----------
   Reguł dla klas Leafleta tu NIE MA — jego arkusz leży w `css/vendor/` i jest
   nietykalny (ta sama zasada, co przy podkładzie wyżej). Stylujemy WYŁĄCZNIE
   własne klasy: sekcję, przełącznik, legendę i zawartość `divIcon`, którą
   składa nasz trasa.js.

   BARWY ZNACZNIKÓW I LINII STOJĄ W JS, nie tutaj, i to jest świadome: linia
   SVG Leafleta dostaje kolor atrybutem `color`, a nie klasą, więc rozdzielenie
   barwy znacznika (CSS) od barwy linii tej samej osoby (JS) dałoby dwa miejsca,
   które muszą się zgadzać. Kształt i wzór kreski zostają cechami niebarwnymi
   i one są tutaj. */
.trasa { margin-bottom: 1.25rem; }
.trasa-tytul { font-size: 1.05rem; margin-bottom: .35rem; }
.trasa-podpis { margin-bottom: .5rem; }
.trasa-przelacznik {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin-bottom: .5rem;
}
/* Wybrana pozycja przełącznika. Obwódka i pogrubienie OBOK wypełnienia, bo
   samo wypełnienie akcentem jest różnicą barwy — a stan nie może zależeć
   wyłącznie od koloru (design/README.md). */
.trasa-wybor[aria-pressed="true"] {
  background: var(--akcent);
  color: var(--akcent-tekst);
  border-color: var(--akcent);
  font-weight: 700;
  box-shadow: inset 0 0 0 2px var(--bg-2);
}
.trasa-uwaga {
  margin: 0 0 .5rem;
  padding: .45rem .7rem;
  border-left: 3px solid var(--uwaga);
  border-radius: var(--promien);
  background: var(--bg-2);
  color: var(--tekst-2);
  font-size: .9rem;
}
.trasa-uwaga:empty { display: none; }
/* Mapa trasy dostaje wysokość z `.mapa-podklad`; tutaj tylko odstęp pod nią. */
.trasa-mapa { margin-bottom: .5rem; }
.trasa-pusto {
  margin: 0;
  padding: .8rem;
  border: 1px dashed var(--linia);
  border-radius: var(--promien);
  background: var(--bg-2);
  color: var(--tekst-2);
}

/* LEGENDA. Wiersz na osobę: znak podglądowy, imię, oznaczenie słowami, liczba
   pomiarów i ewentualna uwaga o stanie zwyrodniałym. */
.trasa-legenda {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .4rem .9rem;
}
.trasa-legenda-wpis {
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
  padding: .25rem .5rem;
  border: 1px solid var(--linia);
  border-radius: var(--promien);
  background: var(--bg-2);
}
/* Osoba niepokazana na mapie. Wygaszenie OBOK obwódki przerywanej: samo
   przygaszenie jest różnicą kontrastu, którą część czytelników pominie. */
.trasa-legenda-poza { opacity: .55; border-style: dashed; }
.trasa-legenda-imie { font-weight: 600; }
.trasa-legenda-uwaga { color: var(--uwaga); font-size: .85rem; }

/* ZNAK PODGLĄDOWY W LEGENDZIE i ZNACZNIK NA MAPIE mają te same kształty —
   `.trasa-znak-N` (legenda, indeks stylu z Go) i `.trasa-znacznik-<kszt>`
   (mapa, nazwa kształtu z trasa.js). Dwie rodziny nazw, bo legenda nie zna
   nazw kształtów, a mapa nie zna indeksów; kolejność wiąże je w Go i pilnuje
   tego strażnica. */
.trasa-legenda-znak {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .6rem;
  font-weight: 700;
  color: #fff;
  border: 2px solid var(--bg-2);
}
.trasa-znak-0 { background: #1F5A38; border-radius: 999px; }
.trasa-znak-1 { background: #7A5CC0; border-radius: 0; }
.trasa-znak-2 { background: #B34A12; border-radius: 0; transform: rotate(45deg); }
.trasa-znak-3 { background: #1B5E8A; border-radius: .3rem; }
.trasa-znak-4 { background: #8A2140; border-radius: 999px / 60%; }

/* ZNACZNIK NA MAPIE. `divIcon` dostaje nasze klasy i nasz `innerHTML`, więc
   cały wygląd jest tutaj. Tło jest CIEMNE i nie zależy od motywu: znacznik
   leży na podkładzie mapy, a nie na tle strony, więc biały numer musi być
   czytelny na jasnych i ciemnych kaflach jednakowo. */
.trasa-znacznik {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .7rem;
  font-weight: 800;
  color: #fff;
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .45);
  background: #1F5A38;
}
.trasa-znacznik-nr { line-height: 1; }
.trasa-znacznik-kolo { border-radius: 999px; background: #1F5A38; }
.trasa-znacznik-kwadrat { border-radius: 0; background: #7A5CC0; }
.trasa-znacznik-romb { border-radius: 0; background: #B34A12; transform: rotate(45deg); }
.trasa-znacznik-romb .trasa-znacznik-nr { transform: rotate(-45deg); }
.trasa-znacznik-zaokr { border-radius: .3rem; background: #1B5E8A; }
.trasa-znacznik-pigulka { border-radius: 999px / 60%; background: #8A2140; }
/* POZYCJA NAJNOWSZA. Grubsza obwódka i pierścień — to ona odpowiada na
   pytanie „gdzie jest teraz", a dziesięć jednakowych znaczników nie pozwala
   jej wskazać. Różnica jest w GRUBOŚCI, nie w barwie. */
.trasa-znacznik-ostatni {
  border-width: 3px;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .7), 0 1px 4px rgba(0, 0, 0, .5);
}
/* PODPIS INICJAŁAMI obok znacznika najnowszego — poza jego obrysem, żeby nie
   zasłonić numeru. `pointer-events: none`, bo podpis nie jest celem
   kliknięcia; okienko otwiera sam znacznik. */
.trasa-znacznik-podpis {
  position: absolute;
  left: 30px;
  top: 2px;
  padding: 0 .25rem;
  border-radius: .2rem;
  background: rgba(255, 255, 255, .92);
  color: #1c1c21;
  font-size: .65rem;
  font-weight: 700;
  white-space: nowrap;
  pointer-events: none;
}
.trasa-znacznik-romb .trasa-znacznik-podpis { transform: rotate(-45deg); }
/* Oznaczenie słowami i liczba pomiarów. Oba są w legendzie NOŚNIKIEM TREŚCI,
   nie ozdobą (bez nich legenda zależy od barwy), więc dostają własne reguły:
   oznaczenie nie ma się łamać w środku pary „kształt, linia", a liczba stoi na
   końcu wiersza i nie konkuruje z imieniem o wagę. */
.trasa-legenda-oznaczenie { white-space: nowrap; }
.trasa-legenda-liczba { margin-left: auto; font-variant-numeric: tabular-nums; }

/* ══ 27. Ekran „Role" — nazwane uprawnienia administracyjne ══════════════════
   REQ-044, FEAT-042, zgłoszenie #114. Makiety: design/BRole.dc.html (wariant
   bazowy „Lista", tabela osoba × uprawnienie), design/ARole.dc.html
   („Kafelki") i design/CRole.dc.html („Oś dnia"). Ta sekcja daje WYGLĄD
   BAZOWY — obowiązujący we wszystkich wariantach; obudowę wariantową dokładają
   `wariant-kafelki.css` i `wariant-osdnia.css`.

   TRZY STANY POCHODZENIA NIE ZALEŻĄ OD KOLORU. Wymóg jest w design/README.md
   („stan nigdy nie zależy wyłącznie od koloru") i w rozstrzygnięciu #114
   punkt 4, a powód jest zmierzony: schemat „Bursztyn" ma `--akcent` i `--uwaga`
   w tym samym odcieniu, więc dwa stany odróżniane samą barwą zlałyby się w nim
   w jeden. Nośniki są tu trzy i wszystkie niezależne:
     1. SŁOWO w plakietce — „DOMYŚLNE" / „NADANE" / „ODEBRANE";
     2. KSZTAŁT OBWÓDKI — przerywana dla uprawnienia z roli (odebranie WRÓCI
        przy następnym nadaniu roli), ciągła dla nadanego ręcznie (nie wróci);
     3. ZDANIE o pochodzeniu pod plakietką, składane w Go.
   Kolor jest dopiero czwarty i tylko dla stanu „ODEBRANE".

   KONTRAST (pułapka 12 w docs/HANDOFF.md). Plakietki pochodzenia stoją na
   `--bg-2`/`--bg-3` i mają .75rem, więc NIE liczą się jako duży tekst.
   Dlatego nie mieszamy tu koloru z tłem: tekst bierze `--tekst` albo `--tekst-2`
   (oba policzone dla sześciu schematów × dwóch trybów), a rozpoznawalność
   niesie obwódka. „ODEBRANE" jest jedyną z barwą i bierze `--blad` na tekst
   pełnym kolorem, bez rozjaśniania tłem. */

/* Zdanie nad listą i pasek narzędzi. `role-pasek` zawija, bo przy skalowaniu
   czcionki do 200% (REQ-025) plakietki widoków nie mieszczą się w jednym
   wierszu z polem szukania — a wtedy mają zejść niżej, nie wyjechać za ekran. */
/* Element opakowujący ekran. Nosi identyfikator `page-role`, po którym test
   przeglądarkowy rozstrzyga, który plik strony złożył serwer — więc reguła jest
   tu treścią, a nie tylko uzasadnieniem istnienia klasy: kolumna pionowa
   z jednym odstępem trzyma nagłówek, pasek narzędzi, listę i stopkę w jednym
   rytmie we wszystkich trzech wariantach. */
.role-strona { display: flex; flex-direction: column; gap: .5rem; }
/* Tytuł, dopowiedzenie i plakietka „tylko administrator" w JEDNYM wierszu,
   z plakietką przy prawej krawędzi — jak w makietach. Bez tego plakietka
   spadała pod tytuł i zabierała wysokość ekranowi, którego treścią jest
   długa lista. */
.role-strona .tytul-strony {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem;
}
.role-strona .role-tylko-admin { margin-left: auto; }
/* Zdanie wstępne jest DŁUGIE, bo mówi trzy rzeczy, których z tabeli nie da się
   wyczytać. Szerokość mierzona w znakach (a nie w pikselach) trzyma je
   w granicy czytelności także przy skalowaniu czcionki do 200% (REQ-025);
   100ch, a nie 68ch, bo przy 68 rosło na komputerze do dziesięciu wierszy
   i wypychało pierwszy wiersz listy poza ekran — a to lista jest tu treścią. */
.role-wstep { max-width: 100ch; }
.role-pasek {
  display: flex; flex-wrap: wrap; gap: .75rem 1rem;
  align-items: flex-start; margin: .75rem 0;
}
.role-szukanie { flex-wrap: wrap; }
/* Pole szukania musi POKAZAĆ swoją podpowiedź w całości — mówi, po czym szuka
   (login, e-mail, imię), a urwane „Szukaj po loginie, e-m…" nie mówi nic. */
.role-szukanie input[type="search"] { min-width: 20rem; flex: 1 1 20rem; }
/* Etykieta pola szukania zostaje WIDOCZNA, nie schowana dla czytnika: makiety
   wszystkich trzech wariantów pokazują, po czym pole szuka, a podpowiedź
   w `placeholder` znika w chwili wpisania pierwszej litery. */
.role-szukanie-etykieta { font-size: .85rem; color: var(--tekst-2); align-self: center; }

/* Plakietki widoków filtrowanych. To ZAWĘŻENIA tej samej listy, nie osobne
   ekrany — dlatego wyglądają jak zakładki jednego zbioru, a nie jak pozycje
   nawigacji. Bieżące zawężenie poznaje się po WYPEŁNIENIU i po grubszej
   obwódce, nie po samej barwie. */
.role-zawezenia { display: flex; flex-wrap: wrap; gap: .4rem; }
.role-zawezenie {
  display: inline-flex; align-items: center; gap: .4rem;
  min-height: var(--dotyk); padding: 0 .8rem;
  border: 1px solid var(--linia); border-radius: 999px;
  background: var(--bg-2); color: var(--tekst-2);
  text-decoration: none; font-size: .9rem; font-weight: 600;
  transition: background var(--przejscie), color var(--przejscie);
}
.role-zawezenie:hover { background: var(--bg-3); color: var(--tekst); }
.role-zawezenie-aktywne {
  border-width: 2px; border-color: var(--akcent);
  background: var(--akcent); color: var(--akcent-tekst);
}
.role-zawezenie-liczba { font-weight: 400; font-variant-numeric: tabular-nums; }

/* Legenda pochodzenia. Lista opisowa, bo to pary „plakietka → co znaczy";
   siatka dwukolumnowa trzyma opis przy swojej plakietce także po zawinięciu. */
.role-legenda {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: .2rem .5rem; margin: 0; font-size: .8rem; color: var(--tekst-2);
}
.role-legenda dt { margin: 0; }
.role-legenda dd { margin: 0; }

/* Plakietki pochodzenia — rodzina `badge-pochodzenie-*`, nadawana przez
   `web.KlasaPochodzeniaUprawnienia` (internal/web/badges.go). Kwadratowe
   narożniki i rozstrzelone litery odróżniają je od plakietek stanu zadania
   (`badge-stan-*`), które są pastylkami: dwie rodziny plakietek na jednym
   ekranie nie mogą wyglądać identycznie. */
.badge-pochodzenie-domyslne,
.badge-pochodzenie-nadane,
.badge-pochodzenie-odebrane {
  border-radius: .25rem; background: none;
  font-size: .7rem; font-weight: 700; letter-spacing: .04em;
  padding: .1rem .4rem;
}
/* PRZERYWANA = z roli. Kształt obwódki jest tu jedynym nośnikiem odpowiedzi na
   pytanie „czy odebranie wróci przy następnym nadaniu roli" — i dlatego jest
   w plakietce, a nie tylko w zdaniu obok. */
.badge-pochodzenie-domyslne { border: 1px dashed var(--tekst-2); color: var(--tekst-2); }
.badge-pochodzenie-nadane { border: 1px solid var(--tekst-2); color: var(--tekst-2); }
.badge-pochodzenie-odebrane { border: 1px solid var(--blad); color: var(--blad); }

/* Kolumna „Osoba": inicjały, imię, LOGIN i plakietki rodzin. Login pismem
   maszynowym, bo jest jedyną wartością unikalną w instalacji i dokładnie tym,
   co pokazuje kolumna „Kto" w Zdarzeniach — ma się czytać znak po znaku. */
.role-osoba { display: flex; align-items: flex-start; gap: .6rem; }
.role-inicjaly {
  flex-shrink: 0;
  width: 2.25rem; height: 2.25rem; border-radius: .5rem;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-3); color: var(--tekst-2);
  font-size: .8rem; font-weight: 700;
}
.role-osoba-dane { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.role-imie { font-weight: 700; }
.role-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; font-size: .8rem; }
.role-login { background: var(--bg-3); border-radius: .25rem; padding: .05rem .3rem; }
.role-rodzina { background: var(--akcent-tlo); color: var(--tekst); font-size: .7rem; font-weight: 600; }
/* BRAK RODZINY MA WŁASNĄ POSTAĆ, a nie pustkę: lista jest instalacyjna, więc
   konta bez rodziny na niej stoją i wiersz musi to powiedzieć. Obwódka
   przerywana zamiast wypełnienia — „bez rodziny" nie jest nazwą rodziny. */
.role-bez-rodziny {
  background: none; border: 1px dashed var(--linia);
  color: var(--tekst-2); font-size: .7rem; font-weight: 600;
}
.role-to-ty {
  background: none; border: 1px solid var(--akcent);
  color: var(--tekst); font-size: .7rem; font-weight: 700;
}
.role-tylko-admin {
  background: color-mix(in srgb, var(--uwaga) 18%, var(--bg-3));
  color: color-mix(in srgb, var(--uwaga) 65%, var(--tekst));
  font-size: .7rem; font-weight: 700; letter-spacing: .04em;
}
.role-role { margin: 0; font-size: .8rem; color: var(--tekst-2); }

/* ── Komórka uprawnienia ─────────────────────────────────────────────────── */
.role-komorka { display: flex; flex-direction: column; gap: .3rem; }
.role-przelacznik-form { margin: 0; }
/* PRZEŁĄCZNIK jest PRZYCISKIEM, nie polem wyboru, i to jest rozstrzygnięcie:
   pole wyboru bez JS nie wysyła niczego, gdy jest odznaczone, więc odebranie
   uprawnienia wymagałoby skryptu — a ekran administracyjny musi działać
   z wyłączonym JS tak samo jak przełącznik lokalizacji w profilu. Stan
   DOCELOWY jedzie w polu ukrytym, a przycisk jest jednocześnie etykietą
   i wartością.
   `--dotyk` na wysokość: cel dotyku ≥44 px (REQ-026). */
.role-przelacznik {
  display: inline-flex; align-items: center; gap: .5rem;
  min-height: var(--dotyk); padding: .2rem .7rem .2rem .3rem;
  border: 2px solid var(--linia); border-radius: 999px;
  background: var(--bg-2); color: var(--tekst-2);
  font: inherit; font-size: .85rem; font-weight: 600; cursor: pointer;
  transition: background var(--przejscie), border-color var(--przejscie), color var(--przejscie);
}
.role-przelacznik:hover { border-color: var(--akcent); color: var(--tekst); }
/* Suwak: krążek przy lewej krawędzi dla stanu „wył.", przy prawej dla „wł.".
   POŁOŻENIE, nie barwa, niesie stan — i dlatego krążek jeździ `margin-left`,
   a nie zmienia samego wypełnienia. */
.role-przelacznik-suwak {
  flex-shrink: 0; width: 2.5rem; height: 1.4rem;
  border-radius: 999px; background: var(--bg-3);
  box-shadow: inset 0 0 0 1px var(--linia);
  position: relative;
}
.role-przelacznik-suwak::after {
  content: ""; position: absolute; top: .2rem; left: .2rem;
  width: 1rem; height: 1rem; border-radius: 999px;
  background: var(--tekst-2);
  transition: transform var(--przejscie), background var(--przejscie);
}
.role-przelacznik-wl { border-color: var(--akcent); color: var(--tekst); }
.role-przelacznik-wl .role-przelacznik-suwak { background: var(--akcent-tlo); }
.role-przelacznik-wl .role-przelacznik-suwak::after {
  transform: translateX(1.1rem); background: var(--akcent);
}
.role-przelacznik-napis { text-align: left; }
/* Nazwa uprawnienia W PRZYCISKU jest schowana wzrokowo w wariancie bazowym
   (nazwę niesie nagłówek kolumny), ale ZOSTAJE w znaczniku dla czytnika ekranu:
   „nadaj" bez dopowiedzenia jest w tabeli o dwóch kolumnach uprawnień
   niejednoznaczne, a szesnaście przycisków „nadaj" na stronie to szesnaście
   pytań bez odpowiedzi. Warianty „Kafelki" i „Oś dnia" pokazują ją wprost —
   tam nagłówka kolumny nie ma. */
.role-przelacznik-co {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); border: 0;
}
/* ZAMEK KONTA `tablo` — na miejscu przełącznika, nie wyszarzony przełącznik.
   Wyszarzona kontrolka bez wyjaśnienia jest w tym projekcie brakiem; powód
   idzie zdaniem przez cały wiersz (`role-blokada-powod`), a tu stoi znak
   i napis, żeby stan dał się odczytać bez czytania zdania. */
.role-zamek {
  display: inline-flex; align-items: center; gap: .4rem;
  min-height: var(--dotyk); padding: .2rem .6rem;
  border: 2px dashed var(--linia); border-radius: .4rem;
  background: var(--bg-3); color: var(--tekst-2);
  font-size: .85rem; font-weight: 600;
}
.role-zamek-znak { font-size: 1.1rem; line-height: 1; }
.role-zamek-napis { white-space: nowrap; }
.role-stan { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.role-stan-slowo { font-size: .85rem; font-weight: 700; }
.role-pochodzenie { flex-basis: 100%; }
/* Skutek zdaniem przy KAŻDYM przełączniku (punkt 5 zlecenia #114). W wariancie
   bazowym stoi w nagłówku kolumny, więc w komórce jest ukryty wzrokowo, ale
   zostaje dla czytnika ekranu — ten ogłasza nagłówek kolumny osobno i nie
   zawsze. Warianty kaflowy i osiowy pokazują go wprost. */
.role-skutek {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); border: 0;
}
.role-kolumna-skutek { display: block; font-weight: 400; }
.role-blokada-powod {
  margin: .2rem 0 0; font-size: .8rem; color: var(--blad);
}
.role-audyt { font-size: .8rem; white-space: nowrap; }

/* ── Tabela wariantu bazowego ────────────────────────────────────────────── */
.role-lista { padding: 0; overflow-x: auto; }
/* Tabela ma minimalną szerokość, bo komórka niesie przełącznik ORAZ zdanie
   o pochodzeniu — ściśnięta zawija po jednym słowie na wiersz. Poniżej tej
   szerokości przewija się `.role-lista`, nie cała strona: poziome przewijanie
   dokumentu było w tym projekcie osobną awarią (zgłoszenia #44, #93). */
.role-tabela { min-width: 52rem; }
.role-tabela th { vertical-align: bottom; }
.role-tabela td { vertical-align: top; }
/* Własny wiersz oglądającego jest WYRÓŻNIONY, ale NIE JEST WYJĄTKIEM
   (#114 punkt 8): przełączniki w nim są czynne, bo najczęstsze pierwsze użycie
   tego ekranu to administrator nadający sobie „Testera". Wyróżnienie jest tłem
   i lewą krawędzią, a nie brakiem kontrolki. */
/* Wiersz osoby. Górna linia rozdziela wiersze także tam, gdzie wiersz „powodu
   blokady" konta `tablo` zdejmuje swoją — bez niej dwa wiersze tego samego
   konta zlewałyby się z następnym kontem w jeden blok. */
.role-wiersz { border-top: 1px solid var(--linia); }
.role-wiersz-ja { background: var(--akcent-tlo); box-shadow: inset .2rem 0 0 var(--akcent); }
.role-wiersz-zablokowany { background: var(--bg-3); }
.role-wiersz-powod td { padding-top: 0; border-top: none; }
.role-pusto { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; }
.role-stopka { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; }

/* Podpozycja menu bocznego — „Testerzy" pod „Rolami". Wcięcie i mniejszy krój
   mówią, że pozycja prowadzi na TĘ SAMĄ trasę z zawężeniem, a nie do drugiego
   ekranu (rozstrzygnięcie #114 punkt 1); adnotację niesie sam napis. */
.sidebar-podpozycja { padding-left: 1.6rem; font-size: .9rem; }

/* Klasy układu wariantów mają reguły w arkuszach swoich wariantów
   (`wariant-kafelki.css`, `wariant-osdnia.css`) — tam, gdzie obudowa
   faktycznie się różni. Reguła bazowa dla siatki kafli jest jednak POTRZEBNA:
   plik strony wariantu „Kafelki" renderuje się także wtedy, gdy arkusz
   wariantu się nie wczyta, a wtedy kafle mają zostać kartami jedna pod drugą,
   nie rozsypanym blokiem. */
.role-siatka { display: grid; gap: .75rem; }
.role-kafel { display: flex; flex-direction: column; gap: .5rem; }

/* Zawężenie dziennika zdarzeń do jednego konta (zgłoszenie #114): dziennik
   zawężony MUSI powiedzieć, że jest zawężony, i dać drogę powrotną do pełnego.
   Szerokość mierzona w znakach, bo to zdanie do przeczytania, nie pasek stanu. */
.zdarzenia-zawezenie { max-width: 68ch; }
/* ══════════════════════════════════════════════════════════════════════════
   EKRAN „MODUŁY" (REQ-043, FEAT-041, #113) — makiety design/?Moduly.dc.html.

   STAN MODUŁU NIE ZALEŻY OD KOLORU (design/README.md, ergonomia). Każdy
   nośnik stanu niesie go POTRÓJNIE: słowem (napis „Włączony" / „Testowanie" /
   „Wyłączony"), ZNAKIEM (✓ / ⚠ / ✗ — `.moduly-znak`, treść, nie ozdoba)
   i FAKTURĄ — segment „Testowanie" ma ukośne prążki, „Wyłączony" obrys
   przerywany. Wszystkie trzy czyta się w odcieniach szarości i przy
   daltonizmie; barwa jest czwartym, nadmiarowym nośnikiem.

   Reguły są BAZOWE (bez `:root[data-wariant=…]`), bo ekran ma we wszystkich
   wariantach tę samą treść i ten sam kontrakt — wariant dokłada u siebie
   wyłącznie układ (`wariant-kafelki.css`, `wariant-osdnia.css`).
   ══════════════════════════════════════════════════════════════════════════ */

/* Znak stanu. `inline-block` z szerokością, żeby napisy w kolumnie segmentów
   zaczynały się w tym samym miejscu bez względu na szerokość glifu. */
.moduly-znak {
  display: inline-block;
  min-width: 1.1em;
  margin-right: .35rem;
  font-weight: 700;
  text-align: center;
}

/* Trzy karty „co znaczy który stan" NAD listą — czytane przed zmianą. */
.moduly-skutki {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: .75rem;
  margin-bottom: 1rem;
}
.moduly-skutek { border-left: 4px solid var(--linia); }
.moduly-skutek-tytul { margin: 0 0 .25rem; font-size: 1rem; }

/* BARWA I FAKTURA STANU — wspólne dla kart skutków, plakietek i segmentów.
   Faktura („Testowanie" prążki, „Wyłączony" obrys przerywany) jest tu
   nośnikiem równorzędnym z barwą, nie jej ozdobą. */
.stan-wlaczony { border-left-color: var(--ok); }
.stan-testowanie {
  border-left-color: var(--uwaga);
  background-image: repeating-linear-gradient(135deg,
    rgb(0 0 0 / 0) 0 6px, rgb(128 128 128 / .14) 6px 12px);
}
.stan-wylaczony { border-left-color: var(--blad); border-left-style: dashed; }

/* Wstęga testerów — jedna na ekran, bo uprawnienie „Tester" jest jedno na
   całą aplikację. Wariant „brak" jest MOCNIEJSZY wizualnie, bo mówi
   o pułapce: moduł w testach, którego nie widzi nikt. */
.modul-testerzy {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
  padding: .6rem .8rem;
  margin-bottom: 1rem;
  border: 1px solid var(--ok);
  border-radius: var(--promien);
  background: var(--bg-2);
}
.modul-testerzy-brak { border-color: var(--blad); border-width: 2px; }
.modul-testerzy-gdzie { color: var(--tekst-2); font-size: .85em; }

/* Lista modułów. */
.moduly-lista { display: grid; gap: .75rem; }
.modul-wiersz { display: grid; gap: .5rem; }
.modul-naglowek { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.modul-nazwa { margin: 0; font-size: 1.15rem; }
.modul-opis { margin: 0; color: var(--tekst-2); }
.modul-stan { font-weight: 700; }
.modul-przygotowanie {
  background: var(--bg-3);
  color: var(--tekst-2);
  letter-spacing: .05em;
  text-transform: uppercase;
}

/* „Co obejmuje — zniknie po wyłączeniu": pełna lista pozycji NA STAŁE, nie po
   rozwinięciu. Skutek ma być widoczny PRZED zmianą stanu. */
.modul-pozycje { display: grid; gap: .35rem; }
.modul-pozycje-lista {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.modul-pozycja {
  padding: .15rem .5rem;
  border: 1px solid var(--linia);
  border-radius: .35rem;
  background: var(--bg-3);
  font-size: .85rem;
  font-weight: 600;
}

/* PRZEŁĄCZNIK TRZECH STANÓW. Cel dotykowy ≥44 px (REQ-026) na każdym
   segmencie, bo każdy jest osobną decyzją. */
.modul-segmenty { display: flex; flex-wrap: wrap; gap: .35rem; }
.modul-segment {
  flex: 1 1 9rem;
  min-height: var(--dotyk);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--linia);
  font-weight: 700;
}
.modul-segment-teraz { border-width: 3px; border-color: var(--tekst); }
.modul-segment-wygaszony { border-style: dashed; opacity: .55; cursor: not-allowed; }
/* Dopisek pod nazwą stanu („teraz" / „niedostępne") — mówi to samo, co
   obwódka, ale słowem, więc nie zależy od widzenia grubości linii. */
.modul-segment-powod {
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--tekst-2);
}

/* POTWIERDZENIE WYŁĄCZENIA — natywny <dialog class="modal"> (SREQ-033).
   Szerokie, bo w środku jest osiem pozycji i trzy zdania; `confirm()`
   przeglądarki nie pomieściłby ani jednego z nich. */
.modul-dialog { max-width: 34rem; }
.modul-dane-zostaja {
  padding: .5rem .7rem;
  border: 1px solid var(--ok);
  border-radius: .45rem;
  background: var(--bg-3);
}
.modul-dialog-akcje { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .75rem; }
/* Akcja główna okna: 56 px (design/README: akcja główna 56–64 px) i barwa
   błędu, bo odbiera ekran całej instalacji. */
.modul-dialog-tak { min-height: var(--dotyk-glowny); background: var(--blad); }

/* Stopka ekranu: kod odpowiedzi, uzasadnienie i wskazanie „Zdarzeń". */
.moduly-stopka { margin-top: 1rem; }

/* ADNOTACJA PRZY ZALEGŁYM POWIADOMIENIU MODUŁU NIEDOSTĘPNEGO (REQ-043).
   Wpis zostaje na liście — to historia domownika — ale nie prowadzi nigdzie,
   więc adnotacja musi wyglądać jak stan wpisu, a nie jak odnośnik. */
.powiadomienie-adnotacja {
  display: inline-block;
  padding: .1rem .45rem;
  border: 1px dashed var(--linia);
  border-radius: .35rem;
  color: var(--tekst-2);
  font-size: .8rem;
}

/* Lista pozycji W POTWIERDZENIU jest listą STRAT, nie spisem zawartości —
   przekreślenie mówi to bez ani jednego dodatkowego słowa i nie zależy od
   barwy (design/README.md, ergonomia). */
.modul-pozycje-usuwane .modul-pozycja { text-decoration: line-through; }
/* Plakietka stanu bieżącego przy nazwie modułu niesie barwę i fakturę stanu
   przez klasy `stan-*`, które pracują na LEWEJ krawędzi — bez tej krawędzi
   plakietka byłaby szara i stan czytałoby się wyłącznie ze słowa. */
.modul-stan { border-left: 4px solid var(--linia); }
