/* =========================================================================
   JRS Polska - system designu
   Edited by: Kryst 11.08.26

   Jeden plik, bo nginx serwuje po HTTP/1.1 - kazdy dodatkowy arkusz to
   osobne polaczenie w puli szesciu. Kolejnosc sekcji:

     1. Tokeny (kolor, typografia, przestrzen, ruch)
     2. Kroje pisma
     3. Reset i podstawy dokumentu
     4. Typografia
     5. Uklad i pojemniki
     6. Ozdoba: luki (motyw mostu z logo JRS)
     7. Przyciski
     8. Formularze
     9. Karty i powierzchnie
    10. Nawigacja
    11. Sciezka kursanta (kroki, test, wybor kursu)
    12. Komunikaty i stany
    13. Ruch: przejscia miedzy stronami, odslony, mikrointerakcje
    14. Uzytki

   CZEGO TU NIE MA I GDZIE TO JEST
   Edited by: Kryst 12.08.26 (PLAN.md P15)

   Ten plik trzyma tokeny i komponenty, ktorych uzywa SCIEZKA KURSANTA -
   sama albo razem z panelem. Uklad ekranow samego panelu (tabele, naglowki
   sekcji, pulpit, lista zgloszen) siedzi w static/css/admin_and_employee.css
   i laduje sie wylacznie zalogowanemu personelowi.

   Regula jest wlasnie taka, a nie "wszystko w jednym pliku", bo ten arkusz
   pobiera KAZDY kursant przy pierwszym wejsciu, przez telefon, czesto przez
   dane komorkowe. Do 12.08.26 podzial nie mial zadnej reguly: `zgl-*`
   i `page-head` staly tutaj, a `pulpit-*` w arkuszu panelu - z niczego nie
   wynikalo, ktore gdzie, wiec kolejna klasa ladowala tam, gdzie akurat
   otwarty byl plik.

   Stad zniknela tez "warstwa zgodnosci" (`button-orange`, `page-title`,
   `alert-overlay`...): panel przeszedl na jeden slownik nazw, a zadna z tych
   21 klas nie miala ani jednego uzycia poza panelem.

   Zasady, ktore obowiazuja wszedzie ponizej:
   - wlasciwosci logiczne (inline-start/end zamiast left/right), bo interfejs
     chodzi po arabsku i persku i musi sie odbijac bez osobnego arkusza RTL,
   - kazdy stan interaktywny ma widoczny :focus-visible - stary arkusz gasil
     obrys przez "outline: none" w czterech plikach,
   - kazda animacja siedzi pod @media (prefers-reduced-motion: no-preference),
   - cel dotykowy nie schodzi ponizej 48px.
   ========================================================================= */


/* =========================================================================
   1. TOKENY
   ========================================================================= */

:root {
  /* --- Marka -----------------------------------------------------------
     Skale wyprowadzone z dwoch kolorow z logo. Wartosci 600 (niebieski)
     i 500 (pomaranczowy) to oryginalne barwy JRS - reszta to ich otoczenie. */

  --blue-50:  #eef3fa;
  --blue-100: #d8e3f3;
  --blue-200: #b3c7e5;
  --blue-300: #86a5d3;
  --blue-400: #5580bd;
  --blue-500: #3665a3;
  --blue-600: #26528c;   /* marka */
  --blue-700: #1f4374;
  --blue-800: #1a3760;
  --blue-900: #152c4d;
  --blue-950: #0d1c33;

  --orange-50:  #fdf3ea;
  --orange-100: #fae2cc;
  --orange-200: #f4c299;
  --orange-300: #ed9f61;
  --orange-400: #e58436;
  --orange-500: #d97014;  /* marka */
  --orange-600: #b85c0f;  /* od tego poziomu bialy tekst ma kontrast >= 4.5 */
  --orange-700: #94490d;
  --orange-800: #74390c;
  --orange-900: #5c2e0b;

  /* --- Papier i atrament ------------------------------------------------
     Edited by: Kryst 11.08.26 - redesign strony kursanta.

     Skala szarosci ponizej jest mimo komentarza CHLODNA (#f8f9fb ma wiecej
     niebieskiego niz czerwonego) i to bylo slychac: pomarancz marki na
     zimnym tle robil sie jaskrawy, a caly ekran mial temperature panelu
     administracyjnego. Motyw jasny jest teraz tym glownym, wiec dostaje
     wlasna skale.

     Para jest klasyczna i drukarska: cieply papier + chlodny granatowy
     atrament. Granat pochodzi z --blue-950 marki, wiec nie wnosi nowego
     koloru - wnosi tylko kontrast 15:1 i wrazenie druku, a nie ekranu.
     Skala szarosci zostaje nietknieta, bo opiera sie na niej panel. */
  --paper-0:   #ffffff;
  --paper-50:  #faf7f2;   /* grunt strony */
  --paper-100: #f3ede4;
  --paper-200: #e9e0d3;
  --paper-300: #d8cbb8;

  --ink-900: #14203a;   /* tekst - 15.2:1 na --paper-50 */
  --ink-700: #46536b;   /* tekst drugorzedny - 6.9:1 */
  --ink-600: #626d86;   /* podpisy - 4.6:1, wciaz ponad progiem AA */

  /* Neutralne z lekkim cieplem, zeby nie kloccily sie z pomaranczem */
  --grey-0:   #ffffff;
  --grey-50:  #f8f9fb;
  --grey-100: #f1f3f7;
  --grey-200: #e4e8ef;
  --grey-300: #cdd4e0;
  --grey-400: #9aa5b8;
  --grey-500: #5f6a7d;
  --grey-600: #4d5768;
  --grey-700: #3a4353;
  --grey-800: #262d3a;
  --grey-900: #171c26;
  --grey-950: #0e1219;

  --green-500: #0d7d55;
  --green-50:  #e8f6f0;
  --amber-500: #966700;
  --amber-50:  #fdf5e3;
  --red-500:   #c0392b;
  --red-600:   #a32c20;
  --red-50:    #fbecea;

  /* --- Role semantyczne (jasny motyw) --------------------------------- */
  --bg:              var(--paper-50);
  --bg-elevated:     var(--paper-0);
  --bg-sunken:       var(--paper-100);
  --bg-inset:        var(--paper-100);

  --text:            var(--ink-900);
  --text-muted:      var(--ink-700);
  --text-subtle:     var(--ink-600);
  --text-on-brand:   var(--paper-0);

  --border:          var(--paper-200);
  --border-strong:   var(--paper-300);

  /* Wlosowa linia - caly uklad redakcyjny stoi na niej zamiast na
     ramkach kart. Slabsza niz --border, bo ma dzielic, nie grodzic. */
  --rule:            color-mix(in srgb, var(--ink-900) 14%, transparent);

  --brand:           var(--blue-600);
  --brand-hover:     var(--blue-700);
  --brand-subtle:    var(--blue-50);
  --brand-text:      var(--blue-700);

  --accent:          var(--orange-500);
  --accent-solid:    var(--orange-600);   /* tlo pod --text-on-accent */
  --accent-hover:    var(--orange-700);
  --accent-subtle:   var(--orange-50);
  --accent-text:     var(--orange-700);
  /* Kolor tekstu na pelnym pomaranczu. W jasnym motywie biel na 600 daje
     4.6:1; oryginalne #D97014 dawalo 3.3:1 i nie przechodzilo AA. */
  --text-on-accent:  #ffffff;

  /* Strzalka w <select> - jako token, bo data-URI nie zna currentColor */
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%235f6a7d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");

  --success:         var(--green-500);
  --success-subtle:  var(--green-50);
  --warning:         var(--amber-500);
  --warning-subtle:  var(--amber-50);
  --danger:          var(--red-500);
  --danger-hover:    var(--red-600);
  --danger-subtle:   var(--red-50);

  --focus-ring:      var(--orange-500);

  /* --- Paleta ciemna: WARTOSCI ------------------------------------------
     Edited by: Kryst 11.08.26 - dodanie przelacznika motywu.

     TU SIE ZMIENIA MOTYW CIEMNY. Ponizej, w dwoch blokach mapujacych,
     sa juz tylko przypisania rol - tam nie ma czego przestawiac.

     Powod tego rozdzielenia: motyw ciemny musi wlaczac sie na dwa
     niezalezne sposoby - z ustawienia systemu (@media) oraz z jawnego
     wyboru w przelaczniku ([data-theme="dark"]). CSS nie pozwala objac
     obu jednym blokiem, wiec mapowanie rol wystepuje dwa razy. Gdyby
     w tych blokach stały wartosci, kazda zmiana motywu wymagalaby
     dwoch edycji i predzej czy pozniej by sie rozjechaly.

     Wartosci sa wiec tutaj, raz. Bloki mapujace zmieniaja sie wylacznie
     wtedy, gdy dochodzi NOWA rola - nie gdy zmienia sie kolor. */
  --d-bg:            var(--grey-950);
  --d-bg-elevated:   #151a24;
  --d-bg-sunken:     #0a0d13;
  --d-bg-inset:      #1c2230;

  --d-text:          #e8ecf3;
  --d-text-muted:    #a8b3c4;
  --d-text-subtle:   #7d8798;
  --d-text-on-brand: #ffffff;

  --d-border:        #262d3a;
  --d-border-strong: #343d4d;
  /* W ciemnym motywie linia dzielaca musi byc jasniejsza od tla,
     nie ciemniejsza - stad biel z niska krycia zamiast atramentu. */
  --d-rule:          rgba(232, 236, 243, 0.16);

  --d-brand:         var(--blue-300);
  --d-brand-hover:   var(--blue-200);
  --d-brand-subtle:  #16263f;
  --d-brand-text:    var(--blue-200);

  --d-accent:        var(--orange-300);
  /* W ciemnym motywie pelny pomarancz jest jasny, a tekst na nim ciemny -
     biel dawalaby 3.3:1. Odwracamy pare zamiast przyciemniac tlo. */
  --d-accent-solid:  var(--orange-300);
  --d-accent-hover:  var(--orange-200);
  --d-accent-subtle: #2e1c0d;
  --d-accent-text:   var(--orange-200);
  --d-text-on-accent: var(--grey-900);

  --d-select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%237d8798' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");

  --d-success:        #3ecf98;
  --d-success-subtle: #0d2a20;
  --d-warning:        #e0a83a;
  --d-warning-subtle: #2b2110;
  --d-danger:         #ef7a6d;
  --d-danger-hover:   #f49a90;
  --d-danger-subtle:  #2e1512;

  --d-focus-ring:    var(--orange-300);

  --d-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
  --d-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);
  --d-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45), 0 2px 4px rgba(0, 0, 0, 0.3);
  --d-shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.5), 0 4px 8px rgba(0, 0, 0, 0.3);
  --d-shadow-xl: 0 24px 48px rgba(0, 0, 0, 0.55), 0 8px 16px rgba(0, 0, 0, 0.35);

  /* --- Zgodnosc wstecz --------------------------------------------------
     Stare arkusze (navbar.css, timetable.css, panel) uzywaja tych dwoch nazw.
     Zostaja jako aliasy, zeby redesign mogl isc ekranami, a nie skokiem. */
  --jrs-blue:   var(--blue-600);
  --jrs-orange: var(--orange-500);

  /* --- Typografia ------------------------------------------------------ */
  --font-sans: 'Montserrat', 'Noto Sans Arabic', system-ui, -apple-system,
               'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* Kroj naglowkowy. Zapasowe szeryfy sa wypisane wprost, bo gdy Literata
     sie nie doladuje, naglowek ma zostac szeryfem - spadek na bezszeryfowy
     rozsypalby kontrast miedzy naglowkiem a tekstem, czyli jedyna rzecz,
     na ktorej stoi cala hierarchia tych stron. */
  --font-display: 'Literata', 'Noto Naskh Arabic', Georgia, 'Times New Roman',
                  'Times', serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
               'Liberation Mono', monospace;

  /* Skala plynna. Dolna granica to telefon 360px, gorna - 1280px.
     Kroki ~1.2 na dole i ~1.25 na gorze: na malym ekranie naglowki nie
     zjadaja calego widoku, na duzym maja oddech. */
  --text-2xs:  clamp(0.6875rem, 0.66rem + 0.12vw,  0.75rem);
  --text-xs:   clamp(0.75rem,   0.72rem + 0.15vw,  0.8125rem);
  --text-sm:   clamp(0.875rem,  0.85rem + 0.12vw,  0.9375rem);
  --text-base: clamp(1rem,      0.96rem + 0.2vw,   1.0625rem);
  --text-lg:   clamp(1.125rem,  1.06rem + 0.3vw,   1.25rem);
  --text-xl:   clamp(1.25rem,   1.14rem + 0.55vw,  1.5rem);
  --text-2xl:  clamp(1.5rem,    1.3rem + 1vw,      1.875rem);
  --text-3xl:  clamp(1.875rem,  1.55rem + 1.6vw,   2.5rem);
  --text-4xl:  clamp(2.25rem,   1.7rem + 2.7vw,    3.5rem);
  /* Stopien wylacznie dla tytulu strony glownej. Osobny od 4xl, bo 4xl
     wystepuje takze w panelu, gdzie 4.75rem byloby krzykiem. */
  --text-5xl:  clamp(2.5rem,    1.55rem + 4.2vw,   4.75rem);

  --leading-tight:   1.15;
  --leading-snug:    1.3;
  --leading-normal:  1.55;
  --leading-relaxed: 1.7;

  --tracking-tight:  -0.02em;
  --tracking-snug:   -0.01em;
  --tracking-normal: 0;
  --tracking-wide:   0.02em;
  --tracking-wider:  0.06em;

  /* --- Przestrzen: krok 4px ------------------------------------------- */
  --space-1:  0.25rem;
  --space-2:  0.5rem;
  --space-3:  0.75rem;
  --space-4:  1rem;
  --space-5:  1.25rem;
  --space-6:  1.5rem;
  --space-8:  2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;

  /* --- Promienie ------------------------------------------------------- */
  --radius-sm:   6px;
  --radius-md:   10px;
  --radius-lg:   16px;
  --radius-xl:   24px;
  --radius-2xl:  32px;
  --radius-full: 999px;

  /* --- Cienie ----------------------------------------------------------
     Barwione na niebiesko marki, nie na czarno - czarny cien na cieplym tle
     robi sie szary i brudny. */
  --shadow-xs: 0 1px 2px rgba(21, 44, 77, 0.06);
  --shadow-sm: 0 1px 3px rgba(21, 44, 77, 0.08), 0 1px 2px rgba(21, 44, 77, 0.04);
  --shadow-md: 0 4px 12px rgba(21, 44, 77, 0.08), 0 2px 4px rgba(21, 44, 77, 0.04);
  --shadow-lg: 0 12px 28px rgba(21, 44, 77, 0.10), 0 4px 8px rgba(21, 44, 77, 0.04);
  --shadow-xl: 0 24px 48px rgba(21, 44, 77, 0.12), 0 8px 16px rgba(21, 44, 77, 0.05);

  /* --- Ruch -------------------------------------------------------------
     Krzywe: "out" do wejsc (szybki start, miekkie ladowanie), "spring" do
     rzeczy, ktore maja sprawiac wrazenie fizycznych. */
  --ease-out:    cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  --duration-fast:   140ms;
  --duration-base:   240ms;
  --duration-slow:   400ms;
  --duration-slower: 700ms;

  /* --- Wymiary --------------------------------------------------------- */
  --container:      72rem;
  --container-text: 44rem;   /* miara czytelna: ~70 znakow */
  --container-form: 40rem;
  /* Edited by: Kryst 12.08.26 (PLAN.md P20) - miara byla wpisana wprost
     w .container-wide, a pasek panelu trzymal sie --container. Skutek:
     naglowek ekranu zaczynal sie 9rem na lewo od logo nad nim. Teraz obie
     strony czytaja te sama nazwe. */
  --container-wide: 90rem;
  --nav-height:     4.5rem;
  --tap-min:        48px;    /* minimalny cel dotykowy */
}

/* --- Panel: ta sama skala, o krok wyzej (PLAN.md 6.3) --------------------
   Edited by: Kryst 12.08.26 (PLAN.md P15)

   Po drugiej stronie panelu siedzi jedna osoba, ktora patrzy w te ekrany
   osiem godzin dziennie - stad wiekszy stopien pisma i wiekszy cel
   klikniecia niz na sciezce kursanta.

   DLACZEGO TO NIE JEST DRUGA SKALA TYPOGRAFICZNA
   Bo nie ma tu ani jednej nowej nazwy. To sa te same tokeny, przekotwiczone
   raz, w jednym miejscu, w zasiegu jednej klasy. Drugi ZESTAW NAZW
   (--text-sm-panel obok --text-sm) bylby ta sama klasa duplikacji, przed
   ktora CLAUDE.md ostrzega przy fladze trybu naboru: dwie nazwy na jedno
   pytanie predzej czy pozniej odpowiadaja roznie. Jedna nazwa o dwoch
   wartosciach w dwoch zasiegach odpowiedziec roznie nie moze.

   Krok podnosi tylko stopnie uzywane w tresci ekranu. Naglowkow (2xl, 3xl)
   nie ruszamy - one sa juz duze, a ich podbicie zabiera miejsce tabeli.

   Klasa siedzi na <body>, a nie na kontenerze, bo pasek nawigacji stoi poza
   <main>. Wartosci sa tymi samymi clamp-ami przesunietymi o jeden szczebel
   skali, a nie mnoznikiem - mnoznik na clamp() rozjezdza sie miedzy dolna
   i gorna granica.

   NAZWA: `panel-ui`, nie `panel`. Edited by: Kryst 12.08.26 - naprawa.

   P15 nazwal te klase `panel` i postawil ja na <body>. W tym samym arkuszu,
   sekcja 6, `.panel` jest OD DAWNA komponentem - biala plachta tresci na
   ekranach kursanta (formularz, test, potwierdzenie), z wlasnym tlem,
   ramka, cieniem, paskiem ::before i `overflow: hidden`. <body class="panel">
   lapal wiec oba znaczenia naraz i kazdy ekran panelu dostawal:

     - `overflow: hidden` na <body>, czyli MARTWY SCROLL. Przegladarka bierze
       overflow dla okna z <body>, kiedy <html> ma `visible` - a `.panel`
       (0,1,0) bije `body` (0,0,1), wiec `overflow-x: hidden` z sekcji 3
       zamienialo sie w oba osie zamkniete. Tabela dluzsza niz ekran nie
       miala jak sie przewinac.
     - pasek `.panel::before` przyklejony do lewego gornego rogu OKNA -
       3px gradientu, ktory z niczego tam nie wynikal,
     - tlo `--bg-elevated`, ramke, zaokraglenie i cien na calym dokumencie.

   Ustepuje nowa nazwa, nie komponent: `.panel` ma dziewiec uzyc w szablonach
   sciezki kursanta, a ta klasa jedno, w base.html. */
body.panel-ui {
  --text-xs:   clamp(0.8125rem, 0.79rem + 0.13vw,  0.875rem);
  --text-sm:   clamp(0.9375rem, 0.91rem + 0.15vw,  1rem);
  --text-base: clamp(1.0625rem, 1.02rem + 0.22vw,  1.125rem);
  --text-lg:   clamp(1.1875rem, 1.12rem + 0.32vw,  1.3125rem);
  --tap-min:   52px;

  /* --- Promienie: ta sama skala, o krok NIZEJ ---------------------------
     Edited by: Kryst 12.08.26

     Ten sam zabieg co ze stopniami pisma wyzej i z tego samego powodu:
     jedna nazwa, dwie wartosci w dwoch zasiegach, zero nowych nazw.
     Kierunek jest odwrotny (nizej, nie wyzej), bo pytanie jest inne.

     Sciezka kursanta to dziesiec ekranow, z ktorych kazdy pokazuje jedna
     rzecz - tam 16px na karcie jest gestem. Ekran panelu pokazuje ich
     kilkanascie naraz i wtedy ten sam promien robi ze strony stos
     pastylek: na ekranie kursow karta, tabela, pudelko formularza,
     kafelek licznika i pasek ostrzezenia mialy DOKLADNIE ten sam obrys,
     wiec nic nie bylo wazniejsze od niczego.

     Wartosci sa dobrane tak, zeby promien byl mniejszy od odstepu
     wewnetrznego elementu - inaczej luk zjada naroznik tresci. Pole
     formularza (10px) i pudelko wokol niego (10px) maja dzis ten sam
     promien i to jest cel: jedno wynika z drugiego, zamiast z nim
     konkurowac.

     --radius-full NIE jest tu ruszane. To nie jest szczebel skali, tylko
     "okrag" - siedzi na przelaczniku motywu (2,75rem kwadratu) i na
     pozycjach paska nawigacji. Przyciski schodza z niego osobno, regula
     w admin_and_employee.css, bo to decyzja o jednym komponencie, a nie
     o skali. */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 10px;
  --radius-xl: 14px;
  --radius-2xl: 18px;
}

/* Arabski i perski: kolejnosc rodzin odwrocona, zeby naskh wygral
   pierwszenstwo takze dla cyfr i znakow interpunkcyjnych wystepujacych
   w naglowku obok pisma arabskiego - inaczej jedna data w naglowku
   przychodzi z Literaty i widac szew miedzy dwoma krojami.

   Naskh ma mniejsza wysokosc x niz Literata, wiec przy tej samej
   wartosci font-size wyglada o klase drobniej. font-size-adjust
   wyrownuje to po wysokosci x, a nie po zgadywanym mnozniku - tam,
   gdzie przegladarka go nie zna, po prostu nic sie nie dzieje. */
:root:lang(ar), :root:lang(fa) {
  --font-display: 'Noto Naskh Arabic', 'Literata', Georgia, serif;
}
:root:lang(ar) h1, :root:lang(fa) h1,
:root:lang(ar) h2, :root:lang(fa) h2,
:root:lang(ar) h3, :root:lang(fa) h3,
:root:lang(ar) .display, :root:lang(fa) .display {
  font-size-adjust: ex-height 0.46;
  line-height: var(--leading-snug);
}

/* --- Motyw ciemny: MAPOWANIE ROL ----------------------------------------
   Edited by: Kryst 11.08.26 - dodanie przelacznika motywu.

   UWAGA: ponizej sa DWA bloki o identycznej tresci i to jest zamierzone.
   CSS nie pozwala objac jedna regula warunku "system ma ciemny" oraz
   warunku "uzytkownik wybral ciemny" naraz, a oba musza dawac ten sam
   skutek.

   Nie ma tu jednak zadnych wartosci kolorow - wylacznie przypisania
   var(--d-*). Zmiana wygladu motywu ciemnego to edycja tokenow --d-*
   w :root, w JEDNYM miejscu. Te bloki ruszamy tylko wtedy, gdy dochodzi
   nowa rola - i wtedy do OBU naraz.

   Trzy stany przelacznika:
     brak atrybutu       - idziemy za ustawieniem systemu,
     data-theme="light"  - jawnie jasny, blokuje ciemny systemowy,
     data-theme="dark"   - jawnie ciemny, takze gdy system jest jasny.
   ========================================================================= */

/* 1/2 - system ustawiony na ciemny, o ile uzytkownik nie wymusil jasnego */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:                var(--d-bg);
    --bg-elevated:       var(--d-bg-elevated);
    --bg-sunken:         var(--d-bg-sunken);
    --bg-inset:          var(--d-bg-inset);

    --text:              var(--d-text);
    --text-muted:        var(--d-text-muted);
    --text-subtle:       var(--d-text-subtle);
    --text-on-brand:     var(--d-text-on-brand);

    --border:            var(--d-border);
    --border-strong:     var(--d-border-strong);
    --rule:              var(--d-rule);

    --brand:             var(--d-brand);
    --brand-hover:       var(--d-brand-hover);
    --brand-subtle:      var(--d-brand-subtle);
    --brand-text:        var(--d-brand-text);

    --accent:            var(--d-accent);
    --accent-solid:      var(--d-accent-solid);
    --accent-hover:      var(--d-accent-hover);
    --accent-subtle:     var(--d-accent-subtle);
    --accent-text:       var(--d-accent-text);
    --text-on-accent:    var(--d-text-on-accent);

    --select-arrow:      var(--d-select-arrow);

    --success:           var(--d-success);
    --success-subtle:    var(--d-success-subtle);
    --warning:           var(--d-warning);
    --warning-subtle:    var(--d-warning-subtle);
    --danger:            var(--d-danger);
    --danger-hover:      var(--d-danger-hover);
    --danger-subtle:     var(--d-danger-subtle);

    --focus-ring:        var(--d-focus-ring);

    --shadow-xs:         var(--d-shadow-xs);
    --shadow-sm:         var(--d-shadow-sm);
    --shadow-md:         var(--d-shadow-md);
    --shadow-lg:         var(--d-shadow-lg);
    --shadow-xl:         var(--d-shadow-xl);

    --jrs-blue:         var(--d-brand);
    --jrs-orange:       var(--d-accent);
  }
}

/* 2/2 - jawny wybor uzytkownika, niezaleznie od ustawienia systemu */
:root[data-theme="dark"] {
  --bg:                var(--d-bg);
  --bg-elevated:       var(--d-bg-elevated);
  --bg-sunken:         var(--d-bg-sunken);
  --bg-inset:          var(--d-bg-inset);

  --text:              var(--d-text);
  --text-muted:        var(--d-text-muted);
  --text-subtle:       var(--d-text-subtle);
  --text-on-brand:     var(--d-text-on-brand);

  --border:            var(--d-border);
  --border-strong:     var(--d-border-strong);
  --rule:              var(--d-rule);

  --brand:             var(--d-brand);
  --brand-hover:       var(--d-brand-hover);
  --brand-subtle:      var(--d-brand-subtle);
  --brand-text:        var(--d-brand-text);

  --accent:            var(--d-accent);
  --accent-solid:      var(--d-accent-solid);
  --accent-hover:      var(--d-accent-hover);
  --accent-subtle:     var(--d-accent-subtle);
  --accent-text:       var(--d-accent-text);
  --text-on-accent:    var(--d-text-on-accent);

  --select-arrow:      var(--d-select-arrow);

  --success:           var(--d-success);
  --success-subtle:    var(--d-success-subtle);
  --warning:           var(--d-warning);
  --warning-subtle:    var(--d-warning-subtle);
  --danger:            var(--d-danger);
  --danger-hover:      var(--d-danger-hover);
  --danger-subtle:     var(--d-danger-subtle);

  --focus-ring:        var(--d-focus-ring);

  --shadow-xs:         var(--d-shadow-xs);
  --shadow-sm:         var(--d-shadow-sm);
  --shadow-md:         var(--d-shadow-md);
  --shadow-lg:         var(--d-shadow-lg);
  --shadow-xl:         var(--d-shadow-xl);

  --jrs-blue:         var(--d-brand);
  --jrs-orange:       var(--d-accent);
}


/* =========================================================================
   2. KROJE PISMA

   Podzial przez unicode-range: przegladarka pobiera wylacznie te pliki,
   ktorych znaki naprawde wystapily na stronie. Kursant z Polski nie sciaga
   cyrylicy, kursant z Ukrainy nie sciaga arabskiego.

   Pliki sa przyciete do alfabetow tych siedmiu jezykow, ktore obsluguje
   LANGUAGES w settings.py, a nie do znakow uzytych w interfejsie - imiona
   i nazwy kursow wpisuja ludzie i moga zawierac dowolna litere alfabetu.
   ========================================================================= */

@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/montserrat/montserrat-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/montserrat/montserrat-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                 U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/montserrat/montserrat-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Montserrat nie ma glifow arabskich. Bez tego kroju arabski i perski
   spadaly na dowolny font systemowy urzadzenia - stad ich "przypadkowy"
   wyglad na tle reszty interfejsu. */
@font-face {
  font-family: 'Noto Sans Arabic';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/noto-sans-arabic/noto-sans-arabic-arabic.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891,
                 U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011,
                 U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
}

/* --- Kroj naglowkowy: Literata ----------------------------------------
   Edited by: Kryst 11.08.26 - redesign strony kursanta.

   Montserrat jest krojem geometrycznym i bardzo neutralnym: swietnie
   znosi interfejs, ale nie ma czym mowic w naglowku. Szeryf obok niego
   zmienia rejestr calej strony z "formularz urzedowy" na "rzecz zrobiona
   dla kogos". To jest cala teza tego redesignu i dlatego to jedyna
   dolozona rodzina.

   Trzy decyzje, ktore warto znac przy wymianie tego pliku:

   1. Os optyczna jest PRZYPIETA do opsz=72, czyli do rozmiaru
      plakatowego. Ten kroj nie wystepuje nigdzie w tekscie ciaglym -
      tylko w naglowkach - wiec rozmiar optyczny do tekstu bylby
      zmarnowany. Przypiecie zbija plik z 199 KB (pelna zmienna) do
      85 KB, czyli PONIZEJ dwoch statycznych odmian (90 KB), a mimo to
      zostawia plynna os grubosci 400-700.
   2. Podzial przez unicode-range jak w Montserracie: czytelnik polskiego
      pobiera latin + latin-ext (67 KB), ukrainskiego latin + cyrillic
      (57 KB), arabskiego ani jednego z nich.
   3. Greka i wietnamski sa swiadomie NIE pobrane - LANGUAGES ich nie ma. */
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/literata/literata-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/literata/literata-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                 U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/literata/literata-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Odpowiednik naglowkowy dla arabskiego i perskiego.

   Bez tego kroju ar i fa dostalyby w naglowku ten sam Noto Sans Arabic,
   co w tekscie - czyli dokladnie te asymetrie, ktorej ten redesign ma
   nie robic: lacina awansuje do szeryfu, a pismo arabskie zostaje na
   bezszeryfowym. Naskh jest w pismie arabskim rejestrem ksiazkowym,
   wiec pelni tu te sama role, co Literata przy lacinie, i obejmuje
   rowniez litery perskie (پ چ ژ گ). */
@font-face {
  font-family: 'Noto Naskh Arabic';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/noto-naskh-arabic/noto-naskh-arabic-arabic.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891,
                 U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011,
                 U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
}


/* =========================================================================
   3. RESET I PODSTAWY
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Kotwica nie chowa sie pod przyklejona nawigacja */
  scroll-padding-top: calc(var(--nav-height) + var(--space-4));
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: 400;
  line-height: var(--leading-normal);
  letter-spacing: var(--tracking-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-synthesis-weight: none;
  overflow-x: hidden;
}

/* Perski i arabski dostaja odrobine wiecej interlinii - wysokie wyrozniki
   i znaki diakrytyczne potrzebuja miejsca, inaczej wiersze sie zlepiaja. */
:root:lang(ar) body, :root:lang(fa) body { line-height: var(--leading-relaxed); }

img, svg, video, canvas { max-width: 100%; height: auto; display: block; }

/* Flagi maja stale proporcje - bez tego "height: auto" wyzej je splaszcza */
.flag { width: 24px; height: 16px; border-radius: 2px; object-fit: cover; flex: none; }

a { color: var(--brand-text); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { color: var(--accent-text); }

button, input, select, textarea { font: inherit; color: inherit; }

/* Widoczny obrys dla klawiatury. Stary arkusz gasil go czterokrotnie
   przez "outline: none" - dla osoby poruszajacej sie tabem strona stawala
   sie nieczytelna. */
:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--accent); color: #fff; }

/* Pasek przewijania w motywie ciemnym nie ma byc biala szpara */
/* color-scheme steruje tym, czego nie malujemy sami: paskiem przewijania,
   domyslnym tlem pola formularza, kalendarzem w <input type="date">.
   Musi isc za motywem we wszystkich trzech stanach, inaczej jawnie
   wybrany ciemny dostaje bialy pasek przewijania przy ciemnej stronie. */
:root { color-scheme: light; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
}
:root[data-theme="dark"]  { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

/* --- Ziarno ------------------------------------------------------------
   Edited by: Kryst 11.08.26 (REDESIGN.md D7)

   Najtansza rzecz w calym redesignie i jedna z najbardziej widocznych.
   Plaskie #faf7f2 czyta sie jako domyslna powierzchnia przegladarki;
   ta sama barwa z ledwo wyczuwalnym ziarnem czyta sie jako papier.
   Cala reszta systemu designu jest zbudowana wokol pary "cieply papier
   + chlodny atrament" (patrz tokeny) - bez faktury ta metafora nie ma
   sie na czym oprzec.

   Szum jest generowany przez feTurbulence w data-URI, wiec nie ma tu
   ani jednego zadania sieciowego i nic sie nie skaluje wraz z rozmiarem
   okna. `position: fixed` sprawia, ze ziarno nie przesuwa sie przy
   przewijaniu - tak zachowuje sie ziarno filmu, nie tapeta.

   Kryjacosc jest niska celowo: powyzej ~0,05 zaczyna zjadac czytelnosc
   tekstu, a to jest strona, na ktorej ktos czyta w obcym alfabecie.
   W motywie ciemnym ziarno swieci zamiast brudzic, wiec schodzi nizej. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 200;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body::after { opacity: 0.02; }
}
:root[data-theme="dark"] body::after { opacity: 0.02; }

/* Wydruk nie ma ziarna - to jest faktura ekranu, na papierze bylaby brudem */
@media print {
  body::after { display: none; }
}


/* Skok do tresci - pierwszy element w tabulacji, niewidoczny do czasu fokusu */
.skip-link {
  position: absolute;
  inset-inline-start: var(--space-4);
  inset-block-start: calc(var(--space-4) * -10);
  z-index: 200;
  padding: var(--space-3) var(--space-5);
  background: var(--brand);
  color: #fff;
  border-radius: var(--radius-md);
  font-weight: 600;
  text-decoration: none;
  transition: inset-block-start var(--duration-base) var(--ease-out);
}
.skip-link:focus {
  inset-block-start: var(--space-4);
  color: #fff;
}


/* =========================================================================
   4. TYPOGRAFIA
   ========================================================================= */

/* Naglowki chodza szeryfem, tekst bezszeryfowym.

   Ujemne swiatlo miedzyliterowe (--tracking-tight, -0.02em) bylo dobrane
   pod Montserrata - kroj geometryczny, ktory przy duzym stopniu robi sie
   rozstrzelony. Szeryf ma inny problem: sciskanie zlepia mu szeryfy
   sasiednich liter. Stad --tracking-snug i wagi 600, nie 800: Literata
   konczy os grubosci na 700, a 800 dawaloby syntetyczne pogrubienie,
   czyli rozmazany kontur (font-synthesis-weight: none w body chroni
   przed tym, ale skutkiem byloby po prostu 700 udajace 800). */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  margin-block: 0 var(--space-4);
  font-weight: 600;
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-snug);
  color: var(--text);
  text-wrap: balance;
}

/* h5 i h6 to nie naglowki tresci, tylko etykiety interfejsu - w panelu
   siedza nad tabelami. Zostaja bezszeryfowe. */
h5, h6 {
  margin-block: 0 var(--space-4);
  font-weight: 700;
  line-height: var(--leading-tight);
  color: var(--text);
  text-wrap: balance;
}

h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); font-weight: 650; }
h5 { font-size: var(--text-base); letter-spacing: var(--tracking-snug); }
h6 { font-size: var(--text-sm); letter-spacing: var(--tracking-wide); text-transform: uppercase; color: var(--text-muted); }

p { margin-block: 0 var(--space-4); text-wrap: pretty; }
p:last-child { margin-block-end: 0; }

strong, b { font-weight: 700; }

small { font-size: var(--text-sm); }

/* Tytul strony - duzy, z akcentem na wyroznionym slowie tam, gdzie
   szablon oznaczy je przez <em>.

   Waga 500, nie 700. Szeryf o wysokim kontrascie (Literata na osi
   optycznej 72) jest przy duzym stopniu mocny sam z siebie; pogrubianie
   go zabiera mu wlasnie ten kontrast, przez ktory wyglada na zrobiony,
   a nie wygenerowany. Interlinia schodzi ponizej 1 - przy 4.75rem
   domyslne 1.15 rozjezdza dwa wiersze tytulu na pol ekranu. */
.display {
  font-family: var(--font-display);
  font-size: var(--text-5xl);
  font-weight: 500;
  line-height: 0.98;
  letter-spacing: -0.025em;
  margin-block-end: var(--space-6);
  text-wrap: balance;
}
/* Pismo arabskie nie ma wersalikow ani tak niskiej interlinii - znaki
   diakrytyczne siadaja wysoko nad wierszem i przy 0.98 wchodza na
   wiersz poprzedni. */
:root:lang(ar) .display, :root:lang(fa) .display {
  line-height: 1.35;
  letter-spacing: 0;
}

.display em {
  font-style: normal;
  color: var(--accent-text);
}

.lead {
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
  max-width: var(--container-text);
  text-wrap: pretty;
}

/* `.eyebrow` (nadtytul nad naglowkiem) zszedl 12.08.26 razem z ostatnim
   ekranem, ktory go uzywal - masthead na `no_registration`. Nowy nadtytul
   w kadrze to `.frame-caption` / `.frame-open-label`, a te maja wlasne
   wyjatki dla naskh. */
/* Wersaliki w pismie arabskim nie istnieja - text-transform tylko halasuje */
:root:lang(ar) h6, :root:lang(fa) h6 { text-transform: none; letter-spacing: 0; }

.text-muted   { color: var(--text-muted); }
.text-subtle  { color: var(--text-subtle); }
.text-accent  { color: var(--accent-text); }
.text-brand   { color: var(--brand-text); }
.text-danger  { color: var(--danger); }
.text-success { color: var(--success); }


/* =========================================================================
   5. UKLAD I POJEMNIKI
   ========================================================================= */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-5);
}

@media (min-width: 48rem) {
  .container { padding-inline: var(--space-8); }
}

.container-wide { max-width: var(--container-wide); }

main {
  display: block;
  padding-block: var(--space-10) var(--space-20);
  min-height: 60vh;
}

.stack > * + * { margin-block-start: var(--space-4); }
.stack-lg > * + * { margin-block-start: var(--space-8); }

.section { padding-block: var(--space-12); }

.prose { max-width: var(--container-text); }


/* =========================================================================
   6. OZDOBA

   Ta sekcja trzymala .arcs i .aurora - dwa okregi wsuniete za rogi
   pojemnika oraz poswiate z dwoch gradientow radialnych. Usuniete przy
   redesignie 11.08.26 (Kryst) razem z ostatnim szablonem, ktory ich
   uzywal.

   Powod nie byl wydajnosciowy - kosztowaly tyle co nic. Z .arcs zostawaly
   na ekranie dwa przypadkowe wycinki kola, czytane raczej jako niedomkniete
   tlo niz jako motyw mostu z logo. Poswiata przy nowym, cieplym papierze
   robila sie po prostu brudna.

   Motyw z logo zostal, ale jako rysunek w kadrze: horyzont w sekcji 11
   (`.arc`, `templates/partials/kadr_obraz.html`). Rysunek mostu (`.bridge`
   plus `partials/bridge.html`), ktory stal tu do 12.08.26, zszedl razem
   z ostatnim ekranem na masztach - `no_registration` weszlo w kadr.
   ========================================================================= */


/* =========================================================================
   7. PRZYCISKI
   ========================================================================= */

.btn {
  --btn-bg: var(--brand);
  --btn-fg: #fff;
  --btn-bg-hover: var(--brand-hover);
  --btn-border: transparent;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: var(--tap-min);
  padding: var(--space-3) var(--space-6);
  border: 1.5px solid var(--btn-border);
  border-radius: var(--radius-full);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: var(--tracking-snug);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  position: relative;
  isolation: isolate;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color     var(--duration-fast) var(--ease-out),
    color            var(--duration-fast) var(--ease-out),
    box-shadow       var(--duration-base) var(--ease-out),
    transform        var(--duration-fast) var(--ease-out);
}

.btn:hover { background: var(--btn-bg-hover); color: var(--btn-fg); }
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

/* Wariant glowny - pomaranczowy. Tlo z odcienia 600, nie 500: bialy tekst
   na oryginalnym #D97014 mial kontrast 3.4:1, ponizej progu WCAG AA. */
.btn-primary {
  --btn-bg: var(--accent-solid);
  --btn-bg-hover: var(--accent-hover);
  --btn-fg: var(--text-on-accent);
  box-shadow: var(--shadow-md);
}
.btn-primary:hover { box-shadow: var(--shadow-lg); }

.btn-secondary {
  --btn-bg: var(--brand);
  --btn-bg-hover: var(--brand-hover);
  box-shadow: var(--shadow-sm);
}

.btn-outline {
  --btn-bg: transparent;
  --btn-fg: var(--brand-text);
  --btn-bg-hover: var(--brand-subtle);
  --btn-border: var(--border-strong);
}
.btn-outline:hover { color: var(--brand-text); border-color: var(--brand); }

.btn-ghost {
  --btn-bg: transparent;
  --btn-fg: var(--text-muted);
  --btn-bg-hover: var(--bg-inset);
}
.btn-ghost:hover { color: var(--text); }

.btn-danger {
  --btn-bg: var(--danger);
  --btn-bg-hover: var(--danger-hover);
  --btn-fg: #fff;
}

/* Wariant kasujacy przy wierszu tabeli.
   Edited by: Kryst 12.08.26 (PLAN.md P15)

   Ta nazwa byla w panelu uzywana JEDENASCIE razy i nie byla zdefiniowana
   nigdzie. Definiuje ja Bootstrap, ale laduje sie przed jrs.css, a `.btn`
   ma te sama specyficznosc i ustawia wlasne tlo - wiec wygrywal pozniejszy
   arkusz i kazdy przycisk "Usun" renderowal sie jako PELNY NIEBIESKI, czyli
   nie do odroznienia od "Zapisz" obok. Na ekranie, na ktorym jedno kasuje
   calego czlowieka z jego deklaracjami, a drugie zapisuje poziom.

   Obrys, a nie pelne tlo: przy kilkudziesieciu wierszach tabela z rzedem
   czerwonych plam przestaje cokolwiek znaczyc - dokladnie tak samo jak
   lista kontrolna swiecaca zawsze na czerwono. Pelny czerwony (`btn-danger`)
   zostaje dla kasowania, ktore jest glownym dzialaniem na swoim ekranie. */
.btn-outline-danger {
  --btn-bg: transparent;
  --btn-fg: var(--danger);
  --btn-bg-hover: var(--danger-subtle);
  --btn-border: var(--danger);
}
.btn-outline-danger:hover { color: var(--danger); border-color: var(--danger); }

.btn-lg {
  min-block-size: 3.5rem;
  padding: var(--space-4) var(--space-8);
  font-size: var(--text-lg);
}
.btn-sm {
  min-block-size: 2.25rem;
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-sm);
  border-radius: var(--radius-full);
}
.btn-block { display: flex; inline-size: 100%; }

.btn .icon { inline-size: 1.15em; block-size: 1.15em; flex: none; }

/* Strzalka w przycisku wiodacym drgnie na hover - w RTL w druga strone */
.btn .icon-arrow { transition: transform var(--duration-base) var(--ease-spring); }
.btn:hover .icon-arrow { transform: translateX(0.2em); }
:dir(rtl) .btn:hover .icon-arrow { transform: translateX(-0.2em); }

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-8);
}
.button-row .btn { flex: 1 1 12rem; }


/* =========================================================================
   8. FORMULARZE
   ========================================================================= */

.form-group { margin-block-end: var(--space-6); }

label,
.form-label {
  display: block;
  margin-block-end: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  letter-spacing: var(--tracking-snug);
}

.form-control,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="tel"],
input[type="number"],
input[type="date"],
input[type="time"],
input[type="search"],
input[type="url"],
select,
textarea {
  inline-size: 100%;
  min-block-size: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  /* Edited by: Kryst 11.08.26 - redesign.
     Pole ma tlo papieru, nie biale. Formularz lezy na bialej plachcie
     (.panel), wiec biale pole na bialym tle bylo widoczne wylacznie
     dzieki obramowaniu - a to znaczy, ze przy szybkim spojrzeniu nie
     bylo widac, gdzie sie klika. Cieple wglebienie widac od razu,
     i rozjasnia sie do bieli dopiero pod kursorem. */
  background: var(--bg);
  color: var(--text);
  border: 1.5px solid var(--border-strong);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow   var(--duration-fast) var(--ease-out),
    background   var(--duration-fast) var(--ease-out);
}

textarea { min-block-size: 7rem; resize: vertical; }

.form-control::placeholder,
input::placeholder,
textarea::placeholder { color: var(--text-subtle); opacity: 1; }

.form-control:hover,
input:hover, select:hover, textarea:hover {
  border-color: var(--text-subtle);
  background: var(--bg-elevated);
}

.form-control:focus,
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--bg-elevated);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent);
}
/* Bez color-mix - sam mocniejszy obrys, nadal czytelny */
@supports not (box-shadow: 0 0 0 4px color-mix(in srgb, red 20%, transparent)) {
  .form-control:focus, input:focus, select:focus, textarea:focus {
    border-width: 2.5px;
  }
}

select {
  appearance: none;
  padding-inline-end: var(--space-10);
  background-image: var(--select-arrow);
  background-repeat: no-repeat;
  background-position: right var(--space-4) center;
  background-size: 1rem;
}
:dir(rtl) select { background-position: left var(--space-4) center; }

.form-text, .help-text {
  display: block;
  margin-block-start: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* Pole z bledem - kolor plus ikona, nie sam kolor */
.form-control.is-invalid,
input.is-invalid, select.is-invalid, textarea.is-invalid {
  border-color: var(--danger);
}
.field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--danger);
}
.field-error .icon { inline-size: 1.1em; block-size: 1.1em; flex: none; margin-block-start: 0.15em; }

/* --- Zgoda: karta z polem wyboru --------------------------------------
   Cala karta jest celem klikniecia. Tekst zgody bywa dlugi, wiec pole
   trzyma sie gory, a nie srodka. */
.consent-card {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-4) var(--space-5);
  background: var(--bg-elevated);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    background   var(--duration-fast) var(--ease-out),
    box-shadow   var(--duration-fast) var(--ease-out);
}
.consent-card:hover { border-color: var(--accent); background: var(--accent-subtle); }
.consent-card:has(input:checked) { border-color: var(--accent); background: var(--accent-subtle); }
.consent-card:has(input:focus-visible) {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}
.consent-card label {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text);
  cursor: pointer;
}

input[type="checkbox"],
input[type="radio"] {
  inline-size: 1.5rem;
  block-size: 1.5rem;
  min-block-size: 0;
  margin: 0;
  padding: 0;
  accent-color: var(--accent-solid);
  cursor: pointer;
  flex: none;
}

/* --- Grupa wyboru: karty zamiast golych krazkow ----------------------- */
.choice-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.choice {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--tap-min);
  padding: var(--space-3) var(--space-5);
  background: var(--bg-elevated);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-full);
  cursor: pointer;
  font-weight: 500;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    background   var(--duration-fast) var(--ease-out),
    color        var(--duration-fast) var(--ease-out);
}
.choice:hover { border-color: var(--accent); background: var(--accent-subtle); }
.choice:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-subtle);
  color: var(--accent-text);
  font-weight: 600;
}
.choice:has(input:focus-visible) { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.choice label { margin: 0; font-weight: inherit; cursor: pointer; }

.captcha-container {
  display: flex;
  justify-content: center;
  margin-block: var(--space-8);
}


/* =========================================================================
   9. KARTY I POWIERZCHNIE
   ========================================================================= */

.card {
  position: relative;
  padding: var(--space-6);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

@media (min-width: 48rem) {
  .card { padding: var(--space-8); }
}

.card-hover {
  transition:
    transform  var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease-out);
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .card-hover:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-lg);
    border-color: var(--border-strong);
  }
}

/* Panel - glowna plachta tresci.

   Edited by: Kryst 11.08.26 - redesign.

   Na ekranach formularza, testu i potwierdzenia biala plachta zostaje,
   i to swiadomie: tam kursant COS WYPELNIA, wiec kartka lezaca na
   papierze jest wlasciwa metafora. Zmienil sie tylko rejestr - mniej
   cienia, wiecej krawedzi - oraz pasek u gory.

   Pasek jest odpowiednikiem nadruku na papierze firmowym: jedna rzecz,
   ktora mowi "to jest dokument stad", bez ani jednego slowa do
   przetlumaczenia. Konczy sie po 6rem zamiast biec przez cala szerokosc,
   bo pelna linia czytalaby sie jak obramowanie okna przegladarki. */
.panel {
  position: relative;
  overflow: hidden;
  padding: var(--space-10) var(--space-6) var(--space-8);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.panel > *:not(.arcs):not(.aurora) { position: relative; z-index: 1; }

.panel::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 6rem;
  block-size: 3px;
  background: linear-gradient(to right, var(--accent-solid), var(--brand));
  z-index: 2;
}
:dir(rtl) .panel::before {
  background: linear-gradient(to left, var(--accent-solid), var(--brand));
}

@media (min-width: 48rem) {
  .panel {
    padding: var(--space-12) var(--space-10) var(--space-10);
    border-radius: var(--radius-xl);
  }
}

.card-icon {
  display: grid;
  place-items: center;
  inline-size: 3rem;
  block-size: 3rem;
  margin-block-end: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--accent-subtle);
  color: var(--accent-text);
}
.card-icon .icon { inline-size: 1.5rem; block-size: 1.5rem; }

.card-icon-brand { background: var(--brand-subtle); color: var(--brand-text); }

/* Siatka kart - auto-fit, wiec sama sie lamie bez punktow granicznych */
.card-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}


/* =========================================================================
   10. NAWIGACJA
   ========================================================================= */

.nav {
  position: sticky;
  inset-block-start: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--bg-elevated) 88%, transparent);
  border-block-end: 1px solid var(--border);
}

@supports (backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px)) {
  .nav {
    -webkit-backdrop-filter: saturate(1.6) blur(12px);
    backdrop-filter: saturate(1.6) blur(12px);
  }
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .nav { background: var(--bg-elevated); }
}

.nav-inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-block-size: var(--nav-height);
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-5);
}

/* --- Pasek, ktory przestaje byc paskiem (sciezka kursanta) --------------
   Edited by: Kryst 11.08.26 (REDESIGN.md D7)

   Bialy pas z obramowaniem, przyklejony do gory kremowej strony, byl
   jedynym obiektem mowiacym "to aplikacja z naglowkiem". Przy trzech
   kontrolkach, z ktorych jedna (logowanie personelu) nie jest dla
   odwiedzajacego, pas kosztowal wiecej niz dawal. Zdjete: tlo, kreska
   i `sticky` - zostaje sama zawartosc lezaca na papierze.

   Panel tego nie dostaje: tam naglowek jest nawigacja miedzy szescioma
   ekranami i ma zostac pod reka przy przewinietej tabeli. Klase dokleja
   base.html pod warunkiem `not PANEL`.
   ========================================================================= */
/* Edited by: Kryst 12.08.26 - lista jezykow chowala sie pod kafelek.

   `position: static` zabralo paskowi nie tylko przyklejenie, ale i sens
   `z-index: 100` z reguly wyzej - z-index dziala wylacznie na elementach
   pozycjonowanych. Samo w sobie nie byloby to grozne, gdyby nie to, ze
   `.nav-plain` NIE zdejmowalo `backdrop-filter` (stoi w @supports przy
   `.nav`, dla przyklejonego paska panelu). A `backdrop-filter` inny niz
   `none` tworzy kontekst nakladania - wiec caly naglowek malowal sie na
   poziomie zwyklego przeplywu, czyli POD `.panel`, ktory jest `relative`.
   Rozwinieta lista osmiu jezykow znikala za gorna krawedzia kafelka po
   trzeciej pozycji, a jej wlasne `z-index: 110` nie mialo jak pomoc:
   dzialalo w srodku uwiezionego kontekstu.

   Stad dwie rzeczy naraz. Rozmycie schodzi, bo na przezroczystym pasku,
   ktory sie nie przewija, nie ma czego rozmywac - zostawalo tylko po to,
   zeby psuc nakladanie. `relative` zostaje, zeby `z-index: 100` znowu cos
   znaczyl: pasek ma byc nad trescia, pod szuflada (150) i oknem (160).
   Bez offsetow, wiec uklad wyglada tak samo jak przy `static`. */
.nav-plain {
  position: relative;
  z-index: 100;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-block-end: 0;
}

/* Kontrolki tez schodza z obwodek. Obwodka mowi "to jest przycisk"
   w gestym interfejsie panelu; na plakacie z jednym przyciskiem zapisu
   jest drugim, slabszym prostokatem obok pierwszego. Cel dotykowy
   zostaje ten sam - znika tylko obramowanie i tlo. */
.nav-plain .theme-toggle,
.nav-plain .nav-toggle {
  border-color: transparent;
  background: none;
  color: var(--text-subtle);
}
.nav-plain .theme-toggle:hover,
.nav-plain .nav-toggle:hover {
  border-color: transparent;
  background: none;
  color: var(--text);
}
@media (min-width: 48rem) {
  .nav-inner { padding-inline: var(--space-8); }
}

.nav-logo {
  display: inline-flex;
  align-items: center;
  flex: none;
  border-radius: var(--radius-sm);
}
.nav-logo img { block-size: 2.5rem; inline-size: auto; }

.nav-spacer { flex: 1 1 auto; }

.nav-links {
  display: none;
  align-items: center;
  gap: var(--space-1);
}
@media (min-width: 75rem) {
  .nav-links { display: flex; }
}

.nav-link {
  display: inline-flex;
  align-items: center;
  min-block-size: 2.5rem;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}
.nav-link:hover { background: var(--bg-inset); color: var(--text); }
.nav-link[aria-current="page"] { color: var(--brand-text); background: var(--brand-subtle); }

.nav-user {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-inline-start: var(--space-4);
  margin-inline-start: var(--space-2);
  border-inline-start: 1px solid var(--border);
}
.nav-user-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

/* --- Wybor jezyka -----------------------------------------------------
   Zwykly <details>. Zamyka sie Esc i klawiszem, dziala bez JavaScriptu -
   poprzednia wersja byla recznie napisanym dropdownem na trzech
   nasluchiwaczach zdarzen. */
.lang {
  position: relative;
  flex: none;
}
.lang > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 2.75rem;
  padding: var(--space-2) var(--space-3);
  border: 1.5px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--bg-elevated);
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  white-space: nowrap;
  transition: border-color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}
.lang > summary::-webkit-details-marker { display: none; }
.lang > summary::marker { content: ""; }
.lang > summary:hover { border-color: var(--accent); background: var(--accent-subtle); }
.lang[open] > summary { border-color: var(--accent); }

.lang-caret {
  inline-size: 0.85rem;
  block-size: 0.85rem;
  color: var(--text-subtle);
  transition: transform var(--duration-base) var(--ease-out);
}
.lang[open] .lang-caret { transform: rotate(180deg); }

.lang-name { display: none; }
@media (min-width: 30rem) { .lang-name { display: inline; } }

.lang-menu {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2));
  inset-inline-end: 0;
  z-index: 110;
  min-inline-size: 13rem;
  padding: var(--space-2);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xl);
}

@media (prefers-reduced-motion: no-preference) {
  .lang[open] .lang-menu {
    animation: menu-in var(--duration-base) var(--ease-out) both;
  }
}
@keyframes menu-in {
  from { opacity: 0; transform: translateY(-0.5rem) scale(0.97); }
  to   { opacity: 1; transform: none; }
}

.lang-option {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-out);
}
.lang-option:hover { background: var(--bg-inset); color: var(--text); }
.lang-option[aria-current="true"] {
  background: var(--accent-subtle);
  color: var(--accent-text);
  font-weight: 700;
}


/* Edited by: Kryst 11.08.26 (REDESIGN.md D7)
   Kadr ma rozlozony wybor jezyka - wstege osmiu pism u dolu (`.hellos`).
   Zwiniety globus w pasku nawigacji jest wtedy DRUGA kontrolka do tej
   samej rzeczy - a dwa razy ten sam wybor na jednym ekranie jest gorszy
   niz ktorykolwiek z nich osobno: kaze zgadywac, czym sie roznia.

   Znika ten zwiniety, nie rozlozony: dla kogos, kto nie czyta lacinka,
   ikona globusa jest zagadka, a powitanie wlasnym pismem nie jest.

   Bez wsparcia dla :has() zostaja obie - czyli stan sprzed tej zmiany,
   brzydki, ale dzialajacy.

   Edited by: Kryst 12.08.26 - `.langbar` wypada z listy selektorow.
   Rozlozony pasek jezykow byl juz tylko na `no_registration`, a ten ekran
   wszedl w kadr i ma wstege jak strona glowna. Zostaje jedna nazwa. */
body:has(.hellos) .nav .lang { display: none; }


/* --- Przelacznik motywu -------------------------------------------------
   Edited by: Kryst 11.08.26

   Powod, dla ktorego to nie jest gadzet: kursant wypelnia formularz
   w biurze JRS, na CUDZYM komputerze. Motyw szedl dotad wylacznie
   z ustawienia systemu tej maszyny - czyli z ustawienia, ktorego on nie
   zmieni i o ktore nie poprosi. Przy oprawie zaprojektowanej pod motyw
   jasny znaczylo to, ze czesc ludzi nigdy jej nie zobaczy.

   Przycisk jest zwyklym <button> w nawigacji, obok wyboru jezyka.
   W znacznikach stoi z atrybutem hidden, a zdejmuje go dopiero nav.js -
   bez JavaScriptu nie mialby czym dzialac, a kontrolka, ktora nic nie
   robi, jest gorsza niz jej brak. Siedzi w HTML-u, a nie jest tworzona
   skryptem, zeby etykieta dla czytnika ekranu szla przez gettext razem
   z reszta interfejsu. Bez JS-a motyw idzie za ustawieniem systemu,
   czyli dokladnie tak jak przed ta zmiana.
   ========================================================================= */
.theme-toggle {
  display: inline-grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  flex: none;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--bg-elevated);
  color: var(--text-muted);
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    background   var(--duration-fast) var(--ease-out),
    color        var(--duration-fast) var(--ease-out);
}
.theme-toggle:hover {
  border-color: var(--accent);
  background: var(--accent-subtle);
  color: var(--accent-text);
}
.theme-toggle .icon { inline-size: 1.15rem; block-size: 1.15rem; }

/* Ikona pokazuje motyw DOCELOWY, nie biezacy - patrz komentarz przy
   symbolach w partials/icons.html. Przy jasnym widac ksiezyc.

   Warunki musza pokryc te same trzy stany, co tokeny kolorow, inaczej
   przycisk w jawnie wybranym ciemnym pokazywalby ikone jasnego. */
.theme-toggle .i-sun  { display: none; }
.theme-toggle .i-moon { display: block; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .i-sun  { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .i-moon { display: none; }
}
:root[data-theme="dark"] .theme-toggle .i-sun  { display: block; }
:root[data-theme="dark"] .theme-toggle .i-moon { display: none; }

/* Jawny jasny musi wygrac z regula spod @media - wystepuje pozniej
   i ma ten sam ciezar, wiec wygrywa kolejnoscia. */
:root[data-theme="light"] .theme-toggle .i-sun  { display: none; }
:root[data-theme="light"] .theme-toggle .i-moon { display: block; }


/* --- Logowanie dla personelu -------------------------------------------
   Edited by: Kryst 11.08.26

   Bylo kolem z ikona klucza, bez slowa, tuz obok drugiego takiego samego
   kola (przelacznik motywu). Dwa identyczne kola obok siebie nie tworza
   hierarchii - tworza zagadke, i to na stronie, ktora w ogole nie jest dla
   personelu.

   Teraz odnosnik z napisem. Napis chowa sie ponizej 48rem, ale NIE przez
   `display: none` - schodzi do stanu widocznego wylacznie dla czytnika
   ekranu, bo bez niego zostaje odnosnik bez nazwy dostepnej. */
.nav-login {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
  min-block-size: var(--tap-min);
  padding-inline: var(--space-3);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-subtle);
  text-decoration: none;
  transition:
    background var(--duration-fast) var(--ease-out),
    color      var(--duration-fast) var(--ease-out);
}
.nav-login:hover {
  background: var(--bg-inset);
  color: var(--text);
}
.nav-login .icon { inline-size: 1.125rem; block-size: 1.125rem; }

@media (max-width: 47.999rem) {
  .nav-login { padding-inline: var(--space-2); }
  .nav-login-text {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}


/* --- Menu mobilne ------------------------------------------------------ */
.nav-toggle {
  display: inline-grid;
  place-items: center;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  border: 1.5px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-elevated);
  cursor: pointer;
  color: var(--text);
}
@media (min-width: 75rem) { .nav-toggle { display: none; } }
.nav-toggle .icon { inline-size: 1.35rem; block-size: 1.35rem; }

.nav-drawer {
  position: fixed;
  inset: 0;
  z-index: 150;
  display: grid;
  grid-template-rows: auto 1fr;
  background: var(--bg);
  padding: var(--space-5);
  overflow-y: auto;
}
.nav-drawer[hidden] { display: none; }

@media (prefers-reduced-motion: no-preference) {
  .nav-drawer:not([hidden]) { animation: drawer-in var(--duration-base) var(--ease-out) both; }
}
@keyframes drawer-in {
  from { opacity: 0; transform: translateY(-1rem); }
  to   { opacity: 1; transform: none; }
}

.nav-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block-end: var(--space-5);
  border-block-end: 1px solid var(--border);
}

.nav-drawer-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-block-start: var(--space-5);
}
.nav-drawer-links .nav-link {
  min-block-size: 3.25rem;
  padding-inline: var(--space-4);
  font-size: var(--text-lg);
}


/* =========================================================================
   11. SCIEZKA KURSANTA
   ========================================================================= */

/* MASZT, MOST I PASEK JEZYKOW ZESZLY 12.08.26 - CALA TROJKA NARAZ.
   Edited by: Kryst 12.08.26

   Stalo tu 340 linii: `.masthead` (plakat na pelna wysokosc okna ze
   strzalka "przewin nizej"), `.bridge` (przeslo mostu ze znaku JRS -
   siedem lin, siedemnascie wieszakow, poklad i kaskada rysowania)
   i `.langbar` (rozlozony wybor jezyka nad tytulem).

   Zeszly nie dlatego, ze byly zle, tylko dlatego, ze przestal istniec
   ostatni ekran, ktory ich uzywal: `no_registration` wszedl w kadr,
   a strona glowna zrobila to dzien wczesniej. Regula, ktorej nie
   dotyka zaden szablon, przy nastepnej zmianie kosztuje wylacznie
   czas na ustalenie, czy wolno ja ruszyc.

   ODPOWIEDNIKI W KADRZE, gdyby czegos szukac: plakat -> `.frame`,
   rysunek -> `.arc` (horyzont, `partials/kadr_obraz.html`), wybor
   jezyka -> `.hellos` (`partials/kadr_stopka.html`).

   Droga powrotu jest w historii: `git show HEAD:static/css/jrs.css`
   sprzed tego commitu plus `partials/bridge.html` i `partials/
   langbar.html` z tego samego miejsca. Diagnoza, dlaczego most czytal
   sie jako siatka tabeli, a nie jako konstrukcja, stoi w REDESIGN.md
   (przebieg pierwszy) - warto ja przeczytac, zanim wroci.
   ========================================================================= */


/* --- Strona glowna: kadr ------------------------------------------------
   Edited by: Kryst 11.08.26 - czwarte podejscie, i pierwsze, ktore nie jest
   strona.

   CO ZASTEPUJE
   Trzy poprzednie przebiegi (plakat z mostem, sciana osmiu powitan, ciemna
   plyta z sekcjami) roznily sie wygladem, a byly tym samym: STRONA. Naglowek,
   sekcje, rytm redakcyjny, tresc do przeczytania. Za kazdym razem lepsza,
   za kazdym razem nie ta rzecz.

   ZALOZENIE, Z KTOREGO WYNIKA CALA RESZTA
   Ta strona nie ma nic wyjasniac. Wiekszosc ludzi zapisuje sie na miejscu,
   przy pracowniku JRS, ktory mowi im wszystko, czego ta strona probowala
   dowiedziec sie napisami; reszte niesie mail po rejestracji. Zostaja wiec
   dwie funkcje - ZROBIC WRAZENIE i OTWORZYC DRZWI - i nic poza nimi:
   powitanie, przycisk, wybor jezyka, data szeptem, koperta w rogu.

   Zniknely: kroki naboru, oba pytania, akapit o podreczniku, stopka,
   przelacznik motywu (kadr jest ciemny zawsze) i PRZEWIJANIE. Pod dolna
   krawedzia okna nie ma juz niczego - to nie jest przeoczenie, tylko cala
   teza tego ekranu.

   DLACZEGO TO NIE JEST "CIEMNA SEKCJA HERO"
   Bo nie ma pojemnika, marginesu, zaokraglenia ani miary. Kadr idzie od
   krawedzi do krawedzi okna i ma dokladnie jego wysokosc; pasek nawigacji
   lezy NA nim jak napisy poczatkowe, nie nad nim. Roznica miedzy karta
   a kadrem jest dokladnie ta sama, co miedzy zdjeciem w ramce a oknem.

   OBRAZ BEZ ANI JEDNEGO PLIKU
   Zadnego zdjecia, zadnego zadania sieciowego (kursant bywa na pakiecie
   danych, a kazdy zewnetrzny adres to takze jego IP u kogos trzeciego).
   Obraz sklada sie z czterech warstw, wszystkie liczone przez GPU:
   trzy wielkie zrodla swiatla o roznych okresach dryfu, luk ze znaku JRS
   jako swietlisty slad, winieta i ziarno tasmy chodzace skokowo.

   OTWARCIE: SWIATLO -> SLOWO -> WSTEGA
   Luk rysuje sie swiatlem, powitanie wychodzi z tej linii (clip-path
   rozchodzacy sie od srodka wiersza), na koncu zapisuje sie osiem pism.
   Okolo 3 sekundy. Nigdy nie blokuje klikniecia: cala tresc jest w drzewie
   od pierwszej klatki, animacje ruszaja WYLACZNIE wyglad, a stanem
   bazowym kazdej regulki jest stan KONCOWY. Dlatego wystarczy zdjac
   animacje (`:root.bez-otwarcia`), zeby kadr byl od razu gotowy - bez
   ani jednej reguly opisujacej "stan po pominieciu".
   ========================================================================= */

/* Edited by: Kryst 11.08.26 - `body.kadr` zamiast `body:has(.frame)`.

   Uklad calego ekranu wisial na `:has()`. Roznica miedzy tym selektorem
   a klasa nie jest tu roznica wygladu: bez `:has()` kadr wraca do pudelka
   z 2rem marginesu, pasek staje NAD nim, a przelacznik motywu wraca na
   ciemne tlo, ktorym nie rzadzi. To nie jest gorsza wersja tego ekranu,
   to jest inny ekran. `:has()` zostaje tam, gdzie jest ulepszeniem
   (obrys na .consent-card, .choice, .answer). O ukladzie decyduje
   szablon - i on jeden wie, co renderuje. Klase daje base.html. */

/* Kadr wychodzi poza <main>, ktory jest pojemnikiem tekstowym. Zerujemy
   wiec miare, wyscielke i minimalna wysokosc - inaczej kadr dostaje 2rem
   bialego marginesu z boku i 5rem pod spodem, czyli przestaje byc kadrem. */
.kadr main {
  max-width: none;
  padding: 0;
  min-height: 0;
}

/* GRUNT POD KADREM.
   Edited by: Kryst 11.08.26

   `body` ma `min-height: 100dvh`, kadr - 100svh. Na telefonie to sa dwie
   ROZNE liczby: dvh liczy okno ze schowanym paskiem adresu, svh z
   pokazanym. Kremowe tlo strony wystawalo wiec spod kadru na wysokosc
   tego paska, a dokument stawal sie przewijalny - czyli ekran, ktorego
   cala teza brzmi "pod dolna krawedzia nie ma niczego", mial pod nia
   jasny pasek do przewiniecia. Rownamy min-height do svh i zabieramy
   powod do przewijania.

   Ciemny grunt zostaje mimo to, bo widac go jeszcze w jednym miejscu:
   przy odbiciu (rubber-band) na iOS zza kadru wychodzi tlo dokumentu.
   Krem mrugajacy zza nocnego obrazu widac za kazdym szarpnieciem palcem.
   `overscroll-behavior` gasi samo odbicie tam, gdzie jest wspierane. */
body.kadr {
  min-height: 100svh;
  background-color: #03060b;
  overscroll-behavior-y: none;
}

/* Globalne ziarno strony (body::after) jest dobrane pod jasny papier.
   Kadr jest ciemny w obu motywach, wiec bierze wartosc "nocna" - tak jak
   caly ekran, ktory motywu nie slucha. */
body.kadr::after { opacity: 0.02; }

/* Pasek lezy NA kadrze. `absolute`, nie `fixed`: strona nie przewija sie
   w ogole, wiec fixed niczego by nie dawal, a wyjmowalby pasek z kontekstu
   przy drukowaniu. */
body.kadr .nav {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  z-index: 3;
}
body.kadr .nav-inner {
  max-width: none;
  padding-inline: clamp(1.25rem, 4vw, 3.5rem);
}

/* Znak JRS jest granatowo-pomaranczowy i na nocnym kadrze gasnie. Monochrom
   bialy czyta sie tu jak napis na tasmie, a nie jak zgubione logo. */
body.kadr .nav-logo img {
  filter: brightness(0) invert(1);
  opacity: 0.88;
}

/* Edited by: Kryst 11.08.26 - 0.56 -> 0.72, i zostaje przy 0.72.

   Podniesione bylo z powodu, ktorego JUZ NIE MA: napis stoi w prawym
   gornym rogu, a tam lezala plama `glow-1`, najjasniejsze miejsce starego
   kadru. 0.56 dawalo 6.4:1 na granacie i okolo 4.4:1 na rozswietlonym
   brazie w rogu, czyli ponizej AA dla pisma tej wielkosci.

   Po zamianie swiatla na snop kierunkowy prawy gorny rog jest jednym
   z najciemniejszych miejsc ekranu - snop wchodzi od 2% do 50% szerokosci,
   czyli po przeciwnej stronie. Wartosc zostaje mimo to: jest teraz z duzym
   zapasem nad progiem, a obnizanie jej wroci tylko po to, zeby nastepna
   zmiana swiatla znow ja podniosla. Napis panelu na kadrze nie musi byc
   szeptem. */
body.kadr .nav-login { color: rgba(246, 241, 232, 0.72); }
body.kadr .nav-login:hover {
  background: rgba(246, 241, 232, 0.08);
  color: rgba(246, 241, 232, 0.92);
}

/* Przelacznik motywu na tym ekranie nie ma czym rzadzic - kadr jest ciemny
   w obu motywach. Na kazdej innej stronie zostaje. */
body.kadr .theme-toggle { display: none; }

.frame {
  /* Barwy kadru: noc marki. Granat JRS schodzacy do czerni, swiatlo
     w pomaranczu marki. Nie sa tokenami strony i nie zaleza od motywu. */
  --kadr-ink:       #f6f1e8;
  --kadr-ink-muted: rgba(246, 241, 232, 0.58);   /* 6.0:1 na dole kadru */
  --kadr-accent:    #f0a05a;    /* 7.2:1 na granacie */
  /* Edited by: Kryst 11.08.26 - #9dc4ff -> #aec6e0, odbarwienie.
     Poprzedni blekit przy 0.42 alfa czytal sie jako osobny, niebieski
     ODCINEK doklejony do horyzontu, a nie jako linia, ktora sie wychladza.
     Nasycenie z 39% na 22%: to samo zimno, bez zmiany tematu. */
  --kadr-cold:      #aec6e0;    /* zimny koniec horyzontu */
  --kadr-on-accent: #17120c;    /* 8.9:1 na powyzszym; biel dalaby 3.3:1 */

  /* BARWA PRZYCISKU - osobna od `--kadr-accent`, i to jest cel.
     Edited by: Kryst 11.08.26

     Przycisk chodzil na `--kadr-accent` (#f0a05a), czyli na barwie swiatla.
     Po zdjeciu poswiaty zostal z tego duzy, pastelowo-brzoskwiniowy owal
     na srodku bardzo ciemnego ekranu - najglosniejszy obiekt kadru i ostatni
     czysty podpis szablonu. Glebszy, bardziej nasycony pomarancz zdejmuje
     "cukierkowosc" i NIE zabiera widocznosci: #e8913f na nocnym gruncie ma
     luminancje 0.378, czyli jest jasniejszy od wszystkiego wokol.

     Ten ekran istnieje po to, zeby ktos kliknal ten przycisk, wiec chowanie
     go jest wykluczone - stad zostaje pelne wypelnienie, rozmiar `btn-lg`
     i szeroka wyscielka. Zmienia sie odcien, nie sila.

     Kontrasty policzone wobec `--kadr-on-accent` (#17120c):
       #e8913f -> 7.7:1  (AA i AAA dla tekstu tej wielkosci)
       #ffb475 -> 10.8:1 (stan :hover)
     `--kadr-accent` zostaje tam, gdzie ma byc jasny: obrys focusu, jezyk
     biezacy we wstedze, cieply koniec horyzontu. */
  --kadr-cta:       #e8913f;
  --kadr-cta-hover: #ffb475;

  /* TARCZA SWIATA - masa pod krawedzia horyzontu.
     Edited by: Kryst 12.08.26

     Dwa przystanki gradientu wypelniajacego tarcze. Musza byc WYRAZNIE
     ciemniejsze od nieba na tej samej wysokosci (#13253d na 76%), bo cala
     rzecz polega na skoku tonalnym: dopiero on zamienia krzywa narysowana
     na tle w krawedz czegos. #04080f przy #13253d to spadek luminancji
     mniej wiecej czterokrotny - widac go, a nie jest to czarny pas.

     Skutek uboczny jest po stronie dostepnosci i jest korzystny: wstega
     osmiu pism (krem przy 0.56 alfa) lezy teraz na ciemniejszym tle, wiec
     jej kontrast ROSNIE. Byla liczona na 5.8:1 na granacie. */
  --kadr-globe:      #04080f;
  --kadr-globe-deep: #010204;

  /* GEOMETRIA SWIATLA - trzy liczby, z ktorych zyje caly obraz.
     Edited by: Kryst 11.08.26 - przebudowa razem ze snopem.

     `--apex` to OS SNOPA: miejsce, w ktorym pasmo swiatla przecina gorna
     krawedz kadru, w procentach jego szerokosci. Ta sama liczba stoi
     w maskach i w `transform-origin`, zeby kolysanie ambientu obracalo snop
     wokol punktu wejscia, a nie przesuwalo go w poprzek kadru.

     Edited by: Kryst 12.08.26 - bylo to ZRODLO, teraz jest OS. 22% -> 26%.

     Snop rozchodzil sie z punktu (maska stozkowa, rozejscie 4:1), bo idea
     brzmiala "swiatlo przez uchylone drzwi" - czyli lampa dwa metry dalej.
     Idea brzmi teraz "slonce padajace na swiat", a slonce jest 150 mln km
     stad: jego promienie sa ROWNOLEGLE. Stozek mowilby, ze zrodlo stoi tuz
     za gorna krawedzia, czyli przeczylby calemu obrazowi. Maski sa wiec znow
     liniowe, a pasmo ma stala szerokosc.

     Z tego wynika ograniczenie na liczbe: maska liczy procenty od szerokosci
     ELEMENTU, a element ma szerokosc kadru, wiec polowa pasma nie moze byc
     szersza niz `--apex` - inaczej lewa krawedz wypada poza element i zostaje
     przycieta na twardo. Przy polowie pasma 23% potrzeba wiec co najmniej
     23%; 26% zostawia 3% zapasu.

     `--rake` to nachylenie. UWAGA NA ZNAK - poprzednia wersja miala go
     odwrotnie, a komentarz opisywal ruch, ktorego nie bylo. `rotate()` jest
     zgodny z ruchem wskazowek zegara, wiec wektor skierowany w dol (0,1)
     przechodzi przy kacie dodatnim na (-sin, cos), czyli w LEWO: snop
     schodzil pod pradem czytania. Ma schodzic w prawo, wiec kat jest
     UJEMNY. Po arabsku i persku dodatni.

     `--swiatlo-x` to miejsce, w ktorym snop dosiega horyzontu - czyli DZIEN
     na tarczy. Stad bierze polozenie kaluza swiatla i srodek winiety.
     Wyliczone, nie dobrane: przesuniecie w bok = (y + 30%) * tan(28st.),
     czyli na horyzoncie (72% wysokosci) 1.02H * 0.532 = 0.54H, a przy
     proporcji 16:9 to 30% szerokosci. Snop wchodzi w 26%, wiec dosiega
     krawedzi w okolicy 57%. Ta sama liczba zamknela dziure z audytu: kaluza
     stala na 94% wysokosci, czyli 22 punkty PONIZEJ linii, na ktora swiatlo
     mialo padac.

     Nachylenie i strona sa jedynymi rzeczami, ktore odbijaja sie przy RTL.
     Sama tarcza i atmosfera sa symetryczne i tak zostaja - swiat nie ma
     strony, ma ja tylko swiatlo.

     SKAD BIORA SIE TE LICZBY OD 12.08.26.
     Nachylenie nie jest juz dobrane - liczy sie z wysokosci slonca nad
     Warszawa w chwili zadania (`base/services/slonce.py`). Snop stoi stromo,
     kiedy slonce stoi wysoko, i klaDzie sie, kiedy schodzi ku zachodowi;
     dzien na tarczy wedruje przez to po horyzoncie z 45% na 74% szerokosci.
     Miejsce WEJSCIA snopa w kadr (`--apex`) zostaje stale i to jest warunek,
     na ktorym trzyma sie reszta: od niego zalezy, ze prawy gorny rog jest
     ciemny, a tam stoi "Staff login".

     WARTOSCI ZAPASOWE W `var()` TO KADR SPRZED TEJ ZMIANY. Strona
     wyrenderowana bez `{% swiatlo_kadru %}` - w szablonie, ktory tego
     znacznika nie wola, albo gdyby modul przestal istniec - wyglada
     dokladnie tak, jak wygladala 12.08.26 przed poludniem. To nie jest
     ozdobnik, tylko warunek: swiatlo w kadrze nie ma prawa zalezec od tego,
     czy cokolwiek policzylo sie poprawnie.

     `--lustro` odbija SAM GRZEBIEN promieni przy RTL. Odsuniecia promieni od
     osi sa podpisane w `.ray` raz, dla jednego kierunku; mnoznik -1 zamienia
     lewa strone peku na prawa bez powtarzania siedmiu regul. */
  --apex: 26%;
  --rake: calc(var(--slonce-kat, 28) * -1deg);
  --swiatlo-x: var(--slonce-x, 57%);
  --lustro: 1;

  /* Obrys focusu bierze sie normalnie z --focus-ring, czyli z pomaranczu
     marki (#d97014). Na granacie daje on 5:1, ale kadr ma tez rog zalany
     swiatlem w tym samym pomaranczu - i tam obrys po prostu znika w tle.
     Jasniejszy akcent kadru trzyma sie powyzej 5:1 wszedzie. */
  --focus-ring: var(--kadr-accent);

  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* Jednostka kontenerowa dla stopnia pisma powitania - patrz .hail. */
  container-type: inline-size;

  display: flex;
  flex-direction: column;
  /* `min-block-size`, nie `block-size`.
     Edited by: Kryst 11.08.26

     Kadr ma wysokosc okna i to sie nie zmienia - zmienia sie to, co robi,
     gdy okno jest NIZSZE niz jego zawartosc: telefon w poziomie, laptop
     z powiekszeniem 150%, przegladarka z paskiem zakladek i konsola. Przy
     `block-size` wysokosc byla sztywna, a `overflow: hidden` po cichu
     obcinal to, co nie weszlo - czyli w najgorszym razie przycisk
     "Zacznij rejestracje", jedyna rzecz, po ktora ten ekran istnieje.
     Teraz kadr w takim oknie rosnie i strona sie przewija. Przewijanie
     jest wbrew tezie tego ekranu, ale teza dotyczy okna, w ktorym tresc
     sie MIESCI; ucinanie przycisku nie jest jej mocniejsza wersja. */
  min-block-size: 100svh;
  /* Dolna wyscielka nie schodzi ponizej paska gestow iPhone'a - bez tego
     wstega jezykow konczy sie pod nim. */
  padding: clamp(1.25rem, 4vw, 3.5rem);
  padding-block-end: max(clamp(1.25rem, 4vw, 3.5rem), env(safe-area-inset-bottom));
  /* PRZESTRZEN I KALUZA SWIATLA.
     Edited by: Kryst 11.08.26 - kaluza z 94% na 76% i cieplo na wlasciwej
     warstwie.

     TU MIESZKA CALE CIEPLO TEGO KADRU i to jest sedno przebudowy. Poprzednio
     cieply byl SNOP, a to nie moglo dzialac: `rgba(232,148,74,0.17)` na
     gruncie #12233b kompozytuje sie do rgb(54,54,62), czyli do neutralnej
     szarosci o nasyceniu 6%. Nie chodzilo o dobor odcienia - niskie alfa
     sredniego pomaranczu na ciemnym granacie MUSI dac szarosc, bo usrednia
     jedno w drugie. Prawdziwy snop w ciemnym pomieszczeniu tez jest zresztaa
     bladym, odbarwionym pasmem; pomarancz nalezy do POWIERZCHNI, na ktore
     swiatlo pada.

     Tutaj ta sama barwa dziala, bo grunt w tym miejscu jest prawie czarny:
     rgba(240,170,100,0.24) na okolicy #05090f daje rgb(59,46,35) - nasycenie
     40% i wyraznie jasniej niz otoczenie. Ta sama farba, inne podloze.

     Kaluza jest PLASKA (42% na 11%), bo laduje na linii: horyzont w okolicy
     72-76% wysokosci jest tam niemal poziomy, wiec swiatlo rozklada sie
     wzdluz niego, a nie w kolo. To jest DZIEN na tarczy - miejsce, w ktorym
     slonce dosiega swiata.

     NIEBO JASNIEJE KU HORYZONTOWI, NIE KU GORZE.
     Edited by: Kryst 12.08.26 - gradient radialny na 24% -> liniowy w dol.

     Grunt byl radialny z najjasniejszym punktem na 24% wysokosci, czyli
     GORA kadru byla najjasniejsza. Dobrane bylo na oko, kiedy ten obraz
     nie znaczyl jeszcze niczego, i mialo dwa skutki: jasniejszy pas granatu
     za paskiem nawigacji, ktory czytal sie jako osobna szarfa (szew z
     audytu), i sprzecznosc z tym, czym ten kadr jest.

     Jesli to jest tarcza swiata widziana z zewnatrz, to u gory jest
     PRZESTRZEN - najciemniejsze miejsce ekranu - a rozjasnienie nalezy do
     atmosfery tuz nad krawedzia. Ponizej krawedzi tarcza znow ciemnieje, bo
     to jej strona nieoswietlona. Stad szczyt jasnosci na 76%, czyli dokladnie
     na horyzoncie, i zejscie w czern w obie strony. Ten sam gradient zamyka
     przy okazji martwy pas pod horyzontem, o ktory szlo w audycie. */
  background:
    /* GORACY PUNKT na samej krawedzi - miejsce uderzenia.
       Edited by: Kryst 12.08.26 - jedna szeroka plama -> rdzen + rozlanie.

       Jedna plama 42% na 11% przy 0.24 czytala sie jako rozmyta poswiata za
       horyzontem, jak blask miasta - bo swiatlo ladujace na powierzchni ma
       MALY i JASNY srodek, a nie rowny rozlew. Rdzen jest teraz waski (14%
       na 4%) i siedzi dokladnie na krawedzi (73%), a rozlanie wokol niego
       robi drugi, szerszy gradient. */
    /* ZACHOD - dwie warstwy dokladane tylko wtedy, gdy slonce jest nisko.
       Edited by: Kryst 12.08.26

       Im nizej slonce, tym dluzsza droga swiatla przez atmosfere i tym
       wiecej blekitu z niej wypada - stad czerwien zachodu. Robimy to
       ODDZIELNA WARSTWA, a nie mieszaniem barwy (`color-mix`), bo to samo
       wychodzi z dodania przezroczystej czerwieni, a dziala wszedzie:
       `color-mix` ma niecale trzy lata, a arkusz ma trafiac takze w telefon,
       ktory nie widzial aktualizacji od dawna.

       Przy `--slonce-cieplo: 0` (slonce wysoko albo brak liczb ze
       znacznika) obie warstwy maja alfa zero, czyli nie ma ich wcale. */
    radial-gradient(13% 4% at var(--swiatlo-x) 73%,
      rgba(255, 148, 96, calc(0.30 * var(--slonce-moc, 1) * var(--slonce-cieplo, 0))), transparent 100%),
    radial-gradient(32% 8% at var(--swiatlo-x) 75%,
      rgba(214, 92, 56, calc(0.24 * var(--slonce-moc, 1) * var(--slonce-cieplo, 0))), transparent 100%),
    /* DZIEN - te same dwie plamy co dotad, przygaszane wysokoscia slonca.
       Mnoznik 1 to poludnie i jednoczesnie sufit; w nocy schodzi do 0.22,
       czyli z kaluzy zostaje rabek na krawedzi swiata. */
    radial-gradient(14% 4% at var(--swiatlo-x) 73%,
      rgba(255, 214, 164, calc(0.38 * var(--slonce-moc, 1))), transparent 100%),
    radial-gradient(34% 9% at var(--swiatlo-x) 75%,
      rgba(240, 160, 90, calc(0.22 * var(--slonce-moc, 1))), transparent 100%),
    linear-gradient(
      to bottom,
      #03060b 0%,
      #050a13 30%,
      #0b1729 58%,
      #13253d 76%,
      #0c1524 88%,
      #05080e 100%
    );
  color: var(--kadr-ink);
}

/* Odbicie swiatla po arabsku i persku. Dwa zapisy tego samego, co przy
   `.arc` i z tego samego powodu: `:dir()` doszedl do Chrome w 120. Snop
   padajacy od strony, w ktora sie czyta, jest snopem padajacym w oczy.
   Odbija sie zrodlo (22% -> 78%), znak nachylenia i miejsce ladowania.

   Edited by: Kryst 12.08.26 - odbicie liczone, nie przepisane. Nachylenie
   i dzien na tarczy przychodza teraz od slonca i zmieniaja sie co kilka
   minut, wiec drugi komplet stalych rozjechalby sie z pierwszym przy
   pierwszym zachodzie. `100% - x` daje to samo, co dawne 43%, i daje to
   przy kazdej wartosci. `--lustro` odbija grzebien promieni. */
[dir="rtl"] .frame,
:dir(rtl) .frame {
  --apex: 74%;
  --rake: calc(var(--slonce-kat, 28) * 1deg);
  --swiatlo-x: calc(100% - var(--slonce-x, 57%));
  --lustro: -1;
}

/* KLISZY TU NIE MA I NIE MA JEJ WRACAC.
   Edited by: Kryst 11.08.26

   Stala tu wloskowa ramka wpuszczona od krawedzi (`.frame::before`,
   1 px przy 0.09 alfa) i mialaby zmieniac "tlo strony" w "plyte". Na
   ekranie nie robila tego z trzech powodow i kazdy z nich jest osobny:

   - Dublowala sie z ciemna krawedzia kadru u dolu, dajac podwojna linie.
   - Przechodzila przez pasek nawigacji, tuz pod znakiem JRS.
   - Byla JEDYNYM obiektem w prawej polowie ekranu, wiec zamiast zbierac
     kadr, wskazywala na jego pustke.

   Czytala sie w efekcie jak obramowanie tabeli albo obrys focusu. Zasada
   ("plyta, nie tlo") byla dobra i nie do tego ekranu: pustke po prawej
   zamyka teraz snop, ktory po naprawie znaku nachylenia laduje wlasnie
   tam, a nie ramka wokol niczego. */

/* --- Swiatlo: jedno zrodlo, dwie smugi ----------------------------------
   Edited by: Kryst 11.08.26 - trzy dryfujace plamy -> snop kierunkowy.

   CO STALO TU WCZESNIEJ I DLACZEGO ODESZLO. Trzy kola z gradientem
   radialnym (90, 78 i 52 vmax), dryfujace po kadrze w okresach 67, 89
   i 113 s. Dwie rzeczy z tego wynikaly:

   1. MUL. `glow-1` w pomaranczu 0.34 lezal na `glow-2` w blekicie 0.5,
      a na obu winieta z 0.62 czerni. Dwie barwy dopelniajace przy niskim
      alfa nie daja glebi, daja szarobrunatny osad - i to on wypelnial
      prawa polowe ekranu. Granat marki, na ktorym liczone byly WSZYSTKIE
      kontrasty tego kadru, nie byl widoczny ani przez chwile.
   2. Zadne z tych swiatel nie mialo ZRODLA. Plama, ktorej nie umiesz
      wskazac skad pada, czyta sie jako gradient w tle aplikacji; snop
      o kierunku czyta sie jako ujecie. To nie kwestia dopracowania -
      dryfujaca plama radialna jest najczestszym pojedynczym elementem
      strony z generatora i zadna ilosc dostrajania tego nie zdejmuje.

   CO JEST TERAZ, PO DRUGIM PRZEBIEGU.
   Edited by: Kryst 12.08.26 - prostokat -> klin ze zrodla, cieplo -> blado.

   Pierwsze podejscie zamienilo trzy plamy na dwie SMUGI: prostokaty
   o stalej szerokosci, przechylone o 15 stopni, wypelnione cieplym
   gradientem. Nie czytalo sie to jako swiatlo i wiadomo dlaczego, punkt
   po punkcie:

   1. BARWA. Cieply pigment przy niskim alfa na granacie kompozytuje sie
      do szarosci - rachunek stoi przy `background` w `.frame`. Snop nie
      moze byc pomaranczowy. Prawdziwy snop w ciemnym pomieszczeniu jest
      bladym, niemal bialym pasmem, a cala barwa siedzi na powierzchniach,
      na ktore swiatlo pada. Wiec: snop blady i JASNIEJSZY, a cieplo
      przeniesione do kaluzy na horyzoncie i do halacji pod pismem.
   2. STALA SZEROKOSC. Swiatlo sie ROZCHODZI. Prostokat tego nie zrobi
      i zadne dostrajanie alfy tego nie nadrobi - stad klin wychodzacy
      z punktu, robiony maska stozkowa (`conic-gradient`).
   3. ZNAK NACHYLENIA. Snop schodzil w LEWO, czyli pod prad czytania -
      patrz `--rake` przy tokenach.
   4. TWARDA JASNA KRAWEDZ U GORY. Najjasniejszy przystanek gradientu stal
      na 0%, czyli dokladnie na krawedzi kadru: czytalo sie to jak przeciek
      swiatla na tasmie. Teraz jasnosc narasta PONIZEJ krawedzi.

   Zostaje z pierwszego podejscia to, co bylo dobre: jedno zrodlo, brak
   plikow, brak `filter: blur` (miekkosc robia maski, spadek - gradient
   tla; rozmycie powierzchni tej wielkosci kosztuje kazda klatke) i para
   warstw zamiast jednej - tylko teraz sa to HALO i RDZEN tego samego
   snopa, a nie dwie osobne smugi. Tak jest zbudowane prawdziwe swiatlo.

   CO JEST TERAZ, PO TRZECIM PRZEBIEGU.
   Edited by: Kryst 12.08.26 - dwa pasma -> obwiednia i siedem promieni.

   Dwa pasma nadal czytaly sie jako reflektor i powod jest jeden, za to
   nie do obejscia dostrajaniem: PROMIENI ZMIERZCHOWYCH JEST ZAWSZE KILKA.
   Nie sa one strumieniami swiatla, tylko cieniami chmur wpuszczonymi
   w rozswietlona atmosfere - wiec o obrazie decyduje to, co MIEDZY nimi.
   Jedno pasmo albo dwa nie maja "miedzy". Dlatego pek ma teraz siedem
   smug o nierownych szerokosciach (stosunek okolo 4:1) i nierownych
   odstepach; rowne bylyby ogrodzeniem, nie swiatlem.

   Zostaje wszystko, co bylo dobre: jedno zrodlo, brak plikow, brak
   `filter: blur`, brak barwy w samym snopie. Dochodzi jedna rzecz, ktorej
   poprzednia wersja swiadomie nie miala - lekkie ROZEJSCIE (`--f` przy
   `.ray`). Uzasadnienie stoi tam, bo to poprawka do wlasnego komentarza
   sprzed dwoch dni. */
.sky {
  position: absolute;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  /* SILA SWIATLA - jedna liczba na caly pek.
     Edited by: Kryst 12.08.26

     Wysokosc slonca nad Warszawa przygasza tu wszystkie osiem warstw naraz.
     Jedynka to poludnie i jednoczesnie SUFIT: kadr nie ma prawa zrobic sie
     jasniejszy, niz byl w chwili, dla ktorej policzono kontrasty wstegi
     osmiu pism, podpisu i "Staff login". Dzieki temu tamte rachunki zostaja
     wazne jako przypadek najgorszy zamiast wymagac powtorzenia dla kazdej
     godziny doby.

     `opacity` na rodzicu, nie osiem mnoznikow w gradientach: to jedna
     wlasciwosc, jedna warstwa kompozycji i zero ryzyka, ze ktoras ze smug
     zostanie w tyle. */
  opacity: var(--slonce-moc, 1);
  /* PROFIL SWIATLA WZDLUZ DROGI - i powod, dla ktorego stoi WLASNIE TUTAJ.
     Edited by: Kryst 12.08.26 - przeniesiony z gradientow poszczegolnych smug.

     Do tej pory kazda smuga niosla swoj wlasny spadek pionowy: zero u gory,
     szczyt w dwoch trzecich, zero tuz nad horyzontem. Przystanki byly jednak
     ulamkami SMUGI, a smuga jest obrocona - i stad dwa bledy, ktorych nie
     widac w kodzie, tylko na ekranie:

     1. Obrot zamienia poziome odsuniecie promienia (`--o`) na przesuniecie
        PIONOWE. Przy 20 stopniach skrajne promienie peku konczyly sie 22
        punkty wysokosci kadru od siebie, mimo identycznych przystankow.
     2. Cala skala jezdzi z `cos(nachylenia)`. Przy 28 stopniach, dla ktorych
        byla dobrana, wszystko sie zgadzalo. Odkad nachylenie idzie za
        sloncem, przestalo: PRZY ZACHODZIE PIEC Z SIEDMIU PROMIENI mialo na
        horyzoncie alfe zero, czyli gaslo, zanim doszlo do ziemi.

     Maska lezy na `.sky`, czyli na elemencie, ktory NIE JEST obrocony i ma
     dokladnie wymiary kadru. Jej przystanki sa wiec procentami wysokosci
     kadru - te same dla kazdego promienia, przy kazdym nachyleniu, o kazdej
     porze roku. Policzone: promienie ladują teraz na 94-100% wlasnego
     szczytu zamiast na 0-99%.

     Przebieg zostaje ten, co byl, i z tych samych powodow: zero na samej
     krawedzi (swiatlo staje sie widoczne dopiero W atmosferze, nie na
     krawedzi kadru - inaczej wyglada jak przeciek swiatla na tasmie),
     narastanie po drodze (im dluzsza droga przez powietrze, tym wiecej sie
     w nim rozprasza) i PELNIA OD 70% W DOL. Pelnia, a nie zejscie do zera,
     bo dolna granice swiatla wyznacza tarcza swiata, ktora je zaslania
     (`.arc` stoi wyzej w kolejnosci malowania) - a nie gradient udajacy, ze
     ja zna. Promien, ktory sam gasnie przed ziemia, jest mgla. */
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0%, rgba(0, 0, 0, 0.18) 18%, rgba(0, 0, 0, 0.48) 38%,
    rgba(0, 0, 0, 0.82) 58%, #000 70%, #000 100%);
  mask-image: linear-gradient(to bottom,
    transparent 0%, rgba(0, 0, 0, 0.18) 18%, rgba(0, 0, 0, 0.48) 38%,
    rgba(0, 0, 0, 0.82) 58%, #000 70%, #000 100%);
  /* Kolysanie obraca caly pek wokol punktu wejscia w kadr - czyli wokol
     `--apex` na wysokosci szczytu smug, ktory siega 30% ponad kadr.
     Maska jedzie z nim, ale 1.6 stopnia na gradiencie pionowym to roznica
     ponizej progu widocznosci. */
  transform-origin: var(--apex) -30%;
  will-change: transform;
}

/* SMUGA - kazdy element jest calym kadrem, a pasmo wycina z niego maska.
   Edited by: Kryst 12.08.26 - maska stozkowa -> liniowa.

   Szerokosc kazdej smugi jest STALA na calej dlugosci i to jest tu cala
   rzecz: slonce stoi 150 mln km stad, wiec jego promienie sa rownolegle.
   Poprzednia wersja rozchodzila sie z punktu (`conic-gradient`, rozejscie
   4:1) i byla poprawna dla lampy stojacej dwa metry za gorna krawedzia -
   czyli dla idei, ktora tym obrazem nie jest. Rozejscie WIDOCZNE GOLYM
   OKIEM mowi "zrodlo jest tuz obok". Rozejscie ledwie mierzalne mowi co
   innego i o tym jest `--f` przy `.ray`.

   `transform-origin` stoi w `--apex`, zeby kolysanie ambientu obracalo pasmo
   wokol punktu wejscia w kadr, a nie przesuwalo go w poprzek. Sama rotacja
   jest tu zresztaa jedynym ruchem, jaki slonce ma prawo wykonac.

   OGRANICZENIE, KTORE TRZEBA PAMIETAC przy zmianie szerokosci: maska liczy
   procenty od szerokosci ELEMENTU, czyli kadru, wiec zadna smuga nie moze
   siegnac dalej w lewo niz `--apex` (26%) - inaczej jej krawedz wypada poza
   element i zostaje przycieta na twardo, dajac promien z jedna strona
   ostra. Najdalsza smuga peku konczy sie 19.3% od osi, czyli na 6.7%
   szerokosci kadru; przy RTL to samo od drugiej strony daje 93.3%. */
.shaft {
  position: absolute;
  display: block;
  inset-block-start: -30%;
  inset-inline-start: 0;
  inline-size: 100%;
  block-size: 165%;
  transform: rotate(var(--rake));
  transform-origin: var(--apex) 0;
}

/* OBWIEDNIA - szeroka, blada, rozmyta na bokach.
   Edited by: Kryst 12.08.26 - bylo HALO jednego pasma, jest poswiata peku.

   Pasmo 46% szerokosci kadru: rdzen pelny na 24%, po 11% miekkiego zejscia
   z kazdej strony. Szeroko, bo to atmosfera rozprasza swiatlo wokol
   promieni, a nie soczewka je skupia. Rola sie nie zmienila, zmienilo sie,
   czego jest poswiata: nie jednego rdzenia, tylko calego peku siedmiu smug,
   ktory siega 19.3% od osi - czyli miesci sie w niej z zapasem, a najdalsze
   promienie wychodza juz w jej miekkie zejscie. To ta warstwa sprawia, ze
   siedem osobnych pasm czyta sie jako jedno swiatlo, a nie jako zaluzja.

   Pierwszy przystanek gradientu pionowego ma alfa 0 i stoi na 16%, czyli
   dokladnie tam, gdzie zaczyna sie widoczny kadr (element siega 30% nad
   niego, 30/165 = 18%). Swiatlo staje sie wiec widoczne PO wejsciu w kadr -
   tak, jak promien staje sie widoczny, gdy wejdzie w atmosfere. */
.shaft-halo {
  /* Jedyna smuga z wlasnym ruchem - reszta peku jedzie na obrocie `.sky`.
     Uzasadnienie przeciwbiegu stoi przy `halo-kolysze`. */
  will-change: transform;
  -webkit-mask-image: linear-gradient(to right,
    transparent calc(var(--apex) - 23%), #000 calc(var(--apex) - 12%),
    #000 calc(var(--apex) + 12%), transparent calc(var(--apex) + 23%));
  mask-image: linear-gradient(to right,
    transparent calc(var(--apex) - 23%), #000 calc(var(--apex) - 12%),
    #000 calc(var(--apex) + 12%), transparent calc(var(--apex) + 23%));
  /* SPADEK JASNOSCI BYL ODWROTNY DO IDEI.
     Edited by: Kryst 12.08.26

     Stalo tu: szczyt 0.16 na 23% wysokosci kadru, potem 0.10, 0.03 i zero.
     Czyli pasmo bylo najjasniejsze U GORY i gaslo, zanim dotarlo do swiata -
     na horyzoncie mialo alfa ~0.08, co na niebie rgb(19,37,61) daje
     rgb(38,54,75). Tego nie widac i nie moglo byc widac. Idea brzmi
     "slonce PADA NA swiat", a na ekranie nic na nic nie padalo: smuga
     u gory i cieplo przy krawedzi byly dwoma osobnymi obiektami.

     Teraz pasmo NABIERA po drodze i KONCZY SIE NA KRAWEDZI. Jedno i drugie
     ma powod:
     - nabiera, bo im dluzsza droga przez atmosfere, tym wiecej swiatla
       sie w niej rozprasza (i bo dzieki temu kadr prowadzi oko z gory
       w dol - do przycisku i do swiata, a nie odwrotnie),
     - konczy sie na krawedzi, bo swiat swiatlo ZASLANIA. To jest ta jedna
       rzecz, ktora zamienia mgle w promien padajacy na powierzchnie:
       promien, ktory przechodzi przez tarcze i leci dalej, jest mgla.

     JEDNO I DRUGIE STOI DZIS NA MASCE `.sky`, NIE TUTAJ - i to jest jedyna
     poprawka do powyzszego akapitu. Przystanki byly tu procentami ELEMENTU,
     a element jest obrocony i wyzszy od kadru (165%), wiec przeliczaly sie
     na wysokosc kadru przez `kadr% = 165 * el% * cos(kat) - 30`. Dopoki kat
     byl staly (28 stopni), dawalo sie to raz policzyc i zapomniec. Odkad
     kat idzie za sloncem, ten przelicznik zmienia sie w ciagu dnia - i sam
     `cos` przesuwal koniec pasma o kilkanascie punktow wysokosci. Maska
     w przestrzeni kadru nie ma tego problemu, bo `.sky` nie jest obrocone.

     PROMIEN CZERWIENIEJE PO DRODZE.
     Edited by: Kryst 12.08.26 - biel na calej dlugosci -> biel do bursztynu.

     Przystanki byly praktycznie jednej barwy (255,247,236 do 255,240,216)
     i to bylo przeoczenie tej samej klasy, co odwrocony spadek jasnosci:
     jesli pasmo NABIERA, bo droga przez atmosfere jest coraz dluzsza, to
     ta sama droga zabiera mu blekit. Swiatlo, ktore przeszlo przez wiecej
     powietrza, jest cieplejsze - na tym polega czerwony zachod i na tym
     polega to, ze poludniowe niebo jest niebieskie.

     Nie kloci sie to z zasada "snop nie moze byc pomaranczowy" spod punktu
     1 wyzej. Tamta dotyczy SLABEGO konca: cieply pigment przy alfa 0.06 na
     granacie usrednia sie do szarosci. Tu ocieplenie siedzi tam, gdzie alfa
     jest najwyzsza, a na gorze, gdzie pasmo jest ledwie widoczne, barwa
     zostaje niemal biala.

     ZOSTAJE TU JUZ TYLKO BARWA I SZCZYT.
     Edited by: Kryst 12.08.26 - spadek jasnosci przeniesiony na `.sky`.

     Zera na obu koncach nie ma i nie ma jej wracac: to wlasnie one, liczone
     w ulamkach obroconego elementu, gasily promienie przed horyzontem
     (rachunek przy masce `.sky`). Jasnosc caloscia rzadzi maska w
     przestrzeni kadru; tutaj zostaje 0.20 jako szczyt - dokladnie tyle, ile
     bylo - i przejscie z bieli w bursztyn. Barwa moze zostac w przestrzeni
     elementu, bo promien odrobine za cieply w polowie drogi jest
     niewidoczny, a promien z alfa zero na horyzoncie widac natychmiast. */
  background: linear-gradient(
    to bottom,
    rgba(255, 250, 242, 0.17) 0%,
    rgba(255, 246, 232, 0.19) 40%,
    rgba(255, 237, 210, 0.20) 62%,
    rgba(255, 228, 188, 0.19) 100%
  );
}

/* PROMIEN - jeden z siedmiu. To one sa swiatlem; obwiednia wyzej jest tylko
   tym, co swiatlo robi z pylem wokol siebie.
   Edited by: Kryst 12.08.26 - jeden rdzen -> pek.

   PIEC LICZB NA PROMIEN, wszystkie w procentach szerokosci kadru poza
   dwiema ostatnimi:
     --o  odsuniecie osi promienia od `--apex` (ze znakiem)
     --w  polowa szerokosci pelnej jasnosci
     --s  miekkie zejscie po kazdej stronie
     --a  mnoznik jasnosci, 1 to najjasniejszy promien peku
     --f  rozejscie w stopniach

   ROZEJSCIE - poprawka do komentarza sprzed dwoch dni.
   Stalo tam, ze promienie slonca sa rownolegle, wiec pek nie ma prawa sie
   rozchodzic. W przestrzeni to prawda, na obrazie nie: w rzucie
   perspektywicznym promienie zbiegaja sie do slonca dokladnie tak, jak tory
   kolejowe zbiegaja sie do horyzontu, i dlatego na kazdej fotografii
   promieni zmierzchowych widac wachlarz. Bledem poprzedniej wersji nie bylo
   samo rozejscie, tylko jego MIARA - stozek 4:1 mowil, ze zrodlo stoi dwa
   metry za krawedzia kadru.

   Tu rozejscie siega 2 stopni na skrajnych promieniach, czyli na horyzoncie
   rozsuwa je o okolo 2% szerokosci kadru (57.4% * tan 2st.). Nie widac tego
   jako wachlarza; widac jako to, ze pek nie jest grzebieniem wycietym
   z jednego kawalka. Kazdy promien obraca sie wokol tego samego punktu co
   reszta (`transform-origin` w `.shaft`), wiec u gory schodza sie razem,
   a rozchodza dopiero ku dolowi - jak wtedy, gdy zrodlo jest za kadrem.

   ODSTEPY I SZEROKOSCI SA NIEROWNE i to nie jest ozdoba. Stosunek szerokosci
   siega 4:1, odstepy chodza od 3.2 do 7.6. Rowny pek czyta sie jako zaluzja
   albo jako gradient powtorzony `repeating-linear-gradient` - czyli jako
   rzecz zrobiona maszyna, a nie jako cienie chmur.

   SUMA JASNOSCI PEKU NIE PRZEKRACZA W ZADNYM PUNKCIE dawnego rdzenia.
   Promienie zachodza na siebie miekkimi zejsciami, wiec liczy sie profil
   poprzeczny, a nie pojedyncze `--a`: jego szczyt wypada dokladnie na 1.00,
   na najszerszym promieniu, i nigdzie indziej go nie przebija. To ta sama
   zasada, co przy `--slonce-moc`: kadr z 12.08.26 jest sufitem, bo dla
   niego policzone sa kontrasty. */
.ray {
  --o: 0%;
  --w: 2%;
  --s: 3%;
  --a: 1;
  --f: 0;
  transform: rotate(calc(var(--rake) + var(--f) * var(--lustro, 1) * 1deg));
  -webkit-mask-image: linear-gradient(to right,
    transparent calc(var(--apex) + var(--o) * var(--lustro, 1) - var(--w) - var(--s)),
    #000        calc(var(--apex) + var(--o) * var(--lustro, 1) - var(--w)),
    #000        calc(var(--apex) + var(--o) * var(--lustro, 1) + var(--w)),
    transparent calc(var(--apex) + var(--o) * var(--lustro, 1) + var(--w) + var(--s)));
  mask-image: linear-gradient(to right,
    transparent calc(var(--apex) + var(--o) * var(--lustro, 1) - var(--w) - var(--s)),
    #000        calc(var(--apex) + var(--o) * var(--lustro, 1) - var(--w)),
    #000        calc(var(--apex) + var(--o) * var(--lustro, 1) + var(--w)),
    transparent calc(var(--apex) + var(--o) * var(--lustro, 1) + var(--w) + var(--s)));
  /* Ta sama zasada co w obwiedni i z tych samych powodow: tutaj barwa
     i szczyt, spadek jasnosci na masce `.sky`. Mocniej, bo promien jest
     swiatlem, a obwiednia tylko tym, co swiatlo robi z atmosfera wokol
     siebie. Wszystkie pasma zaslania ta sama tarcza, wiec zadne nie musi
     wiedziec, gdzie ona jest. Edited by: Kryst 12.08.26 */
  background: linear-gradient(
    to bottom,
    rgba(255, 252, 247, calc(0.30 * var(--a))) 0%,
    rgba(255, 248, 236, calc(0.33 * var(--a))) 40%,
    rgba(255, 240, 216, calc(0.36 * var(--a))) 62%,
    rgba(255, 230, 192, calc(0.34 * var(--a))) 100%
  );
}

/* Sam pek. Kolejnosc liczy od DRUGIEGO dziecka, bo pierwszym jest obwiednia.
   Liczby sa dobrane tak, zeby profil poprzeczny mial siedem wyraznych
   szczytow i szesc dolin schodzacych ponizej polowy sasiadow - inaczej
   sasiadujace promienie zlewaja sie w plaskowyz i pek znow ma dwa pasma,
   tylko szersze. Znak `--f` idzie za znakiem `--o`, bo promienie rozchodza
   sie OD osi. */
.ray:nth-child(2) { --o: -17.0%; --w: 0.7%; --s: 1.6%; --a: 0.38; --f:  1.9; }
.ray:nth-child(3) { --o: -11.0%; --w: 1.9%; --s: 2.2%; --a: 0.75; --f:  1.2; }
.ray:nth-child(4) { --o:  -4.2%; --w: 0.9%; --s: 1.4%; --a: 0.42; --f:  0.5; }
.ray:nth-child(5) { --o:   3.4%; --w: 2.6%; --s: 2.8%; --a: 1.00; --f: -0.4; }
.ray:nth-child(6) { --o:   7.8%; --w: 0.5%; --s: 1.1%; --a: 0.42; --f: -0.9; }
.ray:nth-child(7) { --o:  12.6%; --w: 1.2%; --s: 1.8%; --a: 0.58; --f: -1.4; }
.ray:nth-child(8) { --o:  18.4%; --w: 1.7%; --s: 2.1%; --a: 0.62; --f: -2.0; }

/* SWIATLO NA TELEFONIE - 28 -> 14 stopni, dzien z 57% na 78%.
   Edited by: Kryst 12.08.26

   ODJAZD W BOK rosnie z wysokoscia (przesuniecie = (y+30%) * tan(kat)),
   a szerokosc kadru z nia nie rosnie. Na 390x800 przy 28 stopniach pasmo
   wychodzi PRAWA KRAWEDZIA na 41% wysokosci - dolne 60% kadru zostaje
   ciemne, a horyzont bez dnia.

   14 stopni sprowadza odjazd do 203 px przy 390 px szerokosci, czyli pasmo
   przechodzi cala wysokosc i dosiega horyzontu w okolicy 78% szerokosci.
   Kaluza idzie za nim. Ten sam prog co przy `.hail`: ponizej 30rem proporcje
   kadru przestaja byc proporcjami ujecia.

   Edited by: Kryst 12.08.26 - wlasny zakres slonca zamiast dwoch stalych.

   Telefon nie dostaje ulamka kata z gory, tylko wlasne przelozenie liczone
   z tej samej wysokosci slonca (`slonce.py`, `--slonce-kat-s`): 7 stopni
   przy sloncu w zenicie, 14 przy sloncu na horyzoncie. Gorna granica jest
   twarda - przy 20 stopniach kaluza wypada juz poza prawa krawedzia kadru,
   czyli dzien znika z ekranu, na ktorym jest go najbardziej widac. */
@media (max-width: 30rem) {
  .frame {
    --rake: calc(var(--slonce-kat-s, 14) * -1deg);
    --swiatlo-x: var(--slonce-x-s, 78%);
  }
  [dir="rtl"] .frame,
  :dir(rtl) .frame {
    --rake: calc(var(--slonce-kat-s, 14) * 1deg);
    --swiatlo-x: calc(100% - var(--slonce-x-s, 78%));
  }
}

/* WINIETA. Nadal najtansza rzecz w kadrze i nadal ta, ktora zbiera go
   w ujecie - ale juz nie musi ratowac obrazu przed rozlazeniem sie.

   Edited by: Kryst 11.08.26 - 0.62 -> 0.42 czerni, srodek na `--swiatlo-x`.

   0.62 bylo liczone pod trzy plamy rozlane po calym kadrze: winieta byla
   jedyna rzecza, ktora dawala im krawedz. Przy swietle o kierunku ta sama
   wartosc dusi kadr - to ona razem z brazem z dwoch plam robila szarobure
   narozniki. Snop ma sie w czerni GUBIC, a nie byc w niej gaszony.

   Srodek idzie za swiatlem, nie za geometrycznym srodkiem kadru: spadek
   robi sie wtedy glebszy po stronie przeciwnej do zrodla, czyli tam, gdzie
   ma byc glebszy. Jedna liczba zamiast drugiego gradientu.

   Edited by: Kryst 12.08.26 - srodek z 40% na 62% wysokosci.

   Winieta stala wyzej niz dzien na tarczy, wiec przygaszala go od dolu
   i jednoczesnie rozjasniala gore - czyli pracowala przeciwko atmosferze,
   ktora ma jasniec dopiero przy krawedzi swiata. Zejscie na 62% ustawia ja
   na tej samej wysokosci co kaluza: ciemnieja gorne narozniki, gdzie jest
   przestrzen, a nie dolne, gdzie jest dzien. */
.frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(120% 94% at var(--swiatlo-x) 62%, transparent 38%, rgba(0, 0, 0, 0.46) 100%);
}

/* Ziarno tasmy. Warstwa jest wieksza od kadru i przesuwa sie skokowo
   (`steps`), bo ziarno filmu SKACZE co klatke - plynne przesuwanie daje
   wrazenie brudnej szyby, nie tasmy.

   Globalne ziarno strony (body::after) lezy WYZEJ, nie nizej - jest
   `fixed` na z-index 200, czyli nad wszystkim. Na kadrze schodzi do
   wartosci nocnej (patrz `body.kadr::after`), zeby warstwa dobrana pod
   jasny papier nie mgliła ciemnego obrazu.

   ZIARNO ZYJE W POLTONACH.
   Edited by: Kryst 11.08.26

   Rowne 0.11 po calej plaszczyznie czyta sie jako snieg w telewizorze,
   nie jako tasma: prawdziwe ziarno jest tam, gdzie pada swiatlo, i nie ma
   go w czerni, bo w czerni nie ma czego naswietlic. Maska idzie wiec za
   swiatlem - ten sam `--swiatlo-x`, co grunt i odblask.

   `inset: -60%` -> `-12%`, bo maska liczy procenty od WLASNEJ powierzchni,
   nie od kadru. Przy warstwie 220% wielkosci kadru srodek kadru wypadal
   w 43% maski i kazda wartosc trzeba by przeliczac. Przy 124% granice
   kadru leza w 10-90% i wspolrzedne maski sa tymi samymi liczbami, co
   wszedzie indziej w tej sekcji. Zapas nadal jest z duzym marginesem:
   skok ziarna siega 6%, overhang ma 12% na kazda strone. */
.grain {
  position: absolute;
  inset: -12%;
  z-index: -2;
  pointer-events: none;
  /* Edited by: Kryst 12.08.26 - 0.09 -> 0.15.

     Maska byla dobrym pomyslem, wartosc pod nia za niska: 0.09 pod maska
     gasnaca ku brzegom dawalo na tym gruncie ZERO. Obraz wyszedl gladki
     i cyfrowy, czyli caly rejestr tasmy, po ktory ta warstwa tu jest,
     wypadl.

     Edited by: Kryst 12.08.26 - 0.15 -> 0.20, druga probe.

     0.15 na screenie nadal nie bylo widac. Czesc winy bierze skalowanie
     obrazu (ziarno o okresie 180 px zanika przy zmniejszaniu zrzutu), ale
     nie cala: maska zjada polowe wartosci w pasie, w ktorym ziarno ma byc
     widoczne, wiec 0.15 pod maska to mniej niz 0.11 bez niej. 0.20 wyrownuje
     rachunek. Gdyby wyszlo za mocno, to jest jedna liczba w jedna strone. */
  opacity: 0.2;
  -webkit-mask-image: radial-gradient(52% 46% at var(--swiatlo-x) 42%, #000, rgba(0, 0, 0, 0.3) 68%, transparent 100%);
  mask-image: radial-gradient(52% 46% at var(--swiatlo-x) 42%, #000, rgba(0, 0, 0, 0.3) 68%, transparent 100%);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- Horyzont -----------------------------------------------------------
   Motyw ze znaku JRS, jedyny rysunek na ekranie. Jedna linia we wlasnych
   proporcjach, `slice` kadruje, ale nie splaszcza - to bylo caly blad
   partials/bridge.html, nie sam rysunek.

   Edited by: Kryst 11.08.26 - luk za napisem -> horyzont pod napisem.

   Grzbiet stal na 42% wysokosci, czyli za powitaniem, i mial byc swiatlem,
   z ktorego wychodzi slowo. Nie byl nim: linia grubosci 1.5 px za pismem
   wysokosci 200 px niczego nie oswietla, a rozmyta kopia sciezki dawala
   flare obiektywu. Teraz grzbiet lezy na 74% - jest tym, NA CZYM LADUJE
   snop swiatla. Linia, na ktora pada swiatlo, jest konstrukcja; ta sama
   linia za napisem byla ozdoba.

   Skutek uboczny jest wazniejszy niz sam wyglad: horyzont nie wymaga
   zgodnosci geometrii z niczym. `slice` kadruje po innej osi przy kazdych
   proporcjach okna, wiec grzbiet "tuz pod srodkiem" byl obietnica, ktorej
   ten atrybut nie mogl dotrzymac - na telefonie widac srodkowe ~24%
   viewBoxa. Tam ta krzywa jest niemal plaska, czyli dokladnie horyzontem. */
.arc {
  position: absolute;
  inset: 0;
  z-index: -1;
  inline-size: 100%;
  block-size: 100%;
  pointer-events: none;
}
/* Odbicie luku po arabsku i persku. Dwa zapisy tego samego, bo `:dir()`
   doszedl do Chrome dopiero w 120 - a bez odbicia luk wznosi sie w strone
   przeciwna niz czyta sie strona, czyli w jedyna, w ktora nie powinien. */
[dir="rtl"] .arc,
:dir(rtl) .arc { transform: scaleX(-1); }

/* Barwy luku ida z tokenow kadru; w szablonie zostaje ta sama para jako
   atrybut `stop-color`, czyli zapas na niewczytany arkusz. */
.arc .luk-cieply { stop-color: var(--kadr-accent); }
.arc .luk-zimny  { stop-color: var(--kadr-cold); }

/* Tarcza swiata - ta sama konwencja. Edited by: Kryst 12.08.26 */
.arc .tarcza-gora { stop-color: var(--kadr-globe); }
.arc .tarcza-dol  { stop-color: var(--kadr-globe-deep); }

/* Poswiata horyzontu. Edited by: Kryst 11.08.26 - 9 px/0.55 -> 6 px/0.4.

   Rozmycie 9 px przy kresce 7 px daje plame szersza niz sama linia, a
   plama wzdluz krzywej to flara, nie halo. Halacja na tasmie jest CIASNA:
   otacza linie, nie zastepuje jej. Kreska bloomu zeszla przy tym z 7 na 5,
   a hairline z 1.5 na 1.25 - horyzont ma byc cienki. */
.arc-bloom {
  filter: blur(6px);
  opacity: 0.4;
}

/* --- Rdzen kadru --------------------------------------------------------
   Wysrodkowany w obu osiach, ale odrobine powyzej geometrycznego srodka
   (`margin-block: auto auto` plus przesuniecie): srodek optyczny lezy
   wyzej niz srodek mierzony, a pod spodem stoi jeszcze wstega.

   PODNOSZONY BYL DWA RAZY.
   Edited by: Kryst 12.08.26 - wyscielka z 5rem na 1.5rem.

   `margin-block: auto` srodkuje, a `padding-block-end` podnosi ponad srodek -
   i to jest w porzadku, o ile podnosi o tyle, ile potrzeba na korekte
   optyczna. 7vh (do 5rem) bylo korekta o pol kroku typograficznego za duza,
   a skutek dopiero po przeniesieniu horyzontu stal sie widoczny: tresc
   konczyla sie na ~55% wysokosci, horyzont lezal na 72%, wstega na 93%,
   a miedzy nimi zostawaly dwa pasy pustego granatu bez zadnej roboty.
   Kadr byl gornocięzki z dziura pod spodem.

   1.5rem zostawia korekte optyczna i zamyka dziure: tresc schodzi blizej
   horyzontu, wiec kaluza swiatla na nim zaczyna nalezec do tej samej
   kompozycji, co napis, a nie byc osobnym zdarzeniem na dole ekranu. */
.frame-core {
  position: relative;
  z-index: 1;
  margin-block: auto;
  padding-block-end: clamp(0.5rem, 2vh, 1.5rem);
  text-align: center;
}

/* POWITANIE - stopien pisma liczony z DLUGOSCI SLOWA.
   "Witaj" ma 5 znakow, "Добро пожаловать" - 16. Przy jednym stopniu pisma
   pierwsze zajmuje 20% szerokosci kadru, drugie wychodzi poza krawedz.
   Tutaj szerokosc bloku jest STALA (~86% kadru), a stopien pisma z niej
   wynika:  stopien = 86cqi / (znaki * srednia szerokosc znaku).

   `--znaki` podaje szablon (`{{ slowo|length }}`), `--adv` zalezy od pisma:
   Literata ma okolo 0.56 firetu na znak, naskh okolo 0.44 i dodatkowo
   liczy znaki diakrytyczne o zerowej szerokosci.

   Drugi warunek, `24svh`, pilnuje wysokosci: bez niego "Witaj" na szerokim
   monitorze dostalby pismo 590 px i zjadl caly kadr. Wygrywa mniejszy
   z dwoch - czyli na telefonie rzadzi szerokosc, na monitorze wysokosc.

   Pierwsza deklaracja jest zapasem dla przegladarki bez jednostek
   kontenerowych: gdy druga jest nieznana, po prostu jej nie ma.

   DOLNA GRANICA clamp() BYLA BLEDEM I TO ONA UCINALA NAPIS.
   Edited by: Kryst 11.08.26

   Cala ta regula liczy stopien pisma tak, zeby slowo weszlo w kadr. Dolna
   granica robila dokladnie odwrotnie: kazala pismu byc WIEKSZYM, niz
   wychodzilo z rachunku, a `white-space: nowrap` i `overflow: hidden`
   zamienialy ten nadmiar w ucinanie. "Добро пожаловать" (16 znakow)
   potrzebuje ponizej 457 px szerokosci okna pisma mniejszego niz 2.5rem -
   czyli na KAZDYM telefonie, lacznie z iPhone'em 390 px, gdzie slowo
   wychodzilo poza krawedz o kilkanascie pikseli. Nikt tego nie zglosil,
   bo ucinalo ostatnia litere, a nie polowe napisu.

   2.5rem -> 1.75rem. Ta granica nie ma nic pilnowac poza tym, zeby pismo
   nie zeszlo do rozmiaru przypisu; o tym, czy napis sie miesci, ma
   decydowac rachunek. */
.hail {
  --znaki: 8;
  --najdluzsze: var(--znaki);
  --wiersze: 1;
  --adv: 0.56;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 9vw, 9rem);
  font-size: clamp(1.75rem, min(86cqi / (var(--znaki) * var(--adv)), 24svh), 15rem);
  /* GRUBOSC I SWIATLO MIEDZYLITEROWE PRZY STOPNIU PLAKATOWYM.
     Edited by: Kryst 11.08.26 - 500 -> 400, -0.03em -> -0.045em.

     Literata jest krojem EKRANOWYM o solidnych dolaczeniach - swietnie
     znosi 16 px, a przy 200 px te same dolaczenia robia sie miesiste
     i slowo wyglada na ciezkie, nie na duze. Os grubosci jest plynna
     (400-700), wiec zejscie na 400 nic nie kosztuje i zdejmuje polowe tej
     ciezkosci. Druga polowe zdejmuje ciasniejszy tracking: swiatlo
     miedzyliterowe dobre dla tekstu jest przy stopniu plakatowym dziura.

     Arabski i perski maja to nadpisane ponizej - naskh nie znosi ani
     ujemnego trackingu, ani przerzedzania. */
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.045em;
  white-space: nowrap;
  /* Poswiata pod literami. Niska i ciepla - to ma byc swiatlo odbite od
     pisma, a nie neon.

     Edited by: Kryst 11.08.26 - wypada zimna warstwa.

     Stalo tu 140 px blekitu przy 0.3 alfa. Blekitne halo pod jasnym pismem
     na granatowym gruncie nie czytalo sie jako swiatlo, tylko jako brud -
     ta sama pomylka, co dwie dopelniajace plamy w tle, w mniejszej skali.
     Swiatlo w tym kadrze jest jedno i jest cieple, wiec odbicie od pisma
     tez. Zasieg z 140 na 96 px, bo halacja jest ciasna.

     Edited by: Kryst 12.08.26 - 28/96 px -> 18/52 px, i o polowe mocniej.

     96 px na jednolitym ciemnym gruncie nadal nie czytalo sie jako halacja,
     tylko jako miekki PLACEK wokol napisu: swiatlo rozlane tak szeroko
     przestaje nalezec do liter i zaczyna nalezec do tla. Ciasniej i przez to
     mocniej - 0.20 na 18 px zamiast 0.16 na 28 px.

     To jest teraz jedno z dwoch miejsc, w ktorych siedzi cieplo tego kadru
     (drugie to kaluza na horyzoncie). Snop jest blady i zimny, wiec barwa
     musi byc na POWIERZCHNIACH, a pismo jest tu najwieksza powierzchnia. */
  text-shadow: 0 0 18px rgba(255, 232, 202, 0.20), 0 0 52px rgba(240, 170, 100, 0.12);
}

.hail:lang(ar),
.hail:lang(fa) {
  --adv: 0.44;
  line-height: 1.35;
  letter-spacing: 0;
}

/* POWITANIE NA TELEFONIE - dwa wiersze zamiast jednego malego.
   Edited by: Kryst 11.08.26

   Sama poprawiona granica clamp() wystarcza, zeby nic sie nie ucinalo,
   ale daje "Добро пожаловать" na iPhonie w 34 px - czyli najwiekszy napis
   ekranu w rozmiarze zwyklego naglowka. Powitania wielowyrazowe (ru, ar,
   fa) moga sie tu zlamac, a wtedy rachunek prowadzi sie od NAJDLUZSZEGO
   SLOWA, nie od calego napisu: "пожaловать" zamiast "Добро пожаловать"
   to 10 znakow zamiast 16, czyli 54 px zamiast 34.

   Wysokosc pilnuje `--wiersze`: budzet 24svh dzieli sie na tyle wierszy,
   ile powitanie ich zajmie. Powitania jednowyrazowe (en, pl, uk, fr, es)
   maja `--wiersze: 1` i `--najdluzsze` rowne `--znaki`, wiec nie zmienia
   sie dla nich ani jeden piksel - to jest ta sama regula co wyzej.

   30rem, bo ucinanie zaczynalo sie ponizej ~457 px i tam tez konczy sie
   sensowne pole na jeden dlugi wiersz. `balance` rozklada dwa slowa na
   wiersze o zblizonej dlugosci zamiast zostawiac sierote. */
@media (max-width: 30rem) {
  .hail {
    white-space: normal;
    text-wrap: balance;
    font-size: clamp(
      1.75rem,
      min(86cqi / (var(--najdluzsze) * var(--adv)), 24svh / var(--wiersze)),
      15rem
    );
  }
}

/* PODPIS - jedyne zdanie na ekranie, w rejestrze napisow poczatkowych:
   wersaliki, szerokie swiatlo miedzyliterowe, przygaszone. Ma odpowiadac
   na pytanie "co to jest", nie opowiadac. */
.frame-caption {
  /* Edited by: Kryst 12.08.26 - odstep od powitania z 2rem na 1.25rem.
     Podpis wisial samotnie miedzy dwiema wiekszymi rzeczami - pismem
     wysokosci 200 px i przyciskiem - i przy 2rem odstepu nie nalezal do
     zadnej z nich. Ma nalezec do napisu: jest jego rozwinieciem, wiec
     stoi pod nim, a nie w polowie drogi do przycisku. */
  margin: clamp(0.5rem, 2vh, 1.25rem) 0 0;
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--kadr-ink-muted);
}
:root:lang(ar) .frame-caption,
:root:lang(fa) .frame-caption {
  letter-spacing: 0;
  text-transform: none;
  font-size: var(--text-base);
}

/* Edited by: Kryst 12.08.26 - odstep nad przyciskiem z 3rem na 2.5rem.
   Podpis przysunal sie do napisu, wiec przycisk musi odsunac sie od podpisu,
   inaczej caly stos zbija sie w jeden blok. Trzy elementy pod pismem maja
   byc czytane jako dwie grupy: napis z podpisem, potem dzialanie. */
.frame-act {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  margin-block-start: clamp(1.5rem, 4.5vh, 2.5rem);
}

/* PRZYCISK BEZ KOLOROWEJ POSWIATY POD SPODEM.
   Edited by: Kryst 11.08.26

   Stal tu `box-shadow: 0 18px 40px -18px` w barwie przycisku, czyli glow
   shadow - i to jest, obok dryfujacych plam, drugi najczystszy podpis
   szablonu. Pomaranczowa pastylka na nocnym gruncie ma kontrast 8.9:1
   i nie potrzebuje niczego, zeby sie odciac; poswiata nie dawala jej
   czytelnosci, dawala rejestr.

   Kasowanie musi obejmowac stan `:hover`, bo `.btn-primary:hover` z sekcji
   przyciskow doklada `--shadow-lg` - zdjecie samej deklaracji stad
   zostawiloby cien pojawiajacy sie na najechanie.

   Ruch przy najechaniu zostaje i jest juz zrobiony gdzie trzeba:
   `.btn:hover .icon-arrow` przesuwa strzalke o 0.2em na `--ease-spring`,
   a `.icon-mirror` w szablonie odbija ja po arabsku i persku. */
/* Edited by: Kryst 12.08.26 - wlasna barwa zamiast barwy swiatla.

   Zdjecie poswiaty odslonilo to, co pod nia stalo: duzy, pastelowo-
   brzoskwiniowy owal (`--kadr-accent`, czyli barwa SWIATLA) na srodku
   bardzo ciemnego ekranu. Glebszy pomarancz (`--kadr-cta`, uzasadnienie
   i kontrasty przy tokenach) zdejmuje cukierkowosc.

   Rozmiar, kształt i pelne wypelnienie zostaja BEZ ZMIAN i to jest
   swiadome: ten ekran istnieje po to, zeby ktos ten przycisk kliknal.
   Przycisk ma byc najjasniejszym obiektem kadru - byl nim wczesniej przez
   poswiate, a teraz jest przez to, ze wszystko wokol niego przycichlo.
   Wyscielka schodzi z 3rem na 2.75rem, czyli o wlos, zeby pastylka nie
   byla lozenga; to jedyna zmiana wielkosci. */
.frame-act .btn-primary {
  --btn-bg: var(--kadr-cta);
  --btn-bg-hover: var(--kadr-cta-hover);
  --btn-fg: var(--kadr-on-accent);
  padding-inline: clamp(1.9rem, 3.6vw, 2.75rem);
}
.frame-act .btn-primary,
.frame-act .btn-primary:hover { box-shadow: none; }

/* Data szeptem. Bez ikony, bez kropki, bez slowa "uwaga" - to jedyna liczba
   na ekranie i ma byc czytelna dopiero wtedy, gdy ktos jej poszuka. */
.frame-date {
  /* KRESKA KOLOFONU - krotka i wysrodkowana, nie na szerokosc daty.
     Edited by: Kryst 12.08.26 - `border-block-start` -> `::before` 2.5rem.

     Poprzednia wersja stawiala obramowanie na calej szerokosci tekstu.
     `.frame-act` jest wysrodkowana kolumna flex, wiec ta szerokosc wypadala
     zblizona do szerokosci przycisku - i pod pismem stawaly trzy poziome
     paski jeden nad drugim: podpis, przycisk, kreska. Kreska mialaby byc
     kolofonem, a czytala sie jako separator w karcie.

     Kolofon to kreska KROTKA i wysrodkowana. 2.5rem to ~2.5 firetu tego
     stopnia pisma; przy tej dlugosci nie konkuruje z niczym w pionie
     i robi to, co miala: mowi, ze pod nia jest metryczka, nie tresc.

     `translateX(-50%)` jest fizyczne, ale symetryczne - srodkowanie wyglada
     tak samo w obu kierunkach pisma. */
  position: relative;
  margin: 0;
  padding-block-start: var(--space-4);
  font-size: var(--text-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* 5.7:1 - szept, ale nie ponizej progu. Przy 0.4 wychodzilo 3.3:1,
     czyli napis, ktorego czesc ludzi po prostu nie widzi. */
  color: rgba(246, 241, 232, 0.55);
  font-variant-numeric: tabular-nums;
}
/* Edited by: Kryst 12.08.26 - ta sama kreska pod data na ekranie
   miedzynaborowym (`.frame-note`, nizej). Jedna definicja, bo to jest
   jedna rzecz: znak, ze ponizej stoi metryczka, a nie tresc. Kreska
   nalezy do POWIAZANIA dwoch rzeczy, wiec selektor jest siostrzany -
   sam komunikat, bez daty nad nim, nie ma czego oddzielac. */
.frame-date::before,
.frame-open + .frame-note::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  inline-size: 2.5rem;
  block-size: 1px;
  background: rgba(246, 241, 232, 0.2);
}
:root:lang(ar) .frame-date,
:root:lang(fa) .frame-date { letter-spacing: 0; text-transform: none; }


/* --- Ekran miedzynaborowy: data zamiast przycisku -----------------------
   Edited by: Kryst 12.08.26 - `no_registration` przepisany na kadr.

   Ekran, ktory stoi pod adresem glownym przez WIEKSZOSC ROKU, zostal na
   ukladzie sprzed kadru: masthead z mostem, rozlozony pasek jezykow,
   strzalka "przewin nizej" i trzy ponumerowane wpisy. Nie mial ani
   jednego elementu wspolnego z ekranem, ktory widac w trakcie naboru,
   wiec serwis wygladal jak dwie rozne instytucje w zaleznosci od pory
   roku.

   Kadr jest teraz ten sam co do piksela - obraz, powitanie, podpis,
   wstega i koperta stoja we wspolnych plikach (partials/kadr_obraz.html,
   partials/kadr_stopka.html). Rozni go jedna rzecz i stad te cztery nazwy:
   w miejscu przycisku zapisu stoi DATA OTWARCIA, bo to jedyna rzecz, po
   ktora ktos na ten ekran wraca.

   DATA NIE MA WYGLADAC JAK PRZYCISK. Bez wypelnienia, obrysu, cienia
   i bez stanu :hover - nie ma tu czego kliknac, a pastylka, w ktora
   ktos klika bez skutku, jest gorsza od zwyklego napisu. Od reszty
   ekranu odroznia ja wylacznie stopien pisma i barwa swiatla. */
.frame-open {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

/* Etykieta w rejestrze podpisu kadru - te same wersaliki i to samo
   swiatlo miedzyliterowe, co `.frame-caption` i `.frame-date`. Data pod
   nia ma byc czytana jako odpowiedz na nia, a nie jako druga liczba. */
.frame-open-label {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--kadr-ink-muted);
}

.frame-open-date {
  /* Drugi stopien pisma na ekranie, zaraz po powitaniu. Nie wiecej:
     powitanie liczy sobie stopien z 24svh (patrz `.hail`), wiec data
     rownie duza zaczelaby z nim konkurowac, a to jest podpis pod nia. */
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 5vw, 3.25rem);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.02em;
  /* Barwa SWIATLA (7.2:1 na granacie), nie barwa przycisku. Ta sama
     zasada, co przy jezyku biezacym we wstedze: pomarancz znaczy tu
     "to jest ta rzecz", a nie "to sie klika". Wypelniony `--kadr-cta`
     zostaje wylacznie dla dzialan. */
  color: var(--kadr-accent);
  font-variant-numeric: tabular-nums;
  /* Halacja jak pod powitaniem, ciasniejsza o polowe - data jest
     mniejsza, wiec swiatlo odbite od niej tez. Bez tego jedyny cieply
     napis na ekranie lezy plasko na tle, ktore ma glebie. */
  text-shadow: 0 0 24px rgba(240, 170, 100, 0.16);
}

:root:lang(ar) .frame-open-label,
:root:lang(fa) .frame-open-label {
  letter-spacing: 0;
  text-transform: none;
  font-size: var(--text-sm);
}
/* Naskh nie znosi ujemnego trackingu - ta sama poprawka, co przy `.hail`. */
:root:lang(ar) .frame-open-date,
:root:lang(fa) .frame-open-date { letter-spacing: 0; line-height: 1.5; }

/* Jedyne zdanie na tym ekranie: "nic sie nie stalo". Miara 44 znakow,
   bo to jest akapit do przeczytania raz, a nie napis - przy szerokosci
   kadru staloby sie z niego jedna linia przez caly ekran. */
.frame-note {
  position: relative;
  max-inline-size: 44ch;
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.65;
  color: var(--kadr-ink-muted);   /* 6.0:1 */
  text-wrap: pretty;
}
/* Kreska kolofonu (definicja wyzej, przy `.frame-date::before`) pojawia
   sie wylacznie pod data - bez niej komunikat jest cala trescia bloku
   i nie ma sie od czego odcinac. */
.frame-open + .frame-note { padding-block-start: var(--space-4); }


/* --- Dolna krawedz: wstega jezykow i koperta ---------------------------- */
.frame-foot {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* WSTEGA OSMIU PISM - wybor jezyka.
   Na waskim ekranie przesuwa sie palcem zamiast zawijac: osiem slow
   w trzech wierszach zjada dol kadru, a kadr ma byc kadrem. Wygaszenie
   na obu koncach (`mask-image`) jest jedynym sygnalem, ze jest tego
   wiecej - pasek przewijania na tym ekranie bylby zadrasznieciem.

   WYSCIELKA POZIOMA JEST CZESCIA WYGASZENIA, NIE ODSTEPEM.
   Edited by: Kryst 11.08.26

   Wygaszenie zjada 2.5rem od kazdej krawedzi - takze wtedy, gdy wstega
   stoi nieprzewinieta i na tych 2.5rem lezy pierwsze slowo. Na telefonie
   znaczylo to, ze powitanie w jezyku, ktory akurat wypadl pierwszy na
   liscie ("Welcome"), bylo przygaszone do polowy bez powodu - wygladalo
   na wylaczone, a jest odnosnikiem. Wyscielka rownej szerokosci odsuwa
   slowa spod maski, wiec przy zerowym przewinieciu wygaszenie pada na
   puste miejsce i widac je dopiero wtedy, gdy faktycznie cos za nim
   jest. Na szerokim ekranie nie robi nic - wstega i tak jest srodkowana. */
.hellos {
  flex: 1 1 auto;
  display: flex;
  align-items: baseline;
  gap: clamp(1rem, 2.4vw, 2.25rem);
  min-inline-size: 0;
  overflow-x: auto;
  scrollbar-width: none;
  padding-block: var(--space-2);
  padding-inline: 2.5rem;
  font-family: var(--font-display);
  font-size: clamp(0.9375rem, 1.15vw, 1.1875rem);
  line-height: 1.4;
  white-space: nowrap;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 2.5rem, #000 calc(100% - 2.5rem), transparent);
  mask-image: linear-gradient(to right, transparent, #000 2.5rem, #000 calc(100% - 2.5rem), transparent);
}
.hellos::-webkit-scrollbar { display: none; }

@media (min-width: 64rem) {
  .hellos { justify-content: center; }
}

.hello {
  /* 5.8:1. Wstega jest przygaszona, ale to jest OSIEM ODNOSNIKOW, nie
     ozdoba - musi przechodzic AA jak kazdy inny tekst. */
  color: rgba(246, 241, 232, 0.56);
  text-decoration: none;
  transition: color var(--duration-base) var(--ease-out);
}
.hello:hover,
.hello:focus-visible { color: var(--kadr-ink); }

/* Jezyk biezacy niesie kolor, nie brak koloru. */
.hello[aria-current="true"] { color: var(--kadr-accent); }

.hello:lang(ar),
.hello:lang(fa) { font-size: 1.12em; }

/* Koperta. Caly kontakt, jaki jest na tym ekranie: bez podpisu, bez slowa
   "kontakt", bez akapitu wyjasniajacego, ze to kontakt. */
.frame-mail {
  display: inline-grid;
  place-items: center;
  flex: none;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  border-radius: var(--radius-full);
  color: rgba(246, 241, 232, 0.5);
  transition:
    background var(--duration-fast) var(--ease-out),
    color      var(--duration-fast) var(--ease-out);
}
.frame-mail:hover {
  background: rgba(246, 241, 232, 0.08);
  color: var(--kadr-ink);
}

/* --- Otwarcie -----------------------------------------------------------
   Stanem bazowym kazdej regulki wyzej jest stan KONCOWY, a animacje
   dokladaja tylko droge do niego. Dzieki temu pominiecie otwarcia to
   `animation: none` i nic wiecej - nie ma drugiego kompletu regul
   opisujacych "kadr po pominieciu", ktory moglby sie z pierwszym rozjechac.

   Trzy powody, dla ktorych otwarcie znika:
     - `prefers-reduced-motion` (regula ponizej obejmuje wszystko),
     - ktos juz je w tej sesji widzial (`:root.bez-otwarcia`, ustawiane
       w <head> przed pierwszym malowaniem - zmiana jezyka przeladowuje
       strone i bez tego czolowka leciala by od nowa przy kazdym kliknieciu
       we wstege),
     - ktos kliknal albo nacisnal klawisz (ta sama klasa, z nav.js). */
/* CALOSC TRWA 1,3 s, NIE 3,1 s.
   Edited by: Kryst 11.08.26

   Poprzednia czolowka szla w pieciu taktach: luk (0,25-1,75 s), slowo
   (1,15-2,05), podpis (1,95), przycisk (2,25), wstega slowo po slowie
   (2,55-3,04) i koperta (3,11). Kazdy takt byl ladny i cala rzecz mimo to
   czytala sie jako czolowka z generatora, bo rozpoznaje sie ja nie po
   ruchu, tylko po SCHODKACH: element za elementem, kazdy w swojej chwili,
   przez trzy sekundy. Drogie rzeczy robia jeden ruch.

   Teraz sa trzy fazy i tylko pierwsze dwie sa osobne:
     1. swiatlo pada i rysuje horyzont (0,1-1,0 s),
     2. powitanie wychodzi z tej linii i LADUJE RAZEM z jej koncem
        (0,32-0,92 s), wiec to jest jeden gest, nie dwa,
     3. wszystko pozostale wstaje naraz (0,70-1,32 s).

   Podpis i przycisk dzieli 60 ms - tyle, zeby oko widzialo kolejnosc,
   za malo, zeby czekalo. Wstega zamiata w 20 ms na slowo (bylo 70), czyli
   8 jezykow w 140 ms zamiast 490: nadal zapisuje sie po kolei, ale juz
   nie jako lista.

   Bez zmian zostaje zasada, na ktorej wisi cala reszta: stanem BAZOWYM
   kazdej regulki jest stan koncowy, a animacje dokladaja tylko droge. */
@media (prefers-reduced-motion: no-preference) {
  :root:not(.bez-otwarcia) .frame { animation: kadr-in 400ms var(--ease-out) both; }
  @keyframes kadr-in { from { opacity: 0; } to { opacity: 1; } }

  /* 1. Swiatlo rysuje horyzont. pathLength="1" normalizuje dlugosc sciezki,
        wiec kreskowanie nie zalezy od jej dlugosci w jednostkach viewBoxa. */
  /* `.horyzont`, nie `path` - Edited by: Kryst 12.08.26.
     W SVG stoi teraz drugi `path`: wypelnienie tarczy swiata. Selektor po
     nazwie elementu obejmowalby i jego, a kreskowanie sciezki bez obrysu
     jest wprawdzie bezskutkowe, ale regula, ktora celuje w cos innego niz
     opisuje, jest pulapka na nastepna zmiane. Klase nosza oba obrysy
     horyzontu (poswiata i wloskowa linia), tarcza jej nie ma. */
  :root:not(.bez-otwarcia) .arc .horyzont {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: luk-rysuj 900ms var(--ease-out) 100ms both;
  }
  @keyframes luk-rysuj { to { stroke-dashoffset: 0; } }

  /* 2. Powitanie wychodzi z linii swiatla. clip-path zamkniety w poziomie
        w polowie wysokosci to szczelina; jego otwarcie czyta sie jako
        "slowo wychodzi ze swiatla", a nie jako "tekst sie pojawil".
        Rozmycie schodzi do zera z lekkim opoznieniem, zeby litery
        wyostrzyly sie juz PO wyjsciu. */
  :root:not(.bez-otwarcia) .hail {
    animation:
      slowo-otworz 600ms var(--ease-out) 320ms both,
      slowo-wyostrz 500ms var(--ease-out) 400ms both;
  }
  @keyframes slowo-otworz {
    from { clip-path: inset(50% 0 50% 0); }
    /* Koncowy inset jest mocno ujemny, bo clip-path tnie takze
       poswiate spod liter (text-shadow siega 52 px). */
    to   { clip-path: inset(-150% -5% -150% -5%); }
  }
  @keyframes slowo-wyostrz {
    from { opacity: 0.2; filter: blur(10px); }
    to   { opacity: 1;   filter: blur(0); }
  }

  /* 3. Podpis, przycisk, data - naraz, z dolu. 60 ms miedzy nimi to tyle,
        zeby oko widzialo kolejnosc, i za malo, zeby na nia czekalo. */
  :root:not(.bez-otwarcia) .frame-caption { animation: kadr-wstan 420ms var(--ease-out) 700ms both; }
  :root:not(.bez-otwarcia) .frame-act     { animation: kadr-wstan 420ms var(--ease-out) 760ms both; }
  @keyframes kadr-wstan {
    from { opacity: 0; transform: translateY(0.75rem); }
    to   { opacity: 1; transform: none; }
  }

  /* 4. Wstega zapisuje sie na koncu, slowo po slowie.
        Edited by: Kryst 11.08.26 - opoznienie z `--i`, nie z `:nth-child`.

        Stalo tu osiem regul, po jednej na jezyk. Osiem, bo tyle jest
        jezykow DZISIAJ - a CLAUDE.md obiecuje, ze dziewiaty wymaga
        jednej linijki w LANGUAGES i niczego wiecej. Dziewiaty odnosnik
        nie trafialby w zadna z osmiu regul, wiec dostalby opoznienie
        zerowe: pojawialby sie natychmiast, sam, zanim ruszy pierwszy.
        Numer pozycji podaje szablon w `--i`, arkusz mnozy. */
  :root:not(.bez-otwarcia) .hello {
    --i: 0;
    animation: kadr-wstan 340ms var(--ease-out) calc(820ms + var(--i) * 20ms) both;
  }
  :root:not(.bez-otwarcia) .frame-mail { animation: kadr-wstan 340ms var(--ease-out) 980ms both; }

  /* AMBIENT - jedyny ruch, ktory nie konczy sie nigdy.
     Edited by: Kryst 11.08.26 - dryf plam -> kolysanie snopa.

     Okresy 83 i 127 s sa wzglednie pierwsze, wiec para smug nie wraca do
     tej samej pozycji przez ponad godzine. Oko nie ma czego zapetlic - to
     zostaje z poprzedniej wersji i jest w niej najlepsza rzecza.

     Zmienia sie AMPLITUDA. Plamy jezdzily po 9-11 vw i skalowaly sie o
     14%, bo plama moze - nikt nie wie, gdzie plama "powinna" byc. Snop ma
     zrodlo, wiec kazdy wiekszy ruch mowi, ze zrodlo lata po niebie.
     Zostaje +-2 stopnie obrotu, czyli tyle, ile robi swiatlo dnia idace
     za sloncem. Ruch ma byc na granicy zauwazalnosci; jego zadaniem jest
     tylko to, zeby obraz nie byl martwy.

     Obrot doklada sie do `--rake`, nie zastepuje go - inaczej kolysanie
     rozprostowaloby snop do pionu. Po arabsku `--rake` ma znak przeciwny,
     wiec calc() liczy tam to samo w druga strone, a 2 stopnie w jedna albo
     w druga strone sa nierozroznialne.

     Edited by: Kryst 12.08.26 - wypada `translate3d`.

     Kolysanie mialo tu jeszcze przesuniecie o 1.5-2%. Przy prostokacie to
     bylo obojetne, przy klinie wychodzacym z punktu jest sprzeczne z cala
     konstrukcja: `transform-origin` stoi w `--apex` wlasnie po to, zeby
     snop obracal sie WOKOL ZRODLA, a przesuniecie wozi zrodlo razem z nim.
     Zostaje czysty obrot - swiatlo idace za sloncem, a nie latarka w rece.

     Edited by: Kryst 12.08.26 - kolysze sie NIEBO, nie pojedyncze pasma.

     Przy dwoch smugach obrot siedzial na kazdej z nich osobno i to bylo
     w porzadku. Przy osmiu warstwach byloby juz nie: siedem promieni
     obracanych osobno rozjezdza sie i zbiega, czyli pek zaczyna oddychac
     jak zaluzja, a nie stac w swietle. Kolysze sie wiec `.sky`, czyli
     wszystkie warstwy naraz i jako jedna bryla - a `--f` przy `.ray`
     zostaje tym, czym jest: stalym rozejsciem, nie ruchem.

     Dodatkowa korzysc jest po stronie kosztu. `.sky` ma `will-change`
     i `opacity`, wiec przegladarka trzyma caly pek jako JEDNA warstwe
     kompozycji i obraca ja w calosci. Osiem osobno animowanych warstw
     kosztowaloby osiem razy tyle - a przed zmiana animowane byly dwie.

     Obwiednia dostaje wlasny, wolniejszy przeciwbieg: przy jednym wspolnym
     obrocie poswiata i promienie stalyby wzgledem siebie nieruchomo, czyli
     ruch bylby widoczny tylko na krawedziach kadru. Okresy 97 i 149 s sa
     wzglednie pierwsze - ten sam uklad wraca raz na cztery godziny. */
  .sky        { animation: niebo-kolysze  97s var(--ease-in-out) infinite alternate; }
  .shaft-halo { animation: halo-kolysze  149s var(--ease-in-out) infinite alternate; }
  @keyframes niebo-kolysze { to { transform: rotate(1.6deg); } }
  @keyframes halo-kolysze  { to { transform: rotate(calc(var(--rake) - 1.1deg)); } }

  /* Ziarno skacze osiem razy na sekunde - tyle, ile ma tasma. */
  .grain { animation: ziarno 800ms steps(8) infinite; }
  @keyframes ziarno {
    0%   { transform: translate3d(0, 0, 0); }
    25%  { transform: translate3d(-6%, 4%, 0); }
    50%  { transform: translate3d(5%, -5%, 0); }
    75%  { transform: translate3d(-4%, -3%, 0); }
    100% { transform: translate3d(3%, 5%, 0); }
  }
}

/* Kadr na papierze jest pol strony toneru za tlo, ktorego nikt nie chcial.
   Zostaje powitanie, jedno zdanie i adres. */
@media print {
  /* `min-block-size` razem z `block-size` - kadr ma na papierze zajac
     tyle, ile zajmuje tresc, a nie wysokosc okna przeliczona na strone. */
  .frame {
    block-size: auto;
    min-block-size: 0;
    padding: 0;
    background: none;
    color: #000;
  }
  body.kadr { background: none; }
  /* Edited by: Kryst 12.08.26 - `.frame::before` zdjety z listy razem
     z kliszą, ktorej juz nie ma (uzasadnienie wyzej, przy `.frame`). */
  .frame::after,
  .sky, .grain, .arc, .hellos, .frame-mail { display: none !important; }
  .hail { font-size: 2rem; font-weight: 500; text-shadow: none; }
  /* Kreska kolofonu byla liczona na nocny grunt (biel przy 0.2 alfa) -
     na papierze nie ma jej z czego zrobic. Edited by: Kryst 12.08.26 */
  .frame-date,
  .frame-open + .frame-note { padding-block-start: 0; }
  .frame-date::before,
  .frame-open + .frame-note::before { display: none; }
  /* Barwy kadru sa liczone na nocny grunt: `--kadr-ink-muted` to biel
     przy 0.58 alfa, a `--kadr-accent` jasny pomarancz. Na bialym papierze
     pierwsza znika calkiem, druga zostawia napis o kontrascie ponizej
     2:1. `.frame` ustawia `color: #000`, ale te trzy klasy maja wlasny
     kolor, wiec go nie dziedzicza. Edited by: Kryst 12.08.26 */
  .frame-date,
  .frame-note,
  .frame-open-label,
  .frame-open-date { color: #000; text-shadow: none; }
}


/* WPISY (`.entries`, `.entry`, `.entry-index`, `.entry-dates`) ZESZLY
   12.08.26 razem z ekranem, ktory je mial - `no_registration`.
   Edited by: Kryst 12.08.26

   Byl to uklad redakcyjny: numer, wlosowa linia, tekst - trzy rzeczy do
   przeczytania po kolei. Zastapienie ich kadrem nie jest zamiana wygladu
   na inny wyglad, tylko odpowiedzia na pytanie, po co ktos tu przyszedl:
   po date otwarcia, ktora dzis stoi w kadrze pojedynczo i duzo.

   Gdyby jakis ekran kursanta znow potrzebowal ciagu ponumerowanych
   akapitow - to sa cztery reguly do odtworzenia z historii, nie modul.
   ========================================================================= */


/* --- Stopka ------------------------------------------------------------
   Strony kursanta nie mialy stopki w ogole - tresc konczyla sie
   w polowie ekranu i zaczynala biala pustka. Poza wrazeniem rzeczy
   niedokonczonej znaczylo to, ze adres kontaktowy istnial wylacznie
   na tych ekranach, ktore go akurat wypisywaly.
   ========================================================================= */
/* Bez wlasnego marginesu u gory - <main> ma juz padding-block-end,
   a dodanie do niego drugiego odstepu odsuwalo stopke o 11rem od tresci
   i na krotkich stronach zostawialo ja pod krawedzia ekranu. */
.site-footer {
  border-block-start: 1px solid var(--rule);
  background: var(--bg);
}

.site-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-6);
  padding-block: var(--space-10) var(--space-12);
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.site-footer strong {
  display: block;
  margin-block-end: var(--space-1);
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text);
}

/* Adres i wejscie do panelu w jednej grupie, dosuniete do konca wiersza.
   Bez tego trzy elementy rozdzielone przez `space-between` daja srodkowy
   wisracy w polowie stopki, bez zwiazku z zadna krawedzia. */
.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3) var(--space-8);
}

.site-footer a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-muted);
}
.site-footer a:hover { color: var(--accent-text); }
.site-footer a .icon { color: var(--text-subtle); }


/* --- Kroki rejestracji -------------------------------------------------
   Rejestracja to cztery ekrany. Bez tego paska kursant nie wie, ile
   zostalo - a to jest moment, w ktorym ludzie rezygnuja. */
.steps {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-block-end: var(--space-10);
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.step {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  position: relative;
  text-align: center;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-subtle);
  min-inline-size: 0;
}

/* Edited by: Kryst 11.08.26 - redesign.
   Cyfra kroku idzie krojem naglowkowym i cyframi tabelarycznymi: numer
   kroku jest tu liczba w ciagu, a nie etykieta, wiec ma sie zmieniac
   na miejscu, a nie przeskakiwac w bok miedzy "1" a "4". */
.step-dot {
  display: grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border-radius: 50%;
  background: var(--bg-elevated);
  border: 1.5px solid var(--border-strong);
  color: var(--text-subtle);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  transition: all var(--duration-base) var(--ease-out);
}

/* Kreska laczaca - rysowana od kropki w strone poczatku wiersza, wiec
   w RTL sama laduje po drugiej stronie */
/* Wysokosc i przesuniecie ida za srednica kropki (2.25rem): linia ma
   trafic w jej srodek i konczyc sie na jej krawedzi, nie pod nia. */
.step:not(:first-child)::before {
  content: "";
  position: absolute;
  inset-block-start: 1.125rem;
  inset-inline-end: 50%;
  inline-size: 100%;
  block-size: 1px;
  background: var(--rule);
  transform: translateX(-1.125rem);
  z-index: -1;
}
:dir(rtl) .step:not(:first-child)::before { transform: translateX(1.125rem); }

.step-label { line-height: var(--leading-snug); }

.step.is-done .step-dot {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
.step.is-done { color: var(--text-muted); }

.step.is-current .step-dot {
  background: var(--accent-solid);
  border-color: var(--accent-solid);
  color: var(--text-on-accent);
  box-shadow: 0 0 0 5px var(--accent-subtle);
}
.step.is-current { color: var(--accent-text); }

@media (max-width: 30rem) {
  .step-label { display: none; }
  .step.is-current .step-label { display: block; }
}

/* --- Test jezykowy ------------------------------------------------------ */

/* Kazde pytanie to <fieldset> z <legend> - to poprawna semantyka dla grupy
   pol wyboru i jedyna, ktora czytnik ekranu odczyta jako "pytanie X, opcja
   1 z 4". Fieldset wymaga jednak trzech resetow: ma wlasne obramowanie,
   wlasne marginesy boczne i domyslne min-inline-size: min-content, ktore
   rozpycha siatke odpowiedzi ponad szerokosc ekranu. */
fieldset.question {
  margin-inline: 0;
  border: 1px solid var(--border);
  min-inline-size: 0;
}
legend.question-head {
  inline-size: 100%;
  padding: 0;
}

.question {
  padding: var(--space-6);
  margin-block-end: var(--space-5);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  transition: border-color var(--duration-base) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out);
}
/* Pytanie z udzielona odpowiedzia cichnie - wzrok idzie do nastepnego */
.question:has(input:checked),
.question:has(textarea:not(:placeholder-shown)) {
  border-color: var(--success);
  background: color-mix(in srgb, var(--success-subtle) 60%, var(--bg-elevated));
}
.question:focus-within { border-color: var(--accent); box-shadow: var(--shadow-md); }

.question-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin-block-end: var(--space-5);
}

.question-number {
  display: grid;
  place-items: center;
  inline-size: 1.85rem;
  block-size: 1.85rem;
  flex: none;
  border-radius: 50%;
  background: var(--brand-subtle);
  color: var(--brand-text);
  font-size: var(--text-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.question-text {
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: var(--leading-snug);
  color: var(--text);
}

/* Odpowiedzi jednokrotnego wyboru - karty, nie krazki. Latwiej trafic. */
.answers {
  display: grid;
  gap: var(--space-3);
}
@media (min-width: 40rem) {
  .answers { grid-template-columns: repeat(2, 1fr); }
}

.answer {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-elevated);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
  font-weight: 500;
  line-height: var(--leading-snug);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    background   var(--duration-fast) var(--ease-out),
    transform    var(--duration-fast) var(--ease-out);
}
.answer:hover { border-color: var(--accent); background: var(--accent-subtle); }
.answer:active { transform: scale(0.99); }
.answer:has(input:checked) {
  border-color: var(--accent);
  border-width: 2px;
  background: var(--accent-subtle);
  font-weight: 600;
}
.answer:has(input:focus-visible) { outline: 3px solid var(--focus-ring); outline-offset: 2px; }

/* --- Wybor kursu -------------------------------------------------------- */
.course-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
}

.course {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--bg-elevated);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition:
    border-color var(--duration-base) var(--ease-out),
    box-shadow   var(--duration-base) var(--ease-out),
    transform    var(--duration-base) var(--ease-out);
}
.course:hover { border-color: var(--accent); box-shadow: var(--shadow-md); }
.course:has(input:checked) {
  border-color: var(--accent);
  border-width: 2px;
  background: var(--accent-subtle);
  box-shadow: var(--shadow-md);
}
.course:has(input:focus-visible) { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.course input { margin-block-start: 0.2rem; }

.course > span { display: block; min-inline-size: 0; }

.course-name {
  display: block;
  margin-block-end: var(--space-3);
  font-size: var(--text-lg);
  font-weight: 700;
}

.course-schedule {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.course-schedule li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.course-schedule .icon {
  inline-size: 1em;
  block-size: 1em;
  flex: none;
  color: var(--text-subtle);
}

/* --- Wynik testu -------------------------------------------------------- */
/* Edited by: Kryst 11.08.26 - redesign.
   Wynik testu to jedyny moment calej sciezki, w ktorym kursant dostaje
   informacje O SOBIE. Wczesniej byl niebieskim prostokatem z dwiema
   liczbami posrodku, czyli wygladal jak kazdy inny blok na stronie.
   Teraz jest zestawieniem na liniach, z liczba wielkosci naglowka -
   ma sie czytac jak wpis w dokumencie, nie jak komunikat systemu. */
.score {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-10);
  padding-block: var(--space-6);
  margin-block-end: var(--space-8);
  border-block: 1px solid var(--rule);
}
.score-value {
  font-family: var(--font-display);
  font-size: var(--text-4xl);
  font-weight: 500;
  line-height: 1;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.score-label { margin-block-end: var(--space-2); }
.score-label {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}
:root:lang(ar) .score-label, :root:lang(fa) .score-label {
  text-transform: none; letter-spacing: 0;
}
.score-level {
  display: inline-grid;
  place-items: center;
  min-inline-size: 4rem;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-full);
  background: var(--accent-solid);
  color: var(--text-on-accent);
  font-size: var(--text-xl);
  font-weight: 800;
}

/* --- Ekran koncowy ------------------------------------------------------ */
.outcome {
  max-width: var(--container-text);
  margin-inline: auto;
  text-align: center;
}
/* Edited by: Kryst 11.08.26 - redesign.
   Pierscien zamiast pelnego kola. Wypelniona tarcza w kolorze stanu to
   jezyk powiadomien systemowych ("zapisano", "blad") - tutaj chodzi
   o zamkniecie czegos, co zajelo kwadrans, wiec znak jest cichszy
   i wiekszy, a ciezar bierze na siebie naglowek pod nim. */
.outcome-mark {
  display: grid;
  place-items: center;
  inline-size: 5.5rem;
  block-size: 5.5rem;
  margin: 0 auto var(--space-8);
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--success) 35%, transparent);
  background: var(--success-subtle);
  color: var(--success);
}
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .outcome-mark { border-color: var(--success); }
}
.outcome-mark .icon { inline-size: 2.5rem; block-size: 2.5rem; }
.outcome-mark-error {
  background: var(--danger-subtle);
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
}
/* Edited by: Kryst 11.08.26 (PLAN.md P4)
   Trzeci wariant, bo ekran koncowy ma trzy wymowy, nie dwie. "Juz sie
   zapisales" i "nie wybrales grupy" to sytuacje, w ktorych system zadzialal
   poprawnie - czerwony pierscien mowilby czlowiekowi, ze cos zepsul, akurat
   wtedy, gdy zrobil wszystko dobrze. Kolor akcentu, nie ostrzezenia: to nie
   jest nawet polbled. */
.outcome-mark-info {
  background: var(--accent-subtle);
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .outcome-mark-error { border-color: var(--danger); }
  .outcome-mark-info { border-color: var(--accent); }
}

/* Edited by: Kryst 11.08.26 (PLAN.md P4)
   "Co teraz zrobic" jest osobnym akapitem, wyroznionym, bo to jedyne zdanie
   na tym ekranie, po ktorym czlowiek ma cos ZROBIC. Reszta tylko wyjasnia. */
.outcome-next {
  margin-block-start: var(--space-6);
  padding: var(--space-5) var(--space-6);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--bg-sunken);
  text-align: start;
}
.outcome-next .icon { color: var(--accent); flex: none; }
.outcome-next-row { display: flex; gap: var(--space-3); align-items: flex-start; }
.outcome-next p { font-size: var(--text-base); color: var(--text); margin: 0; }

.outcome p { font-size: var(--text-lg); color: var(--text-muted); }

@media (prefers-reduced-motion: no-preference) {
  .outcome-mark { animation: pop var(--duration-slow) var(--ease-spring) both; }
  .outcome-mark .icon { animation: pop var(--duration-slow) var(--ease-spring) 120ms both; }
}
@keyframes pop {
  from { opacity: 0; transform: scale(0.6); }
  to   { opacity: 1; transform: none; }
}


/* =========================================================================
   12. KOMUNIKATY I STANY
   ========================================================================= */

.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  margin-block-end: var(--space-5);
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--brand);
  border-radius: var(--radius-md);
  background: var(--bg-elevated);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  box-shadow: var(--shadow-xs);
}
.alert .icon { inline-size: 1.25rem; block-size: 1.25rem; flex: none; margin-block-start: 0.1em; }
.alert-body { flex: 1 1 auto; }
.alert-title { font-weight: 700; margin-block-end: var(--space-1); }

.alert-success { border-inline-start-color: var(--success); background: var(--success-subtle); color: var(--text); }
.alert-success .icon { color: var(--success); }
.alert-warning { border-inline-start-color: var(--warning); background: var(--warning-subtle); }
.alert-warning .icon { color: var(--warning); }
.alert-danger, .alert-error { border-inline-start-color: var(--danger); background: var(--danger-subtle); }
.alert-danger .icon, .alert-error .icon { color: var(--danger); }
.alert-info { border-inline-start-color: var(--brand); background: var(--brand-subtle); }
.alert-info .icon { color: var(--brand-text); }

.alert-close {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 2rem;
  block-size: 2rem;
  margin: -0.25rem -0.5rem -0.25rem 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-subtle);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}
.alert-close:hover { background: var(--bg-inset); color: var(--text); }
.alert-close .icon { inline-size: 1rem; block-size: 1rem; }

.messages { margin-block-end: var(--space-6); }

/* Blok informacyjny - lzejszy od alertu, do tresci pomocniczej */
.note {
  padding: var(--space-5);
  background: var(--bg-inset);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}
.note ul { margin: var(--space-3) 0 0; padding-inline-start: var(--space-5); }
.note li + li { margin-block-start: var(--space-2); }

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.2rem var(--space-3);
  border-radius: var(--radius-full);
  background: var(--bg-inset);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  white-space: nowrap;
}
.badge-success { background: var(--success-subtle); color: var(--success); }
.badge-warning { background: var(--warning-subtle); color: var(--warning); }
.badge-danger  { background: var(--danger-subtle);  color: var(--danger); }
.badge-brand   { background: var(--brand-subtle);   color: var(--brand-text); }
.badge-accent  { background: var(--accent-subtle);  color: var(--accent-text); }

/* Stan pusty */
.empty {
  padding: var(--space-16) var(--space-6);
  text-align: center;
  color: var(--text-muted);
}
.empty-icon {
  display: grid;
  place-items: center;
  inline-size: 4rem;
  block-size: 4rem;
  margin: 0 auto var(--space-5);
  border-radius: 50%;
  background: var(--bg-inset);
  color: var(--text-subtle);
}
.empty-icon .icon { inline-size: 2rem; block-size: 2rem; }




/* =========================================================================
   13. RUCH

   Trzy warstwy, wszystkie opcjonalne:
     - przejscia miedzy stronami (View Transitions) - dziala bez JS,
       przegladarka bez wsparcia po prostu przeladowuje strone,
     - odslony przy przewijaniu (scroll-driven animations) - jw.,
     - mikrointerakcje na elementach sterujacych.

   Cala sekcja siedzi pod prefers-reduced-motion: no-preference.
   ========================================================================= */

@media (prefers-reduced-motion: no-preference) {

  /* --- Przejscia miedzy dokumentami ------------------------------------
     Jedna deklaracja zamienia przeladowanie strony w plynne przejscie.
     Sciezka kursanta (formularz -> test -> wybor kursu -> potwierdzenie)
     przestaje migac na bialo miedzy krokami. */
  @view-transition { navigation: auto; }

  ::view-transition-old(root) {
    animation: vt-out var(--duration-base) var(--ease-in-out) both;
  }
  ::view-transition-new(root) {
    animation: vt-in var(--duration-slow) var(--ease-out) both;
  }
  @keyframes vt-out { to { opacity: 0; transform: translateY(-0.5rem); } }
  @keyframes vt-in { from { opacity: 0; transform: translateY(1rem); } }

  /* Nawigacja i logo maja trwac miedzy stronami, nie przenikac razem z trescia */
  .nav { view-transition-name: nav; }
  ::view-transition-old(nav), ::view-transition-new(nav) { animation: none; }

  /* `.reveal` (wejscie calej sekcji) i `.reveal-stagger` (kaskada po
     dzieciach) zeszly 12.08.26 razem z masztem na `no_registration` -
     byl to ostatni szablon, ktory je nosil. Kadr ma wlasne otwarcie
     (sekcja `.frame`), a odslona przy przewijaniu ponizej zostaje: uzywaja
     jej trzy ekrany sciezki kursanta. Edited by: Kryst 12.08.26 */

  /* --- Odslona przy przewijaniu ----------------------------------------
     animation-timeline: view() nie wymaga ani jednej linii JavaScriptu
     ani IntersectionObservera. Przegladarki bez wsparcia ignoruja regule
     i element jest po prostu widoczny od razu - dlatego warunek @supports
     obejmuje takze stan poczatkowy. */
  @supports (animation-timeline: view()) {
    .reveal-on-scroll {
      animation: reveal-scroll linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 28%;
    }
    @keyframes reveal-scroll {
      from { opacity: 0; transform: translateY(2rem) scale(0.985); }
      to   { opacity: 1; transform: none; }
    }
  }

  /* --- Luki oddychaja ---------------------------------------------------
     Bardzo powolny, ledwo zauwazalny ruch. Animowana jest wylacznie
     transformacja i przezroczystosc, wiec rzecz zyje na kompozytorze
     i nie wywoluje przeliczania ukladu. */
  .arcs::before { animation: drift-a 26s var(--ease-in-out) infinite alternate; }
  .arcs::after  { animation: drift-b 32s var(--ease-in-out) infinite alternate; }
  @keyframes drift-a {
    from { transform: translate3d(0, 0, 0) rotate(0deg); }
    to   { transform: translate3d(-1.5rem, 1rem, 0) rotate(12deg); }
  }
  @keyframes drift-b {
    from { transform: translate3d(0, 0, 0) rotate(0deg); }
    to   { transform: translate3d(1.25rem, -0.75rem, 0) rotate(-10deg); }
  }
}

/* Bez wsparcia dla view() elementy musza byc widoczne - inaczej zostalyby
   przezroczyste na zawsze. Domyslnie nic nie ukrywamy; klasa dziala tylko
   wtedy, gdy przegladarka umie ja ozywic. */


/* =========================================================================
   14. UZYTKI
   ========================================================================= */

.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.icon {
  inline-size: 1.25rem;
  block-size: 1.25rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Ikony kierunkowe odbijaja sie w RTL; symbole (koperta, ksiazka) nie */
:dir(rtl) .icon-mirror { transform: scaleX(-1); }

.center { text-align: center; }
.flow > * + * { margin-block-start: var(--space-4); }
.hidden, [hidden] { display: none !important; }

.divider {
  block-size: 1px;
  border: 0;
  margin-block: var(--space-8);
  background: var(--border);
}

/* Druk: bez ozdob, bez ruchu, bez nawigacji */
@media print {
  /* Wybor jezyka na papierze jest martwa kontrolka. Wstega (.hellos)
     i reszta ozdob kadru znikaja we wlasnym bloku @media print przy .frame. */
  /* Edited by: Kryst 12.08.26 - z listy wypadly `.bridge`, `.langbar`,
     `.masthead` i `.entries`: te nazwy nie maja juz definicji ani uzycia
     (patrz sekcja 11). `.arcs` i `.aurora` zostaja - zeszly przy redesignie
     11.08.26 i nie sa czescia tej zmiany. */
  .nav, .arcs, .aurora, .nav-drawer, .skip-link, .btn,
  .site-footer { display: none !important; }
  body { background: #fff; color: #000; }
  .panel, .card { box-shadow: none; border: 1px solid #ccc; }
  .panel::before { display: none; }
}
