/* ==========================================================================
   MaturalneSOS — Design System v2.0 nałożony na Bootstrap 5.3.8
   ==========================================================================
   Źródło wartości: /DESIGN-SYSTEM.md (jedyne źródło prawdy — zmiana wartości
   tokena = zmiana tu ORAZ tam, plus wpis w changelogu DESIGN-SYSTEM.md).

   Ładować w <head> ZARAZ PO bootstrap.min.css, przed jakimkolwiek innym CSS.

   Struktura pliku:
     1. Fonty (General Sans, Inter)
     2. Tokeny design systemu jako custom properties --ds-*
     3. Most do zmiennych Bootstrapa --bs-*
     4. Baza dokumentu (rendering fontu, focus, zaznaczenie, łamanie tekstu)
     5. Typografia — klasy narzędziowe .text-*
     6. Atomy wielokrotnego użytku: przyciski, linki, badge'e, listy, pola
        formularza, karta bazowa, płyty sekcji (slaby)
     7. Mechanizm koloru przedmiotu (12 kolorów, wyłącznie jako akcent)
     8. Skala desktopowa (jeden media query podmieniający same zmienne)

   Komponenty specyficzne dla treści (karta kursu, karta nauczyciela, karta
   opinii, tabela porównawcza, hero, sticky bar, karuzela, stopka) NIE są tu
   budowane — powstają przy pracy nad konkretną sekcją (patrz CLAUDE.md,
   „Pętla pracy nad sekcją"), korzystając z tokenów zdefiniowanych poniżej.
   ========================================================================== */

/* ── 1. Fonty ────────────────────────────────────────────────────────────
   Self-hosted w assets/fonts — bez zapytań do Google Fonts / Fontshare przy
   każdym wejściu (istotne przy 80% ruchu mobilnego). Tylko podzbiory latin +
   latin-ext (pokrywają polskie znaki: ą ć ę ł ń ó ś ź ż) — pełny zestaw
   Cyrillic/Greek/Vietnamese nie jest tu potrzebny (locale: pl-PL).
   General Sans: 400/500/600 (nagłówki, przyciski, tytuły). Waga 700 jest
   zadeklarowana, ale system jej NIE używa (patrz DESIGN-SYSTEM.md →
   „Typography → Wagi"); zostaje wyłącznie jako awaryjny stopień dla logotypu.
   Inter: 400/500/600 (tekst, leady, metadane); 700 tak samo — poza systemem. */
@font-face {
  font-family: "General Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/general-sans-400.woff2") format("woff2");
}
@font-face {
  font-family: "General Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/general-sans-500.woff2") format("woff2");
}
@font-face {
  font-family: "General Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/general-sans-600.woff2") format("woff2");
}
@font-face {
  font-family: "General Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/general-sans-700.woff2") format("woff2");
}

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

:root {

  /* ── 2. Tokeny design systemu ──────────────────────────────────────── */

  /* Marka */
  --ds-color-primary: #0A5CF5;
  --ds-color-primary-hover: #0A4AC4;
  --ds-color-primary-press: #0840A8;
  --ds-color-primary-ink: #0940A9;
  --ds-color-primary-deep: #0A2E9E;
  --ds-color-primary-tint: #EEF3FF;
  --ds-color-primary-soft: #D6E3FF;
  --ds-color-primary-line: #BCD2FD;
  --ds-color-on-primary: #FFFFFF;
  /* Przygaszona biel na ciemnej płycie. Od v2.2 tylko AKCENT OD 21px, nie
     domyślny kolor tekstu wspierającego: na rozjaśnionym `brand-deep` daje
     w najjaśniejszym narożniku Lc 64 (na starym gradiencie 83). Pilnuje tego
     `.slab-deep .text-muted-on-deep` niżej w tym pliku. */
  --ds-color-on-primary-mute: #E8EEFF;
  /* Akcent „słoneczny" NA CIEMNEJ PŁYCIE. Pełny {colors.cta-solar} na
     gradiencie marki daje tylko Lc 63 (a na rozjaśnionym `brand-deep` już 43) —
     jako tekst wypada tam całkowicie. Ten rozjaśniony wariant trzyma Lc 90 na
     `brand` i Lc 67 w najjaśniejszym narożniku `brand-deep`, czyli wystarcza
     na nagłówek od 21px, nie na etykietę 13px. Nadal czyta się jako ten sam
     żółty akcent. */
  --ds-color-on-deep-accent: #FFF2D2;

  /* CTA — dwa i tylko dwa kolory mogą nieść akcję */
  --ds-color-cta-solar: #FFC42E;
  --ds-color-cta-solar-hover: #FFB800;
  --ds-color-cta-solar-press: #EDA800;
  --ds-color-cta-solar-tint: #FFF6DF;
  --ds-color-cta-solar-ink: #6B4B00;
  --ds-color-on-cta-solar: #131A2B;
  --ds-color-cta-blue: #0A5CF5;
  --ds-color-cta-blue-hover: #0A4AC4;
  --ds-color-cta-blue-press: #0840A8;
  --ds-color-on-cta-blue: #FFFFFF;

  /* Płótna i powierzchnie */
  --ds-color-canvas: #FFFFFF;
  --ds-color-canvas-warm: #FCFAF7;
  --ds-color-canvas-soft: #F6F3EE;
  --ds-color-canvas-blue: #EFF4FF;
  --ds-color-surface: #FFFFFF;
  --ds-color-surface-deep: #0A2E9E;
  --ds-color-hairline: #EAE6DF;
  --ds-color-hairline-cool: #E6EAF1;

  /* Tekst — rampa dobrana pod APCA (WCAG 3.0), nie pod WCAG 2.1.
     Wartości Lc na bieli / na kremie {canvas-warm}:
       ink            104 / 101   tekst główny, nagłówki
       ink-secondary   96 /  93   leady, opisy, treść ciągła — domyślny
       ink-muted       93 /  91   metadane od 13px (patrz DESIGN-SYSTEM.md → Kontrast)
       ink-faint       74 /  71   WYŁĄCZNIE nie-tekst: placeholder, stan wyłączony, glif dekoracyjny
     Rampa jest krótka i ciemna świadomie — hierarchię niesie rozmiar i waga,
     nie wyblakły szary. Stronę czytają maturzyści i ich rodzice, często na
     telefonie w świetle dziennym. */
  --ds-color-ink: #131A2B;
  --ds-color-ink-secondary: #333B4A;
  --ds-color-ink-muted: #3B4250;
  --ds-color-ink-faint: #6A7284;

  /* Semantyka — kolor sygnału (wypełnienie, kropka, ramka, ikona) jest oddzielony
     od koloru tekstu. Żywa zieleń #0E9F6E na 13px czyta się jak plama, ale jako
     kropka albo tło ptaszka niesie sygnał lepiej niż jej ciemny wariant — więc
     każdy z trzech kolorów ma dwie postacie i NIE wolno ich mylić:
       *            → wypełnienia, kropki, ramki, ikony (nie-tekst)
       *-ink        → KAŻDY tekst, także glify ✓/✕ i etykiety badge'y
     Warianty -ink trzymają Lc ≥ 80 na własnym tincie i ≥ 86 na bieli. */
  --ds-color-success: #0E9F6E;
  --ds-color-success-ink: #085E41;
  --ds-color-success-tint: #E8F8F2;
  --ds-color-warning: #D97706;
  --ds-color-warning-ink: #7F4504;
  --ds-color-warning-tint: #FEF4E4;
  --ds-color-error: #DC2626;
  --ds-color-error-ink: #921818;
  --ds-color-error-tint: #FDEDED;
  --ds-color-info: #0A5CF5;

  /* Kolory przedmiotów — WYŁĄCZNIE jako akcent, nigdy CTA */
  /* Ta sama zasada co w semantyce: `base` niesie sygnał (pasek, kropka, ikona,
     wypełnienie), `deep` niesie TEKST i trzyma Lc ≥ 80 na własnym tincie oraz
     ≥ 84 na bieli. `base` pozostał żywy — to on odpowiada za rozpoznawalność
     przedmiotu w siatce dwunastu kafli. */
  --ds-subj-polski-pod: #F0517E;      --ds-subj-polski-pod-deep: #8E0B30;      --ds-subj-polski-pod-tint: #FDEAF0;
  --ds-subj-polski-roz: #B01048;      --ds-subj-polski-roz-deep: #8D0B39;      --ds-subj-polski-roz-tint: #FDEAF1;
  --ds-subj-mat-pod: #FFA400;         --ds-subj-mat-pod-deep: #7A4F00;         --ds-subj-mat-pod-tint: #FEF7E9;
  --ds-subj-mat-roz: #B85C00;         --ds-subj-mat-roz-deep: #844200;         --ds-subj-mat-roz-tint: #FEF4E9;
  --ds-subj-angielski: #D42FB8;       --ds-subj-angielski-deep: #841A72;       --ds-subj-angielski-tint: #FBECF8;
  --ds-subj-biologia: #3FAF2A;        --ds-subj-biologia-deep: #236317;        --ds-subj-biologia-tint: #EFFAED;
  --ds-subj-chemia: #8B2BFF;          --ds-subj-chemia-deep: #590CB4;          --ds-subj-chemia-tint: #F2E9FE;
  --ds-subj-wos: #FF5A3C;             --ds-subj-wos-deep: #911600;             --ds-subj-wos-tint: #FEECE9;
  --ds-subj-biznes: #2E2790;          --ds-subj-biznes-deep: #2D2691;          --ds-subj-biznes-tint: #EEEDFA;
  --ds-subj-informatyka: #00B4D8;     --ds-subj-informatyka-deep: #006073;     --ds-subj-informatyka-tint: #E9FBFE;
  --ds-subj-geografia: #0DA678;       --ds-subj-geografia-deep: #076549;       --ds-subj-geografia-tint: #EAFDF7;
  --ds-subj-historia: #5E4632;        --ds-subj-historia-deep: #5E4632;        --ds-subj-historia-tint: #F7F3F0;

  /* Gradienty — zakres odcienia trzymany krótko (błękit → fiolet). Skok do
     magenty czytał się jak domyślny gradient generatora, nie jak decyzja.
     Jasność jest ograniczona od GÓRY przez kontrast: na obu gradientach leży
     biały tekst, a gradient musi trzymać próg na całej długości, nie tylko
     w punkcie startowym. Najjaśniejszy punkt `brand` daje bieli Lc 90.
     `brand-deep` jest jawnym WYJĄTKIEM od progów APCA (patrz DESIGN-SYSTEM.md
     → „Kontrast → Kiedy wolno zejść poniżej progu"): wchodzi w Volt Blue
     i wyżej, więc w najjaśniejszym narożniku daje bieli Lc 75, a
     {colors.on-primary-mute} tylko Lc 64. Konsekwencja jest twarda —
     na `.slab-deep` tekst jest BIAŁY, a `.text-muted-on-deep` schodzi
     do roli akcentu od 21px, nie domyślnego koloru tekstu wspierającego. */
  --ds-gradient-brand: linear-gradient(135deg, #0940A9 0%, #1A48C8 55%, #2F44C4 100%);
  --ds-gradient-brand-deep: linear-gradient(160deg, #0A2E9E 0%, #0A5CF5 60%, #4C6BFF 100%);
  --ds-gradient-solar: linear-gradient(135deg, #FFD24A 0%, #FFC42E 55%, #FFAE00 100%);
  --ds-gradient-word: linear-gradient(96deg, #0A5CF5 0%, #4C6BFF 52%, #6D53FF 100%);
  --ds-gradient-warm-glow: radial-gradient(58% 72% at 50% 0%, #FFF4E3 0%, rgba(252,250,247,0) 72%);
  --ds-gradient-blue-glow: radial-gradient(64% 56% at 86% 6%, #E7EFFE 0%, rgba(252,250,247,0) 72%);

  /* Fonty */
  --ds-font-display: "General Sans", system-ui, sans-serif;
  --ds-font-body: "Inter", system-ui, sans-serif;

  /* Wagi — system kończy się na 600. Nagłówek robi wrażenie rozmiarem
     i ujemnym trackingiem, nie grubością (patrz DESIGN-SYSTEM.md → Wagi). */
  --ds-weight-regular: 400;
  --ds-weight-medium: 500;
  --ds-weight-semi: 600;

  /* Skala typograficzna — wartości MOBILNE (bazowe). Desktop podmienia same
     te zmienne w jednym media query na końcu pliku, więc każdy komponent,
     który czyta --ds-text-*, skaluje się razem z systemem i nie potrzebuje
     własnego breakpointu. */
  --ds-text-display-hero: 29px;
  --ds-text-display-1: 26px;
  --ds-text-h1: 23px;
  --ds-text-h2: 19px;
  --ds-text-h3: 17px;
  --ds-text-title: 15px;
  --ds-text-body-lg: 16px;
  --ds-text-body: 15px;
  --ds-text-body-sm: 14px;
  --ds-text-label: 13px;
  --ds-text-caption: 13px;
  --ds-text-eyebrow: 13px;
  --ds-text-numeral: 32px;
  --ds-text-btn-lg: 16px;
  --ds-text-btn: 15.5px;
  --ds-text-btn-sm: 15px;

  /* Zaokrąglenia — świadomie ciaśniejsze niż w v1.x. Duże promienie (28/40px)
     na małych elementach czytają się jak domyślny „przyjazny" preset;
     krótszy promień daje wrażenie rzeczy narysowanej, nie wygenerowanej. */
  --ds-radius-xs: 6px;
  --ds-radius-sm: 10px;
  --ds-radius-md: 12px;
  --ds-radius-lg: 14px;
  --ds-radius-xl: 18px;
  --ds-radius-xxl: 24px;
  --ds-radius-full: 999px;

  /* Odstępy */
  --ds-space-1: 4px;  --ds-space-2: 8px;   --ds-space-3: 12px;  --ds-space-4: 16px;
  --ds-space-5: 20px; --ds-space-6: 24px;  --ds-space-8: 32px;  --ds-space-10: 40px;
  --ds-space-12: 48px; --ds-space-16: 64px; --ds-space-20: 80px; --ds-space-24: 96px;

  /* Rytm sekcji — jedno miejsce na „ile powietrza ma blok". Wartości dobrane
     pod realny rytm sekcji kursu (32–40px na mobile): sekcja ma się kończyć
     wtedy, gdy kończy się jej treść, a nie zostawiać ekranu pustki. */
  --ds-section-y: 40px;
  --ds-slab-y: 32px;

  /* Cienie — krótkie, nisko kontrastowe, zawsze w dół. Poświata w kolorze
     wypełnienia została ścięta z 50% do ~24%: przy 80% ruchu mobilnego duża
     kolorowa łuna pod przyciskiem czyta się jako rozmycie, nie jako głębia. */
  --ds-shadow-xs: 0 1px 1px rgba(19,26,43,0.04);
  --ds-shadow-sm: 0 1px 2px rgba(19,26,43,0.05), 0 2px 6px -3px rgba(19,26,43,0.08);
  --ds-shadow-md: 0 1px 2px rgba(19,26,43,0.04), 0 8px 18px -10px rgba(19,26,43,0.14);
  --ds-shadow-lg: 0 2px 4px rgba(19,26,43,0.04), 0 20px 36px -18px rgba(19,26,43,0.18);
  --ds-shadow-cta-solar: 0 1px 1px rgba(19,26,43,0.07), 0 5px 12px -5px rgba(180,128,0,0.34);
  --ds-shadow-cta-blue: 0 1px 1px rgba(19,26,43,0.07), 0 5px 12px -5px rgba(10,92,245,0.30);
  --ds-shadow-ring-focus: 0 0 0 3px rgba(10,92,245,0.20);
  --ds-shadow-sticky-bar: 0 -1px 0 rgba(19,26,43,0.06), 0 -8px 22px -14px rgba(19,26,43,0.20);
  --ds-shadow-mark-yes-hi: 0 2px 6px -2px rgba(10,92,245,0.40);
  /* Wewnętrzne światło u góry wypełnienia — to ono, a nie cień, daje
     przyciskowi wypukłość. Zawsze doklejane do box-shadow CTA. */
  --ds-inset-hi: inset 0 1px 0 rgba(255,255,255,0.30);

  /* Ruch — krótszy i mniej sprężysty niż w v1.x. Interfejs ma odpowiadać
     natychmiast; ruch potwierdza akcję, nie odgrywa jej. */
  --ds-duration-fast: 120ms;
  --ds-duration-base: 180ms;
  --ds-duration-slow: 320ms;
  --ds-duration-link: 220ms;
  --ds-ease-out-soft: cubic-bezier(0.2, 0.7, 0.3, 1);
  --ds-ease-spring: cubic-bezier(0.32, 1.2, 0.5, 1);
  --ds-press-scale: 0.985;
  --ds-hover-lift: translateY(-1px);

  /* ── 3. Most do zmiennych Bootstrapa ────────────────────────────────
     Bootstrap 5.3 czyta te zmienne na żywo w utilities (.bg-primary,
     .text-primary, .border-primary, .link-primary, .badge, .alert…) i w
     body/link/radius/shadow. Przyciski (.btn-primary/-secondary itd.) mają
     kolory hover/active zapieczone jako hex w kompilacji Bootstrapa, więc
     te NIE recolorują się przez sam --bs-primary — dlatego CTA budujemy
     jako własne klasy .btn-solar/.btn-blue niżej, zamiast liczyć na
     .btn-primary. */
  --bs-primary: var(--ds-color-primary);
  --bs-primary-rgb: 10, 92, 245;                 /* zsynchronizuj z --ds-color-primary */
  --bs-secondary: var(--ds-color-ink-secondary);
  --bs-secondary-rgb: 51, 59, 74;                /* zsynchronizuj z --ds-color-ink-secondary */
  --bs-success: var(--ds-color-success);
  --bs-success-rgb: 14, 159, 110;
  --bs-warning: var(--ds-color-warning);
  --bs-warning-rgb: 217, 119, 6;
  --bs-danger: var(--ds-color-error);
  --bs-danger-rgb: 220, 38, 38;
  --bs-info: var(--ds-color-info);
  --bs-info-rgb: 10, 92, 245;
  --bs-light: var(--ds-color-canvas-warm);
  --bs-light-rgb: 252, 250, 247;
  --bs-dark: var(--ds-color-ink);
  --bs-dark-rgb: 19, 26, 43;

  --bs-body-color: var(--ds-color-ink-secondary);
  --bs-body-color-rgb: 51, 59, 74;
  --bs-body-bg: var(--ds-color-canvas);
  --bs-body-bg-rgb: 255, 255, 255;
  --bs-body-font-size: var(--ds-text-body);
  --bs-body-font-weight: var(--ds-weight-regular);
  --bs-body-line-height: 1.6;
  --bs-emphasis-color: var(--ds-color-ink);
  --bs-secondary-color: var(--ds-color-ink-muted);

  --bs-border-color: var(--ds-color-hairline);
  --bs-border-color-translucent: var(--ds-color-hairline);

  --bs-border-radius: var(--ds-radius-sm);
  --bs-border-radius-sm: var(--ds-radius-xs);
  --bs-border-radius-lg: var(--ds-radius-md);
  --bs-border-radius-xl: var(--ds-radius-lg);
  --bs-border-radius-xxl: var(--ds-radius-xl);
  --bs-border-radius-pill: var(--ds-radius-full);

  --bs-box-shadow: var(--ds-shadow-md);
  --bs-box-shadow-sm: var(--ds-shadow-sm);
  --bs-box-shadow-lg: var(--ds-shadow-lg);

  --bs-font-sans-serif: var(--ds-font-body);
  --bs-body-font-family: var(--bs-font-sans-serif);

  --bs-link-color: var(--ds-color-primary-ink);
  --bs-link-color-rgb: 9, 64, 169;
  --bs-link-hover-color: var(--ds-color-primary);
  --bs-link-hover-color-rgb: 10, 92, 245;
}

/* ── 4. Baza dokumentu ───────────────────────────────────────────────────
   Detale renderingu opisane w DESIGN-SYSTEM.md → „Craft". Były tam
   udokumentowane od v1.2, ale nigdy nie trafiły do CSS — tu są. */

html {
  -webkit-text-size-adjust: 100%;   /* Android Chrome inaczej powiększa tekst w kolumnach */
  /* Wyłącza natywne "scroll anchoring" przeglądarki (Chrome/Firefox) —
     mechanizm, który przy zmianie layoutu NAD widocznym obszarem sam
     dopisuje kompensację do scrollY, żeby treść "nie skoczyła". Header
     (sections/header.php) zwija .hdr-top z 36px na 0px po przewinięciu
     (assets/css/header.css) — bez tej reguły przeglądarka kompensowała ten
     skok layoutu przesuwając scrollY o te same ~36px, co zbijało wynik
     poniżej progu OFF w js nasłuchującym scrolla (assets/js/header.js),
     zdejmowało klasę .is-scrolled, .hdr-top wracał do 36px, scrollY wracał
     nad próg ON, i tak w kółko — nagłówek "skakał" przy delikatnym scrollu.
     Ustawienie samo na .hdr-root/.hdr-top (kandydat na węzeł kotwicy) NIE
     wystarczyło — zmierzone empirycznie: kompensacja dalej działała, bo
     przeglądarka wybierała inny węzeł kotwicy niżej na stronie. Działa
     dopiero globalnie, na przewijanym elemencie (html). */
  overflow-anchor: none;
}

body {
  /* ss01/cv11 w Inter: jednoznaczna jedynka i prosta „l" — istotne w cenach,
     datach i licznikach, gdzie 1/l/I mylą się przy 12–15px na telefonie. */
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;  /* szara plama tapnięcia psuje żółte CTA na Androidzie */
}

/* Nagłówki zawsze w General Sans i zawsze w wadze 600 — grubszy stopień nie
   istnieje w systemie. `text-wrap: balance` wyrównuje długości linii; bez
   tego przy 21–27px na 360px ostatnia linia zostaje z jednym słowem. */
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: var(--ds-font-display);
  font-weight: var(--ds-weight-semi);
  letter-spacing: -0.02em;
  text-wrap: balance;
}

p, li, dd { text-wrap: pretty; }

/* Wyróżnienie w treści też kończy się na 600 — domyślne `bold` przeglądarki
   to 700 i wciągało do dokumentu czwarty plik fontu tylko po to, żeby pogrubić
   pojedyncze słowo w akapicie. */
b, strong { font-weight: var(--ds-weight-semi); }

a { text-decoration: none; }

/* Jeden pierścień focusu dla całego dokumentu. `:where()` zeruje specyficzność,
   więc komponent z własnym stanem focusu (pola formularza, CTA) nadpisuje go
   bez `!important`. Outline z offsetem działa na każdym tle — sam cień ginie
   na ciemnych płytach. */
:where(a, button, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--ds-color-primary);
  outline-offset: 2px;
}

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

/* Cyfry tabelaryczne — ceny, liczniki, daty i metadane nie skaczą przy
   zmianie wartości i wyrównują się w kolumnach. */
.nums { font-variant-numeric: tabular-nums lining-nums; }

/* ── 5. Typografia — klasy narzędziowe ───────────────────────────────
   Rozmiary czytane ze zmiennych, więc przełączenie na skalę desktopową
   dzieje się w JEDNYM miejscu (sekcja 8), a nie w każdej klasie osobno. */
.text-display-hero { font-family: var(--ds-font-display); font-weight: var(--ds-weight-semi); font-size: var(--ds-text-display-hero); line-height: 1.12; letter-spacing: -0.03em; }
.text-display-1    { font-family: var(--ds-font-display); font-weight: var(--ds-weight-semi); font-size: var(--ds-text-display-1);    line-height: 1.15; letter-spacing: -0.028em; }
.text-heading-1    { font-family: var(--ds-font-display); font-weight: var(--ds-weight-semi); font-size: var(--ds-text-h1);           line-height: 1.2;  letter-spacing: -0.025em; }
.text-heading-2    { font-family: var(--ds-font-display); font-weight: var(--ds-weight-semi); font-size: var(--ds-text-h2);           line-height: 1.25; letter-spacing: -0.02em; }
.text-heading-3    { font-family: var(--ds-font-display); font-weight: var(--ds-weight-semi); font-size: var(--ds-text-h3);           line-height: 1.35; letter-spacing: -0.015em; }
.text-title        { font-family: var(--ds-font-display); font-weight: var(--ds-weight-semi); font-size: var(--ds-text-title);        line-height: 1.4;  letter-spacing: -0.01em; }
.text-body-lg      { font-family: var(--ds-font-body); font-weight: var(--ds-weight-regular); font-size: var(--ds-text-body-lg); line-height: 1.55; }
.text-body         { font-family: var(--ds-font-body); font-weight: var(--ds-weight-regular); font-size: var(--ds-text-body);    line-height: 1.6; }
.text-body-sm      { font-family: var(--ds-font-body); font-weight: var(--ds-weight-regular); font-size: var(--ds-text-body-sm); line-height: 1.5; }
.text-label        { font-family: var(--ds-font-body); font-weight: var(--ds-weight-semi);    font-size: var(--ds-text-label);   line-height: 1.3; }
.text-caption      { font-family: var(--ds-font-body); font-weight: var(--ds-weight-regular); font-size: var(--ds-text-caption); line-height: 1.45; }
.text-numeral-xl   { font-family: var(--ds-font-display); font-weight: var(--ds-weight-semi); font-size: var(--ds-text-numeral); line-height: 1; letter-spacing: -0.03em; font-variant-numeric: tabular-nums lining-nums; }

/* Eyebrow: wersaliki w wadze 600 (nie 700) z szerszym trackingiem. Wersalik
   w ciężkiej wadze przy 11–12px zlepia się w plamę; światło między literami
   robi tu robotę zamiast grubości. */
.text-eyebrow {
  font-family: var(--ds-font-body);
  font-weight: var(--ds-weight-semi);
  font-size: var(--ds-text-eyebrow);
  line-height: 1.2;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ds-color-ink);
}

/* Eyebrow z kreską — redakcyjny sygnał początku bloku (DESIGN-SYSTEM.md →
   Craft → „Rytm dokumentu"). Kreska dziedziczy kolor tekstu. */
.text-eyebrow-rule { display: inline-flex; align-items: center; gap: var(--ds-space-2); }
.text-eyebrow-rule::before {
  content: ""; flex: none; width: 18px; height: 1px;
  background-color: currentColor; opacity: 0.45;
}

/* Wyróżnienia w nagłówkach — dokładnie jedno na nagłówek (patrz DESIGN-SYSTEM.md
   → "Wyróżnienia w nagłówkach"). Domyślny wariant to `.hl-color`; gradient
   jest wariantem rzadkim, maksymalnie raz na stronę. */
.hl-gradient {
  background: var(--ds-gradient-word); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hl-marker {
  /* Marker zaczyna się nieco wyżej niż linia bazowa i jest lekko przechylony
     przez `skew` na tle — ślad pisaka, nie prostokąt. */
  background: linear-gradient(transparent 62%, var(--ds-color-cta-solar-tint) 62%);
  box-decoration-break: clone; -webkit-box-decoration-break: clone; padding: 0 0.08em;
}
.hl-marker-blue {
  background: linear-gradient(transparent 62%, var(--ds-color-primary-soft) 62%);
  box-decoration-break: clone; -webkit-box-decoration-break: clone; padding: 0 0.08em;
}
/* Ręcznie rysowane podkreślenie SVG — <span class="hl-underline">tekst<svg
   viewBox="0 0 200 12" preserveAspectRatio="none"><path d="..."/></svg></span>.
   Path rysuje się przy wejściu elementu w DOM (stroke-dashoffset 300→0). */
.hl-underline { position: relative; display: inline-block; white-space: nowrap; }
.hl-underline svg { position: absolute; left: 0; right: 0; bottom: -0.2em; width: 100%; height: 0.3em; overflow: visible; }
.hl-underline path {
  stroke: var(--ds-color-cta-solar); stroke-width: 4; fill: none; stroke-linecap: round;
  stroke-dasharray: 300; stroke-dashoffset: 300;
  animation: ds-hl-draw 520ms var(--ds-ease-out-soft) 260ms forwards;
}
@keyframes ds-hl-draw { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
  .hl-underline path { animation: none; stroke-dashoffset: 0; }
}

.hl-color { color: var(--ds-color-primary); }
.hl-color.subj-chemia, .hl-color.subj-polski-pod, .hl-color.subj-polski-roz, .hl-color.subj-mat-pod,
.hl-color.subj-mat-roz, .hl-color.subj-angielski, .hl-color.subj-biologia, .hl-color.subj-wos,
.hl-color.subj-biznes, .hl-color.subj-informatyka, .hl-color.subj-geografia, .hl-color.subj-historia {
  color: var(--subj-deep);
}
/* Wariant na bazowym (nie "deep") tonie przedmiotu — do wyróżnień na tle,
   które już jest w tincie tego samego przedmiotu, gdzie deep bywa zbyt blisko
   tła i wyróżnienie ma brzmieć jak sam kolor marki kursu, nie jak tekst. */
.hl-color-base { color: var(--ds-color-primary); }
.hl-color-base.subj-chemia, .hl-color-base.subj-polski-pod, .hl-color-base.subj-polski-roz, .hl-color-base.subj-mat-pod,
.hl-color-base.subj-mat-roz, .hl-color-base.subj-angielski, .hl-color-base.subj-biologia, .hl-color-base.subj-wos,
.hl-color-base.subj-biznes, .hl-color-base.subj-informatyka, .hl-color-base.subj-geografia, .hl-color-base.subj-historia {
  color: var(--subj);
}

/* ── 6. Atomy wielokrotnego użytku ────────────────────────────────────── */

/* Przyciski CTA — jeden Solar na ekran telefonu (patrz CLAUDE.md, reguła 2).
   Wypukłość bierze się z wewnętrznego światła (--ds-inset-hi) i krótkiego
   cienia, nie z kolorowej łuny. */
.btn-solar, .btn-blue, .btn-sec, .btn-outline, .btn-ghost {
  font-family: var(--ds-font-display);
  font-weight: var(--ds-weight-semi);
  font-size: var(--ds-text-btn);
  letter-spacing: -0.005em;
  border-radius: var(--ds-radius-md);
  min-height: 48px;
  padding: 14px 22px;
  transition: background-color var(--ds-duration-base) var(--ds-ease-out-soft),
              transform var(--ds-duration-fast) var(--ds-ease-out-soft),
              box-shadow var(--ds-duration-base) var(--ds-ease-out-soft);
}
/* Stopień CTA nie schodzi poniżej 16px: czarny tekst na żółtym #FFC42E daje
   Lc 75 i to jest sufit tej pary (żółty musi zostać żółty). Przy Lc 75 APCA
   wymaga 16px — dlatego etykieta głównego CTA jest jedynym miejscem, gdzie
   rozmiar wynika z kontrastu, a nie z hierarchii. */
.btn-lg.btn-solar, .btn-lg.btn-blue { font-size: var(--ds-text-btn-lg); padding: 15px 26px; min-height: 52px; }

.btn-solar {
  background-color: var(--ds-color-cta-solar);
  color: var(--ds-color-on-cta-solar);
  border: none;
  box-shadow: var(--ds-shadow-cta-solar), var(--ds-inset-hi);
}
.btn-solar:hover      { background-color: var(--ds-color-cta-solar-hover); color: var(--ds-color-on-cta-solar); transform: var(--ds-hover-lift); }
.btn-solar:active     { background-color: var(--ds-color-cta-solar-press); transform: scale(var(--ds-press-scale)); box-shadow: var(--ds-shadow-xs); }
.btn-solar:focus-visible { box-shadow: var(--ds-shadow-cta-solar), var(--ds-inset-hi), var(--ds-shadow-ring-focus); }

.btn-blue {
  background-color: var(--ds-color-cta-blue);
  color: var(--ds-color-on-cta-blue);
  border: none;
  box-shadow: var(--ds-shadow-cta-blue), var(--ds-inset-hi);
}
.btn-blue:hover  { background-color: var(--ds-color-cta-blue-hover); color: var(--ds-color-on-cta-blue); transform: var(--ds-hover-lift); }
.btn-blue:active { background-color: var(--ds-color-cta-blue-press); transform: scale(var(--ds-press-scale)); box-shadow: var(--ds-shadow-xs); }

.btn-sec {
  background-color: var(--ds-color-canvas);
  color: var(--ds-color-ink);
  border: 1px solid var(--ds-color-hairline);
  box-shadow: var(--ds-shadow-sm);
  min-height: 48px;
}
.btn-sec:hover { border-color: var(--ds-color-ink-faint); color: var(--ds-color-ink); transform: var(--ds-hover-lift); }

.btn-outline {
  background-color: transparent;
  color: var(--ds-color-primary-ink);
  border: 1px solid var(--ds-color-primary-line);
}
.btn-outline:hover { background-color: var(--ds-color-primary-tint); color: var(--ds-color-primary-ink); }

.btn-ghost {
  background-color: transparent;
  color: var(--ds-color-primary-ink);
  border: none;
  border-radius: var(--ds-radius-sm);
  min-height: 44px;
  padding: 10px 14px;
}
.btn-ghost:hover { background-color: var(--ds-color-primary-tint); color: var(--ds-color-primary-ink); }
.btn-ghost.btn-icon { padding: 10px; min-height: 44px; min-width: 44px; }

.btn-sm.btn-solar, .btn-sm.btn-blue, .btn-sm.btn-sec, .btn-sm.btn-outline, .btn-sm.btn-ghost {
  font-size: var(--ds-text-btn-sm); padding: 11px 16px; min-height: 44px;
}

/* CTA główne na mobile domyślnie pełnej szerokości (DESIGN-SYSTEM.md → Przyciski);
   od 1024px wraca do naturalnej szerokości treści. */
.btn-block { display: block; width: 100%; }
@media (min-width: 1024px) {
  .btn-block { display: inline-block; width: auto; }
}

/* Linki tekstowe — linia bazowa widoczna zawsze (link rozpoznawalny bez
   interakcji), warstwa aktywna zamalowuje się od lewej. */
.lnk {
  color: var(--ds-color-primary-ink);
  font-family: var(--ds-font-body);
  font-weight: var(--ds-weight-medium);
  background-image:
    linear-gradient(var(--ds-color-primary), var(--ds-color-primary)),
    linear-gradient(var(--ds-color-primary-line), var(--ds-color-primary-line));
  background-repeat: no-repeat;
  background-position: 0 100%, 0 100%;
  background-size: 0% 1.5px, 100% 1px;
  transition: background-size var(--ds-duration-link) var(--ds-ease-out-soft), color var(--ds-duration-link) var(--ds-ease-out-soft);
}
.lnk:hover { color: var(--ds-color-primary); background-size: 100% 1.5px, 100% 1px; }

.lnk-arrow { color: var(--ds-color-primary-ink); font-family: var(--ds-font-display); font-weight: var(--ds-weight-semi); }
.lnk-arrow .arrow { display: inline-block; transition: transform var(--ds-duration-link) var(--ds-ease-out-soft); }
.lnk-arrow:hover .arrow { transform: translateX(3px); }

/* Baza .badge Bootstrapa. Jego zmienne (--bs-badge-*) są deklarowane na samym
   .badge, nie na :root — most z sekcji 3 tu nie sięga, więc nadpisujemy je tu.
   Bez tego każdy badge bez naszego modyfikatora koloru (np. .subj-badge)
   renderował się w wadze 700 i ciągnął czwarty plik fontu. Świadomie NIE
   ustawiamy tu `text-transform` — wersaliki nadaje dopiero wariant kolorowy,
   bo część badge'y (kicker hero) ma być zdaniowa. */
.badge {
  --bs-badge-font-size: var(--ds-text-eyebrow);
  --bs-badge-font-weight: var(--ds-weight-semi);
  --bs-badge-padding-x: 10px;
  --bs-badge-padding-y: 5px;
  --bs-badge-border-radius: var(--ds-radius-full);
}

/* Warianty kolorystyczne. Tekst zawsze w wariancie -ink; żywy kolor sygnału
   zostaje tylko tam, gdzie nie jest literą (kropka „na żywo").
   Padding niesymetryczny: wersaliki z trackingiem zostawiają puste światło po
   ostatniej literze, więc prawa strona dostaje 1px mniej. */
.badge-solar   { background-color: var(--ds-color-cta-solar-tint); color: var(--ds-color-cta-solar-ink); }
.badge-warn    { background-color: var(--ds-color-warning-tint); color: var(--ds-color-warning-ink); }
.badge-success { background-color: var(--ds-color-success-tint); color: var(--ds-color-success-ink); }
.badge-count   { background-color: var(--ds-color-primary-tint); color: var(--ds-color-primary-ink); }
.badge-live    { background-color: var(--ds-color-error-tint); color: var(--ds-color-error-ink); }
.badge-live::before {
  content: ""; display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background-color: var(--ds-color-error); margin-right: 6px;
  animation: ds-pulse 1.8s infinite;
}
@keyframes ds-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .badge-live::before { animation: none; }
}
.badge-solar, .badge-warn, .badge-success, .badge-count, .badge-live {
  font-family: var(--ds-font-body); font-weight: var(--ds-weight-semi); font-size: var(--ds-text-eyebrow);
  letter-spacing: 0.09em; text-transform: uppercase;
  border-radius: var(--ds-radius-full); padding: 5px 10px 5px 11px;
}

/* Baner statusu — pasek na całą szerokość KOLUMNY TREŚCI (nie strony), nad
   tytułem sekcji. Dziś jedyne użycie: sections/potwierdzenie_status.php,
   stan „nie ma czego potwierdzić" na /potwierdzenie-zapisu.php — treść tam
   jest wyliczana po stronie serwera (status transakcji imoje), nie whitelistą
   kodu z adresu, więc to nie ten sam mechanizm co .page-banner niżej. Litera
   zawsze w wariancie -ink, nigdy w żywym kolorze sygnału (reguła 12). */
.status-banner {
  margin: 0 0 var(--ds-space-5);
  padding: 12px 14px;
  border-radius: var(--ds-radius-lg);
  font-size: var(--ds-text-body);
  font-weight: var(--ds-weight-semi);
  line-height: 1.45;
}
.status-banner-error { background-color: var(--ds-color-error-tint);   color: var(--ds-color-error-ink); }
.status-banner-ok    { background-color: var(--ds-color-success-tint); color: var(--ds-color-success-ink); }

/* Baner globalny (błąd/sukces) po przekierowaniu z backendu (?error=<kod>,
   ?success=<kod>) — wspólny atom renderowany przez sections/message_banner.php,
   PRZED sections/header.php, na KAŻDEJ stronie serwisu (whitelist kod → tekst
   w classes/Messages.php, nigdy surowy $_GET). Inny komponent niż
   .status-banner wyżej: ten stoi na samej górze strony (nie kolumny treści)
   i jest pełną, żywą płytą na całą szerokość viewportu, nie tintem — stąd
   biały tekst na wariancie -ink koloru (mocniejszy odpowiednik tintu),
   zamiast tint + -ink jak w .status-banner. Kontrast biały/success-ink:
   Lc 91 — nad progiem 80 wymaganym dla 15px. */
.page-banner {
  width: 100%;
}
/* #e74c3c na życzenie Tomka, zamiast --ds-color-error-ink (ten token
   zostaje bez zmian — używany też jako kolor TEKSTU w kilku miejscach,
   np. .badge-live, .invalid-feedback; zmiana jego wartości zmieniłaby
   te miejsca "przy okazji"). Świadome odstępstwo od progu kontrastu
   (CLAUDE.md → reguła 11): biały/#e74c3c liczone w APCA daje Lc ~70,
   czyli 10 punktów poniżej progu 80 wymaganego dla 15px (na desktopie,
   gdzie tekst rośnie do 16px, próg to 75 — 5 punktów zapasu). To pasek
   statusu, nie ścieżka zakupu (CTA/cena/termin/formularz/przypis) —
   sprawdzone na 360px, tekst czytelny mimo niższego kontrastu. */
.page-banner-error   { background-color: #e74c3c; }
.page-banner-success { background-color: var(--ds-color-success-ink); }
.page-banner-primary { background-color: var(--ds-color-primary); }
.page-banner__text {
  max-width: 1320px;
  margin: 0 auto;
  padding: var(--ds-space-3) var(--ds-space-4);
  color: #fff;
  font-size: var(--ds-text-body);
  font-weight: var(--ds-weight-semi);
  line-height: 1.45;
  text-align: center;
}
@media (min-width: 1024px) {
  .page-banner__text { padding-inline: var(--ds-space-5); }
}

/* ── Rotator komunikatów dowodu społecznego (.csb-rot) ────────────────────
   Przełącza kilka wariantów tekstu w tej samej linijce bez skoku layoutu.
   Pierwszy host: sections/course_last60mins_views.php (pasek nad sticky
   price barem kursu, do 1279px) + .csb-proof w sections/course_stickybar.php
   (od 1280px, ten sam markup $clvRotatorHtml wstawiony po raz drugi) —
   oba czytane i synchronizowane przez assets/js/course_stickybar.js. Drugi
   host: sections/zapis_form.php (przyklejone CTA na dole strony zapisowej,
   mobile), sterowany przez assets/js/course_last60mins_views.js. Stopień
   pisma i kolor tekstu zostają hostowi — ten atom daje tylko grid, widoczność
   i animację wjazdu/zjazdu.

   Grid ze wszystkimi wariantami w tej samej komórce (1/1): wysokość/szerokość
   bierze się z największego wariantu i nic nie skacze przy podmianie.

   Podmiana to przewijanie w pionie: schodzący komunikat odjeżdża o 7px w górę
   i gaśnie (120 ms), wchodzący startuje 120 ms później, podnosząc się o 7px
   z dołu (180 ms). Razem 300 ms, pod sufitem 350 ms z CLAUDE.md; oba ruchy
   wyłącznie na transform i opacity.

   KTÓRY wariant jest widoczny rozstrzyga visibility po klasie .is-active,
   nie animacja — stan bazowy aktywnego komunikatu jest „w pełni widoczny",
   a animacja siedzi w osobnych klasach .is-entering/.is-leaving, zdejmowanych
   licznikiem w JS po 360 ms (zamiast animationend, które klatki wstrzymane
   w tle mogłyby nie wysłać). Najgorszy możliwy przypadek to więc podmiana
   bez animacji, nigdy pusta albo podwójna linijka. Dodatkowy zysk z
   visibility: schowany wariant wypada z drzewa dostępności. */
.csb-rot {
  display: grid;
  justify-items: center;
}
.csb-rot-item {
  grid-area: 1 / 1;
  visibility: hidden;
}
.csb-rot-item.is-active {
  visibility: visible;
}
.csb-rot-item.is-entering {
  /* backwards: przez pierwsze 120 ms (opóźnienie) element trzyma klatkę "from",
     czyli jest jeszcze niewidoczny — inaczej mignąłby w pełni na wierzchu
     komunikatu, który dopiero odjeżdża. */
  animation: csb-rot-in var(--ds-duration-base) var(--ds-ease-out-soft) var(--ds-duration-fast) backwards;
}
.csb-rot-item.is-leaving {
  visibility: visible;
  animation: csb-rot-out var(--ds-duration-fast) var(--ds-ease-out-soft) forwards;
}
@keyframes csb-rot-in {
  from { opacity: 0; transform: translateY(7px); }
}
@keyframes csb-rot-out {
  to { opacity: 0; transform: translateY(-7px); }
}
/* Globalna reguła prefers-reduced-motion niżej w tym pliku skraca czas
   trwania animacji, ale nie rusza animation-delay — bez tego linijka byłaby
   przy wyłączonych animacjach pusta przez te 120 ms opóźnienia. */
@media (prefers-reduced-motion: reduce) {
  .csb-rot-item.is-entering { animation-delay: 0s; }
}

/* Listy z markerem */
.list-check, .list-check-no, .list-num { list-style: none; padding-left: 0; }
.list-check li, .list-check-no li, .list-num li { display: flex; align-items: flex-start; gap: var(--ds-space-3); }
.list-check li::before {
  content: "✓"; flex: none; width: 20px; height: 20px; border-radius: 50%;
  background-color: var(--ds-color-success-tint); color: var(--ds-color-success-ink);
  display: flex; align-items: center; justify-content: center; font-size: 13px;
}
.list-check-no li::before {
  content: "✕"; flex: none; width: 20px; height: 20px; border-radius: 50%;
  background-color: var(--ds-color-error-tint); color: var(--ds-color-error-ink);
  display: flex; align-items: center; justify-content: center; font-size: 13px;
}
.list-num { counter-reset: ds-num; }
.list-num li::before {
  counter-increment: ds-num; content: counter(ds-num);
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  background-color: var(--ds-color-primary-tint); color: var(--ds-color-primary-ink);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--ds-text-label); font-weight: var(--ds-weight-semi);
}

/* Pola formularza (nadpisuje .form-control / .form-select Bootstrapa).
   16px to jedyny rozmiar w systemie, który NIE zjechał w dół razem ze skalą —
   poniżej tego iOS przybliża widok przy focusie i użytkownik wypada z układu. */
.form-control, .form-select {
  min-height: 50px;
  padding: 14px 15px;
  font-size: 16px;
  border: 1px solid var(--ds-color-hairline-cool);
  border-radius: var(--ds-radius-sm);
}
.form-control:focus, .form-select:focus {
  border-color: var(--ds-color-primary);
  box-shadow: var(--ds-shadow-ring-focus);
}
.form-control.is-invalid, .was-validated .form-control:invalid {
  border-color: var(--ds-color-error);
}
/* Komunikat błędu to tekst — wariant -ink, nie kolor sygnału. */
.invalid-feedback, .form-text.is-error { color: var(--ds-color-error-ink); }

/* Karta bazowa (nadpisuje .card Bootstrapa) */
.card {
  background-color: var(--ds-color-surface);
  border: 1px solid var(--ds-color-hairline);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-sm);
}
.card.card-hoverable {
  transition: transform var(--ds-duration-base) var(--ds-ease-out-soft), box-shadow var(--ds-duration-base) var(--ds-ease-out-soft), border-color var(--ds-duration-base) var(--ds-ease-out-soft);
}
.card.card-hoverable:hover { transform: var(--ds-hover-lift); box-shadow: var(--ds-shadow-md); border-color: var(--ds-color-hairline-cool); }

/* Płyty sekcji ("slaby") — maks. dwie ciemne (.slab-deep) na stronę */
.m-sec { background-color: var(--ds-color-canvas); padding: var(--ds-section-y) 16px; }
.slab { border-radius: var(--ds-radius-xxl); margin-inline: 10px; }
.slab-warm { background-color: var(--ds-color-canvas-warm); padding: var(--ds-slab-y) 16px; }
.slab-blue { background-color: var(--ds-color-canvas-blue); padding: var(--ds-slab-y) 16px; }
.slab-deep {
  background: var(--ds-gradient-brand-deep);
  color: var(--ds-color-on-primary);
  padding: var(--ds-slab-y) 16px;
}
/* Przygaszona biel na płycie ma sens dopiero od 21px. Od v2.2 gradient wchodzi
   w Volt Blue i wyżej, więc w najjaśniejszym narożniku #E8EEFF daje tylko Lc 64
   (było 83 na #0D37BB) — przy progu 75 dla 16px to niedobór 11 punktów, czyli
   POZA dopuszczalnym zapasem 10 punktów z reguły odstępstw. Przy 21px próg
   spada do 65 i para wraca na styk. Dlatego cała dolna połowa skali — łącznie
   z `body-lg` i `title`, które przed v2.2 jeszcze się łapały — wraca do bieli:
   hierarchię niesie wtedy rozmiar, nie wygaszenie.
   Uwaga: `.text-body` na ciemnym i tak przegrywa z Bootstrapowym `.text-body`
   (ma `!important`) — na płytach używaj `.text-body-lg` / `.text-body-sm`. */
.slab-deep .text-muted-on-deep { color: var(--ds-color-on-primary-mute); }
.slab-deep .text-caption.text-muted-on-deep,
.slab-deep .text-body-sm.text-muted-on-deep,
.slab-deep .text-body-lg.text-muted-on-deep,
.slab-deep .text-title.text-muted-on-deep,
.slab-deep .text-label.text-muted-on-deep,
.slab-deep .text-eyebrow.text-muted-on-deep { color: var(--ds-color-on-primary); }

/* ── 7. Mechanizm koloru przedmiotu ──────────────────────────────────
   Kolor przedmiotu to WYŁĄCZNIE akcent (pasek, ikona, kropka, badge, tło
   hero landingu) — nigdy CTA. Dodaj modyfikator `.subj-<klucz>` do kontenera
   komponentu (np. `<div class="course subj-chemia">`); komponent czyta
   lokalne zmienne --subj/--subj-deep/--subj-tint zamiast wpisywać hex.
   Docelowo o wybór klasy decyduje metoda klasy PHP `Course::classes()` —
   patrz CLAUDE.md, sekcja "Klasy CSS". Dodajesz nowy przedmiot? Dopisz go
   tu ORAZ w DESIGN-SYSTEM.md. */
.subj-polski-pod  { --subj: var(--ds-subj-polski-pod);  --subj-deep: var(--ds-subj-polski-pod-deep);  --subj-tint: var(--ds-subj-polski-pod-tint); }
.subj-polski-roz  { --subj: var(--ds-subj-polski-roz);  --subj-deep: var(--ds-subj-polski-roz-deep);  --subj-tint: var(--ds-subj-polski-roz-tint); }
.subj-mat-pod     { --subj: var(--ds-subj-mat-pod);     --subj-deep: var(--ds-subj-mat-pod-deep);     --subj-tint: var(--ds-subj-mat-pod-tint); }
.subj-mat-roz     { --subj: var(--ds-subj-mat-roz);     --subj-deep: var(--ds-subj-mat-roz-deep);     --subj-tint: var(--ds-subj-mat-roz-tint); }
.subj-angielski   { --subj: var(--ds-subj-angielski);   --subj-deep: var(--ds-subj-angielski-deep);   --subj-tint: var(--ds-subj-angielski-tint); }
.subj-biologia    { --subj: var(--ds-subj-biologia);    --subj-deep: var(--ds-subj-biologia-deep);    --subj-tint: var(--ds-subj-biologia-tint); }
.subj-chemia      { --subj: var(--ds-subj-chemia);      --subj-deep: var(--ds-subj-chemia-deep);      --subj-tint: var(--ds-subj-chemia-tint); }
.subj-wos         { --subj: var(--ds-subj-wos);         --subj-deep: var(--ds-subj-wos-deep);         --subj-tint: var(--ds-subj-wos-tint); }
.subj-biznes      { --subj: var(--ds-subj-biznes);      --subj-deep: var(--ds-subj-biznes-deep);      --subj-tint: var(--ds-subj-biznes-tint); }
.subj-informatyka { --subj: var(--ds-subj-informatyka); --subj-deep: var(--ds-subj-informatyka-deep); --subj-tint: var(--ds-subj-informatyka-tint); }
.subj-geografia   { --subj: var(--ds-subj-geografia);   --subj-deep: var(--ds-subj-geografia-deep);   --subj-tint: var(--ds-subj-geografia-tint); }
.subj-historia    { --subj: var(--ds-subj-historia);    --subj-deep: var(--ds-subj-historia-deep);    --subj-tint: var(--ds-subj-historia-tint); }

/* Akcenty gotowe do użycia wewnątrz kontenera .subj-* */
.subj-accent-bar { background-color: var(--subj); }
.subj-badge { background-color: var(--subj-tint); color: var(--subj-deep); }
.subj-icon-tile { background-color: var(--subj-tint); color: var(--subj-deep); }
.subj-dot { background-color: var(--subj); border-radius: 50%; }

/* Kafel ikony (40px, rounded.sm) — obok tintu dostaje ledwie widoczny pierścień
   w kolorze przedmiotu, bez niego jasne tinty rozpływają się na bieli.
   Modyfikator .icon-tile-sm (34px, rounded.xs) do gęstszych siatek (megamenu). */
.icon-tile {
  width: 40px; height: 40px; border-radius: var(--ds-radius-sm);
  display: grid; place-items: center; flex: none;
  box-shadow: inset 0 0 0 1px rgba(19, 26, 43, 0.06);
}
.icon-tile > svg { width: 22px; height: 22px; opacity: 0.92; }
.icon-tile-sm { width: 34px; height: 34px; border-radius: var(--ds-radius-xs); }
.icon-tile-sm > svg { width: 19px; height: 19px; }

/* Kontener strony — Bootstrap .container-fluid ma padding 12px stałe
   (--bs-gutter-x / 2); nasz layout wymaga 16px na mobile i 20px od 1024px
   (DESIGN-SYSTEM.md → Struktura strony). Współdzielony przez wszystkie sekcje. */
.container-ms { width: 100%; max-width: 1320px; margin-inline: auto; padding-inline: var(--ds-space-4); }
@media (min-width: 1024px) { .container-ms { padding-inline: var(--ds-space-5); } }

/* Placeholder obrazu — świadomie „szkicowy", żeby nikt nie pomylił go z gotową
   grafiką (patrz CLAUDE.md → "Otwarte punkty" → Grafiki). Raster ukośny +
   podpis zdaniowy. W produkcji zastąpić <img>. */
.ph {
  position: relative; display: grid; place-items: center; text-align: center; padding: 10px;
  background:
    repeating-linear-gradient(-45deg, rgba(10, 92, 245, 0.045) 0 1px, transparent 1px 9px),
    linear-gradient(150deg, var(--ds-color-canvas-soft) 0%, var(--ds-color-primary-tint) 100%);
  color: var(--ds-color-ink-muted); font-size: 12px; font-weight: var(--ds-weight-medium);
  box-shadow: inset 0 0 0 1px rgba(19, 26, 43, 0.05);
}

/* Ocena w gwiazdkach — 5 gwiazdek stałych (bez JS), kolor cta-solar. Rozmiar
   nadawany przez kontekst (np. .trust-row .stars). */
.stars { display: flex; gap: 2px; color: var(--ds-color-cta-solar); }
.stars svg { width: 15px; height: 15px; }

/* Wiersz zaufania pod CTA hero — gwiazdki + ocena + dowód społeczny
   (DESIGN-SYSTEM.md → "Komponenty sygnaturowe" → trust-row). */
.trust-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; color: var(--ds-color-ink-secondary); }
.trust-row .stars svg { width: 13px; height: 13px; }
.trust-row-dot { width: 3px; height: 3px; border-radius: 50%; background-color: var(--ds-color-ink-faint); flex: none; }

/* Stos awatarów zachodzących na siebie — dowód „dołączyło już wielu
   kursantów". Domyślnie inicjały (span z samym tekstem) tam, gdzie
   jeszcze nie ma realnych zdjęć; `<img>` w środku wypełnia całe kółko
   (patrz reguła niżej) tam, gdzie już są (np. course_hero.php, zdjęcia
   z /images/opinions/). */
.avatar-stack { display: flex; }
.avatar-stack > span {
  width: 26px; height: 26px; border-radius: var(--ds-radius-full);
  border: 2px solid var(--ds-color-canvas); margin-inline-start: -8px;
  display: grid; place-items: center; flex: none;
  font-family: var(--ds-font-body); font-size: 10.5px; font-weight: var(--ds-weight-semi); color: var(--ds-color-ink-muted);
  background-color: var(--ds-color-canvas-soft);
  overflow: hidden;
}
.avatar-stack > span:first-child { margin-inline-start: 0; }
.avatar-stack > span img { width: 100%; height: 100%; object-fit: cover; }

/* Wariant ciasny — wiersze zaufania obok innej treści (np. hero kursu), gdzie
   awatary są drugorzędne wobec oceny/gwiazdek i mają zajmować mniej miejsca. */
.avatar-stack-sm > span {
  width: 21px; height: 21px; margin-inline-start: -9px; font-size: 9px; border-width: 1.5px;
}
.avatar-stack-sm > span:first-child { margin-inline-start: 0; }

/* ── 8. Skala desktopowa ─────────────────────────────────────────────────
   Jeden media query na cały system: podmienia wyłącznie zmienne, więc
   wszystko (klasy .text-*, komponenty sekcji czytające --ds-text-*) skaluje
   się razem. 1024px to breakpoint "desktop" z DESIGN-SYSTEM.md, celowo inny
   niż siatka Bootstrapa (lg=992px), która nadal służy do układu kolumn.
   Rozmiary rosną WYŁĄCZNIE u góry skali — body/label/caption zostają takie
   same, bo długość linii na desktopie i tak jest ograniczona kontenerem. */
@media (min-width: 1024px) {
  :root {
    --ds-text-display-hero: 46px;
    --ds-text-display-1: 38px;
    --ds-text-h1: 31px;
    --ds-text-h2: 23px;
    --ds-text-h3: 18px;
    --ds-text-body-lg: 17px;
    --ds-text-body: 16px;
    --ds-text-numeral: 44px;

    --ds-section-y: 72px;
    --ds-slab-y: 56px;
  }
}

/* Redukcja ruchu — globalnie, nie per-komponent. */
@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;
  }
}
