/* =================================================================================================
   Platforma Olimpiady – arkusz stylów interfejsu.

   Bez frameworka i bez CDN-a: jedna zależność mniej w CSP i w SRI, a cała paleta oraz typografia
   są opisane tokenami w :root, więc zmiana wyglądu nie wymaga ruszania szablonów.

   Kolejność sekcji:
     1. fonty (self-hosted, woff2, font-display: swap)
     2. tokeny (paleta, skala odstępów, promienie, cienie) + wariant ciemny
     3. reset i podstawy dokumentu
     4. układ (nagłówek, nawigacja, treść, stopka)
     5. typografia i tekst pomocniczy
     6. komponenty (karty, przyciski, formularze, tabele, badge, alerty, oś czasu, KPI…)
     7. widoki (strona główna, panel uczestnika, panel recenzenta, koordynator)
     8. narzędzia i dostępność

   Zasady:
     - żaden kolor nie jest wpisany „z ręki” poza blokami tokenów,
     - wszystkie stany interaktywne mają :focus-visible o kontraście AA,
     - animacje respektują prefers-reduced-motion,
     - style inline w szablonach nie występują (poza warstwą adnotacji pdf.js, która pozycjonuje
       prostokąty z JS – patrz apps/web/middleware.py).
   ============================================================================================== */

/* --- 1. Fonty ------------------------------------------------------------------------------- */
/* Pliki leżą w static/fonts/ i są serwowane przez WhiteNoise spod 'self' (CSP: font-src 'self').
   Podział latin / latin-ext jest istotny: polskie znaki diakrytyczne (ą ć ę ł ń ś ź ż) mieszkają
   w latin-ext, więc bez drugiego pliku tekst renderowałby się fallbackiem systemowym. */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/inter-latin-ext-400.0a6ff1a7c1e4.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: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/inter-latin-400.da03732a27e1.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: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/inter-latin-ext-500.90ff4f7e8bcb.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: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/inter-latin-500.51205681a1ba.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: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/inter-latin-ext-600.65aed4e1ef63.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: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/inter-latin-600.2ede57db1a3a.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: "Inter";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/inter-latin-ext-700.aa4c26661f64.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: "Inter";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/inter-latin-700.8ca0a5155296.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: "Source Serif 4";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/source-serif-4-latin-ext-600.596d71390520.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: "Source Serif 4";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/source-serif-4-latin-600.beb0c8d8bee8.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: "Source Serif 4";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/source-serif-4-latin-ext-700.8fa74259bc59.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: "Source Serif 4";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/source-serif-4-latin-700.45959eeff97f.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;
}

/* --- 2. Tokeny ----------------------------------------------------------------------------- */

:root {
  color-scheme: light dark;

  /* Paleta bazowa – akademicka: granat, papier, czerwień z logotypu. */
  --navy-900: #0b1729;
  --navy-800: #12233f;
  --navy-700: #1b3153;
  --navy-600: #274677;

  /* Czerwień marki jest wzięta z logotypu organizatora (ciemna czerwień #8b0404, stopień 700).
     Stopnie istnieją dlatego, że jeden odcień nie obsłuży obu tł serwisu:
     - 600 (#a10f0f) to najjaśniejszy odcień, który trzyma 8.1:1 z białym tekstem – przyciski
       i plamy na papierze,
     - 700/800 to obrys i stan „hover” tych przycisków,
     - 500 jest jeszcze widoczny jako gruba kreska na granacie (3.2:1),
     - 300 jest jedynym, który jako **tekst** na granacie ma ≥4.5:1 (6.4:1 na --navy-800).
     Stąd dwie role: ``--accent`` na jasnym tle i ``--accent-on-brand`` w pasku/stopce. */
  --red-300: #ef8a82;
  --red-500: #d43228;
  --red-600: #a10f0f;
  --red-700: #8b0404;
  --red-800: #6d0303;

  /* Bursztyn został wyłącznie kolorem statusu „ostrzeżenie” – nie jest już kolorem marki. */
  --amber-600: #c8892b;
  --amber-700: #a06a17;
  --amber-800: #7a4f0c;
  --green-700: #2e7d5b;
  --green-800: #1f5c42;

  /* Czerwień sygnałowa (błąd, akcja niszcząca) jest o pół kroku cieplejsza i jaśniejsza od
     czerwieni marki: „cegła”, a nie „karmazyn”. Bez tego przesunięcia przycisk „Usuń” i przycisk
     „Zarejestruj się” miałyby ten sam kolor, a to dwa zupełnie różne komunikaty. */
  --brick-600: #bd3b26;
  --brick-700: #8f2c16;

  /* Role kolorów – wyłącznie tych używamy w komponentach. */
  --bg: #f6f4ee;
  --bg-muted: #efece3;
  --surface: #fffefb;
  --surface-2: #faf8f2;
  --surface-inset: #f1eee5;
  --ink: #171d27;
  --ink-soft: #3c4453;
  --muted: #5d6472;
  --line: #dcd6c8;
  --line-strong: #c4bcaa;

  --brand: var(--navy-800);
  --brand-ink: #f4f1e8;
  --brand-soft: #e6e9f0;

  --accent: var(--red-600);
  --accent-ink: var(--red-800);
  --accent-soft: #f8e5e3;
  /* Akcent na granatowym tle (pasek nagłówka, stopka, hero): ciemna czerwień byłaby tam plamą
     nie do odróżnienia od tła, więc tekst i znaczniki biorą stopień 300, a grube kreski 500. */
  --accent-on-brand: var(--red-300);
  --accent-rule: var(--red-500);

  --ok: var(--green-700);
  --ok-ink: var(--green-800);
  --ok-soft: #e0efe7;

  --warn: var(--amber-700);
  --warn-ink: var(--amber-800);
  --warn-soft: #f8eed9;

  --danger: var(--brick-600);
  --danger-ink: var(--brick-700);
  --danger-soft: #fbe8e0;

  --info: var(--navy-600);
  --info-ink: var(--navy-700);
  --info-soft: #e4eaf4;

  --neutral-ink: #4a515e;
  --neutral-soft: #e9e6dd;

  --link: #1d4a86;
  --link-hover: #12233f;
  --focus: var(--red-600);

  /* Skala odstępów 4/8/12/16/24/32/48. */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-6: 1.5rem;
  --sp-8: 2rem;
  --sp-12: 3rem;

  --radius-sm: 4px;
  --radius: 8px;
  --radius-lg: 14px;

  --shadow-sm: 0 1px 2px rgba(18, 35, 63, 0.06), 0 1px 3px rgba(18, 35, 63, 0.05);
  --shadow: 0 2px 4px rgba(18, 35, 63, 0.06), 0 8px 24px rgba(18, 35, 63, 0.07);
  --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.6);

  --container: 1100px;
  --reading: 68ch;

  --font-sans: "Inter", system-ui, "Segoe UI", Roboto, sans-serif;
  --font-serif: "Source Serif 4", Georgia, "Times New Roman", serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f1726;
    --bg-muted: #0b1120;
    --surface: #162032;
    --surface-2: #1b2740;
    --surface-inset: #111a2b;
    --ink: #e9edf5;
    --ink-soft: #cbd3e0;
    --muted: #9fabbf;
    --line: #27334c;
    --line-strong: #374561;

    --brand: #17243d;
    --brand-ink: #eef1f7;
    --brand-soft: #1d2942;

    /* W ciemnym wariancie akcent musi być jaśniejszy od tła, a nie ciemniejszy: ta sama czerwień
       z logotypu, rozjaśniona do 7.4:1 na tle strony. Rola „na granacie” jest tu tym samym
       odcieniem – tło strony i pasek nagłówka mają w tym wariancie zbliżoną jasność. */
    --accent: #ef8a82;
    --accent-ink: #f6b3ad;
    --accent-soft: #33100f;
    --accent-on-brand: #ef8a82;
    --accent-rule: #d9483c;

    --ok: #4cae83;
    --ok-ink: #7fd0aa;
    --ok-soft: #10281f;

    --warn: #dda44a;
    --warn-ink: #edc078;
    --warn-soft: #2c2210;

    /* Błąd zostaje cieplejszy od akcentu także tutaj – inaczej alert i marka byłyby tym samym
       odcieniem łososia. */
    --danger: #ec8f6e;
    --danger-ink: #f3b294;
    --danger-soft: #2f1a11;

    --info: #7ea6e0;
    --info-ink: #a6c4ee;
    --info-soft: #142239;

    --neutral-ink: #b3bdcd;
    --neutral-soft: #1c2740;

    --link: #8fb6e8;
    --link-hover: #c3d8f5;
    --focus: #ef8a82;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow: 0 2px 6px rgba(0, 0, 0, 0.45), 0 12px 32px rgba(0, 0, 0, 0.35);
    --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.04);
  }
}

/* --- 3. Reset i podstawy -------------------------------------------------------------------- */

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

/* Atrybut ``hidden`` ma chować **naprawdę**. Przeglądarka daje mu tylko ``display: none``
   z arkusza domyślnego, więc bije go każda nasza reguła z klasą – i tak właśnie było w bloku
   „szkoła”: ``.form p { display: flex }`` wygrywało z ``[hidden]``, pole „Nazwa szkoły” stało
   otwarte mimo niezaznaczonej kratki, uczestnik je wypełniał i dostawał od serwera „wybierz
   szkołę z listy”. To jest połowa zgłoszenia z produkcji (druga połowa siedziała w CDN-ie).
   Reguła jest globalna i z ``!important``, bo ``hidden`` nie jest sugestią. */
[hidden] {
  display: none !important;
}

html {
  scroll-padding-top: 4.5rem;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ink);
  background-color: var(--bg);
  /* Delikatna „faktura papieru”: dwa bardzo miękkie gradienty, żadnych obrazów do pobrania. */
  background-image:
    radial-gradient(1200px 420px at 50% -180px, rgba(18, 35, 63, 0.07), transparent 70%),
    radial-gradient(720px 320px at 100% 0, rgba(161, 15, 15, 0.06), transparent 70%);
  background-repeat: no-repeat;
  -webkit-font-smoothing: antialiased;
}

img,
svg,
canvas {
  max-width: 100%;
}

img {
  height: auto;
}

a {
  color: var(--link);
  text-underline-offset: 0.16em;
  text-decoration-thickness: 1px;
}

a:hover {
  color: var(--link-hover);
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 2px;
}

::selection {
  background: var(--accent-soft);
  color: var(--ink);
}

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: var(--sp-8) 0;
}

/* --- 4. Układ ------------------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  /* Stopień 600 wprost, a nie ``--accent``: w ciemnym wariancie akcent jest jasny (kolor tekstu
     na ciemnym tle) i biała etykieta przestałaby być na nim czytelna. */
  background: var(--red-600);
  color: #fff;
  padding: var(--sp-2) var(--sp-4);
  font-weight: 600;
  border-radius: 0 0 var(--radius) 0;
}

.skip-link:focus {
  left: 0;
}

.topbar {
  background: var(--brand);
  color: var(--brand-ink);
  border-bottom: 3px solid var(--accent-rule);
}

.topbar__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-6);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  text-decoration: none;
  color: var(--brand-ink);
}

/* Logotyp organizatora (czarne kontury + ciemna czerwień) jest zaprojektowany na jasne tło:
   w granatowym pasku dostaje jasną plakietkę o wysokości paska nawigacji. */
.brand__logo {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  background: #f6f4ee; /* stała: logo wymaga jasnego tła także w trybie ciemnym */
  border-radius: var(--radius-sm, 6px);
  line-height: 0;
}

.brand__logo img {
  height: 36px;
  width: auto;
  display: block;
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.brand:hover .brand__logo,
.brand:focus-visible .brand__logo {
  outline: 2px solid var(--accent-on-brand);
  outline-offset: 2px;
}

.brand__mark {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: 0.01em;
  line-height: 1.1;
}

.brand__mark::first-letter {
  color: var(--accent-on-brand);
}

.brand__sub {
  font-size: 0.8125rem;
  color: rgba(244, 241, 232, 0.72);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}


.nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-4);
  margin: 0;
  padding: 0;
}

.nav__link {
  position: relative;
  display: inline-block;
  padding: var(--sp-1) 0;
  color: rgba(244, 241, 232, 0.88);
  text-decoration: none;
  font-size: 0.9375rem;
  font-weight: 500;
  border-bottom: 2px solid transparent;
}

/* Strona główna jako domek (pierwsza pozycja menu). ``inline-flex`` wyrównuje rysunek do linii
   bazowej sąsiednich napisów; nazwa pozycji stoi w ``visually-hidden`` obok rysunku. */
.nav__link--home {
  display: inline-flex;
  align-items: center;
}

.nav__home-icon {
  display: block;
}

.nav__link:hover {
  color: #fff;
  border-bottom-color: rgba(239, 138, 130, 0.6);
}

.nav__link[aria-current="page"] {
  color: #fff;
  border-bottom-color: var(--accent-on-brand);
  font-weight: 600;
}

/* Rozwijana pozycja menu („Dokumenty”, templates/base.html). Cały mechanizm jest w ``<details>``
   i w tym arkuszu – żadnego skryptu, więc pozycja działa też przy zablokowanym JavaScripcie.

   Otwarcie ma dwie drogi i obie muszą zadziałać w każdej przeglądarce:

   - kliknięcie/klawisz przełącza atrybut ``open`` – to robi przeglądarka,
   - najechanie i fokus wewnątrz otwierają listę wizualnie, bez zmiany atrybutu. Silniki chowają
     zawartość zamkniętego ``<details>`` na dwa różne sposoby: starsze regułą ``display: none``
     w arkuszu użytkownika (znosi ją reguła na ``.nav-menu__list``), nowsze ``content-visibility``
     na pseudoelemencie ``::details-content`` (znosi ją reguła niżej). Stąd obie – nadmiarowa
     w danej przeglądarce nic nie kosztuje, a brakująca zostawiłaby menu bez rozwinięcia. */
.nav-menu {
  position: relative;
  margin: 0;
}

.nav-menu__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  cursor: pointer;
  list-style: none;
}

/* Domyślny trójkąt ``<summary>`` w dwóch wariantach zapisu; własny znacznik jest niżej. */
.nav-menu__summary::-webkit-details-marker {
  display: none;
}

.nav-menu__summary::marker {
  content: "";
}

.nav-menu__summary::after {
  content: "";
  width: 0.36rem;
  height: 0.36rem;
  margin-bottom: 0.18rem;
  border-right: 2px solid currentcolor;
  border-bottom: 2px solid currentcolor;
  transform: rotate(45deg);
}

.nav-menu[open] > .nav-menu__summary::after {
  margin-top: 0.18rem;
  margin-bottom: 0;
  transform: rotate(-135deg);
}

.nav-menu__summary:hover {
  color: #fff;
  border-bottom-color: rgba(239, 138, 130, 0.6);
}

.nav-menu__summary--active {
  color: #fff;
  border-bottom-color: var(--accent-on-brand);
  font-weight: 600;
}

.nav-menu__list {
  list-style: none;
  margin: var(--sp-2) 0 0;
  padding: 0;
}

.nav-menu__list > li {
  margin: 0;
}

.nav-menu__link {
  display: block;
  padding: var(--sp-2) var(--sp-3);
  color: rgba(244, 241, 232, 0.88);
  text-decoration: none;
  font-size: 0.9375rem;
  border-left: 2px solid transparent;
}

.nav-menu__link:hover {
  color: #fff;
  background: rgba(244, 241, 232, 0.08);
}

.nav-menu__link[aria-current="page"] {
  color: #fff;
  border-left-color: var(--accent-on-brand);
  font-weight: 600;
}

/* Odnośnik do samej sekcji stoi nad dokumentami i jest od nich oddzielony kreską – to inny
   rodzaj celu (lista) niż pozycje pod nim (pojedynczy dokument). */
.nav-menu__link--all {
  font-weight: 600;
}

.nav-menu__list > li:first-child {
  border-bottom: 1px solid rgba(244, 241, 232, 0.16);
  margin-bottom: var(--sp-1);
  padding-bottom: var(--sp-1);
}

@media (min-width: 900px) {
  .nav-menu__list {
    position: absolute;
    top: calc(100% + var(--sp-3));
    left: calc(var(--sp-3) * -1);
    z-index: 40;
    min-width: 17rem;
    margin: 0;
    padding: var(--sp-2) 0;
    background: var(--brand);
    border: 1px solid rgba(244, 241, 232, 0.18);
    border-top: 3px solid var(--accent-rule);
    border-radius: 0 0 var(--radius) var(--radius);
    box-shadow: var(--shadow);
  }


  /* Niewidoczny "mostek" wypełnia odstęp między nagłówkiem a listą: kursor przechodzący przez
     tę przerwę nadal jest nad potomkiem .nav-menu, więc :hover nie gaśnie i lista nie znika. */
  .nav-menu__list::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: calc(var(--sp-3) + 4px);
  }

  .nav-menu:not([open]) > .nav-menu__list {
    display: none;
  }

  .nav-menu:hover > .nav-menu__list,
  .nav-menu:focus-within > .nav-menu__list {
    display: block;
  }

  .nav-menu:hover::details-content,
  .nav-menu:focus-within::details-content {
    content-visibility: visible;
  }
}

/* Pasek konta (templates/base.html). Wcześniej te same odnośniki stały jako ``.nav--roles``
   dosztukowane z prawej strony nawigacji serwisu – przy dłuższej etykiecie („Rejestracja rusza
   8 września 2026”) wychodziły poza układ i zawijały się w przypadkowym miejscu. Teraz mają własny
   wiersz **nad** paskiem głównym: ciemniejszy i niższy, więc hierarchia jest widoczna bez czytania,
   a logotyp z nawigacją serwisu zostaje w swoim wierszu.

   Zawijanie, nie przewijanie: wiersz jest zwykłym ``flex-wrap``, więc na wąskim ekranie przyciski
   schodzą niżej i nic nie wystaje poza szerokość okna. */
/* Przyklejony do góry okna: logotyp i wejście do konta zostają widoczne przy przewijaniu długich
   dokumentów. ``sticky`` siedzi na **zewnętrznym** elemencie, będącym bezpośrednim dzieckiem
   ``<body>``: element przyklejony trzyma się tylko w granicach swojego rodzica, więc na wewnętrznym
   ``.topbar__account`` odklejałby się, gdy tylko rodzic wyjedzie poza ekran. Tło musi być kryjące,
   bo pod paskiem przesuwa się treść. */
.topbar--account {
  position: sticky;
  top: 0;
  z-index: 40;
  border-bottom: 0;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.topbar__account {
  background: var(--brand);
  border-bottom: 1px solid rgba(244, 241, 232, 0.14);
}

.topbar__account-inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--sp-2) var(--sp-4);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
}

/* Najważniejsze pozycje menu w przyklejonym pasku: między logotypem a przyciskami konta,
   nieco mocniejsze niż odnośniki dolnego menu, bo stoją na tle bez własnego wiersza. */
.nav--primary {
  /* Ukryta, dopóki pasek się nie przyklei (klasa ``is-stuck`` ze static/js/sticky-bar.js): na
     górze strony te same pozycje stoją w menu serwisu tuż pod paskiem. */
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-4);
  margin-left: var(--sp-2);
}

.topbar--account.is-stuck .nav--primary {
  display: flex;
  animation: nav-primary-in 180ms ease-out;
}

@keyframes nav-primary-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .topbar--account.is-stuck .nav--primary {
    animation: none;
  }
}

.nav__link--primary {
  font-weight: 600;
}

.account-bar {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-2) var(--sp-3);
}

/* Przycisk nigdy nie jest szerszy od paska: długa etykieta zapowiedzi rejestracji łamie się
   w środku przycisku, zamiast rozepchać stronę. */
.account-bar__btn {
  max-width: 100%;
}

.account-bar__link {
  padding: var(--sp-1) 0;
  color: rgba(244, 241, 232, 0.9);
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 500;
  border-bottom: 2px solid transparent;
}

.account-bar__link:hover {
  color: #fff;
  border-bottom-color: rgba(239, 138, 130, 0.6);
}

.account-bar__link[aria-current="page"] {
  color: #fff;
  border-bottom-color: var(--accent-on-brand);
  font-weight: 600;
}

.account-bar__sep {
  width: 1px;
  height: 1.25rem;
  background: rgba(244, 241, 232, 0.24);
}

.account-bar__who {
  font-size: 0.8125rem;
  color: rgba(244, 241, 232, 0.7);
  max-width: 24ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-bar__logout {
  display: inline-flex;
  margin: 0;
}

/* Na granacie jasny ``.btn--secondary`` byłby plamą w kolorze pola formularza – w pasku konta
   (tak samo jak w hero) zostaje z niego sam obrys. */
.account-bar .btn--secondary {
  background: transparent;
  border-color: rgba(244, 241, 232, 0.55);
  color: #f4f1e8;
}

.account-bar .btn--secondary:hover {
  background: rgba(244, 241, 232, 0.12);
  border-color: #f4f1e8;
  color: #fff;
}

/* Obrys fokusu w granatowych pasach: ciemna czerwień akcentu jest tam niewidoczna. */
.topbar :focus-visible,
.footer :focus-visible {
  outline-color: var(--accent-on-brand);
}

/* Na wąskim ekranie wyrównanie do prawej zostawiałoby poszarpaną kolumnę przycisków – tam pasek
   zbiera się do środka. */
@media (max-width: 560px) {
  /* Na telefonie pasek z logotypem i przyciskami ma trzy wiersze (~140 px) – przyklejony zabierałby
     jedną szóstą ekranu na każdej stronie dokumentu, więc tam przewija się z treścią. */
  .topbar--account {
    position: static;
    box-shadow: none;
  }

  .topbar__account-inner {
    justify-content: center;
  }

  .account-bar {
    margin-left: 0;
    width: 100%;
    justify-content: center;
  }

  .nav--primary {
    margin-left: 0;
    width: 100%;
    justify-content: center;
  }
}

/* Nawigacja serwisu trzyma się prawej krawędzi wiersza z logotypem (od szerokości, na której
   mieści się w jednej linii); niżej zawija się pod logotyp. */
@media (min-width: 900px) {
  .nav--cms {
    margin-left: 0;
  }
}

/* --- Pasek logotypów sponsorów (menu serwisu, po prawej stronie „FAQ”) ------------------------ */
/* Uwaga organizatora z 21.09.2026, „jak na Olimpiadzie Biologicznej” (olimpbiol.pl): taśma
   logotypów, kilka naraz, przesuwa się o jeden co ``data-interval`` sekund
   (static/js/sponsor-slider.js buduje samą animację; ten arkusz opisuje wyłącznie geometrię).

   Pudełko ma **stałą wysokość**, więc wiersz menu nigdy nie zmienia rozmiaru, kiedy taśma jedzie.
   Szerokość jest elastyczna (``flex: 1 1 auto``) między jednym a czterema slotami – tyle, ile
   akurat zmieści się w wolnym miejscu wiersza menu; ``overflow: hidden`` przycina resztę, więc
   bez skryptu (i z ``prefers-reduced-motion``) widać po prostu tyle logotypów, ile się mieści.
   Znaki partnerów są projektowane na jasne tło, więc każdy dostaje jasną plakietkę – ta sama
   sztuczka, co przy logotypie organizatora w stopce (``.footer__logo``/``.brand__logo``). */
/* Dok taśmy: element wiersza menu, który bierze **wolne miejsce na prawo od ostatniej pozycji**
   (``flex: 1 1 0`` – podstawa zero, więc o zawinięciu do nowego wiersza decyduje samo minimum,
   czyli jedno pole, a nie szerokość całej taśmy; z ``flex-basis: auto`` taśma o dziewięciu
   logotypach spadała pod menu). Taśma stoi w nim przy prawej krawędzi. */
.sponsor-slider-dock {
  --sponsor-slot-w: 132px;
  --sponsor-slot-h: 44px;
  flex: 1 1 0;
  min-width: var(--sponsor-slot-w);
  display: flex;
  justify-content: flex-end;
  container-type: inline-size;
}

/* Wiersz menu musi sięgać prawej krawędzi kolumny, żeby dok miał co brać: sama ``.nav`` jest
   tak szeroka, jak jej pozycje, więc bez rozciągnięcia „wolne miejsce” wynosiłoby zero. Tylko
   wtedy, gdy taśma jest na stronie i na ekranach, na których ją widać. */
@media (min-width: 900px) {
  .nav--cms:has(.sponsor-slider-dock) {
    flex: 1 1 auto;
  }
}

/* Szerokość taśmy to **pełne pola**: tyle, ile mieści dok, najwyżej cztery. Bez zaokrągłenia
   w dół przy krawędzi stałaby połówka logotypu. ``round()`` znają przeglądarki z lat 2023+;
   starsza zostaje przy pierwszej deklaracji, czyli przy jednym polu – taśma nadal jedzie. */
.sponsor-slider {
  position: relative;
  overflow: hidden;
  height: var(--sponsor-slot-h);
  flex: 0 0 auto;
  width: var(--sponsor-slot-w);
  width: clamp(
    var(--sponsor-slot-w),
    round(down, 100cqw, var(--sponsor-slot-w)),
    calc(var(--sponsor-slot-w) * 4)
  );
  /* Przeciąganie myszą/palcem przesuwa taśmę (sponsor-slider.js), nie zaznacza tekst i nie
     zaczyna natywnego przeciągania obrazka/odnośnika – patrz też ``draggable="false"`` w
     szablonie. ``pan-y`` zostawia pionowe przewijanie strony działające na dotyku. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

.sponsor-slider__track {
  display: flex;
  align-items: center;
  height: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  width: max-content;
  /* Przejście dokłada wyłącznie skrypt (klasa domyślna jedzie bez animacji – bez JS-u taśma
     stoi w miejscu, więc nie ma czego animować). */
  transition: transform 0.9s ease;
  will-change: transform;
}

.sponsor-slider__item {
  flex: 0 0 var(--sponsor-slot-w);
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

.sponsor-slider__link {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  text-decoration: none;
  -webkit-user-drag: none;
}

/* Pierścień fokusu rysuje się na ``<a>``, czyli **pod** plakietką w porządku malowania, ale
   ``outline`` stoi poza ramką elementu (dodatni ``outline-offset``) i wychodzi poza plakietkę
   niezależnie od tła, które ta rysuje na wierzchu – patrz też tryb wysokiego kontrastu niżej. */
.sponsor-slider__link:focus-visible {
  outline: 2px solid var(--accent-on-brand);
  outline-offset: 3px;
  border-radius: var(--radius-sm, 6px);
}

/* Plakietka ma **stały, jednakowy rozmiar** niezależnie od logotypu (dawniej była tak szeroka,
   jak jej treść, więc partnerzy z szerszym znakiem dostawali szerszą plakietkę – wyglądało to
   nierówno) – 6 px odstępu z każdej strony od krawędzi pola (``--sponsor-slot-w/h``).
   ``overflow: hidden`` jest siatką bezpieczeństwa: sam obrazek już mieści się w środku dzięki
   regule niżej, ale rendition większa niż plakietka (zdarza się – patrz uwaga organizatora)
   nie ma prawa jej rozepchać. */
.sponsor-slider__frame {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: calc(var(--sponsor-slot-w) - 12px);
  height: calc(var(--sponsor-slot-h) - 8px);
  padding: 4px 10px;
  overflow: hidden;
  background: #f6f4ee; /* stała: znaki partnerów wymagają jasnego tła także w trybie ciemnym */
  border-radius: var(--radius-sm, 6px);
}

/* Obrazek skaluje się **w dół** do plakietki z zachowaniem proporcji (``object-fit: contain``
   działa tu, bo plakietka ma teraz definite rozmiar – wcześniej ``max-height: 100%`` na
   fleksowym dziecku bez wysokości rodzica nic nie robił, stąd logotypy 44–112 px wysokości
   wychodziły poza pigułkę), ale nigdy w górę: ``width``/``height`` z atrybutów HTML dają obrazowi
   jego naturalny rozmiar jako bazę, a ``width``/``height: auto`` w CSS tylko go zmniejszają,
   nigdy nie powiększają ponad tę bazę. ``min-width/height: 0`` zdejmuje fleksowe minimum
   zawartości, które inaczej blokowałoby kurczenie się poniżej rozmiaru samego obrazka. */
.sponsor-slider__logo {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  min-width: 0;
  min-height: 0;
  object-fit: contain;
  flex: 0 1 auto;
  -webkit-user-drag: none;
}

/* Poniżej 900 px nawigacja serwisu zawija się pod logotyp (patrz reguła ``.nav--cms`` wyżej) –
   pasek sponsorów zabrałby wtedy miejsce, którego brakuje na same pozycje menu. Strona główna
   ma swój pełny pas partnerów (``.partner-strip``) niezależnie od tego pudełka. */
@media (max-width: 899px) {
  .sponsor-slider-dock,
  .sponsor-slider {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sponsor-slider__track {
    transition: none;
  }
}

/* --- Linia czasu edycji (pasek „terminalowy” w nagłówku) ------------------------------------- */
/*
   Trzy reguły, na których stoi cały ten blok:

   1. **W spoczynku pasek jest jedną linią.** Nagłówek serwisu ma zachować swoją wysokość, więc
      poza kursorem widać wyłącznie wykres: żadnych wierszy „kodu”, żadnej legendy, żadnego
      dymka. Wysokość w spoczynku to jeden wiersz monospace (``line-height: 1.2`` przy kroju
      najwyżej 14 px), czyli ok. 17 px – tyle, ile dokłada do nagłówka cienka kreska.
   2. **Rozwija się w dół.** Panel z wierszami „kodu” i legendą stoi **pod** wykresem i jest
      zwinięty do zera wysokości (``grid-template-rows: 0fr`` → ``1fr``; ``overflow: hidden``
      na wewnętrznym pudełku). Dzięki temu rozwinięcie nie rusza samego wykresu – linia zostaje
      tam, gdzie była – i przesuwa wyłącznie to, co jest pod nagłówkiem.
   3. **Szerokość nie zmienia się nigdy.** Wykres ma 102 znaki i skaluje się jednostką ``cqw``
      (``.timeline-strip`` jest kontenerem zapytań), a legenda jest zwykłym zawijanym rzędem
      chipów w tej samej kolumnie. Nic tu nie jest pozycjonowane procentowo i nic nie wystaje.

   Trzy kolory stanu stoją na granacie nagłówka, więc żaden nie jest tym samym odcieniem, co
   w treści strony: niebieski i zielony są rozjaśnione do kontrastu ≥ 4.5:1 z ``--brand``,
   a „teraz” bierze gotową rolę ``--accent-on-brand`` (czerwień 300) – jedyną czerwień
   w serwisie czytelną na granacie.

   Poniżej 640 px wykres znika i zostaje z niego zwinięty kalendarz (``.tl-list``): 102 znaki
   miałyby tam wysokość dwóch pikseli. Obie postacie są rozłączne (``display: none``), więc
   czytnik ekranu nigdy nie czyta ich obu. */

/* Dok linii czasu – od 21.09.2026 pasek stoi na dole strony, tuż nad stopką (templates/base.html),
   a nie w nagłówku. Tło nagłówka jedzie razem z nim, bo kolory wykresu (``--tl-*``) są dobrane pod
   granat; ``flex-shrink: 0`` z tego samego powodu, co w stopce: ``body`` jest kolumną flex i to
   treść ma się kurczyć, nie pasek. Bez bieżącej edycji znacznik nie rysuje niczego – puste
   opakowanie znika, żeby nad stopką nie został granatowy pasek bez treści. */
.timeline-dock {
  flex-shrink: 0;
  background: var(--brand);
  color: var(--brand-ink);
  padding: var(--sp-2) 0;
}

.timeline-dock:not(:has(.timeline-strip)) {
  display: none;
}

.timeline-strip {
  position: relative;
  container-type: inline-size;
  max-width: var(--container);
  margin: 0 auto;
  /* Zero odstępu w pionie: w spoczynku pasek ma być wysoki na jedną linię i ani piksela więcej.
     Oddech rozwiniętego panelu jest **w środku** panelu, czyli zwija się razem z nim. */
  padding: 0 var(--sp-4);

  --tl-past: #8fb4e6;
  --tl-current: var(--accent-on-brand);
  --tl-upcoming: #7fd0aa;
  --tl-idle: rgba(244, 241, 232, 0.58);
  --tl-ink: rgba(244, 241, 232, 0.88);
}

.tl {
  position: relative;
  z-index: 1;
  margin: 0;
  max-width: 100%;
  font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
  /* Dzielnik wzięty z pomiaru, nie z metryki kroju: 102 znaki (klamry plus sto komórek) zajmują
     w przeglądarce ok. 0,69 em każdy, a przy małych rozmiarach krok znaku jeszcze rośnie przez
     zaokrąglanie. Dzielnik 76 zostawia ok. 8% zapasu. Górne 14 px wynika z reguły 1: przy
     ``line-height: 1.2`` daje to wiersz wysoki na 17 px. Deklaracja ``vw`` nad nią jest awaryjna
     – przeglądarka bez zapytań kontenerowych po prostu jej nie nadpisze. */
  font-size: clamp(0.4rem, 1vw, 0.75rem);
  font-size: clamp(6px, calc(100cqw / 76), 14px);
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--tl-idle);
  white-space: pre;
  /* Twarda granica na wypadek kroju o szerszym kroku, niż zakłada dzielnik wyżej: wykres zostaje
     przycięty, zamiast rozepchnąć stronę. */
  overflow-x: clip;
  overflow-y: visible;
}

.tl--code {
  color: rgba(244, 241, 232, 0.6);
  font-weight: 400;
}

/* Opakowanie samego wykresu. Istnieje po to, żeby animacja wejścia (``clip-path``) przycinała
   **wykres**, a nie cały blok. */
.tl__bar {
  display: inline-block;
}

.tl__bracket {
  color: rgba(244, 241, 232, 0.5);
}

.tl__c--past {
  color: var(--tl-past);
}

.tl__c--current {
  color: var(--tl-current);
}

.tl__c--upcoming {
  color: var(--tl-upcoming);
}

.tl__head {
  color: #fff;
  animation: tl-caret 1.1s steps(1, end) infinite;
}

/* Znacznik wydarzenia: kreska na linii, która jest odnośnikiem albo przyciskiem. Reset przycisku
   jest tu konieczny – bez niego natywne tło, obrys i krój rozerwałyby siatkę znaków. */
.tl__mark {
  position: relative;
  appearance: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  color: var(--tl-ink);
  text-decoration: none;
  cursor: pointer;
}

.tl__mark:hover,
.tl__mark:focus-visible,
.tl__mark.is-active {
  color: #fff;
}

/* --- panel rozwijany w dół ------------------------------------------------------------------- */
/*
   ``grid-template-rows: 0fr → 1fr`` to jedyny sposób na **płynną** zmianę wysokości do rozmiaru
   treści bez mierzenia jej skryptem: ``height: auto`` się nie animuje, a ``max-height`` z magiczną
   liczbą albo ucina legendę, albo zostawia pustkę. Przeglądarka bez tej animacji (starsza niż
   Chrome 107 / Firefox 120) po prostu przeskoczy – panel nadal działa. */
.tl-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 250ms ease;
}

.tl-panel__inner {
  overflow: hidden;
  min-height: 0;
}

.timeline-strip:hover .tl-panel,
.timeline-strip:focus-within .tl-panel,
.timeline-strip.is-open .tl-panel {
  grid-template-rows: 1fr;
}

/* Oddech rozwiniętego panelu jest w środku pudełka z ``overflow: hidden``, więc w spoczynku
   zwija się razem z treścią i nie dokłada nagłówkowi ani piksela. */
.tl-panel__inner > :first-child {
  margin-top: var(--sp-1);
}

.tl-panel__inner > :last-child {
  margin-bottom: var(--sp-2);
}

.tl-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.tl-legend__chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35em;
  padding: 1px var(--sp-2);
  border: 1px solid rgba(244, 241, 232, 0.16);
  border-radius: 999px;
  background: rgba(244, 241, 232, 0.05);
  font-size: 0.75rem;
  line-height: 1.6;
  color: rgba(244, 241, 232, 0.8);
  /* Chip podświetla się, kiedy kursor stoi na jego kresce (i odwrotnie) – parowanie robi
     static/js/timeline-strip.js po numerze wydarzenia w ``data-tl-chip``. */
  transition: background-color 120ms ease-out, border-color 120ms ease-out;
}

.tl-legend__chip.is-active {
  border-color: rgba(244, 241, 232, 0.55);
  background: rgba(244, 241, 232, 0.16);
  color: #fff;
}

.tl-legend__title {
  font-weight: 600;
}

.tl-legend__chip--past .tl-legend__title {
  color: var(--tl-past);
}

.tl-legend__chip--current .tl-legend__title {
  color: var(--tl-current);
}

.tl-legend__chip--upcoming .tl-legend__title {
  color: var(--tl-upcoming);
}

.tl-legend__dates {
  color: rgba(244, 241, 232, 0.66);
  font-variant-numeric: tabular-nums;
}

.tl-legend__note {
  color: rgba(244, 241, 232, 0.55);
  font-style: italic;
}

/* Warstwa „pomiaru”: paczka falowa rysowana przez static/js/timeline-strip.js wyłącznie pod
   kursorem. Leży pod paskiem i nie łapie zdarzeń myszy – gdyby je łapała, nie dałoby się
   najechać na kreskę, która jest pod nią. Bez skryptu płótno ma rozmiar 0×0. */
.tl__quantum {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 240ms ease-out;
}

.timeline-strip.is-measuring .tl__quantum {
  opacity: 1;
}

/* Na dotyku nie ma stanu „najechania”: warstwa nigdy by się nie pokazała, a płótno kosztowałoby
   pamięć na każdej stronie. Panel rozwija tam dotknięcie kreski (klasa ``is-open``). */
@media (hover: none) {
  .tl__quantum {
    display: none;
  }
}

/* Zwinięty kalendarz: postać paska na telefonie. Na szerokim ekranie nie ma go wcale (``display:
   none`` wyjmuje go także z drzewa dostępności), bo tam informację niosą kreski i legenda. */
.tl-list {
  display: none;
}

.tl-list__summary {
  padding: var(--sp-1) 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgba(244, 241, 232, 0.82);
  cursor: pointer;
}

.tl-list__items {
  margin: 0 0 var(--sp-1);
  padding: 0;
  list-style: none;
}

.tl-list__item {
  font-size: 0.8125rem;
  line-height: 1.6;
  color: rgba(244, 241, 232, 0.82);
}

.tl-list__mark {
  color: rgba(244, 241, 232, 0.5);
  font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
}

.tl-list__item--past .tl-list__mark,
.tl-list__mark--past {
  color: var(--tl-past);
}

.tl-list__item--current .tl-list__mark,
.tl-list__mark--current {
  color: var(--tl-current);
}

.tl-list__item--upcoming .tl-list__mark,
.tl-list__mark--upcoming {
  color: var(--tl-upcoming);
}

.tl-list__title {
  color: #f4f1e8;
  font-weight: 600;
}

.tl-list__item--current .tl-list__title {
  color: var(--tl-current);
}

.tl-list__dates {
  color: rgba(244, 241, 232, 0.66);
  font-variant-numeric: tabular-nums;
}

.tl-list__note {
  color: rgba(244, 241, 232, 0.55);
  font-style: italic;
}

@media (max-width: 639px) {
  .tl,
  .tl-panel {
    display: none;
  }

  .tl-list {
    display: block;
  }
}

/* Animacje. Klasę ``is-animated`` nadaje skrypt po wczytaniu strony, więc bez JavaScriptu pasek
   jest po prostu narysowany – nic nie „dojeżdża”. Kursor terminala miga z samego arkusza, bo to
   jedna reguła i nie ma czego degradować. */
@keyframes tl-caret {
  0%,
  49% {
    opacity: 1;
  }

  50%,
  100% {
    opacity: 0.2;
  }
}

@keyframes tl-draw {
  from {
    clip-path: inset(0 100% 0 0);
  }

  to {
    clip-path: inset(0 0 0 0);
  }
}

.timeline-strip.is-animated .tl__bar {
  animation: tl-draw 1.2s steps(34, end);
}

@media (prefers-reduced-motion: reduce) {
  .tl__head,
  .timeline-strip.is-animated .tl__bar {
    animation: none;
  }

  /* Rozwinięcie panelu zostaje – to treść, a nie ozdoba – ale dzieje się natychmiast. */
  .tl-panel,
  .tl-legend__chip {
    transition: none;
  }

  .tl__quantum {
    display: none;
  }
}

.page {
  flex: 1 0 auto;
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--sp-8) var(--sp-4) var(--sp-12);
}

.page > h1:first-child,
.page > .page-head:first-child {
  margin-top: 0;
}

.page-head {
  margin-bottom: var(--sp-6);
}

.page-head h1 {
  margin-bottom: var(--sp-2);
}

.footer {
  flex-shrink: 0;
  background: var(--navy-900);
  color: rgba(244, 241, 232, 0.72);
  font-size: 0.875rem;
  border-top: 3px solid var(--accent-rule);
}

.footer__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--sp-8) var(--sp-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
  justify-content: space-between;
}

.footer p {
  margin: 0 0 var(--sp-2);
  max-width: 60ch;
}

.footer strong {
  color: rgba(244, 241, 232, 0.92);
}

.footer a {
  color: var(--accent-on-brand);
}

.footer__meta {
  font-size: 0.8125rem;
  color: rgba(244, 241, 232, 0.5);
  font-variant-numeric: tabular-nums;
}

/* Odnośniki do dokumentów formalnych (RODO, cookie, spis dokumentów) w jednym wierszu z kropkami
   rozdzielającymi – bez listy, bo to nie jest nawigacja serwisu, tylko stopka prawna. */
.footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  font-size: 0.8125rem;
}

.footer__links a + a::before,
.footer__links a + .footer__link-button::before {
  content: "·";
  margin-right: var(--sp-3);
  color: rgba(244, 241, 232, 0.4);
  /* Separator jest atomowym pudełkiem liniowym, bo podkreślenie **nie wchodzi** do wnętrza
     takiego pudełka (CSS Text Decoration: dekoracja nie propaguje się do atomic inline).
     Bez tego kropka przed „Ustawieniami cookies” i jej 12-pikselowy margines wpadały w podkreślenie
     przycisku (``.footer__link-button``) i rysowały się jako kreska przed tekstem – organizator
     zgłosił to 15.09 jako „zabłąkane _”. Sąsiednie odnośniki podkreślenia nie mają, więc u nich
     artefakt nie występował i regułę widać było wyłącznie przy tym jednym elemencie. */
  display: inline-block;
  text-decoration: none;
}

/* „Ustawienia cookies” – element sterujący, a nie adres, więc w HTML jest to ``<button>``
   (patrz templates/base.html). Wygląda jak sąsiedzi, bo dla czytelnika stoi w tym samym rzędzie
   stopki; ``font`` dziedziczymy jawnie, bo przycisk ma własny krój z arkusza przeglądarki. */
.footer__link-button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--accent-on-brand);
  text-decoration: underline;
  cursor: pointer;
}

/* Znak fundacji obok nazwy organizatora. Plakietka jest tą samą, co pod logotypem w nagłówku:
   logotyp jest rysowany na biel, a stopka jest granatowa w obu wariantach kolorystycznych. */
.footer__organizer {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.footer__logo {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  background: #f6f4ee; /* stała: logotyp wymaga jasnego tła także w trybie ciemnym */
  border-radius: var(--radius-sm, 6px);
  line-height: 0;
}

.footer__logo img {
  height: 40px;
  width: auto;
  max-width: 160px;
  object-fit: contain;
  display: block;
}

/* Odnośniki do profili organizatora – ten sam wiersz w stopce i na stronie „Kontakt”.
   Lista, a nie ciąg odnośników jak w ``.footer__links``: te cztery pozycje nie mają tekstu, więc
   czytnik ekranu nie ma po czym poznać, ile ich jest – ``<ul>`` mówi to sam („lista, 4 elementy”).
   Znak ma 20 px, ale całym odnośnikiem jest kwadrat z ``padding`` – 20 px to za mało na palec
   (WCAG 2.5.8 chce 24 px), a przy samej ikonie nie ma jak tego pola powiększyć tekstem.
   Koloru nie ustawiamy: w stopce dziedziczy z ``.footer a``, poza nią z domyślnego odnośnika,
   dzięki czemu ``fill="currentColor"`` w SVG trzyma się obu wariantów kolorystycznych. */
.social-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  list-style: none;
  margin: var(--sp-3) 0 var(--sp-2);
  padding: 0;
}

.social-links a {
  display: inline-flex;
  padding: 6px;
  border-radius: var(--radius-sm);
  line-height: 0;
}

.social-links svg {
  display: block;
}

/* Stopka nie zmienia odnośnikom koloru na :hover (``.footer a`` wygrywa z ``a:hover``), a przy
   ikonie bez tekstu odpada też podkreślenie – bez własnego stanu najechanie nie dałoby żadnego
   sygnału. Plakietka i rozjaśnienie do bieli ``.footer strong``; obrys :focus-visible zostaje ten
   z ``.footer :focus-visible``. */
.footer__social a:hover,
.footer__social a:focus-visible {
  color: rgba(244, 241, 232, 0.92);
  background: rgba(244, 241, 232, 0.1);
}

/* --- 5. Typografia -------------------------------------------------------------------------- */

h1,
h2,
h3,
h4 {
  font-family: var(--font-serif);
  font-weight: 700;
  line-height: 1.2;
  color: var(--ink);
  letter-spacing: -0.005em;
  text-wrap: balance;
}

h1 {
  font-size: clamp(1.75rem, 1.3rem + 1.6vw, 2.35rem);
  margin: 0 0 var(--sp-4);
}

h2 {
  font-size: 1.4rem;
  margin: var(--sp-8) 0 var(--sp-3);
}

h3 {
  font-size: 1.125rem;
  font-weight: 600;
  margin: var(--sp-6) 0 var(--sp-2);
}

h4 {
  font-size: 1rem;
  font-weight: 600;
  margin: var(--sp-4) 0 var(--sp-2);
}

p {
  margin: 0 0 var(--sp-4);
}

.lead {
  font-size: 1.0625rem;
  color: var(--ink-soft);
  max-width: var(--reading);
}

.hint {
  color: var(--muted);
  font-size: 0.875rem;
}

.closed {
  color: var(--warn-ink);
  font-weight: 600;
}

.quote {
  border-left: 3px solid var(--accent);
  background: var(--surface-2);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--sp-3) var(--sp-4);
  margin: 0 0 var(--sp-4);
  white-space: pre-wrap;
  color: var(--ink-soft);
}

.section-heading {
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--sp-2);
}

.eyebrow {
  display: block;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: var(--sp-2);
}

.section {
  margin-bottom: var(--sp-12);
}

.section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  border-bottom: 1px solid var(--line);
  margin: var(--sp-8) 0 var(--sp-4);
}

.section__head h2 {
  margin: 0 0 var(--sp-2);
}

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

.prose h2 {
  margin-top: var(--sp-8);
}

.prose ul,
.prose ol {
  padding-left: 1.25rem;
}

.prose li {
  margin-bottom: var(--sp-2);
}

.prose blockquote {
  margin: var(--sp-4) 0;
  padding-left: var(--sp-4);
  border-left: 3px solid var(--accent);
  color: var(--ink-soft);
  font-family: var(--font-serif);
  font-size: 1.0625rem;
}

.cms-body {
  max-width: var(--reading);
}

.cms-figure {
  margin: var(--sp-6) 0;
}

.cms-figure img {
  border-radius: var(--radius);
  border: 1px solid var(--line);
  display: block;
}

.cms-figure figcaption {
  margin-top: var(--sp-2);
  color: var(--muted);
  font-size: 0.875rem;
}

.cms-doc {
  margin: var(--sp-4) 0;
}

/* --- 6. Komponenty -------------------------------------------------------------------------- */

/* 6.1 Karty */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-6);
  margin: 0 0 var(--sp-4);
}

.card > :first-child {
  margin-top: 0;
}

.card > :last-child {
  margin-bottom: 0;
}

.card--accent {
  border-left: 4px solid var(--accent);
}

.card--quiet {
  background: var(--surface-2);
  box-shadow: none;
}

.card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-3);
}

.card__head h2,
.card__head h3 {
  margin: 0;
}

.card__title {
  font-family: var(--font-serif);
  font-weight: 700;
}

.card__meta {
  color: var(--muted);
  font-size: 0.875rem;
  margin: 0;
}

/* Wyższa specyficzność niż reguły treści karty (np. ``.news-card p``), żeby stopka karty
   nie straciła odstępu nad kreską. */
.card .card__foot {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}

.cards {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  margin: 0 0 var(--sp-6);
  padding: 0;
  list-style: none;
}

.cards--wide {
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
}

.cards > li {
  margin: 0;
}

.cards .card {
  height: 100%;
  margin: 0;
}

/* 6.2 Przyciski */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 0.5rem 1rem;
  min-height: 2.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

a.btn {
  text-decoration: none;
}

.btn--primary {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.btn--primary:hover {
  background: var(--navy-700);
  border-color: var(--navy-700);
  color: #fff;
}

.btn--accent {
  background: var(--accent);
  border-color: var(--red-700);
  color: #fff;
}

.btn--accent:hover {
  background: var(--red-700);
  border-color: var(--red-800);
  color: #fff;
}

/* Ten sam przycisk stoi też na granacie (pasek konta, hero). Ciemna czerwień na granacie jest
   plamą bez konturu, więc tam obrys bierze jasny stopień akcentu – kształt przycisku jest
   widoczny, a biały tekst nadal leży na ciemnej czerwieni. */
.topbar .btn--accent,
.hero .btn--accent {
  border-color: var(--accent-on-brand);
}

/* W ciemnym wariancie ``--accent`` jest jasnym łososiem – dobrym na tekst, złym jako tło pod białe
   litery (2:1). Przycisk zostaje więc przy ciemnej czerwieni marki w obu wariantach; na ciemnym tle
   wyodrębnia go jasny obrys, tak samo jak w granatowym pasku. */
@media (prefers-color-scheme: dark) {
  .btn--accent {
    background: var(--red-600);
    border-color: var(--red-300);
    color: #fff;
  }

  .btn--accent:hover {
    background: var(--red-700);
    border-color: var(--red-300);
    color: #fff;
  }
}

.btn--secondary {
  background: var(--surface);
  border-color: var(--line-strong);
  color: var(--ink);
}

.btn--secondary:hover {
  background: var(--surface-2);
  border-color: var(--brand);
  color: var(--ink);
}

.btn--danger {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}

.btn--danger:hover {
  background: var(--brick-700);
  border-color: var(--brick-700);
  color: #fff;
}

.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--link);
  text-decoration: underline;
}

.btn--ghost:hover {
  background: var(--surface-inset);
  color: var(--link-hover);
}

.btn--small {
  min-height: 2rem;
  padding: 0.25rem 0.65rem;
  font-size: 0.8125rem;
}

.btn--block {
  width: 100%;
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Przyciski poza systemem klas (fallback dla formularzy Django). Znacznik linii czasu
   (``.tl__mark``) jest z tego wyjęty razem z ``.link``: to nie jest przycisk formularza, tylko
   jeden znak w siatce monospace – tło, obrys i minimalna wysokość rozerwałyby pasek. */
button:not(.btn):not(.link):not(.tl__mark) {
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  padding: 0.5rem 1rem;
  min-height: 2.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

button.link {
  background: none;
  border: none;
  color: var(--link);
  text-decoration: underline;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
}

.actions--end {
  justify-content: flex-end;
}

/* 6.3 Formularze */
.form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  max-width: 34rem;
}

.form--wide {
  max-width: none;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  max-width: 40rem;
}

.stack p {
  margin: 0;
}

label,
.form p > label,
.stack p > label {
  display: block;
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--ink);
  margin-bottom: var(--sp-1);
}

/* Gwiazdka przy polu obowiązkowym. Klasę ``required`` dokłada Django z ``required_css_class``
   (apps/web/forms.py), a znak rysuje arkusz – w HTML-u gwiazdki nie ma ani razu i to jest celowe:
   czytnik ekranu i tak czyta wymagalność z atrybutu ``required`` na kontrolce, więc przeczytana
   na głos „gwiazdka” po każdej etykiecie byłaby drugą, gorszą kopią tej samej informacji.
   Zgłoszenie organizatora: z formularza nie dało się odczytać, co trzeba wypełnić. */
label.required::after {
  content: " *";
  color: var(--accent);
}

/* Legenda nad formularzem („Pola oznaczone * są obowiązkowe.”). Ta sama waga i kolor, co
   podpowiedzi pod polami – to objaśnienie znaku, a nie kolejne pole. */
.form__required-note {
  margin: 0;
  color: var(--muted);
  font-size: 0.8125rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
/* ``datetime-local`` obsługuje panel koordynatora (terminy etapu). Bez tego selektora pole
   zostawało w domyślnym stylu przeglądarki – w formularzu obok pól tekstowych wyglądało jak
   element z innego serwisu. */
input[type="datetime-local"],
input[type="url"],
input[type="search"],
/* ``tel`` wypadło z tej listy razem z przejściem pola „Telefon” na klawiaturę numeryczną
   (apps/web/forms.py::phone_field) i to jest cała treść zgłoszenia organizatora: „okno do
   wprowadzenia nr telefonu ma wyraźnie mniejszy rozmiar”. Bez selektora kontrolka zostaje przy
   domyślnej szerokości przeglądarki (``size=20``, czyli dwadzieścia znaków), więc w kolumnie pól
   rozciągniętych na 100 % szerokości jedno stoi o połowę węższe i wygląda na pole innego rodzaju.
   Szerokość ustala **arkusz**, a nie atrybut ``size`` w HTML-u: atrybut liczy się w znakach
   bieżącego kroju i nie ma jak dogonić pozostałych pól. */
input[type="tel"],
select,
textarea {
  width: 100%;
  padding: 0.5rem 0.7rem;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-inset);
}

textarea {
  min-height: 6rem;
  resize: vertical;
}

input:hover,
select:hover,
textarea:hover {
  border-color: var(--muted);
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--brand);
  outline: 3px solid var(--focus);
  outline-offset: 1px;
}

input:disabled,
select:disabled,
textarea:disabled,
fieldset:disabled input,
fieldset:disabled textarea {
  background: var(--surface-inset);
  color: var(--muted);
  cursor: not-allowed;
}

input[type="checkbox"],
input[type="radio"] {
  width: auto;
  accent-color: var(--brand);
  margin-right: var(--sp-2);
}

/* Pole wyboru z ``as_p``: kratka i etykieta w jednej linii (kolumna wyglądałaby jak błąd). */
.form p:has(> input[type="checkbox"]),
.stack p:has(> input[type="checkbox"]) {
  flex-direction: row-reverse;
  justify-content: flex-end;
  align-items: center;
  gap: var(--sp-2);
}

.form p:has(> input[type="checkbox"]) label,
.stack p:has(> input[type="checkbox"]) label {
  margin-bottom: 0;
}

/* Formularze Django renderowane przez ``as_p``: etykieta nad polem, pomoc i błędy pod nim. */
.form p,
.stack p,
.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin: 0;
}

.helptext,
.form .helptext {
  display: block;
  color: var(--muted);
  font-size: 0.8125rem;
}

ul.errorlist {
  list-style: none;
  margin: 0 0 var(--sp-1);
  padding: var(--sp-2) var(--sp-3);
  border-left: 3px solid var(--danger);
  background: var(--danger-soft);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  color: var(--danger-ink);
  font-size: 0.875rem;
  font-weight: 500;
}

ul.errorlist li {
  margin: 0;
}

ul.errorlist.nonfield {
  border-left-width: 4px;
  margin-bottom: var(--sp-3);
}

/* Pola pisane ręcznie (nie przez ``form.as_p``) mają wyglądać dokładnie tak samo, więc podpowiedź
   i błąd dostają te same reguły, co ``.helptext`` i ``ul.errorlist``. Bez tego panel pisany
   w szablonie różniłby się od formularza renderowanego przez Django – w tym samym serwisie. */
.field__hint {
  display: block;
  color: var(--muted);
  font-size: 0.8125rem;
}

.field__error {
  display: block;
  margin: 0 0 var(--sp-1);
  padding: var(--sp-2) var(--sp-3);
  border-left: 3px solid var(--danger);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--danger-soft);
  color: var(--danger-ink);
  font-size: 0.875rem;
  font-weight: 500;
}

/* Pole z błędem: obrys, nie tylko czerwony tekst pod spodem. Selektor łapie ``aria-invalid``
   **przede wszystkim** – to ten atrybut niesie informację do czytnika ekranu, więc jeżeli jest
   postawiony poprawnie, klasa jest już niepotrzebna. ``.field--invalid`` zostaje dla szablonów,
   które stawiają samą klasę; wtedy brakuje im ``aria-invalid`` i to jest błąd do poprawienia. */
:is(input, select, textarea)[aria-invalid="true"],
.field--invalid :is(input, select, textarea) {
  border-color: var(--danger);
  background: var(--danger-soft);
}

:is(input, select, textarea)[aria-invalid="true"]:focus,
.field--invalid :is(input, select, textarea):focus {
  border-color: var(--danger);
}

/* Kratka (albo przełącznik) i etykieta w jednym wierszu. Domyślny ``.field`` jest kolumną, bo
   etykieta stoi nad polem – przy polu wyboru kolumna wyglądałaby jak błąd renderowania. */
.field--inline {
  flex-direction: row;
  align-items: baseline;
  gap: var(--sp-2);
}

.field--inline label {
  font-weight: 400;
}

fieldset {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--sp-4);
  margin: 0;
  min-width: 0;
}

legend {
  font-family: var(--font-serif);
  font-weight: 700;
  padding: 0 var(--sp-2);
  color: var(--ink);
}

form.inline {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
}

form.inline input[type="text"] {
  width: auto;
  min-width: 9rem;
}

/* Wąska karta logowania / rejestracji. */
.auth {
  max-width: 30rem;
  margin: var(--sp-8) auto var(--sp-12);
}

.auth .card {
  padding: var(--sp-8);
}

.auth h1 {
  font-size: 1.6rem;
  text-align: center;
}

.auth__intro {
  text-align: center;
  color: var(--muted);
  margin-bottom: var(--sp-6);
}

.auth__alt {
  margin: var(--sp-6) 0 0;
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
  text-align: center;
  font-size: 0.9375rem;
  color: var(--muted);
}

/* Logowanie przez dostawcę zewnętrznego (sekcja „Lub kontynuuj z” pod formularzem). */
.social-auth {
  margin-top: var(--sp-6);
}

/* Etykieta z kreskami po bokach – bez dodatkowych elementów w DOM. */
.social-auth__label {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-4);
  color: var(--muted);
  font-size: 0.875rem;
}

.social-auth__label::before,
.social-auth__label::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.social-auth__form {
  margin: 0 0 var(--sp-3);
}

.social-auth__form:last-child {
  margin-bottom: 0;
}

.social-auth__icon {
  flex: none;
}

/* Strefa uploadu – stylowana etykieta na natywnym input[type=file]. */
.dropzone {
  display: block;
  border: 2px dashed var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
  padding: var(--sp-6);
  text-align: center;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.dropzone:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.dropzone__label {
  display: block;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.0625rem;
  margin-bottom: var(--sp-1);
  color: var(--ink);
}

.dropzone__hint {
  display: block;
  color: var(--muted);
  font-size: 0.875rem;
  margin-bottom: var(--sp-3);
}

.dropzone input[type="file"] {
  display: block;
  margin: 0 auto var(--sp-3);
  max-width: 100%;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

.dropzone input[type="file"]::file-selector-button {
  font: inherit;
  font-weight: 600;
  margin-right: var(--sp-2);
  padding: 0.35rem 0.8rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

.dropzone input[type="file"]:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* 6.4 Tabele */
.scroll {
  overflow-x: auto;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface);
}

.scroll .table,
.scroll .grid {
  border: 0;
  margin: 0;
}

/* Przyklejony nagłówek tabeli działa dopiero wtedy, gdy jest **do czego** go przykleić: ``th``
   ma ``position: sticky; top: 0``, ale kontener bez ograniczonej wysokości jest dokładnie tak
   wysoki jak tabela i nigdy się nie przewija w pionie. Stąd osobny wariant zamiast wysokości na
   ``.scroll`` – krótka tabela w ramce z ``max-height`` miałaby na dole niepotrzebną dziurę.
   70vh, a nie 100vh: nad tabelą stoi nagłówek widoku, a pod nią bywa pasek zaznaczenia. */
.scroll--tall {
  max-height: 70vh;
  overflow-y: auto;
}

/* Tabela robocza (lista plików, log operacji, wyniki wyszukiwania): ten sam komponent, gęstszy
   wiersz. Przy trzydziestu wierszach to różnica między jednym ekranem a dwoma. */
.table--compact td,
.table--compact th {
  padding: var(--sp-1) var(--sp-2);
}

.table--compact {
  font-size: 0.875rem;
}

/* Kody uczestników, adresy e-mail i nazwy plików nie mają spacji, a bywają długie: bez tego
   jedna komórka rozpycha tabelę szerzej niż ekran, a suwak dostaje cała strona zamiast ramki. */
.table td,
.grid td {
  overflow-wrap: anywhere;
}

.table,
.grid {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin: 0 0 var(--sp-6);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
}

.table caption,
.grid caption {
  caption-side: top;
  text-align: left;
  color: var(--muted);
  font-size: 0.875rem;
  padding: 0 0 var(--sp-2);
}

.table th,
.grid th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-inset);
  color: var(--ink);
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: left;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 2px solid var(--line-strong);
  white-space: nowrap;
}

.table td,
.grid td {
  padding: var(--sp-3);
  border-bottom: 1px solid var(--line);
  vertical-align: top;
  color: var(--ink-soft);
}

.table tbody tr:nth-child(even),
.grid tbody tr:nth-child(even) {
  background: var(--surface-2);
}

.table tbody tr:hover,
.grid tbody tr:hover {
  background: var(--brand-soft);
}

.table tbody tr:last-child td,
.grid tbody tr:last-child td {
  border-bottom: 0;
}

.table--rank td:first-child {
  font-weight: 700;
  color: var(--ink);
  width: 4.5rem;
}

/* Przewijanie poziome dotyczy samej tabeli, nie strony: bez tego kontenera szesnastowierszowy
   harmonogram na telefonie przesuwałby razem z sobą nagłówek i stopkę. ``tabindex`` nadaje
   przeglądarka (obszar przewijalny jest fokusowalny), więc tabelę da się przewinąć klawiaturą. */
.table-scroll {
  overflow-x: auto;
  margin: 0 0 var(--sp-6);
}

.table-scroll > .table {
  margin-bottom: 0;
}

/* Harmonogram redakcyjny: pierwsza kolumna jest tematem (``<th scope="row">``), więc czyta się jak
   nazwa wiersza, a nie jak nagłówek tabeli – stąd zdjęcie wersalików i przyklejenia z ``.table th``.
   Kolumny z datą i godzinami nie łamią się w środku daty. */
.schedule th[scope="row"] {
  position: static;
  background: transparent;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  white-space: normal;
  border-bottom: 1px solid var(--line);
  padding: var(--sp-3);
  min-width: 16ch;
}

.schedule__date,
.schedule__time {
  white-space: nowrap;
}

/* Lista zadań w panelu koordynatora: tytuł jest nazwą wiersza (``<th scope="row">``), a nie
   nagłówkiem tabeli – tak samo, jak temat w harmonogramie warsztatów. Bez tego tytuł zadania
   szedłby wersalikami w kolorze nagłówka i czytałby się jak etykieta kolumny. */
.problem-table th[scope="row"] {
  position: static;
  background: transparent;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  white-space: normal;
  border-bottom: 1px solid var(--line);
  padding: var(--sp-3);
  min-width: 18ch;
}

/* Ostatni wiersz nie ma kreski pod komórkami (``.table tbody tr:last-child td``) – nagłówek
   wiersza musi zniknąć razem z nimi, inaczej zostaje urwana kreska pod jedną kolumną. */
.problem-table tbody tr:last-child th[scope="row"] {
  border-bottom: 0;
}

.table tr.is-qualified {
  background: var(--ok-soft);
}

.table tr.is-qualified:hover {
  background: var(--ok-soft);
}

.table tr.is-qualified td:first-child {
  box-shadow: inset 3px 0 0 var(--ok);
}

.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.total {
  font-weight: 700;
  color: var(--ink);
}

/* 6.5 Badge */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.02em;
  white-space: nowrap;
  background: var(--neutral-soft);
  color: var(--neutral-ink);
  border-color: var(--line-strong);
}

.badge::before {
  content: "";
  width: 0.45em;
  height: 0.45em;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.badge--ok {
  background: var(--ok-soft);
  color: var(--ok-ink);
  border-color: var(--ok);
}

.badge--warn {
  background: var(--warn-soft);
  color: var(--warn-ink);
  border-color: var(--warn);
}

.badge--danger {
  background: var(--danger-soft);
  color: var(--danger-ink);
  border-color: var(--danger);
}

.badge--info {
  background: var(--info-soft);
  color: var(--info-ink);
  border-color: var(--info);
}

.badge--accent {
  background: var(--accent-soft);
  color: var(--accent-ink);
  border-color: var(--accent);
}

.badge--neutral {
  background: var(--neutral-soft);
  color: var(--neutral-ink);
  border-color: var(--line-strong);
}

/* Odznaka z samą liczbą („3 nowe”, „12”). Kropka przed liczbą nic nie znaczy i tylko zwęża pole
   na cyfry, więc znika; ``tabular-nums`` trzyma szerokość przy zmianie 9 → 10. */
.badge--count {
  font-variant-numeric: tabular-nums;
}

.badge--count::before {
  display: none;
}

.tag {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  background: var(--brand-soft);
  color: var(--info-ink);
  border-radius: var(--radius-sm);
  padding: 0.05rem 0.4rem;
}

/* 6.6 Alerty i komunikaty */
.messages {
  list-style: none;
  max-width: var(--container);
  margin: var(--sp-4) auto 0;
  padding: 0 var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.msg,
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line-strong);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-soft);
  font-size: 0.9375rem;
  margin: 0 0 var(--sp-3);
}

.msg::before,
.alert::before {
  content: "i";
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border-radius: 50%;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 0.875rem;
  line-height: 1;
  background: var(--info);
  color: #fff;
}

.msg-success,
.alert--success {
  border-color: var(--ok);
  background: var(--ok-soft);
  color: var(--ok-ink);
}

.msg-success::before,
.alert--success::before {
  content: "\2713";
  background: var(--ok);
}

.msg-warning,
.alert--warning {
  border-color: var(--warn);
  background: var(--warn-soft);
  color: var(--warn-ink);
}

.msg-warning::before,
.alert--warning::before {
  content: "!";
  background: var(--warn);
  color: #1f1400;
}

.msg-error,
.alert--error {
  border-color: var(--danger);
  background: var(--danger-soft);
  color: var(--danger-ink);
}

.msg-error::before,
.alert--error::before {
  content: "!";
  background: var(--danger);
}

.msg-info,
.alert--info {
  border-color: var(--info);
  background: var(--info-soft);
  color: var(--info-ink);
}

/* Wariant komunikatu wewnątrz zdania (fragment HTMX). */
span.msg {
  display: inline-flex;
}

.notice {
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
  padding: var(--sp-4);
  color: var(--ink-soft);
  max-width: var(--reading);
}

/* 6.7 Pusty stan */
.empty {
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
  padding: var(--sp-8) var(--sp-6);
  text-align: center;
  color: var(--muted);
  margin: 0 0 var(--sp-4);
}

.empty__title {
  display: block;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.0625rem;
  color: var(--ink-soft);
  margin-bottom: var(--sp-1);
}

/* Pusty stan ma odpowiedzieć na dwa pytania: „dlaczego tu nic nie ma” i „co mam z tym zrobić”.
   Samo „Brak danych” wygląda jak awaria i generuje zgłoszenie do organizatora. */
.empty__text {
  /* Wyśrodkowany blok o czytelnej długości wiersza: pusty stan stoi na całej szerokości karty,
     a zdanie rozciągnięte na 120 znaków czyta się gorzej niż złamane. */
  max-width: 52ch;
  margin: 0 auto;
}

.empty__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin: var(--sp-4) 0 0;
}

td.empty,
.table td.empty {
  border: 0;
  background: transparent;
  padding: var(--sp-6);
}

/* 6.8 Oś czasu etapów */
.timeline {
  list-style: none;
  margin: 0 0 var(--sp-6);
  padding: 0;
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.timeline__item {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-top: 4px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-4);
}

.timeline__item--done {
  border-top-color: var(--ok);
}

.timeline__item--open {
  border-top-color: var(--accent);
  box-shadow: var(--shadow);
}

.timeline__item--next {
  border-top-color: var(--line-strong);
  background: var(--surface-2);
}

.timeline__kind {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  font-weight: 700;
  margin: 0 0 var(--sp-2);
}

.timeline__dates {
  margin: 0 0 var(--sp-3);
  padding: 0;
  list-style: none;
  font-size: 0.875rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.timeline__dates div {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 0.1rem 0;
}

.timeline__dates dt {
  color: var(--muted);
}

.timeline__dates dd {
  margin: 0;
  color: var(--ink-soft);
  text-align: right;
}

/* Blok „terminy etapów” w treści redakcyjnej (``cms/blocks/stage_timeline.html``). Nagłówek jest
   opcjonalnym polem bloku, więc bywa pierwszym elementem sekcji – wtedy własny margines górny
   dublowałby odstęp od śródtytułu stojącego nad blokiem. */
.stage-timeline__heading {
  margin-top: 0;
}

/* 6.9 Odliczanie */
.countdown {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-4);
  background: var(--navy-800);
  color: #f4f1e8;
  border-radius: var(--radius);
  border-left: 4px solid var(--accent);
  padding: var(--sp-4) var(--sp-6);
  margin: 0 0 var(--sp-4);
}

.countdown__label {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(244, 241, 232, 0.7);
  display: block;
  width: 100%;
}

.countdown__deadline {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  font-weight: 700;
}

.countdown__value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--accent-on-brand);
  margin-left: auto;
}

/* 6.10 Kafelki KPI */
.kpi {
  list-style: none;
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  margin: 0 0 var(--sp-8);
  padding: 0;
}

.kpi__item {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-4);
}

.kpi__value {
  display: block;
  font-family: var(--font-serif);
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}

@media (prefers-color-scheme: dark) {
  .kpi__value {
    color: var(--accent);
  }
}

.kpi__label {
  display: block;
  margin-top: var(--sp-1);
  font-size: 0.8125rem;
  color: var(--muted);
  letter-spacing: 0.02em;
}

.kpi__item--alert .kpi__value {
  color: var(--danger);
}

.kpi__item--attention .kpi__value {
  color: var(--accent-ink);
}

/* Trzeci wiersz kafelka: zmiana, próg, termin („+3 od wczoraj”). Drobniejszy od etykiety, żeby
   liczba została najgłośniejszym elementem kafelka. */
.kpi__hint {
  display: block;
  margin-top: var(--sp-1);
  font-size: 0.75rem;
  color: var(--muted);
}

/* Kafelek, który prowadzi do listy stojącej za liczbą. Cały kafelek jest polem kliknięcia
   (a nie sama liczba), więc odnośnik rozpycha się na całą jego powierzchnię. */
.kpi__item--link {
  padding: 0;
}

.kpi__item--link > a {
  display: block;
  padding: var(--sp-4);
  color: inherit;
  text-decoration: none;
  border-radius: inherit;
}

.kpi__item--link > a:hover {
  background: var(--surface-2);
}

.kpi__item--link > a:hover .kpi__label {
  color: var(--ink-soft);
  text-decoration: underline;
}

/* 6.11 Listy z definicjami / metadane */
.meta-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-1) var(--sp-4);
  margin: 0 0 var(--sp-4);
  font-size: 0.9375rem;
}

.meta-list dt {
  color: var(--muted);
}

.meta-list dd {
  margin: 0;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* Lista definicji z bloku ``definitions`` – tabela dwukolumnowa dokumentu („Cel | Podstawa”).
   Każda para stoi w osobnej karcie, wartość pod etykietą: komórki są tu całymi zdaniami, więc
   dwie kolumny obok siebie zwężałyby tekst poniżej granicy czytelności na węższych ekranach.
   Nagłówki kolumn powtarzają się jako drobna etykieta nad każdą częścią pary. */
.definitions {
  display: grid;
  gap: var(--sp-2);
  margin: var(--sp-4) 0 var(--sp-6);
}

.definitions__row {
  display: grid;
  gap: var(--sp-2);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--sp-3) var(--sp-4);
}

.definitions dt,
.definitions dd {
  margin: 0;
}

.definitions__term {
  font-weight: 600;
}

.definitions__value {
  color: var(--ink-soft);
}

.definitions__label {
  display: block;
  margin-bottom: var(--sp-1);
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.doc-list,
.results-list,
.plain-list {
  list-style: none;
  margin: 0 0 var(--sp-6);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.doc-list li,
.results-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--sp-3) var(--sp-4);
}

.link-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.link-list a {
  font-weight: 600;
}

/* 6.12 Czipy – filtr, wybór, etykieta -------------------------------------------------------- */
/*
 * Czip a odznaka: czip jest **kontrolką** (klika się go, żeby zawęzić listę), odznaka **opisuje**
 * stan wiersza. Wyglądają podobnie, więc różnicę musi nieść zachowanie, a nie tylko kolor – stąd
 * osobny komponent zamiast wariantu ``.badge``: czip ma pole dotyku, kursor i stan „wybrany”.
 *
 * Stan wybrany czytamy z ``aria-current="page"`` (odnośnik zmieniający adres) albo
 * ``aria-pressed="true"`` (przycisk przełączający). Świadomie **nie** ma klasy ``.chip--active``:
 * jedno źródło prawdy znaczy, że nie da się zrobić czipa, który wygląda na włączony, a dla
 * czytnika ekranu jest wyłączony. Taki rozjazd jest w panelach najczęstszym błędem dostępności.
 */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
  padding: 0;
  list-style: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  /* 2.25 rem = 36 px: pole dotyku ponad zalecanym minimum (WCAG 2.5.8 – 24 px), bez robienia
     z paska filtrów drugiego rzędu przycisków. */
  min-height: 2.25rem;
  padding: 0.25rem var(--sp-3);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-soft);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.chip:hover {
  border-color: var(--brand);
  background: var(--surface-2);
  color: var(--ink);
}

/* Wybrany czip ma i tło, i grubszy obrys, i pogrubienie – trzy sygnały zamiast jednego koloru,
   bo pasek filtrów bywa czytany w słońcu, na tanim ekranie i z deuteranopią. */
.chip[aria-current],
.chip[aria-pressed="true"] {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}

.chip[aria-current]:hover,
.chip[aria-pressed="true"]:hover {
  background: var(--navy-700);
  border-color: var(--navy-700);
  color: var(--brand-ink);
}

/* Licznik przy nazwie filtru („Nowe 7”). Cyfry tabelaryczne, żeby pasek nie drgał przy
   odświeżeniu listy, i własne tło – liczba jest dopowiedzeniem, nie częścią etykiety. */
.chip__count {
  padding: 0 0.4em;
  border-radius: 999px;
  background: var(--surface-inset);
  color: var(--muted);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

.chip[aria-current] .chip__count,
.chip[aria-pressed="true"] .chip__count {
  background: rgba(255, 255, 255, 0.16);
  color: var(--brand-ink);
}

/* Czip, który nie jest kontrolką (etykieta na karcie): bez kursora i bez stanu najechania –
   inaczej obiecywałby kliknięcie, którego nie ma. */
.chip--static {
  cursor: default;
  background: var(--surface-2);
  font-weight: 500;
}

.chip--static:hover {
  border-color: var(--line-strong);
  background: var(--surface-2);
  color: var(--ink-soft);
}

/* 6.13 Zakładki widoku ----------------------------------------------------------------------- */
/*
 * Zakładki są **odnośnikami**: przełączają widok przez adres, więc da się je skopiować, otworzyć
 * w nowej karcie i cofnąć przyciskiem przeglądarki. Wariant ``role="tab"`` (przełączanie bez
 * przeładowania) dostaje te same reguły – stan bierzemy z ``aria-selected``, nie z klasy.
 *
 * ``.tab--active`` zostaje jako dopisek dla szablonów, które i tak stawiają ``aria-current``:
 * klasa i atrybut mają wyglądać identycznie, żeby dwa panele pisane osobno nie rozjechały się.
 */
.tabs {
  display: flex;
  gap: var(--sp-1);
  margin: 0 0 var(--sp-6);
  border-bottom: 1px solid var(--line);
  /* Na wąskim ekranie pas przewija się w poziomie zamiast zawijać do drugiego wiersza: zawinięta
     zakładka stoi nad kreską, która jej nie dotyczy, i wygląda jak inny komponent. */
  overflow-x: auto;
  scrollbar-width: thin;
}

.tab {
  flex: 0 0 auto;
  padding: var(--sp-2) var(--sp-4);
  /* Podkreślenie aktywnej zakładki jest kreską pod spodem, ale rezerwujemy je na każdej – bez
     tego kliknięcie przesuwałoby zawartość o 3 px. */
  border-bottom: 3px solid transparent;
  margin-bottom: -1px;
  color: var(--muted);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  background: none;
  cursor: pointer;
}

.tab:hover {
  color: var(--ink);
  border-bottom-color: var(--line-strong);
}

.tab--active,
.tab[aria-current],
.tab[aria-selected="true"] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* 6.14 Rozwijana sekcja ---------------------------------------------------------------------- */
/*
 * Natywne ``<details>``: otwiera się myszą i klawiaturą, zna ``aria-expanded`` bez naszej pomocy
 * i działa przy wyłączonym JavaScripcie. Każdy własny „akordeon” ze skryptem jest w porównaniu
 * z tym regresem – i tak właśnie działa rozwijane menu dokumentów w nagłówku.
 */
.details {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  margin: 0 0 var(--sp-4);
}

.details__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  color: var(--ink);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

/* Domyślny trójkąt w dwóch zapisach (WebKit i reszta) – własny znacznik jest niżej. */
.details__summary::-webkit-details-marker {
  display: none;
}

.details__summary::marker {
  content: "";
}

/* Znacznik rysuje ramka obrócona o 45° – jeden element mniej w DOM i zero żądań na ikonę.
   Obrót ma przejście, więc gasi go globalna reguła ``prefers-reduced-motion``. */
.details__summary::after {
  content: "";
  flex: none;
  width: 0.5em;
  height: 0.5em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-0.15em) rotate(45deg);
  transition: transform 0.15s ease;
}

.details[open] > .details__summary::after {
  transform: translateY(0.1em) rotate(225deg);
}

.details__summary:hover {
  background: var(--surface-2);
}

/* Oddech treści jest w środku, a nie marginesem na ``<details>``: zwinięta sekcja ma wtedy
   wysokość samego nagłówka i sąsiednie sekcje stoją równo. */
.details__body {
  padding: var(--sp-4);
  border-top: 1px solid var(--line);
}

.details__body > :first-child {
  margin-top: 0;
}

.details__body > :last-child {
  margin-bottom: 0;
}

/* 6.15 Układ dwukolumnowy z przyklejoną kolumną boczną ---------------------------------------- */
/*
 * ``minmax(0, 1fr)``, a nie ``1fr``: domyślne ``min-width: auto`` elementu siatki liczy się od
 * **zawartości**, więc jedna szeroka tabela rozepchnęłaby kolumnę i cała strona dostałaby poziomy
 * suwak. To jest najczęstsza przyczyna „strona się przewija w bok na telefonie”.
 *
 * Kolumna boczna przykleja się dopiero tam, gdzie jest obok treści. Na wąskim ekranie stoi pod
 * treścią i przyklejona być nie może – zasłaniałaby to, co czytamy.
 */
.layout-2col {
  display: grid;
  gap: var(--sp-6);
  align-items: start;
  margin: 0 0 var(--sp-8);
}

.layout-2col__main {
  min-width: 0;
}

.layout-2col__aside {
  min-width: 0;
}

@media (min-width: 1000px) {
  .layout-2col {
    grid-template-columns: minmax(0, 1fr) 20rem;
  }

  /* Kolumna boczna jest po prawej także wtedy, gdy w DOM stoi przed treścią – kolejność czytania
     (najpierw treść, potem skróty) nie musi być kolejnością na ekranie. */
  .layout-2col--aside-first {
    grid-template-columns: 20rem minmax(0, 1fr);
  }

  .layout-2col__aside {
    position: sticky;
    /* Pasek konta przykleja się do góry okna i ma ok. 4.5 rem – kolumna boczna zaczyna się pod
       nim, a nie za nim. Ten sam odstęp, co ``html { scroll-padding-top }``. */
    top: 5rem;
    max-height: calc(100vh - 6rem);
    overflow-y: auto;
  }
}

/* 6.16 Powłoka panelu ------------------------------------------------------------------------ */
/*
 * Wspólny szkielet dla panelu koordynatora, uczestnika i recenzenta: pionowa nawigacja sekcji
 * plus treść. Osobno od ``.layout-2col``, bo to nie jest „treść i dodatek” – nawigacja panelu
 * jest stałym elementem sterującym i ma swoje ``<nav aria-label>``.
 */
.panel {
  display: grid;
  gap: var(--sp-6);
  align-items: start;
  margin: 0 0 var(--sp-8);
}

.panel__main {
  min-width: 0;
}

/*
 * Uwaga na zasięg: układ samego pasa nawigacji dostaje **wyłącznie** ta ``.panel__nav``, której
 * bezpośrednimi dziećmi są ``.panel__nav-link`` – czyli ta zbudowana z tego systemu. Panel
 * koordynatora ma w tym samym miejscu własne menu w ``<details>`` (``coordinator.css``): gdyby
 * złapała je reguła ``display: flex; overflow-x: auto``, rozwinięta lista przycięłaby się do
 * wysokości paska. ``:has()`` jest tu tańsze niż druga nazwa klasy dla tego samego miejsca
 * w układzie – powłoka ``.panel`` zostaje wspólna, a wypełnienie jest sprawą panelu.
 */
.panel__nav:has(> .panel__nav-link) {
  display: flex;
  /* Na wąskim ekranie pozycje stoją w jednym przewijalnym pasie. Nie chowamy ich za rozwijaczem:
     osiem sekcji za „Menu” to osiem dotknięć zamiast jednego, a pas przewija się kciukiem. */
  gap: var(--sp-1);
  overflow-x: auto;
  scrollbar-width: thin;
  padding-bottom: var(--sp-1);
  border-bottom: 1px solid var(--line);
}

.panel__nav-link {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
  min-height: 2.25rem;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.panel__nav-link:hover {
  background: var(--surface-2);
  color: var(--ink);
}

/* Stan aktywny wyłącznie z ``aria-current`` – patrz komentarz przy czipach. Kreska po lewej
   (na szerokim ekranie) i tło: kolor nie jest jedynym nośnikiem. */
.panel__nav-link[aria-current] {
  background: var(--brand-soft);
  color: var(--brand);
  box-shadow: inset 3px 0 0 var(--accent);
}

.panel__nav-count {
  margin-left: auto;
  padding: 0 0.4em;
  border-radius: 999px;
  background: var(--surface-inset);
  color: var(--muted);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

@media (min-width: 900px) {
  .panel {
    grid-template-columns: 15rem minmax(0, 1fr);
  }

  .panel__nav:has(> .panel__nav-link) {
    flex-direction: column;
    overflow-x: visible;
    border-bottom: 0;
    padding-bottom: 0;
    position: sticky;
    /* Pod przyklejonym paskiem konta – ta sama wartość, co ``scroll-padding-top`` dokumentu. */
    top: 5rem;
  }

  .panel__nav-link {
    justify-content: flex-start;
  }
}

/* 6.17 Pasek zaznaczenia --------------------------------------------------------------------- */
/*
 * Akcje grupowe („zaznacz wiersze i zrób z nimi X”) należą do dołu ekranu, a nie nad tabelę:
 * zaznacza się przewijając listę, a przycisk musi być pod ręką w chwili, w której się skończyło.
 * Pasek stoi w tym samym ``<form>``, co tabela, i dopóki nic nie jest zaznaczone – ma ``hidden``.
 *
 * Licznik potrzebuje ``aria-live="polite"``: zmiana zaznaczenia jest jedyną informacją zwrotną,
 * a bez tego atrybutu nie dociera do nikogo, kto nie patrzy na pasek.
 */
.bulk-bar {
  position: sticky;
  bottom: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: var(--sp-4) 0 0;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.bulk-bar__count {
  margin: 0;
  color: var(--ink);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
}

.bulk-bar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

/* --- 7. Widoki ------------------------------------------------------------------------------ */

/* 7.1 Strona główna */
.hero {
  position: relative;
  background: linear-gradient(135deg, var(--navy-800), var(--navy-700) 60%, var(--navy-600));
  color: #f4f1e8;
  border-radius: var(--radius-lg);
  padding: var(--sp-12) var(--sp-8);
  margin-bottom: var(--sp-8);
  overflow: hidden;
  box-shadow: var(--shadow);
}

.hero::after {
  content: "";
  position: absolute;
  inset: auto -60px -120px auto;
  width: 320px;
  height: 320px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(212, 50, 40, 0.42), transparent 65%);
  pointer-events: none;
}

.hero__inner {
  position: relative;
  max-width: 46rem;
}

.hero .eyebrow {
  color: var(--accent-on-brand);
}

.hero h1 {
  color: #fff;
  font-size: clamp(2rem, 1.4rem + 2.4vw, 3rem);
  margin-bottom: var(--sp-4);
}

.hero__text {
  font-size: 1.0625rem;
  color: rgba(244, 241, 232, 0.85);
  max-width: 44ch;
}

.hero__text a {
  color: var(--accent-on-brand);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}

.hero__actions .btn--secondary {
  background: transparent;
  border-color: rgba(244, 241, 232, 0.5);
  color: #f4f1e8;
}

.hero__actions .btn--secondary:hover {
  background: rgba(244, 241, 232, 0.12);
  border-color: #f4f1e8;
  color: #fff;
}

/* Zapowiedź startu rejestracji pod przyciskami (``cms.SiteSettings.registration_note``). Zwykły
   wiersz tekstu, a nie plakietka jak ``hero__status``: to informacja o dacie, a nie stan systemu –
   wyróżniona ramką konkurowałaby wzrokowo z przyciskiem, pod którym stoi. */
.hero__note {
  max-width: 44ch;
  margin-top: var(--sp-4);
  font-size: 0.875rem;
  color: rgba(244, 241, 232, 0.85);
}

.hero__status {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-top: var(--sp-6);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius);
  background: rgba(244, 241, 232, 0.1);
  border: 1px solid rgba(239, 138, 130, 0.5);
  font-size: 0.875rem;
  color: #f4f1e8;
}

.news-card__date {
  display: block;
  font-size: 0.8125rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--sp-1);
}

.news-card h2,
.news-card h3,
.archive-list .card__title {
  margin: 0 0 var(--sp-2);
  font-size: 1.125rem;
}

.news-card h2 a,
.news-card h3 a,
.archive-list .card__title a {
  text-decoration: none;
  color: var(--ink);
}

.news-card h2 a:hover,
.news-card h3 a:hover,
.archive-list .card__title a:hover {
  color: var(--link);
  text-decoration: underline;
}

.news-card p {
  color: var(--ink-soft);
  font-size: 0.9375rem;
  margin: 0;
}

.article {
  max-width: var(--reading);
}

.article__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
  color: var(--muted);
  font-size: 0.875rem;
  margin-bottom: var(--sp-6);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line);
}

.article__lead {
  font-family: var(--font-serif);
  font-size: 1.1875rem;
  line-height: 1.5;
  color: var(--ink);
  margin-bottom: var(--sp-6);
}

.backlink {
  display: inline-block;
  margin-top: var(--sp-8);
  font-weight: 600;
}

/* 7.1a Sekcja „jak zacząć w 3 krokach” na stronie głównej. Numer kroku rysuje licznik CSS,
   a nie znacznik w HTML-u: lista jest ``<ol>``, więc czytnik ekranu i tak poda kolejność,
   a duplikowanie numeru w treści dawałoby „krok 1 1”. */
.steps {
  list-style: none;
  counter-reset: krok;
  margin: 0 0 var(--sp-6);
  padding: 0;
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.steps__item {
  counter-increment: krok;
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-top: 4px solid var(--accent);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-4);
}

.steps__item::before {
  content: counter(krok);
  display: block;
  font-family: var(--font-serif);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
  margin-bottom: var(--sp-2);
}

.steps__title {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  font-weight: 700;
  margin: 0 0 var(--sp-1);
}

.steps__text {
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

.steps__cta {
  margin: 0;
}

/* 7.1c Partnerzy: karty na /partnerzy/ i pas logotypów na stronie głównej.

   Kółko z inicjałami zastępuje logotyp, którego dla większości partnerów nie ma. Jest tego
   samego rozmiaru co kadr obrazu, więc siatka nie skacze, kiedy część kart ma plik, a część
   nie. Karty stoją w tej samej siatce ``.cards``, co aktualności — partner nie jest osobnym
   gatunkiem treści, tylko kartą o innym nagłówku. */
.partners__group {
  margin-bottom: var(--sp-6);
}

.partner-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.partner-card__mark {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  min-height: 80px;
}

/* Kadr logotypu: stała wysokość i białe tło. Znaki instytucji mają skrajnie różne proporcje
   (pas PCSS-u 1600×208 obok kwadratowego godła IF PAN), więc bez wspólnego kadru karty w siatce
   miałyby nazwy na różnych wysokościach. Tło jest jasne także w trybie ciemnym – logotypy są
   rysowane na biel i na granacie znikałyby razem z konturem.

   Kadr urósł ze 112 na 150 px (obraz z 96 na 128) po zgłoszeniu organizatora o nieczytelnym
   napisie Wydziału Informatyki i Telekomunikacji PWr. Ten znak jest **prawie kwadratowy**
   (584×528), więc dwie kolumny siatki nic by mu nie dały – ogranicza go wysokość kadru, a nie
   szerokość kolumny. Nieczytelny był napis **wewnątrz pliku**: nazwa wydziału złożona drobnym
   krojem i przeskalowana do 96 px razem z całym znakiem. Jedyne, co tu pomaga, to narysować znak
   większy – stąd te dwie liczby. Powyżej 128 px kafle zaczynają dominować stronę, a nazwa
   instytucji stoi i tak pod znakiem jako tekst. */
.partner-card__mark--logo {
  justify-content: center;
  height: 150px;
  min-height: 0;
  padding: var(--sp-3);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
}

.partner-card__logo {
  max-width: 100%;
  max-height: 128px;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* Wszystkie karty partnerów mają **ten sam rozmiar** – decyzja organizatora (P. Góra, 18.09.2026).
   Wcześniej znak-pas (``PartnerValue.is_wide``, np. PCSS, AIQLAB) dostawał dwie kolumny siatki, żeby
   napis w nim był większy; dawało to jednak karty różnej szerokości w jednym rzędzie i czytało się
   jak wyróżnienie jednego partnera kosztem pozostałych. Klasa ``partner--wide`` zostaje w szablonie
   (``templates/cms/partners_page.html``) jako informacja o proporcji znaku, ale nie zmienia już
   układu: każdy logotyp mieści się w tym samym kadrze przez ``object-fit: contain``. */
.partners {
  /* Każdy rząd tak wysoki, jak najwyższa karta grupy – bez tego rząd z nazwami jednowierszowymi
     byłby niższy od rzędu z nazwą na trzy wiersze. */
  grid-auto-rows: 1fr;
}

.partners > li {
  display: flex;
}

.partners > li > .partner-card {
  flex: 1 1 auto;
  width: 100%;
}

.partner-card__initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--brand-soft);
  color: var(--brand);
  font-family: var(--font-serif);
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* Odnośnik obejmuje logotyp **i** nazwę (templates/cms/partners_page.html), więc to on, a nie
   karta, jest kolumną: przejmuje układ ``.partner-card`` dla obu swoich dzieci. Bez tego kadr
   znaku i nazwa zlepiłyby się w jeden wiersz tekstu, bo ``<a>`` jest domyślnie elementem
   liniowym. Kolor i podkreślenie dziedziczą się z otoczenia – kafel ma wyglądać jak karta,
   a nie jak odnośnik tekstowy; podkreślenie pojawia się dopiero przy najechaniu i przy fokusie,
   i to **pod nazwą**, bo to ona jest tu tekstem. */
.partner-card__link {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  color: inherit;
  text-decoration: none;
}

.partner-card__link:hover .partner-card__name,
.partner-card__link:focus-visible .partner-card__name {
  text-decoration: underline;
}

.partner-card__name {
  font-family: var(--font-serif);
  font-size: 1.0625rem;
  line-height: 1.3;
  /* Miejsce na trzy wiersze nazwy w każdej karcie: grupy partnerów są osobnymi siatkami, więc
     samo ``grid-auto-rows`` wyrównuje karty tylko w obrębie grupy. Stała wysokość pola nazwy
     daje ten sam rozmiar karty na całej stronie (najdłuższa dzisiejsza nazwa ma trzy wiersze). */
  min-height: calc(3 * 1.3em);
  margin: 0;
}

.partner-card__text {
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

.partners-cta {
  padding: var(--sp-6);
}

.partners-cta__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-4) 0 0;
}

/* Pas logotypów na stronie głównej. Siatka o **stałej** liczbie kolumn, a nie zawijający się
   ``flex``: przy zawijaniu ostatni wiersz miał tyle kafli, ile zostało (cztery i dwa rozciągnięte
   na pół szerokości), co czytało się jak błąd układu. Trzy kolumny to kompromis wymuszony
   proporcjami znaków – pas PCSS-u jest ośmiokrotnie szerszy niż wyższy, więc przy sześciu kolumnach
   miałby siedemnaście pikseli wysokości. Sześciu partnerów układa się w dwa równe wiersze. */
.partner-strip {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Równe kafle: każdy rząd tak wysoki, jak najwyższy kafel pasa (podpis bywa na dwa wiersze). */
  grid-auto-rows: 1fr;
  gap: var(--sp-4);
  margin: 0;
  padding: 0;
}

@media (min-width: 700px) {
  .partner-strip {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Kafel jest kolumną „znak + nazwa” o wspólnej wysokości minimalnej, więc pas wygląda jak jeden
   rząd znaków, a nie jak zbiór obrazków o przypadkowych rozmiarach. ``min-height`` zamiast
   ``height``: nazwa instytucji bywa dwuwierszowa („Poznańskie Centrum Superkomputerowo-Sieciowe”),
   a przycięty podpis byłby gorszy od nierównego wiersza kafli – siatka wyrównuje je i tak,
   bo pozycje w wierszu rozciągają się do najwyższej. Tło jest białe – patrz
   ``.partner-card__mark--logo``. */
.partner-strip__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 148px;
  padding: var(--sp-3) var(--sp-4);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

/* Bez podwójnej szerokości: wszystkie logotypy w pasie mają równe pola – decyzja organizatora
   z 18.09.2026 (patrz ``.partners``). Klasa ``partner-strip__item--wide`` zostaje w szablonie bez
   wpływu na układ. */

.partner-strip__link {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  width: 100%;
  height: 100%;
  text-decoration: none;
}

/* Kadr znaku jest osobnym elementem, a nie samym obrazem: podpis z nazwą stoi pod nim, więc
   obraz potrzebuje własnego pudełka o stałej wysokości – inaczej kafle z niskim znakiem miałyby
   podpis wyżej niż kafle ze znakiem kwadratowym. 96 px zamiast dawnych 72 z tego samego powodu,
   co 150 px w kadrze karty na ``/partnerzy/``: znaki z nazwą instytucji wpisaną w plik (logotyp
   Wydziału Informatyki i Telekomunikacji PWr) w mniejszym kadrze mają napis poniżej progu
   czytelności, a przeskalować da się wyłącznie cały znak. */
.partner-strip__frame {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 96px;
}

.partner-strip__logo {
  max-width: 100%;
  max-height: 96px;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* Podpis pod znakiem. 0,875 rem, a nie 0,8125 rem jak podpowiedzi: to jest **treść** – nazwa
   instytucji, której organizator nie mógł odczytać z samego logotypu – a nie objaśnienie
   czegoś, co widać obok. Kolor jest przygaszony, bo pierwszym planem kafla zostaje znak. */
.partner-strip__name {
  font-family: var(--font-serif);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
  color: var(--ink-soft);
}

/* 7.1b Dokument urzędowy (regulamin): metryka, załącznik, sticky spis rozdziałów.
   Układ jest jednokolumnowy do 1099 px – spis rozdziałów stoi wtedy nad treścią, tak jak
   w dokumencie drukowanym. Od 1100 px (szerokość kontenera) przechodzi do lewej kolumny
   i przykleja się przy przewijaniu. Całość na samym CSS: żadnego skryptu, więc spis działa
   też przy wyłączonym JS i w widoku wydruku. */
.doc-layout {
  display: grid;
  gap: var(--sp-6);
}

.doc-intro {
  margin-bottom: var(--sp-6);
}

.doc-meta {
  margin-bottom: var(--sp-4);
  padding-bottom: 0;
  border-bottom: 0;
}

/* Pas czynności nad treścią dokumentu: „Pobierz PDF” i „Drukuj”. ``flex`` z zawijaniem, bo na
   telefonie dwa przyciski obok siebie nie mieszczą się w jednym wierszu, a odstęp między nimi
   ma być odstępem, a nie przypadkową spacją z HTML-a. */
.doc-print {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
}

/* Karta „Do pobrania”: lista plików przypiętych do strony (templates/cms/_attachments.html)
   oraz sekcja dokumentów na stronie głównej. Jeden komponent, bo w obu miejscach czytelnik robi
   to samo – wybiera plik i klika „Pobierz”. */
.doc-download {
  max-width: var(--reading);
  margin-bottom: var(--sp-6);
}

/* Na stronie głównej karta jest samodzielną sekcją, a nie akapitem w kolumnie czytelniczej. */
.doc-download--home {
  max-width: none;
}

.doc-download__title {
  margin: 0 0 var(--sp-3);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.doc-files {
  list-style: none;
  margin: 0;
  padding: 0;
}

.doc-files__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  padding: var(--sp-3) 0;
  border-top: 1px solid var(--line);
}

.doc-files__item:first-child {
  padding-top: 0;
  border-top: 0;
}

.doc-files__item:last-child {
  padding-bottom: 0;
}

/* Ikona rodzaju pliku jest znacznikiem tekstowym w ramce, a nie obrazkiem: nie dokłada żądania,
   czyta się w wariancie ciemnym i na wydruku, a nowy format wymaga jednej reguły niżej, nie
   nowego pliku SVG. Domyślna treść („PLIK”) obsługuje rozszerzenia bez własnej reguły. */
.doc-files__item::before {
  content: "PLIK";
  flex: 0 0 auto;
  min-width: 3.25rem;
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--muted);
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-align: center;
}

.doc-files__item--pdf::before {
  content: "PDF";
  border-color: var(--danger);
  background: var(--danger-soft);
  color: var(--danger-ink);
}

.doc-files__item--doc::before,
.doc-files__item--docx::before,
.doc-files__item--odt::before {
  content: "DOC";
  border-color: var(--info);
  background: var(--info-soft);
  color: var(--info-ink);
}

.doc-files__item--xls::before,
.doc-files__item--xlsx::before,
.doc-files__item--ods::before,
.doc-files__item--csv::before {
  content: "XLS";
  border-color: var(--ok);
  background: var(--ok-soft);
  color: var(--ok-ink);
}

.doc-files__item--zip::before {
  content: "ZIP";
}

.doc-files__meta {
  flex: 1 1 14rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.doc-files__label {
  font-weight: 600;
}

.doc-files__hint {
  margin: 0;
}

/* Spis dokumentów (templates/cms/document_index_page.html). Karta ma stały układ: tytuł, metryka,
   zajawka, a przyciski przyklejone do dołu – dzięki temu w siatce o różnych długościach zajawek
   „Czytaj” stoi w każdej karcie na tej samej wysokości i nie trzeba go szukać. */
.doc-index .card {
  display: flex;
  flex-direction: column;
}

.doc-card__title {
  font-size: 1.25rem;
  margin: 0 0 var(--sp-3);
}

.doc-card__title a {
  text-decoration: none;
}

.doc-card__title a:hover {
  text-decoration: underline;
  text-decoration-color: var(--accent);
}

/* Metryka na karcie jest wierszem opisu, a nie nagłówkiem strony: bez kreski i bez dużego
   odstępu, które ma ta sama klasa na stronie dokumentu. */
.doc-card__meta {
  gap: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-3);
  padding-bottom: 0;
  border-bottom: 0;
}

.doc-card__lead {
  margin: 0 0 var(--sp-4);
  color: var(--ink-soft);
}

.doc-card .doc-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: auto;
}

.doc-toc {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  padding: var(--sp-4);
}

.doc-toc__title {
  margin: 0 0 var(--sp-2);
  font-size: 0.8125rem;
  font-family: var(--font-sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.doc-toc__list {
  margin: 0;
  padding-left: 1.1rem;
  font-size: 0.9375rem;
}

.doc-toc__list li {
  margin-bottom: var(--sp-1);
}

.doc-body {
  /* Szerokość czytelnicza – ten sam token, co w artykułach newsroomu. */
  max-width: var(--reading);
}

.doc-heading {
  /* Kotwice z paska adresu nie mogą lądować tuż pod krawędzią okna. */
  scroll-margin-top: var(--sp-6);
}

.doc-heading--chapter {
  margin-top: var(--sp-12);
  padding-top: var(--sp-4);
  border-top: 2px solid var(--line);
}

.doc-heading--par {
  margin-top: var(--sp-8);
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  letter-spacing: 0.01em;
}

.doc-body > .doc-heading:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.notice {
  margin: var(--sp-6) 0;
  padding: var(--sp-4);
  border-left: 4px solid var(--info);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--info-soft);
  color: var(--ink);
}

.notice--warning {
  border-left-color: var(--warn);
  background: var(--warn-soft);
}

.notice > :first-child {
  margin-top: 0;
}

.notice > :last-child {
  margin-bottom: 0;
}

@media (min-width: 1100px) {
  /* Nazwane obszary, a nie kolejność źródłowa: w HTML-u najpierw stoi nagłówek dokumentu
     (tytuł, metryka, link do pliku) – to on jest treścią, a spis rozdziałów nawigacją.
     Na szerokim ekranie spis wjeżdża do lewej kolumny przez oba wiersze, więc przykleja
     się na całej wysokości dokumentu, a nagłówek zostaje w jednej osi z tekstem. */
  .doc-layout {
    grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
    grid-template-areas: "toc head" "toc body";
    align-items: start;
    column-gap: var(--sp-8);
    row-gap: 0;
  }

  .doc-head {
    grid-area: head;
  }

  .doc-toc {
    grid-area: toc;
    position: sticky;
    top: var(--sp-4);
    max-height: calc(100vh - 2 * var(--sp-4));
    overflow-y: auto;
  }

  .doc-body {
    grid-area: body;
  }

  /* Strona treści bez spisu sekcji (mniej niż trzy śródtytuły): pusta kolumna ``toc``
     wcinałaby tekst o 15 rem bez powodu, więc wracamy do jednej kolumny. */
  .doc-layout--plain {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "head" "body";
  }
}

/* 7.2 Panel recenzenta */
.review-layout {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 4fr);
  gap: var(--sp-6);
  align-items: start;
}

@media (max-width: 1000px) {
  .review-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

.review-layout > section {
  min-width: 0;
}

.pdf-pane,
.form-pane {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-6);
}

.pdf-pane > :first-child,
.form-pane > :first-child {
  margin-top: 0;
}

.pdf-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
  background: var(--surface-inset);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--sp-2);
  margin-bottom: var(--sp-3);
  font-size: 0.875rem;
}

.pdf-toolbar button {
  min-height: 2rem;
  padding: 0.25rem 0.65rem;
  font-size: 0.8125rem;
}

.pdf-toolbar [data-pdf-pageinfo] {
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  min-width: 6rem;
  text-align: center;
}

.pdf-toolbar [data-pdf-status] {
  margin-left: auto;
  color: var(--muted);
}

.pdf-stage {
  position: relative;
  display: inline-block;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  overflow: hidden;
  max-width: 100%;
}

.pdf-stage canvas {
  display: block;
  max-width: 100%;
}

/* Warstwa adnotacji leży dokładnie na canvasie; prostokąty pozycjonuje JS stylem inline
   (stąd 'unsafe-inline' w style-src – patrz apps/web/middleware.py). */
.pdf-annotations {
  position: absolute;
  inset: 0;
}

.pdf-annotations .rect {
  position: absolute;
  border: 2px solid var(--danger);
  background: rgba(189, 59, 38, 0.12);
  border-radius: 2px;
}

.annotations {
  list-style: none;
  margin: 0 0 var(--sp-4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.annotations li {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--sp-2) var(--sp-3);
  font-size: 0.9375rem;
  overflow-wrap: anywhere;
}

.annotations li.hint {
  border-left-color: var(--line-strong);
  color: var(--muted);
}

/* Przycisk „usuń” dopisywany przez warstwę adnotacji (static/js/review-annotations.js). */
.annotations li button {
  min-height: 1.75rem;
  padding: 0.1rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  margin-left: var(--sp-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--danger-ink);
  cursor: pointer;
}

.annotations li button:hover {
  border-color: var(--danger);
  background: var(--danger-soft);
}

/* Skala ocen jako duże segmenty. */
.scale {
  display: grid;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
}

.score-option {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  gap: var(--sp-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--sp-3) var(--sp-4);
  cursor: pointer;
  font-weight: 400;
  margin: 0;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.score-option:hover {
  border-color: var(--accent);
  background: var(--surface-2);
}

.score-option:has(input:checked) {
  border-color: var(--brand);
  border-width: 2px;
  background: var(--brand-soft);
  padding: calc(var(--sp-3) - 1px) calc(var(--sp-4) - 1px);
}

.score-option:has(input:focus-visible) {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

.score-option__value {
  font-family: var(--font-serif);
  font-size: 1.5rem;
  font-weight: 700;
  min-width: 1.5rem;
  text-align: center;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}

@media (prefers-color-scheme: dark) {
  .score-option__value {
    color: var(--accent);
  }
}

.score-option__label {
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

fieldset:disabled .score-option {
  opacity: 0.6;
  cursor: not-allowed;
}

.actions--sticky {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: flex-end;
  margin-top: var(--sp-6);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 -4px 16px rgba(18, 35, 63, 0.1);
}

#draft-status {
  min-height: 1.5rem;
  margin-top: var(--sp-3);
  font-size: 0.9375rem;
}

.dispute {
  margin-bottom: var(--sp-6);
}

/* 7.3 Panel uczestnika */
.problem-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-6);
  margin: 0 0 var(--sp-4);
}

.problem-card > :first-child {
  margin-top: 0;
}

.problem-card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-2);
}

.problem-card__head h3 {
  margin: 0;
  font-size: 1.125rem;
}

.problem-card__specs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  color: var(--muted);
  font-size: 0.875rem;
  margin-bottom: var(--sp-4);
}

.code-chip {
  display: inline-block;
  font-family: ui-monospace, "SFMono-Regular", "Cascadia Mono", Consolas, monospace;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  background: var(--brand-soft);
  color: var(--info-ink);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 0.1rem 0.45rem;
}

.feedback {
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--sp-3) var(--sp-4);
  margin: 0;
}

.feedback__title {
  display: block;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: var(--sp-1);
}

.feedback p {
  margin: 0 0 var(--sp-2);
  color: var(--ink-soft);
}

.feedback p:last-child {
  margin-bottom: 0;
}

.annotation {
  font-size: 0.875rem;
  color: var(--muted);
}

.result-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}

.result-summary__points {
  font-family: var(--font-serif);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}

@media (prefers-color-scheme: dark) {
  .result-summary__points {
    color: var(--accent);
  }
}

/* 7.4 Koordynator */
.stage-panel__actions {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  align-items: stretch;
}

.stage-panel__actions form {
  display: flex;
  flex-direction: column;
  justify-content: flex-end; /* przyciski w jednej linii niezależnie od liczby pól nad nimi */
  gap: var(--sp-2);
  margin: 0;
  padding: var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

.stage-panel__actions form p {
  margin: 0;
}

.stage-panel__actions label {
  font-size: 0.8125rem;
}

/* Odnośniki karty etapu (terminy, zadania, admin) – jeden pasek nad blokiem akcji POST-owych,
   żeby „przejdź do” nie wyglądało jak kolejny przycisk wykonujący operację. */
.stage-panel__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin: 0 0 var(--sp-4);
  font-size: 0.9375rem;
}

/* 7.5 Strona 429 */
.throttled {
  max-width: 32rem;
  margin: var(--sp-12) auto;
  text-align: center;
}

.throttled__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
  background: var(--warn-soft);
  border: 2px solid var(--warn);
  color: var(--warn-ink);
  font-family: var(--font-serif);
  font-size: 2rem;
  font-weight: 700;
  margin-bottom: var(--sp-4);
}

.throttled__retry {
  display: inline-block;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  background: var(--surface-inset);
  border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-4);
}

/* --- Baner komunikatów organizatora ---------------------------------------------------------
   Pasek pod nagłówkiem, na każdej stronie serwisu. Kolory biorą się z tych samych tokenów, co
   plakietki stanu (``--info``/``--warn``/``--danger``), więc „awaria” w banerze wygląda tak samo,
   jak „awaria” w tabeli – czytelnik uczy się jednego kodu kolorów, a nie dwóch.
   Na wydruku banera nie ma: to komunikat o stanie serwisu „teraz”, a nie treść dokumentu. */
.announcements {
  max-width: var(--container);
  margin: var(--sp-3) auto 0;
  padding: 0 var(--sp-4);
}

.announcement {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-2);
  border-radius: var(--radius);
  /* Gruba kreska z lewej zamiast obramowania dookoła: baner ma być odróżnialny od karty treści,
     a nie wyglądać jak kolejna karta. */
  border-left: 4px solid var(--info);
  background: var(--info-soft);
  color: var(--ink);
}

.announcement--warning {
  border-left-color: var(--warn);
  background: var(--warn-soft);
}

.announcement--danger {
  border-left-color: var(--danger);
  background: var(--danger-soft);
}

.announcement__text {
  margin: 0;
  flex: 1 1 auto;
}

.announcement__link {
  margin-left: var(--sp-2);
  white-space: nowrap;
}

.announcement__close {
  flex: 0 0 auto;
  background: none;
  border: 0;
  cursor: pointer;
  font-size: 1.25rem;
  line-height: 1;
  padding: 0 var(--sp-1);
  color: inherit;
}

.announcement__close:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

@media print {
  .announcements {
    display: none;
  }
}

/* --- Najczęstsze pytania (/faq/) ------------------------------------------------------------
   Pytanie jest elementem <summary>, czyli natywnym rozwijaczem przeglądarki: działa klawiaturą
   i bez JavaScriptu, a przy polityce CSP bez kodu inline to jedyne rozwiązanie, które nie
   wymaga własnego pliku skryptu. Stąd wyłącznie kosmetyka: kursor, znacznik listy i odstępy. */
.faq__item {
  margin-bottom: 0.75rem;
}

.faq__question {
  cursor: pointer;
  font-weight: 600;
  /* Domyślny trójkąt zostaje – to on mówi czytelnikowi, że pozycja się rozwija. */
  list-style-position: outside;
}

.faq__question:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

.faq__answer {
  margin-top: 0.75rem;
}

/* --- 7b. Wyszukiwarka szkół (rejestracja uczestnika) ---------------------------------------- */

/* Blok jest jednym polem formularza złożonym z pięciu kontrolek – miejscowość, wyszukiwarka,
   ukryty identyfikator, kratka wyjątku i wolny tekst (patrz templates/web/_school_picker.html) –
   więc odstępy w środku są mniejsze niż między polami. Obie listy podpowiedzi (miejscowości
   i szkół) używają tych samych klas ``.school-picker__list``/``__item``: to ten sam komponent
   w dwóch rolach, a nie dwa podobne. */
.school-picker {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Kotwica listy podpowiedzi: opakowanie samego pola tekstowego, nie całego bloku. */
.school-picker__control {
  display: block;
  position: relative;
}

.school-picker__list {
  list-style: none;
  margin: 0;
  padding: 0;
  /* Lista wisi nad formularzem, żeby nie przesuwała pól przy każdym naciśnięciu klawisza. */
  position: absolute;
  z-index: 20;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  max-height: 18rem;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}

.school-picker__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
  border-bottom: 1px solid var(--line);
}

.school-picker__item:last-child {
  border-bottom: 0;
}

.school-picker__item:hover,
.school-picker__item--active {
  background: var(--brand-soft);
}

.school-picker__name {
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--ink);
}

.school-picker__meta {
  color: var(--muted);
  font-size: 0.8125rem;
}

/* --- 8. Narzędzia i dostępność -------------------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Para do sekcji ``@media print``: ``.print-only`` widać wyłącznie na papierze (nagłówek z datą
   wydruku i adresem strony), ``.no-print`` wyłącznie na ekranie. Bez ``!important`` – element
   ma być normalnie ukryty, a nie ukryty wbrew regułom układu; ``@media print`` odkrywa go
   regułą o tej samej swoistości, ale w bloku stojącym niżej. */
.print-only {
  display: none;
}

.stack-gap {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
}

.mt-0 {
  margin-top: 0;
}

@media (max-width: 640px) {
  .page {
    padding: var(--sp-6) var(--sp-3) var(--sp-8);
  }

  .hero {
    padding: var(--sp-8) var(--sp-4);
  }

  .card,
  .problem-card,
  .pdf-pane,
  .form-pane {
    padding: var(--sp-4);
  }

  .topbar__inner {
    gap: var(--sp-2);
  }

  .nav {
    gap: var(--sp-1) var(--sp-3);
  }

  /* Menu serwisu ma dziewięć pozycji – na wąskim ekranie zawija się do trzech wierszy.
     Świadomie zostaje pełną listą zamiast chować nadmiar za rozwijaczem: każda pozycja jest
     wtedy jednym dotknięciem, bez otwierania i bez skryptu. Wyjątkiem jest sekcja dokumentów,
     której lista rozwija się w przepływie (na tej szerokości nie ma dropdownu) – cztery
     dokumenty w pasku zajęłyby więcej miejsca niż wszystko pozostałe razem. Cena to trochę
     wyższy nagłówek, więc na tej szerokości zmniejszamy krój i odstępy zamiast ukrywać
     odnośniki. */
  .nav--cms .nav__link {
    font-size: 0.875rem;
  }

  .nav--cms {
    column-gap: var(--sp-3);
  }

  /* Adres e-mail skraca się wielokropkiem – pełny jest w ``title`` i w panelu konta. Reguła
     celuje w ``.account-bar__who``; do v0.16.0 stała tu nieużywana klasa ``.nav__who``, pamiątka
     po pasku sprzed wydzielenia paska konta (grep po ``templates/`` nie znajdował jej nigdzie),
     więc e-mail na 360 px rozpychał pasek na trzy wiersze. */
  .account-bar__who {
    max-width: 12ch;
  }
}

/* Ekrany najwęższe z tych, które obsługujemy (iPhone SE i podobne: 360 px CSS). Nie ma tu nowego
   układu – są trzy zabezpieczenia przed poziomym suwakiem na **całej** stronie. Poziomo przewija
   się w tym serwisie wyłącznie to, co ma własną ramkę: tabela w ``.scroll``, pas zakładek,
   nawigacja panelu. */
@media (max-width: 400px) {
  .page {
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
  }

  /* Przyciski w rzędzie akcji rozkładają się na całą szerokość, zamiast zostawiać sierotę
     w drugim wierszu. */
  .actions > .btn {
    flex: 1 1 auto;
  }

  /* Długi kod, adres lub nazwa pliku w treści karty łamie się zamiast rozpychać kartę. */
  .card,
  .empty,
  .notice,
  .msg {
    overflow-wrap: anywhere;
  }

  .kpi {
    grid-template-columns: 1fr;
  }

  .account-bar {
    gap: var(--sp-1) var(--sp-2);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Wydruk. Dokumenty organizatora (regulamin, polityka RODO, wzór zgody opiekuna) trafiają na
 * papier: zgodę opiekuna trzeba wydrukować, podpisać i odesłać. Z kartki znika więc wszystko,
 * co jest nawigacją ekranu, a zostaje sama treść dokumentu. Adresy odnośników dopisujemy przy
 * treści – na papierze „patrz Regulamin” bez adresu jest odesłaniem donikąd. */
@media print {
  .topbar,
  .topbar--account,
  .footer,
  .nav,
  .cookie-notice,
  .messages,
  .skip-link,
  /* Ikony serwisów na wydruku są nieczytelne: odnośnik nie ma tekstu, a reguła ``.prose a::after``
     dopisałaby adres do pustego znaku. Na stronie „Kontakt” te same cztery adresy stoją obok
     w liście redakcyjnej („Media społecznościowe”) i to ona zostaje na papierze. */
  .social-links,
  .actions,
  .hero__actions,
  .doc-print,
  /* Elementy sterujące paneli. Na papierze filtr, zakładka i pasek zaznaczenia są kłamstwem:
     wyglądają jak treść, a nic nie robią – a pas zakładek dodatkowo sugeruje, że wydruk zawiera
     wszystkie widoki, podczas gdy zawiera jeden. */
  .chips,
  .tabs,
  .panel__nav,
  .bulk-bar,
  .no-print,
  [data-print] {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
  }

  .page {
    max-width: none;
    margin: 0;
    padding: 0;
  }

  .doc-layout,
  .doc-body,
  .doc-head {
    display: block;
  }

  /* Nagłówek sekcji nie ma zostawać sam na dole kartki, a tabela – rozjeżdżać się na dwie. */
  h1,
  h2,
  h3 {
    break-after: avoid-page;
  }

  table,
  figure,
  .card {
    break-inside: avoid;
  }

  .prose a[href^="/"]::after,
  .prose a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    word-break: break-all;
  }

  /* --- Tabela wyników na papierze -------------------------------------------------------------
   * Tabela wyników etapu jest drukowana i wieszana w szkole – to jest jej normalne życie, a nie
   * przypadek. Na ekranie mieszka w ``.scroll`` z przyklejonym nagłówkiem; na papierze jedno
   * i drugie szkodzi: ramka przewijalna obcina wiersze na krawędzi kartki, a ``position: sticky``
   * wyrzuca nagłówek na pierwszą stronę i zostawia kolejne bez opisu kolumn. */
  .scroll,
  .scroll--tall,
  .table-scroll {
    overflow: visible !important;
    max-height: none !important;
    border: 0;
  }

  .table,
  .grid {
    border-color: #000;
    font-size: 0.8125rem;
  }

  /* Nagłówek powtarza się na każdej kartce – bez tego druga strona rankingu to kolumna liczb
     bez wyjaśnienia, czego dotyczą. */
  thead {
    display: table-header-group;
  }

  tfoot {
    display: table-footer-group;
  }

  tr {
    break-inside: avoid;
  }

  .table th,
  .grid th {
    position: static;
    background: transparent;
    border-bottom: 2px solid #000;
    color: #000;
  }

  .table td,
  .grid td {
    border-bottom: 1px solid #999;
    color: #000;
  }

  /* Zebra i podświetlenie wiersza są sygnałem ekranowym. Na papierze zżerają toner, a na wydruku
     czarno-białym zamieniają się w szarą kratę, przez którą gorzej widać liczby. */
  .table tbody tr:nth-child(even),
  .grid tbody tr:nth-child(even),
  .table tr.is-qualified,
  .table tbody tr:hover {
    background: transparent !important;
  }

  /* Wiersz zakwalifikowany traci tło, więc musi odzyskać znaczenie kształtem: gruba kreska przy
     lewej krawędzi i pogrubienie. Kolor nigdy nie jest jedynym nośnikiem – także na papierze. */
  .table tr.is-qualified td,
  .table tr.is-qualified th {
    font-weight: 700;
  }

  .table tr.is-qualified td:first-child {
    border-left: 3px solid #000;
  }

  /* Odznaka drukuje się jako ramka z tekstem: tła drukarka i tak zgubi albo zamaże, a słowo
     („zakwalifikowany”, „trening”) niesie całą informację. */
  .badge,
  .tag,
  .chip--static {
    background: transparent !important;
    color: #000 !important;
    border: 1px solid #000 !important;
  }

  .badge::before {
    display: none;
  }

  /* --- Weryfikacja dokumentu ------------------------------------------------------------------
   * Wydruk potwierdzenia bywa załącznikiem do papierowej teczki, więc metryka ma być listą
   * „termin: wartość”, a nie siatką, która na kartce rozjedzie się na dwie kolumny. */
  .meta-list,
  .definitions {
    display: block;
  }

  .meta-list dt,
  .definitions dt {
    font-weight: 700;
    margin-top: var(--sp-2);
  }

  .meta-list dd,
  .definitions dd {
    margin: 0 0 var(--sp-1);
  }

  .card,
  .section,
  .empty {
    border: 1px solid #000;
    box-shadow: none;
    background: transparent;
  }

  .page-head {
    border-bottom: 2px solid #000;
    margin-bottom: var(--sp-4);
  }

  /* Element, który **istnieje tylko na papierze**: nagłówek z adresem strony i datą wydruku.
     Na ekranie byłby szumem, na kartce jest jedyną informacją o tym, skąd ta kartka pochodzi. */
  .print-only {
    display: block !important;
  }
}


/* --- blok zgód w formularzach rejestracji ---------------------------------------------------
 * Zgody stoją w osobnym ``<fieldset>``, bo są oświadczeniami o znanej treści, a nie kolejnymi
 * polami danych. Etykieta bywa dwuzdaniowa i zawiera odnośnik do dokumentu, więc pole wyboru
 * musi stać obok pierwszego wiersza tekstu, a nie w jego środku – stąd siatka, a nie ``float``.
 */
.consents {
  margin: var(--sp-6) 0 0;
  padding: var(--sp-2) var(--sp-4) 0;
  border: 1px solid var(--line, #d7dce5);
  border-radius: var(--radius);
  background: var(--surface-2, #faf8f2);
}

.consents > legend {
  padding: 0 var(--sp-2);
  font-weight: 600;
}

/* Jedna zgoda = jeden wiersz. Etykiety są dwu- i trzyzdaniowe, więc bez wyraźnego odstępu
   i kreski między wierszami nie było widać, do którego oświadczenia należy które pole wyboru.
   Kreska jest u góry (a nie u dołu): pierwszy wiersz nie potrzebuje linii pod legendą. */
.consents__item {
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  gap: var(--sp-2) var(--sp-3);
  align-items: start;
  margin: 0;
  padding: var(--sp-4) 0;
  border-top: 1px solid var(--line);
}

.consents__item:first-of-type {
  border-top: 0;
  padding-top: var(--sp-2);
}

/* Pole wyboru w zgodzie jest jedyną kontrolką, którą trafia się palcem w treści zdania – stąd
   powiększona kratka i wyrównanie do pierwszego wiersza tekstu etykiety, nie do jego środka. */
.consents__item input[type="checkbox"] {
  width: 1.15rem;
  height: 1.15rem;
  margin: 0.2rem 0 0;
  accent-color: var(--accent);
}

.consents__item label {
  display: block;
  font-weight: 400;
  cursor: pointer;
}

/* Znacznik wymagalności, podpowiedź i błąd zaczynają się pod etykietą, nie pod polem wyboru. */
.consents__item .consents__required,
.consents__item .helptext,
.consents__item ul.errorlist {
  grid-column: 2;
  margin: 0;
}

/* „wymagane” jest plakietką, a nie gwiazdką: gwiazdka w bloku, w którym część zgód jest wymagana
   warunkowo, a część dobrowolna, nie mówi nic bez legendy. */
.consents__required {
  justify-self: start;
  padding: 0.05rem 0.4rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* Podpowiedź („wymagane dla osób niepełnoletnich”, „dobrowolne – …”) stoi pod swoim wierszem
   i dotyczy tylko jego – dlatego nie ma jej w plakietce obok etykiety. */
.consents__note {
  display: block;
}

/* --- blok antyspamowy formularzy rejestracji -------------------------------------------------
 * CAPTCHA jest własna (django-simple-captcha, obrazek spod naszego adresu – patrz
 * apps/web/captcha.py), więc jej wygląd jest naszą sprawą, a nie stylem wstrzykniętym przez
 * obcy skrypt. Widżet pakietu renderuje trzy rodzeństwa w jednym akapicie: ``<img class="captcha">``,
 * ukryte pole z kluczem i pole odpowiedzi – bez własnego kontenera. Siatka układa je bez
 * dodatkowego znacznika: etykieta, podpowiedź i błąd na całą szerokość, obrazek i pole obok siebie.
 *
 * Selektor ``:has()`` łapie też formularz komitetu (renderowany przez ``form.as_p``, bez naszej
 * klasy); klasa ``.captcha-field`` zostaje dla przeglądarek bez ``:has()`` – dostaną wtedy
 * obrazek nad polem, czyli układ brzydszy, ale nadal używalny.
 */
.form p.captcha-field,
.form p:has(> img.captcha) {
  display: grid;
  grid-template-columns: auto minmax(6rem, 1fr);
  align-items: center;
  gap: var(--sp-1) var(--sp-3);
}

.form p.captcha-field > label,
.form p.captcha-field > .helptext,
.form p.captcha-field > ul.errorlist,
.form p:has(> img.captcha) > label,
.form p:has(> img.captcha) > .helptext,
.form p:has(> img.captcha) > ul.errorlist {
  grid-column: 1 / -1;
}

/* Obrazek jest bitmapą w kolorach jasnego motywu (CAPTCHA_BACKGROUND_COLOR), więc w motywie
   ciemnym dostaje ramkę – bez niej biały prostokąt wyglądałby jak dziura w formularzu. */
img.captcha {
  display: block;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: #fffefb;
}

/* Pułapka na boty: pole **jest** w formularzu i **jest** wysyłane, tylko go nie widać. Dlatego
   przesunięcie poza ekran, a nie ``display: none`` – część botów pomija pola, których
   przeglądarka nie renderuje, a to jest dokładnie to, czego chcemy uniknąć. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Akapit, w którym została sama pułapka (``form.as_p`` – rejestracja komitetu), nie zostawia
   po sobie odstępu. Bez tego między polami byłaby niewytłumaczalna dziura. */
.form p:has(> .hp-field) {
  display: none;
}

/* Pasek cookie (templates/base.html) – informacyjny albo pytający o zgodę, zależnie od tego, czy
   organizator włączył analitykę. Leży nad treścią, bo ma zniknąć po jednym kliknięciu i nie może
   przy każdym wejściu przesuwać układu strony. Klasa na ``<html>`` pochodzi ze
   static/js/consent.js: pasek jest w HTML widoczny, więc informacja dociera także bez
   JavaScriptu, a po odpowiedzi nie mignie na kolejnych podstronach. */
.cookie-notice {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 60;
  background: var(--navy-900);
  color: rgba(244, 241, 232, 0.9);
  border-top: 3px solid var(--accent-rule);
  box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.25);
  font-size: 0.875rem;
}

.cookie-notice-ack .cookie-notice {
  display: none;
}

.cookie-notice__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-4);
}

.cookie-notice__text {
  margin: 0;
  max-width: 80ch;
}

.cookie-notice__link {
  color: var(--accent-on-brand);
  white-space: nowrap;
}

/* Przycisk nie zwęża się poniżej swojej treści, kiedy tekst zawinie się do dwóch wierszy. */
.cookie-notice__ok {
  flex: 0 0 auto;
}

/* Para przycisków w trybie zgody (Akceptuję wszystkie / Tylko niezbędne). Oba są tego samego
   rozmiaru i stoją w jednym rzędzie – odmowa nie może być trudniejsza od zgody. Na wąskim ekranie
   zawijają się razem, a nie jeden pod drugi w przypadkowym miejscu. */
.cookie-notice__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
}

/* --- Ścieżka oceniania pracy (karta zadania w panelu uczestnika) ---------------------------- */
/* Cztery kroki procedury w jednym wierszu. Kolor nie jest jedynym nośnikiem stanu: krok miniony
   ma wypełnioną kropkę, bieżący – obwódkę i pogrubienie, przyszły – kropkę pustą. Wymóg WCAG
   1.4.1, ale też zwykła czytelność na wydruku i w trybie wysokiego kontrastu. */

.status-track-box {
  margin: var(--sp-3) 0 var(--sp-4);
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.status-track__caption {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--sp-2);
}

.status-track {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Kreska łącząca kroki rysuje się jako tło wiersza, a nie osobny element – dzięki temu zawinięcie
   na wąskim ekranie nie zostawia w powietrzu kreski prowadzącej donikąd. */
.status-track__step {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--muted);
  font-size: 0.95rem;
}

.status-track__dot {
  width: 0.7rem;
  height: 0.7rem;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 2px solid var(--line-strong);
  background: transparent;
}

.status-track__step--done {
  color: var(--ok-ink);
}

.status-track__step--done .status-track__dot {
  border-color: var(--ok);
  background: var(--ok);
}

.status-track__step--current {
  color: var(--ink);
  font-weight: 600;
}

.status-track__step--current .status-track__dot {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.status-track__step--failed {
  color: var(--danger-ink);
  font-weight: 600;
}

.status-track__step--failed .status-track__dot {
  border-color: var(--danger);
  background: var(--danger);
}

.status-track__note {
  margin: var(--sp-3) 0 0;
  font-size: 0.95rem;
  color: var(--ink-soft);
}

.status-track__note--warn {
  color: var(--danger-ink);
}

/* --- Karta informacji zwrotnej (/me/stages/<id>/feedback/) ---------------------------------- */
/* Komentarze recenzentów stoją jeden pod drugim i są oddzielone odstępem, a nie linią: każdy
   z nich jest już ramką (.feedback), a druga ramka wokół nich robiłaby z karty tabelę. */

.feedback-card .feedback + .feedback {
  margin-top: var(--sp-3);
}

/* --- Rubryka oceniania i wątek notatek recenzentów (panel recenzenta) ----------------------- */
/* Kryteria stoją jedno pod drugim jako osobne bloki, a nie w tabeli: każde ma tytuł, opis, pole
   punktów i komentarz, więc wiersz tabeli i tak musiałby się złamać na telefonie. */

.rubric__item {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--sp-3);
  margin: 0 0 var(--sp-3);
}

.rubric__item legend {
  font-weight: 600;
  padding: 0 var(--sp-2);
}

.rubric__item input[type="number"] {
  max-width: 8rem;
}

.notes {
  list-style: none;
  margin: 0 0 var(--sp-4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.notes__item {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--sp-2) var(--sp-3);
  overflow-wrap: anywhere;
}

/* Własny wpis jest wyróżniony akcentem – w wątku bez nazwisk to jedyny sposób, żeby recenzent
   od razu widział, które zdania są jego. */
.notes__item--own {
  border-left-color: var(--accent);
}

.notes__author {
  font-weight: 600;
}

.notes__time {
  margin-left: var(--sp-2);
  font-size: 0.875rem;
  color: var(--muted);
}

.notes__text {
  margin: var(--sp-1) 0 0;
  white-space: pre-wrap;
}

/* --- Podgląd kodu w panelu recenzenta ----------------------------------------------------------
   Listing rozwiązania oddanego jako ``.py``/``.ipynb``: ponumerowane wiersze i podświetlenie
   tych, do których recenzent przypiął uwagę. Wysokość jest ograniczona, bo listing stoi w tej
   samej kolumnie, co podgląd pliku – rozwiązanie na tysiąc wierszy nie może zepchnąć formularza
   oceny poza ekran. */
.code-listing {
  max-height: 60vh;
  overflow: auto;
  margin: 0;
  padding: var(--sp-2) 0;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
  font-size: 0.8125rem;
  line-height: 1.5;
}

.code-line {
  display: flex;
  gap: var(--sp-2);
  padding: 0 var(--sp-2);
}

/* Wiersz z uwagą ma ten sam akcent, co własny wpis w wątku notatek – jeden język wyróżnienia
   w całym panelu recenzenta. */
.code-line--noted {
  background: var(--warn-soft);
  box-shadow: inset 3px 0 0 var(--warn);
}

/* Numer linii jest przyciskiem (wpisuje linię do formularza uwagi), ale ma wyglądać jak numer:
   bez ramki, bez tła, w kolorze tekstu drugoplanowego. Stała szerokość utrzymuje kod w jednej
   kolumnie niezależnie od tego, czy plik ma dziewięć wierszy, czy dziewięćset. */
.code-line__no {
  flex: 0 0 4ch;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  text-align: right;
  cursor: pointer;
  user-select: none;
}

.code-line__text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* --- przełączniki języka i kontrastu w pasku konta -------------------------------------------- */

/* Dwa jednoprzyciskowe formularze POST (a nie odnośniki – zmieniają stan po stronie serwera).
   Mają wyglądać jak reszta paska, więc nie wnoszą własnego marginesu ani tła; ``inline`` z
   komponentu formularzy zdejmuje domyślny odstęp ``<form>``. */
.account-bar__prefs {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}

/* Oba przełączniki są ikonami bez napisu, a ikona jest węższa od przycisku z tekstem – stała
   szerokość minimalna trzyma je w rozmiarze celu dotykowego (WCAG 2.5.8 chce 24 px) i nie pozwala
   pozostałym pozycjom paska przeskoczyć, gdy przycisk zmieni zawartość po kliknięciu. */
.account-bar__prefs button {
  min-width: 2.5rem;
}

/* Przycisk-ikona. ``.btn--ghost`` podkreśla swoją treść, bo jest tam tekstem – pod rysunkiem
   podkreślenie rysowałoby kreskę bez znaczenia, a przy ukrytej etykiecie („visually-hidden”)
   nie ma czego podkreślać. Odstęp też odpada: wewnątrz stoi jedno pudełko, nie dwa. */
.account-bar__btn--icon {
  text-decoration: none;
  gap: 0;
  padding-inline: var(--sp-2);
}

/* Flaga języka docelowego. Obrys idzie ``currentColor``, bo biały pas polskiej flagi na jasnym
   tle (i granatowy Union Jack na granatowym pasku) inaczej zlewa się z tłem; ten sam obrys
   rysuje ikonę w trybie wysokiego kontrastu, w którym kolory flag zostają własne. */
.pref-flag {
  display: block;
  border-radius: 2px;
  box-shadow: 0 0 0 1px currentColor;
}

.pref-icon {
  display: block;
}

/* =================================================================================================
   TRYB WYSOKIEGO KONTRASTU  ``<html data-contrast="high">``
   =================================================================================================

   Włącza go człowiek przełącznikiem „Kontrast” w pasku konta; atrybut na ``<html>`` stawia
   ``apps.accounts.preferences`` (dla konta zapisany w ``UserPreference``, dla gościa w sesji).

   Dlaczego osobny tryb, skoro jest wariant ciemny: ciemny motyw odpowiada na pytanie „jakie
   światło mam w pokoju”, a ten na pytanie „czy w ogóle widzę tę krawędź”. Paleta serwisu jest
   ciepła i niskokontrastowa z premedytacją (papier, granat, ceglany akcent) – ładna, ale przy
   słabym wzroku, na tanim ekranie i w słońcu granica karty po prostu znika.

   Trzy zasady tego trybu:

   - **czerń, biel i żółć**. Trzy kolory zamiast dwudziestu: każdy dodatkowy odcień to kolejna para,
     której kontrast trzeba sprawdzić, a żółć na czerni jest jedynym ostrzeżeniem czytelnym także
     przy deuteranopii (najczęstszej wadzie rozpoznawania barw). Znaczenie niesie **kształt
     i słowo**, kolor jest wzmocnieniem, nie komunikatem – odznaki mają swoje etykiety tekstowe,
   - **każda krawędź jest widoczna**: obramowania idą na 2 px i na kolor tekstu, a cienie znikają.
     Cień jest miękką sugestią granicy i w tym trybie nie niesie nic poza rozmyciem,
   - **fokus widać z drugiego końca pokoju**: gruby, żółty pierścień z czarnym podkładem, ten sam
     na każdym elemencie sterującym. To jest jedyna informacja o tym, gdzie jest klawiatura.

   Nadpisujemy **tokeny**, a nie poszczególne komponenty: dzięki temu tryb obejmuje też arkusze
   dokładane per ekran (``coordinator-tools.css``, ``statistics.css``, ``upload-preview.css``),
   które żadnego własnego koloru nie definiują. Selektor ``:where()`` przy regułach
   komponentowych trzyma ich swoistość nisko, żeby nie wygrywały z regułami stanu (``:disabled``).
   ============================================================================================== */

:root[data-contrast="high"] {
  --bg: #000;
  --bg-muted: #000;
  --surface: #000;
  --surface-2: #000;
  --surface-inset: #1a1a1a;
  --ink: #fff;
  --ink-soft: #fff;
  --muted: #f2f2f2;
  --line: #fff;
  --line-strong: #fff;

  --brand: #000;
  --brand-ink: #fff;
  --brand-soft: #1a1a1a;

  /* Żółć jest w tym trybie jedynym akcentem i jedyną barwą „uwagi”. Na czerni daje kontrast
     ponad 15:1, czyli z zapasem ponad wymaganie AAA (7:1) dla tekstu. */
  --accent: #ffe500;
  --accent-ink: #ffe500;
  --accent-soft: #000;
  --accent-on-brand: #ffe500;
  --accent-rule: #ffe500;

  --ok: #fff;
  --ok-ink: #fff;
  --ok-soft: #000;
  --warn: #ffe500;
  --warn-ink: #ffe500;
  --warn-soft: #000;
  --danger: #ffe500;
  --danger-ink: #ffe500;
  --danger-soft: #000;
  --info: #fff;
  --info-ink: #fff;
  --info-soft: #000;
  --neutral-ink: #fff;
  --neutral-soft: #000;

  --link: #ffe500;
  --link-hover: #fff;
  --focus: #ffe500;

  /* Cień jest miękką sugestią granicy – w tym trybie granicę niesie obramowanie, a rozmycie
     tylko zjadałoby kontrast krawędzi. */
  --shadow-sm: none;
  --shadow: none;
  --shadow-inset: none;
}

/* Tło dokumentu i tekst wprost, bo część elementów dziedziczy je z ``body``, a nie z tokenu. */
:root[data-contrast="high"] body {
  background: var(--bg);
  color: var(--ink);
}

/* Każda krawędź na 2 px i w kolorze tekstu. ``:where`` trzyma swoistość na zerze, więc reguły
   stanu (``:disabled``, ``aria-current``) nadal wygrywają. */
:root[data-contrast="high"] :where(.card, .table, .table td, .table th, .btn, .form input,
.form select, .form textarea, input, select, textarea, .badge, .msg, .notice, .empty) {
  border: 2px solid currentColor;
  border-radius: var(--radius-sm);
  box-shadow: none;
}

/* Odnośniki zawsze podkreślone: w tym trybie kolor nie może być jedyną różnicą między odnośnikiem
   a tekstem (WCAG 1.4.1 – „użycie koloru”). */
:root[data-contrast="high"] a {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* Jeden pierścień fokusu dla wszystkiego, co da się kliknąć albo w co da się wpisać. ``:focus``
   obok ``:focus-visible``, bo niektóre przeglądarki nie uznają kliknięcia myszą w pole tekstowe
   za „visible” – a w tym trybie widoczny kursor klawiatury jest ważniejszy niż estetyka. */
:root[data-contrast="high"] :is(a, button, input, select, textarea, summary, [tabindex]):is(:focus, :focus-visible) {
  outline: 4px solid var(--focus);
  outline-offset: 2px;
  background: #000;
  color: #fff;
}

/* Odznaki i komunikaty: znaczenie niesie tekst, więc tło znika, a zostaje ramka i kolor pisma.
   Żółć rezerwujemy dla ostrzeżeń, odmów i akcentu – reszta jest biała. */
:root[data-contrast="high"] :where(.badge, .msg) {
  background: #000;
  color: #fff;
}

:root[data-contrast="high"] :where(.badge--warn, .badge--danger, .msg-error, .msg-warning) {
  color: #ffe500;
}

/* Obrazki i wykresy zostają bez zmian – ale słupki statystyk i pasek postępu biorą kolor z tokenów,
   więc w tym trybie stają się białe na czarnym torze z białą ramką. To jest zamierzone: wykres
   ma pokazywać proporcję, a liczba stoi obok niego i to ona jest odpowiedzią. */

/* --- komponenty paneli w trybie wysokiego kontrastu ------------------------------------------
 *
 * Komponenty z sekcji 6.12–6.17 biorą kolory z tokenów, więc kolorystycznie są tu obsłużone same
 * z siebie. Zostaje to, czego token nie załatwia: **stan wybrany**. Czip, zakładka i pozycja
 * nawigacji panelu rozróżniają się na ekranie tłem i odcieniem – po sprowadzeniu palety do czerni
 * i bieli oba stany stałyby się identyczne. W tym trybie różnicę niesie więc kształt: odwrócenie
 * (żółte tło, czarne pismo) i grubsza krawędź. Ta sama zasada, co w całym serwisie: kolor jest
 * wzmocnieniem, nie komunikatem.
 */
:root[data-contrast="high"] :where(.chip, .tab, .panel__nav-link, .details, .bulk-bar, .kpi__item) {
  border: 2px solid currentColor;
  background: #000;
  color: #fff;
  box-shadow: none;
}

/* Stan wybrany = odwrócenie kontrastu. Jest to jedyny sposób odróżnienia dwóch czipów, który
   przetrwa monochromatyczny ekran i ustawienie „odwróć kolory” w systemie. */
:root[data-contrast="high"] :where(.chip[aria-current], .chip[aria-pressed="true"],
.panel__nav-link[aria-current]) {
  background: #ffe500;
  color: #000;
  border-color: #ffe500;
  box-shadow: none;
}

:root[data-contrast="high"] :where(.chip[aria-current], .chip[aria-pressed="true"]) .chip__count,
:root[data-contrast="high"] .panel__nav-link[aria-current] .panel__nav-count {
  background: #000;
  color: #ffe500;
}

/* Zakładka aktywna: gruba żółta kreska pod spodem zamiast subtelnego akcentu, plus żółte pismo.
   Sama kreska 3 px na czerni bywa zjadana przez skalowanie ekranu, stąd oba sygnały naraz. */
:root[data-contrast="high"] :where(.tab--active, .tab[aria-current], .tab[aria-selected="true"]) {
  color: #ffe500;
  border-bottom: 4px solid #ffe500;
}

/* Liczba KPI jest w tym trybie żółta, bo to jedyna treść kafelka, którą czyta się z odległości;
   etykieta zostaje biała, żeby żółć nie straciła roli „tu patrz”. */
:root[data-contrast="high"] .kpi__value {
  color: #ffe500;
}

:root[data-contrast="high"] .kpi__label,
:root[data-contrast="high"] .kpi__hint {
  color: #fff;
}

/* Pusty stan i błąd pola: kreska przerywana i cienki pasek boczny giną na czerni, więc jedno
   i drugie dostaje pełną ramkę. Błąd zostaje żółty – to ostrzeżenie, a nie treść. */
:root[data-contrast="high"] .empty {
  border-style: solid;
}

:root[data-contrast="high"] .field__error {
  border: 2px solid #ffe500;
  border-radius: var(--radius-sm);
  background: #000;
  color: #ffe500;
}

/* Pole odrzucone przez walidację: obrys 2 px w żółci zamiast czerwonawego tła, którego w tej
   palecie nie ma. ``:where`` trzyma swoistość na zerze, żeby reguła ``:disabled`` nadal wygrała. */
:root[data-contrast="high"] :where(input, select, textarea)[aria-invalid="true"] {
  border: 2px solid #ffe500;
  background: #000;
  color: #fff;
}

/* Znacznik rozwinięcia ``<details>`` rysują dwie krawędzie ``currentColor`` – w tym trybie
   dziedziczą biel z nagłówka, więc wystarczy zadbać, żeby nagłówek nie miał własnego tła. */
:root[data-contrast="high"] .details__summary:hover {
  background: #000;
  text-decoration: underline;
}

/* Plakietka slidera sponsorów ma stałe jasne tło (``#f6f4ee``) niezależne od motywu – to jest
   zamierzone (znaki partnerów wymagają jasnego tła zawsze), więc w tym trybie dostaje tylko
   wyraźną ramkę, tak jak reszta elementów z tłem stałym (patrz reguła ``.card``/``.badge``
   wyżej). Pierścień fokusu dostaje własny, grubszy wariant zamiast ogólnej reguły
   ``:is(a, …):is(:focus, :focus-visible)`` – ta ogólna maluje czarne tło na samym ``<a>``, które
   plakietka i tak zakrywa swoim tłem, więc bez tej reguły pierścień zlewałby się z jasną ramką. */
:root[data-contrast="high"] .sponsor-slider__frame {
  border: 2px solid #000;
}

:root[data-contrast="high"] .sponsor-slider__link:is(:focus, :focus-visible) {
  outline: 4px solid var(--focus);
  outline-offset: 4px;
  background: none;
}
