/* Powloka pulpitu v2. Piec motywow przelaczanych atrybutem data-theme na <html>.
   To NIE jest piec palet tego samego - roznia sie typografia i siatka.
   Liczba pochodzi z JEDNEGO miejsca - `USTAWIENIA_SCHEMAT["motyw"]` w `vd-v2d`
   (10.1) - i zapadka w `tests/test-poswiata.py` pilnuje, zeby to zdanie i tamta
   lista mowily to samo: motyw dopisany do schematu bez poprawki tutaj zostawia
   w pierwszym wierszu arkusza liczbe, ktora czyta sie przed wszystkim innym. */

/* --- Motyw 1: "siatka znakow" - pulpit jako przyrzad tekstowy --------------- */
:root, [data-theme="grid"] {
  /* Komorka znaku - jednostka ukladu. Te dwie liczby sa ZAPASEM na czas przed
     pierwszym pomiarem: `shell/siatka.js` mierzy prawdziwa komorke pisma
     `--font-mono` w rozmiarze `--mono-size` i nadpisuje je na <html>. Wpisana
     tu na sztywno wartosc klamalaby wszedzie tam, gdzie zabraklo "IBM Plex
     Mono" - a wtedy linie w tle rozjezdzaja sie z tekstem. */
  --cell-w: 8px;  --cell-h: 15px;
  --mono-size: 13px;                       /* rozmiar pisma stalej szerokosci */
  --bg:        #12141a;
  --surface:   #1a1d26;
  --fg:        #d7dae0;
  --fg-dim:    #7d848f;
  --line:      #2b2f3a;
  --accent:    #7fd6c1;
  /* Kolor tego, CO NIE DOSZLO - i tylko tego. Wczesniej te role gral
     `--accent`, wiec "przekroczony prog", "blad logowania" i "okno ma ognisko"
     swiecily dokladnie tym samym kolorem: pulpit nie mial jak powiedziec, ze
     cos sie nie udalo. Token stoi w BAZIE, nie w motywie - kazdy motyw musi
     umiec powiedziec "nie doszlo", wiec podanie WLASNEJ wartosci jest jego
     prawem, a milczenie o niej nie jest. Jedyny motyw, ktory mial tu literal
     ("przyrzad", #b4231a), oddal go do swojego bloku bez zmiany odcienia. */
  --blad:      #e8695a;
  /* Powierzchnia LITA - dla okien, ktore nie sa szyba (etap 7.3). W motywach
     nieprzezroczystych to po prostu `--surface`; nadpisuje ja tylko "warstwy",
     bo tylko tam `--surface` jest szklem, a szklo bez rozmycia pod spodem
     przestaje byc powierzchnia i staje sie folia.

     TO `var()` PODSTAWIA SIE NA KORZENIU, a nie u czytelnika (11.15). Wartosc
     niestandardowa liczy sie na elemencie, KTORY DOSTAL DEKLARACJE - czyli
     tutaj, na <html>; w dol drzewa schodzi juz KOLOR, nie odwolanie. Skutek
     jest taki, ze redefinicja `--surface` U POTOMKA przesuwa wszystkich
     konsumentow `var(--surface)` (spis nizej) i ANI JEDNEGO konsumenta
     `var(--surface-lita)`. Zmierzone `tests/wd-lita.py` sygnalem wpisanym na
     okno: pasek tytulu idzie za sygnalem, cialo okna zostaje - w 4 z 5
     motywow, bo w "warstwach" okno jest szyba i samo czyta `--surface`.
     Motyw z WLASNYM literalem ("warstwy", "poswiata") nie drgnie nawet od
     redefinicji na korzeniu: nie ma tam juz czego podstawiac. */
  --surface-lita: var(--surface);
  /* Drugie ogniwo lancucha to kroj NAPRAWDE obecny na maszynie (etap 10.12).
     `fc-match "Space Grotesk"` oddaje NotoSans-Regular.ttf, wiec sama nazwa
     wymarzona znaczyla "cokolwiek, co system uzna za sans" - a to co miesiac
     moze byc czym innym. Nazwa wymarzona ZOSTAJE na czele: gdy kroj kiedys
     przyjdzie z apt, motyw ma go wziac bez zmiany arkusza. Zero @font-face
     i zero url() - pulpit nie sciaga pisma, wiec lancuch jest jedynym
     miejscem, gdzie ta decyzja moze byc zapisana. */
  --font-ui:   "Space Grotesk", "Inter", ui-sans-serif, system-ui, sans-serif;
  /* Kolejnosc TEGO lancucha zostaje nietknieta, choc `ui-monospace` stoi tu
     przed obecnym krojem. To jedyny motyw, w ktorym okna przyciagaja sie do
     komorki, wiec przestawienie ogniw jest jedyna zmiana krojow zdolna ruszyc
     UKLAD, a nie tylko ksztalt liter. Obecny kroj juz w tym lancuchu jest. */
  --font-mono: "IBM Plex Mono", ui-monospace, "DejaVu Sans Mono", monospace;
  --radius: 3px;
  --bar-h: 28px;
}

/* --- Motyw 2: "przyrzad" - precyzja, wlosowe linie, jeden sygnal ----------- */
[data-theme="instrument"] {
  --cell-w: 8px;  --cell-h: 15px;
  --mono-size: 13px;
  --bg:        #eceae5;
  --surface:   #f7f6f3;
  --fg:        #23252a;
  --fg-dim:    #6f7278;
  --line:      #c9c6bf;
  --accent:    #c8721a;
  --blad:      #b4231a;
  /* Jak w "siatce znakow": IBM Plex nie istnieje na tej maszynie, wiec za
     nazwa wymarzona stoi kroj obecny. W lancuchu stalej szerokosci obecne
     ogniwa ida PRZED `ui-monospace`, bo `ui-monospace` to nie kroj, tylko
     "co tam przegladarka ma" - postawione wczesniej zjadaloby wybor.
     Advance Liberation Mono i DejaVu Sans Mono to 0,6 em, tyle samo co
     dzisiejszy domyslny monospace, wiec komorka `shell/siatka.js` zostaje
     8x15 px (pilnuje tego test-siatka.py). */
  --font-ui:   "IBM Plex Sans", "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", "Liberation Mono", "DejaVu Sans Mono",
               ui-monospace, monospace;
  --radius: 0px;
  --bar-h: 26px;
}

/* --- Motyw 3: "warstwy" - glebia, ktorej v1 nigdy nie miala ---------------- */
[data-theme="layers"] {
  --cell-w: 8px;  --cell-h: 15px;
  --mono-size: 13px;
  --bg:        #1b1030;
  --surface:   rgb(255 255 255 / .07);
  /* Kolor szyby ZLOZONEJ z tlem pulpitu - tyle, ile widac przez rozmyte
     `--bg`. Okno spoza trojki szyb ma wygladac jak to samo okno, tylko bez
     glebi, a nie jak okno z innego motywu. */
  --surface-lita: #2a2145;
  --fg:        #f0edf7;
  --fg-dim:    #a79fbe;
  --line:      rgb(255 255 255 / .14);
  --accent:    #b892ff;
  --blad:      #f2705f;
  --font-ui:   ui-sans-serif, system-ui, sans-serif;
  --font-mono: ui-monospace, monospace;
  --radius: 12px;
  --bar-h: 30px;
}

/* --- Motyw 4: "nosna" - anuncjator, w ktorym stan nominalny jest bezbarwny --
   Ciemna plyta aparaturowa, a nie czern kosmosu, i dokladnie dwa znaczenia
   koloru: bursztyn "patrz tutaj" (--accent) oraz czerwien "nie doszlo"
   (--blad). Pisma sa tu NAPRAWDE obecne na maszynie (fc-match: Inter ->
   Inter-Regular.otf, Liberation Mono -> LiberationMono-Regular.ttf) - ten
   motyw byl pierwszym, ktory tego pilnowal. "Siatka znakow" i "przyrzad"
   nadrobily to w 10.12 (obecny kroj dopisany za nazwa wymarzona), wiec dzis
   zaden motyw nie renderuje sie czyms innym, niz deklaruje. Advance
   Liberation Mono to 0,6 em, tyle samo co DejaVu, wiec `shell/siatka.js`
   zmierzy te sama komorke 8x15 px i okres grzebienia nosnej "jedna kratka =
   jedna milisekunda" nie klamie.

   Czego ten blok swiadomie NIE deklaruje:
     --surface-lita - dziedziczone `var(--surface)` z bazy wystarcza, bo
       #262c30 jest lite. Nadpisuja je tylko "warstwy", gdzie powierzchnia
       jest szklem, a szklo bez rozmycia pod spodem przestaje nia byc.
     --bar-h - zostaje 28 px z bazy. Gorny pasek i tak bierze wysokosc
       z `--pasek-gorny-h`, wiec wlasna liczba bylaby kopia z "przyrzadu",
       ktorej nikt nie czyta.
     --ruch* - tempo powloki jest wspolne dla WSZYSTKICH motywow (blok "Ruch"
       nizej); motyw mowi, jak to wyglada, a nie jak szybko sie porusza.

   `--nosna-czynna` to jedyne miejsce w projekcie, gdzie zapisane jest, ze to
   TEN motyw rysuje nosna: `shell/boot.js` czyta ja ze stylu wyliczonego na
   <html>, zamiast trzymac druga liste motywow obok tej ze schematu serwera. */
[data-theme="carrier"] {
  --cell-w: 8px;  --cell-h: 15px;
  --mono-size: 13px;
  --bg:        #1c2124;
  --surface:   #262c30;
  --fg:        #e3e6e4;
  --fg-dim:    #8b9396;
  --line:      #394245;
  --accent:    #e0b552;
  --blad:      #e87458;
  --font-ui:   "Inter", "Liberation Sans", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "Liberation Mono", "DejaVu Sans Mono", ui-monospace, monospace;
  --radius: 0px;
  --nosna-czynna: 1;
}

/* --- Motyw 5: "poswiata" - glebia rysowana halo, a nie cieniem (etap 11) ----
   Brief przyszedl zrzutem fikcyjnego "NEXUS OS": ciemny granat, szklane
   panele, lazurowa poswiata, wskazania w trzech barwach. Motyw odpowiada na
   inne pytanie niz "warstwy": tamten mowi, JAK WYSOKO okno wisi (czarny cien
   przesuniety w dol), ten mowi, KTORE jest najblizej (pierscien i centryczne
   halo o malejacej alfie, przesuniecie ZERO na obu osiach). Ta sama dana
   wejsciowa - `data-glebia` z `shell/menedzer.js` - rozlaczna wlasciwosc
   wyjsciowa.

   BLOK STOI TUTAJ, A NIE NA KONCU ARKUSZA, I TO JEST WYMOG, NIE PORZADEK.
   `:root` i `[data-theme="x"]` maja ROWNA specyficznosc (0,1,0), wiec o
   zwyciestwie decyduje KOLEJNOSC W PLIKU. Blok motywu dopisany za blokiem
   "Ruch" (nizej) pobilby jedyny w tym arkuszu wyjatek o mniej ruchu samym
   polozeniem - i to bez ani jednej deklaracji `--ruch*` w sobie: wystarczy
   `--ruch-krzywa` albo pozniejsze `transition`. Objaw - zlamana prosba
   o mniej ruchu - jest NIEWIDOCZNY na zrzucie, wiec pilnuja tego zapadki
   w `tests/test-ruch.py`, a nie dyscyplina.

   Czego ten blok NIE MA PRAWA deklarowac - tokeny WSPOLNE, wszystkie
   ustawiane w `:root` PONIZEJ tego miejsca: `--ruch-szybko`, `--ruch`,
   `--ruch-wolno`, `--ruch-krzywa` (blok "Ruch") oraz `--pasek-gorny-h`.
   Motyw mowi, JAK pulpit wyglada, a nie jak szybko sie porusza i gdzie
   zaczyna sie warstwa okien. Nie deklaruje tez `--nosna-czynna`: to token
   PRYWATNY "nosnej" i jedyne miejsce w projekcie, w ktorym zapisano, ktory
   motyw rysuje pasmo. Lista tych nazw nie jest w tescie przepisana - jest
   LICZONA z polozenia (`tokeny_wspolne` w `tests/test-ruch.py`), wiec kazdy
   przyszly token dopisany do `:root` nizej obejmie ta sama zapadka.

   Czego tu nie ma, choc do motywu nalezy: tapeta (11.10). Nie jest tokenem
     korzenia - to dwie reguly na `#desk`, wiec stoi przy `#desk`, zaraz za
     jego definicja. Cztery stopnie glebi (11.5) tez nie tutaj: to
     reguly na `.okno`, a nie tokeny korzenia - stoja przy cieniach "warstw",
     zaraz za nimi. Tak samo `--szklo` (11.6) JEST tutaj, ale cztery reguly,
     ktore je czytaja, stoja przy swoich elementach - kazda tuz za rownolegla
     regula "warstw". `--mtr-barwa` (11.8) NIE JEST tokenem korzenia i nigdy
     nim nie bedzie: trzy barwy tozsamosci wskazan siedza na `.mtr-poz[data-
     mtr]` przy skali, bo token w `:root` mialby jedna wartosc na motyw,
     a tych wartosci sa trzy - po jednej na wskazanie.

   Ani jednego `transition` i ani jednej `animation`: prosba o mniej ruchu
   jest w tym motywie spelniona trywialnie, bo nie ma czego wylaczac. */
[data-theme="glow"] {
  --cell-w: 8px;  --cell-h: 15px;
  --mono-size: 13px;
  /* Dolna granica rampy #050810 -> #0b1220 ze zrzutu. Widoczne tam, gdzie
     nie doszla tapeta (warstwa okien, ekran logowania). */
  --bg:        #070b14;
  /* --- Powierzchnie POLICZONE, nie dobrane (zadanie 11.4) ------------------
     Referencja opisuje panel dwoma slowami: przezroczysty i rozmyty,
     rgba(18,28,48,0.55). Nie kopiujemy TECHNIKI, kopiujemy WYNIK - skladamy
     te wartosc z podlozem na papierze i wpisujemy literal:

       --surface = rgb(28 44 74 / .55) nad --bg #070b14
         R  .55*28 + .45*7  = 18,55 -> 19 = 0x13
         G  .55*44 + .45*11 = 29,15 -> 29 = 0x1d
         B  .55*74 + .45*20 = 49,70 -> 50 = 0x32      razem #131d32
       --surface-lita = #131d32 - ta sama liczba, tylko LITA. Cialo okna ma
         wiec kolor panelu z referencji CO DO BAJTU, nie majac ani jednej
         warstwy kompozytora.
       pasek tytulu = --surface zlozone nad #131d32 (`.okno-pasek` ma
         background: var(--surface) i lezy NA `.okno`)
         R  .55*28 + .45*19 = 23,95 -> 24 = 0x18
         G  .55*44 + .45*29 = 37,25 -> 37 = 0x25
         B  .55*74 + .45*50 = 63,20 -> 63 = 0x3f      razem #18253f
         Roznica wobec ciala okna: 5 / 8 / 13 na kanal. Wstega JASNIEJSZA,
         widoczna - i to jest liczba, a nie ostroznosc.

     DLACZEGO --surface-lita STOI TU WLASNYM LITERALEM. Baza ma
     `--surface-lita: var(--surface)` (linia 46), wiec milczenie tutaj
     zostawiloby cialo okna POLPRZEZROCZYSTE. Dzis nad plaskim --bg wyszedlby
     z tego ten sam #131d32 i roznicy nie widac wcale - ale 11.10 kladzie na
     `#desk` tapete, a wtedy KAZDE okno pokazywaloby przez siebie obrazek.
     To jest dokladnie to, czego zabrania R3 (rozmytych/przezroczystych
     warstw ma byc DWIE i staly, a nie po jednej na okno), a pulapka jest
     CICHA: objawi sie o szesc zadan pozniej i w innym pliku.

     SPIS KONSUMENTOW `var(--surface)` - pelny, z grepa, a nie z pamieci
     (pierwotna propozycja podala 8 z 13 pozycji jako liste wyczerpujaca):
        642  #menubar
        715  [data-theme="carrier"] #menubar        (martwe w glow)
        755  .mb-menu-lista
       1006  .dok-skrot, .dok-okno
       1222  #komunikat
       1294  .lw-wiersz--kursor
       1318  .apka-o button
       1355  .pliki-guzik, .podglad-guzik
       1436  .brama-karta
       1642  [data-theme="layers"] .okno--szklo     (martwe w glow)
       1916  .okno-pasek
       2086  .edytor-sciezka
       2097  .edytor-guzik
     `var(--surface-lita)` czytaja dwa: 1625 `.okno` i 2147 `.aktu`.
     Z trzynastu pozycji dwie sa w tym motywie martwe, wiec powierzchnia
     polprzezroczysta trafia na jedenascie elementow CHROMU i na ZERO cial
     okien - a to jest cala teza filaru 2. */
  --surface:      rgb(28 44 74 / .55);
  --surface-lita: #131d32;
  /* --- Szklo chromu (zadanie 11.6) ---------------------------------------
     JEDNA liczba na CZTERY powierzchnie, wiec token, a nie cztery literaly.
     "Warstwy" trzymaja dwie (14 px na chromie, 18 px na szybie okna), bo tam
     rozmycie NIESIE RANGE w stosie. Tutaj rangi nie niesie - niesie ja halo
     z 11.5 - wiec druga wartosc bylaby roznica, ktora nic nie znaczy.

     SIGMA, NIE PROMIEN, i to jest cala arytmetyka tej liczby. `blur(<len>)`
     bierze ODCHYLENIE STANDARDOWE gaussa, nie promien jadra, wiec 8 px to
     rozmycie siegajace ~24 px (trzy sigmy) w kazda strone. Osemka jest
     POLICZONA z komorki znaku (`--cell-w: 8px`): gauss o sigmie s tlumi wzor
     o okresie T z czynnikiem exp(-2*pi^2*s^2/T^2), czyli
       laseczki pisma   T =   8 px -> exp(-19,7)  = 0,000000003
       wiersze tekstu   T =  15 px -> exp(-5,61)  = 0,004
       ksztalty okien   T = 100 px -> exp(-0,126) = 0,88
     Tresc terminala pod paskiem i pod dokiem znika CALKOWICIE, a ukladu
     wiekszych plam - czyli tego, ze pod spodem cos lezy - szklo nie zabiera.
     O to w szkle chodzi i dlatego liczba wychodzi z pisma, a nie z oka.

     Mniejsza sigma niz u sasiada jest przy tym TANSZA, nie skromniejsza:
     koszt splotu rosnie z promieniem jadra, a liczba szklanych powierzchni
     (dwie na stojacym pulpicie - patrz dowod (d) w `test-poswiata.py`)
     zostaje ta sama niezaleznie od liczby okien. */
  --szklo: blur(8px);
  --fg:        #e6edf7;
  --fg-dim:    #8fa3bd;
  /* Wlosowa linia. Alfa jest WYNIKIEM pomiaru, nie wyborem odcienia:
     `tests/test-poswiata.py` czyta ja z PIKSELI zrzutu i zada, zeby obwodka
     `.mtr-skala` odcinala sie od tla przy >= 1,4:1 takze wtedy, gdy caly
     wskaznik lezy pod `opacity: .5` (`.mtr-poz--brak`, czyli "nie wiem").
     Ten drugi warunek jest ostrzejszy o polowe i to on ustawil liczbe.
     Zmierzone na zrzucie: .28 (z pierwotnej propozycji) -> 1,20:1,
     .40 -> 1,32, .46 -> 1,38, .48 -> 1,40 dokladnie na progu, .50 -> 1,44,
     .52 -> 1,46. Wpisane jest .52, zeby prog mial zapas, a nie byl trafiony.
     Zmiana tej wartosci w dol zapala tamten przypadek. */
  --line:      rgb(90 140 200 / .52);
  /* Lazur referencji. ZAPISANY JAKO LITERAL HEX i tak ma zostac:
     `apps/terminal.js` oddaje ten token xtermowi jako kolor karetki, a
     `css.toColor` biblioteki ma szybka sciezke tylko dla `#hex` i
     przecinkowego `rgb(a,b,c)` - awaria po `rgb(var(--x))` jest CICHA
     (wyjatek polkniety, podstawiony kolor domyslny). */
  --accent:    #4a9eff;
  /* Jedyny cieply kolor w chlodnej palecie, wiec "nie doszlo" nie da sie
     pomylic ani z lazurem, ani z zadnym akcentem wtornym. */
  --blad:      #f8705f;
  /* Oba kroje sa NAPRAWDE na maszynie (fc-match: Inter -> Inter-Regular.otf,
     Liberation Mono -> LiberationMono-Regular.ttf), tak jak w "nosnej".
     Advance Liberation Mono to 0,6 em, wiec `shell/siatka.js` zmierzy te sama
     komorke 8x15 px. Zero @font-face i zero url() - COEP require-corp. */
  --font-ui:   "Inter", "Liberation Sans", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "Liberation Mono", "DejaVu Sans Mono", ui-monospace, monospace;
  /* Dolna granica referencji (14-16). UCZCIWIE: to NIE jest roznica wobec
     "warstw" (12px) i nie wolno jej tak przedstawiac. */
  --radius: 14px;
  --bar-h: 30px;
}

/* --- Paleta ANSI (etap 10.16) ----------------------------------------------
   Szesnascie kolorow, ktorymi maluja SIE PROGRAMY w terminalu: znak zachety,
   `ls`, `git`, `grep --color`, `htop`. Do 10.16 nie nalezaly do nikogo -
   `apps/terminal.js` oddawal xtermowi tylko `--bg`, `--fg`, `--accent`
   i `--line`, wiec cala szesnastka byla DOMYSLNA PALETA BIBLIOTEKI, ta sama
   we wszystkich motywach. Skutek dalo sie zmierzyc na zrzucie „nosnej":
   najjasniejszym punktem calego pulpitu byl jaskrawozielony znak zachety -
   kolor, ktory w jezyku tego motywu nie znaczy NIC, a krzyczal najglosniej.

   To jest dlug BAZY, nie jednego motywu - jak `--blad` w 10.2. Kazdy motyw
   musi umiec powiedziec „to wypisal program", wiec podanie WLASNEJ palety
   jest jego prawem, a milczenie o niej nie jest. Motyw, ktory jej nie poda,
   ODZIEDZICZY palete „siatki znakow" - i to jest usterka NIEWIDOCZNA dla
   `getComputedStyle`, bo odziedziczony kolor wyglada dokladnie tak samo jak
   wlasny. Zmierzone: po wycieciu tego bloku z „nosnej" wszystkie pomiary
   kontrastu i nasycenia zostaja zielone, bo oba tla sa ciemne i bliskie.
   Dlatego `tests/test-ansi.py` ma zapadke, ktora czyta ZRODLO tego arkusza
   i zada od kazdego motywu ZE SCHEMATU SERWERA szesnastu wlasnych deklaracji
   - takze od motywu jeszcze nienapisanego.

   INDEKS JEST KONTRAKTEM, NIE PORZADKIEM MOTYWU. `--ansi-1` to czerwien
   PROGRAMU, a nie „pierwszy kolor palety": `git diff` czerwienia mowi
   „usuniete", a nie „awaria", wiec przemapowanie musi zachowac znaczenia.
   Zadaniem jest STROJENIE - tak jak robia Solarized czy Gruvbox - a nie
   odebranie programom koloru.

   Trzy reguly, wedle ktorych te 64 wartosci zostaly POLICZONE, a nie dobrane:
     1. Kontrast wobec `--bg` nigdy ponizej 3:1. Stad neutralne 0 i 7 nie sa
        czernia ani biela, tylko szarosciami ODSUNIETYMI od tla: program
        piszacy „czarnym" po ciemnym tle ma zostac czytelny. W „przyrzadzie"
        (tlo jasne) dziala to w druga strone i cala szesnastka jest ciemna.
     2. Nasycenie (HSV) nigdy powyzej najbardziej nasyconego tokenu motywu.
        To jest ta zapadka, ktora wywala domyslna palete xterma: `#cc0000`
        ma nasycenie 1,00, a sufity motywow to 0,61-0,87.
     3. Wariant jasny (8-15) ma WIEKSZY kontrast niz podstawowy (0-7), a cala
        szesnastka zostaje PONIZEJ kontrastu `--fg` - wyjscie programu nie ma
        prawa byc wyrazniejsze niz tekst, ktory pulpit pisze od siebie.
        Ze `--accent` paleta nie sciga sie kontrastem, tylko nasyceniem:
        w motywie o jasnym tle („przyrzad") bursztyn ma wobec tla ledwie
        2,97:1, a mimo to jest najglosniejszy na ekranie, bo najbardziej
        kolorowy. Ten wyscig rozstrzyga regula 2, nie ta.
   Kolejnosc odcieni: 1/9 czerwony, 2/10 zielony, 3/11 zolty, 4/12 niebieski,
   5/13 karmazynowy, 6/14 blekitny; najblizsza para odcieni jest oddalona
   o 64 stopnie, zeby przygaszenie nie skleilo zieleni z blekitem.
   --------------------------------------------------------------------------- */
:root, [data-theme="grid"] {
  /* „siatka znakow" - sufit nasycenia 0.61 (najbardziej nasycony token motywu). */
  --ansi-0:  #676a73;   /* czarny          kontrast 3.4:1, nasycenie 0.10 */
  --ansi-1:  #a67067;   /* czerwony        kontrast 4.5:1, nasycenie 0.38 */
  --ansi-2:  #558964;   /* zielony         kontrast 4.5:1, nasycenie 0.38 */
  --ansi-3:  #8b7d56;   /* zolty           kontrast 4.5:1, nasycenie 0.38 */
  --ansi-4:  #677fa7;   /* niebieski       kontrast 4.5:1, nasycenie 0.38 */
  --ansi-5:  #a269aa;   /* karmazyn        kontrast 4.5:1, nasycenie 0.38 */
  --ansi-6:  #548687;   /* blekit          kontrast 4.5:1, nasycenie 0.38 */
  --ansi-7:  #9ca0ad;   /* bialy           kontrast 7.1:1, nasycenie 0.10 */
  --ansi-8:  #828590;   /* jasny czarny    kontrast 5.0:1, nasycenie 0.10 */
  --ansi-9:  #d98375;   /* jasny czerwony  kontrast 6.5:1, nasycenie 0.46 */
  --ansi-10: #5caa73;   /* jasny zielony   kontrast 6.5:1, nasycenie 0.46 */
  --ansi-11: #ad985d;   /* jasny zolty     kontrast 6.5:1, nasycenie 0.46 */
  --ansi-12: #769bda;   /* jasny niebieski kontrast 6.5:1, nasycenie 0.46 */
  --ansi-13: #d078de;   /* jasny karmazyn  kontrast 6.5:1, nasycenie 0.46 */
  --ansi-14: #5ba6a8;   /* jasny blekit    kontrast 6.5:1, nasycenie 0.46 */
  --ansi-15: #babfcf;   /* jasny bialy     kontrast 10.0:1, nasycenie 0.10 */
}

[data-theme="instrument"] {
  /* „przyrzad" - sufit nasycenia 0.87 (najbardziej nasycony token motywu). */
  --ansi-0:  #817d77;   /* czarny          kontrast 3.4:1, nasycenie 0.08 */
  --ansi-1:  #995854;   /* czerwony        kontrast 4.5:1, nasycenie 0.45 */
  --ansi-2:  #417549;   /* zielony         kontrast 4.5:1, nasycenie 0.44 */
  --ansi-3:  #7e6646;   /* zolty           kontrast 4.5:1, nasycenie 0.44 */
  --ansi-4:  #536a97;   /* niebieski       kontrast 4.5:1, nasycenie 0.45 */
  --ansi-5:  #935295;   /* karmazyn        kontrast 4.5:1, nasycenie 0.45 */
  --ansi-6:  #45707e;   /* blekit          kontrast 4.5:1, nasycenie 0.45 */
  --ansi-7:  #4f4d49;   /* bialy           kontrast 7.0:1, nasycenie 0.08 */
  --ansi-8:  #65625d;   /* jasny czarny    kontrast 5.1:1, nasycenie 0.08 */
  --ansi-9:  #843c37;   /* jasny czerwony  kontrast 6.5:1, nasycenie 0.58 */
  --ansi-10: #275c30;   /* jasny zielony   kontrast 6.6:1, nasycenie 0.58 */
  --ansi-11: #684d2b;   /* jasny zolty     kontrast 6.5:1, nasycenie 0.59 */
  --ansi-12: #385185;   /* jasny niebieski kontrast 6.5:1, nasycenie 0.58 */
  --ansi-13: #7c357f;   /* jasny karmazyn  kontrast 6.5:1, nasycenie 0.58 */
  --ansi-14: #2b5865;   /* jasny blekit    kontrast 6.5:1, nasycenie 0.57 */
  --ansi-15: #383634;   /* jasny bialy     kontrast 10.0:1, nasycenie 0.07 */
}

[data-theme="layers"] {
  /* „warstwy" - sufit nasycenia 0.67 (najbardziej nasycony token motywu). */
  --ansi-0:  #6f697a;   /* czarny          kontrast 3.4:1, nasycenie 0.14 */
  --ansi-1:  #ab7066;   /* czerwony        kontrast 4.5:1, nasycenie 0.40 */
  --ansi-2:  #538b66;   /* zielony         kontrast 4.5:1, nasycenie 0.40 */
  --ansi-3:  #8d7e54;   /* zolty           kontrast 4.5:1, nasycenie 0.40 */
  --ansi-4:  #697fb0;   /* niebieski       kontrast 4.5:1, nasycenie 0.40 */
  --ansi-5:  #a16bb2;   /* karmazyn        kontrast 4.5:1, nasycenie 0.40 */
  --ansi-6:  #55878e;   /* blekit          kontrast 4.5:1, nasycenie 0.40 */
  --ansi-7:  #a79db7;   /* bialy           kontrast 7.0:1, nasycenie 0.14 */
  --ansi-8:  #8c8399;   /* jasny czarny    kontrast 5.0:1, nasycenie 0.14 */
  --ansi-9:  #df8274;   /* jasny czerwony  kontrast 6.5:1, nasycenie 0.48 */
  --ansi-10: #59ac75;   /* jasny zielony   kontrast 6.5:1, nasycenie 0.48 */
  --ansi-11: #b09a5c;   /* jasny zolty     kontrast 6.6:1, nasycenie 0.48 */
  --ansi-12: #789ae7;   /* jasny niebieski kontrast 6.5:1, nasycenie 0.48 */
  --ansi-13: #cf79e9;   /* jasny karmazyn  kontrast 6.5:1, nasycenie 0.48 */
  --ansi-14: #5da6b2;   /* jasny blekit    kontrast 6.5:1, nasycenie 0.48 */
  --ansi-15: #c8bcda;   /* jasny bialy     kontrast 10.0:1, nasycenie 0.14 */
}

[data-theme="carrier"] {
  /* „nosna" - sufit nasycenia 0.63 (najbardziej nasycony token motywu). */
  --ansi-0:  #6e7475;   /* czarny          kontrast 3.4:1, nasycenie 0.06 */
  --ansi-1:  #a37f75;   /* czerwony        kontrast 4.5:1, nasycenie 0.28 */
  --ansi-2:  #689072;   /* zielony         kontrast 4.5:1, nasycenie 0.28 */
  --ansi-3:  #928669;   /* zolty           kontrast 4.5:1, nasycenie 0.28 */
  --ansi-4:  #7489a0;   /* niebieski       kontrast 4.5:1, nasycenie 0.28 */
  --ansi-5:  #a179a8;   /* karmazyn        kontrast 4.5:1, nasycenie 0.28 */
  --ansi-6:  #698d91;   /* blekit          kontrast 4.5:1, nasycenie 0.28 */
  --ansi-7:  #a3acae;   /* bialy           kontrast 7.0:1, nasycenie 0.06 */
  --ansi-8:  #899092;   /* jasny czarny    kontrast 5.0:1, nasycenie 0.06 */
  --ansi-9:  #d29686;   /* jasny czerwony  kontrast 6.5:1, nasycenie 0.36 */
  --ansi-10: #72b282;   /* jasny zielony   kontrast 6.5:1, nasycenie 0.36 */
  --ansi-11: #b6a274;   /* jasny zolty     kontrast 6.5:1, nasycenie 0.36 */
  --ansi-12: #84a7cf;   /* jasny niebieski kontrast 6.5:1, nasycenie 0.36 */
  --ansi-13: #ce8cdc;   /* jasny karmazyn  kontrast 6.5:1, nasycenie 0.36 */
  --ansi-14: #73aeb4;   /* jasny blekit    kontrast 6.5:1, nasycenie 0.36 */
  --ansi-15: #c3cdcf;   /* jasny bialy     kontrast 10.0:1, nasycenie 0.06 */
}

[data-theme="glow"] {
  /* „poswiata" - sufit nasycenia 0,71 (`--accent` #4a9eff, najbardziej
     nasycony token motywu; 11.4 dopisze powierzchnie o 0,55-0,62, wiec sufit
     zostanie ten sam). To NAJWYZSZY sufit w calym arkuszu, a paleta i tak
     stoi na 0,44 i 0,53 - regula 2 jest granica, nie celem. Motyw, ktory
     wyciagnalby programy pod sam sufit, powiedzialby lazurem to samo, co
     mowi wlasnym akcentem, i „ognisko okna" przestaloby cokolwiek znaczyc.

     Tlo `--bg` #070b14 jest najciemniejsze w projekcie (luminancja 0,0034),
     wiec `--fg` ma wobec niego az 16,7:1 i drabina kontrastow 3,4 / 4,5 /
     5,0 / 6,5 / 7,0 / 10,0 - ta sama, co w czterech motywach powyzej
     (LICZBA NIE ZE SCHEMATU: liczy bloki STOJACE WYZEJ W TYM
     arkuszu, a nie inwentarz - blok „poswiaty" jest piaty) -
     miesci sie pod regula 3 z ogromnym zapasem. Drabina zostaje NIERUSZONA
     wlasnie dlatego: „jasny czerwony" ma znaczyc w kazdym motywie tyle samo,
     a nie tyle, ile pozwala tlo.

     Neutralne 0 / 7 / 8 / 15 to lazurowa szarosc (odcien 214, o dwa stopnie
     od `--accent`), a nie czern i biel - regula 1. Odcienie 1-6 sa nudzone
     ku motywowi tylko tam, gdzie to nic nie znaczy: niebieski siedzi na 212,
     czyli na odcieniu akcentu, bo w tym motywie „niebieski programu" i lazur
     pulpitu maja byc tym samym kolorem. Pozostale piec - czerwien 9, zolc 44,
     zielen 140, blekit 186, karmazyn 290 - leza w promieniu 5 stopni od tych
     samych odcieni w „siatce znakow" (8,6 / 44,2 / 137,3 / 181,2 / 292,6),
     bo indeks jest KONTRAKTEM, a nie porzadkiem motywu: `git diff` ma
     czerwienic usuniete tak samo we wszystkich pieciu motywach. */
  --ansi-0:  #5c6774;   /* czarny          kontrast 3.4:1, nasycenie 0.21 */
  --ansi-1:  #a7685d;   /* czerwony        kontrast 4.5:1, nasycenie 0.44 */
  --ansi-2:  #4a855e;   /* zielony         kontrast 4.5:1, nasycenie 0.44 */
  --ansi-3:  #87784c;   /* zolty           kontrast 4.5:1, nasycenie 0.44 */
  --ansi-4:  #5a7ba1;   /* niebieski       kontrast 4.5:1, nasycenie 0.44 */
  --ansi-5:  #a061ac;   /* karmazyn        kontrast 4.5:1, nasycenie 0.44 */
  --ansi-6:  #4c8187;   /* blekit          kontrast 4.5:1, nasycenie 0.44 */
  --ansi-7:  #8c9caf;   /* bialy           kontrast 7.0:1, nasycenie 0.20 */
  --ansi-8:  #758192;   /* jasny czarny    kontrast 5.0:1, nasycenie 0.20 */
  --ansi-9:  #db7967;   /* jasny czerwony  kontrast 6.5:1, nasycenie 0.53 */
  --ansi-10: #4ea56b;   /* jasny zielony   kontrast 6.5:1, nasycenie 0.53 */
  --ansi-11: #aa9250;   /* jasny zolty     kontrast 6.5:1, nasycenie 0.53 */
  --ansi-12: #6398d3;   /* jasny niebieski kontrast 6.5:1, nasycenie 0.53 */
  --ansi-13: #d16be5;   /* jasny karmazyn  kontrast 6.5:1, nasycenie 0.53 */
  --ansi-14: #4fa0a9;   /* jasny blekit    kontrast 6.5:1, nasycenie 0.53 */
  --ansi-15: #a8bbd2;   /* jasny bialy     kontrast 10.0:1, nasycenie 0.20 */
}

/* --- Ruch (etap 7.5) -------------------------------------------------------
   Czasy przejsc sa WSPOLNE dla wszystkich motywow, choc typografia i siatka nie sa.
   Motyw odpowiada na pytanie "jak to wyglada", a nie "jak szybko sie porusza":
   trzy zestawy czasow znaczylyby trzy miejsca do poprawienia przy kazdej
   zmianie tempa i trzy rozne pulpity pod palcem tego samego czlowieka.

   Ta trojka jest odpowiednikiem `LIMIT_RUCHOW` z `shell/ruch.js`: arkusz mowi,
   ILE TRWA ruch, a kod - CZY w ogole sie odbywa. Dlatego prosba o mniej ruchu
   jest tu JEDNYM blokiem `@media` na caly arkusz, a nie wyjatkiem dopisywanym
   przy kazdej animacji z osobna - ten drugi uklad juz tu byl (trzy bloki) i
   przy czwartej animacji zaczal klamac.

   TE CZTERY LICZBY SA TOKENAMI WSPOLNYMI. `:root` i `[data-theme="x"]` maja
   ROWNA specyficznosc (0,1,0), wiec rozstrzyga POLOZENIE W PLIKU - a to ma
   DWA PRZECIWNE NASTEPSTWA i oba sa ciche. Motyw stojacy NAD ta baza przegra,
   wiec jego wlasna deklaracja bedzie MARTWA; motyw dopisany POD nia wygra
   KOLEJNOSCIA, nie specyficznoscia, i zabierze zero wyjatkowi o mniej ruchu,
   ktory stoi dwa wiersze nizej. Dzis wszystkie bloki motywow stoja NAD
   (pilnuje tego `tokeny_wspolne`, ktory liczy inwentarz z polozenia), wiec
   grozi pierwsze - ale na koncu pliku dopisuje sie najlatwiej i wtedy grozi
   drugie. Zmierzone `tests/wd-wspolne.py` w kazdym z pieciu motywow, sygnalem
   999ms wstrzykiwanym w to tempo po obu stronach <link> do tego arkusza: NAD -
   zostaje 160ms, POD - motyw wygrywa, a przy udawanej prosbie o mniej ruchu
   podmienia 0s na 999ms. (Sygnal opisany SLOWAMI, a nie zapisem deklaracji:
   dwie zapadki w `tests/test-ruch.py` licza w CALYM pliku wystapienia tej
   nazwy z dwukropkiem oraz nazwy wyjatku, wiec cytat w prozie zapalilby je na
   czerwono - i slusznie, bo grep po tym arkuszu ma znajdowac deklaracje,
   a nie ich opisy.)
   Zaden motyw nie ma wiec prawa ich deklarowac po
   ZADNEJ ze stron; pilnuje tego `tokeny_wspolne` w `tests/test-ruch.py`, ktory
   te nazwy LICZY z polozenia w pliku, zamiast miec je przepisane. */
:root {
  --ruch-szybko: 70ms;   /* lupa doku - startowana od nowa przy kazdym ruchu myszy */
  --ruch:        160ms;  /* domyslne przejscie powloki */
  --ruch-wolno:  300ms;  /* wskazania serwera - wolniej, bo to odczyt, nie gest */
  --ruch-krzywa: cubic-bezier(.2,.8,.3,1);
}
/* Jedyny wyjatek w calym arkuszu. Zerowy CZAS, a nie `transition: none`:
   deklaracje zostaja tam, gdzie stoja (przy swoich elementach), wiec nie da
   sie dolozyc animacji, ktora o tym wyjatku nie wie. */
@media (prefers-reduced-motion: reduce) {
  :root { --ruch-szybko: 0s; --ruch: 0s; --ruch-wolno: 0s; }
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }

body {
  background: var(--bg);
  color: var(--fg);
  font: 13px/1.5 var(--font-ui);
  overflow: hidden;
}
[data-theme="grid"] body {
  /* Sygnatura motywu: siatka znaku widoczna w tle - ta sama komorka, do ktorej
     przyciagaja sie okna (etap 7.1).

     Rysowana jest co OSMA kolumna i co DRUGI wiersz, a nie kazda komorka:
     linia co 8 px zlewa sie w plaszczyzne, a nie w siatke. Okna przyciagaja
     sie do pelnej komorki, wiec kazda linia w tle nadal wypada dokladnie na
     krawedzi, ktora okno moze zajac - narysowany jest podzbior siatki, a nie
     inna siatka.

     Przesuniecie o `--pasek-gorny-h` sadza poczatek siatki na gornej krawedzi
     warstwy okien: `#menubar` ma `box-sizing: border-box`, wiec jego ramka
     miesci sie w tej wysokosci i `#windows` zaczyna sie dokladnie pod nia.
     Zmienna, a nie `--bar-h`, bo od 9.4 (czesc 3/N) gorny pasek na dotyku
     jest wyzszy od paska biurkowego - a siatka narysowana od `--bar-h`
     rozjechalaby sie z oknami o te 17 px na kazdym telefonie. */
  background-image:
    linear-gradient(to right,  var(--line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--line) 1px, transparent 1px);
  background-size: calc(var(--cell-w) * 8) calc(var(--cell-h) * 2);
  background-position: 0 var(--pasek-gorny-h);
  opacity: 1;
}
[data-theme="layers"] body {
  background:
    radial-gradient(120% 90% at 25% 15%, #3a2170 0%, transparent 60%),
    radial-gradient(100% 80% at 80% 85%, #6a2a63 0%, transparent 55%),
    var(--bg);
}

/* `position: relative` jest tu dla doku z autoukrywaniem (3.5): wysuniety
   pasek lezy NAD warstwa okien, wiec musi kotwiczyc sie w pulpicie, a nie
   w oknie przegladarki - inaczej ekran logowania i pulpit walczylyby
   o te sama krawedz. */
#desk { display: flex; flex-direction: column; height: 100%; position: relative; }

/* --- Tapeta "poswiaty" (etap 11.10) ----------------------------------------
   Obraz siada na `#desk`, a NIE na `body` - i to jest jedyna decyzja tego
   bloku, ktora cokolwiek oszczedza. `#desk` ma `hidden`, dopoki nie ma sesji,
   wiec ekran logowania nie pobiera ani jednego bajta tapety: element
   z `display: none` nie ma tla do namalowania. `body` widac ZAWSZE, wiec
   tapeta na nim byloby 20 KB pobranym przez kazdego, kto trafi pod adres -
   takze przez tego, kto hasla nie zna.

   ZWYKLY `background-image` I NIC WIECEJ. Bez `::before`, bez
   `position: fixed`, bez `opacity`, bez `filter`, bez `will-change`: kazda
   z tych czterech deklaracji zaklada KONTEKST UKLADANIA, a wtedy `z-index`
   okien przestaje byc porownywany miedzy oknami. Cztery stopnie glebi
   z 11.5 i cala warstwa okien licza na to, ze pulpit zadnego kontekstu nie
   zaklada; `background-image` go nie zaklada.

   Dwie warstwy w jednej wlasnosci: gorna to jednolity gradient
   przyciemniajacy, dolna to obraz. Pod nimi `background-color: var(--bg)`
   jako podloga - widoczna, zanim obraz dojdzie, i tam, gdzie kadr `cover`
   go nie donosi.

   ALFA PRZYCIEMNIENIA JEST WYNIKIEM POMIARU, a nie odcieniem. Mierzone
   (skrypt liczacy po WSZYSTKICH pikselach obu plikow, zapadka w
   `tests/test-poswiata.py`): najjasniejszy piksel to (118,125,137)
   w wariancie szerokim i (123,128,142) w waskim - czyli LICZBE USTAWIA
   WARIANT WASKI, bo jego skala zbiera jasniejsze maksimum. Skladamy ten
   piksel z przyciemnieniem, a potem ze szklem paska (`--surface` .55) i
   pytamy o kontrast NAJSLABSZEGO tekstu, jaki na tym szkle stoi -
   `--fg-dim` (`.mb-stat` w gornym pasku, `.dok-okno--zmin` w doku):

       alfa   szeroki   waski
       .00     3,13:1    3,05:1
       .35     4,14:1    4,07:1
       .40     4,30:1    4,24:1
       .45     4,47:1    4,41:1     <- ciagle ponizej progu
       .50     4,65:1    4,59:1     <- oba z zapasem

   Wpisane jest .5. Warunek z `--fg-dim` jest OSTRZEJSZY PONAD DWUKROTNIE
   niz warunek z `--fg` (6,85:1 i 6,68:1 juz przy alfie zero), wiec dowod
   pytajacy tylko o `--fg` przepuscilby tapete BEZ przyciemnienia wcale.

   Liczone po pikselu SUROWYM, choc szklo rozmywa: `blur(8px)` usrednia
   maksimum do (71,54,107) i (57,45,91), czyli tylko pomaga. Rozmycia sie
   nie doliczamy z dwoch powodow - kadr `cover` zalezy od proporcji okna
   przegladarki, wiec nie wiadomo, ktory fragment wypadnie pod paskiem,
   a sufit ma miec zapas, nie byc trafiony.

   `rgb(7 11 20 / .5)` to `--bg` z alfa. Literal, bo alfy nie da sie dolozyc
   do hexa w `var()`; ze to ta sama liczba, co `--bg`, pilnuje test - inaczej
   zmiana tla motywu zostawilaby przyciemnienie w starym kolorze.

   Przyciemnienie stoi w `--tapeta-cien`, bo warianty sa dwa, a alfa ma byc
   JEDNA. Rozpisana na obie reguly wygladalaby tak samo i dawala dwie liczby
   do rozjechania sie - przy czym rozjazd widac by bylo wylacznie na ekranie
   wezszym niz prog, czyli tam, gdzie nikt nie patrzy podczas pracy. */
[data-theme="glow"] #desk {
  --tapeta-cien: linear-gradient(rgb(7 11 20 / .5), rgb(7 11 20 / .5));
  background-color: var(--bg);
  background-image: var(--tapeta-cien), url("/tlo/nexus-1920.webp");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* Wariant waski idzie przez `[data-uklad]`, a NIE przez `@media`: prog
   `PROG_WASKI_PX` mieszka w `web/shell/dotyk.js` i jest JEDEN. Druga jego
   kopia w arkuszu rozjechalaby sie po cichu - dok, pasek i tapeta pytalyby
   o "waski" dwoma roznymi liczbami, a widac by to bylo dopiero na tym
   jednym telefonie, ktory trafi miedzy nie. Zakaz stoi przy doku (9.3). */
[data-theme="glow"][data-uklad="waski"] #desk {
  background-image: var(--tapeta-cien), url("/tlo/nexus-960.webp");
}

#menubar {
  /* Wysokosc STAD, a nie z `--bar-h`: na dotyku pasek musi urosnac do celu
     dla palca i robi to jednym miejscem (etap 9.4, czesc 3/N, ponizej). */
  height: var(--pasek-gorny-h);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 12px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  font-size: 12px;
  /* WLASNY KONTEKST UKLADANIA, NAD `main` - i to nie jest kosmetyka.
     Menu rozwija sie jako dziecko `#menubar` z `z-index: 30`. Dopoki pasek nie
     jest pozycjonowany, ta liczba konkuruje bezposrednio z `main` (position:
     relative, z-index: 0) i wygrywa. Ale KAZDA wlasciwosc tworzaca kontekst
     ukladania na pasku - `backdrop-filter` motywu "warstwy", w przyszlosci
     `filter`, `transform`, `opacity` czy `will-change` - zamyka `z-index: 30`
     WEWNATRZ paska, a sam pasek wchodzi do rodzica jako `z-index: auto`, czyli
     zero. Wtedy `main`, stojacy PO nim w dokumencie, wygrywa remis kolejnoscia
     i laduje NAD menu.
     Objaw jest okrutny, bo `main` nie ma tla: menu jest w calosci widoczne,
     wyglada poprawnie, a kazde klikniecie trafia w `main`. Straznik "klik poza
     menu" slusznie je wtedy zamyka - klik NAPRAWDE byl poza. Zgloszenie
     czlowieka brzmialo "klikniecie w dowolny punkt menu nie dziala, po prostu
     sie zamyka, nawet aplikacje sie nie otwieraja" i dotyczylo motywu "warstwy".
     Zmierzone 2026-09-01: w `grid` `elementFromPoint` na pozycji menu zwraca
     BUTTON, w `layers` - MAIN.
     Dlatego pasek dostaje kontekst SAM, tutaj, w bazie: skoro i tak go zyskuje
     przy pierwszej takiej wlasciwosci, niech go ma zawsze i niech stoi NAD
     `main`. Inaczej ta sama usterka wraca przy kazdym nowym motywie z rozmyciem,
     a wyglada za kazdym razem jak awaria menu, nie jak kolejnosc malowania. */
  position: relative;
  z-index: 40;
}
[data-theme="layers"] #menubar { backdrop-filter: blur(14px); }
/* SZKLO "POSWIATY" 1/4 (zadanie 11.6). Cztery powierzchnie i ANI JEDNA
   WIECEJ: `#menubar`, `.mb-menu-lista`, `#dock`, `.brama-karta` - dokladnie
   te, ktore rozmywaja juz "warstwy". Zaden nowy element nie dostaje szkla,
   bo liczba szklanych powierzchni jest w tym projekcie BUDZETEM, a nie
   ozdoba: kompozytor trzyma dla kazdej z nich rozmyta kopie tego, co pod nia
   lezy. Cztery to sufit ARKUSZA; na stojacym pulpicie zywe sa dwie (menu
   jest zwiniete, brama zamknieta) i - to jest cala teza - dwie zostaja
   takze przy dwunastu oknach, bo ani jedna z tych regul nie dotyczy `.okno`.
   Pilnuje tego dowod (d) w `tests/test-poswiata.py`, liczac elementy
   o niepustym `backdropFilter`, a nie reguly.
   Rozmycie jest jedno na wszystkie cztery i mieszka w `--szklo` - powod
   i arytmetyka stoja przy tokenie. */
[data-theme="glow"] #menubar { backdrop-filter: var(--szklo); }

/* --- Pasmo nosnej (etap 10.6) ----------------------------------------------
   Sygnatura motywu "Nosna". Dolna krawedz gornego paska przestaje byc obwodka
   i staje sie pasmem 2 px na CALA szerokosc pulpitu, zlozonym z dwoch warstw -
   i to zlozenie jest cala jego odpornoscia:

     * spodnia warstwa to martwa, ciagla kreska `var(--line)`. Jest ZAWSZE,
       wiec pasek nigdy nie traci swojej krawedzi, nawet gdy nie ma czego
       mierzyc;
     * wierzchnia to grzebien znacznikow `var(--nosna-lampa, transparent)`
       o okresie `var(--nosna-okres, 8px)`. Obie wlasciwosci pisze na <html>
       `shell/nosna.js` (zadanie 10.7) ze ZMIERZONEGO czasu przelotu do maszyny
       po drugiej stronie: jedna kratka znaku (`--cell-w`) = jedna milisekunda,
       wiec szerokosc ekranu jest osia czasu, a opoznienie widac katem oka.

   Cala maszyna stanow jest tu ZAPASOWA WARTOSCIA `var()`, a nie regula
   warunkowa: brak zywego gniazda terminala = brak wlasciwosci `--nosna-lampa`
   = lampa przezroczysta = BRAK NOSNEJ narysowany doslownie. Ani jednej reguly
   z warunkiem, ani jednej linijki JS gaszacej cokolwiek - wzorzec do powtorzenia
   przy kazdym przyszlym wskazniku telemetrii w tym projekcie.

   `background` musi byc JEDNA deklaracja wielowarstwowa (skroty tla nie
   sumuja sie miedzy regulami), a `padding` nie jest tu ruszany: skrot
   skasowalby `padding-top: env(safe-area-inset-top)` z reguly dotykowej
   nizej, a ten selektor ma specyficznosc (1,1,0) wobec (1,0,0), wiec
   wygralby ZAWSZE, nie tylko przypadkiem. */
[data-theme="carrier"] #menubar {
  border-bottom: 0;
  background:
    repeating-linear-gradient(90deg, var(--nosna-lampa, transparent) 0 1px,
                              transparent 1px var(--nosna-okres, 8px))
      bottom left / 100% 2px no-repeat,
    linear-gradient(var(--line), var(--line)) bottom left / 100% 2px no-repeat,
    var(--surface);
}

/* --- Menu paska gornego (etap 3.6) -----------------------------------------
   Korzen menu (u nas: `mb-brand` z paska) dostaje od `shell/menu.js` wlasna
   klase `mb-menu` i to ona jest tu stylowana - modul nie ma prawa zalezec od
   nazwy klasy, ktora nadal mu gospodarz. `position: relative` kotwiczy w nim
   rozwinieta liste; bez tego lista liczylaby sie od `#desk` i przy kazdym
   motywie (rozne `--bar-h`) trzeba by ja przesuwac z reki. */
.mb-menu { position: relative; display: flex; align-items: center; }

.mb-menu-guzik {
  font: inherit;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--accent);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 2px 8px;
  cursor: pointer;
}
.mb-menu-guzik:hover { border-color: var(--line); }
.mb-menu-guzik:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mb-menu-guzik[aria-expanded="true"] { border-color: var(--accent); }

/* Lista istnieje w DOM tylko wtedy, gdy menu jest otwarte (shell/menu.js) -
   nie ma tu wiec reguly `display: none`, bo nie ma czego chowac.
   `max-height` + `overflow-y` sa granica UKLADU odpowiadajaca limitowi
   MAX_POZYCJI z kodu: lista dluzsza niz ekran chowa swoj dol pod krawedzia
   i pozycji z konca nie da sie kliknac. */
.mb-menu-lista {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 30;              /* nad oknami: #windows ma wlasny kontekst, z-index 0 */
  min-width: 220px;
  max-height: calc(100vh - var(--pasek-gorny-h) - 20px - env(safe-area-inset-bottom));
  overflow-y: auto;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(0 0 0 / .35);
}
[data-theme="instrument"] .mb-menu-lista { box-shadow: none; }
[data-theme="carrier"] .mb-menu-lista { box-shadow: none; }
[data-theme="layers"] .mb-menu-lista { backdrop-filter: blur(14px); }
/* SZKLO "POSWIATY" 2/4 + CIECIE. Razem ze szklem lista ODDAJE czarny cien
   bazy (`0 8px 24px rgb(0 0 0 / .35)`) i NIE dostaje w zamian poswiaty.
   Dwa osobne powody, oba wazne:
     * czern. Motyw ma palete chlodna i lazurowa; cien bazy maluje kolor
       spoza niej, wiec jest jedynym miejscem, w ktorym "poswiata" plamila by
       ekran czernia (precedens: `instrument` i `carrier` gasza go tu tak samo,
       tylko z innego powodu - tam chodzi o plaskosc);
     * halo. Poswiata jest w tym motywie ZDANIEM O STOSIE OKIEN (11.5:
       `data-glebia`, cztery stopnie). Menu, komunikat i brama nie maja
       w tym stosie zadnej rangi, wiec halo powtorzone pod nimi przestaje
       byc informacja i zaczyna zaglaszac sygnature. Halo wystepuje
       WYLACZNIE na oknach - i to jest cala tresc ciecia. */
[data-theme="glow"] .mb-menu-lista {
  backdrop-filter: var(--szklo);
  box-shadow: none;
}

.mb-menu-grupa {
  color: var(--fg-dim);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 6px 8px 2px;
}
.mb-menu-grupa + .mb-menu-grupa,
.mb-menu-poz + .mb-menu-grupa { border-top: 1px solid var(--line); margin-top: 4px; }

.mb-menu-poz {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  color: var(--fg);
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  padding: 4px 8px;
  cursor: pointer;
  /* Tytul okna bywa sciezka pliku - pozycja ma nie rozpychac listy na pol ekranu. */
  max-width: 44ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mb-menu-poz:hover:not(:disabled) { background: var(--line); }
.mb-menu-poz:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.mb-menu-poz:disabled { color: var(--fg-dim); cursor: default; }

/* Wybor motywu (etap 7.4). Ptaszek rysuje sie z `aria-checked`, a nie z osobnej
   klasy: stan slyszany przez czytnik ekranu i stan widziany na ekranie maja byc
   TYM SAMYM atrybutem, bo dwa nosniki tego samego stanu rozjezdzaja sie przy
   pierwszym przeoczonym przypisaniu. Miejsce na znacznik rezerwuja OBIE
   pozycje - inaczej lista przeskakiwalaby w bok przy kazdym przelaczeniu. */
.mb-menu-poz[role="menuitemradio"] { padding-left: 22px; position: relative; }
.mb-menu-poz[role="menuitemradio"]::before {
  content: "✓";
  position: absolute;
  left: 7px;
  color: var(--accent);
  opacity: 0;
}
.mb-menu-poz[aria-checked="true"]::before { opacity: 1; }

/* Nazwa aktywnej aplikacji (etap 3.6). Skracanie jest tu warunkiem ukladu,
   nie ozdoba: tytul okna bywa sciezka pliku, a pasek bez ograniczenia
   zepchnalby zegar i guzik "Wyloguj" poza ekran. Pelna nazwa zostaje
   w podpowiedzi i w `aria-label`, ktore ustawia `shell/pasek.js`. */
.mb-app {
  color: var(--fg);
  max-width: 32ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mb-spacer{ flex: 1; }

/* Diagnostyka backendu. `nowrap` jest tu WARUNKIEM ukladu, nie ozdoba, i to
   jest drugie odkrycie czesci 5/N etapu 9.3 - to, ktorego nie widac nawet na
   waskim pulpicie, bo nie wyglada na przepelnienie.

   `#menubar` ma STALA wysokosc (`--bar-h`, 28 px) i `align-items: center`.
   Tekst, ktory sie nie miesci, nie jest przez to przycinany ani zwezany -
   ZAWIJA sie: zmierzone na 390 px, `.mb-stat` mial 50 px wysokosci, a zegar
   54 px, czyli po trzy wiersze w pasku wysokim na 28 px. Nadmiar wychodzi
   wtedy w GORE i w DOL, po ~13 px: gora ląduje pod wcieciem ekranu telefonu
   (9.4), dol - na warstwie okien. Strona nie zglasza przy tym nic, bo
   `scrollWidth` jest w porzadku, a konsola milczy.

   Z `nowrap` napis nie ma jak urosnac w gore, a `ellipsis` daje mu jedyny
   sensowny sposob na oddanie miejsca - ten sam, ktory `.mb-app` ma od 3.6.
   Reguly nie ma pod `[data-uklad="waski"]`, bo zawijanie w pasku o stalej
   wysokosci jest bledem po OBU stronach progu: biurkowe okno przegladarki
   szersze o piksel od progu ma dokladnie ten sam pasek. */
.mb-stat  {
  color: var(--fg-dim);
  font-family: var(--font-mono);
  font-size: 11px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Wskazania serwera (etap 7.2) -----------------------------------------
   Trzy wskazania z wlosowa skala. Rysuje je `shell/metryki.js`, ktory dostaje
   od paska sam pusty `mb-metryki` - CSS opisuje tu KSZTALT, a nie tresc.

   Ksztalt jest JEDEN dla wszystkich motywow, a rozni je sama paleta
   (`--line`, `--fg-dim`, `--accent`, `--radius`), bo drugi uklad wskazan
   znaczylby drugi zestaw regul do utrzymania. Sygnatura motywu "przyrzad" to
   nie inny ksztalt, tylko to, ze u niego skala jest jedyna kreska na ekranie:
   nie ma sie z czym zlac, bo cieni w tym motywie nie ma wcale. */
.mb-metryki { display: flex; align-items: center; gap: 12px; }

.mb-metryki .mtr-poz {
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-dim);
}
.mtr-etykieta { letter-spacing: .06em; }
/* `tabular-nums` jest tu warunkiem, nie ozdoba: bez niego wskazanie zmienia
   szerokosc przy kazdej cyfrze i pasek drga co pare sekund. */
.mtr-wartosc {
  min-width: 6ch;
  font-variant-numeric: tabular-nums;
}

/* Skala: prostokat obrysowany wlosowa linia, o STALEJ szerokosci. Zmienna
   szerokosc znaczylaby, ze dwa wskazania obok siebie nie daja sie porownac
   wzrokiem, a caly sens skali jest w porownaniu. */
.mtr-skala {
  position: relative;
  width: 34px;
  height: 7px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
/* Wypelnienie zakotwiczone w LEWEJ krawedzi: `scaleX` mnozy od `transform-origin`,
   wiec bez tego wskazowka roslaby w obie strony od srodka.
   Przejscie dotyczy wylacznie `transform` - to jedyna wlasciwosc, ktora zmienia
   sie tu w kolko, i jedyna, ktora przegladarka animuje bez przeliczania ukladu. */
.mtr-wypelnienie {
  position: absolute;
  inset: 0;
  transform-origin: left center;
  transform: scaleX(0);
  /* `--mtr-barwa` jest ZMIENNA NA RODZICU (`.mtr-poz[data-mtr]`), a nie druga
     regula na tym elemencie - i na tym stoi cale zadanie 11.8. Regula
     `[data-theme="x"] .mtr-poz[data-mtr="y"] .mtr-wypelnienie` miala by
     (0,4,0), czyli bila by kolor progu z reguly nizej (0,2,0) i zabila by go
     CICHO: skala swiecila by barwa takze wtedy, gdy wskazanie przekroczylo
     PROG_SYGNAL. Zmienna nie ma wagi zadnej - podstawia sie w te jedna
     deklaracje, a kazda regula o wyzszej wadze nadal ja przykrywa.
     Zapas (`--fg-dim`) stoi w `var()`, wiec motyw, ktory o barwie milczy,
     zostaje przy przygaszonej skali i nie musi o niej wiedziec. */
  background: var(--mtr-barwa, var(--fg-dim));
  transition: transform var(--ruch-wolno) linear, background-color var(--ruch);
}
/* Jeden kolor sygnalowy - patrz decyzja 5 w `shell/metryki.js`. Prog jest
   w KODZIE (PROG_SYGNAL), tutaj jest tylko jego wyglad. Kolorem jest `--blad`,
   a nie `--accent`: przekroczony prog to stan, ktory sie NIE UDAL, a `--accent`
   maluje w tym samym pasku rzeczy zwyczajne (ognisko okna, czynna pozycja doku)
   - jeden kolor na oba znaczenia kasowal cala informacje z tego wskazania. */
.mtr-poz--sygnal { color: var(--blad); }
.mtr-poz--sygnal .mtr-wypelnienie { background: var(--blad); }
/* --- Barwa jako TOZSAMOSC wskazania (zadanie 11.8, tylko "poswiata") --------
   Wyjatek od decyzji 5 w `shell/metryki.js`, przyznany przez czlowieka
   2026-09-02 i WASKI: tylko ten motyw, tylko te trzy wskazania serwera, tylko
   tozsamosc ("ktore to"), nigdy stan ("czy zle"). Stan ma dalej JEDEN kolor
   i jest nim `--blad` z reguly TUZ WYZEJ - ta ma (0,2,0), a zmienna ponizej
   nie wazy nic, wiec prog wygrywa we wszystkich pieciu motywach. Zapadka na
   to jest w `tests/test-poswiata.py` (klasa `Barwy`), na zywym DOM, a nie
   w tym zdaniu.

   Trzy odcienie sa CHLODNE, a `--blad` (#f8705f) jest CIEPLY, i to jest
   warunek (b) wyroku, a nie estetyka: oko, ktore nie odroznia czerwieni od
   zieleni, widzi te cztery kolory na osi zolto-niebieskiej. Zmierzone
   symulacja Vienota (ten sam test): temperatura B - (R+G)/2 po symulacji
   protanopii wynosi -41 dla `--blad` wobec +59 / +94 / +31 dla lazuru,
   fioletu i turkusu, a po symulacji deuteranopii -80 wobec +72 / +88 / +47.
   Najciasniejszy odstep to 72 stopnie kanalu (turkus, protanopia) - roznica
   lezy wiec w temperaturze, a nie w odcieniu, i zostaje takze wtedy, gdy
   odcienia nie widac.

   Nasycenie kazdej z trzech barw (HSV: 0,63 / 0,44 / 0,64) stoi PONIZEJ
   `--accent` (0,71), czyli pod sufitem nasycenia calego motywu z reguly 2 -
   wskazanie nie ma prawa krzyczec glosniej niz najglosniejszy token motywu.
   Klucze `obc`/`ram`/`sesje` sa kluczami `POZYCJE` z `shell/metryki.js`;
   czwarte wskazanie dopisane tam bez reguly tutaj jest czerwone. */
[data-theme="glow"] .mtr-poz[data-mtr="obc"]   { --mtr-barwa: #4f9fd4; }  /* lazur  */
[data-theme="glow"] .mtr-poz[data-mtr="ram"]   { --mtr-barwa: #a07de0; }  /* fiolet */
[data-theme="glow"] .mtr-poz[data-mtr="sesje"] { --mtr-barwa: #4dbfd6; }  /* turkus */
/* Brak odczytu: skala pusta i przygaszona. Zero i "nie wiem" musza sie roznic,
   bo inaczej zgaszone zrodlo wyglada jak spokojna maszyna. */
.mtr-poz--brak { opacity: .5; }
.mtr-poz--brak .mtr-skala { border-style: dashed; }
/* Zegar tez `nowrap` - z tego samego powodu (patrz `.mb-stat` wyzej), i tu
   widac go najlepiej: "wt., 1 wrz  09:43" ma w srodku spacje, wiec zawijal sie
   na trzy wiersze. Ponizej progu zegara nie ma wcale, ale to jest decyzja
   `shell/pasek.js` (znika RAZEM z timerem), a nie regula arkusza - CSS nie
   umie zgasic `setTimeout`. */
.mb-clock {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* `z-index: 0` zamyka okna we WLASNYM kontekscie ukladania. Bez tego dok
   musialby przebic z-index najwyzszego okna, a ten rosnie bez ograniczenia
   (okno.js: Z_BAZA + licznik wierzchu), wiec kazda stala tutaj bylaby
   pytaniem „ile razy czlowiek dzis kliknal w okno". */
#windows { flex: 1; position: relative; overflow: hidden; z-index: 0; }

#dock {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  gap: 8px;
  padding: 8px;
}

/* --- Wciecie ekranu (etap 9.4, czesc 5/N) ----------------------------------
   Nie o rozmiarze celu, tylko o MARGINESIE: u gory aparat, u dolu pasek gestow
   lezacy NAD trescia. Te reguly i `viewport-fit=cover` w `index.html` sa jedna
   zmiana: bez tamtego napisu `env()` jest zerem na telefonie, a sam napis bez
   nich jest REGRESJA. Emulacja tego nie zlapie (podaje wciecia takze bez
   `cover`), wiec napisu pilnuje zapadka ZRODLOWA. Bez `[data-wskaznik]`:
   wciecie jest faktem o EKRANIE, a `env()` na biurku daje 0 px. Gora nie
   miesci sie tutaj: `#menubar` ma `height` i border-box, wiec sam
   `padding-top` zjadlby guzikom ich 44 px - o tyle samo rosnie
   `--pasek-gorny-h`. Reszta 9.4 jest w mapie i w `tests/test-uklad.py`. */
#desk {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
#menubar { padding-top: env(safe-area-inset-top); }
#dock { padding-bottom: calc(8px + env(safe-area-inset-bottom)); }
.dok-skrot, .dok-okno {
  font: inherit;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 6px 14px;
  cursor: pointer;
  transition: transform var(--ruch) var(--ruch-krzywa), border-color var(--ruch);
}
.dok-skrot:hover, .dok-okno:hover {
  transform: translateY(-3px); border-color: var(--accent);
}
.dok-skrot:focus-visible, .dok-okno:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* Pozycja okna (etap 3.5). Tytul jest przycinany, a nie rozpycha paska:
   liczba pozycji rosnie z liczba okien ZMINIMALIZOWANYCH, a ta nie ma
   gornego ograniczenia - dlugi tytul mnozony przez te liczbe zjadlby pulpit. */
.dok-okno {
  max-width: 20ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Zminimalizowane: przygaszone, bo jego zawartosc jest odmontowana - to nie
   jest to samo co okno lezace pod innym. */
.dok-okno--zmin { color: var(--fg-dim); border-style: dashed; }
/* Aktywne oznacza podkreslenie sygnalowe, nie tlo: w motywie "warstwy"
   powierzchnia jest polprzezroczysta i zmiana tla gubi sie w rozmyciu. */
.dok-okno[aria-current="true"] {
  border-color: var(--accent);
  box-shadow: inset 0 -2px 0 var(--accent);
}
.dok-przerwa { width: 1px; align-self: stretch; background: var(--line); margin: 0 4px; }

/* --- Autoukrywanie (etap 3.5) ---------------------------------------------
   Dok z autoukrywaniem WYCHODZI Z UKLADU. Gdyby zostal w kolumnie flex,
   schowanie oddawaloby oknom obraz, ale nie MIEJSCE: pas jego wysokosci
   zostalby pusty, czyli caly zysk z chowania zniknalby. */
.dok--auto {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 1;                      /* nad warstwa okien - patrz #windows */
  transition: transform var(--ruch) var(--ruch-krzywa);
}
/* Zjazd idzie `transform`, nie `bottom`: przesuniecie warstwy nie przelicza
   ukladu ani razu, a `bottom` liczylby go w kazdej klatce animacji.
   Margines dokladany do 100% zabiera z ekranu takze cien paska. */
.dok--auto.dok--schowany { transform: translateY(calc(100% + 14px)); }

/* Strefa czula: pas przy dolnej krawedzi, ktory zglasza samo jej przekroczenie.
   Cena jest realna i swiadoma - te kilka pikseli nie nalezy juz do okna pod
   spodem - dlatego pas jest tak waski, jak da sie trafic myszka. */
.dok-strefa {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 5px;
  z-index: 1;
}

/* --- Powiekszanie przy kursorze (etap 3.5) ---------------------------------
   `transform-origin` MUSI byc `bottom center`: guzik ma rosnac w gore, bo w
   dol jest krawedz ekranu, a w bok liczy go dok (rozsuwanie sasiadow stoi na
   zalozeniu, ze skala jest symetryczna wzgledem srodka guzika).

   Przejscie jest krotkie i liniowe. Domyslne .16s z krzywa to przy ruchu myszy
   animacja startowana od nowa 60 razy na sekunde - guzik nigdy nie dochodzi do
   swojego rozmiaru i lupa wlecze sie za kursorem.

   Swiadomie NIE ma tu `will-change: transform`: to zadanie osobnej warstwy
   kompozytora dla KAZDEJ pozycji doku przez caly czas zycia karty, czyli
   pamiec proporcjonalna do liczby okien - dokladnie to, czego wymog nadrzedny
   zabrania. Chromium promuje guzik na czas animacji i tak. */
.dok--lupa > [data-dok] {
  transform-origin: bottom center;
  transition: transform var(--ruch-szybko) linear, border-color var(--ruch);
}
/* Podskok z najechania zastapiony jest lupa. Dwa efekty na tej samej
   wlasciwosci znaczylyby skok o 3 px w chwili, gdy lupa oddaje `transform`. */
.dok--lupa .dok-skrot:hover, .dok--lupa .dok-okno:hover { transform: none; }

/* --- Uklad dotykowy (etap 9.3) ---------------------------------------------
   Arkusz NIE pyta tu o `@media (hover: none)` ani o `(max-width: 720px)`, choc
   moglby: prog i kryterium wskaznika zyja w `shell/dotyk.js`, a tutaj czytany
   jest tylko ich WYNIK (`data-uklad`, `data-wskaznik` na <html>). Powod jest
   ten sam, co przy `data-theme`: druga kopia progu zgadza sie z pierwsza
   dokladnie do pierwszej zmiany, a rozjazd bylby CICHY - uklad wygladalby
   poprawnie po jednej stronie progu i mieszal po drugiej. Do tego decyzja
   o wskazniku jest zmierzona (Chromium bez glowy klamie na `hover`), wiec
   musi byc jedna dla arkusza i dla kodu.

   Podskok pod kursorem gasnie na dotyku, bo `:hover` na dotyku NIE JEST
   przelotny: przegladarki mobilne zostawiaja go na ostatnio tapnietym
   elemencie az do tapniecia obok. Guzik doku zostalby wiec uniesiony
   i podswietlony jako "ten pod palcem" dlugo po tym, jak palec poszedl
   gdzie indziej - czyli dok klamalby o tym, co sie zaraz otworzy. */
[data-wskaznik="dotyk"] .dok-skrot:hover,
[data-wskaznik="dotyk"] .dok-okno:hover { transform: none; }

/* Dok, ktory sie MIESCI (etap 9.3, czesc 4/N). Pytamy o `data-uklad`, a nie
   o `data-wskaznik`: mieszczenie sie jest pytaniem o MIEJSCE, a palec nie ma
   z nim nic wspolnego (te same 390 px w oknie przegladarki na biurku miesci
   dokladnie tyle samo pozycji). To trzecie uzycie podzialu z decyzji 1
   w `shell/dotyk.js` i najostrzejsze: tu widac, ze prog dotyczy szerokosci.

   Zmierzone na 390 px: cztery skroty zajmuja 310 px z 374 px wnetrza paska,
   wiec PIERWSZA pozycja okna juz sie nie miesci - a od czesci 3/N dok jest
   jedyna droga do jedenastu z dwunastu okien. Przy dwunastu oknach pasek
   chce 586 px i psuje sie na dwa sposoby, z ktorych zaden nie wyglada jak
   "dok jest za waski":
     * `justify-content: center` rozklada nadmiar NA OBIE STRONY, a `scrollLeft`
       nie schodzi ponizej zera: skroty stoja wtedy na x = -196 i nie da sie do
       nich dojechac zadnym gestem. Znikaja z pulpitu Terminal, Pliki i Edytor -
       czyli wszystko, czym otwiera sie cokolwiek. To jest szkoda TRWALA, a nie
       widok: przewijanie nie siega ujemnych wspolrzednych w zadnej przegladarce.
     * przegladarka mobilna nie tnie tego, co wystaje poza `width=device-width`,
       tylko POMNIEJSZA cala strone, zeby to pokazac - zmierzone na tym pulpicie:
       `innerWidth` rosnie z 390 na 710, czyli maleje czcionka w terminalu i cala
       reszta. Uwaga: te konkretne 710 px robi dzis GORNY PASEK (`#menubar`),
       ktory na 390 px nie miesci sie sam; dok stoi w tej samej kolejce ze swoimi
       586 px i przejmie objaw, gdy tamten zostanie naprawiony. Dlatego pasek ma
       trzymac swoj nadmiar U SIEBIE (`overflow-x`), a nie liczyc na to, ze ktos
       obok przepelnia strone mocniej.
   Stad `flex-start` zamiast `center`: nadmiar ma wychodzic wylacznie w prawo,
   bo tylko w te strone da sie przewinac. `justify-content: safe center`
   (wsparte w Chromium 151) robi dokladnie to i tylko wtedy, gdy trzeba - ale
   przegladarka, ktora go nie zna, odrzuca deklaracje i zostaje przy `center`
   z reguly wyzej, czyli przy TYM WLASNIE bledzie. Awaryjne zachowanie brzmi
   „skroty nie do wywolania na iPhonie, ktorego nie mamy w przyrzadzie" i to
   przesadza: wyrownanie jest jedno, zawsze to samo, po obu stronach progu
   przetestowane. */
[data-uklad="waski"] #dock {
  justify-content: flex-start;
  overflow-x: auto;
  /* Bez tego przewijanie doku, ktore doszlo do konca, przechodzi na strone -
     a poziomy gest na krawedzi telefonu znaczy "wstecz", czyli wyjscie
     z pulpitu w chwili, gdy czlowiek szuka ostatniej zakladki. */
  overscroll-behavior-x: contain;
}
/* Pozycje przestaja sie kurczyc. Domyslne `flex-shrink: 1` nie chroni przed
   niczym: `.dok-okno` ma `overflow: hidden`, wiec jego minimalny rozmiar to
   ZERO i zmierzone na telefonie pozycje mialy po 30 px - sam padding, tytul
   przyciety do pustego miejsca. Dwanascie nierozroznialnych prostokatow to
   nie jest zmieszczony dok, to jest dok bez informacji; do tego 30 px lezy
   ponizej celu dotykowego, ktorego pilnuje 9.4. */
[data-uklad="waski"] #dock > * { flex: 0 0 auto; }
/* Za to sufit szerokosci schodzi z 20ch na 12ch: przy 390 px pozycja 20ch
   zjada polowe paska, wiec widac dwie zakladki naraz. Przyciety tytul jest
   tu uczciwszy niz przewijanie o ekran na kazde przelaczenie. */
[data-uklad="waski"] .dok-okno { max-width: 12ch; }

/* Cel dotykowy w doku (etap 9.4, czesc 1/N). Liczby tu NIE MA: `--cel-dotyk`
   przychodzi z `shell/dotyk.js` razem z progiem szerokosci, tak jak `--cell-w`
   przychodzi z `shell/siatka.js`. Wpisane wprost 44 px byloby druga kopia
   polityki, ktorej nie da sie sprawdzic z kodu.

   Pytamy o `data-wskaznik`, a NIE o `data-uklad` - i to jest dokladnie
   odwrotnie niz przy mieszczeniu sie doku dwie reguly wyzej. Rozmiar celu
   jest pytaniem o PALEC: to samo 390 px w oknie przegladarki na biurku ma
   mysz, ktora trafia w 20 px bez pudla, a tablet w poziomie ma 1024 px
   i palec, ktory nie trafia. Zwiazanie tego z szerokoscia dawaloby cele
   dla palca tam, gdzie zadnego palca nie ma, i odbieraloby je tam, gdzie jest.

   `min-height`, a nie `height`: pozycja z dwuwierszowym tytulem ma prawo byc
   wyzsza, a `height` przycielaby jej tekst. Wysokosci zawartosci nie ruszamy -
   guzik centruje ja sam (domyslne zachowanie <button>), wiec dolozony
   `padding` bylby trzecia liczba pilnujaca tego samego.

   Zmierzone przed zmiana w emulacji 390x844: pozycje doku mialy 34 px
   wysokosci - wszystkie, skroty i zakladki okien tak samo. Dok, ktory od
   czesci 3/N etapu 9.3 jest jedyna droga do jedenastu z dwunastu okien,
   nie ma prawa wymagac celowania. */
[data-wskaznik="dotyk"] .dok-skrot,
[data-wskaznik="dotyk"] .dok-okno {
  min-height: var(--cel-dotyk);
  /* Takze w poprzek: zakladka okna o tytule "a" ma zostac celem, a nie
     paskiem szerokim na dwie litery. Sufit 12ch z reguly wyzej jest przy
     tym szerszy niz cel, wiec te dwie liczby nie walcza ze soba - a gdyby
     kiedys zaczely, CSS i tak przyznaje pierwszenstwo `min-width`. */
  min-width: var(--cel-dotyk);
}

[data-theme="layers"] #dock { backdrop-filter: blur(14px); border-radius: 16px; margin: 0 auto 10px; max-width: 100%; }
/* SZKLO "POSWIATY" 3/4 + PIGULKA DOKU (11.9). Cztery deklaracje w JEDNEJ
   regule i to nie jest upodobanie do zwiezlosci: `border-radius` bez
   `max-width` daje zaokraglony pas przez caly ekran, a `margin: 0 auto` bez
   `max-width` daje regresje ZMIERZONA w 9.10 - strona 434 px przy ekranie
   390 px. Mechanizm: `#desk` jest kolumna flex z domyslnym
   `align-items: stretch`, wiec `margin: 0 auto` na osi poprzecznej WYLACZA
   stretch i dok przestaje rosnac do rodzica, a zaczyna do TRESCI - dwanascie
   zakladek robi z niego pasek szerszy od telefonu. `max-width: 100%` wraca
   sufit, ktory stretch dawal za darmo. Rozdzielenie tych trzech linii miedzy
   dwie reguly zniosloby ten zwiazek z pola widzenia tego, kto edytuje jedna
   z nich - stad jedna linia, dokladnie jak w "warstwach" wyzej.

   UCZCIWIE: ta regula jest z "warstwami" TOZSAMA co do bajtu poza samym
   szklem i jest WPROST WYLACZONA z listy roznic R1-R5 w mapie. Wchodzi, bo
   referencja opisuje dok najprecyzyjniej ze wszystkich elementow ("wysrodkowany,
   w OSOBNYM pojemniku o duzym zaokragleniu") - to jest wiernosc kupiona za
   zero odroznienia i tak trzeba to nazwac, zamiast sprzedawac pigulke jako
   cecha motywu. 16 px, a nie `var(--radius)` (14): pojemnik ma byc
   zaokraglony MOCNIEJ niz pozycje, ktore w nim leza. */
[data-theme="glow"] #dock { backdrop-filter: var(--szklo); border-radius: 16px; margin: 0 auto 10px; max-width: 100%; }

/* --- Komunikat powloki (etap 3.5) -----------------------------------------
   Lezy NAD oknami i nie lapie klikniec: to jest zdanie do przeczytania, a nie
   okno dialogowe. Modalne pytanie „ok?" po odmowie limitu zabieraloby pulpit
   za kazdym razem, gdy czlowiek klika trzynaste okno. */
#komunikat {
  position: absolute;
  left: 50%;
  bottom: calc(var(--bar-h) + 34px);
  transform: translateX(-50%);
  max-width: min(60ch, 90%);
  margin: 0;
  padding: 8px 14px;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(0 0 0 / .35);
  pointer-events: none;
  z-index: 2;
}

/* --- Motyw "przyrzad": zero cieni (etap 7.2) -------------------------------
   Obietnica motywu brzmi "wlosowe linie ZAMIAST cieni", a cien zostawiony
   w jednym miejscu wystarczy, zeby ja zlamac: to wlasnie ten element wyglada
   wtedy jak wklejony z innego pulpitu. Reguly `box-shadow: none` przy `.okno`
   i przy liscie menu zostaja tam, gdzie stoja (przy swoich elementach), a tu
   domyka je ostatni cien powloki - linia komunikatu. Cienie WEWNETRZNE
   (`inset`) sa czym innym i zostaja: to nie glebia, tylko kreska sygnalowa
   pod aktywna pozycja doku i przy wierszu z kursorem. */
[data-theme="instrument"] #komunikat { box-shadow: none; }

/* --- Motyw "Nosna": zero cieni (etap 10.6) ---------------------------------
   Ta sama decyzja, co w "przyrzadzie", ale z innej przyczyny: panel aparatury
   jest PLASKA plyta, a cienie bazy sa literalna czernia (`rgb(0 0 0 / .35)`),
   ktorej ten motyw nie ma w palecie - zostawiony cien maluje wiec kolor spoza
   motywu. Trzy reguly stoja przy swoich elementach, a nie w jednej zbiorczej
   liscie selektorow: lista rozjezdza sie z pierwszym nowym cieniem, ktorego
   nikt do niej nie dopisze, a reguly przy elemencie widac przy jego edycji.

   Cienie WEWNETRZNE (`inset`) zostaja i tu, i tam: to nie glebia, tylko
   kreska sygnalowa (dok, wiersz z kursorem, pasek okna zmaksymalizowanego
   nizej). `.okno--szklo` i `data-glebia="0..3"` przychodza z `shell/menedzer.js`
   w KAZDYM motywie i tutaj swiadomie nie robia nic - rozmycie i wielostopniowa
   poswiata to warstwy kompozytora na okno, czyli dokladnie ten koszt, ktorego
   zabrania wymog nadrzedny mapy. */
[data-theme="carrier"] #komunikat { box-shadow: none; }
/* CIECIE "POSWIATY" (11.6). Trzecia z tych samych przyczyn: czarny cien bazy
   jest tu jedynym kolorem spoza palety, a poswiaty komunikat nie dostaje,
   bo nie ma rangi w stosie okien - powod pelny stoi przy liscie menu wyzej.
   Rozmycia ten element NIE dostaje: to zdanie znikajace samo po kilku
   sekundach, a szklo kosztuje przez caly czas zycia warstwy, nie przez czas
   patrzenia. Cztery szklane powierzchnie to sufit i komunikat jest poza nim. */
[data-theme="glow"] #komunikat { box-shadow: none; }

/* --- Lista wirtualizowana (etap 4.2) --------------------------------------
   Wiersze sa POZYCJONOWANE BEZWZGLEDNIE i przesuwane `transform`, a nie
   ulozone jeden pod drugim: pozycja wiersza N nie moze zalezec od tego, ile
   wierszy narysowano przed nim, bo w widoku jest ich najwyzej ~60 z 50 000.
   Wysokosc wiersza podaje modul (--lw-wiersz-h) - CSS i JS musza sie tu
   zgadzac co do piksela, wiec jest jedno zrodlo tej liczby, w kodzie. */
.lw {
  position: relative;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  outline: none;
}
.lw:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.lw-plotno { position: relative; width: 100%; }
.lw-wiersz {
  position: absolute;
  left: 0; top: 0; right: 0;
  height: var(--lw-wiersz-h, 22px);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 10px;
  font: 12px/1 var(--font-mono);
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  /* Wiersz nie wplywa na uklad poza soba - przegladarka nie musi liczyc
     calej listy, zeby przerysowac jeden wiersz przy przewijaniu. */
  contain: layout paint style;
}
.lw-wiersz--kursor { background: var(--surface); box-shadow: inset 2px 0 0 var(--accent); }
/* Wiersz, ktorego strona jest jeszcze w drodze. Ma byc widac, ze to "nie wiem
   jeszcze", a nie pusty plik - inaczej szybkie przewijanie wyglada jak utrata
   danych. */
.lw-wiersz--pusty { color: var(--fg-dim); }

/* --- Aplikacja "O pulpicie" (etap 3.5) ------------------------------------ */
.apka-o { padding: 14px 16px; }
.apka-o h1 { font: 600 16px/1.3 var(--font-ui); margin: 0 0 8px; }
.apka-o p { margin: 0 0 10px; color: var(--fg-dim); }
.apka-o dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  margin: 0 0 12px;
  font-family: var(--font-mono);
  font-size: 12px;
}
.apka-o dt { color: var(--fg-dim); }
.apka-o dd { margin: 0; color: var(--fg); }
.apka-o-linia { font-family: var(--font-mono); font-size: 12px; }
.apka-o button {
  font: inherit;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 5px 12px;
  cursor: pointer;
}
.apka-o button:hover:not(:disabled) { border-color: var(--accent); }
.apka-o button:disabled { color: var(--fg-dim); cursor: default; }

/* --- Menedzer plikow (etap 4.7) -------------------------------------------
   Kolumna o WYSOKOSCI CIALA OKNA, nie o wysokosci tresci: lista wirtualna
   potrzebuje `clientHeight` scrollera, zeby wiedziec, ile wierszy narysowac.
   Bez `min-height: 0` na srodkowym pasie flexbox rozdalby okno do wysokosci
   calej listy i wirtualizacja z 4.2 przestalaby cokolwiek znaczyc. */
.pliki { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.pliki-lista { flex: 1; min-height: 0; }
.pliki-pasek, .podglad-pasek {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
}
.pliki-sciezka {
  flex: 1;
  min-width: 0;
  font: 12px/1 var(--font-mono);
  color: var(--fg-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pliki-guzik, .podglad-guzik {
  font: inherit;
  font-size: 12px;
  line-height: 1;
  color: var(--fg-dim);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 4px 9px;
  cursor: pointer;
  text-decoration: none;
}
.pliki-guzik:hover:not(:disabled),
.podglad-guzik:hover { color: var(--fg); border-color: var(--accent); }
.pliki-guzik:disabled { color: var(--fg-dim); opacity: .45; cursor: default; }
.pliki-guzik:focus-visible,
.podglad-guzik:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pliki-linia, .podglad-linia {
  margin: 0;
  padding: 4px 10px;
  font: 11px/1.4 var(--font-mono);
  color: var(--fg-dim);
  border-top: 1px solid var(--line);
}
.pliki-nazwa { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pliki-meta { color: var(--fg-dim); font-variant-numeric: tabular-nums; }
.lw-wiersz[data-typ="katalog"] .pliki-nazwa { color: var(--accent); }
.lw-wiersz[data-typ="dowiazanie"] .pliki-nazwa { font-style: italic; }

/* --- Podglad pliku (domkniecie etapu 4.7) ---------------------------------
   Ta sama kolumna co menedzer plikow: pasek u gory, tresc rozciagnieta na
   reszte, jedna linia komunikatu na dole. Przewija sie TRESC, nie okno -
   `min-height: 0` na srodkowym pasie jest tu warunkiem, a nie ozdoba. */
.podglad { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.podglad-tresc { flex: 1; min-height: 0; overflow: auto; }
.podglad-nazwa {
  flex: 1;
  min-width: 0;
  font: 12px/1 var(--font-mono);
  color: var(--fg-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* `pre-wrap` zamiast `pre`: wiersz na 4000 znakow (minified JS, jeden dlugi
   log) rozdmuchalby szerokosc plotna i dolozyl poziomy pasek na cale okno. */
.podglad-tekst {
  margin: 0;
  padding: 8px 10px;
  font: 12px/1.5 var(--font-mono);
  color: var(--fg);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* Miniatura ma 256 px dluzszego boku i tyle ma zajmowac: `max-width` chroni
   waskie okno, ale nie rozciagamy jej w gore - rozmyty obrazek udawalby
   podglad w pelnej rozdzielczosci, ktorego z rozmyslu nie ma. */
.podglad-obraz {
  display: block;
  margin: 12px auto;
  max-width: calc(100% - 24px);
  height: auto;
}

/* --- Ekran logowania (etap 2.6) -------------------------------------------
   `hidden` musi wygrywac z `display:flex` na #desk, inaczej pulpit mignalby
   pod ekranem logowania zanim JS zdazy zapytac o sesje. */
[hidden] { display: none !important; }

.brama {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 1000;
}
.brama:empty { display: none; }

.brama-karta {
  width: min(360px, 100%);
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 26px 24px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
[data-theme="layers"] .brama-karta {
  backdrop-filter: blur(18px);
  box-shadow: 0 1px 2px rgb(0 0 0 / .3), 0 12px 40px rgb(0 0 0 / .45);
}
/* SZKLO "POSWIATY" 4/4. Tu widac roznice miedzy dwoma motywami w jednej
   parze regul: sasiad dokłada karcie DWA czarne cienie, "poswiata" nie
   dokłada ani jednego.

   `box-shadow: none` NIE JEST TU WPISANE, wbrew literalnemu brzmieniu 11.6,
   i to jest swiadome: baza `.brama-karta` (wyzej) nie maluje zadnego cienia,
   wiec ta deklaracja byla by martwa - gasilaby cien, ktorego nie ma. Zapadka
   nie znika, tylko przenosi sie tam, gdzie i tak musi stac: dowod (c)
   w `tests/test-poswiata.py` czyta ZLOZONY `box-shadow` tej karty i zada
   `none`. Cien dopisany kiedykolwiek do bazy zapali wiec ten test - a martwa
   deklaracja przepuscilaby go w ciszy, bo cien bylby zgaszony i nikt by sie
   nie dowiedzial, ze powstal.

   UWAGA WYKONAWCZA (mapa, 11.6): ta karta jest w "poswiacie" osiagalna
   WYLACZNIE po wylogowaniu w tej samej karcie przegladarki. `index.html:2`
   trzyma `data-theme="grid"` statycznie, bo ekran logowania nie ma sesji,
   z ktorej mozna by odczytac motyw. Test tej powierzchni musi wiec przejsc
   droga logowanie -> zmiana motywu -> wylogowanie; inaczej mierzy motyw
   "siatka znakow" i nie wie o tym. */
[data-theme="glow"] .brama-karta { backdrop-filter: var(--szklo); }

.brama-tytul {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--accent);
}
.brama-podtytul { margin: -6px 0 6px; color: var(--fg-dim); font-size: 12px; }

.brama-pole { display: flex; flex-direction: column; gap: 5px; font-size: 12px; }
.brama-pole > span { color: var(--fg-dim); }
.brama-pole input {
  font: 13px/1.4 var(--font-mono);
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 10px;
}
.brama-pole input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.brama-pole input:read-only { opacity: .6; }

.brama-wyslij {
  margin-top: 6px;
  font: inherit;
  color: var(--bg);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 9px 14px;
  cursor: pointer;
}
.brama-wyslij:disabled { cursor: progress; opacity: .65; }
.brama-wyslij:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }

/* Cel dotykowy bramy (dlug 9.7). Zmierzone w 390x844, w kazdym motywie:
   guzik 40 px, pole 36 px - a palec potrzebuje 44. Ekran "brak polaczenia"
   pozycza guzik od logowania, wiec jedna regula stoi za dwa ekrany. Pole
   idzie z guzikiem: ten sam palec musi wpisac haslo, zeby bylo co wyslac.
   `min-height`, nie `padding` - oba centruja tresc same; bez `min-width`
   (inaczej niz dok wyzej), bo oba sa juz na szerokosc karty. Wskaznik,
   nie szerokosc - powod tez przy doku. */
[data-wskaznik="dotyk"] .brama-wyslij,
[data-wskaznik="dotyk"] .brama-pole input { min-height: var(--cel-dotyk); }

/* Pusty komunikat nie rezerwuje miejsca - karta nie skacze przy pierwszym bledzie,
   bo :empty chowa element w calosci, a nie tylko jego tresc. */
.brama-blad { margin: 2px 0 0; font-size: 12px; color: var(--blad); }
.brama-blad:empty { display: none; }

.mb-user { color: var(--fg-dim); font-family: var(--font-mono); font-size: 11px; }
.mb-wyloguj {
  font: inherit;
  font-size: 11px;
  color: var(--fg-dim);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2px 8px;
  cursor: pointer;
}
.mb-wyloguj:hover:not(:disabled) { color: var(--fg); border-color: var(--accent); }
.mb-wyloguj:disabled { opacity: .5; cursor: progress; }
.mb-wyloguj:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Cel dotykowy w GORNYM PASKU (etap 9.4, czesc 3/N). Ta sama liczba i ten sam
   selektor, co w czesciach 1/N i 2/N. Trzecie i ostatnie miejsce w powloce,
   gdzie stoja guziki.

   Zmierzone przed zmiana w emulacji 390x844: `.mb-menu-guzik` 56x24 px,
   `.mb-wyloguj` 60x23 px. Poziomo obydwa sa wieksze od celu, wiec chybia sie
   tylko w PIONIE - i to jest gorszy przypadek niz wyglada, bo pasek stoi przy
   samej gornej krawedzi ekranu. Palec siegajacy tam ma nad soba wciecie
   telefonu i pasek stanu systemu, a pod soba `#windows`: chybienie w gore
   trafia w system operacyjny, w dol - w okno. Guzik `pulpit` jest przy tym
   JEDYNA droga do menu, czyli do otwarcia czegokolwiek poza czterema skrotami
   doku i do przelaczenia motywu.

   Pasek musi urosnac RAZEM z nimi (ten sam rachunek, co w 2/N), ale nie da
   sie tego zrobic `min-height`: `#menubar` ma `height` i `align-items:
   center`, wiec element wyzszy od paska nadal by z niego wystawal - rosnac
   ma sam pasek. Stad WLASNA zmienna, a nie
   `min-height` doklejone z boku: `--pasek-gorny-h` jest jednym miejscem, ktore
   odpowiada na pytanie "gdzie zaczyna sie warstwa okien", i czytaja ja takze
   siatka w tle (`background-position`) oraz sufit rozwinietego menu
   (`max-height`). Bez tego pasek urosly do 45 px zostawilby siatke motywu
   "siatka znakow" narysowana 17 px za wysoko, a listy menu nie da sie wtedy
   dosunac do dolnej krawedzi.

   `--bar-h` zostaje NIETKNIETE i to jest ta sama decyzja, co w czesci 2/N,
   tylko z drugiej strony: tamta zmienna opisuje pasek TYTULU okna (i jego
   `min-height` na dotyku), a `#menubar` ma wlasny rachunek, bo jest jeden
   na pulpit i nie powtarza sie dwanascie razy. Zlanie ich w jedno znaczyloby,
   ze motyw zmieniajacy grubosc ramki okna przesuwa pasek gorny.

   `+ 1px` to ten sam rachunek border-box, co w ramce okna (2/N): pasek ma
   `border-bottom`, wiec bez tego cel wystaje z niego o pol piksela.

   TOKEN WSPOLNY, dokladnie jak `--ruch*`, i z ta sama pulapka o dwoch koncach:
   przy rownej specyficznosci rozstrzyga POLOZENIE, wiec motyw stojacy NAD tym
   wierszem (tak stoja dzis wszystkie) przegra go po cichu, a dopisany POD nim
   pobije go KOLEJNOSCIA i zabierze `#menubar`, siatce w tle i sufitowi menu
   ich jedno zrodlo prawdy. Zmierzone `tests/wd-wspolne.py`: sygnal `99px`
   spod arkusza podnosi pasek z 26-30 px na 99 w kazdym z pieciu motywow,
   ten sam sygnal znad arkusza nie rusza go wcale. Motyw zmienia `--bar-h`
   (grubosc paska TYTULU okna) i tyle - rachunek nizej przelicza sie sam. */
:root { --pasek-gorny-h: calc(var(--bar-h) + env(safe-area-inset-top)); }
[data-wskaznik="dotyk"] {
  --pasek-gorny-h: calc(var(--cel-dotyk) + 1px + env(safe-area-inset-top));
}
[data-wskaznik="dotyk"] .mb-menu-guzik,
[data-wskaznik="dotyk"] .mb-wyloguj {
  min-width: var(--cel-dotyk);
  min-height: var(--cel-dotyk);
}

/* Cel dotykowy w POZYCJACH ROZWINIETEGO MENU (etap 9.4, czesc 4/N). Ta sama
   liczba i ten sam selektor, co w czesciach 1/N-3/N - ale to JEST osobny cel,
   nie dokonczenie guzika, ktory te liste otwiera. Guzik `pulpit` jest jeden i stoi obok pustego paska; pozycji jest
   dziesiec, jedna pod druga, stykajacych sie bokami. Chybienie o pol opuszka
   nie jest wiec nietrafieniem, tylko trafieniem W SASIADA - a sasiadem
   `Zminimalizuj` jest `Zamknij`, czyli ten sam rachunek, co w ramce okna:
   jedyna nieodwracalna rzecz w tej powloce lezy 0 px od odwracalnej.
   Zmierzone przed zmiana w emulacji 390x844: pozycje maja 26 px wysokosci
   i 210 px szerokosci, wiec chybia sie WYLACZNIE w pionie. Menu jest przy tym
   jedyna droga do uruchomienia czegokolwiek poza czterema skrotami doku.

   Rosnie `line-height`, a nie padding, i nie jest to kwestia gustu: pozycja
   skraca dlugie tytuly wielokropkiem (tytul okna bywa sciezka pliku),
   a wysrodkowanie tresci przez `display: flex` zabiera jej to po cichu -
   `ellipsis` nie dziala na anonimowej pozycji flexa, wiec napis zostalby
   uciety w pol litery, bez znaku, ze cos jeszcze jest. Wiersz wysoki na
   `--cel-dotyk` przy zerowym paddingu pionowym daje ten sam cel, sam srodkuje
   tekst i zostawia blok blokiem. Padding POZIOMY nietkniety: niesie tez
   miejsce na ptaszek motywu (`padding-left: 22px`).
   `min-height` obok niego nie jest zapasem - to ono niesie POLITYKE i o nie
   pyta zapadka; cel oparty na samej tresci znikalby przy zmianie czcionki.

   Naglowki grup (`.mb-menu-grupa`) zostaja male, bo to napisy, nie cele -
   nie reaguja na dotkniecie, wiec urosniete zabralyby miejsce za nic. Za to
   lista rosnie o ~180 px i jej sufit (`max-height` od `--pasek-gorny-h`)
   przestaje byc formalnoscia: pilnuje go osobna zapadka, bo cel wypchniety
   pod krawedz ekranu jest gorszy niz cel za maly - w tamten trafia sie za
   trzecim razem. */
[data-wskaznik="dotyk"] .mb-menu-poz {
  min-height: var(--cel-dotyk);
  padding-top: 0;
  padding-bottom: 0;
  line-height: var(--cel-dotyk);
}

/* --- Okno (etapy 3.2 i 3.3) ------------------------------------------------
   Rozmiarow minimalnych NIE ma tutaj celowo: MIN_SZER/MIN_WYS zyja w okno.js,
   bo to gest je liczy. Powtorzenie ich w CSS dawaloby dwa zrodla prawdy i
   uklad, ktory nie zgadza sie z tym, co okno o sobie mysli. */
.okno {
  position: absolute;
  top: 0; left: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface-lita);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 1px 2px rgb(0 0 0 / .3), 0 10px 30px rgb(0 0 0 / .38);
  overflow: hidden;
}
/* --- Szyba motywu "warstwy" (etap 7.3) -------------------------------------
   `backdrop-filter` jest tu na klasie, a nie na `.okno`, bo kosztuje: kompozytor
   trzyma dla kazdej szyby rozmyta kopie tego, co pod nia lezy. Klase przyznaje
   `LIMIT_SZKLA` oknom najwyzej w stosie menedzer okien (shell/menedzer.js) -
   arkusz nie ma jak policzyc okien, a limit, ktorego nikt nie liczy, nie jest
   limitem. Reszta okien zostaje przy `--surface-lita` z reguly wyzej.

   To takze JEDYNE miejsce, w ktorym motyw "warstwy" wraca do przezroczystego
   `--surface`: szklo bez rozmycia pod spodem to nie glebia, tylko nieczytelny
   tekst na cudzym oknie. */
[data-theme="layers"] .okno--szklo {
  background: var(--surface);
  backdrop-filter: blur(18px);
}

/* --- Cienie warstwowe motywu "warstwy" (etap 7.3, czesc 2/2) ----------------
   Cien wyzej (`.okno`) jest JEDEN dla wszystkich okien, wiec dwanascie okien
   wisi na tej samej wysokosci - a pulpit, na ktorym wszystko wisi rowno, nie
   ma warstw, tylko obwodki. Tutaj wysokosc bierze sie z miejsca w stosie:
   `data-glebia` przydziela menedzer okien (shell/menedzer.js) tym samym
   kluczem `okno.kolejnosc`, ktorym przydziela szybe. Arkusz nie ma jak
   policzyc okien, wiec nie ma jak zrobic tego sam.

   Kazdy stopien to TRZY nalozone cienie, nie jeden rozmyty:
     * styk (0 1px 2px) - mowi, ze okno dotyka pulpitu; jest na kazdym stopniu,
       takze na dnie, bo bez niego okno wyglada jak wyciete i naklejone;
     * otoczenie (srednie rozmycie) - miekkie swiatlo dookola;
     * swiatlo glowne (duze rozmycie, duze przesuniecie) - to ono niesie
       wysokosc i to ono kosztuje, wiec schodzi ze stopniami do zera.
   Jeden cien o rosnacym rozmyciu dalby te sama "gleboksc" na kazdym stopniu,
   tylko bardziej rozmyta - a glebia to kontrast miedzy stykiem a poswiata,
   nie sam promien.

   Ostatni stopien (dno) ma sam styk: przy dwunastu oknach dziewiec z nich
   NIE maluje glebokiego rozmycia. To jest cala oszczednosc - patrz
   `POZIOMY_CIENIA`. */
[data-theme="layers"] .okno[data-glebia="0"] {
  box-shadow:
    0 1px 2px rgb(0 0 0 / .30),
    0 8px 16px rgb(0 0 0 / .34),
    0 24px 52px rgb(0 0 0 / .50);
}
[data-theme="layers"] .okno[data-glebia="1"] {
  box-shadow:
    0 1px 2px rgb(0 0 0 / .28),
    0 5px 10px rgb(0 0 0 / .30),
    0 14px 30px rgb(0 0 0 / .40);
}
[data-theme="layers"] .okno[data-glebia="2"] {
  box-shadow:
    0 1px 2px rgb(0 0 0 / .26),
    0 3px 6px rgb(0 0 0 / .26),
    0 7px 15px rgb(0 0 0 / .32);
}
[data-theme="layers"] .okno[data-glebia="3"] {
  box-shadow: 0 1px 2px rgb(0 0 0 / .24);
}
/* Zmaksymalizowane okno nie wisi nad niczym - jego krawedzie sa krawedziami
   ekranu, wiec cien malowalby sie poza widokiem i kosztowal za nic.

   SPECYFICZNOSC, NIE KOLEJNOSC - pelne uzasadnienie u "poswiaty" nizej,
   przy tym samym wyjatku. W skrocie: reguly glebi maja (0,3,0), wiec wyjatek
   pisany `.okno.okno--maks` mialby tyle samo i wygrywal wylacznie tym, ze
   stoi ZA nimi w pliku. Stad `[data-glebia]` tutaj: (0,4,0). Zapadka:
   `test_wyjatek_bije_waga_a_nie_kolejnoscia_w_pliku` w test-warstwy.py. */
[data-theme="layers"] .okno[data-glebia].okno--maks { box-shadow: none; }
/* Okno w ukladzie waskim ma krawedzie ekranu z tej samej przyczyny, wiec
   i tak samo nie ma czego cieniowac. Osobny selektor, a nie dopisanie klasy
   `okno--maks` w kodzie: to sa dwa rozne stany (patrz `ustawPelnyEkran`
   w okno.js) i zlanie ich znaczyloby okno, ktore po powrocie na biurko
   zostaje zmaksymalizowane, bo ktos je kiedys ogladal na telefonie. */
[data-theme="layers"] .okno[data-glebia].okno--pelny { box-shadow: none; }

/* --- Glebia motywu "poswiata": halo zamiast cienia (etap 11.5) -------------
   Ta sama dana wejsciowa, co u sasiada wyzej - `data-glebia` z
   `shell/menedzer.js` - i ROZLACZNA wlasciwosc wyjsciowa. "Warstwy" mowia,
   jak WYSOKO okno wisi: przesunieciem czarnego cienia w dol, rosnacym ze
   stopniem. "Poswiata" mowi, KTORE okno jest najblizej: przesuniecie jest
   ZERO na obu osiach we WSZYSTKICH czterech stopniach, a niesie je malejaca
   ALFA przy stalym srodku. Ani jedna warstwa nie jest czarna.

   To jest roznica R1/R2 z mapy i jedyna, ktora rozstrzyga sonda pikselowa
   NAD gorna krawedzia okna: wszystkie przesuniecia "warstw" sa dodatnie w Y,
   wiec nad oknem nie ma tam ich swiatla glownego - jest tylko przyciemnienie
   ogonem rozmycia. Halo jest centryczne, wiec nad krawedzia JASNIEJE.
   Zmierzone na zrzucie (`tests/test-poswiata.py`, sonda 6 px nad krawedzia,
   kanal B wobec tla pulpitu tego samego motywu): glow +12 (10, 19, 32 wobec
   7, 11, 20), layers -12 (49, 28, 96 wobec 56, 32, 108). Ta sama sonda, dwa
   znaki - i to jest cala roznica R2.

   Kazdy stopien to PARA, nie jeden rozmyty cien:
     * pierscien `0 0 0 1px` w chlodnym blekicie - stoi na KAZDYM stopniu,
       takze na dnie, bo bez niego okno wyglada jak wyciete i naklejone; to
       jest odpowiednik "styku" (`0 1px 2px`) u sasiada. Alfa maleje
       .24 -> .18 -> .13 -> .09;
     * halo `0 0 Npx` w lazurze - to ono niesie RANGE W STOSIE i to ono
       kosztuje, wiec schodzi ze stopniami DO ZERA: 34px/.15, 22px/.10,
       12px/.06, brak. Przy dwunastu oknach dziewiec z nich nie maluje
       ZADNEGO rozmycia - ten sam podzial oszczednosciowy co w "warstwach",
       inny rysunek.

   Wartosci sa LITERALAMI, a nie `var(--accent)` i `var(--line)`, i tak ma
   zostac: to, co zmienia sie ze stopniem, jest wlasnie ALFA, a oba tokeny
   niosa alfe WLASNA, policzona na czym innym (`--line` ustawil kontrast
   obwodki wskazania, patrz komentarz przy tokenie). Zlozenie ich tutaj
   wymagaloby `color-mix` na kazdym stopniu, czyli czterech miejsc zamiast
   czterech liczb.

   MUSI to byc `box-shadow`. `filter: drop-shadow` i `::after` z
   `opacity < 1` daja ten sam obraz, ale KAZDE z nich zaklada kontekst
   ukladania na warstwie okien - czyli wraca do usterki z 2026-09-01, w
   ktorej `elementFromPoint` oddawal MAIN zamiast elementu pod wskaznikiem.
   Halo siedzi na SAMEJ ramce okna i nigdzie indziej w srodku: `.okno` ma
   `overflow: hidden`, wiec poswiata dopisana czemukolwiek WEWNATRZ okna
   zostalaby przycieta do ramki i zamiast swiatla dalaby prostokat. */
[data-theme="glow"] .okno[data-glebia="0"] {
  box-shadow:
    0 0 0 1px rgb(90 140 200 / .24),
    0 0 34px rgb(74 158 255 / .15);
}
[data-theme="glow"] .okno[data-glebia="1"] {
  box-shadow:
    0 0 0 1px rgb(90 140 200 / .18),
    0 0 22px rgb(74 158 255 / .10);
}
[data-theme="glow"] .okno[data-glebia="2"] {
  box-shadow:
    0 0 0 1px rgb(90 140 200 / .13),
    0 0 12px rgb(74 158 255 / .06);
}
/* Dno stosu: sam pierscien, ZERO rozmycia - tak samo jak stopien 3 wyzej. */
[data-theme="glow"] .okno[data-glebia="3"] {
  box-shadow: 0 0 0 1px rgb(90 140 200 / .09);
}
/* Okno zmaksymalizowane i okno w ukladzie waskim maja krawedzie ekranu, wiec
   34-pikselowe halo malowaloby sie POZA widokiem - a na dotyku KAZDE okno
   jest zmaksymalizowane, czyli byloby to za darmo i zawsze, wprost wbrew
   argumentowi o koszcie z R3.

   SPECYFICZNOSC, NIE KOLEJNOSC. `[data-theme="glow"] .okno[data-glebia="0"]`
   to (0,3,0) - atrybut liczy sie jak klasa - i `[data-theme="glow"]
   .okno.okno--maks` mialoby DOKLADNIE TYLE SAMO. Wyjatek wygrywalby wtedy
   samym polozeniem w pliku, a to znaczy, ze przestawienie regul (albo
   dopisanie stopnia nizej) po cichu przywraca halo. Dlatego wyjatek stoi na
   TYM SAMYM kluczu, co reguly glebi, plus klasa stanu: (0,4,0). Sprawdza to
   mutacja przez CSSOM w `tests/test-poswiata.py` - test dokleja kopie reguly
   glebi na KONIEC arkusza i zada, zeby `box-shadow` dalej byl `none`.

   Dwa selektory, a nie jeden z dwiema klasami - z tego samego powodu, co
   u "warstw": `okno--maks` i `okno--pelny` to dwa rozne stany (patrz
   `ustawPelnyEkran` w `okno.js`) i zlanie ich znaczyloby okno, ktore po
   powrocie na biurko zostaje zmaksymalizowane. */
[data-theme="glow"] .okno[data-glebia].okno--maks { box-shadow: none; }
[data-theme="glow"] .okno[data-glebia].okno--pelny { box-shadow: none; }

[data-theme="instrument"] .okno { box-shadow: none; }
[data-theme="carrier"] .okno { box-shadow: none; }
.okno--aktywne { border-color: var(--accent); }
.okno--maks { border-radius: 0; }

/* Okno zmaksymalizowane nie ma widocznej obwodki - jego krawedzie sa
   krawedziami warstwy okien - wiec `.okno--aktywne { border-color: --accent }`
   z bazy nie ma sie gdzie pokazac i pulpit przestaje mowic, ktore okno jest
   czynne. Znacznik wraca jako kreska WEWNETRZNA przy lewej krawedzi paska
   tytulu, tym samym jezykiem, co `.lw-wiersz--kursor` i zakladka doku.

   Tylko na oknie zmaksymalizowanym i TYLKO na czynnym. Mapa pisze ten selektor
   samym `.okno--maks`, ale zmaksymalizowane okno da sie zostawic pod spodem
   (klasa zostaje, ognisko idzie gdzie indziej) - bursztyn, jedyna lampa
   sygnalowa motywu, mowilby wtedy "patrz tutaj" o oknie, ktore wlasnie
   przestalo byc czynne. Znacznik ma powtarzac obwodke, a nie sam fakt
   maksymalizacji. */
[data-theme="carrier"] .okno--maks.okno--aktywne .okno-pasek {
  box-shadow: inset 2px 0 0 var(--accent);
}

/* --- Okno w ukladzie waskim (etap 9.3, czesc 2/N) --------------------------
   Ponizej progu z `shell/dotyk.js` okno wypelnia warstwe: geometrie pisze
   `okno.js` (`#zastosujGeometrie`), bo to ona jest zrodlem prawdy o polozeniu.
   Arkuszowi zostaje to, czego geometria nie opisuje - i ani jednej reguly
   wiecej, zeby nie bylo drugiego miejsca decydujacego o rozmiarze okna.

   Klasa idzie z KODU (`Okno.ustawPelnyEkran`), a nie z `[data-uklad="waski"]
   .okno`, choc selektor bylby krotszy. Powod jest ten sam, dla ktorego uchwyty
   znikaja tutaj, a gest gasnie tam: gdyby arkusz chowal uchwyty sam, a kod
   o ukladzie nie wiedzial, przeciaganie dzialaloby dalej - po niewidocznym
   uchwycie i po pasku tytulu. Jedna decyzja, jedno zrodlo. */
.okno--pelny { border-radius: 0; }
/* Pasek tytulu nie jest juz uchwytem, wiec nie ma prawa wygladac jak uchwyt.
   Na dotyku kursora nie widac, ale prog ma dwie strony: waskie okno
   przegladarki na biurku ma mysz i to ona pokazywalaby "chwyc mnie" nad
   czyms, czego nie da sie chwycic. */
.okno--pelny .okno-pasek { cursor: default; }
/* Maksymalizacja okna, ktore zajmuje caly ekran, nie ma co zabrac ani co
   oddac. Guzik zostawiony na miejscu byl by jedynym przyciskiem powloki,
   ktory na telefonie nic nie robi - a na 390 px kazde 30 px paska tytulu
   jest miejscem, ktorego brakuje samemu tytulowi. */
.okno--pelny .okno-maks { display: none; }

/* --- Pojawienie sie okna (etap 7.5, czesc 1/2) -----------------------------
   Klase zaklada i zdejmuje `shell/ruch.js` (przez `Okno.otworz`), a nie arkusz:
   to kod decyduje, CZY ruch sie odbywa, i to on ma potem po sobie posprzatac.
   Animacja zapisana na `.okno` na stale grala by takze wtedy, gdy czlowiek
   poprosil o mniej ruchu - a wtedy caly ten etap byloby widac tylko w CSS.

   Animowana jest SAMA PRZEZROCZYSTOSC, choc okno wjezdzajace ze skalowaniem
   wygladaloby zywiej. Powod nie jest estetyczny: `transform` na `.okno` nalezy
   do GEOMETRII - to nim okno stoi tam, gdzie stoi (`#zastosujGeometrie`),
   i to nim jest przeciagane. Druga rzecz piszaca po tej samej wlasciwosci
   znaczy okno, ktore w chwili oddania jej z powrotem podskakuje - dokladnie
   ten sam blad, przed ktorym dok broni sie regula `.dok--lupa ... :hover
   { transform: none }`. Osobne `scale` z ostatnich lat CSS tez nie pomaga:
   sklada sie z `transform` jako macierz, wiec skaluje takze PRZESUNIECIE okna
   i okno oddalone od rogu pulpitu wjezdzaloby bokiem.

   Zaczynamy od .35, a nie od zera: okno, ktore pojawia sie z niczego, przez
   pierwsze klatki jest niewidoczne i pulpit wyglada, jakby klikniecie nie
   zadzialalo. */
@keyframes okno-wejscie {
  from { opacity: .35; }
  to   { opacity: 1; }
}
.okno--wejscie { animation: okno-wejscie var(--ruch) var(--ruch-krzywa); }

/* --- Znikniecie okna (etap 7.5, czesc 2/2) ---------------------------------
   Nosi to NIE ramka zamknietego okna, tylko jej duch: plytki klon, ktory
   `shell/okno.js` zostawia na jej miejscu (`pozegnaj`). Stad ta jedna regula
   wystarcza na PIEC motywow ze schematu - i ta liczba jest PRZELICZONA
   (11.15, `tests/wd-duch.py`), a nie podbita o jeden po dopisaniu "Poswiaty".
   Zmierzone `getComputedStyle` zywej ramki tuz przed zamknieciem i ducha
   zaraz po nim, w kazdym motywie ze schematu: ramka, promien, cien, rozmycie
   i tlo wychodza IDENTYCZNE w calej piatce, a prostokat co do piksela.

   PRZYCZYNA JEST SZERSZA NIZ "DUCH MA KLASE `.okno`" i dopiero szersza wersja
   tlumaczy, czemu ta liczba rosnie sama. `cloneNode(false)` niesie WSZYSTKIE
   klasy i WSZYSTKIE atrybuty, a chrom okna wisi w tym arkuszu na trzech
   roznych nosnikach: na klasie `.okno` (ramka, promien, tlo), na klasie
   `.okno--szklo` (rozmycie "warstw") i na ATRYBUCIE `data-glebia` (cien
   warstwowy "warstw" oraz pierscien i halo "Poswiaty"). Kontrola dodatnia
   z tamtego pomiaru: duch pozbawiony `data-glebia` i `okno--szklo` gubi chrom
   wylacznie w "warstwach" (cien, rozmycie, tlo) i w "Poswiacie" (pierscien
   i halo) - w pozostalych trzech nie zmienia sie nic, bo ich cien od glebi
   nie zalezy. Pierwszy motyw, ktory zawiesi chrom na czyms, czego plytki klon
   nie niesie - na dziecku ramki albo na sasiedztwie w drzewie - bedzie tym,
   przy ktorym liczba przestanie rosnac sama i trzeba bedzie druga regule.

   `pointer-events: none`, bo duch jest widokiem, a nie uczestnikiem: przez te
   pare klatek lezy na wierzchu dokladnie tam, gdzie czlowiek wlasnie kliknal
   "x", i bez tego przejmowalby nastepne klikniecie za okno, ktorego nie ma.
   (Z tabulacji i drzewa dostepnosci zabiera go `inert` z kodu.)

   Do zera, a nie do .35 jak przy pojawieniu: tam chodzilo o to, ZEBY BYLO WIDAC
   od pierwszej klatki, tu o to, zeby na koncu nie zostal slad - duch zdjety
   przy widocznej przezroczystosci mrugnalby na pozegnanie.

   `reverse` zamiast drugiej krzywej. `--ruch-krzywa` jest krzywa PRZYBYCIA:
   szybko na poczatku, miekko na koncu. Puszczona wprost na znikaniu robi
   z ruchu atrape - zmierzone na wdrozonej statyce: po 45% czasu duch mial juz
   przezroczystosc .09, czyli caly ruch dzial sie w pierwszych 70 ms, a przez
   reszte nie bylo widac nic. Odegrana wstecz jest wlasnie krzywa odejscia
   (wolno rusza, przyspiesza), a przy okazji nie zaklada w arkuszu drugiego
   tokenu tempa - znikniecie jest DOSLOWNIE pojawieniem sie czytanym od konca,
   wiec keyframes tez ida od zera do jedynki. */
@keyframes okno-wyjscie {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.okno--wyjscie {
  animation: okno-wyjscie var(--ruch) var(--ruch-krzywa) reverse;
  pointer-events: none;
  /* Stan SPOCZYNKOWY ducha, nie ozdoba przy animacji. Miedzy koncem animacji
     a usunieciem ducha z dokumentu jest szczelina (zdarzenie, obsluga, mikro-
     zadanie); bez tej linii duch wracalby w niej do pelnej widocznosci
     - `animation-fill-mode` jest domyslnie `none` - i mrugnalby na pozegnanie.
     Sama animacja i tak wygrywa z ta deklaracja przez caly czas trwania. */
  opacity: 0;
}

.okno-pasek {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--bar-h);
  padding: 0 6px 0 10px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  cursor: grab;
  touch-action: none;          /* inaczej przegladarka zabiera gest na przewijanie */
  user-select: none;
}
.okno--gest .okno-pasek { cursor: grabbing; }
.okno-tytul {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 12px;
  color: var(--fg-dim);
}
.okno--aktywne .okno-tytul { color: var(--fg); }

.okno-przyciski { display: flex; gap: 4px; }
.okno-przycisk {
  font: inherit;
  font-size: 12px;
  line-height: 1;
  color: var(--fg-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 3px 7px;
  cursor: pointer;
}
.okno-przycisk:hover { color: var(--fg); border-color: var(--line); }
.okno-zamknij:hover { color: var(--accent); border-color: var(--accent); }
.okno-przycisk:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Cel dotykowy w ramce okna (etap 9.4, czesc 2/N). Ta sama liczba i ten sam
   selektor, co w doku (1/N). Rozne jest tylko to, co ten cel kosztuje.

   Zmierzone przed zmiana w emulacji 390x844: guziki ramki maja 20 px wysokosci
   i 22-23 px szerokosci - NAJGORSZY cel w calym pulpicie, gorszy niz 34 px
   pozycji doku z czesci 1/N. I to nie sa guziki dowolne: `okno-zamknij` sasiaduje
   z `okno-min` przez 4 px przerwy, wiec chybienie o polowe opuszka nie jest
   nietrafieniem - jest ZAMKNIECIEM okna zamiast zwiniecia go. Zamkniecie
   to jedyna rzecz w powloce, ktora znika na zawsze (patrz "Pozegnanie" w okno.js);
   zwiniecie jest odwracalne jednym tapnieciem w dok.

   Pasek tytulu musi urosnac RAZEM z nimi i to nie jest ozdoba. `.okno-pasek`
   ma `height: var(--bar-h)` (26-30 px zaleznie od motywu), czyli MNIEJ niz cel;
   guzik wyzszy od swojego paska wystaje z niego w obie strony - dolna polowa
   jego pola klikniecia lezy nad `.okno-cialo` (czyli nad terminalem, ktory
   pierwszy lapie dotkniecie), a gorna nad tym, co stoi ponad oknem. Cel
   nadany samemu guzikowi bylby wiec celem tylko na rysunku. `min-height`
   wygrywa tu z `height` bez ruszania `--bar-h`: zmienna opisuje pasek pulpitu
   BIURKOWEGO i jest wspolna z `#menubar`, ktory ma wlasny rachunek (jego guziki
   to osobna czesc tego zadania).

   Wysokosci i szerokosci samych znakow nie ruszamy - `padding` zostaje taki,
   jaki byl. Guzik centruje swoja tresc sam (domyslne zachowanie <button>),
   wiec dolozony odstep bylby trzecia liczba pilnujaca tego samego, a przy
   okazji zabralby tytulowi miejsce, ktorego na 390 px nie ma.

   `okno-maks` w ukladzie waskim jest `display: none` (czesc 2/N etapu 9.3),
   wiec regula nie doklada mu ani piksela - na telefonie w pasku stoja dwa
   cele po 44 px, nie trzy. */
/* Cel PLUS wlasna kreska paska. To nie jest zapas "na oko": arkusz ma
   `* { box-sizing: border-box }`, wiec pasek wysoki na 44 px ma w srodku 43 px
   i wysrodkowany w nim cel o boku 44 px wystaje pol piksela w gore i pol
   w dol - dokladnie ten blad, ktory zapadka lapie przez `wystaje`. */
[data-wskaznik="dotyk"] .okno-pasek { min-height: calc(var(--cel-dotyk) + 1px); }
[data-wskaznik="dotyk"] .okno-przycisk {
  min-width: var(--cel-dotyk);
  min-height: var(--cel-dotyk);
}

.okno-cialo { flex: 1; min-height: 0; overflow: auto; }

/* Gest: warstwa kompozytora TYLKO na czas trwania (patrz komentarz w okno.js).
   Wylaczenie zdarzen w ciele okna jest tu konieczne, a nie kosmetyczne: bez
   niego xterm albo <iframe> lapie pointermove i okno gubi sie w polowie ruchu. */
.okno--gest { will-change: transform; }
.okno--gest .okno-cialo { pointer-events: none; }

.okno-uchwyt { position: absolute; touch-action: none; }
.okno-uchwyt[data-kier="n"] { top: -3px; left: 10px; right: 10px; height: 7px; cursor: ns-resize; }
.okno-uchwyt[data-kier="s"] { bottom: -3px; left: 10px; right: 10px; height: 7px; cursor: ns-resize; }
.okno-uchwyt[data-kier="w"] { left: -3px; top: 10px; bottom: 10px; width: 7px; cursor: ew-resize; }
.okno-uchwyt[data-kier="e"] { right: -3px; top: 10px; bottom: 10px; width: 7px; cursor: ew-resize; }
.okno-uchwyt[data-kier="nw"] { top: -3px; left: -3px; width: 13px; height: 13px; cursor: nwse-resize; }
.okno-uchwyt[data-kier="ne"] { top: -3px; right: -3px; width: 13px; height: 13px; cursor: nesw-resize; }
.okno-uchwyt[data-kier="sw"] { bottom: -3px; left: -3px; width: 13px; height: 13px; cursor: nesw-resize; }
.okno-uchwyt[data-kier="se"] { bottom: -3px; right: -3px; width: 13px; height: 13px; cursor: nwse-resize; }
/* Zmaksymalizowane okno nie ma czego zmieniac - uchwyty na krawedzi ekranu
   tylko przechwytywalyby klikniecia w pasku i doku. */
.okno--maks .okno-uchwyt { display: none; }
/* To samo w ukladzie waskim - a tam wazy wiecej niz wyglad: uchwyt `s` lezy
   na dolnej krawedzi okna, czyli dokladnie nad stalym paskiem doku (9.3
   czesc 1), i lapalby palec siegajacy po zminimalizowane okno. */
.okno--pelny .okno-uchwyt { display: none; }

/* --- Terminal (etap 5.5) ---------------------------------------------------
   Ta sama kolumna, co w podgladzie: plotno rozciagniete na wszystko, jedna
   linia stanu na dole - i to ona ZNIKA, gdy nie ma nic do powiedzenia, bo
   kazdy zabrany wiersz to jeden wiersz mniej dla powloki.
   `overflow: hidden` na plotnie jest warunkiem, nie ozdoba: przewijaniem
   zarzadza xterm (200 wierszy z "Wymogu nadrzednego"), a drugi pasek
   przewijania - od okna - psulby jego pomiar rozmiaru komorki. */
.term { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.term-plotno { flex: 1; min-height: 0; overflow: hidden; padding: 4px 0 0 6px; }
.term-plotno .xterm { height: 100%; }
.term-linia {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 4px 10px;
  font: 11px/1.4 var(--font-mono);
  color: var(--fg-dim);
  border-top: 1px solid var(--line);
}
/* Zerwane lacze mowi KOLOREM, nie tylko slowem (etap 10.3). Guzik "Polacz
   ponownie" jest BEZPOSREDNIM dzieckiem tej linii, a jego `hidden` przelaczaja
   dokladnie dwa miejsca w `apps/terminal.js` (`#polacz` chowa, `#zerwane`
   pokazuje) - stan lacza lezy wiec juz w DOM-ie i regula czyta go wprost:
   zero JS, zero timera, zero stanu do utrzymania w pamieci karty. Regula stoi
   w BAZIE, nie w motywie, bo "nie doszlo" znaczy to samo w kazdym z nich -
   i kazdy ma na to wlasny `--blad` (etap 10.2). Brak `:has()` degraduje sie
   lagodnie: pas zostaje szary, a guzik dziala tak samo. */
.term-linia:has(> [data-p="polacz"]:not([hidden])) {
  color: var(--blad);
  border-top-color: var(--blad);
}
.term-linia > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.term-guzik {
  padding: 2px 8px;
  font: inherit;
  color: var(--fg-dim);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.term-guzik:hover { color: var(--fg); border-color: var(--accent); }
.term-guzik:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* --- Edytor (etap 6.5) -----------------------------------------------------
   Ta sama kolumna, co menedzer plikow i podglad: pasek u gory, plotno
   rozciagniete na reszte, jedna linia komunikatu na dole.
   `min-height: 0` na plotnie jest warunkiem, nie ozdoba: bez niego flexbox
   rozdalby okno do wysokosci calego dokumentu, a CodeMirror renderowalby
   wszystkie wiersze naraz - czyli dokladnie to, czemu zapobiega zakaz
   `viewportMargin: Infinity` z 6.2. Wirtualizacja widoku wymaga, zeby widok
   MIAL skonczona wysokosc. */
.edytor { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.edytor-plotno { flex: 1; min-height: 0; overflow: hidden; }
/* Chrom SILNIKA (klasy `.CodeMirror-*`) stoi w `style/kodmirror.css` i wchodzi
   leniwie, razem z silnikiem - patrz naglowek tamtego pliku. Tego arkusza nie
   wolno rozdzielac na dwa domy: regula na klase biblioteki dopisana TUTAJ
   przegra z `codemirror.css`, ktory wchodzi <link>iem po `shell.css`, a zapadka
   zrodlowa czyta juz tamten plik. Pilnuje `tests/test-kodmirror.py`. */
.edytor-pasek {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
}
.edytor-sciezka {
  flex: 1;
  min-width: 0;
  font: 12px/1 var(--font-mono);
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 5px 8px;
}
.edytor-sciezka:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.edytor-guzik {
  font: inherit;
  font-size: 12px;
  line-height: 1;
  color: var(--fg-dim);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 5px 9px;
  cursor: pointer;
}
.edytor-guzik:hover:not(:disabled) { color: var(--fg); border-color: var(--accent); }
.edytor-guzik:disabled { color: var(--fg-dim); opacity: .45; cursor: default; }
.edytor-guzik:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.edytor-linia {
  margin: 0;
  padding: 4px 10px;
  font: 11px/1.4 var(--font-mono);
  color: var(--fg-dim);
  border-top: 1px solid var(--line);
}

/* --- Propozycja nowej wersji (etap 10.17, czesc 2/2) -----------------------
   Pasek stawiany przez `shell/aktualizacja.js`, gdy po wdrozeniu czeka nowy
   worker. Nie ma tu ANI JEDNEGO wlasnego koloru - wszystko idzie ze zmiennych
   motywu, bo motywow jest kilka i kazdy maluje chrom po swojemu; element
   z wpisanym kolorem wyglada dobrze w tym motywie, w ktorym go pisano.

   `z-index` jest WYZSZY niz brama (1000), i to jest jedyna liczba w tym bloku,
   ktora cos rozstrzyga. Brama to `position: fixed; inset: 0` BEZ
   `pointer-events: none`, wiec lapie klikniecia z calego ekranu - propozycja
   pod nia bylaby widoczna i MARTWA, czyli dokladnie ta klasa usterki, ktorej
   testy klikajace syntetycznie nie widza (etap 10.18). Wdrozenie potrafi
   trafic karte stojaca na ekranie logowania i wtedy propozycja jest najtansza
   do przyjecia, bo nie ma jeszcze czego zamykac.

   Gora, a nie dol: dol nalezy do doku i do linii komunikatu, a na telefonie
   takze do paska gestow systemu. */
.aktu {
  position: fixed;
  /* `--pasek-gorny-h`, a nie `--bar-h`: ta pierwsza jest wysokoscia paska
     GORNEGO razem z bezpiecznym marginesem telefonu i rosnie przy wskazniku
     dotykowym. `--bar-h` opisuje pasek TYTULU okna - propozycja liczona z
     niej wchodzilaby na menu na kazdym telefonie. */
  top: calc(var(--pasek-gorny-h) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 1001;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: max-content;
  max-width: min(52ch, calc(100vw - 24px));
  padding: 12px 14px;
  color: var(--fg);
  background: var(--surface-lita);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
}
.aktu-tekst { margin: 0; font-size: 12px; line-height: 1.45; }
.aktu-guziki { display: flex; gap: 8px; align-items: center; }
.aktu-teraz {
  font: inherit;
  font-size: 12px;
  color: var(--bg);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 6px 12px;
  cursor: pointer;
}
.aktu-pozniej {
  font: inherit;
  font-size: 12px;
  color: var(--fg-dim);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 6px 12px;
  cursor: pointer;
}
.aktu-pozniej:hover { color: var(--fg); border-color: var(--accent); }
.aktu-teraz:focus-visible,
.aktu-pozniej:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }

/* Telefon (etap 9.3). Dwa pytania, dwa atrybuty - i to jest ta sama decyzja,
   co przy doku: o MIEJSCE pyta `data-uklad` (waski ekran ma dostac pasek na
   cala szerokosc, bo `width: max-content` przy tym zdaniu i tak ja wyczerpie),
   a o CEL PALCA pyta `data-wskaznik`, bo palec ma te sama grubosc niezaleznie
   od tego, ile miejsca zostalo. Liczbe `--cel-dotyk` podaje `shell/dotyk.js`,
   zeby arkusz nie znal jej z drugiej reki. */
[data-uklad="waski"] .aktu {
  left: 12px;
  right: 12px;
  width: auto;
  max-width: none;
  transform: none;
}
[data-wskaznik="dotyk"] .aktu-teraz,
[data-wskaznik="dotyk"] .aktu-pozniej {
  min-height: var(--cel-dotyk);
  padding: 6px 16px;
}
