/* ============================================================================
   05-komponenty.css — elementy WSPÓLNE dla całej aplikacji
   Panele, tabele danych, okna, pigułki, kropki, toasty, paginacja, zakładki,
   przełącznik, stan pusty. Widoki (Projekty, Kalkulator, Kartoteki) tego NIE
   powtarzają — tu jest jedyna definicja.

   Zasady: brak wymuszania priorytetu, wartości wyłącznie z 00-tokens.css,
   selektory płytkie, warianty przez klasę modyfikującą. Tryb ciemny działa sam,
   bo wszystko idzie przez tokeny — nie ma tu ani jednej reguły [data-theme].
   Wyjątek: szerokości kontenerów (okno, toast) nie mają tokenu, bo dotyczą
   jednego komponentu — siedzą w zmiennych lokalnych (--okno-w, --toast-w),
   dzięki czemu wariant nadpisuje je jedną linijką.
   ============================================================================ */
/* === 1. PANELE I KARTY — pojemnik na każdą sekcję widoku ==================== */
/* Kolumna: nagłówek zostaje na górze, treść dostaje resztę wysokości. */
.panel { display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--surface); box-shadow: var(--cien-1); }
/* Pasek tytułu: tytuł z lewej, akcje z prawej (odpycha je .spacer). */
.panel > header { display: flex; align-items: center; gap: var(--s-2); min-height: var(--h-row); padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--line); }
.panel > header h2, .panel > header h3 { font-size: var(--t-md); font-weight: var(--w-bold); line-height: var(--lh-tight); }
/* Jeden element rozpycha nagłówek — bez niego każdy widok kombinuje własny margines. */
.panel > header .spacer { margin-left: auto; }
.panel-body { min-height: 0; padding: var(--s-4); }
/* Karta = ten sam materiał co panel, ale samodzielny kafel bez nagłówka. */
/* NAPRAWA 11.08.2026 — .card miala promien 9, a .panel 12, mimo ze komentarz
   wyzej mowi wprost „ten sam material co panel". Ramka i cien byly te same,
   rozjezdzal sie tylko rog. */
.card { min-width: 0; padding: var(--s-4); border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--surface); box-shadow: var(--cien-1); }
/* Wyciete 11.08.2026 — martwe reguly, zero wystapien w public/js. Szczegoly: AUDYT-CSS-2026-08-11.md B9. */
/* === 3. MODALE — okno na środku, scroll TYLKO w treści ===================== */
/* Przyciemnienie + rozmycie odcina uwagę od widoku pod spodem.
   .app-confirm-overlay dzieli tę regułę, bo to ten sam mechanizm. */
/* Wyściółka 20 px, nie 24 (monolit 7914). Te 4 px z każdej strony to 8 px
   wysokości, których brakowało oknom liczonym jako `100dvh - 32px`: okno
   kończyło się 8 px od dołu ekranu zamiast 16 i przy niższym oknie
   przeglądarki tło dostawało własny pasek przewijania. */
.modal-backdrop, .app-confirm-overlay { position: fixed; inset: 0; z-index: var(--z-okno); display: grid; place-items: center; padding: 20px; background: color-mix(in srgb, var(--zaslona-kolor) 55%, transparent); backdrop-filter: blur(var(--s-1)); }
/* Potwierdzenie wymaga DECYZJI, więc stoi nad oknami, dymkami i lightboxem —
   1:1 z monolitem (12000). Na wspólnej warstwie okien (60) chowało się pod
   toastem zapisu i pod powiększonym zdjęciem. */
.app-confirm-overlay { z-index: var(--z-potwierdzenie); }
/* WYLOGOWANIE PO BEZCZYNNOŚCI (24.10.2026): logowanie nad ekranem na NIEPRZEZROCZYSTEJ zasłonie — widok pod spodem (dane, formularze,
   otwarte okna) zostaje nietknięty, ale nie jest widoczny dla kogoś, kto usiadł przy odblokowanym komputerze. */
.app-confirm-overlay.is-blokada { background: var(--bg); backdrop-filter: none; }
/* Nagłówek i stopka nieruchome, treść scrolluje.
   WYSOKOŚĆ: `min(760px, 92vh)`, NIE samo 90vh (monolit 7960). Na ekranie
   1270 px nasze 90vh dawało oknu 1143 px i rozciągało je na siłę: treść
   dostawała własny pasek przewijania WEWNĄTRZ okna, które samo też się
   przewijało — stąd dwa paski naraz. 760 px to sufit v8 dla zwykłych okien. */
/* W1 (27.09.2026): JEDNA SKÓRKA OKNA na komputerze i telefonie — szare tło okna (--okno-tlo), na nim białe grupy
   (19-modale-tresc.css „SKÓRKA OKNA”), bez ramki, róg arkusza 20 px. Telefon zmienia tylko to, co wymusza ekran
   (cały ekran: bez rogu i cienia — 9-telefon/2-okna.css §1). */
.modal { --okno-w: 1120px; display: flex; flex-direction: column; width: min(var(--okno-w), 100%); max-height: min(760px, 92vh / var(--skala-ui)); overflow: hidden; border: 0; border-radius: var(--arkusz-r); background: var(--okno-tlo); box-shadow: var(--cien-3); }
/* W1: POLA W OKNIE — róg 12 px i fokus jak na telefonie. Zmienne czytają reguły pól w 03-kontrolki (i pola-przyciski:
   data, lista osób w 15-zadania); poza oknem ich nie ma. POPR3-pola (28.09.2026): okno NIE ma już własnego tła, ramki
   ani obrysu pola — szare wypełnienie bez ramki wyglądało jak pole zablokowane. Tło i ramka z tokenów --pole-* (00-tokens). */
.modal, .app-confirm {
  --okno-pole-r: var(--pole-r);
  --okno-pole-fokus-ramka: var(--accent);
  --okno-pole-fokus-pierscien: 3px;
  --okno-pole-brak-obrys: 0 0 0 1px var(--danger);
  --okno-pole-brak-fokus: 0 0 0 1px var(--danger);
  /* W1d: róg każdego przycisku w oknie (treść, stopka, potwierdzenie) — ten sam na obu ekranach. */
  --okno-przycisk-r: var(--r);   /* wzór: przycisk w oknie i poza nim ma ten sam róg 12 px (`.btn`) */
}
/* RM7 (v15 b14, 04.10.2026): okna mają TĘ SAMĄ skalę pisma co strony — jak we wzorze wyglądu (12 podpisy, 13 tekst
   drugorzędny, 14 treść). Reguła POPR2-x04, która w oknach podnosiła --t-xs i --t-sm do --t-base, zdjęta: przy skali wzoru
   robiła z całego okna 14 px (Kalkulator i Wiadomości miały 80–95 % tekstu większego niż we wzorze). */
/* W1: nagłówek bez kreski — stoi na tle okna, pod nim zaczynają się białe grupy (jak na telefonie). */
.modal > header { display: flex; align-items: center; gap: var(--s-3); flex: 0 0 auto; padding: var(--s-3) var(--s-5); border-bottom: 0; }
.modal > header h2, .modal > header h3 { font-size: var(--t-lg); font-weight: var(--w-bold); line-height: var(--lh-tight); }
/* Zamknięcie zawsze na końcu paska. Klasy `.modal-close` NIE MA w markupie —
   JS renderuje `<button class="icon-button danger">`, więc celujemy w nie. */
.modal > header > .icon-button:last-child { margin-left: auto; }
/* W1: „?” i „×” w nagłówku okna — sam znak, bez tła i ramki; pod kursorem okrągłe szare podświetlenie.
   „?” w kolorze akcentu, „×” w kolorze tekstu — tak samo na telefonie (tam tylko większe pole dotyku, 9-telefon/2-okna). */
.modal > header .icon-button[data-action="close-modal"] { border-color: transparent; border-radius: var(--r-pill); background: transparent; color: var(--text); }
.modal > header .modal-help-btn { width: var(--h-control); height: var(--h-control); border-color: transparent; border-radius: var(--r-sm); background: transparent; color: var(--text-muted); }   /* runda 3: jak „×” na karcie — ikona okręgu z „?” (.icon-rama-pomoc 18 px), róg 8, szara; wzór: .ui-ik 36 px (dotyk 44) */
.modal > header :is(.icon-button[data-action="close-modal"], .modal-help-btn):hover { border-color: transparent; background: var(--btn-2); }
/* Treść = jedyny element ze scrollem (min-height:0 odblokowuje go we flexie). */
.modal > form { display: grid; gap: var(--s-4); flex: 1 1 auto; min-height: 0; overflow: auto; padding: var(--s-5); }
/* Treść okna zbudowana jako <div class="modal-form"> (nie <form>) też jest jedynym elementem ze scrollem. Bez tego okno (overflow: hidden)
   ucinało środek i stopkę, gdy treść była wyższa niż okno: „Edytuj przypomnienie” (stopka 95 px pod ekranem telefonu), Dzwonek na
   telefonie poziomo (58 px), a na komputerze stopka przypomnienia o 14 px. Wygląd, odstępy i padding zostają z `.modal-form`;
   `flex: 0 1 auto` = treść tylko KURCZY się (i przewija), gdy nie mieści się w oknie — gdy mieści się, wysokość jest jak dotąd. `:where()` = bez wagi, więc własne reguły okien (np. Nowe zadanie, Zadanie) nadal wygrywają. */
.modal > :where(div.modal-form) { flex: 0 1 auto; min-height: 0; overflow: auto; }
/* Akcje do prawej — potwierdzenie tam, gdzie kończy się czytanie. */
/* W1: stopka na tle okna z włosową kreską nad sobą (jak na telefonie; było: jasnoszara stopka z pełną kreską). */
.modal > footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--s-2); flex: 0 0 auto; padding: var(--s-3) var(--s-5); border-top: 1px solid var(--kreska-wlos); background: var(--okno-tlo); }
.modal > footer .spacer { margin-right: auto; }
/* POPR-n22: „Więcej ⋯” w stopce (ui/stopka-wiecej.js) — komputer: pojemnik przezroczysty, przyciski w rzędzie jak dotąd;
   tablet i telefon: przyciski dodatkowe w menu nad stopką, w stopce zostaje „Anuluj”, akcja główna i „Więcej ⋯”. */
.stopka-wiecej, .stopka-wiecej-panel { display: contents; }
.stopka-wiecej-przycisk { display: none; }
:root:is([data-ui="mobile"], [data-ui="tablet"]) .stopka-wiecej { position: relative; display: flex; flex: 0 0 auto; }
:root:is([data-ui="mobile"], [data-ui="tablet"]) #modal-root .modal > footer > .stopka-wiecej[data-stopka-wiecej] { flex: 0 0 auto; }
:root:is([data-ui="mobile"], [data-ui="tablet"]) .stopka-wiecej-przycisk { display: inline-flex; }
:root:is([data-ui="mobile"], [data-ui="tablet"]) .stopka-wiecej-panel { position: absolute; right: 0; bottom: calc(100% + var(--s-2)); z-index: var(--z-dymek); display: none; flex-direction: column; gap: var(--s-2); width: max-content; min-width: 240px; max-width: calc(100vw / var(--skala-ui) - var(--s-5)); padding: var(--s-2); border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--surface); box-shadow: var(--cien-3); }
:root:is([data-ui="mobile"], [data-ui="tablet"]) .stopka-wiecej.is-otwarte > .stopka-wiecej-panel { display: flex; }
:root:is([data-ui="mobile"], [data-ui="tablet"]) .stopka-wiecej-panel > :is(.button, button) { width: 100%; justify-content: center; }
/* POPR-n45: jedna kolejność w każdej stopce okna — przycisk główny zawsze ostatni (z prawej), także w grupie przycisków
   wewnątrz stopki (okno Design: było Anuluj · Zapisz zmiany · Generuj prezentację · Kopiuj link → Anuluj · Kopiuj link ·
   Zapisz zmiany · Generuj prezentację). „Anuluj” zostaje tam, gdzie jest (przed głównym; „Pomoc” z lewej za rozpórką). */
.modal > footer > .button.primary, .modal > footer > * > .button.primary { order: 1; }
/* ROL-1: powód wyłączonego „Zapisz" (brak prawa) — obok przycisku, bo na dotyku nie ma dymków. */
.blokada-prawa-opis { max-width: 46ch; color: var(--text-muted); font-size: var(--t-xs); line-height: 1.3; }
/* Okna robocze (kalkulacja, projekt) mają zająć ekran — tabela w środku potrzebuje miejsca. */
/* Monolit 7967: `calc(100vh - 40px)`, nie 90vh — przy ekranie 1000 px to
   różnica 60 px wysokości okna. */
.modal.full-height { display: flex; flex-direction: column; height: calc(100vh / var(--skala-ui) - var(--h-row)); max-height: calc(100vh / var(--skala-ui) - var(--h-row)); overflow: hidden; }
/* OKNA KARTOTEK mają w v8 SUFIT 760 px, a nie całą wysokość ekranu
   (monolit 15030). Bez tego każde okno kartoteki rozciągało się na 1230 px
   i dawało drugi pasek przewijania. Wyjątki niżej mają własne wymiary. */
/* `.planning-modal` USUNIĘTY z tej reguły — to był selektor martwy.
   Okno Planowania powstaje z klasami „planning-modal full-height"
   (public/js/okna/), BEZ `catalog-modal`, więc warunek
   `.catalog-modal.planning-modal` nie mógł się spełnić nigdy.
   Rozmiar bierze z `.modal.full-height` i to jest poprawne — Planowanie
   ma w środku kolumnę czatu i listę konstrukcji, więc korzysta z pełnej
   wysokości, a nie z sufitu 760 px właściwego kartotekom. */
.modal.catalog-modal.full-height, .modal.catalog-modal.material-modal { width: min(1180px, 100%); }   /* położenie i wysokość: 20-okna-polozenie.css */
/* Ekipa i materiał to jedyne kartotekowe okna, które w v8 biorą pełną wysokość
   (monolit 15079 i 15426) — mają w środku długie tabele. */
.modal.catalog-modal.crew-modal.full-height { width: min(1440px, 100%); }
.modal.catalog-modal.material-modal.full-height { width: min(1320px, 100%); }
/* Dostawca i klient: wiersz osoby kontaktowej ma pięć kolumn (osoba, stanowisko,
   mail, telefon z kierunkowym, kosz). Przy 1180 px na treść zostaje 829 px i
   kontrolki wychodzą poza swoje kolumny — pole formularza celowo NIE kurczy się
   poniżej naturalnej szerokości (`.field` w 03-kontrolki, decyzja z v8). Zamiast
   ściskać kolumny poszerzamy okno, tak samo jak wyżej robią materiał i ekipa. */
/* WYSOKOŚĆ: wspólna reguła kartotek daje `height: min(760px, …)`, a treść okna klienta
   potrzebuje 768 px — brakowało ośmiu pikseli i okno startowało z paskiem przewijania,
   mimo że na ekranie było jeszcze 991 px miejsca (zmierzone 13.08.2026).
   ZMIERZONE TRZY WARIANTY, zanim to tu trafiło:
     `height: auto` i `fit-content` — na dużym ekranie dobrze, ale gdy `max-height`
     przytnie okno (mały ekran, 500 px), środek zostaje przy swojej wysokości i NIE
     przewija się: dolne pola stają się nieosiągalne. ODRZUCONE.
     Sztywne 880 px — na dużym ekranie pasek znika (treść rozciąga się do 765 px,
     bez pustej dziury), na małym `max-height` przycina okno, wysokość zostaje
     określona i środek przewija się normalnie. TO ZOSTAJE. */
.modal.catalog-modal.supplier-modal.full-height, .modal.catalog-modal.client-modal.full-height { width: min(1280px, 100%); }
/* Wysokość i położenie okien pełnowysokościowych (także odstęp tła, który kiedyś ucinał okno u dołu) — 20-okna-polozenie.css. */
/* Wąsko do krótkich formularzy: szerokie okno na trzy pola wygląda pusto. */
.modal.narrow { --okno-w: 560px; }
/* === 4. PIGUŁKI STATUSU — jedna reguła, ton wstrzykiwany przez --ton ======= */
/* Tony definiujemy RAZ i osobno od komponentu: te same klasy zasilają pigułkę,
   kropkę etapu i pasek etapu w kaflu.

   DOKŁADNIE TE dziesięć nazw wypuszcza kod — nic więcej nie dopisuj „na zapas":
     statusToneKey()  (public/js/00-state.js) → consulting, design, plans,
                        production, montaz, warranty, certification,
                        pending, inprogress, done
     STAGE_TONE_BY_ID (00-state.js)          → ten sam zestaw
   Klasę składa JS w locie: `"tone-" + klucz` (public/js/rdzen/ + public/js/ui/).
   Uwaga: „montaz" jest po POLSKU, reszta po angielsku — tak zwraca kod.

   Wycięte jako martwe (nigdy nie powstają): `[data-tone="…"]` (atrybutu nikt nie
   ustawia), polskie duplikaty (tone-plany, tone-wtoku…), tone-assembly,
   tone-late, tone-planned, tone-ok, tone-warn, tone-danger, tone-accent. */
/* KAŻDY ton wnosi DWIE wartości: `--ton` (tło, ramka, kropka, pasek) oraz
   `--ton-tekst` (napis). Dodane 11.08.2026 — powód policzony, nie wyczuty:
   pigułka malowała napis PEŁNYM tonem na tle będącym 16-procentowym odcieniem
   TEGO SAMEGO tonu. Zmierzone kontrasty przed poprawką:
     Design 1,75 · Oczekuje 2,25 · Gotowy 2,76 · Produkcja 2,92 ·
     Certyfikacja 3,04 · W toku 3,34 · Gwarancja 3,76 · Konsultacje 3,90 ·
     Plany 4,13 · Montaż 4,23   — wszystkie poniżej progu 4,5:1.
   Napisy niosą STAN projektu, więc są czytane najczęściej w całej aplikacji.

   Dlaczego procent jest INNY dla każdego tonu, a nie jeden wspólny: tony
   różnią się jasnością o rząd wielkości (Design to jasny błękit #7ec2f0,
   Gwarancja to nasycona czerwień #dc2626). Jedna wspólna domieszka albo nie
   ratuje jasnych (przy 70% Design nadal ma 3,06:1), albo niepotrzebnie zabija
   nasycenie ciemnych. Procent dobrany osobno = najmniejsza możliwa zmiana
   każdego koloru, przy której napis przekracza próg. Wyniki po poprawce
   mieszczą się w wąskim paśmie 4,64–4,92:1, więc pigułki są wreszcie
   jednakowo czytelne — a że domieszka zachowuje barwę, statusy nadal
   rozróżnia się kolorem (pamięć `statusy-kolory`).
   Motyw ciemny wychodzi sam: tam `--text` jest jasny, więc ta sama domieszka
   ROZJAŚNIA napis zamiast go przyciemniać. */
.tone-consulting { --ton: var(--etap-konsultacje); --ton-tekst: color-mix(in srgb, var(--etap-konsultacje) 85%, var(--text)); }
.tone-design { --ton: var(--etap-design); --ton-tekst: color-mix(in srgb, var(--etap-design) 50%, var(--text)); }
.tone-plans { --ton: var(--etap-plany); --ton-tekst: color-mix(in srgb, var(--etap-plany) 90%, var(--text)); }
.tone-production { --ton: var(--etap-produkcja); --ton-tekst: color-mix(in srgb, var(--etap-produkcja) 70%, var(--text)); }
.tone-montaz { --ton: var(--etap-montaz); --ton-tekst: color-mix(in srgb, var(--etap-montaz) 90%, var(--text)); }
.tone-warranty { --ton: var(--etap-gwarancja); --ton-tekst: color-mix(in srgb, var(--etap-gwarancja) 85%, var(--text)); }
.tone-certification { --ton: var(--etap-certyfikacja); --ton-tekst: color-mix(in srgb, var(--etap-certyfikacja) 70%, var(--text)); }
.tone-pending { --ton: var(--status-oczekuje); --ton-tekst: color-mix(in srgb, var(--status-oczekuje) 70%, var(--text)); }
.tone-inprogress { --ton: var(--status-wtoku); --ton-tekst: color-mix(in srgb, var(--status-wtoku) 75%, var(--text)); }
.tone-done { --ton: var(--status-gotowy); --ton-tekst: color-mix(in srgb, var(--status-gotowy) 65%, var(--text)); }
/* Tło = 16% tonu wmieszane w powierzchnię (czytelne w obu motywach), tekst = pełny
   ton, ramka = ton przygaszony. Bez podanego tonu: neutralna szarość.

   UWAGA NA KASKADĘ: brak tonu obsługujemy FALLBACKIEM w `var(--ton, …)`,
   a NIE deklaracją `--ton: var(--text-muted)` w tej regule. Deklaracja miałaby
   tę samą wagę co `.tone-*` wyżej, ale stoi PÓŹNIEJ w pliku — więc wygrywałaby
   z każdą klasą tonu i wszystkie pigułki byłyby szare (tak było; zmierzone:
   31 pigułek z klasą tonu, wszystkie w jednym kolorze). */
/* Wysokość bierze się z wyściółki i interlinii, NIE ze sztywnego `min-height`
   — monolit ma 3px 8px, 13 px, wagę 600 i wychodzi 23-25 px. Nasze zerowe
   wcięcie z `min-height: 27px` dawało pigułki wyższe o 2-4 px i o stopień
   drobniejsze, i to na KAŻDYM ekranie, bo to komponent globalny. Wysokość
   wiersza projektu jest w obu wersjach ta sama (57 px) — sprawdzone. */
/* NAPRAWA 11.08.2026 — NAPIS PRZYCIEMNIONY DOMIESZKĄ --text.
   Pigułka malowała napis PEŁNYM tonem na tle będącym 16-procentowym odcieniem
   TEGO SAMEGO tonu — czyli kolorem na jego własnym bladym wariancie. Zmierzone
   na żywej stronie (z uwzględnieniem przezroczystości): „W toku" 3,34:1,
   „Gwarancja" 3,76:1, „Konsultacje" 3,90:1 — wszystkie poniżej progu 4,5:1,
   a to są napisy niosące STAN projektu, więc czyta się je najczęściej.
   Domieszka `--text` przyciemnia napis ZACHOWUJĄC barwę tonu, więc statusy
   nadal rozróżnia się kolorem (pamięć `statusy-kolory`), tylko dają się
   przeczytać: po zmianie 5,07 / 5,64 / 5,34:1.
   Wartość 70% to nie nowy pomysł — dokładnie tak liczy kolor tekstu tabela
   walidacji BOM (24-bom-katalogi.css:33 i :44). Ten sam wzór działa też
   w trybie ciemnym, bo tam `--text` jest jasny i domieszka rozjaśnia. */
.status-pill { display: inline-flex; align-items: center; gap: var(--s-1); padding: 3px var(--s-2); border: 1px solid color-mix(in srgb, var(--ton, var(--text-muted)) 34%, transparent); border-radius: var(--r-pill); background: color-mix(in srgb, var(--ton, var(--text-muted)) 16%, var(--surface)); color: var(--ton-tekst, var(--ton, var(--text-muted))); font-size: var(--t-base); font-weight: var(--w-bold); line-height: var(--lh-tight); white-space: nowrap; }
/* W1d: w OKNIE status = kropka w kolorze tonu + słowo (wzorzec telefonu 13.09), na obu ekranach; w widokach
   na komputerze zostaje pigułka. */
.modal .status-pill:not(.solid) { gap: 6px; padding: 0; border: 0; background: transparent; color: var(--text); font-weight: var(--w-bold); }
.modal .status-pill:not(.solid)::before { content: ""; width: 8px; height: 8px; flex: none; border-radius: 50%; background: var(--ton, var(--text-muted)); }
/* Wariant pełny — do jednego, najważniejszego statusu na ekranie. */
.status-pill.solid { border-color: transparent; background: var(--ton, var(--text-muted)); color: var(--text-invert); }
/* === 5. KROPKI ETAPÓW — status bez zabierania miejsca w wąskiej kolumnie === */
/* Ton przez FALLBACK, nie przez własną deklarację `--ton` — patrz uwaga
   przy `.status-pill`. Deklaracja stojąca po klasach `.tone-*` bije je
   i wszystkie kropki wychodzą bezbarwne (to był zgłoszony błąd „kropka Plany
   bez koloru"). */
.stage-dot { display: inline-block; flex: 0 0 auto; width: var(--s-2); height: var(--s-2); border-radius: var(--r-pill); background: var(--ton, var(--text-faint)); vertical-align: middle; }
/* === 6. TOASTY — komunikat po zapisie, nad wszystkim, sam znika ============ */
/* pointer-events:none, żeby kontener nie blokował klikania pod spodem —
   pojedynczy toast przywraca sobie klikalność. */
.toast-root { --toast-w: 420px; position: fixed; top: var(--s-4); left: 50%; z-index: var(--z-toast); display: grid; gap: var(--s-2); width: min(var(--toast-w), calc(100vw / var(--skala-ui) - var(--s-7))); transform: translateX(-50%); pointer-events: none; }
.toast { --ton: var(--accent); display: flex; align-items: flex-start; gap: var(--s-2); padding: var(--s-3) var(--s-4); border: 1px solid var(--line); border-radius: var(--r); background: var(--surface); box-shadow: var(--cien-2); color: var(--text); font-weight: var(--w-medium); pointer-events: auto; animation: toast-wjazd var(--wolno) ease-out; }
.toast-tekst { flex: 1 1 auto; min-width: 0; align-self: center; overflow-wrap: anywhere; }
/* Warianty przestawiają tylko ton i tło — reszta wyglądu wspólna. ERG5-03: JS nadaje je wg rodzaju
   (błąd = danger, sukces = ok); ramka też w tonie, żeby dymek od razu mówił, czy się udało. */
.toast.ok { --ton: var(--ok); border-color: var(--ton); background: var(--ok-soft); }
.toast.warn { --ton: var(--warn); border-color: var(--ton); background: var(--warn-soft); }
.toast.danger { --ton: var(--danger); border-color: var(--ton); background: var(--danger-soft); }
/* Krzyżyk błędu — błąd nie znika sam, zamyka go człowiek. Tło przezroczyste, żeby nie był plamą na czerwieni. */
.toast-zamknij { margin: calc(var(--s-1) * -1) calc(var(--s-2) * -1) calc(var(--s-1) * -1) 0; border-color: transparent; background: transparent; color: var(--text); }
.toast-zamknij:hover { background: color-mix(in srgb, var(--ton) 14%, transparent); }
/* ERG6-01: komunikat z przyciskiem („Cofnij”): tekst z lewej, przycisk z prawej, w jednej linii. */
.toast.has-akcja { align-items: center; }
.toast .toast-akcja { flex: none; align-self: center; min-height: var(--h-control-sm); }
/* TELEFON — #61, 13.09.2026 (test ścieżek na telefonie). Reguła mobilna siedziała w 04-uklad.css i
   dokładała tylko `bottom`, a ta bazowa (ładowana PÓŹNIEJ) zostawiała `top` i `left:50%`. Kontener
   miał więc górę i dół naraz: siatka rozciągała jedyny toast na CAŁĄ wysokość ekranu — białe pole
   zasłaniało okno kalkulatora i zjadało kliknięcia, dopóki komunikat nie zniknął. Teraz: na szerokość
   ekranu, wysokość według treści.
   ERG5-03 (26.09.2026): u GÓRY ekranu, nie nad dolnym paskiem. Na dole dymek „Uzupełnij wymagane pola"
   zasłaniał akurat pole do poprawy (okno na cały ekran przewija pole w dół, pod dymek), a błąd zostaje
   teraz do zamknięcia. U góry przykrywa najwyżej nagłówek okna; pole z błędem przewija się na środek. */
:where(:root[data-ui="mobile"]) .toast-root { top: calc(var(--s-3) + env(safe-area-inset-top, 0px)); right: var(--s-4); bottom: auto; left: var(--s-4); width: auto; align-content: start; transform: none; }
/* Wjazd z góry: ruch mówi „to nowy komunikat", bez niego toast po prostu miga. */
@keyframes toast-wjazd {
  from { opacity: 0; transform: translateY(calc(var(--s-3) * -1)); }
  to { opacity: 1; transform: translateY(0); }
}
/* === 7. OKNO POTWIERDZENIA — mniejszy modal: pytanie + dwa przyciski ======= */
/* W1: bez ramki, róg arkusza 20 px — jak na telefonie (tam arkusz od dołu: tylko górne rogi).
   POPR-n45 (28.09): ta sama skórka co każde okno — szare tło okna (--okno-tlo), tytuł jak nagłówek okna (--t-lg, --w-bold),
   stopka z włosową kreską nad sobą. Było: białe tło i mniejszy tytuł — okna pytań wyglądały jak inny program. */
.app-confirm { --okno-w: 440px; width: min(var(--okno-w), 100%); border: 0; border-radius: var(--arkusz-r); background: var(--okno-tlo); box-shadow: var(--cien-3); }
.app-confirm-body { display: grid; gap: var(--s-2); padding: var(--s-5) var(--s-5) var(--s-4); }
.app-confirm-title { font-size: var(--t-lg); font-weight: var(--w-bold); line-height: var(--lh-tight); }
.app-confirm-text { color: var(--text-muted); }
.app-confirm-actions { display: flex; justify-content: flex-end; gap: var(--s-2); padding: var(--s-3) var(--s-5); border-top: 1px solid var(--kreska-wlos); }
/* === 8. PAGINACJA, ZAKŁADKI, PRZEŁĄCZNIK ================================== */
/* Stopka listy: licznik z lewej, strony z prawej (odpycha .spacer).
   JEDNA RAMA dla całej aplikacji: `.pager` (Projekty) i `.catalog-pager`
   (kartoteki) miały różną wyściółkę — 8/16 kontra 12/14 — więc stopka
   w Projektach była niższa i inaczej odsunięta od krawędzi niż w Blokach
   czy Materiałach. Wyściółka 12/14 jest ta sama co w pasku narzędzi
   (arkusz 06), dzięki czemu góra i dół karty wyglądają symetrycznie.
   Różni je już tylko rozkład treści — kartoteki centrują stronicowanie. */
/* `min-height` trzyma JEDNĄ wysokość stopki niezależnie od tego, czy są w niej
   przyciski: bez tego kartoteka bez stronicowania (np. Klienci) miała stopkę
   40.6 px, a stronicowana 51.8 px. 27 px to wysokość przycisku `mini`
   i strzałki stronicowania, 2 × 12 px to wyściółka, 1 px to górna kreska
   (pudełko liczy się z ramką, więc bez tego 1 px stopki nadal różniły się
   o grubość kreski: 51 kontra 51.8). */
.pager, .catalog-pager { display: flex; align-items: center; gap: var(--s-2); min-height: calc(27px + var(--s-3) * 2 + 1px); padding: var(--s-3) 14px; border-top: 1px solid var(--line); color: var(--text-muted); font-size: var(--t-sm); font-variant-numeric: tabular-nums; }
.pager .spacer, .catalog-pager .spacer { flex: 1; }
/* Licznik zawartości — z LEWEJ. Numer strony i strzałki — z PRAWEJ.
   Strzałki zamiast napisów „Poprzednia strona" / „Następna strona":
   kwadratowe pole 27 px, ten sam rozmiar co przyciski `mini` w stopce,
   więc stopka ma jedną wysokość niezależnie od tego, czy jest stronicowana. */
.pager-info { white-space: nowrap; }
.pager-strona { margin-right: var(--s-2); white-space: nowrap; }
.pager-nav { display: inline-flex; align-items: center; gap: var(--s-1); }
.pager-arrow { width: 27px; height: var(--h-control-sm); border: 1px solid var(--line-strong); border-radius: var(--r-sm); background: var(--surface); color: var(--text-muted); font-size: var(--t-md); line-height: 1; }
.pager-arrow:hover:not(:disabled) { border-color: var(--accent-strong); color: var(--accent-strong); }
.pager-arrow:disabled { opacity: .4; cursor: default; }
/* Zakładki: aktywna oznaczona kreską, nie tłem — nie myli się z przyciskiem. */
.tabs { display: flex; align-items: center; gap: var(--s-1); border-bottom: 1px solid var(--line); }
.tab { position: relative; display: inline-flex; align-items: center; min-height: var(--h-tab); padding: 0 var(--s-3); color: var(--text-muted); font-size: var(--t-base); font-weight: var(--w-medium); white-space: nowrap; transition: color var(--szybko) ease; }
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent-strong); }
/* JEDNA WYSOKOŚĆ WSZYSTKICH ZAKŁADEK — patrz `--h-tab` w arkuszu tokenów.
   Cztery rodzaje zakładek miały cztery wysokości (38.4 / 36 / 34.8 / 33.2),
   więc każdy pasek zakładek w aplikacji był innej grubości. Wyściółka
   pionowa schodzi do zera, bo wysokość niesie teraz token, a treść
   jest wyśrodkowana flexem. */
.dash-tab, .timeline-tab { display: inline-flex; align-items: center; justify-content: center; min-height: var(--h-tab); }
/* Kreska nachodzi na linię .tabs (bottom:-1px), więc nie robi się podwójna. */
.tab.active::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: calc(var(--s-1) / 2); border-radius: var(--r-pill); background: var(--accent); }
/* Przełącznik 2–3 opcji (np. tabela/kafle) — jeden blok, bez odstępów między opcjami. */
.segmented { display: inline-flex; overflow: hidden; border: 1px solid var(--line-strong); border-radius: var(--r-sm); background: var(--surface); }
.segmented button { min-height: var(--h-control); padding: 0 var(--s-3); border-right: 1px solid var(--line); color: var(--text-muted); font-size: var(--t-sm); font-weight: var(--w-medium); white-space: nowrap; transition: background var(--szybko) ease, color var(--szybko) ease; }
.segmented button:last-child { border-right: 0; }
.segmented button:hover { background: var(--surface-2); }
.segmented button.active { background: var(--accent); color: var(--text-invert); }
/* W1d: przełącznik w OKNIE jak na telefonie — szara szyna, wybrany = biała pigułka z cieniem (obie wersje ekranu). */
.modal .segmented { gap: 0; padding: 3px; border: 0; border-radius: var(--pole-r); background: color-mix(in srgb, var(--text) 8%, var(--okno-tlo)); }
.modal .segmented > :is(button, .button) { border: 0; border-radius: var(--r-sm); background: transparent; box-shadow: none; color: var(--text-muted); font-weight: var(--w-bold); }
.modal .segmented > :is(.active, [aria-pressed="true"], [aria-selected="true"]) { background: var(--surface); box-shadow: 0 1px 2px color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
/* === 9. PRZYCISKI Z LICZNIKIEM (pasek górny, karta projektu) ============= (z dawnego 26, K15)
   Licznik jest pozycjonowany absolutnie (`.notification-count` w §14
   niżej), więc przycisk MUSI być układem odniesienia —
   `.icon-button` sam z siebie nie ma `position`, a bez tego kropka odlatuje
   w róg strony. Rozmiar, obrys i typografia licznika zostają wspólne;
   tutaj zmienia się wyłącznie TON, żeby trzy sąsiadujące przyciski
   (zadania · przypomnienia · wiadomości) dały się rozróżnić kątem oka. */
.tasks-button, .reminder-button, .project-tasks-button { position: relative; }
/* RM5 (v15 b14): liczniki w górnym pasku są jak we wzorze — wszystkie trzy w akcencie (reguła w 04-uklad.css, pasek górny);
   ikona nie zmienia koloru przy nieprzeczytanych. Tu zostaje licznik zadań na karcie projektu. */
.project-tasks-button .notification-count { background: var(--accent); color: var(--on-accent); }
/* „Czat" na wizytówce (tablet) to przycisk z NAPISEM — licznik stoi w linii za słowem, nie na nim (w rogu zasłaniał „t"). */
/* === 10. PODGLĄD ZDJĘĆ (LIGHTBOX) ========================================== (z dawnego 27, K16)
   Podgląd renderu na pełnym ekranie. Zasłona liczona z --text, a sterowanie
   z --bg: ta para ODWRACA SIĘ RAZEM z motywem, więc kontrast jest dobry i w
   dzień, i w nocy — bez ani jednej reguły [data-theme]. Ten sam mechanizm co
   .modal-backdrop w 05-komponenty, tylko gęstszy, bo pod spodem jest zdjęcie. */
.app-lightbox { position: fixed; inset: 0; z-index: var(--z-lightbox); display: none; align-items: center; justify-content: center; gap: var(--s-4); padding: var(--s-5); background: color-mix(in srgb, var(--zaslona-kolor) 82%, transparent); }
.app-lightbox.is-open { display: flex; }
.app-lightbox figure { display: grid; justify-items: center; gap: var(--s-2); max-width: calc(100vw / var(--skala-ui) - 200px); margin: 0; }
.app-lightbox img { max-width: 100%; max-height: calc(100vh / var(--skala-ui) - 130px); border-radius: var(--r); background: var(--surface); box-shadow: var(--cien-3); }
.app-lightbox figcaption { color: var(--bg); font-size: var(--t-sm); }
.app-lightbox-nav, .app-lightbox-close { display: inline-grid; place-items: center; flex: none; width: var(--s-7); height: var(--s-7); border: 1px solid color-mix(in srgb, var(--bg) 40%, transparent); border-radius: var(--r-pill); background: color-mix(in srgb, var(--bg) 14%, transparent); color: var(--bg); cursor: pointer; }
.app-lightbox-nav:hover, .app-lightbox-close:hover { background: color-mix(in srgb, var(--bg) 28%, transparent); }
.app-lightbox-close { position: absolute; top: var(--s-4); right: var(--s-4); }
/* JS renderuje przyciski „poprzednie/następne" PUSTE (bez <span class="icon">),
   więc strzałkę musi narysować ::before. To ta sama ścieżka Lucide co
   .icon-chevron-left w 07-ikony — kolor bierze się z currentColor, bo maska
   maluje tłem, a fill w data URI jest dla maski bez znaczenia. */
.app-lightbox-nav::before { content: ""; width: var(--s-4); height: var(--s-4); background-color: currentColor; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m15 18-6-6 6-6'/%3E%3C/svg%3E") center / contain no-repeat; }
.app-lightbox-next::before { transform: rotate(180deg); }
/* NAPRAWA 11.08.2026 — maska bazowa to chevron-LEFT (27-wspolne:127), wiec
   obrot o 90deg zamienial strzalke w lewo na strzalke W GORE. Zaden obrot. */
.app-lightbox-prev::before { transform: none; }
:root[data-ui="mobile"] .app-lightbox-prev { left: var(--s-2); }
/* === 11. POPUP INFORMACYJNY (appInfo) ===================================== (z dawnego 27, K16)
   Szerszy od potwierdzenia, bo mieści instrukcję, a nie pytanie tak/nie.
   Treść wyrównana DO LEWEJ — akapitów nie czyta się wyśrodkowanych. */
.app-confirm.app-info { --okno-w: 620px; }
.app-info-content { color: var(--text-muted); line-height: var(--lh-base); text-align: left; }
.app-info-content p { margin: var(--s-2) 0; }
.app-info-content strong { color: var(--text); }
.app-info-content ol, .app-info-content ul { display: grid; gap: var(--s-2); margin: var(--s-2) 0; padding-left: var(--s-5); }
.app-confirm-input { width: 100%; }
/* --- POMOC „Co może która rola?" (kartoteka Użytkownicy) -------------------
   Dziesięć ról nie mieści się w domyślnym oknie 620 px bez przewijania, więc ta
   odmiana bierze własną szerokość i własny scroll. Przewija się WYŁĄCZNIE treść —
   nagłówek i „Rozumiem" zostają na miejscu, żeby przy długiej liście dało się
   zamknąć okno bez zjeżdżania na sam dół. */
/* ⛔ CALY BLOK `.roles-help` USUNIETY 17.08.2026 (17 regul).
   Stylowal okno „Co moze ktora rola?”, ktore zostalo skasowane razem z trzema
   funkcjami budujacymi jego tresc. Wlasciciel: „przycisk podpowiedzi nie jest
   globalny i nie przenosi do pomocy, tylko ma jakies swoje opisy” — program mial
   DWA systemy pomocy. Przycisk „?” w kartotece Uzytkownicy prowadzi teraz do
   `/pomoc.html#s-uprawnienia`, tam gdzie globalna „Pomoc”. Nie przywracac. */
/* === 12. FORMULARZE W OKNACH ============================================== (z dawnego 27, K16) */
.modal-form, .premium-modal-form { display: grid; gap: var(--s-3); padding: var(--s-3); }
.modal-title-stack { display: grid; gap: 1px; min-width: 0; }
.modal-subtitle { display: inline-flex; align-items: center; gap: var(--s-1); color: var(--text-muted); font-size: var(--t-xs); }
.modal-back-button { margin-right: var(--s-2); }
.user-form-grid, .phone-grid, .date-pair, .price-line-grid, .version-editor-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2); }
/* E3 (27.09.2026): okno „Osoba” — zdjęcie w wąskiej kolumnie (do 180 px) zamiast połowy okna; formularz dostaje resztę.
   Dawniej dwie równe kolumny: po lewej duże puste pole z kółkiem, po prawej ściśnięte pola. Telefon: jedna kolumna (niżej). */
.user-form-grid { grid-template-columns: minmax(140px, 180px) minmax(0, 1fr); gap: var(--s-4); align-items: start; }
/* Telefon w wierszu kontaktu ma własną regułę, a nie równy podział jak wyżej:
   kierunkowy niesie flagę, nazwę kraju i strzałkę, numer tylko cyfry. */
.contact-phone-grid { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); gap: var(--s-2); }
/* Ptaszek z etykietą — cały wiersz klikalny, ptaszek nigdy się nie kurczy. */
.checkbox-line, .radio-line { display: inline-flex; align-items: center; gap: var(--s-2); cursor: pointer; }
.checkbox-line.compact { min-height: var(--h-control-sm); font-size: var(--t-sm); }
/* Pole kwoty (.currency-field, .has-currency-select) NIE jest tutaj: pełną
   definicję ma 20-kalkulator-projektu.css. Powtórka nadpisałaby tamtą,
   bo ten arkusz ładuje się później — zostawiamy jednego właściciela. */
/* === 13. LISTY, GĘSTE TABELE, STREFY ZRZUTU =============================== (z dawnego 27, K16) */
.compact-list { display: grid; gap: var(--s-2); }
/* Wiersz „opis … wartość" — ta sama forma w Ustawieniach, ofertach i modalach. */
.list-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s-3); padding: var(--s-3); border: 1px solid var(--line); border-radius: var(--r); background: var(--surface); }
.list-row strong { display: block; }
/* Tabela z dużą liczbą wąskich kolumn: stały układ + minimalna szerokość,
   żeby zamiast ściskania kolumn pojawił się poziomy pasek przewijania. */
.compact-data-table { table-layout: fixed; min-width: 980px; }
.compact-data-table th, .compact-data-table td { height: var(--h-ikona); padding: var(--s-1) var(--s-2); font-size: var(--t-xs); line-height: var(--lh-tight); vertical-align: middle; }
.compact-data-table strong, .compact-data-table small { display: block; overflow: hidden; text-overflow: ellipsis; }
.compact-data-table small { color: var(--text-muted); }
.compact-empty { padding: var(--s-3); border: 1px dashed var(--line); border-radius: var(--r-sm); color: var(--text-muted); }
/* Ramka przerywana = „upuść tu plik". Ciągła znaczyłaby gotowe pole. */
.drop-zone, .compact-import-drop { display: grid; gap: var(--s-3); padding: var(--s-4); border: 1px dashed var(--line-strong); border-radius: var(--r); background: var(--surface-2); }
/* ERG6-04: przycisk „Wgraj zestawienie konstrukcji” zawsze w całości (max-content), ściska się tylko nazwa pliku —
   przy pasku przewijania okna napis ucinał się z obu stron. */
.premium-drop { grid-template-columns: max-content minmax(0, 1fr); align-items: center; }
/* Wyciete 11.08.2026 — martwe reguly, zero wystapien w public/js. Szczegoly: AUDYT-CSS-2026-08-11.md B9. */
/* === 14. LICZNIKI NIEPRZECZYTANYCH ======================================== (z dawnego 27, K16)
   Kropka z liczbą na kopercie/dzwonku. Pierścień w kolorze powierzchni odcina
   ją od ikony pod spodem, więc czyta się nawet na ciemnym pasku. */
.notification-button { position: relative; }
.notification-count { position: absolute; top: calc(var(--s-1) * -1); right: calc(var(--s-1) * -1); display: inline-grid; place-items: center; min-width: 17px; height: 17px; border-radius: var(--r-pill); background: var(--danger); color: var(--text-invert); font-size: var(--t-xs); font-weight: var(--w-bold); box-shadow: 0 0 0 2px var(--surface); }
/* Podgląd zdjęć na wąskim ekranie. */
@media (max-width: 980px) {
  .app-lightbox figure { max-width: calc(100vw / var(--skala-ui) - var(--s-7)); }
}
:where(:root[data-ui="mobile"]) .user-form-grid, :where(:root[data-ui="mobile"]) .phone-grid, :where(:root[data-ui="mobile"]) .contact-phone-grid, :where(:root[data-ui="mobile"]) .date-pair, :where(:root[data-ui="mobile"]) .price-line-grid, :where(:root[data-ui="mobile"]) .version-editor-grid, :where(:root[data-ui="mobile"]) .calc-context-row { grid-template-columns: minmax(0, 1fr); }
:where(:root[data-ui="mobile"]) .app-lightbox { padding: var(--s-3); gap: var(--s-2); }
/* --- Okno pod oknem (z dawnego 28, K17) --- */
/* Okno pod oknem — tło spodniego znika, żeby nie było dwóch przyciemnień. */
.modal-backdrop.is-suspended { display: none; }

/* WYMAGANIA HASŁA (24.10.2026): lista pod polem nowego hasła — „✗” na czerwono, „✓” na zielono, na żywo przy pisaniu.
   Marker nie jest jedynym nośnikiem znaczenia: ptaszek/krzyżyk i kolor idą razem z tekstem warunku. */
.haslo-wymagania { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3); margin: var(--s-2) 0 0; padding: 0; list-style: none; color: var(--text-muted); font-size: var(--t-sm); line-height: var(--lh-base); }
.haslo-wymagania li { display: inline-flex; align-items: baseline; gap: var(--s-1); }
.haslo-wymog-znak { color: var(--danger); font-weight: var(--w-bold); }
.haslo-wymagania li.is-ok { color: var(--ok-strong); }
.haslo-wymagania li.is-ok .haslo-wymog-znak { color: var(--ok); }
