@charset "UTF-8";
/*
 * ==========================================================================
 * 3DMENU - WITRYNA OFERTOWA (start / oferta / wdrozenie / kontakt)
 * ==========================================================================
 * SPIS TRESCI:
 *  1. Zmienne i system projektowy
 *  2. Reset i podstawy
 *  3. Uklad: tlo strony, pasma, kontener, dostepnosc
 *  4. Preloader
 *  5. Animacje
 *  6. Przyciski i pigulki
 *  7. Naglowek i nawigacja
 *  8. Naglowek podstrony
 *  9. Robot dekoracyjny
 * 10. Hero - kolumna tekstowa
 * 11. Hero - scena dania (karuzela, panel demo, podglad 3D)
 * 12. FAQ
 * 13. Tabele
 * 14. Cennik
 * 15. Wdrozenie - kroki
 * 16. Kontakt - dane i formularz
 * 17. Dokument prawny
 * 18. Stopka
 * 19. Sekcje tresci
 * 20. Okno modalne
 * 20a. Skala 1:1 - nagranie z telefonu (sekcja .proof)
 * 21. Kalkulator wyceny
 * 22. Zapytania medialne
 * 23. Strona bledu 404
 *
 * ZASADA - stan faktyczny na 2026-09-09, po audycie:
 *   kolory     - zero wartosci na sztywno ponizej sekcji 1. Wszystkie krycia
 *                ida przez triplety --rgb-*. Trzymac tak dalej.
 *   cienie     - zero na sztywno, komplet w --shadow-* i --glow-*.
 *   odstepy    - w wiekszosci przez --sp-*, ale nie w komplecie.
 *   typografia - NIE JEST doprowadzona: 17 surowych `font-size` i 37 surowych
 *                `line-height` w 13 roznych wartosciach, bez ani jednego
 *                tokena --lh-*. To jest znany dlug, nie stan docelowy.
 *                Nie dokladac kolejnych - przy nowej sekcji siegac po --fs-*.
 * Progi w @media sa wyjatkiem trwalym: CSS nie pozwala uzyc tam custom
 * properties, wiec ich wartosci sa udokumentowane przy --bp-* i musza byc
 * trzymane w parze.
 *
 * KOLORY: projekt z Claude Design opisywal je w oklch(). Tutaj stoja jako sRGB,
 * bo oklch() odpada na starszych WebView na Androidzie, a strona ma dzialac
 * takze tam. Przy kazdej zmiennej jest zrodlowa wartosc oklch z projektu,
 * zeby dalo sie wrocic do oryginalu bez zgadywania.
 * ==========================================================================
 */

/* ==========================================================================
   1. ZMIENNE I SYSTEM PROJEKTOWY
   ========================================================================== */

:root {

    /* --- Skladowe RGB ----------------------------------------------------
       Trzy kolory wystepuja w projekcie kilkanascie razy z roznym kryciem.
       Trzymane jako triplet, zeby polprzezroczyste warianty nizej powstawaly
       z JEDNEJ definicji zamiast kilkunastu recznie wpisanych rgba(). */
    --rgb-accent:       46, 230, 168;   /* #2EE6A8 - zielen marki, wariant jasny */
    --rgb-accent-deep:  22, 193, 137;   /* oklch(72% .152 163) - zielen bazowa   */
    --rgb-text:        234, 245, 238;   /* #EAF5EE - biel tekstu                 */
    --rgb-line:        244, 250, 245;   /* oklch(98% .01 150) - biel krawedzi    */

    /* Tla wystepuja i jako pelny kolor, i jako polprzezroczysta warstwa nad
       zdjeciem - dlatego one takze zaczynaja sie od tripletu. */
    --rgb-bg:            6, 31, 18;    /* oklch(21.5% .04 158)  - tlo strony     */
    --rgb-bg-deep:       2, 18,  9;    /* oklch(16.5% .032 158) - stopka, pas FAQ */
    --rgb-bg-footer:     2, 19,  9;    /* stopka - o wlos jasniej niz pasmo       */
    --rgb-bg-card:       3, 17,  8;    /* oklch(16% .03 158)    - dno kart       */
    --rgb-bg-input:      1, 12,  5;    /* oklch(14% .028 158)   - pola formularza */
    --rgb-error:        248, 113, 113;  /* oklch(70% .18 25)     - komunikat bledu */
    --rgb-gold:        255, 196, 46;    /* #FFC42E - punktory, ptaszki, bledy pol */
    --rgb-bg-pill:      10, 40, 26;    /* oklch(25% .045 160)   - pigulka pakietu */
    --rgb-bg-plan:       8, 29, 18;    /* dno karty cennika                      */
    --rgb-btn-hero:     21, 87, 57;    /* wypelnienie dwoch przyciskow hero       */

    /* --- Tla -------------------------------------------------------------- */
    --c-bg:             rgb(var(--rgb-bg));
    --c-bg-deep:        rgb(var(--rgb-bg-deep));
    --c-bg-footer:      rgb(var(--rgb-bg-footer));

    /* --- Marka ------------------------------------------------------------ */
    --c-accent:         #16C189;    /* oklch(72% .152 163) - zielen bazowa       */
    --c-accent-vivid:   #2EE6A8;    /* akcenty, aktywna pozycja menu             */
    --c-accent-soft:    #3EE6A8;    /* linki, obwodki                            */
    --c-accent-lit:     #9FF2D2;    /* hover na przyciskach obrysowanych         */
    --c-accent-mint:    #4FD694;    /* poczatek gradientu w naglowku hero        */
    --c-accent-lime:    #B9E784;    /* koniec gradientu, wiersz drugi            */
    --c-accent-olive:   #D3E378;    /* poczatek gradientu, wiersz trzeci         */
    --c-gold:           rgb(var(--rgb-gold));  /* punktory, "ptaszki", bledy pol  */
    --c-on-accent:      #07231A;    /* tekst na pelnym zielonym tle              */

    /* --- Tekst ------------------------------------------------------------ */
    --c-text:           #F4F6F0;                        /* oklch(97% .008 120)   */
    --c-text-strong:    #EAF5EE;
    --c-text-nav:       #A2B8A6;                        /* oklch(76% .035 150)   */
    --c-text-88:        rgba(var(--rgb-text), 0.88);
    --c-text-85:        rgba(var(--rgb-text), 0.85);
    --c-text-80:        rgba(var(--rgb-text), 0.80);
    --c-text-78:        rgba(var(--rgb-text), 0.78);
    --c-text-72:        rgba(var(--rgb-text), 0.72);
    --c-text-70:        rgba(var(--rgb-text), 0.70);
    --c-text-60:        rgba(var(--rgb-text), 0.60);
    --c-text-50:        rgba(var(--rgb-text), 0.50);
    --c-text-55:        rgba(var(--rgb-text), 0.55);
    /* UWAGA: liczba w nazwie jest HISTORYCZNA. Krycie podniesione 2026-09-09
       z 0,45 na 0,52, bo 0,45 dawalo kontrast 4,09:1 na tle strony, czyli
       ponizej progu 4,5:1 wymaganego przez WCAG AA dla malego tekstu - a tym
       tokenem pisana jest miedzy innymi klauzula o przetwarzaniu danych przy
       przycisku wysylki formularza. Po zmianie: 5,02:1 na --c-bg i 5,19:1 na
       --c-bg-deep. Roznica optyczna wobec 0,45 jest minimalna. */
    --c-text-45:        rgba(var(--rgb-text), 0.52);
    /* Galka przelacznika w stanie wylaczonym. Wydzielona z --c-text-45 przy
       tamtej zmianie: to element graficzny, nie tekst, wiec proga kontrastu
       dla tekstu nie dotyczy, a jego jasnosc byla dobrana okiem. */
    --c-switch-knob:    rgba(var(--rgb-text), 0.45);
    --c-text-arrow:     #CFEEDE;   /* strzalki panelu demo */
    --c-text-error:     rgb(252, 165, 165);
    --c-text-35:        rgba(var(--rgb-text), 0.35);

    /* --- Krawedzie -------------------------------------------------------- */
    --c-border:         rgba(var(--rgb-line), 0.12);
    --c-border-faint:   rgba(var(--rgb-line), 0.08);  /* kreska nad paskiem stopki */
    --c-overlay:        rgba(var(--rgb-bg-deep), 0.82); /* zaslona okna modalnego */
    --c-border-soft:    rgba(var(--rgb-line), 0.10);
    /* Kreski po bokach nadkreslen ("AR W PRAKTYCE", "FAQ", "OFERTA"...).
       WLASNY token, nie --c-border-soft: tamten niesie takze obrysy kart,
       pol formularza i kresek FAQ, wiec rozjasnienie samych nadkreslen
       przez niego pociagnelo by kilkanascie niepowiazanych rzeczy.
       0,14 zamiast 0,10 - wlasciciel rozjasnil je u siebie i zatwierdzil,
       ta sama wartosc, co kreski miedzy pytaniami FAQ na telefonie. */
    --c-eyebrow-line:   rgba(var(--rgb-line), 0.14);
    --c-border-thin:    rgba(var(--rgb-line), 0.14);
    --c-border-mid:     rgba(var(--rgb-line), 0.18);
    --c-border-strong:  rgba(var(--rgb-line), 0.20);
    /* 0.30 bylo za slabe tam, gdzie za karta swieci poswiata strony: zmierzone
       na ofercie przy 549 px, kontrast gornej krawedzi do tla spadal z +42
       po lewej do +24 po prawej, czyli w najjasniejszym miejscu krawedz
       praktycznie znikala. */
    --c-border-accent:      rgba(var(--rgb-accent), 0.42);
    --c-border-accent-soft: rgba(var(--rgb-accent-deep), 0.32);
    --c-border-accent-dim:  rgba(var(--rgb-accent-deep), 0.20);
    --c-border-error:       rgba(var(--rgb-error), 0.45);
    --c-border-accent-lit:  rgba(var(--rgb-accent-deep), 0.60);
    --c-border-ghost:       rgba(62, 230, 168, 0.40);
    /* Obrys DWOCH przyciskow hero, mocniejszy od reszty przyciskow obrysowanych
       (0,55 zamiast 0,40). Tylko one stoja na zdjeciu, a nie na spokojnym tle
       sekcji - tam wspolne 0,40 gubi sie w kadrze. Reszta zostaje bez zmian,
       zeby nie zmieniac wygladu przyciskow na podstronach. */
    --c-border-btn-hero:    rgba(62, 230, 168, 0.55);
    --c-border-popular:     rgba(62, 230, 168, 0.65);
    --c-border-tabs:        rgba(var(--rgb-accent-deep), 0.19);
    --c-border-focus:       rgba(var(--rgb-accent-deep), 0.55);
    --c-ring-dash:          rgba(62, 230, 168, 0.22);
    --c-gold-line:          rgba(var(--rgb-gold), 0.75);
    --c-border-gold:        rgba(var(--rgb-gold), 0.55);   /* obrys pola z bledem */

    /* --- Szuflada nawigacji: paleta z aplikacji ----------------------------
       Wlasciciel chce, zeby wysuwane menu witryny wygladalo jak menu produktu
       (3dmenu.pl/app/demo). Wartosci przeniesione 1:1 ze static/v1/css/main.css
       aplikacji - w nawiasie nazwa zmiennej po tamtej stronie, zeby dalo sie je
       zsynchronizowac, gdyby tamta paleta kiedys drgnela. */
    --rgb-drawer-bg:      12, 36, 23;              /* #0C2417 = --bg-alt   */
    --c-drawer-bg:        rgb(var(--rgb-drawer-bg));   /* tlo panelu        */
    /* Panel wysuniety jest lekko przezroczysty (polecenie wlasciciela) - stad
       druga postac tego samego koloru. Krycie 0,86: widac, ze pod spodem cos
       jest, a tekst pozycji menu nie traci kontrastu. */
    --c-drawer-glass:     rgba(var(--rgb-drawer-bg), 0.86);
    /* Lista jezykow. W aplikacji jej pudelko NIE MA koloru tla - cala jego
       barwa bierze sie z rozmycia tego, co pod nim, podbitego jasnoscia.
       Stad tutaj tylko obrys i cien; reszta parametrow (promien, dopelnienie,
       szerokosc) stoi przy regule w sekcji 16. */
    --c-lang-border:      #094A2B;   /* --lang-dropdown-border-mobile      */
    /* Zaslona pod lista jezykow. W aplikacji jest CZARNA i rzadsza od naszej
       (--black-alpha-60 zamiast --c-overlay, czyli 0,82 na kolorze tla) - a to
       wlasnie ona decyduje, jak wyglada panel: jego barwa jest rozmyciem tego,
       co pod nim. Przy naszej gestszej zaslonie panel wychodzil ciemny i plaski
       zamiast przezroczystego. */
    --c-scrim-app:        rgba(0, 0, 0, 0.60);   /* --black-alpha-60         */
    --shadow-lang:        0 10px 40px rgba(0, 0, 0, 0.50);  /* --black-alpha-50 */
    --c-drawer-rule:      rgba(214, 233, 223, 0.15);  /* --border-color     */
    --c-drawer-text:      #D6E9DF;   /* --text-primary  - pozycje menu     */

    /* --- Wypelnienia stanow ----------------------------------------------- */
    --c-fill-accent:    rgba(var(--rgb-accent-deep), 0.12);
    --c-fill-hover:     rgba(var(--rgb-line), 0.05);
    --c-fill-thumb:     rgba(var(--rgb-line), 0.04);
    --c-fill-ghost:     rgba(62, 230, 168, 0.12);
    /* Welon pod dwoma przyciskami hero. Prawie przezroczysty i w kolorze, w ktory
       kadr strony glownej i tak jest wtopiony (--c-bg-deep) - chodzi wylacznie
       o to, zeby napis nie lezal bezposrednio na kieliszku i talerzu ze zdjecia.
       Mocniejsze wypelnienie zabiloby forme przycisku, ktora jest "sam obrys". */
    --c-fill-btn-hero:  rgba(var(--rgb-btn-hero), 0.34);

    /* --- Poswiaty i cienie ------------------------------------------------
       Pierwsza rzecz strojona przy zmianie zdjecia w tle - dlatego osobno. */
    /* Sila i zasieg dobrane pomiarem do ORYGINALNEGO projektu z Claude Design
       (claude-design-eksport-export/oferta.html). Wlasciciel zglosil, ze gora
       podstron jest u nas za ciemna. Zmierzone w tych samych punktach kadru
       przy 1440x900 - oryginal / bylo / jest:
         srodek gory   #004127 / #002B1C / #00422B
         prawy gorny   #004A2F / #003620 / #00472C
         przy maskotce #004329 / #002A19 / #003A24
       Nizsze partie kadru pozostaja bez zmian - poswiata ich nie dosiega. */
    --glow-page:        radial-gradient(110% 85% at 68% 4%,
                            rgba(0, 140, 92, 0.52) 0%, transparent 70%);
    /* Slabsza odmiana pod dlugi tekst dokumentow prawnych. */
    --glow-doc:         radial-gradient(90% 45% at 60% 0%,
                            rgba(0, 127, 83, 0.16) 0%, transparent 60%);
    /* Przyciemnienie u samej gory hero. Wlasciciel dobral rgb(9 41 24) - o
       wlos jasniej i bardziej zielono niz tlo strony (--rgb-bg), zeby pas pod
       naglowkiem nie robil sie szary. Wartosc stoi wprost, jak w pozostalych
       skladnikach tej poswiaty. */
    /* Poswiata strony startowej, wartosci dobrane przez wlasciciela. Trzecia
       warstwa nie jest juz swiatlem w lewym dolnym rogu (bylo 80% 70% at 10% 90%),
       tylko wysoka kolumna postawiona na srodku, pod scena.
       UWAGA: komentarz musi stac NAD deklaracja, nie w srodku listy warstw -
       w wartosci zmiennej zostaje w tokenach i trafia do wyliczonego tla. */
    --glow-hero:        linear-gradient(180deg, rgba(9, 41, 24, 0.55) 0%, transparent 26%),
                        radial-gradient(116% 90% at 42% 18%, rgba(0, 107, 69, 0.68) 0%, transparent 60%),
                        radial-gradient(80% 170% at 50% 29%, rgba(15, 80, 37, 0.45) 0%, transparent 65%);
    /* Poswiata pol formularza. Trzy skladniki, kazdy robi co innego:
       - waska aura na zewnatrz: pole "swieci" na tlo, ale nie rozlewa sie,
       - kreska swiatla u gory wewnatrz: krawedz lapie odblask, jak w karcie,
       - delikatne przyciemnienie pod spodem: pole ma glebokosc, nie jest plaskie.
       Wartosci celowo niskie - wlasciciel prosil o efekt hologramu, a nie
       o pole, ktore rzuca sie w oczy bardziej niz tresc. */
    /* Poswiata pod belka naglowka. ZIELONA, nie czarna - i to jest sedno
       zabiegu, nie ozdoba: belka ma nieprzezroczyste tlo --c-bg i gubi sie
       wszedzie tam, gdzie przewinie sie nad ciemniejsza powierzchnia (kadr
       zdjecia, pasmo stopki, ciemny dol karty). Czarny cien nic by tam nie
       dal, bo ciemne na ciemnym nie odcina. Zielona poswiata na zielonym tle
       strony wtapia sie i jest niewidoczna, a na ciemnym robi wyrazna
       krawedz - czyli pojawia sie dokladnie wtedy, kiedy jest potrzebna.
       Przesuniecie w dol i ujemny spread trzymaja ja pod belka, zamiast
       rozlewac na boki. */
    --glow-header:      0 0.5rem 1.5rem -0.5rem rgba(var(--rgb-accent-deep), 0.38);
    --glow-field:       0 0 0.875rem -0.375rem rgba(var(--rgb-accent-deep), 0.30),
                        inset 0 1px 0 rgba(var(--rgb-accent), 0.10),
                        inset 0 -1px 0 rgba(0, 0, 0, 0.22);
    --glow-field-hover: 0 0 1rem -0.3125rem rgba(var(--rgb-accent-deep), 0.42),
                        inset 0 1px 0 rgba(var(--rgb-accent), 0.16),
                        inset 0 -1px 0 rgba(0, 0, 0, 0.22);
    --glow-field-focus: 0 0 1.5rem -0.25rem rgba(var(--rgb-accent-deep), 0.55),
                        inset 0 1px 0 rgba(var(--rgb-accent), 0.26),
                        inset 0 -1px 0 rgba(0, 0, 0, 0.22);

    --glow-stage:       radial-gradient(closest-side,
                            rgba(22, 144, 101, 0.17), rgba(5, 102, 65, 0.07) 55%, transparent 76%);
    --glow-floor:       radial-gradient(closest-side,
                            rgba(19, 158, 111, 0.16), rgba(11, 118, 77, 0.06) 52%, transparent 80%);
    --glow-faq:         radial-gradient(90% 100% at 50% 0%, rgba(0, 88, 56, 0.26) 0%, transparent 70%),
                        radial-gradient(45% 100% at 82% 0%, rgba(0, 107, 69, 0.18) 0%, transparent 68%);
    --glow-step:        radial-gradient(closest-side, rgba(0, 152, 105, 0.12), transparent 74%);
    /* Wnetrze okna kalkulatora. Bylo plaskie --c-bg-deep (2 18 9), czyli
       najciemniejszy kolor w palecie - okno wygladalo przez to ponuro, a jest
       to ekran, na ktorym gosc spedza chwile i liczy pieniadze (zgloszenie
       wlasciciela: "pesymistyczne, ciemne tlo").
       Dwie warstwy w rodzinie, ktora witryna juz zna: poswiata od gory jak
       w pasmie FAQ (--glow-faq) i zielony gradient wzdluz przekatnej jak
       w kartach pakietow. Podklad zostaje nieprzezroczysty, bo przez okno nie
       moze przebijac strona pod nim. */
    --grad-modal-calc:  radial-gradient(120% 70% at 50% 0%,
                            rgba(0, 152, 105, 0.30) 0%, transparent 64%),
                        linear-gradient(165deg,
                            rgba(0, 107, 69, 0.22) 0%,
                            rgba(var(--rgb-bg), 0.90) 58%);
    /* Preloader - wartosci z szablonu 3DMenu. */
    --c-glow-logo:      rgba(34, 197, 94, 0.15);
    --c-particle-shadow: rgba(74, 222, 128, 0.8);

    --shadow-cta:       0 8px 24px rgba(var(--rgb-accent), 0.28);
    --shadow-ring:      0 0 18px rgba(var(--rgb-accent), 0.35);
    /* Zabezpieczenie czytelnosci akapitu hero, ktory stoi wprost na zdjeciu.
       Dwie warstwy, bo jedna nie wystarcza: krotka i gesta odkleja litere od
       tla, dluga i rozmyta przygasza sam kadr wokol bloku tekstu.
       Naglowek hero cienia NIE dostaje - decyzja wlasciciela: przy tym stopniu
       pisma i grubosci 700 broni sie sam. */
    --shadow-text-hero: 0 1px 2px rgba(var(--rgb-bg-deep), 0.55),
                        0 0 14px rgba(var(--rgb-bg-deep), 0.75);

    --shadow-nav:       0 0 14px rgba(var(--rgb-accent), 0.55);
    --shadow-num:       0 0 22px rgba(var(--rgb-accent-deep), 0.50);
    --shadow-tab:       0 0 18px rgba(var(--rgb-accent-deep), 0.25);
    --glow-plan:        0 0 48px rgba(var(--rgb-accent-deep), 0.30);
    /* Okno modalne: zielona aureola karty cennika plus miekki cien pod spodem,
       zeby ramka odklejala sie od zaslony. Bez cienia wewnetrznego - to on
       robil z okna ciezka, przytloczona skrzynke. */
    --shadow-modal:     0 0 48px rgba(var(--rgb-accent-deep), 0.22),
                        0 1.875rem 3.75rem -1.25rem rgba(0, 0, 0, 0.65);
    --shadow-badge:     0 0 24px rgba(var(--rgb-accent), 0.45);
    /* Cien szuflady pada w PRAWO, na zaslonieta strone - stad zerowe
       przesuniecie w pionie. Wartosci jak w aplikacji. */
    --shadow-drawer:    10px 0 30px rgba(0, 0, 0, 0.50);
    /* Przycisk trybu AR w scenie hero - patrz sekcja 11. */
    --shadow-ar:        0 6px 18px rgba(0, 0, 0, 0.40);
    --shadow-panel:     inset 0 22px 44px -22px rgba(0, 0, 0, 0.85);
    --drop-robot:       drop-shadow(0 34px 54px rgba(0, 0, 0, 0.45));
    --drop-robot-near:  drop-shadow(0 26px 44px rgba(0, 0, 0, 0.50));
    /* Maskotka kontaktu na szerokim ekranie. Zmienna, bo ten sam wymiar jest
       SUFITEM w pasmie 561-1080 px (sekcja 22) - wlasciciel nie chce, zeby
       gdziekolwiek byla wieksza niz na desktopie. Wysokosc z proporcji pliku
       robot-kontakt.webp (1016 x 683). */
    --robot-inline-w:   31.75rem;
    --robot-inline-h:   calc(var(--robot-inline-w) * 683 / 1016);

    /* --- Karty ------------------------------------------------------------ */
    --grad-card:        linear-gradient(160deg, rgba(0, 107, 69, 0.10) 0%, rgba(var(--rgb-bg-card), 0.50) 62%);
    /* Karty cennika stoja na zdjeciu tla podstrony, wiec musza miec wlasne, gestsze
   tlo - przy niemal przezroczystym kadr przebijal przez tabele cen. */
    --grad-plan:        linear-gradient(180deg, rgba(var(--rgb-accent), 0.14) 0%, rgba(var(--rgb-bg-plan), 0.78) 60%);
    --grad-plan-pop:    linear-gradient(180deg, rgba(var(--rgb-accent), 0.20) 0%, rgba(var(--rgb-bg-plan), 0.82) 60%);

    /* --- Uklad ------------------------------------------------------------
       Wartosci wziete wprost z projektu, dlatego stoja osobno od skali
       odstepow nizej - zaokraglenie ich do skali przesunieoby kadr. */
    --container-max:    90rem;      /* 1440 px */
    --container-pad:    3rem;       /* 48 px   */
    --header-pad-y:     1.125rem;   /* 18 px   */
    --header-h:         5.0625rem;  /* 18 + 45 + 18 - wysokosc paska naglowka  */
    --logo-h:           2.8125rem;  /* 45 px   */
    /* Minimalny cel dotkniecia wg wytycznych dostepnosci. Ta sama wartosc
       jest szerokoscia hamburgera ORAZ bocznych kolumn paska na telefonie -
       gdyby sie rozjechaly, logo przestaloby stac na osi ekranu. */
    --tap-min:          2.75rem;    /* 44 px   */
    /* Szuflada nawigacji na waskim ekranie. Obie liczby przeniesione wprost
       z aplikacji 3DMenu (static/v1/css/main.css, .mobile-drawer). Wlasciciel
       zdecydowal jednak, ze panel ma zakrywac ekran w calosci, wiec tokeny
       --drawer-w (20.1875rem) i --drawer-max (85vw) zostaly usuniete: obie
       wartosci byly nadpisywane w tym samym bloku @media i nigdy nie doszly
       do skutku. Wartosci do ewentualnego powrotu stoja w komentarzu ROLLBACK
       przy samej regule .site-nav w sekcji 22. */
    /* Lista jezykow zjezdzajaca spod paska. Szerokosc i droga wjazdu wprost
       z aplikacji 3DMenu (.mobile-lang-overlay .lang-dropdown): 230 px panelu
       i 25 px, ktore pokonuje w pionie. */
    --lang-menu-w:      14.375rem; /* 230 px */
    --lang-menu-shift:  1.5625rem; /*  25 px */
    --lang-menu-drop:   0.875rem;  /*  14 px - odstep listy od paska         */

    /* Pigulka jezykow na szerokim ekranie. Wymiary wprost z paska aplikacji
       3DMenu (.lang-switcher-btn w header-top): wysokosc 44 px, 8 px miedzy
       globem, skrotem i strzalka, 4 px dopelnienia po bokach. Odstep od
       przycisku demo to 17 px - tyle, ile tam dzieli glob od koszyka. */
    --lang-pill-h:      2.75rem;   /*  44 px - rowna --tap-min               */
    --lang-pill-gap:    0.5rem;    /*   8 px                                 */
    --lang-pill-cta:    1.0625rem; /*  17 px - odstep od przycisku demo      */
    --lang-caret:       1rem;      /*  16 px                                 */
    --t-caret:          0.3s;
    --ease-caret:       cubic-bezier(0.175, 0.885, 0.32, 1.275);
    /* Zdjecie w tle - patrz body::before. Dwie liczby: ile szerokosci okna
       zajmuje kadr i ponizej jakiej szerokosci przestaje sie kurczyc.
       Strojenie ich zmienia wysokosc kadru na wszystkich podstronach naraz. */
    /* PODGLAD: pochylenie telefonu w sekcji "Skala 1:1" w strone kafelkow.
       Wyzerowanie tej jednej liczby cofa efekt bez ruszania regul. */
    /* Stan SPOCZYNKOWY obudowy telefonu: PROSTO. Kat narasta dopiero pod
       wplywem przewijania - wpisuje go komponent Tilt przy kazdej klatce
       (patrz CONFIG.tilt.maxDeg). Gdy skrypt nie wstanie, telefon po prostu
       stoi prosto. */
    --proof-tilt:        0deg;
    --proof-perspective: 1400px;
    /* Korekta wysrodkowania - te sama wartosc wpisuje Tilt, przeskalowana
       katem. Obrot w perspektywie nie jest symetryczny: krawedz wychodzaca
       do przodu rosnie bardziej, niz kurczy sie cofnieta, wiec rzut obudowy
       przesuwa sie w bok. Zmierzone przy 12 stopniach: srodek telefonu lezal
       4,1 px na lewo od srodka podpisu przy szerokosci 336 px, czyli 1,22 %
       jego wlasnej szerokosci. Przy zerowym kacie nie ma czego korygowac. */
    --proof-tilt-shift:  0%;

    --page-bg-scale: 100%;
    --page-bg-min:   2000px;
    --page-bg-pos:   right top;

    --head-pad-top:     3.375rem;   /* 54 px   */
    --head-pad-bottom:  3.125rem;   /* 50 px   */
    --col-narrow:       0.82fr;     /* lewa kolumna ukladu dwukolumnowego      */
    --col-wide:         1.18fr;     /* prawa kolumna                           */

    /* --- Skala odstepow --------------------------------------------------- */
    --sp-1:  0.25rem;   /*  4 px */
    --sp-2:  0.5rem;    /*  8 px */
    --sp-3:  0.75rem;   /* 12 px */
    --sp-4:  1rem;      /* 16 px */
    --sp-5:  1.375rem;  /* 22 px */
    --sp-6:  1.5rem;    /* 24 px */
    --sp-7:  2rem;      /* 32 px */
    --sp-8:  2.375rem;  /* 38 px */
    --sp-9:  2.875rem;  /* 46 px */
    --sp-10: 4rem;      /* 64 px */
    --sp-11: 5.5rem;    /* 88 px */
    --sp-12: 6.5rem;    /* 104 px */

    /* --- Promienie -------------------------------------------------------- */
    --r-sm:     1rem;       /* 16 px - pola formularza    */
    --r-md:     1.25rem;    /* 20 px - miniatury dan      */
    --r-lg:     1.625rem;   /* 26 px - karty              */
    --r-xl:     2.375rem;   /* 38 px - panel demo         */
    --r-pill:   62.4375rem; /* 999 px                     */
    /* Srednica przycisku AR - 37 px, wartosc dobrana przez wlasciciela
       w inspektorze. Wczesniej rownala sie kolku jezykow i hamburgera
       (--tap-min, 44 px), a jeszcze wczesniej miala 46.
       ZNAK W SRODKU zostaje 22 px i to jest osobna wartosc wlasnie po to:
       zmniejsza sie samo kolko, nie jego zawartosc. Przy 37 px zostaje na
       obwodzie 7,5 px swiatla z kazdej strony.
       UWAGA na przyszlosc: 37 px jest ponizej --tap-min, czyli ponizej
       zalecanego minimum celu dotykowego (44 px). Przycisk stoi w wolnym polu
       nad panelem miniatur, wiec nie ma obok czego trafic przez pomylke,
       ale przy dokladaniu czegokolwiek w tym rogu warto o tym pamietac. */
    --ar-btn-d:      2.3125rem;  /* 37 px */
    --ar-btn-icon:   1.375rem;  /* 22 px - znak w jego srodku      */

    /* --- Typografia -------------------------------------------------------
       clamp() przeniesiony 1:1 z projektu - dolna granica to rozmiar na
       telefonie, gorna na monitorze 1440 px i szerszym. */
    --font-sans:        'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

    --fs-eyebrow:       0.8125rem;  /* 13   px */
    --fs-micro:         0.78125rem; /* 12.5 px */
    --fs-label:         0.84375rem; /* 13.5 px */
    --fs-nav:           clamp(0.9375rem, 1.2vw, 1.09375rem);
    --fs-h1:            clamp(2.25rem, 4vw, 3.5rem);
    --fs-h1-hero:       clamp(2.1875rem, 4.78vw, 4.875rem);
    --fs-h1-sup:        0.494em;  /* "AR" przy "3D" - 34 px przy stopniu 68.8 px */
    --fs-h2:            clamp(1.625rem, 2.5vw, 2.5rem);
    /* Sufit dla tytulu sekcji "Skala 1:1" - patrz .proof__title (sekcja 20a).
       Ten jeden naglowek ma zostac w JEDNYM wierszu (polecenie wlasciciela),
       a przy pelnym --fs-h2 mijal sie z szerokoscia kolumny o 11 px. */
    --fs-h2-proof:      min(var(--fs-h2), 2.4375rem);
    --fs-h3:            2rem;       /* 32   px */
    --fs-price:         clamp(2.75rem, 4.5vw, 3.875rem);
    --fs-lead-hero:     1.1875rem;  /* 19   px */
    --fs-lead:          1.0625rem;  /* 17   px */
    --fs-body:          1rem;       /* 16   px */
    --fs-sm:            0.96875rem; /* 15.5 px */
    --fs-xs:            0.90625rem; /* 14.5 px */

    --ls-tight:        -0.02em;
    --ls-eyebrow:       0.22em;
    --ls-label:         0.20em;
    --ls-cta:           0.10em;

    /* --- Ruch -------------------------------------------------------------- */
    --t-fast:   0.25s;
    --t-preloader: 0.5s;   /* zejscie zaslony - tyle samo, co w szablonie */
    --t-intro:   0.45s;      /* wejscie tla i maskotki na podstronie bez zaslony */
    --t-base:   0.35s;
    --ease:     cubic-bezier(0.4, 0, 0.2, 1);
    /* Wjazd szuflady. Czas i krzywa skopiowane z aplikacji 3DMenu, bo
       wlasciciel chce DOKLADNIE tamten ruch: koncowka krzywej przekracza 1,
       wiec panel wychodzi odrobine za daleko i wraca - to ten "odbity"
       dojazd, ktory odroznia go od zwyklego wysuniecia. */
    /* Pierscien pulsujacy przy przycisku AR. Dlugi cykl jest celowy: znak ma
       zwracac uwage, a nie migac - wartosc z projektu wlasciciela. */
    --t-ar-pulse:  2.4s;
    --t-drawer:    0.4s;
    --ease-drawer: cubic-bezier(0.175, 0.885, 0.32, 1.1);
    /* Zjazd listy jezykow, takze skopiowany z aplikacji. Wejscie jest DLUZSZE
       od wyjscia i idzie po krzywej z mocnym przestrzeleniem (1,56), wiec
       panel wpada z odbiciem; znika juz bez ozdob, samym `ease-in`. */
    --t-lang-in:   0.5s;
    --t-lang-out:  0.2s;
    --ease-lang:   cubic-bezier(0.34, 1.56, 0.64, 1);
    --spin-ring: 120s;      /* obrot przerywanego okregu przy kroku wdrozenia  */
    --float-robot: 4.5s;
    /* Amplitudy ruchu maskotki. Osobno, bo to jedyne wartosci w tym arkuszu,
       ktore dobiera sie okiem, a nie wyliczeniem - i dobiera sie je do
       ROZMIARU konkretnej maskotki (patrz .robot--steps nizej). */
    --lift-robot:   1.125rem;   /* wysokosc unoszenia */
    --jitter-robot: 1px;        /* wychylenie pojedynczego skoku drgan */
    --t-faq:     0.11s;      /* rozwijanie odpowiedzi w FAQ i obrot strzalki   */
    --t-modal:   0.26s;      /* wejscie i zejscie okna modalnego; MUSI byc rowny
                                CONFIG.modal.hideMs w core.js                  */
    /* Krzywa "wystrzel i wyhamuj": prawie cala droga w pierwszej polowie czasu,
       potem lagodne dojscie. Zmierzone od klikniecia: ruch rusza po 26 ms
       i konczy po 109 ms - czyta sie jako natychmiastowe, a mimo to widac
       ruch, a nie przeskok. Przy 180 ms bylo 49 i 166 ms i wlasciciel uznal
       to za powolne. */
    --ease-faq:  cubic-bezier(0.05, 0.9, 0.2, 1);
    --t-thumb-slide: 0.62s;  /* przejazd rzedu miniatur; lustro CONFIG.showcase.slideMs */
    --t-glow:    5s;        /* cykl pulsowania poswiaty CTA i karty cennika    */

    /* --- Wjazd trzech kafelkow "proof" -------------------------------------
       Kola nadlatuja z jednego punktu za prawa krawedzia listy, wiec pierwsze
       ma najdluzsza trase, a ostatnie najkrotsza; ruszaja po kolei.
       Klatki kluczowe, a NIE krzywa typu "back": przy krzywej przestrzal jest
       procentem drogi, a drogi sa tu skrajnie rozne (1360 px wobec 450 px),
       wiec kazde kolo odbijaloby sie inaczej. W klatkach przestrzal jest staly. */
    /* Wjazd trzech kol w sekcji "proof". Animacja odpala sie RAZ, gdy sekcja
       wejdzie w kadr, i leci wlasnym tempem - nie jest przewijana scrollem.
       Wersja sterowana pozycja szarpala, bo kolko myszy przewija skokami po
       kilkadziesiat pikseli i kazdy gosc widzial inna animacje.
       0,7 s na lot i 0,14 s odstepu, czyli caly rzad wchodzi w 0,98 s. */
    --t-reveal:      0.7s;
    --t-reveal-step: 0.14s;
    --ease-reveal-in:   cubic-bezier(0.25, 1, 0.5, 1);
    /* Powrot z przestrzalu. Krzywa lagodna Z OBU STRON i rozlozona na dlugim
       odcinku osi (30 % czasu), zeby kolo DRYFOWALO na miejsce, a nie odbijalo
       sie. Wczesniejsze proby: 1,35rem przy 32 % czasu (za dlugie), 0,6rem
       przy 14 % (za gwaltowne) - w obu cofniecie czytalo sie jako osobny ruch. */
    --ease-reveal-back: cubic-bezier(0.33, 0, 0.67, 1);
    /* Glebokosc przestrzalu. 4 px to tyle, zeby dalo sie go zauwazyc katem oka,
       a nie tyle, zeby byl osobnym zdarzeniem. Musi tez zostac mniejszy niz
       odstep miedzy kolami (--sp-4, 16 px) - inaczej kolo przelatujace w lewo
       weszloby na sasiada, ktory juz stoi. */
    --reveal-overshoot: -0.25rem;

    /* --- Scena 3D ----------------------------------------------------------
       Rozmiary zalezne od widoku, przeniesione z projektu. Trzymane jako
       zmienne, bo kadr sceny stroi sie tu najczesciej. */
    --stage-h:      min(50vw, 53.875rem, 66vh);
    /* Kadr sceny konczy sie TUZ PRZED przerywanym okregiem - tak, zeby obiekt
       mial jak najwiecej miejsca, ale zeby jego krawedz nie wchodzila na okrag.
       Zmierzone przy 1440x900: okrag 657 px, kadr 630 px, czyli 13 px zapasu
       z kazdej strony. */
    --stage-dish:   min(50vw, 53.5rem, 70vh);
    /* Odstep miedzy okregiem a gorna krawedzia pasma to (wysokosc pasma minus
       wysokosc okregu) / 2 - okrag jest w pasmie wysrodkowany, wiec to jedyne,
       co go ustala. Skracanie pasma nie wchodzi w gre: przy 1440x900 o jego
       wysokosci decyduje kolumna tekstu, a nie `min-height` (zmierzone - po
       skroceniu min-height o 3rem pasmo zmalalo tylko z 710 na 690 px).
       Dlatego rosnie okrag: 68vh -> 73vh zmniejsza odstep z 49 na 26 px. */
    /* Srednica kola kroku. Cztery kolumny na szerokim ekranie. */
    --step-d:       clamp(17.5rem, 19.5vw, 20rem);

    /* Wnetrze kola w sekcji "Skala 1:1" - ikona i dopelnienie. Oba PLYNNE
       z tego samego powodu, co --step-d wyzej: kolo ma stala proporcje
       (aspect-ratio: 1), a jego srednica zalezy od szerokosci kolumny -
       zmierzone 246 px przy oknie 1440, ale juz 176 px przy 1081. Tresc miala
       staly wymiar (201 px przy dwuwierszowym napisie), wiec ponizej okolo
       1245 px przestawala sie miescic i kolo rozciagalo sie w owal
       (zgloszenie wlasciciela z 1100 px: +18 px wysokosci).
       Gorne granice to dokladnie te wartosci, ktore obowiazywaly dotad, wiec
       na szerokim ekranie nie zmienia sie nic; schodza dopiero tam, gdzie
       zaczyna brakowac miejsca. */
    --tile-icon:    clamp(3.5rem, 5vw, 4.5rem);
    --tile-pad:     clamp(1rem, 1.5vw, 1.375rem);
    --stage-ring:   min(52vw, 55.625rem, 73vh);
    /* Drugi okrag - TUZ POD przerywanym, nie w polowie promienia. Wzor to
       obwodka ikony w kafelkach "proof": zmierzone tam 1px kreski zewnetrznej,
       6,3 px przerwy i 2,9 px kreski wewnetrznej, jasniejszej od zewnetrznej.
       Przerwa zostaje w wartosci bezwzglednej, a nie w procencie srednicy:
       przeniesiona proporcjonalnie z kola 72 px na kolo 657 px daloby 58 px
       odstepu, czyli dwa osobne okregi zamiast pary linii. */
    --stage-ring-inner-gap: 0.375rem;
    --stage-ring-inner-w:   4px;
    --stage-glow:   min(62vw, 70.5rem, 86vh);
    --stage-lift:   3.75rem;    /* przesuniecie dania w gore wzgledem srodka   */
    /* Kostka ladowania w scenie. Jedna zmienna, bo rozmiar wchodzi w trzech
       miejscach naraz: pudelko, sciany i ich odsuniecie w glab (polowa boku) -
       rozjechanie ich rozklada szescian. Podniesione o 20% z 2,75rem na prosbe
       wlasciciela. */
    --cube-size:    3.3rem;
    /* Zdjecie z Oferty jako faktura w tle okna kalkulatora. Osobno adres
       i krycie, bo krycie stroi sie okiem, a adres zmienia sie razem z kadrem
       podstrony. */
    --modal-photo:       url('../img/bg-oferta.jpg');
    /* Krycie dobrane POMIAREM, nie na oko. Kadr jest bardzo ciemny (najjasniejszy
       pas to rgb(3,45,27)), wiec przy 0,22 zmienial tlo ramki o szesc jednostek
       na kanale zielonym - czyli praktycznie wcale, i dokladnie tak to wygladalo.
       Przy 0,60 probka tla idzie z rgb(44,62,52) na rgb(43,79,64): faktura jest
       czytelna, a liczby po prawej maja dalej spokojne tlo. */
    /* Ile ZDJECIA widac w tle szerokiego okna. Odkad kadr jest podkladem,
       a nie nakladka, ta liczba mowi wprost, jak mocno przebija sie przez
       welon - i to ona decyduje, czy okno jest zielone, czy ciemne. */
    --modal-photo-alpha: 0.55;
    --panel-w:      29.875rem;  /* 478 px - szerokosc panelu demo              */
    --panel-drop:   0rem;       /* miejsce pod scena na panel demo. Zero na    */
                                /* szerokim ekranie, gdzie panel lezy NA       */
                                /* scenie; rosnie ponizej 1080 px.             */
    --thumb-size:   3.625rem;   /* 58 px                                       */
    /* Szerokosc obudowy telefonu w sekcji "Skala 1:1". JEDNA zmienna, bo ta
       sama wartosc jest szerokoscia podpisu pod nim - kreski po jego bokach
       maja konczyc sie rowno z krawedziami obudowy. */
    --phone-w:      min(21rem, 100%);
    --thumb-gap:    0.5625rem;  /* 9 px - odstep miedzy miniaturami            */

    /* --- Warstwy ----------------------------------------------------------- */
    --z-bg:      0;
    --z-content: 4;
    --z-ar:      3;   /* przycisk AR: nad plotnem sceny i nad kostka ladowania */
    --z-robot:   6;
    --z-header:  7;   /* nad maskotka: belka jest sticky i musi ja zaslaniac */
    --z-drawer-scrim: 19;  /* zaslona: pod szuflada, nad cala reszta strony */
    --z-drawer: 20;
    --z-modal:  60;
    --z-preloader: 9999;

    /* --- Progi @media (informacyjnie) --------------------------------------
       CSS nie pozwala uzyc custom property w warunku @media, wiec te wartosci
       sa powtorzone recznie w sekcji 16. Zmiana progu = zmiana w dwoch
       miejscach; ta lista jest zrodlem prawdy.
       --bp-lg: 1080px  uklady dwukolumnowe schodza do jednej kolumny
       --bp-md:  860px  nawigacja zmienia sie w szuflade
       --bp-sm:  560px  zageszczenie ukladu na telefonie                      */
}


/* ==========================================================================
   2. RESET I PODSTAWY
   ========================================================================== */

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

html {
    -webkit-text-size-adjust: 100%;
    /* Skok kotwica ma sie zatrzymac POD belka, nie za nia. Wartosc jest o 20 px
       glebsza niz sama wysokosc belki: przy rownych 84 px tuz nad krawedzia
       sekcji widac bylo jasna kreske grubosci piksela. Skok robiony z kodu
       (Anchors.jump) liczy to osobno, bo `scrollTo` tej wlasciwosci nie widzi. */
    scroll-padding-top: 4rem;
    /* CELOWO bez `scroll-behavior: smooth`: scena hero rysuje 3D w petli, a przy
       animowanym przewijaniu przegladarka interpoluje pozycje na tym samym
       watku i scroll robi sie ciezki.
       BEZ `scroll-padding-top`: naglowek nie jest przyklejony, wiec odjezdza
       razem z trescia, a zapas zostawialby nad kotwica kawalek poprzedniej
       sekcji. */
}

/* Suwak strony zawsze widoczny. Domyslne paski nakladkowe pokazuja sie tylko
   w trakcie przewijania, wiec nie widac, ze strona ma czym jechac dalej;
   podanie wlasnych regul przelacza Chromium na pasek klasyczny. Safari na
   macOS je ignoruje i trzyma sie ustawienia systemu - ograniczenie platformy.
   Zmierzone: sam pasek nie wplywa na plynnosc (to jednorazowe malowanie). */
::-webkit-scrollbar { width: 0.875rem; }

::-webkit-scrollbar-track { background: var(--c-bg-deep); }

::-webkit-scrollbar-thumb {
    background-color: var(--c-border-mid);
    border: 4px solid var(--c-bg-deep);
    border-radius: var(--r-pill);
}

::-webkit-scrollbar-thumb:hover { background-color: var(--c-border-strong); }


/* Poswiaty sceny hero i pasek miniatur celowo wychodza poza obrys swoich
   kontenerow - tak wygladaja w projekcie. Zeby nie wystawialy przy tym
   poziomego suwaka, przycinamy je na poziomie dokumentu.
   `overflow-x: hidden` na samym `body` NIE wystarcza: przewijaniem steruje
   `html`, wiec to ono musi przycinac. `clip` jest wersja wlasciwa - nie robi
   z elementu kontenera przewijania, wiec nie psuje `position: sticky` ani
   przewijania do kotwic; `hidden` zostaje jako wariant zapasowy dla starszych
   przegladarek. */
html,
body { overflow-x: hidden; }

@supports (overflow: clip) {
    html,
    body { overflow-x: clip; }
}

body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background-color: var(--c-bg);
    color: var(--c-text);
    font-family: var(--font-sans);
    font-size: var(--fs-body);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a {
    color: var(--c-accent-soft);
    text-decoration: none;
    transition: color var(--t-fast) var(--ease);
}

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

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

button { cursor: pointer; }

::selection { background: rgba(var(--rgb-accent), 0.3); }

:focus-visible {
    outline: 2px solid var(--c-accent-vivid);
    outline-offset: 3px;
    border-radius: var(--sp-1);
}

/* Element pojawia sie dopiero, gdy JS potwierdzi, ze ma sens (np. przelacznik
   podgladu 3D). Atrybut `hidden` sam w sobie przegrywa z `display:flex`. */
[hidden] { display: none !important; }


/* ==========================================================================
   3. UKLAD: TLO STRONY, PASMA, KONTENER, DOSTEPNOSC
   ========================================================================== */

/* Dwie warstwy tla jako pseudoelementy `body`, a nie osobne <div>-y z projektu:
   to samo malowanie, a w markupie nie zostaja dwa puste wezly, ktore czytnik
   ekranu i tak musi pominac. */
body::before,
body::after {
    content: '';
    position: fixed;
    inset: 0;
    z-index: var(--z-bg);
    pointer-events: none;
}

/* Zdjecie tla podstrony idzie pod calosc strony, razem z pasmem naglowka -
   decyzja wlasciciela. Wysokosc z proporcji (`100% auto`), kotwica u gory,
   nizej samo tlo: rozciagniecie na oba wymiary wyciagalo kadr w pionie przy
   wysokich oknach. Wtopienie w kolor tla jest wypalone w pliku, bo gradient
   w CSS musialby zgadywac, gdzie konczy sie zdjecie. */
body::before {
    /* Kolor pod zdjeciem MUSI byc tym samym, w ktory zdjecie jest wtopione -
       inaczej na dole kadru widac prog. Kazda podstrona podaje go razem ze
       swoim plikiem (--page-fill obok --page-bg), bo to wlasciwosc konkretnego
       obrazu, a nie calej witryny. Zmierzone na dostarczonych plikach: nowe
       kadry koncza sie na #021208, czyli --c-bg-deep. */
    background-color: var(--page-fill, var(--c-bg));

    /* Dwie warstwy: welon NAD zdjeciem (pierwszy w kolejnosci maluje sie
       wyzej) i samo zdjecie. Welon ma kolor wtopienia kadru, wiec plaskiej
       czesci pod zdjeciem nie rusza - tam kolor kladzie sie sam na siebie -
       a samo zdjecie przygasza. Domyslnie zadnego nie ma. */
    background-image: var(--page-veil, none), var(--page-bg, none);

    /* Szerokosc zdjecia NIE schodzi ponizej `--page-bg-min`. Przy samym
       `100% auto` kadr chudnie razem z oknem - przy 1440 px zdjecie oferty ma
       735 px wysokosci zamiast 1021 px, ktore ma przy 2000 px - i pasmo robi
       sie plaskie. Od progu w gore nic sie nie zmienia (100% jest juz wieksze),
       ponizej progu zdjecie zostaje w rozmiarze z progu i jest przycinane
       z LEWEJ, bo kotwica stoi po prawej (PODGLAD - wariant do porownania
       z zakotwiczeniem po lewej; zmiana to jedno slowo nizej). */
    background-size: var(--page-bg-scale) auto;
    background-position: var(--page-bg-pos);
    background-repeat: no-repeat;
}

/* Ponizej progu (`--page-bg-min`) kadr przestaje sie kurczyc razem z oknem:
   bierzemy wieksza z dwoch wartosci - skale strony i sam prog. Od progu w gore
   zostaje sama skala: na podstronach 100 %, na stronie glownej 150 % - tam
   powiekszenie ma obowiazywac na kazdej szerokosci.
   Prog jest wpisany w warunek liczba, bo CSS nie przyjmuje zmiennej w @media -
   przy zmianie `--page-bg-min` trzeba poprawic OBA miejsca. */
@media (max-width: 1999.98px) {
    body::before {
        background-size: max(var(--page-bg-scale), var(--page-bg-min)) auto;
    }
}

/* Welon nad zdjeciem, w kolorze wtopienia kadru. Gestnieje tam, gdzie stoi
   tresc, bo najjasniejsze punkty kadru (kieliszek, swiece, talerz) wypadaja
   wlasnie pod tekstem.
   Stopnie licza sie od SZEROKOSCI ZDJECIA, nie okna: do 2000 px zdjecie ma
   stale 2000 px i stoi przy lewej krawedzi, wiec 64 % to zawsze 1280 px.
   Kolejnosc stopni MUSI rosnac - CSS nie sortuje ich, tylko przycina kazdy
   cofniety do poprzedniego, co daje twarda krawedz zamiast przejscia.
   Tylko powyzej 960 px: nizej kadr wraca do 100 % i tekst schodzi pod niego. */
@media (min-width: 960.02px) {
    body.page-start {
        --page-veil: linear-gradient(to right,
                     rgba(var(--rgb-bg-deep), 0.15) 0%,
                     rgba(var(--rgb-bg-deep), 0.50) 64%,
                     rgba(var(--rgb-bg-deep), 0.40) 76%,
                     rgba(var(--rgb-bg-deep), 0.28) 88%,
                     rgba(var(--rgb-bg-deep), 0.25) 100%);
    }
}

body::after { background: var(--page-glow, var(--glow-page)); }

/* --- Tlo poszczegolnych podstron -----------------------------------------
   Kazda podstrona wnosi wlasne zdjecie przez zmienna, a nie przez regule z
   wpisana sciezka w kilku miejscach. Wariant 1920 px obsluguje monitory,
   wariant 960 px wchodzi ponizej progu telefonu (sekcja 16) - to samo zdjecie
   w rozdzielczosci 4000 px kosztowaloby na telefonie ok. 32 MB pamieci po
   dekompresji, a widac z niego rozmyty gradient. */
/* Zdjecie stoi na dole naglowka strony glownej, a pasmo FAQ zostaje samym
   kolorem. Warstwa kadru przewija sie TU razem ze strona, a nie jest przypieta
   do okna jak na podstronach: pasmo FAQ ma wlasne, nieprzezroczyste tlo, wiec
   przy przewijaniu wjezdzalo na przypiete zdjecie i przecinalo je ruchoma
   krawedzia. `position: relative` na <body> jest po to, zeby warstwa liczyla
   wysokosc od calego dokumentu, a nie od pierwszego ekranu. */
body.page-start { position: relative; }
body.page-start::before { position: absolute; }

/* Tlo strony glownej. Dolna krawedz kadru jest wtopiona w --c-bg-deep juz
   w pliku, tak samo jak w kadrach podstron. Skala 100 %, bo kadr jest szerszy
   niz poprzedni (1,97:1) i przy tej samej szerokosci okna wychodzi wyzszy;
   przy 150 % scena z daniem schodzila pod krawedz ekranu.
   `?v=` przy nazwie: kadr podmienia sie POD TA SAMA nazwa, a .htaccess daje
   obrazkom miesieczny cache - bez tego przegladarka oddaje kopie z dysku. */
.page-start     { --page-glow: var(--glow-hero);
                  /* ROLLBACK: --page-bg: url('../img/bg-faq.jpg'); */
                  /* `?v=` jest tu po to, zeby dalo sie podmienic kadr POD TA SAMA
                     nazwa i zobaczyc go od razu. Bez tego nie widac nowego pliku:
                     .htaccess daje obrazkom `max-age` na miesiac (zalozenie bylo
                     takie, ze podmiana idzie przez nowa nazwe), wiec przegladarka
                     oddaje kopie z dysku i nie pyta serwera. Przy kazdym kolejnym
                     eksporcie tego samego kadru podnosimy numer. */
                  --page-bg:   url('../img/bg-faq-2.jpg?v=11');
                  --page-fill: var(--c-bg-deep);
                  /* Kotwica po LEWEJ - tresc kadru siedzi po tej stronie. W pionie kadr
                     startuje POD belka naglowka: belka jest nieprzezroczysta
                     i zakrywala pierwsze 162 wiersze pliku, a tam siedzi glowa
                     goscia. Pas nad zdjeciem dostaje --page-fill, czyli kolor,
                     ktory i tak jest pod belka. ROLLBACK: `left top`. */
                  /* ROLLBACK: --page-bg-scale: 150%; */
                  --page-bg-scale: 100%;
                  --page-bg-pos:   left var(--header-h); }
/* Kadry pelnowymiarowe to ORYGINALNE pliki JPEG od wlasciciela, bez naszego
   ponownego kodowania - zadnej dolozonej straty. Zmierzone: zrodla maja
   0,13-0,14 bita na piksel, czyli sa juz mocno skompresowane u zrodla, a krok
   przez WebP q95 dokladal 0,3% bledu (RMSE). Skoro roznica w wadze wychodzila
   niewielka, prosciej nie kodowac ponownie wcale. */
/* Przy kazdej podstronie stoi kolor, w ktory jej kadr jest wtopiony. Zmierzone
   na dolnej krawedzi plikow: wszystkie schodza do --c-bg-deep. */
/* EKSPERYMENT, jak wyzej: pasmo FAQ oddaje zdjecie naglowkowi strony i traci
   cien styku. Powrot to przywrocenie `--band-photo` i skasowanie `box-shadow`
   ponizej. */
.faq            { --band-photo: none;
                  --band-photo-ratio: 4118 / 1584;
                }

/* Pasmo FAQ przestaje malowac wlasne tlo. To ono tworzylo widoczna krawedz:
   nieprzezroczysty kolor przykrywal poswiate strony (body::after), ktora NAD
   pasmem byla widoczna, a pod nim juz nie. Bez wlasnego tla powierzchnia jest
   jedna i ciagla - to samo tlo podstrony i ta sama poswiata biegna przez cala
   strone, wiec nie ma czego wtapiac gradientem.
   Selektor z `.page-start`, bo `.band--photo` stoi w arkuszu nizej i przy
   rownej wadze wygralby z samym `.faq`. */
.page-start .faq { background-color: transparent; }

/* Razem z tlem znika poswiata gornej krawedzi pasma (.faq::before). Jej rola
   bylo "polaczenie sekcji z hero powyzej" - przy jednej, ciaglej powierzchni
   nie ma juz czego laczyc, a sama poswiata rysowala wlasnie te jasniejsza
   smuge, ktora bylo widac jako przejscie. */
.page-start .faq::before { display: none; }

/* I cien styku z warstwy zdjecia (.band--photo::after). Zdjecia w tym pasmie
   juz nie ma, wiec cien nie ma czego stykac - a to wlasnie on rysowal ostatnia
   pozostala kreske na granicy sekcji. Selektor znowu z `.page-start`, bo
   `.band--photo::after` stoi w arkuszu nizej. */
.page-start .faq::after { box-shadow: none; }


.page-oferta    { --page-bg:    url('../img/bg-oferta.jpg');
                  --page-fill:  var(--c-bg-deep); }
.page-wdrozenie { --page-bg:    url('../img/bg-wdrozenie.jpg');
                  --page-fill:  var(--c-bg-deep); }
.page-kontakt   { --page-bg:    url('../img/bg-kontakt.jpg');
                  --page-fill:  var(--c-bg-deep); }

/* Dokumenty prawne to dlugi tekst ciagly. Pelna poswiata za akapitami meczy
   przy czytaniu, wiec podstrona dostaje slabszy wariant - samo tlo i delikatne
   swiatlo przy gornej krawedzi. */
.page-doc       { --page-glow: var(--glow-doc); }

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

/* Pasmo: pelna szerokosc okna, tresc wysrodkowana kontenerem w srodku.
   Zastepuje trik `width:100vw; margin-left:calc(50% - 50vw)` z projektu,
   ktory przy widocznym pasku przewijania wystawia poziomy suwak. */
.band {
    position: relative;
    z-index: var(--z-content);
    width: 100%;
}

/* Stopka jest o jedna jednostke na kanale ciemniejsza niz --c-bg-deep i niz
   dolna krawedz zdjec podstron: roznica jest niedostrzegalna, wiec wtopienie
   kadru zostaje bezszwowe, a stopka siada o wlos nizej tonalnie.
   Tlo jest nieprzezroczyste celowo - warstwy `body::before/::after` leza pod
   stopka i maja byc zakryte. */
.band--deep {
    background-color: var(--c-bg-footer);
}

.band--line { border-bottom: 1px solid var(--c-border-soft); }

/* Pozioma kreska oddzielajaca pasma - ten sam kolor i grubosc, co kreska pod
   naglowkiem podstrony, zeby na witrynie byl jeden rodzaj podzialu.
   Odstep gorny daje dopelnienie pasma powyzej; dolny trzeba dolozyc tutaj, bo
   nastepne pasmo ma zerowe dopelnienie od gory. */
.rule {
    width: 100%;
    height: 0;
    margin: 0 0 var(--sp-11);
    border: 0;
    border-top: 1px solid var(--c-border-soft);
}

/* Pasmo ze zdjeciem u gory; kadr wnosi sekcja przez `--band-photo`.
   `100% auto` z kotwica u gory: nic nie jest przyciete w poziomie, a wysokosc
   nie zalezy od wysokosci pasma, wiec rozwiniecie FAQ nie skaluje tla.
   NIE uzywamy `background-attachment: fixed` - zmierzone, mediana klatki
   31,7 ms zamiast 16,7 ms. Wtopienie w kolor pasma jest wypalone w pliku:
   gradient w CSS musialby zgadywac, gdzie konczy sie zdjecie, a to zalezy
   od szerokosci okna. */
.band--photo { background-color: var(--c-bg-deep); }

/* Zdjecie i cien styku siedza na OSOBNEJ warstwie, nie na tle pasma. Otwarcie
   pytania FAQ zmienia wysokosc pasma, a to uniewaznialoby malowanie tla i cienia
   na calej wysokosci - zmierzone: p90 klatki 22,7 ms, po zdjeciu 17,1 ms.
   Ta warstwa liczy wysokosc z proporcji obrazu, wiec zalezy tylko od szerokosci
   okna. `::after`, bo `::before` jest w FAQ zajete przez poswiate krawedzi. */
.band--photo::after {
    content: '';
    position: absolute;
    inset: 0 0 auto;
    z-index: -1;
    aspect-ratio: var(--band-photo-ratio, 2.6);
    background-image: var(--band-photo);
    background-size: 100% 100%;
    background-repeat: no-repeat;
    box-shadow: var(--shadow-panel);
    pointer-events: none;
}

.page-main {
    position: relative;
    z-index: var(--z-content);
    flex: 1 1 auto;
    width: 100%;
}

.skip-link {
    position: absolute;
    left: -100vw;
    top: 0;
    z-index: var(--z-drawer);
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-accent-vivid);
    color: var(--c-on-accent);
    font-weight: 700;
}

.skip-link:focus { left: var(--sp-3); top: var(--sp-3); }

/* Tekst wylacznie dla czytnika ekranu - nazwa dania w karuzeli, opis stanu
   przelacznika 3D. Widoczny uklad nie moze sie przez niego przesunac. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Ramka Google Tag Manager dla przegladarek bez JavaScriptu. Klasa zamiast
   atrybutu `style` z fragmentu Google, bo wyglad stoi w tym projekcie wylacznie
   w arkuszu. `display: none` nie wstrzymuje ladowania ramki - licznik dziala. */
.gtm-noscript { display: none; visibility: hidden; }


/* ==========================================================================
   4. PRELOADER
   ==========================================================================
   Przeniesiony 1:1 z szablonu 3DMenu; zmieniamy tylko kolory na tokeny tego
   projektu. Zaslona znika dopiero, gdy zejda fonty i grafika (patrz Preloader
   w components.js).
   ========================================================================== */

#preloader {
    position: fixed;
    inset: 0;
    z-index: var(--z-preloader);
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--c-bg);
    transition: opacity var(--t-preloader) ease, visibility var(--t-preloader) ease;
}

#preloader.is-hidden {
    opacity: 0;
    visibility: hidden;
}

/* Podstrona otwarta z linku, czyli bez zaslony. Klase dokłada skrypt w <head> -
   JESZCZE PRZED pierwszym malowaniem, wiec zaslona nie zdazy mignac.
   `display: none`, a nie `opacity`, bo tu nie ma czego animowac: zaslony
   w ogole nie ma byc. */
.bez-zaslony #preloader { display: none; }

/* --- Miekkie wejscie strony bez zaslony ----------------------------------
   Bez zaslony zdjecie tla wskakuje w gotowy uklad pol sekundy po tekscie.
   Zamiast skoku dajemy przenikanie: tlo i maskotka startuja przezroczyste
   i wchodza, gdy ich pliki sa gotowe (klasy dokłada Preloader.softIntro).
   Tekst nie czeka na nic. Bez JS klasa nie powstaje i nic nie jest ukrywane. */
body::before { transition: opacity var(--t-intro) ease; }

.bez-zaslony body::before { opacity: 0; }
.bez-zaslony.tlo-gotowe body::before { opacity: 1; }

.bez-zaslony .robot__img,
.bez-zaslony .faq__robot img {
    opacity: 0;
    transition: opacity var(--t-intro) ease;
}

.bez-zaslony .robot__img.is-ready,
.bez-zaslony .faq__robot img.is-ready { opacity: 1; }

.preloader__wrapper {
    position: relative;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding-inline: var(--sp-7);
}

.preloader__logo-box {
    position: relative;
    width: 70%;
    max-width: 15.625rem;
    margin-inline: auto;
}

.preloader__logo {
    display: block;
    width: 100%;
    height: auto;
    object-fit: contain;
}

.preloader__glow {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: -1;
    width: 300%;
    aspect-ratio: 1;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: radial-gradient(circle at center, var(--c-glow-logo) 0%, rgba(var(--rgb-bg), 0) 65%);
    pointer-events: none;
}

/* Srodek orbity. Czastki wstawia JS - kazda dostaje wlasny promien, kat i czas
   przez custom properties (jedyne dopuszczalne uzycie atrybutu `style`,
   bo to wartosci losowane przy starcie, nie stale projektu). */
.orbit-center {
    position: absolute;
    top: 50%;
    left: 12%;
    width: 0;
    height: 0;
    z-index: 5;
}

/* Zmienne --spin-*, --pulse-delay, --scatter, --wobble-* i --base-radius
   ustawia JS przy tworzeniu czastki (wartosci losowane, wiec nie da sie ich
   zapisac w arkuszu). Kazde uzycie ma wartosc zapasowa: gdyby skrypt nie
   zdazyl, czastki maja wygladac poprawnie, a nie zniknac z powodu
   nierozwiazanej zmiennej. */
.particle-spin {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    animation: spin linear infinite;
    animation-duration: var(--spin-duration, 6s);
}

.particle-pulse {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    /* Rozrzut jako UŁAMEK promienia, nie jako piksele. Powod jest zmierzony:
       --base-radius przelicza sie przy kazdej zmianie szerokosci okna
       (Preloader.trackLogoSize), a rozrzut i drganie byly wpisywane raz, przy
       tworzeniu kropki. W podgladzie responsywnym przegladarki zostawaly wiec
       wartosci z pierwszego wczytania i chmurka nie schodzila razem z orbita
       (zgloszenie wlasciciela). Ulamek skaluje sie sam, bo liczy sie od
       promienia, ktory juz jest aktualny. */
    --radius: calc(var(--base-radius, 2.1875rem) * (1 + var(--scatter-f, 0)));
    --inner-radius: calc(var(--radius) * 0.3);
    transform: translateX(var(--radius));
    animation: pulse 2.5s infinite;
    animation-delay: var(--pulse-delay, 0s);
}

/* Amplituda drgania tez jest ULAMKIEM promienia - patrz komentarz przy
   --radius wyzej. Dzieki temu kropka drga proporcjonalnie do swojej orbity
   i nie trzeba jej przeliczac przy zmianie szerokosci okna. */
.particle {
    --wobble: calc(var(--base-radius, 2.1875rem) * var(--wobble-f, 0.2));
    position: absolute;
    top: 50%;
    left: 50%;
    border-radius: 50%;
    box-shadow: 0 0 6px var(--c-particle-shadow);
    opacity: 0;
    filter: saturate(1.9) brightness(1.2);
    animation: wobble ease-in-out infinite alternate, appear 0.8s ease-out forwards;
    animation-duration: var(--wobble-duration, 1.2s), 0.8s;
    animation-delay: 0s, var(--pulse-delay, 0s);
    will-change: transform, opacity;
}


/* ==========================================================================
   5. ANIMACJE
   ========================================================================== */

/* --- Preloader (przeniesione z szablonu 3DMenu bez zmian) ----------------- */

@keyframes appear {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@keyframes spin {
    from { transform: rotate(var(--start-angle, 0deg)); }
    to   { transform: rotate(calc(var(--start-angle, 0deg) + var(--spin-angle, 360deg))); }
}

@keyframes pulse {
    0%, 65% {
        transform: translateX(var(--radius));
        animation-timing-function: cubic-bezier(0.5, 0, 1, 1);
    }
    75% {
        transform: translateX(var(--inner-radius));
        animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
    }
    90%, 100% { transform: translateX(var(--radius)); }
}

@keyframes wobble {
    from { transform: translate(calc(-50% - var(--wobble)), calc(-50% - var(--wobble))); }
    to   { transform: translate(calc(-50% + var(--wobble)), calc(-50% + var(--wobble))); }
}

/* TRZY osie na raz (X+Y+Z), nie dwie - z samym X+Y kostka co 1/4 obrotu
   (co 750 ms w 3-sekundowej petli) przechodzi przez ustawienie plaskie
   wzgledem kamery i przez ulamek sekundy wyglada jak kwadrat, nie bryla.
   Trzecia os rozbija ta symetrie - dokladna kopia @keyframes spinCube
   z 3dmenu/static/v1/css/main.css. */
@keyframes spinCube {
    0%   { transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); }
    100% { transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg); }
}

@keyframes robotFloat {
    0%, 100% { transform: translate3d(0, 0, 0) rotate(-0.6deg); }
    50%      { transform: translate3d(0, calc(var(--lift-robot) * -1), 0) rotate(0.6deg); }
}

/* Mikrodrgania nalozone na unoszenie - robot ma wygladac na zawieszony na
   silnikach, nie na plynacy. Kroki co 9%, zeby ruch byl skokowy, nie plynny. */
@keyframes robotJitter {
    0%   { transform: translate3d(0, 0, 0); }
    9%   { transform: translate3d(calc(var(--jitter-robot) * -1), calc(var(--jitter-robot) * 0.5), 0); }
    18%  { transform: translate3d(calc(var(--jitter-robot) * 0.5), calc(var(--jitter-robot) * -1), 0); }
    27%  { transform: translate3d(calc(var(--jitter-robot) * 1), calc(var(--jitter-robot) * 0.5), 0); }
    36%  { transform: translate3d(calc(var(--jitter-robot) * -0.5), calc(var(--jitter-robot) * -0.5), 0); }
    45%  { transform: translate3d(calc(var(--jitter-robot) * 0.5), calc(var(--jitter-robot) * 1), 0); }
    54%  { transform: translate3d(calc(var(--jitter-robot) * -1), calc(var(--jitter-robot) * -0.5), 0); }
    63%  { transform: translate3d(calc(var(--jitter-robot) * 1), calc(var(--jitter-robot) * -1), 0); }
    72%  { transform: translate3d(calc(var(--jitter-robot) * -0.5), calc(var(--jitter-robot) * 1), 0); }
    81%  { transform: translate3d(calc(var(--jitter-robot) * 0.5), calc(var(--jitter-robot) * -0.5), 0); }
    90%  { transform: translate3d(calc(var(--jitter-robot) * -1), calc(var(--jitter-robot) * 0.5), 0); }
    100% { transform: translate3d(0, 0, 0); }
}

/* Pulsowanie poswiaty. Cien jest STALY i siedzi na pseudoelemencie, a pulsuje
   wylacznie jego `opacity` i `transform` - jedyna para, ktora kompozytor
   obsluguje bez przemalowania. Animowany wprost `box-shadow` kosztowal
   zmierzone 32,6 ms na klatke zamiast 23,6 ms. */
@keyframes glowPulse {
    0%, 100% { opacity: 0.55; transform: scale(0.99); }
    50%      { opacity: 1;    transform: scale(1); }
}

/* Wjazd kola z sekcji "proof". Krycie wchodzi w pierwszej cwiartce lotu, zeby
   bylo widac LECACY obiekt, a nie przenikajaca plame - o to wlasnie chodzi
   w tym efekcie. Dalej lot, delikatny przestrzal i spokojny dryf na miejsce. */
/* Wejscie kafelkow na TELEFONIE: krotki dojazd z prawej plus przenikanie,
   bez przestrzelenia. Na szerokim ekranie kola nadlatuja z daleka i odbicie
   jest tam istota efektu; w kolumnie kafelki stoja jeden pod drugim, ta sama
   animacja czytala sie jako szarpniecie (zgloszenie wlasciciela: "moga sie
   pojawiac lagodniej"). */
@keyframes revealSoft {
    from {
        opacity: 0;
        transform: translateX(var(--reveal-slot));
    }
    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes revealSlide {
    0% {
        opacity: 0;
        transform: translateX(calc(var(--reveal-slot) * var(--reveal-steps)));
    }
    /* Krycie MUSI byc podane takze w OSTATNIEJ klatce. Gdy go tam nie ma,
       animacja interpoluje je z powrotem do wartosci spod spodu - a ta wynosi
       zero - i kolo gasnie w locie zamiast wyladowac. Zmierzone na tym bledzie:
       krycie 0,99 na 180 ms, 0,47 na 300 ms, 0,11 na 900 ms. */
    25% { opacity: 1; }
    /* Koniec lotu: kolo jest juz 20 px ZA swoim miejscem. Stad dalej wraca
       druga krzywa, bo inaczej dojscie mialoby ten sam gwaltowny charakter,
       co lot, i przestrzal czytalby sie jak drganie. */
    /* Szczyt przestrzalu. Reszta osi (30 %) idzie juz na spokojny powrot -
       patrz --ease-reveal-back. */
    70% {
        transform: translateX(var(--reveal-overshoot));
        animation-timing-function: var(--ease-reveal-back);
    }
    100% { opacity: 1; transform: translateX(0); }
}

@keyframes ringCW  { to { transform: rotate(360deg); } }
@keyframes ringCCW { to { transform: rotate(-360deg); } }


/* ==========================================================================
   6. PRZYCISKI I PIGULKI
   ========================================================================== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    font-weight: 600;
    text-align: center;
    white-space: nowrap;
    transition: color var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease);
}

/* Pelne zielone tlo - jedyny przycisk tego rodzaju w naglowku. */
.btn--solid {
    padding: 0.65625rem 1.25rem;
    background: var(--c-accent-vivid);
    border-color: var(--c-accent-vivid);
    color: var(--c-on-accent);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: var(--ls-cta);
    text-transform: uppercase;
    box-shadow: var(--shadow-cta);
}

.btn--solid:hover,
.btn--solid:focus-visible {
    filter: brightness(1.1);
    color: var(--c-on-accent);
}

/* Obrys - podstawowy przycisk tresci. */
.btn--ghost {
    padding: 0.9375rem var(--sp-7);
    background: transparent;
    border-color: var(--c-border-ghost);
    color: var(--c-text-strong);
    font-size: var(--fs-lead);
}

/* Jeden stan najechania dla wszystkich przyciskow obrysowanych - wczesniej
   byly dwa i roznica w reakcji na kursor byla juz tylko niekonsekwencja
   (zgloszenie wlasciciela). */
.btn--ghost:hover,
.btn--ghost:focus-visible {
    border-color: var(--c-accent-soft);
    background: var(--c-fill-ghost);
    color: var(--c-text-strong);
    box-shadow: var(--shadow-ring);
}

/* Dwa przyciski hero maja rowna szerokosc niezaleznie od dlugosci etykiety -
   inaczej "Zobacz oferte" i "Porozmawiajmy" tworza schodek. Klasa siedzi
   wylacznie na tej parze, wiec sluzy tez za zaczep dla welonu pod etykieta -
   pozostale przyciski obrysowane stoja na spokojnym tle i go nie potrzebuja. */
.btn--fixed {
    width: 13.25rem;
    padding-inline: 0;
    background-color: var(--c-fill-btn-hero);
    border-color: var(--c-border-btn-hero);
}

/* Welon zostaje takze po najechaniu. Wspolna regula `.btn--ghost:hover` ustawia
   `background` skrotem, wiec skasowalaby kolor tla - dlatego wypelnienie stanu
   wchodzi tu jako WARSTWA OBRAZU nad welonem, a nie zamiast niego.
   Ciemnej poswiaty w spoczynku NIE MA - probowalismy jej w kilku szerokosciach
   rozmycia (24, 30, 60 i 100 px) i wlasciciel zdecydowal, ze przyciski maja
   stac na samym welonie. Przywrocenie to jedna linia `box-shadow` wyzej. */
.btn--ghost.btn--fixed:hover,
.btn--ghost.btn--fixed:focus-visible {
    background-color: var(--c-fill-btn-hero);
    background-image: linear-gradient(var(--c-fill-ghost), var(--c-fill-ghost));
}

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

.btn--wide { padding: 0.9375rem 1.875rem; font-weight: 500; }

/* Ikona w przycisku. Zielona NIEZALEZNIE od koloru etykiety: przyciski
   obrysowane maja tekst bialawy (--c-text-strong), a ikona ma byc akcentem,
   a nie druga plama tego samego koloru. `flex: 0 0 auto`, zeby nie zwezala sie
   przy dlugiej etykiecie, i lekko opuszczona optycznie - kwadratowy znak przy
   tekscie z dlugimi ogonkami czyta sie inaczej niz przy wersalikach. */
.btn__icon {
    flex: 0 0 auto;
    color: var(--c-accent-vivid);
    transition: color var(--t-fast) var(--ease);
}

/* Na najechaniu ikona idzie razem z obrysem - inaczej zostawalaby ciemniejsza
   od reszty przycisku. */
.btn:hover .btn__icon,
.btn:focus-visible .btn__icon { color: var(--c-accent-lit); }

/* Wariant .btn--cta (pelne zielone tlo, wersaliki, pulsujaca poswiata) oraz
   otaczajacy go pierscien .cta-ring zostaly usuniete. Wlasciciel ujednolicil
   przyciski: wdrozenie i formularz kontaktu uzywaja teraz tego samego stylu,
   co "Wybierz plan" w cenniku, czyli .btn--ghost. Poswiata `glowPulse` zyje
   dalej przy karcie polecanego pakietu. */


/* Nadkreslenie sekcji: "OFERTA", "FAQ", "KONTAKT BEZPOSREDNI". */
.eyebrow {
    margin: 0 0 var(--sp-4);
    font-size: var(--fs-eyebrow);
    font-weight: 600;
    letter-spacing: var(--ls-eyebrow);
    color: var(--c-accent);
}

.eyebrow--strong {
    font-weight: 700;
    letter-spacing: var(--ls-label);
    color: var(--c-accent-vivid);
}


/* ==========================================================================
   7. NAGLOWEK I NAWIGACJA
   ========================================================================== */

/* Bez kreski u dolu - decyzja wlasciciela. Naglowek oddziela od tresci sam
   kolor tla, a tam, gdzie pod nim zaczyna sie zdjecie, jego gorna krawedz. */
/* PODGLAD: naglowek jedzie razem z trescia. Wymaga wlasnej warstwy NAD
   maskotka (--z-header > --z-robot) - pasmo naglowka podstrony jest celowo
   podniesione, zeby maskotka mogla z niego wystawac, wiec przy przewijaniu
   wjezdzalaby na belke. */
.site-header {
    position: sticky;
    top: 0;
    z-index: var(--z-header);
    background: var(--c-bg);
    box-shadow: var(--glow-header);
}

.site-header__inner {
    /* Kotwica dla listy jezykow: na szerokim ekranie zwisa ona spod paska,
       rownana PRAWA krawedzia do marginesu tresci - tak samo, jak w aplikacji
       3DMenu, gdzie lista po prawej stronie idzie do linii koszyka, a nie pod
       sam glob. */
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    padding-block: var(--header-pad-y);
}

.brand { flex: 0 0 auto; display: flex; align-items: center; }
.brand__logo { height: var(--logo-h); width: auto; }

.site-nav {
    flex: 1 1 auto;
    min-width: 0;
}

.site-nav__list {
    display: flex;
    justify-content: center;
    gap: 2px;
}

.site-nav__link {
    display: block;
    padding: 0.5625rem clamp(0.5rem, 1vw, 1rem);
    border-radius: var(--r-pill);
    font-size: var(--fs-nav);
    font-weight: 600;
    white-space: nowrap;
    color: var(--c-text-nav);
}

.site-nav__link:hover,
.site-nav__link:focus-visible { color: var(--c-text); }

.site-nav__link.is-active,
.site-nav__link[aria-current] {
    font-weight: 700;
    color: var(--c-accent-vivid);
    text-shadow: var(--shadow-nav);
}

.site-header__side {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}

/* Dwa okragle przyciski paska: hamburger po lewej i jezyki po prawej. Oba
   widoczne dopiero ponizej progu szuflady (sekcja 16) i oba w JEDNEJ regule -
   maja byc identyczne z definicji, a nie przez powtorzone wartosci. Rozmiar
   to --tap-min, czyli dokladnie szerokosc bocznych kolumn paska na telefonie:
   kolka stoja wtedy symetrycznie wzgledem logo na srodku. */
.nav-toggle,
.lang-btn {
    display: none;
    width: var(--tap-min);
    height: var(--tap-min);
    padding: 0;
    background: transparent;
    border: 1px solid var(--c-border-mid);
    border-radius: var(--r-pill);
    color: var(--c-text);
}

/* Na SZEROKIM ekranie glob nie jest kolkiem, tylko pigulka: glob, skrot jezyka
   i strzalka w jednym rzedzie, bez obramowania - dokladnie jak w pasku
   aplikacji 3DMenu (wskazanie wlasciciela). Kolko z reguly wyzej zostaje
   telefonowi; tam pasek ma miec dwa jednakowe kolka po bokach znaku.

   `order` zamiast przenoszenia w markupie: pigulka ma stanac PRZED przyciskiem
   demo, ale w gridzie telefonu jest bezposrednim dzieckiem paska i to on
   sadza ja w trzeciej kolumnie. Dziecko `.site-header__side` nie moglo by
   tego zrobic, bo grid-area dziala tylko na bezposrednich dzieciach. */
.lang-btn {
    display: flex;
    align-items: center;
    gap: var(--lang-pill-gap);
    width: auto;
    height: var(--lang-pill-h);
    padding-inline: var(--sp-1);
    border: 0;
    color: var(--c-text-80);
    font-size: var(--fs-sm);
    font-weight: 600;
    order: 1;
    /* Pasek daje wlasny odstep (--sp-5), a od przycisku demo ma byc 17 px. */
    margin-right: calc(var(--lang-pill-cta) - var(--sp-5));
}

.site-header__side { order: 2; }

/* Pod wskaznikiem cala pigulka - glob, skrot i strzalka - bierze zielen marki.
   Bez poswiaty: w aplikacji wlasciciel kazal zdjac text-shadow i drop-shadow. */
@media (hover: hover) and (pointer: fine) {
    .lang-btn:hover { color: var(--c-accent-vivid); }
}

.lang-btn__code { line-height: 1; }

.lang-btn__caret {
    display: block;
    width: var(--lang-caret);
    height: var(--lang-caret);
    transition: transform var(--t-caret) var(--ease-caret);
}

/* Strzalka obraca sie z otwarciem listy. Stan bierzemy z `aria-expanded`,
   ktory skrypt i tak ustawia - nie ma po co dokladac drugiej klasy ani
   ruszac stylu z JS (arkusz jest jedynym miejscem na wyglad). */
.lang-btn[aria-expanded="true"] .lang-btn__caret { transform: rotate(180deg); }

/* Glob rysowany jest sama kreska grubosci 1,4 px, wiec przy rownym rozmiarze
   z hamburgerem (18 px, kreski 2 px) wygladal na mniejszy - to roznica masy
   znaku, nie wymiaru. Stad 26 px: pole rysunku jest wieksze, ale ilosc tuszu
   w kolku zgadza sie z lewa strona. Droga do tej liczby: 20 px wygladalo na
   mniejsze od hamburgera, 24 px juz prawie dobrze, a wlasciciel poprosil
   o jeszcze piksel na promieniu - czyli dwa na srednicy. */
.lang-btn__icon {
    display: block;
    /* W pigulce glob ma 1,3 rem - tyle, co .ico-lang-svg w pasku aplikacji.
       Na telefonie urasta do 26 px (media query nizej): tam stoi sam w kolku
       i musi wazyc tyle, co hamburger po drugiej stronie logo. */
    width: 1.3rem;
    height: 1.3rem;
    flex-shrink: 0;
    margin-inline: auto;
}

.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
    display: block;
    width: 1.125rem;
    height: 2px;
    margin-inline: auto;
    background: currentColor;
    border-radius: 2px;
    transition: transform var(--t-fast) var(--ease),
                opacity var(--t-fast) var(--ease);
}

.nav-toggle__bars { position: relative; }

.nav-toggle__bars::before,
.nav-toggle__bars::after {
    content: '';
    position: absolute;
    left: 0;
}

.nav-toggle__bars::before { top: -0.375rem; }
.nav-toggle__bars::after  { top: 0.375rem; }

.nav-toggle[aria-expanded='true'] .nav-toggle__bars { background: transparent; }
.nav-toggle[aria-expanded='true'] .nav-toggle__bars::before { transform: translateY(0.375rem) rotate(45deg); }
.nav-toggle[aria-expanded='true'] .nav-toggle__bars::after  { transform: translateY(-0.375rem) rotate(-45deg); }

.site-nav__cta { display: none; }

/* --- Szuflada nawigacji ---------------------------------------------------
   Nawigacja ponizej progu z sekcji 16 przestaje byc rzedem w belce i staje sie
   panelem wjezdzajacym z lewej - ten sam ruch, co szuflada w aplikacji 3DMenu.
   Pasek szuflady i zaslona pod nia sa w markupie na kazdej szerokosci (buduje
   je Navigation w components.js), ale sens maja tylko w ukladzie szuflady,
   wiec na szerokim ekranie schodza z drogi. Uklad i ruch panelu opisuje
   sekcja 16 - tutaj zostaje wylacznie wyglad krzyzyka, ktory z progiem nie ma
   nic wspolnego. */
.site-nav__head,
.site-nav__overlay { display: none; }

/* Lista jezykow na SZEROKIM ekranie: zwisa spod paska, rownana prawa krawedzia
   do marginesu tresci (kotwica siedzi na .site-header__inner). Tak samo idzie
   lista po prawej stronie w pasku aplikacji 3DMenu - do linii koszyka, a nie
   pod sam glob. Uklad telefonu opisuje sekcja 16; tam panel jest `fixed`
   i wjezdza na srodku ekranu, wiec ta regula go nie dotyczy.

   Rozmycie tla WLACZA SIE DOPIERO w stanie otwartym - ta sama zasada, co przy
   szufladzie: `backdrop-filter` trzyma wlasna warstwe kompozytora takze na
   elemencie niewidocznym, a lista wisi w drzewie przez cala wizyte. */
.lang-menu {
    position: absolute;
    top: 100%;
    /* Do LINII TRESCI, nie do krawedzi okna: przyleganie liczy sie od
       padding-boxa paska, a ten ma po bokach dopelnienie kontenera, wiec
       "0" wypuszczalo liste 48 px za prawa krawedz przycisku demo. Zmienna,
       a nie liczba - dopelnienie kontenera zwe sie na wezszych ekranach. */
    right: var(--container-pad);
    z-index: var(--z-drawer);
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    min-width: var(--lang-menu-w);
    margin-top: var(--lang-menu-drop);
    padding: var(--sp-2);
    border: 1px solid var(--c-lang-border);
    border-radius: 1.25rem;
    box-shadow: var(--shadow-lang);
    visibility: hidden;
    opacity: 0;
    transform: translateY(calc(-1 * var(--lang-menu-shift)));
    transition: transform var(--t-lang-out) ease-in,
                opacity var(--t-lang-out) ease-in,
                visibility 0s linear var(--t-lang-out);
}

.lang-menu.is-open {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
    backdrop-filter: blur(16px) brightness(1.25);
    -webkit-backdrop-filter: blur(16px) brightness(1.25);
    transition: transform var(--t-lang-in) var(--ease-lang),
                opacity var(--t-fast) ease-out,
                visibility 0s linear 0s;
}

/* Zaslona pod lista ma sens tylko w ukladzie telefonu (przykrywa cala strone
   i lapie klikniecie obok). Na szerokim ekranie klikniecie obok obsluguje
   ten sam element, ale bez zaciemnienia - lista jest maly panel przy pasku,
   a nie okno nad strona. */
.lang-menu__overlay {
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-drawer) - 1);
    background: transparent;
    visibility: hidden;
}

.lang-menu__overlay.is-open { visibility: visible; }

/* --- Lista jezykow --------------------------------------------------------
   Zjezdza spod paska po kliknieciu globu, na tej samej zasadzie, co w wersji
   mobilnej aplikacji: panel nad strona, zaslona pod nim, wybor jezyka albo
   klikniecie obok zamyka. Uklad i ruch opisuje sekcja 16 - tutaj zostaje
   wyglad samych pozycji, bo z progiem nie ma nic wspolnego.
   Ramka, tlo i cien sa DOKLADNIE takie, jak w oknie modalnym (sekcja 20):
   to jedyne dwa panele tej witryny, ktore staja nad przyciemniona strona,
   wiec maja wygladac jak jedna rodzina. */
/* Pozycje listy - wymiary i stopien pisma wprost z `.lang-option` w aplikacji:
   12/20 px dopelnienia, promien 12 px, 1,05rem pisma o grubosci 500. Krojem
   zostaje nasz Inter, a jezyk biezacy i stan pod palcem swieca zloto - tak
   samo jak tam, tyle ze naszym zlotem (--c-gold zamiast #facc15). */
.lang-menu__item {
    /* Bez zaznaczania tekstu i bez niebieskiego blysku pod palcem. Dotkniecie
       pozycji na telefonie podswietlalo ja jak zaznaczony tekst - a to jest
       pozycja do wybrania, nie akapit (zgloszenie wlasciciela).
       Od 2026-09-10 pozycja jest odnosnikiem <a>, a nie <button>: kolor i krok
       pisma stoja nizej w tej samej regule, a podkreslenie zdejmuje globalny
       reset elementu "a", wiec wyglad nie zmienil sie ani o piksel. */
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: var(--tap-min);
    padding: 0.75rem 1.25rem;
    border: 0;
    border-radius: 0.75rem;
    background: transparent;
    color: var(--c-drawer-text);
    font-size: 1.05rem;
    font-weight: 500;
    text-align: center;
    transition: color var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease);
}

.lang-menu__item:hover,
.lang-menu__item:focus-visible {
    background: var(--c-fill-hover);
    color: var(--c-gold);
}

.lang-menu__item.is-active,
.lang-menu__item[aria-current] {
    color: var(--c-gold);
    font-weight: 700;
}

/* Krzyzyk jest konieczny, a nie ozdobny: otwarta szuflada zakrywa hamburger
   (stoi na telefonie po lewej), wiec bez niego jedynym wyjsciem zostawalaby
   zaslona obok panelu. Wyglad jak przy krzyzyku okna modalnego (sekcja 20),
   ramiona rysowane CSS-em - tak samo jak kreski hamburgera wyzej, zeby
   skrypt nie musial budowac SVG. */
.site-nav__close {
    position: relative;
    flex: 0 0 auto;
    width: var(--tap-min);
    height: var(--tap-min);
    padding: 0;
    background: var(--c-fill-hover);
    border: 1px solid var(--c-border-mid);
    border-radius: var(--r-pill);
    color: var(--c-text-80);
    transition: color var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease);
}

.site-nav__close::before,
.site-nav__close::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1.125rem;
    height: 2px;
    margin: -1px 0 0 -0.5625rem;
    background: currentColor;
    border-radius: 2px;
}

.site-nav__close::before { transform: rotate(45deg); }
.site-nav__close::after  { transform: rotate(-45deg); }

.site-nav__close:hover,
.site-nav__close:focus-visible {
    border-color: var(--c-border-accent);
    background: var(--c-fill-accent);
    color: var(--c-accent-vivid);
}


/* ==========================================================================
   8. NAGLOWEK PODSTRONY
   ========================================================================== */

/* Pasmo naglowka maluje sie NAD nastepnym pasmem, bo maskotka celowo z niego
   wystaje w dol (patrz .robot--head), a pasmo ze zdjeciem ma nieprzezroczyste
   tlo i inaczej ucinaloby jej dolna polowe. Sam `z-index` na maskotce nie
   wystarcza: `.band` tworzy kontekst nakladania, wiec maskotka nie wydostanie
   sie z niego - podniesc trzeba caly naglowek. */
.page-head {
    position: relative;
    z-index: var(--z-robot);
}

.page-head__inner {
    position: relative;
    padding-block: var(--head-pad-top) var(--head-pad-bottom);
}

.page-head__title {
    margin-bottom: 0.875rem;
    font-size: var(--fs-h1);
    line-height: 1.06;
    font-weight: 700;
    letter-spacing: var(--ls-tight);
}

/* BEZ `text-wrap: pretty` - zgloszenie wlasciciela z telefonu: "ten tekst
   normalnie dalej by doszedl". I tak jest: ta wlasciwosc znaczy co innego
   w kazdej przegladarce. Chrome tylko chroni przed sierota w ostatnim wierszu,
   ale Safari optymalizuje CALY akapit i wyrownuje wiersze do siebie - na waskim
   ekranie zostawia przez to widoczny pas pustego pola po prawej. Zwykle zawijanie
   jest przewidywalne: wiersz konczy sie tam, gdzie nie miesci sie kolejne slowo. */
.page-head__lead {
    max-width: 40rem;
    font-size: var(--fs-lead);
    line-height: 1.6;
    color: var(--c-text-60);
}

.page-head__lead--wide { max-width: 45rem; }



/* ==========================================================================
   9. ROBOT DEKORACYJNY
   ========================================================================== */

/* Maskotka wisi poza obrysem swojego pasma, dlatego nadrzedny wezel NIE moze
   miec `overflow:hidden`. Nie jest trescia - poza drzewem dostepnosci i bez
   przechwytywania klikniec. */
.robot {
    position: absolute;
    z-index: var(--z-robot);
    pointer-events: none;
}

.robot__img {
    width: 100%;
    filter: var(--drop-robot);
}

/* Ruch ma TYLKO maskotka na Wdrozeniu - decyzja wlasciciela. Na pozostalych
   podstronach maskotka stoi: na starcie dlatego, ze kadr i tak zyje scena 3D,
   a na ofercie i kontakcie dlatego, ze ma tam byc spokojnie.
   `will-change` idzie razem z animacja, bo tylko animowany element ma powod
   trzymac wlasna warstwe kompozytora. */
.robot--steps,
.robot--steps .robot__img { will-change: transform; }

.robot--steps { animation: robotFloat var(--float-robot) ease-in-out infinite; }

.robot--steps .robot__img { animation: robotJitter 0.75s steps(1, end) infinite; }

.robot--head {
    right: 7%;
    bottom: -8.9375rem;   /* -120 px + 23 px w dol na zyczenie wlasciciela */
    width: clamp(14.375rem, 25vw, 23.125rem);
}

/* Amplitudy takie same jak przy pozostalych maskotkach, czyli takie jak
   w ORYGINALNYM eksporcie z Claude Design: tam maskotka na Wdrozeniu ma
   dokladnie te 487 px szerokosci co u nas, unosi sie o 18 px i drga o 1 px.
   Podnosilem te wartosci 2,5-krotnie, bo na planszy porownawczej pokazalem
   maskotke w 190 px i wyszlo z tego, ze na stronie ruch jest za slaby -
   bledna przeslanka. Prawdziwa przyczyna "nic sie nie rusza" lezala gdzie
   indziej (ustawienie systemowe goscia), wiec wracamy do wartosci z projektu. */
.robot--steps {
    right: 7%;
    bottom: -13.3125rem;  /* -190 px + 23 px w dol, tak samo jak .robot--head */
    width: 30.4375rem;
    max-width: 100%;
}

/* Odbicie w poziomie - w projekcie robot na "Wdrozeniu" patrzy w lewo. */
/* Odbicie w poziomie przez OSOBNA wlasciwosc `scale`, a nie przez `transform`.
   `transform` jest tu juz zajety przez animacje `robotJitter`, ktora go w calosci
   nadpisuje - zmierzone: wyliczona macierz obrazka to matrix(1,0,0,1,x,y), czyli
   pierwsza wartosc 1 zamiast -1 i maskotka patrzy w zla strone przez caly czas
   trwania animacji, czyli zawsze. `scale` sklada sie z `transform` zamiast z nim
   rywalizowac. Tak samo robi oryginalny eksport z Claude Design (scale:-1 1). */
.robot--flip .robot__img { scale: -1 1; }

/* W kolumnie kontaktu robot plynie w ukladzie, nie wisi nad nim. */
.robot--inline {
    position: static;
    margin-top: auto;
    padding-top: 3.25rem;   /* odstep dobrany przez wlasciciela w przegladarce */
    width: var(--robot-inline-w);
    max-width: 100%;
}

.robot--inline .robot__img { filter: var(--drop-robot-near); }


/* ==========================================================================
   10. HERO - KOLUMNA TEKSTOWA
   ========================================================================== */

/* Dolne dopelnienie hero ustawia odstep od panelu z miniaturkami do kreski
   oddzielajacej sekcje. Wlasciciel zmierzyl, ze brakowalo szesciu pikseli:
   bylo 22 px, ma byc 28 - tyle samo, ile jest miedzy naglowkiem strony
   a gornym lukiem przerywanego okregu sceny (zmierzone 27 px). */
.hero {
    position: relative;
    padding-bottom: 0.375rem;
}

.hero__grid {
    display: grid;
    grid-template-columns: minmax(0, var(--col-narrow)) minmax(0, var(--col-wide));
    gap: var(--sp-6);
    align-items: center;
    min-height: calc(100vh - 11.875rem);
}

.hero__col {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1.625rem;
    padding-block: var(--sp-9);
    padding-right: var(--sp-6);
}

.hero__title {
    font-size: var(--fs-h1-hero);
    line-height: 1.1;
    font-weight: 700;
    letter-spacing: var(--ls-tight);
}

/* `position` czyni z wiersza UKLAD ODNIESIENIA dla wykladnika "AR"
   (patrz .hero__title-sup nizej). Siedzi tutaj, a nie na modyfikatorze
   `--nowrap`: tamten mowi o lamaniu wiersza, nie o pozycjonowaniu. */
.hero__title-line { display: block; position: relative; }

/* Dwa wiersze naglowka sa wypelnione gradientem. `background-clip:text`
   wymaga przezroczystego koloru tekstu - stad `color:transparent`. */
.hero__title-line--mint,
.hero__title-line--lime {
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.hero__title-line--mint {
    background-image: linear-gradient(90deg, var(--c-accent-mint), var(--c-accent-lime));
}

.hero__title-line--lime {
    background-image: linear-gradient(90deg, var(--c-accent-olive), var(--c-gold));
}

/* "AR" jako wykladnik przy "3D".

   Pozycjonowany BEZWZGLEDNIE, a nie `vertical-align`. Poprzednia wersja
   liczyla podniesienie z wysokosci wersalika kroju (Inter 0.75em) - dzialala,
   ale po podmianie fontu rozjechalaby sie po cichu. Tu polozenie jest wprost
   podane i nie zalezy od metryk.

   Wszystkie trzy wartosci sa w `em`, NIE w pikselach. Stopien H1 jest plynny
   (`clamp` 2.1875-4.875rem), wiec sztywne piksele trzymaja rozmiar w miejscu,
   gdy naglowek maleje: zmierzone przy `font-size:35px` stosunek AR do H1 szedl
   0.51 (1440 px) -> 0.85 (860 px) -> 1.00 ponizej 560 px, czyli na telefonie
   "AR" mialo rozmiar "Menu 3D". W `em` proporcja jest ta sama na kazdej
   szerokosci. `margin-left` i `top` licza sie od WLASNEGO stopnia indeksu.

   W markupie przed znacznikiem stoi PRAWDZIWA SPACJA - bez niej tresc
   naglowka czytana przez wyszukiwarke i czytnik ekranu brzmi "Menu 3DAR".
   `letter-spacing` nadpisane, bo odziedziczone --ls-tight sciskaloby
   wersaliki. */
.hero__title-sup {
    position: absolute;
    top: 0.294em;            /* 10 px przy stopniu indeksu 34 px */
    margin-left: 0.294em;    /* 10 px - dobicie do spacji, ktora juz jest w tresci */
    font-size: var(--fs-h1-sup);
    line-height: 1;
    font-weight: 600;
    letter-spacing: normal;
}

/* Pierwsze dwa wiersze nie moga sie lamac - projekt trzyma je w jednej linii. */
.hero__title-line--nowrap { white-space: nowrap; }

.hero__lead {
    max-width: 27.5rem;
    font-size: var(--fs-lead-hero);
    line-height: 1.6;
    color: var(--c-text-72);
    text-shadow: var(--shadow-text-hero);
}

/* Wyroznienie w akapicie hero. Element liniowy w istniejacym akapicie, wiec
   nie rusza ukladu - zmienia sie tylko kolor i grubosc frazy. */
.hero__claim {
    color: var(--c-accent-vivid);
    font-weight: 600;
}

.hero__actions {
    display: flex;
    /* NIE `wrap` - polecenie wlasciciela: dopoki uklad jest desktopowy
       (dwie kolumny), oba przyciski maja stac OBOK SIEBIE; pod siebie moga
       zejsc dopiero tam, gdzie uklad zmienia sie na wysrodkowany.
       Same 212 px z `.btn--fixed` na to nie pozwalaly: dwa przyciski plus
       odstep potrzebuja 440 px, a kolumna tekstu ma ich tyle dopiero od
       okolo 1245 px (zmierzone: 484 px przy oknie 1300, ale juz 443 px przy
       1200 i 394 px przy 1081) - i wlasnie tam para sie lamala.
       Po zdjeciu zawijania nadmiar zabiera domyslne `flex-shrink`: przyciski
       schodza rownomiernie do ok. 189 px przy najweszszej kolumnie tego pasma,
       czyli zostaja jednakowe i miesci sie w nich etykieta. Tam, gdzie miejsca
       starcza, dalej maja swoje 212 px i nic sie nie zmienia.
       Ponizej 560 px osobna regula ustawia je w kolumne na pelna szerokosc
       i to zostaje - tam zejscie pod siebie jest zamierzone. */
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--sp-4);
}

.hero__features {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3) 1.75rem;
    max-width: 27.5rem;
    margin-top: 0.625rem;
}

.hero__feature {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    font-size: var(--fs-sm);
    color: var(--c-text-78);
}

.hero__feature::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: var(--r-pill);
    background: var(--c-gold);
}


/* ==========================================================================
   11. HERO - SCENA DANIA
   ========================================================================== */

.hero__stage {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: calc(100vh - 13.125rem);
}

.stage {
    position: relative;
    align-self: stretch;
    height: var(--stage-h);
    flex: 0 0 auto;
    /* Ujemne marginesy z projektu: scena wychodzi poza swoja kolumne, zeby
       obiekt nie wygladal na wcisniety w prawa polowe siatki. */
    margin-right: max(-6%, -2.5rem);
    margin-left: max(-14%, -2.5rem);
}

/* --- Przycisk trybu AR ----------------------------------------------------
   Znak z projektu zatwierdzonego przez wlasciciela: okragly przycisk z obrysem
   w kolorze marki, ciemnym przezroczystym wypelnieniem i pierscieniem, ktory
   pulsuje, zeby zwracac uwage. Wartosci z projektu przelozone na tokeny
   arkusza - zaden kolor, cien ani czas nie wchodzi tu na sztywno.

   POLOZENIE jest bezwzgledne i to jest istotne: przycisk nie moze wejsc do
   przeplywu, bo przesunalby scene, panel miniatur i akapit pod nimi, czyli
   caly uklad zatwierdzony wczoraj. Tak zawieszony nie rusza ani jednego
   piksela - zmierzone.

   Stoi POD daniem, tuz nad panelem miniatur (wskazanie wlasciciela), na osi
   sceny. Odleglosc od dolu liczona z tych samych zmiennych, co polozenie
   panelu, a nie wpisana liczba: panel siedzi `--panel-lift` nad dolem sceny
   i schodzi o `--sp-4` w dol, wiec `--panel-lift - --sp-4` to dokladnie jego
   gorna krawedz, a dolozone `--sp-4` to swiatlo miedzy nim a przyciskiem.
   Dzieki temu przy kazdym progu, na ktorym zmienia sie podjazd panelu,
   przycisk jedzie razem z nim i swiatlo zostaje takie samo (zmierzone 16 px
   przy 390 i przy 860 px).

   Domyslnie SCHOWANY, a pokazuje go dopiero klasa `is-ready`, ktora dokłada
   skrypt po potwierdzeniu, ze biblioteka na TYM urzadzeniu AR uruchomi
   (Showcase.refreshAr). Szerokosc okna o tym nie swiadczy - waskie okno na
   biurku AR nie ma, a tablet w poziomie ma. Prog 1080 zostaje mimo to jako
   drugi warunek, bo polozenie przycisku liczy sie z `--panel-lift`, a ta
   zmienna istnieje wylacznie w ukladzie jednokolumnowym. */
.stage__ar {
    display: none;
    position: absolute;
    /* = gorna krawedz panelu (--panel-lift minus --sp-4) plus tyle samo
       swiatla nad nim. Po skroceniu wychodzi samo --panel-lift. */
    bottom: var(--panel-lift);
    /* PO PRAWEJ, rowno z krawedzia panelu miniatur pod spodem (wskazanie
       wlasciciela). Panel jest wysrodkowany w scenie i wezszy od niej, wiec
       jego wciecie to polowa roznicy szerokosci - a `max(0px, ...)` zalatwia
       waski ekran, gdzie panel bierze cala scene i wciecia nie ma. Liczone
       z tej samej zmiennej, co szerokosc panelu, wiec krawedzie nie rozjada
       sie przy zmianie tamtej wartosci. */
    right: max(0px, calc((100% - var(--panel-w)) / 2));
    z-index: var(--z-ar);
    width: var(--ar-btn-d);
    height: var(--ar-btn-d);
}

/* Pierscien pulsujacy. Osobny pseudoelement, a nie cien przycisku: ma
   rosnac i gasnac niezaleznie od tla przycisku, ktore zostaje nieruchome. */
.stage__ar::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: var(--r-pill);
    border: 2px solid var(--c-accent);
    animation: arPulse var(--t-ar-pulse) ease-out infinite;
    pointer-events: none;
}

/* Puls NIE jest wylaczany przy `prefers-reduced-motion` i jest to ta sama
   swiadoma decyzja, co przy maskotkach i kotwicach (patrz .robot--steps oraz
   komponent Anchors): wlasciciel ma to ustawienie wlaczone w systemie
   i zglaszal juz dwa razy jako usterke, ze "nic sie nie animuje". Powrot do
   poszanowania ustawienia to dopisanie tu bloku @media z `animation: none`. */
@keyframes arPulse {
    0%   { transform: scale(1);   opacity: 0.55; }
    70%  { transform: scale(1.9); opacity: 0; }
    100% { transform: scale(1.9); opacity: 0; }
}

.ar-btn {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1.5px solid var(--c-accent);
    border-radius: var(--r-pill);
    /* Tlo TAKIE SAMO, co w kolach krokow na Wdrozeniu (wskazanie wlasciciela):
       lekko przezroczyste, bez ciemnej plamy - poswiata plus 5% krycia
       akcentu. Wczesniej stalo tu rgba(--rgb-bg-deep, 0.92), czyli prawie
       nieprzezroczysta ciemna kropka.
       Warstwa kompozytora od `backdrop-filter` powstaje TYLKO wtedy, gdy
       przycisk jest widoczny - w pozostalych przypadkach rodzic ma
       `display: none`. To ta sama pulapka, ktora w aplikacji 3DMenu
       kosztowala setki MB pamieci karty przy kilkunastu ukrytych warstwach. */
    background: var(--glow-step), rgba(var(--rgb-accent-deep), 0.05);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    box-shadow: var(--shadow-ar);
    color: var(--c-accent-vivid);
    cursor: pointer;
    transition: background var(--t-fast) var(--ease),
                transform var(--t-fast) var(--ease);
}

.ar-btn:hover,
.ar-btn:focus-visible { background: rgba(var(--rgb-accent), 0.18); }

.ar-btn:active { transform: translateY(1px); }

.ar-btn svg {
    width: var(--ar-btn-icon);
    height: var(--ar-btn-icon);
}


/* Przerywany okrag i dwie poswiaty - czysta dekoracja, poza drzewem dostepnosci. */
.stage__ring,
.stage__glow {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
}

.stage__ring {
    width: var(--stage-ring);
    height: var(--stage-ring);
    border: 1.5px dashed var(--c-ring-dash);
    border-radius: 50%;
}

/* Wewnetrzny okrag pod obiektem 3D. Pseudoelement, a nie nowy <span>: to czysta
   dekoracja, a rodzic jest juz `position: absolute`, wiec sluzy za uklad
   odniesienia - markup zostaje bez zmian.
   Kreska ciagla i grubsza od przerywanej (4px przy 1,5px) -
   dokladnie ten uklad, co para linii wokol ikony w kafelkach "proof": cienka
   kreska, mala przerwa, grubsza kreska. Dzieki temu scena i kafelki maja ten
   sam znak. */
.stage__ring::before {
    content: '';
    position: absolute;
    /* `inset` liczy sie od wewnetrznej krawedzi kreski przerywanej, wiec ta
       jedna wartosc JEST widoczna przerwa miedzy oboma okregami. */
    inset: var(--stage-ring-inner-gap);
    /* Kreska jest grubsza od przerywanej, ale BARDZIEJ przezroczysta (0,20
       zamiast 0,32) - przy 4 px pelniejszy kolor robil sie twarda obrecza. */
    border: var(--stage-ring-inner-w) solid var(--c-border-accent-dim);
    border-radius: 50%;
}

.stage__glow {
    width: var(--stage-glow);
    height: var(--stage-glow);
    background: var(--glow-stage);
}

.stage__glow--floor {
    top: 76%;
    width: min(50vw, 55rem, 67vh);
    height: min(24vw, 26.25rem, 32vh);
    background: var(--glow-floor);
}

/* Scena 3D. Zero WebP-renderow pod spodem (byla tu wczesniej "talia dan" -
   usunieta: migala 2D obrazem, zanim zdazyl wstac model). Miejsce zapelnia
   WYLACZNIE <model-viewer> plus stan ladowania (kostka/tekst) na czas, zanim
   pierwszy obiekt zdazy sie pokazac. */
.stage__viewport {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--stage-dish);
    height: var(--stage-dish);
    transform: translate(-50%, calc(-50% - var(--stage-lift)));
    display: block;
}

/* Scena 3D wchodzi przenikaniem. Czasy przeniesione z banera 3DMenu: wejscie
   1,25 s (spokojne), zejscie 0,45 s (szybsze, zeby przerwa miedzy obiektami
   byla krotka). */
.stage__viewer {
    width: 100%;
    height: 100%;
    background: transparent;
    --poster-color: transparent;
    opacity: 0;
    transition: opacity 1.25s cubic-bezier(0.33, 0, 0.25, 1);
}

.stage__viewer.is-visible { opacity: 1; }

.stage__viewer.is-hiding {
    opacity: 0;
    transition: opacity 0.45s ease-in;
}

/* Wlasny pasek postepu i przycisk AR biblioteki - scena ma wlasny panel,
   a AR wchodzi dopiero przy wersji mobilnej. */
.stage__viewer::part(default-progress-bar) { display: none; }
.stage__viewer::part(default-ar-button) { display: none; }

/* --- Stan ladowania (kostka) ----------------------------------------------
   Ten sam obracajacy sie szescian, co w preloaderze calego banera 3DMenu
   (patrz .promo-cover / .cube w 3dmenu/static/v1/css/main.css) - tu w skali
   samej sceny, nie calej strony. Widoczny WYLACZNIE do pierwszego udanego
   odsloniecia obiektu (patrz Showcase.reveal); kolejne dania juz go nie
   przywracaja, bo miedzy nimi jest zwykle przenikanie modelu. */
/* Kostka ma stac dokladnie w srodku PRZERYWANEGO OKREGU, a nie w srodku
   swojego rodzica: `.stage__viewport` jest podniesiony o --stage-lift (danie
   ma wisiec nad srodkiem sceny), wiec kostka bez korekty siedziala o tyle samo
   za wysoko - zmierzone 60 px na desktopie i 32 px na telefonie. Przesuniecie
   w dol o te sama zmienna sprowadza jej srodek na srodek okregu i trzyma sie
   samo, gdy --stage-lift zmienia sie na progach. */
.stage__cube {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: translateY(var(--stage-lift));
}

.cube-wrap {
    width: var(--cube-size);
    height: var(--cube-size);
    perspective: 50rem;
}

.cube {
    position: relative;
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
    animation: spinCube 3s infinite linear;
}

.cube__face {
    position: absolute;
    width: var(--cube-size);
    height: var(--cube-size);
    border: 2px solid var(--c-accent-vivid);
    background: transparent;
    box-shadow: inset 0 0 0.625rem rgba(var(--rgb-accent), 0.15),
                0 0 0.5rem rgba(var(--rgb-accent), 0.2);
}

.cube__face--front  { transform: rotateY(0deg)    translateZ(calc(var(--cube-size) / 2)); }
.cube__face--back   { transform: rotateY(180deg)  translateZ(calc(var(--cube-size) / 2)); }
.cube__face--right  { transform: rotateY(90deg)   translateZ(calc(var(--cube-size) / 2)); }
.cube__face--left   { transform: rotateY(-90deg)  translateZ(calc(var(--cube-size) / 2)); }
.cube__face--top     { transform: rotateX(90deg)  translateZ(calc(var(--cube-size) / 2)); }
.cube__face--bottom  { transform: rotateX(-90deg) translateZ(calc(var(--cube-size) / 2)); }

/* Tekst zapasowy: brak WebGL, blad biblioteki, brak JavaScriptu. Ten sam
   wzorzec, co reszta projektu 3DMenu ("podglad 3D niedostepny" zamiast
   pustego okna). Domyslnie widoczny (dziala bez JS) - skrypt go chowa, gdy
   scena realnie rusza. */
.stage__fallback {
    position: absolute;
    inset: auto 0 0;
    text-align: center;
    font-size: var(--fs-xs);
    color: var(--c-text-50);
}

/* --- Panel demo ---------------------------------------------------------- */

.demo-panel {
    position: absolute;
    left: 50%;
    /* Panel wystaje pod scene i o to wystawanie pomniejszony jest odstep do
       dolnej krawedzi pasma. Bylo -2% (ok. 12 px) i 46 px odstepu; -6% daje
       ok. 36 px wystawania i 22 px odstepu, czyli o polowe mniej. */
    bottom: -6%;
    z-index: var(--z-content);
    transform: translateX(-50%);
    width: var(--panel-w);
    max-width: 90%;
    padding: var(--sp-5) var(--sp-6) 1.25rem;
    border: 1px solid var(--c-border-accent-soft);
    border-radius: var(--r-xl);
    /* 0,24 zamiast 0,38 - panel ma przepuszczac wiecej kadru. Sama wartosc
       krycia nie kosztuje nic: kompozytor i tak sklada te warstwe. Kosztuje
       `backdrop-filter` ponizej, ale on stal tu wczesniej i zostaje bez zmian -
       to on robi czytelnosc napisow przy tak slabym tle. */
    background: rgba(var(--rgb-bg), 0.24);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}

.demo-panel__head {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    margin-bottom: var(--sp-5);
}

.demo-panel__label {
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: 0.18em;
    color: var(--c-accent-vivid);
    /* Napis jest teraz zdaniem, a nie trzyznakowa etykieta - na najwezszych
       telefonach lamie sie na dwie linie i bez tego druga linia stawalaby
       przy lewej krawedzi panelu. */
    text-align: center;
}

.demo-panel__row {
    display: flex;
    align-items: center;
    /* Okno miniatur ma teraz stala szerokosc i nie rozpycha wiersza, wiec
       strzalki razem z nim wysrodkowujemy w panelu. */
    justify-content: center;
    gap: var(--sp-3);
}

.demo-panel__arrow {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.125rem;
    height: 2.125rem;
    padding: 0;
    border: 1px solid var(--c-border-mid);
    border-radius: var(--r-pill);
    background: transparent;
    color: var(--c-text-arrow);
    transition: border-color var(--t-base) var(--ease), color var(--t-base) var(--ease);
}

.demo-panel__arrow:hover,
.demo-panel__arrow:focus-visible {
    border-color: var(--c-border-accent-lit);
    color: var(--c-accent-lit);
}

/* Okno na piec miniatur. Szerokosc liczona wprost (5 sztuk + 4 odstepy) -
   MUSI zgadzac sie z CONFIG.showcase.thumbWindow w core.js. `overflow:hidden`
   wycina wszystko poza oknem; to co pod spodem (.demo-panel__track) jest
   szersze i to ono sie przesuwa, nie ta ramka. */
.demo-panel__thumbs {
    /* `0 0 auto`, NIE `1 1 auto`: przy `flex-grow: 1` okno rozpychalo sie do
       calej szerokosci wiersza (zmierzone 336 px zamiast wyliczonych 326 px),
       a te nadmiarowe 10 px wpuszczaly do kadru 1 px obramowania szostej,
       niewidocznej miniatury - przy przewijaniu widac bylo kreske przy
       krawedzi. Okno ma miec DOKLADNIE szerokosc pieciu kafelkow. */
    flex: 0 0 auto;
    min-width: 0;
    width: calc(var(--thumb-size) * 5 + var(--thumb-gap) * 4);
    overflow: hidden;
}

/* Pasek WSZYSTKICH miniatur w rzedzie - szerszy niz okno, ktore go tnie.
   Przesuwa go WYLACZNIE `--window-start` (liczba calkowita, ustawiana z JS
   w Showcase.paintThumbs): CSS sam przelicza ja na piksele przez `calc`,
   wiec skrypt nigdy nie liczy pikseli i nie musi znac biezacego rem-a.
   Plynny "premium" slajd zamiast skoku is-shown/display:none sprzed
   poprzedniej wersji - zgloszenie wlasciciela ("bum-bum" zamiast smooth). */
.demo-panel__track {
    display: flex;
    gap: var(--thumb-gap);
    transform: translateX(calc(-1 * var(--window-start, 0) * (var(--thumb-size) + var(--thumb-gap))));
    /* Dluzej i lagodniej niz zwykle przejscie interfejsu (0,25 s): pasek jedzie
       o cala strone, wiec przy krotkim czasie czyta sie jak przeskok, a nie
       jak przewijanie. Krzywa bez przyspieszenia na starcie - ruch zaczyna sie
       miekko i wyhamowuje.
       Skrypt odmierza ten sam czas (CONFIG.showcase.slideMs), zeby po dojezdzie
       wrocic z drugiej kopii kafelkow do pierwszej - patrz Showcase.pageBy. */
    transition: transform var(--t-thumb-slide) cubic-bezier(0.22, 0.61, 0.36, 1);
}

.thumb {
    flex: 0 0 auto;
    width: var(--thumb-size);
    height: var(--thumb-size);
    padding: var(--sp-1);
    border: 1px solid var(--c-border-thin);
    border-radius: var(--r-md);
    background-color: var(--c-fill-thumb);
    transition: border-color var(--t-base) var(--ease),
                background-color var(--t-base) var(--ease),
                box-shadow var(--t-base) var(--ease);
}

/* Miniatura jako <img>, a nie tlo z projektu: sciezka do pliku zostaje wtedy
   w markupie zamiast w atrybucie `style`, a przegladarka moze ja pobrac
   rownolegle z reszta strony, nie dopiero po wyliczeniu stylow. Plik ma 116 px
   - dwukrotnosc rozmiaru na ekranie - wiec caly pasek waza 48 kB zamiast
   700 kB, ktore zajmowalyby pelne rendery uzyte w skali. */
.thumb__img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* Zwykle podswietlenie - obwodka i wypelnienie, BEZ poswiaty. Zgloszenie
   wlasciciela: box-shadow z rozmyciem wychodzil poza zaokraglony brzeg
   kafelka i na ciemnym tle wygladal jak brzydki prostokat pod spodem; glow
   jest tez po prostu drozszy do przemalowania niz plaska zmiana koloru. */
.thumb.is-active {
    border-color: var(--c-accent);
    background-color: var(--c-fill-accent);
}


/* ==========================================================================
   12. FAQ
   ========================================================================== */

/* Calosc malowania tla niesie wspolna klasa .band--photo (sekcja 5). */
.faq { position: relative; }

/* Poswiata przy gornej krawedzi pasma - laczy sekcje z hero powyzej. */
.faq::before {
    content: '';
    position: absolute;
    inset: 0 0 auto;
    height: 21.25rem;
    background: var(--glow-faq);
    pointer-events: none;
}

.faq__grid {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, var(--col-narrow)) minmax(0, var(--col-wide));
    gap: var(--sp-6);
    align-items: start;
    padding-block: var(--sp-12) 6.5rem;
}

/* Kolumna rozciagnieta na wysokosc calego wiersza siatki (grid ma
   `align-items: start`, wiec trzeba o to poprosic wprost) i ulozona jako flex
   w pionie - tylko po to, zeby maskotka mogla zjechac na sam dol przez
   `margin-top: auto`. Przy 19 pytaniach lista po prawej jest duzo wyzsza od
   tekstu po lewej i pod maskotka robila sie dziura. `flex-start` na przekroju
   zostawia dzieciom ich dotychczasowe szerokosci (przycisk i tytul same
   dobieraja szerokosc do tresci). */
.faq__intro {
    align-self: stretch;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    padding: 2.25rem var(--sp-6) 0 0;
}

.faq__title {
    margin-bottom: 1.125rem;
    font-size: var(--fs-h2);
    line-height: 1.15;
    font-weight: 700;
    letter-spacing: var(--ls-tight);
}

.faq__title span { display: block; white-space: nowrap; }

.faq__lead {
    max-width: 27.5rem;
    margin-bottom: 2.125rem;
    font-size: var(--fs-lead);
    line-height: 1.6;
    color: var(--c-text-60);
}

.faq__robot {
    display: flex;
    align-self: stretch;      /* rodzic ma `flex-start`, a maskotka ma byc wysrodkowana */
    justify-content: center;
    /* Dwa marginesy `auto` dziela wolne miejsce kolumny po rowno, wiec maskotka
       stoi w polowie drogi miedzy przyciskiem a dolem listy pytan - a ta ma
       zmienna wysokosc, wiec stala wartosc by tego nie utrzymala. W ukladzie
       jednokolumnowym oba `auto` schodza do zera i minimalny odstep stoi
       osobno, w media query 1080px.
       `padding-bottom` podnosi maskotke o POLOWE swojej wartosci, bo powieksza
       jej pudelko w dol i kazdy z dwoch `auto` maleje o polowe. */
    margin-block: auto;
    padding-bottom: 5rem;
    pointer-events: none;
    /* W markupie maskotka stoi PRZED akapitem - tak, zeby na telefonie tekst
       mogl ja oplywac (float dziala tylko w przod). Tutaj, w kolumnie flex,
       `order` odsyla ja z powrotem na koniec, wiec uklad dwukolumnowy wyglada
       dokladnie jak dotad. */
    order: 1;
}

/* Maskotka FAQ celowo BEZ cienia. Rozwijanie pytania przesuwa ja w kolumnie,
   a `filter: drop-shadow` przeliczal sie wtedy od nowa z calego obrazka przy
   kazdej klatce - zmierzone 46,8 ms zamiast 21,6 ms. Sam cien byl tu i tak
   niewidoczny (1-3 jednostki na 255 na ciemnym tle). Maskotki w naglowkach
   podstron cien zachowuja: tam tlo jest jasniejsze i nic nie zmienia wysokosci. */
.faq__robot img { transform: translateZ(0); }

/* Kreska pod pytaniem slabsza niz dotad (bylo --c-border-strong, 20%): lista
   ma dwanascie pozycji, wiec mocna linia dwanascie razy pod rzad zaczynala
   dominowac nad trescia. Wartosc wskazana przez wlasciciela to dokladnie
   --c-border-soft. */
.faq__item { border-bottom: 1px solid var(--c-border-soft); }

/* Skrot do kontaktu pod lista pytan - patrz markup w index.html. Na szerokim
   ekranie go nie ma: tam odnosniki w naglowku i stopce sa w zasiegu wzroku,
   a przycisk dublowalby wezwanie z sekcji kontaktu. */
.faq__cta { display: none; }

.faq__question {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-5);
    width: 100%;
    padding: var(--sp-5) var(--sp-1);
    background: transparent;
    border: none;
    color: var(--c-text-strong);
    font-size: 1.09375rem;
    font-weight: 500;
    text-align: left;
    transition: color var(--t-fast) var(--ease);
}

.faq__question:hover { color: var(--c-accent-lit); }

/* Otwarte pytanie przejmuje zielen etykiety "FAQ" (.eyebrow, --c-accent), zeby
   bylo widac, ktora pozycja jest rozwinieta, takze gdy odpowiedz nie miesci sie
   w kadrze. Regula stoi PO :hover, bo obie maja te sama szczegolowosc - a stan
   otwarcia ma byc wazniejszy niz najechanie kursorem. */
.faq__question[aria-expanded='true'] { color: var(--c-accent); }

.faq__chevron {
    flex: 0 0 auto;
    color: var(--c-accent-soft);
    transition: transform var(--t-faq) var(--ease-faq);
}

.faq__question[aria-expanded='true'] .faq__chevron { transform: rotate(180deg); }

/* Rozwijanie przez `grid-template-rows` (0fr <-> 1fr), nie przez max-height:
   max-height animuje ZAPAS, nie rzeczywista wysokosc, wiec ruch konczyl sie po
   79 ms z deklarowanych 350 i reszta byla bezruchem. `fr` interpoluje sie do
   faktycznej wysokosci tresci.
   STAN DOMYSLNY JEST OTWARTY - bez skryptu wszystkie odpowiedzi sa widoczne;
   zwija je dopiero JS. */
.faq__answer {
    display: grid;
    /* `minmax(0, ...)`, a nie samo `1fr`. Zapis `0fr` znaczy `minmax(auto, 0fr)`,
       a to `auto` bierze minimalna wysokosc tresci - razem z dolnym dopelnieniem
       akapitu (var(--sp-6)), ktorego zadne `min-height` nie zdejmuje. Zwiniete
       pytanie zostawialo przez to 24 px paska (zmierzone). Jawne zero jako
       dolna granica sciezki usuwa te podloge. */
    grid-template-rows: minmax(0, 1fr);
    overflow: hidden;
    transition: grid-template-rows var(--t-faq) var(--ease-faq);
}

/* Odnosnik na koncu odpowiedzi - w tym samym akapicie (patrz komentarz
   w markupie), ale w osobnym wierszu. Podkreslenie, bo to jedyny odnosnik
   wewnatrz biegnacego tekstu i bez niego nie odroznialby sie od zdania. */
.faq__more {
    display: block;
    margin-top: var(--sp-3);
}

.faq__more a {
    color: var(--c-accent);
    text-decoration: underline;
    text-underline-offset: 3px;
}

.faq__answer[data-open='false'] { grid-template-rows: minmax(0, 0fr); }

/* ...ale gdy skrypt JEST, odpowiedzi maja byc zwiniete juz od pierwszej klatki.
   Klase `js` doklada skrypt w <head> kazdej podstrony, czyli przed pierwszym
   malowaniem; komponent FAQ dopisuje `data-open` dopiero po sparsowaniu
   dokumentu. Bez tej reguly gosc, ktory wchodzi wprost na #faq, widzi jak
   dwanascie odpowiedzi składa sie na jego oczach - zgloszenie wlasciciela. */
.js .faq__answer:not([data-open]) { grid-template-rows: minmax(0, 0fr); }

.faq__answer p {
    /* Element siatki musi miec jawnie zdjeta minimalna wysokosc, inaczej nie
       da sie go scisnac ponizej wysokosci tresci i wiersz nigdy nie dojdzie
       do zera. Przyciecie siedzi tutaj, a nie na rodzicu, bo to ten element
       jest sciskany. */
    min-height: 0;
    overflow: hidden;
    /* Bez przenikania tekstu: `opacity` na rozwijanej odpowiedzi wymusza wlasna
   warstwe przy KAZDEJ klatce razem ze zmiana wysokosci, wiec kompozytor nie
   sklada jej za darmo. Sam ruch wysokosci wystarcza. */
    padding: 0 3.75rem var(--sp-6) var(--sp-1);
    font-size: var(--fs-body);
    line-height: 1.65;
    color: var(--c-text-60);
}


/* ==========================================================================
   13. TABELE
   ==========================================================================
   Siatka jednopikselowa: KAZDA komorka ma pelna krawedz, a `border-collapse`
   sklada sasiadujace w jedna linie. Bez tego przy `separate` linie miedzy
   komorkami sa podwojne i tabela robi sie "gruba".
   ========================================================================== */



/* ==========================================================================
   14. CENNIK
   ========================================================================== */

.pricing { padding-block: var(--sp-9) var(--sp-11); }

.pricing__switch {
    display: flex;
    justify-content: center;
    margin-bottom: 3.25rem;
}

.pricing__tabs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-2);
    width: calc(33.3333% - 0.91666rem);
    min-width: 17.5rem;
    padding: var(--sp-1);
    border: 1.5px solid var(--c-border-tabs);
    border-radius: var(--r-pill);
    background: rgba(var(--rgb-bg-pill), 0.13);
}

.pricing__tab {
    padding: 0.625rem 1.25rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    color: var(--c-text-50);
    font-size: var(--fs-sm);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    transition: color var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease);
}

.pricing__tab:hover {
    background: var(--c-fill-hover);
    color: var(--c-text-85);
}

.pricing__tab[aria-selected='true'] {
    border-color: var(--c-accent);
    background: var(--c-fill-accent);
    color: var(--c-accent-soft);
    box-shadow: var(--shadow-tab);
}

.pricing__grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-5);
    align-items: stretch;
}

.plan {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 2.125rem 2.125rem var(--sp-8);
    border: 1px solid var(--c-border-accent);
    border-radius: var(--r-lg);
    background: var(--grad-plan);
}

.plan--popular {
    border-color: var(--c-border-popular);
    background: var(--grad-plan-pop);
}

/* Poswiata karty polecanego pakietu. Pseudoelement lezy pod trescia
   karty i nie lapie klikniec. Celowo `::before`, a nie `::after`: plakietka
   "polecany" tez jest pozycjonowana i wystaje ponad obrys karty, czyli lezy
   dokladnie w zasiegu zewnetrznego cienia. Przy z-index `auto` o kolejnosci
   malowania decyduje kolejnosc w drzewie, wiec poswiata musi byc pierwsza. */
.plan--popular::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: var(--glow-plan);
    animation: glowPulse var(--t-glow) ease-in-out infinite;
    pointer-events: none;
}

.plan__badge {
    position: absolute;
    top: -1.125rem;
    left: 50%;
    transform: translateX(-50%);
    padding: 0.5625rem 1.375rem;
    border-radius: var(--r-pill);
    background: linear-gradient(90deg, var(--c-accent-vivid), var(--c-gold));
    color: var(--c-on-accent);
    font-size: var(--fs-eyebrow);
    font-weight: 800;
    letter-spacing: 0.14em;
    white-space: nowrap;
    box-shadow: var(--shadow-badge);
}

.plan__name {
    margin-top: 1.125rem;
    text-align: center;
    font-size: 1.375rem;
    font-weight: 800;
    letter-spacing: 0.18em;
}

.plan__pricing {
    margin: var(--sp-5) 0 1.75rem;
    text-align: center;
}

/* Cena rocznie: przekreslona suma dwunastu miesiecy i kwota oszczednosci.
   Oba wiersze istnieja w markupie takze w trybie miesiecznym - przelacznik
   tylko je odslania, wiec Google widzi obie ceny bez uruchamiania skryptu. */
.plan__old {
    margin-bottom: -0.375rem;
    font-size: 2.125rem;
    font-weight: 600;
    color: var(--c-text-55);
    text-decoration: line-through;
    text-decoration-color: var(--c-gold-line);
    text-decoration-thickness: 2px;
}

.plan__save {
    display: inline-block;
    margin-bottom: 1.125rem;
    font-size: var(--fs-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--c-accent-soft);
}

.plan__pricing[data-billing='mc'] .plan__old,
.plan__pricing[data-billing='mc'] .plan__save { display: none; }

.plan__amount {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    gap: var(--sp-2);
}

.plan__value {
    font-size: var(--fs-price);
    font-weight: 900;
    line-height: 1;
    letter-spacing: var(--ls-tight);
}

.plan__currency {
    padding-top: var(--sp-1);
    font-size: 1.1875rem;
    font-weight: 800;
    line-height: 1;
    color: var(--c-text-70);
}

.plan__period {
    margin-top: 0.5625rem;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--c-accent-soft);
}

.plan__features {
    display: flex;
    flex-direction: column;
    gap: 0.9375rem;
    flex: 1 1 auto;
}

.plan__feature {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    font-size: var(--fs-sm);
    line-height: 1.4;
    color: var(--c-text-85);
}

.plan__check { flex: 0 0 auto; }

.plan__cta { margin-top: 2.125rem; }


/* ==========================================================================
   15. WDROZENIE - KROKI
   ========================================================================== */

.steps { padding-block: 4.5rem var(--sp-11); }

.steps__grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: start;
    /* Ujemny margines rozsuwa okregi tak, zeby dotykaly sie krawedziami -
       w projekcie tworza schodkowy lancuch, a nie cztery osobne kafelki. */
    margin-inline: -1.375rem;
}

.step {
    display: flex;
    justify-content: center;
    padding-inline: 0.375rem;
    /* Schodek: kazdy kolejny krok opada o 72 px. Odsuniecie liczy `nth-child`,
       a nie atrybut `style` w markupie - inaczej uklad wracalby do HTML-a. */
}

.step:nth-child(2) { margin-top: 4.5rem; }
.step:nth-child(3) { margin-top: 9rem; }
.step:nth-child(4) { margin-top: 13.5rem; }

.step__disc {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* KOLO MA BYC KOLEM, nie jajem - twarde wymaganie wlasciciela, wiec
       ksztalt pilnuje `aspect-ratio: 1`, a nie para width/height. Roznica jest
       istotna: przy `min-height` tresc dluzsza od kola rozpychala je w dol
       i robil sie owal (zmierzone 240 x 410, czyli 1:1,7). Teraz srednica
       moze rosnac, ale wysokosc zawsze idzie za szerokoscia.
       Skoro ksztalt jest sztywny, to tresc musi sie w nim zmiescic - stad
       srednica liczona od SZEROKOSCI KOLUMNY (100%) z sufitem, a nie ze stalej
       liczby: w waskiej kolumnie kolo bierze cala dostepna szerokosc, wiec ten
       sam tekst lamie sie na mniej wierszy. */
    /* Srednica jako ZMIENNA, bo od niej liczy sie tez dopelnienie. Bez tego
       `padding: 16%` liczy sie od KONTENERA, a nie od kola - a to nie to samo:
       w jednej kolumnie kontener ma ok. 790 px, wiec 16% dawalo 126 px zamiast
       56 px, tekst byl sciskany do polowy szerokosci i rozpychal wysokosc
       (zmierzone przy 859 px: kolo 352 szerokie, ale 532 wysokie). Liczone od
       --step-d dopelnienie jest zawsze te samo wzgledem kola. */
    width: var(--step-d);
    aspect-ratio: 1;
    flex: 0 0 auto;
    padding: calc(var(--step-d) * 0.15);
    border-radius: 50%;
    /* ======================================================================
       PROBA: KOLO Z WYPELNIENIEM I ROZMYCIEM
       ======================================================================
       Zamowienie wlasciciela: ten sam kolor, co grubszy obrys wewnetrzny
       (--c-border-accent-dim, czyli --rgb-accent-deep przy kryciu 0,20), tylko
       mocniej przezroczysty, plus lekkie rozmycie tla pod kolem.

       KOSZT, ktory trzeba znac przed decyzja: `backdrop-filter` zaklada
       WLASNA warstwe kompozytora i trzyma ja przez caly czas, takze wtedy, gdy
       kolo jest wygaszone do zera. Tu takich warstw sa CZTERY, wszystkie stale
       widoczne, a do tego kola zmieniaja krycie przy kazdej klatce
       przewijania - czyli kompozytor przelicza rozmycie w ruchu. W tym samym
       arkuszu rozmycie szuflady, zaslon i listy jezykow jest CELOWO bramkowane
       stanem `.is-open` i stoi przy tym komentarz o setkach MB pamieci karty
       w aplikacji. Tutaj bramki nie ma i byc nie moze, bo kolo jest widoczne
       zawsze.

       ROLLBACK: skasowac trzy linie ponizej i przywrocic
       `background: var(--glow-step);`. */
    background: var(--glow-step), rgba(var(--rgb-accent-deep), 0.05);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    text-align: center;
}

/* Obrot jest zatrzymany, dopoki gosc nie najedzie na krok. Zapetlona animacja
   na czterech okregach naraz to cztery warstwy kompozytora malowane bez
   przerwy - na telefonie widac to od razu na zuzyciu baterii. */
.step__ring {
    position: absolute;
    inset: 0;
    z-index: 2;
    border: 1.5px dashed var(--c-border-accent-soft);
    border-radius: 50%;
    transition: border-color var(--t-base) var(--ease);
    animation: ringCW var(--spin-ring) linear infinite;
    animation-play-state: paused;
}

/* DRUGI, WEWNETRZNY OBRYS - wskazanie wlasciciela, wzorem przycisku jezyka
   w naglowku: tam ring zewnetrzny daje obrys przycisku, a wewnetrzny rysuje
   sama ikona (okrag globusa), i miedzy nimi zostaje swiatlo. Krok mial dotad
   jeden obrys, wiec czytal sie plasko obok tamtego.

   Zewnetrzny zostaje PRZERYWANY i to jest swiadome: kreskowany okrag wiaze
   krok z ta sama rodzina form, co pierscien pod scena 3D (.stage__ring)
   i kafelki "AR W PRAKTYCE" - w referencji oba obrysy sa ciagle, ale tam nie
   ma czego wiazac. Wewnetrzny jest za to ciagly i slabszy, zeby czytal sie
   jako echo zewnetrznego, a nie jako drugi rownorzedny okrag.

   Proporcje wzieta WPROST z .stage__ring, czyli z duzego okregu pod scena 3D,
   tylko przeskalowane do mniejszego kola - polecenie wlasciciela. Uklad jest
   tam odwrotny do intuicji i o to wlasnie chodzi: kreska wewnetrzna jest
   GRUBSZA od zewnetrznej (tam 4 px przy 1,5 px), a przerwa miedzy nimi MALA
   (6 px). Za pierwszym razem zrobilem odwrotnie - cienka kreske daleko od
   obrysu - i wygladalo to jak dwa osobne okregi zamiast jednego znaku.
   Kreska wewnetrzna jest grubsza, ale BARDZIEJ przezroczysta
   (--c-border-accent-dim, 0,20 wobec 0,32): przy pelniejszym kolorze robi sie
   z niej twarda obrecz. Ten sam powod jest opisany przy .stage__ring.
   `inset` w rem, nie w procentach: przerwa ma zostac optycznie taka sama
   niezaleznie od tego, jak duze jest kolo - procent rozjezdzalby ja razem
   ze srednica. */
.step__ring::after {
    content: '';
    position: absolute;
    inset: 0.25rem;
    /* 4 px, nie 3 - wskazanie wlasciciela. Tyle samo, co kreska wewnetrzna
       w duzym okregu pod scena 3D (--stage-ring-inner-w), wiec oba znaki maja
       teraz identyczna grubosc. */
    border: 4px solid var(--c-border-accent-dim);
    border-radius: 50%;
}

.step:nth-child(even) .step__ring { animation-name: ringCCW; }

.step:hover .step__ring,
.step:focus-within .step__ring {
    border-color: var(--c-border-accent-lit);
    animation-play-state: running;
}

.step__n {
    margin-bottom: var(--sp-3);
    font-size: 1.875rem;
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0.04em;
    color: var(--c-accent-soft);
    text-shadow: var(--shadow-num);
}

.step__title {
    margin-bottom: var(--sp-2);
    font-size: 1.3125rem;
    line-height: 1.2;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.step__lead {
    margin-bottom: var(--sp-2);
    font-size: var(--fs-sm);
    line-height: 1.45;
    font-weight: 600;
    color: var(--c-text-88);
}

.step__desc {
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--c-text-50);
}

.steps__cta {
    display: flex;
    justify-content: center;
    margin-top: var(--sp-10);
}

/* Przycisk pod krokami jest o ok. 20% wiekszy od pozostalych - to glowne
   wezwanie tej podstrony, wiec ma sie wybijac rozmiarem, a nie innym stylem.
   Skalujemy tekst i oba dopelnienia w tej samej proporcji, zeby proporcje
   pigulki zostaly te same: 17 -> 20.4 px, 15 -> 18 px, 32 -> 38.4 px. */
.steps__cta .btn {
    padding: 1.125rem 2.4rem;
    font-size: 1.275rem;
}


/* ==========================================================================
   16. KONTAKT - DANE I FORMULARZ
   ========================================================================== */

.contact { padding-block: 4.5rem var(--sp-11); }

.contact__grid {
    display: grid;
    grid-template-columns: minmax(0, var(--col-narrow)) minmax(0, var(--col-wide));
    gap: var(--sp-7);
    align-items: start;
}

/* Polozenie trzech klockow siatki podane WPROST. Automat ustawialby je inaczej:
   karta formularza brala caly pierwszy wiersz drugiej kolumny, a jego wysokosc
   (897 px przy oknie 1440) stawala sie wysokoscia CALEGO wiersza - maskotka
   ladowala wiec dopiero pod karta, a lewa kolumna konczyla sie pod lista
   kontaktow i miala ponizej 490 px pustego pola (zgloszenie wlasciciela:
   "w wersji desktopowej ten robot nie byl na samym dole, byl w lewej kolumnie,
   kolo formularza").
   Rozpiecie karty na DWA wiersze rozklada jej wysokosc na oba, wiec maskotka
   wraca obok formularza, a `margin-top: auto` z reguly `.robot--inline`
   (sekcja 9) dosuwa ja do dolu kolumny - jej dolna krawedz staje rowno z dolem
   karty. Tamten margines byl tam od poczatku wlasnie po to; nie mial czego
   dosuwac, dopoki maskotka siedziala we wlasnym wierszu pod karta.
   Wszystkie trzy elementy, a nie samo rozpiecie karty: wynik jest ten sam, ale
   zmiana kolejnosci w markupie nie przestawi go po cichu. */
.contact__grid > .contact__aside { grid-column: 1; grid-row: 1; }
.contact__grid > .form-card      { grid-column: 2; grid-row: 1 / span 2; }
.contact__grid > .robot--inline  { grid-column: 1; grid-row: 2; }

.contact__aside {
    display: flex;
    flex-direction: column;
    padding: var(--sp-2) var(--sp-6) var(--sp-2) 0;
}

.contact__title {
    margin-bottom: var(--sp-8);
    font-size: var(--fs-h3);
    font-weight: 700;
    letter-spacing: var(--ls-tight);
}

.contact__title em {
    font-style: normal;
    color: var(--c-accent-vivid);
}

.contact__list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
}

/* Odstep nad naglowkiem godzin - `.eyebrow` sam z siebie nie ma marginesu
   gornego (przy hero pod naglowkiem sekcji nie jest potrzebny), tutaj stoi
   zaraz po liscie kontaktow i potrzebuje oddechu. */
.contact__list + .eyebrow { margin-top: var(--sp-7); }

.contact__row {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
}

.contact__icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    color: var(--c-accent-vivid);
}

.contact__label {
    display: block;
    margin-bottom: var(--sp-1);
    font-size: var(--fs-micro);
    font-weight: 500;
    letter-spacing: 0.16em;
    color: var(--c-text-50);
}

.contact__value {
    display: inline-block;
    font-size: 1.1875rem;
    font-weight: 700;
    color: var(--c-text-strong);
}

/* E-mail i telefon to glowne wezwania do dzialania na tej podstronie, a sam
   wiersz tekstu ma ok. 23 px wysokosci - mniej niz polowa wygodnego celu
   dotkniecia. Padding powieksza obszar klikalny, ujemny margines zdejmuje
   skutek uboczny dla odstepow w liscie. */
a.contact__value {
    padding-block: var(--sp-3);
    margin-block: calc(var(--sp-3) * -1);
}

a.contact__value:hover,
a.contact__value:focus-visible { color: var(--c-accent-vivid); }

.form-card {
    padding: 2.75rem 2.5rem;
    border: 1px solid var(--c-border-soft);
    border-top-color: var(--c-border-accent-soft);
    border-radius: var(--r-lg);
    background: var(--grad-card);
}

.form-card__title {
    margin-bottom: var(--sp-2);
    font-size: var(--fs-h3);
    font-weight: 700;
    letter-spacing: var(--ls-tight);
}

.form-card__lead {
    margin-bottom: 2.125rem;
    font-size: var(--fs-body);
    color: var(--c-text-60);
}

.form__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-5) var(--sp-6);
}

/* Pole formularza jest komorka siatki. Bez `min-width:0` dluga podpowiedz
   w <input> rozpycha kolumne ponad przydzielony 1fr i para pol rozjezdza sie
   na waskim ekranie. */
.field { min-width: 0; }

.field--full { grid-column: 1 / -1; }

.field__label {
    display: block;
    margin-bottom: 0.5625rem;
    font-size: var(--fs-xs);
    font-weight: 500;
    color: var(--c-text-80);
}

.field__optional {
    font-weight: 400;
    color: var(--c-text-45);
}

/* Pola dostaja to samo swiatlo, co karta formularza: neutralny obrys z ZIELONA
   GORNA krawedzia. To wlasnie ta jedna krawedz daje wrazenie, ze ramka gdzieniegdzie
   sie swieci - reszta obrysu jest zwyklym, spokojnym konturem.
   Do tego trzyczesciowa poswiata (patrz --glow-field w sekcji 1) i trzy stopnie
   natezenia: spoczynek, kursor nad polem, pole aktywne. */
.field__input {
    width: 100%;
    padding: 0.9375rem 1.125rem;
    border: 1px solid var(--c-border-soft);
    /* Swiatlo pada z gornego LEWEGO rogu: gorna krawedz najmocniej, lewa slabiej,
       prawa i dolna zostaja neutralne. Dzieki dwom krawedziom kolor widac takze
       na narozniku, a nie tylko jako kreske u gory - zgloszenie wlasciciela. */
    border-top-color: var(--c-border-accent-soft);
    border-left-color: var(--c-border-accent-dim);
    border-radius: var(--r-sm);
    background: rgba(var(--rgb-bg-input), 0.5);
    box-shadow: var(--glow-field);
    color: var(--c-text-strong);
    font-size: var(--fs-sm);
    outline: none;
    transition: border-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease);
}

.field__input:hover {
    border-top-color: var(--c-border-accent-lit);
    border-left-color: var(--c-border-accent-soft);
    box-shadow: var(--glow-field-hover);
}

.field__input:focus {
    border-color: var(--c-border-focus);
    border-top-color: var(--c-border-accent-lit);
    border-left-color: var(--c-border-accent-lit);
    box-shadow: var(--glow-field-focus);
}

textarea.field__input { resize: vertical; min-height: 8.75rem; }

/* --- Blad w polu ---------------------------------------------------------
   Komunikat wchodzi DO SRODKA pola, w miejsce podpowiedzi: stoi tam, gdzie
   patrzy gosc, i nie przesuwa ukladu o wysokosc dodatkowego wiersza.
   Kolorem jest zloto witryny, nie czerwien - decyzja wlasciciela. Czerwien
   zostaje przy .form__error, czyli przy nieudanej wysylce. */
.field.is-invalid .field__input {
    border-color: var(--c-border-gold);
    border-top-color: var(--c-border-gold);
    border-left-color: var(--c-border-gold);
    padding-right: 2.75rem;   /* miejsce na znak po prawej */
}

.field.is-invalid .field__input::placeholder {
    color: var(--c-gold);
    opacity: 1;               /* Firefox przycisza podpowiedz domyslnie */
}

/* Wykrzyknik przy prawej krawedzi pola. Wysrodkowany tak samo jak strzalka
   listy: top/bottom i margin auto, wiec nie zalezy od wysokosci pola. */
.field.is-invalid .field__control::before {
    content: '!';
    position: absolute;
    right: 1.125rem;
    top: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    height: 1.25rem;
    margin-block: auto;
    /* Znak rysowany obrysem, nie plama koloru: pelne kolo zlota bilo po oczach
       przy dwoch bledach naraz, a grafika witryny jest kreskowa (patrz ikony
       hologramowe). */
    border: 1px solid var(--c-border-gold);
    border-radius: 50%;
    background: rgba(var(--rgb-gold), 0.10);
    color: var(--c-gold);
    font-size: var(--fs-micro);
    font-weight: 800;
    line-height: 1;
    pointer-events: none;
}

/* Pole wyboru dostaje wlasna strzalke zamiast systemowej. Systemowa stoi tuz
   przy prawej krawedzi ramki i nie da sie jej odsunac - zgloszenie wlasciciela.
   Strzalka jest rysowana dwiema krawedziami obroconego kwadratu, a nie obrazkiem:
   dziedziczy kolor ze zmiennej projektu i skaluje sie razem z tekstem.
   Wewnetrzny odstep z prawej robi jej miejsce, zeby nie wchodzila na tresc. */
select.field__input {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 2.75rem;
    cursor: pointer;
}

/* Opakowanie obejmujace WYLACZNIE liste. Strzalka wisi na nim, a nie na calym
   polu, bo pole zawiera takze etykiete - jego srodek wypadalby miedzy etykieta
   a lista. */
.field__control {
    position: relative;
    display: block;
}

/* Strzalka NALEZY do listy wyboru, wiec selektor zaczyna sie od `.field--select`:
   opakowania `.field__control` uzywaja takze pola tekstowe (miejsce na znak
   bledu) i nie moga dostac chevronu przy okazji.

   Stoi w pionie na srodku listy, i to bez wpisanej odleglosci od dolu:
   `top: 0; bottom: 0` plus `margin-block: auto` wysrodkowuje ja w opakowaniu
   niezaleznie od tego, jaka wysokosc ma lista. Zmiana rozmiaru pisma,
   dopelnienia albo wysokosci pola nie wymaga poprawiania tej reguly. */
.field--select .field__control::after {
    content: '';
    position: absolute;
    /* Wiecej niz wewnetrzny odstep pola (1.125rem), bo obrocony kwadrat wystaje
       poza wlasne pudelko o polowe przekatnej - przy rownej wartosci strzalka
       optycznie kleilaby sie do ramki. Odleglosc wskazana przez wlasciciela. */
    right: 1.27rem;
    top: 0;
    bottom: 0;
    width: 0.5rem;
    height: 0.5rem;
    margin-block: auto;
    border-right: 2px solid var(--c-accent-soft);
    border-bottom: 2px solid var(--c-accent-soft);
    transform: rotate(45deg);
    pointer-events: none;     /* klikniecie w strzalke ma otwierac liste */
}

.field__input::placeholder { color: var(--c-text-35); }

.form__submit { margin-top: 1.875rem; }

/* Przycisk wysylki wypelnia szerokosc karty formularza - tak samo jak przycisk
   pakietu wypelnia szerokosc swojej karty. Poza tym nie ma juz zadnych wlasnych
   deklaracji: ma wygladac dokladnie jak "Wybierz plan". */
.form__submit .btn { width: 100%; }

/* Notka o przetwarzaniu danych. Formularz wysyla je na NASZ serwer i zapisuje
   adres IP zgloszenia, wiec odnosnik do polityki musi byc przy przycisku,
   a nie tylko w stopce. */
.form__note {
    margin-top: var(--sp-4);
    font-size: var(--fs-xs);
    line-height: 1.55;
    color: var(--c-text-45);
}

.form__note a { color: var(--c-accent-lit); }

/* Komunikat bledu wysylki. Czerwien tylko na obrysie i tekscie - pelne czerwone
   tlo bilo by po oczach na ciemnym motywie. `role="alert"` w markupie sprawia,
   ze czytnik ekranu ogłasza tresc od razu po pojawieniu. */
.form__error {
    margin-top: var(--sp-4);
    padding: 0.875rem 1.125rem;
    border: 1px solid var(--c-border-error);
    border-radius: var(--r-sm);
    background: rgba(var(--rgb-error), 0.10);
    color: var(--c-text-error);
    font-size: var(--fs-sm);
}

.form__done {
    padding: 1.75rem;
    border: 1px solid var(--c-border-ghost);
    border-radius: 1.125rem;
    color: var(--c-accent-lit);
    font-size: 1.03125rem;
    line-height: 1.5;
}

/* Po udanej wysylce karta pokazuje SAMO podziekowanie - wskazanie wlasciciela.
   Nadkreslenie, tytul i zacheta stoja poza <form> (dzieci karty), nota
   o zgodzie w nim, ale poza polami. Klase zaklada ContactForm.confirm. */
.form-card.is-sent > :not(form),
.form-card.is-sent .form__note { display: none; }


/* ==========================================================================
   17. DOKUMENT PRAWNY
   ==========================================================================
   Regulamin i polityka prywatnosci to dlugie teksty ciagle - inny material niz
   reszta witryny. Jedna waska kolumna, wyrazna hierarchia naglowkow i nic
   wiecej; wszystko, co ozdobne, tylko rozpraszaloby przy czytaniu.
   ========================================================================== */

.doc {
    max-width: 48rem;
    padding-block: var(--sp-9) var(--sp-11);
}

.doc h2 {
    margin: var(--sp-9) 0 var(--sp-4);
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: var(--ls-tight);
}

.doc h3 {
    margin: var(--sp-7) 0 var(--sp-3);
    font-size: 1.1875rem;
    font-weight: 700;
    color: var(--c-accent-lit);
}

.doc h4 {
    margin: var(--sp-6) 0 var(--sp-3);
    font-size: var(--fs-lead);
    font-weight: 700;
}

.doc h5 {
    margin: var(--sp-5) 0 var(--sp-2);
    font-size: var(--fs-body);
    font-weight: 600;
    color: var(--c-text-85);
}

.doc h2:first-child,
.doc h3:first-child { margin-top: 0; }

/* Bez `text-wrap: pretty` - patrz uzasadnienie przy .page-head__lead (sekcja 8).
   Tu wazy jeszcze wiecej, bo dokument prawny to kilkaset wierszy. */
.doc p {
    margin-bottom: var(--sp-4);
    font-size: var(--fs-body);
    line-height: 1.7;
    color: var(--c-text-72);
}

.doc ul {
    margin-bottom: var(--sp-4);
    padding-left: var(--sp-5);
    list-style: disc;
}

.doc li {
    margin-bottom: var(--sp-2);
    font-size: var(--fs-body);
    line-height: 1.7;
    color: var(--c-text-72);
}

.doc li::marker { color: var(--c-accent); }

.doc__meta {
    margin-bottom: var(--sp-7);
    font-size: var(--fs-xs);
    color: var(--c-text-50);
}

/* Adres administratora - blok danych, nie zdanie. */
.doc__address {
    margin-bottom: var(--sp-4);
    padding: var(--sp-5) var(--sp-6);
    border: 1px solid var(--c-border);
    border-radius: var(--r-sm);
    background: var(--grad-card);
    font-style: normal;
    line-height: 1.7;
    color: var(--c-text-85);
}

/* Ostrzezenie o kopii dokumentu - ma byc widoczne, ale nie krzyczec. */
.doc__note {
    margin-bottom: var(--sp-7);
    padding: var(--sp-4) var(--sp-5);
    border-left: 3px solid var(--c-gold);
    background: var(--c-fill-hover);
    font-size: var(--fs-xs);
    color: var(--c-text-70);
}


/* ==========================================================================
   18. STOPKA
   ========================================================================== */

.site-footer__grid {
    display: grid;
    grid-template-columns: 1.4fr 1fr 1fr 1fr;
    gap: var(--sp-7);
    padding-block: var(--sp-10) var(--sp-7);
}

.site-footer__brand { max-width: 22rem; }

/* Znak marki w stopce PRZYGASZONY, ale w swoich kolorach - decyzja wlasciciela.
   Powod przygaszenia jest ten sam na kazdej szerokosci: belka z logo stoi
   `sticky` u gory, wiec oba znaki bywaja w kadrze naraz i pelny duplikat na
   dole czyta sie jak pomylka. Krycie 0,55 wlasciciel zatwierdzil na telefonie
   (po drodze byla proba z 0,7).
   Odbarwienie zostaje WYLACZNIE na telefonie (prog 860, nizej w arkuszu) -
   decyzja wlasciciela. Przez chwile stalo tutaj, czyli na kazdej szerokosci;
   na desktopie znak ma byc w swoich kolorach, tylko przygaszony. */
.site-footer__logo {
    height: 2.25rem;
    width: auto;
    margin-bottom: var(--sp-4);
    opacity: 0.55;
}

.site-footer__tag {
    font-size: var(--fs-xs);
    line-height: 1.6;
    color: var(--c-text-50);
}

.site-footer__title {
    margin-bottom: var(--sp-4);
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--c-accent);
}

.site-footer__list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

.site-footer__link {
    font-size: var(--fs-xs);
    color: var(--c-text-70);
}

.site-footer__link:hover,
.site-footer__link:focus-visible { color: var(--c-accent-vivid); }

/* Przycisk Cookiebota (baner zgody wpiety w GTM) w rogu ekranu jest schowany -
   wskazanie wlasciciela: na telefonie zaslanial przyciski strony. Zgode zmienia
   sie odnosnikiem "Ustawienia cookies" w stopce (CookieSettings w components.js),
   ktory otwiera to samo okno. Nakladka idzie razem z przyciskiem, bo sluzy
   tylko jemu.
   `!important`, bo arkusz Cookiebota wchodzi PO naszym, a jego specyficznosc
   jest poza nasza kontrola - jedyne miejsce, gdzie przebijamy cudzy styl. */
#CookiebotWidget,
#CookiebotWidgetUnderlay { display: none !important; }

/* Pasek zamykajacy: prawa autorskie i dane rejestrowe. */
.site-footer__bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-6);
    padding-block: var(--sp-5);
    border-top: 1px solid var(--c-border-faint);
    color: var(--c-text-45);
    font-size: var(--fs-xs);
}

.site-footer__legal { text-align: right; }


/* ==========================================================================
   19. SEKCJE TRESCI
   ==========================================================================
   Bloki przeniesione ze starej strony 3dmenu.pl przy okrajaniu tresci:
   korzysci na stronie startowej i pasmo dla sieci.
   ========================================================================== */

/* Uniwersalny naglowek sekcji tresci. Blok "Dlaczego 3Dmenu" (karty-kafelki
   pod hero na stronie startowej) zostal zdjety - byl dodatkiem, o ktory
   wlasciciel nie prosil; strona startowa idzie prosto z hero w FAQ, tak jak
   w oryginalnym projekcie z Claude Design. Ostatnim uzyciem tej klasy bylo
   "Porownanie pakietow" na oferta.html, takze usuniete na prosbe wlasciciela -
   regula zostaje jako gotowa typografia dla nastepnej sekcji tekstowej. */

.why__title {
    margin-bottom: 0.875rem;
    font-size: var(--fs-h2);
    line-height: 1.15;
    font-weight: 700;
    letter-spacing: var(--ls-tight);
}

.card {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    padding: var(--sp-7) var(--sp-6);
    border: 1px solid var(--c-border);
    border-radius: var(--r-lg);
    background: var(--grad-card);
}

.card__title {
    font-size: 1.1875rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.card__text {
    font-size: var(--fs-sm);
    line-height: 1.6;
    color: var(--c-text-60);
}

/* --- Pasmo dla sieci i franczyz (oferta) --------------------------------- */

.enterprise {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-7);
    margin-top: var(--sp-9);
    /* Dopelnienie poziome takie samo jak w kartach pakietow (2.125rem). Obie
       skrzynki maja tez obramowanie 1 px, wiec prawa krawedz tresci wypada
       w obu dokladnie w tym samym miejscu - i przycisk ponizej pokrywa sie
       z przyciskiem trzeciej karty. */
    padding: var(--sp-8) 2.125rem;
    border: 1px solid var(--c-border-accent);
    border-radius: var(--r-lg);
    background: var(--grad-card);
}

.enterprise__title {
    margin-bottom: var(--sp-2);
    font-size: 1.625rem;
    font-weight: 700;
    letter-spacing: var(--ls-tight);
}

.enterprise__text {
    max-width: 38rem;
    font-size: var(--fs-sm);
    line-height: 1.6;
    color: var(--c-text-60);
}

/* Szerokosc odtworzona z siatki cennika, zeby przycisk stal w jednej kolumnie
   z przyciskiem trzeciego pakietu. `100%` to szerokosc tresci pasma Enterprise;
   dodajac z powrotem jego dopelnienie i obramowanie wracamy do szerokosci
   kontenera, dzielimy na trzy kolumny z odstepami i odejmujemy dopelnienie oraz
   obramowanie karty. Liczone, a nie wpisane, bo szerokosc kolumny zalezy od
   szerokosci okna. */
.enterprise__cta {
    flex: 0 0 auto;
    width: calc((100% + 4.25rem + 2px - 2 * var(--sp-5)) / 3 - 4.25rem - 2px);
}


/* ==========================================================================
   20. OKNO MODALNE
   ==========================================================================
   Jedno okno na podstrone; tresc wnosza panele `.modal__pane`. Zaslona, ramka
   i przycisk zamkniecia sa wspolne dla kazdego rodzaju tresci, a to, co od
   tresci zalezy (dopelnienie, szerokosc), ustawia modyfikator panelu - patrz
   .modal__pane--video. Dolozenie panelu z dokumentem to nowy modyfikator,
   bez ruszania regul ponizej.
   ========================================================================== */

.modal {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-5);
}

/* Zaslona i ramka wchodza osobno: zaslona sama przezroczystoscia, ramka
   dodatkowo ruchem z gory. Klase `is-open` doklada skrypt w nastepnej klatce
   po zdjeciu `hidden` - inaczej nie byloby od czego animowac. */
.modal__overlay {
    position: absolute;
    inset: 0;
    background: var(--c-overlay);
    opacity: 0;
    transition: opacity var(--t-modal) var(--ease);
}

.modal.is-open .modal__overlay { opacity: 1; }

/* `overflow: hidden` zaokragla tresc panelu razem z ramka - dzieki temu wideo
   nie potrzebuje wlasnego promienia i nie wystaje z narozników. */
.modal__dialog {
    position: relative;
    /* `isolation` jest tu warunkiem dzialania, nie ozdoba: warstwa ze zdjeciem ma
       `z-index: -1`, a ujemny indeks szuka najblizszego kontekstu nakladania.
       Bez tej linii ramka zadnego nie tworzy i warstwa ladowala pod TLEM RAMKI,
       czyli byla niewidoczna mimo pobrania pliku. */
    isolation: isolate;
    display: flex;
    flex-direction: column;
    max-width: 100%;
    max-height: 100%;
    overflow: hidden;
    /* Obrys i wypelnienie DOKLADNIE takie, jak pasmo "Sieci i franczyzy" na
       ofercie, czyli sekcja, z ktorej okno sie otwiera (.enterprise) - decyzja
       wlasciciela. Kolor tla i gradient stoja osobno, bo --grad-card jest
       polprzezroczysty: na pasmie lezy na tle podstrony, a tutaj musi dostac
       wlasna nieprzezroczysta podkladke, inaczej przez okno widac strone. */
    border: 1px solid var(--c-border-accent);
    border-radius: var(--r-lg);
    background-color: var(--c-bg-deep);
    background-image: var(--grad-card);
    box-shadow: var(--shadow-modal);
    opacity: 0;
    transform: translateY(-1.5rem);
    transition: opacity var(--t-modal) var(--ease),
                transform var(--t-modal) var(--ease);
}

.modal.is-open .modal__dialog {
    opacity: 1;
    transform: translateY(0);
}

/* Krzyzyk stoi w pasku okna, wiec nie potrzebuje ani pozycjonowania, ani
   wlasnej warstwy - tresc przewija sie POD paskiem, a nie pod przyciskiem. */
.modal__close {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border: 1px solid var(--c-border-mid);
    border-radius: var(--r-pill);
    background: var(--c-fill-hover);
    color: var(--c-text-80);
    /* Krzyzyk stoi na jasnych klatkach nagrania - bez rozmycia tla gubi sie
       na jasnym kadrze. */
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: color var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease);
}

.modal__close:hover,
.modal__close:focus-visible {
    border-color: var(--c-border-accent);
    background: var(--c-fill-accent);
    color: var(--c-accent-vivid);
}

/* --- Panel z wideo -------------------------------------------------------
   Nagranie jest pionowe (780x1386, czyli 9:16) i to ono wyznacza rozmiar
   ramki. Oba ograniczenia naraz - wysokosc do 85% okna i szerokosc do
   dostepnej - trzymaja proporcje same, bo element zastepowany zna swoja. */
/* Ramka dzieli sie na dwie czesci: nieruchomy pasek z nazwa panelu
   i przyciskiem zamkniecia oraz tresc pod nim. Kolumna `flex` zamiast wpisanej
   wysokosci - pasek bierze tyle, ile trzeba, a panel dostaje reszte i to on ma
   pasek przewijania. Szerokosc zalezy juz od tresci: klase `--wide` doklada
   Modal panelom oznaczonym `data-modal-wide` (kalkulator), nagranie zostaje
   przy szerokosci wynikajacej z samego kadru. */
/* WNETRZE SZEROKIEGO OKNA - ten sam przepis, co kola krokow na Wdrozeniu
   (wskazanie wlasciciela: "wstaw taki background, jak jest w tych okregach,
   z rozmyciem"). Tam kolo jest niemal przezroczyste, a caly efekt robi zdjecie
   POD nim, rozmyte i przykryte zielona poswiata.
   Zeby to samo dalo sie zrobic w oknie, warstwy musialy zamienic sie
   miejscami: zdjecie jest teraz TLEM RAMKI, a pseudoelement nad nim niesie
   poswiate i rozmycie. Poprzednio bylo odwrotnie - zdjecie lezalo na wierzchu
   i przykrywalo kazda zielen, ktora pod nie wkladalismy, wiec okno zostawalo
   ciemne mimo zmian.
   Welon nad zdjeciem liczy sie z --modal-photo-alpha, zeby ta jedna zmienna
   dalej sterowala tym, ile z kadru widac. */
.modal__dialog--wide {
    width: min(64rem, 100%);
    background-color: rgb(var(--rgb-bg));
    background-image:
        linear-gradient(rgba(var(--rgb-bg), calc(1 - var(--modal-photo-alpha))),
                        rgba(var(--rgb-bg), calc(1 - var(--modal-photo-alpha)))),
        var(--modal-photo);
    background-size: cover;
    background-position: center;
}

/* Kalkulator dostaje w tle to samo zdjecie, co Oferta, przygaszone do samej
   faktury - okno na plaskim kolorze robilo sie ponure, a to jest ekran, na
   ktorym gosc spedza chwile. Warstwa lezy POD trescia (`z-index: -1` maluje sie
   nad tlem ramki, a pod jej zawartoscia) i nie przewija sie razem z panelem,
   wiec nie odciaga uwagi od liczb.
   Tylko przy `--wide`, czyli przy kalkulatorze: nagranie zaslania ramke soba
   i faktura byla by pod nim niewidoczna, a i tak by sie doladowala. */
.modal__dialog--wide::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    /* Dokladnie to, co niesie kolo kroku (.step__disc): poswiata od srodka
       i piec procent krycia akcentu. Rozmycie dziala na to, co POD spodem,
       czyli na zdjecie w tle ramki. */
    background: var(--glow-step), rgba(var(--rgb-accent-deep), 0.05);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    pointer-events: none;
}

.modal__bar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-5) var(--sp-4) var(--sp-7);
    border-bottom: 1px solid var(--c-border-faint);
}

.modal__bar-title {
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--c-accent);
}

/* Panel bierze cala wysokosc pozostala po pasku i to on sie przewija.
   `min-height: 0` jest konieczne: element `flex` domyslnie nie kurczy sie
   ponizej swojej tresci i przewijanie przenioslo by sie na cala strone. */
.modal__pane--calc {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--sp-7);
}

.modal__pane--video { display: flex; }

/* Panel z dokumentem (regulamin, polityka). Tresc przychodzi z gotowej
   podstrony razem z jej kontenerem, wiec zdejmujemy tu geometrie STRONY -
   szerokosc i odstepy w oknie wyznacza juz panel, nie uklad podstrony. */
.modal__pane--doc {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--sp-7);
}

.modal__pane--doc .doc {
    max-width: none;
    padding-block: 0;
    padding-inline: 0;
}

/* Panel, ktorego tresc dopiero sie dociaga. Jedna linijka, wiec okno nie
   podskakuje potem o cala wysokosc kalkulatora - i tak stoi na srodku. */
.modal__loading {
    padding: var(--sp-8) var(--sp-7);
    font-size: var(--fs-sm);
    color: var(--c-text-60);
}

/* `width/height: auto` MUSI tu byc: atrybuty `width` i `height` w markupie
   (potrzebne, zeby przegladarka znala proporcje przed pobraniem pliku) dzialaja
   jak zwykle deklaracje CSS, wiec bez nadpisania sam `max-height` sciskalby
   obraz w pionie zamiast go pomniejszac. */
.modal__video {
    display: block;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 85vh;
    background: var(--c-bg-deep);
}

/* Blokada przewijania tla przy otwartym oknie. Dopelnienie z prawej zastepuje
   szerokosc paska przewijania (--scrollbar-w wstrzykuje Modal.lockScroll),
   inaczej strona podskakiwalaby w bok w chwili otwarcia. */
.has-modal,
.has-modal body { overflow: hidden; }

.has-modal body { padding-right: var(--scrollbar-w, 0px); }


/* ==========================================================================
   20a. SKALA 1:1 - NAGRANIE Z TELEFONU
   ==========================================================================
   Duzy telefon po lewej, po prawej naglowek, akapit i trzy okragle kafelki.
   Ramka telefonu jest rysowana tutaj, bo kadr pokazuje sam ekran - dzieki temu
   obudowa dziedziczy kolory projektu i skaluje sie z ukladem.
   ========================================================================== */

/* Kreska nad sekcja wnosi WLASNY odstep pod soba (--sp-11, patrz .rule), wiec
   gorne dopelnienie sekcji jest zerowe - inaczej odstep liczylby sie dwa razy.
   Dolne zostaje pelne, dzieki czemu od kreski do pierwszego obiektu jest tyle
   samo, co od ostatniego do krawedzi pasma. */
/* Kola wjezdzaja spoza prawej krawedzi EKRANU, wiec bez przyciecia dokument
   dostawalby poziomy suwak. Przyciecie stoi na sekcji, a nie na samej liscie:
   sekcja jest pasmem pelnej szerokosci, wiec jej krawedz POKRYWA SIE z krawedzia
   okna i kolo znika dokladnie tam, gdzie konczy sie ekran - zamiast byc widocznie
   ucinane w polowie kolumny.
   `clip`, a nie `hidden`: nie robi z sekcji kontenera przewijania (co zabraloby
   przyklejony naglowek) i pozwala zostawic pion widoczny. */
.proof {
    padding-block: 0 var(--sp-11);
    overflow-x: clip;
    overflow-y: visible;
}

.proof__grid {
    display: grid;
    grid-template-columns: minmax(0, var(--col-narrow)) minmax(0, var(--col-wide));
    gap: var(--sp-8);
    align-items: center;
}

/* --- Telefon -------------------------------------------------------------- */

.proof__phone {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-5);
}

/* Poswiata za telefonem - ten sam pomysl, co przerywany okrag przy scenie 3D:
   miekkie kolo, ktore odrywa urzadzenie od plaskiego tla. */
.proof__halo {
    position: absolute;
    top: 50%;
    left: 42%;
    z-index: -1;
    width: 37rem;
    height: 37rem;
    max-width: 150%;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: var(--glow-stage);
    pointer-events: none;
}

.proof__frame {
    position: relative;
    width: var(--phone-w);
    padding: 0.875rem;
    border: 1px solid var(--c-border-mid);
    border-top-color: var(--c-border-accent-soft);
    border-radius: 3rem;
    background: var(--grad-card);
    box-shadow: var(--glow-field);

    /* PODGLAD: telefon odwrocony ekranem w strone tekstu i kafelkow po prawej.
       Dodatni `rotateY` obraca sciane przednia w prawo - lewa krawedz wychodzi
       do przodu, prawa cofa sie w glab. Obraca sie sama obudowa, nie caly
       `.proof__phone`: podpis pod telefonem ma zostac plaski, zeby litery
       nie traciły ostrosci. */
    transform: perspective(var(--proof-perspective)) rotateY(var(--proof-tilt))
               translateX(var(--proof-tilt-shift));
}

/* Sluchawka nad ekranem - jeden pasek, zeby obudowa czytala sie jak telefon,
   a nie jak zaokraglona ramka na zdjecie. */
.proof__speaker {
    position: absolute;
    top: 1.5rem;
    left: 0;
    right: 0;
    z-index: var(--z-content);
    width: 3.5rem;
    height: 0.3125rem;
    margin-inline: auto;
    border-radius: var(--r-pill);
    /* Ciemna, nie jasna: pasek lezy NA nagraniu, ktore w gornej czesci bywa
       jasne - bialawy obrys gubil sie na nim calkowicie. */
    background: rgba(var(--rgb-bg-deep), 0.75);
}

/* Kadrowanie obrazka BEZ ruszania pliku. Material ma 400x700 i wlasna ramke
   telefonu wrysowana w obraz; pokazujemy z niego prostokat 288x618 zaczynajacy
   sie w punkcie (56, 52), czyli sam ekran. Przesuniecia sa ulamkami rozmiaru
   okienka, wiec kadr trzyma sie przy kazdej szerokosci:
     szerokosc obrazka   400/288 = 138,9%
     przesuniecie w bok   -56/288 = -19,44%
     przesuniecie w pion  -52/618 =  -8,41%
   Wysokosc okienka wyznacza proporcja 288:618. */
.proof__screen {
    position: relative;
    width: 100%;
    aspect-ratio: 288 / 618;
    overflow: hidden;
    border-radius: 2.25rem;
    background: var(--c-bg-deep);
}

.proof__shot {
    position: absolute;
    top: -8.41%;
    left: -19.44%;
    width: 138.9%;
    height: auto;
    /* Reset wysyla wszystkim obrazkom `max-width: 100%`, przez co kadr
       zatrzymywal sie na szerokosci okienka i kadrowanie nie dzialalo wcale
       (zmierzone: 248 px zamiast 344). Tutaj obrazek MA byc szerszy niz jego
       opakowanie - na tym polega przyciecie. */
    max-width: none;
}

/* Podpis pod telefonem, z kreskami po bokach - jak w projekcie. */
/* Podpis ma DOKLADNIE szerokosc telefonu (ta sama wartosc, co .proof__frame),
   zeby kreski po bokach konczyly sie rowno z jego krawedziami. Napis
   ("Widok AR z telefonu") mieci sie w tej szerokosci w jednej linii, a `nowrap`
   pilnuje, zeby przy dluzszym nie zaczal sie lamac i zjadac kresek. */
.proof__caption {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    width: var(--phone-w);
    white-space: nowrap;
    font-size: var(--fs-micro);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--c-text-45);
}

.proof__caption::before,
.proof__caption::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: var(--c-eyebrow-line);
}

/* --- Kolumna tekstu ------------------------------------------------------- */

/* Nadkreslenie ciagnie kreske w prawo, az do krawedzi kolumny. */
.proof__eyebrow {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
}

.proof__eyebrow::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: var(--c-eyebrow-line);
}

.proof__title {
    margin-bottom: var(--sp-5);
    /* Wlasny sufit stopnia, nie samo --fs-h2 - zgloszenie wlasciciela: na
       szerokim ekranie z naglowka schodzilo do drugiego wiersza samo "1:1".
       Rachunek: kolumna tekstu przestaje rosnac przy 1440 px i zostaje na
       771 px, a --fs-h2 ronie jeszcze do 1600 px. Napis ma 19,55 szerokosci
       wlasnego stopnia, wiec miesci sie do 771/19,55 = 39,4 px - a pelne
       --fs-h2 daje 40 px i braklo 11 px. 39 px zamyka go w jednym wierszu
       przy kazdej szerokosci od 1081 px w gore (zmierzone).
       UWAGA przy zmianie TRESCI tego naglowka: to jest sufit dobrany do tego
       konkretnego napisu. Dluzszy trzeba przemierzyc na nowo - albo pogodzic
       sie z dwoma wierszami. */
    font-size: var(--fs-h2-proof);
    line-height: 1.1;
    font-weight: 700;
    letter-spacing: var(--ls-tight);
    /* Naglowek ma isc na CALA szerokosc kolumny, tak jak akapit pod nim.
       Stalo tu `max-width: 18ch`, czyli granica osiemnastu znakow - lamala
       tytul w polowie kolumny, mimo ze obok bylo wolne miejsce. */
}

.proof__lead {
    max-width: 44rem;
    font-size: var(--fs-lead);
    line-height: 1.6;
    color: var(--c-text-60);
}

/* --- Trzy kafelki --------------------------------------------------------- */

.proof__tiles {
    margin-top: var(--sp-8);
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-4);

    /* Jedno "miejsce w rzedzie" - wlasna szerokosc kola plus przerwa. Kazde kolo
       startuje o TYLE miejsc na prawo, ile dzieli je od punktu tuz za lista:
       pierwsze o trzy, drugie o dwa, trzecie o jedno. Dzieki temu wszystkie
       czekaja w tym samym punkcie i wjezdzaja jedno za drugim. */
    --reveal-slot: calc(100% + var(--sp-4));

}

/* Stan poczatkowy wisi na `is-reveal`, ktora dokłada SKRYPT (komponent Reveal).
   Bez JS-a klasy nie ma, wiec kafelki stoja normalnie widoczne - inaczej awaria
   skryptu chowalaby trwale kawalek tresci, a strona ma byc czytelna bez niego. */
.proof__tiles.is-reveal .proof__tile {
    opacity: 0;
    transform: translateX(calc(var(--reveal-slot) * var(--reveal-steps)));
}

/* Druga klasa dokladana jest raz, gdy sekcja wejdzie w kadr - i wtedy animacja
   po prostu leci. `both` trzyma klatke koncowa po jej zakonczeniu, wiec kola
   zostaja na miejscu; cofania nie ma, bo efekt ma zwrocic uwage raz, a nie
   towarzyszyc przewijaniu. */
.proof__tiles.is-reveal.is-in .proof__tile {
    animation: revealSlide var(--t-reveal) var(--ease-reveal-in) var(--reveal-delay) both;
}

/* Kolejnosc "jedno, drugie, trzecie" i dlugosc trasy ida razem: kto rusza
   pierwszy, ten ma najdalej. */
.proof__tiles.is-reveal .proof__tile:nth-child(1) { --reveal-steps: 3; --reveal-delay: 0s; }
.proof__tiles.is-reveal .proof__tile:nth-child(2) { --reveal-steps: 2; --reveal-delay: var(--t-reveal-step); }
.proof__tiles.is-reveal .proof__tile:nth-child(3) { --reveal-steps: 1; --reveal-delay: calc(var(--t-reveal-step) * 2); }

/* Kolo, a nie karta: sekcja ma juz dwie prostokatne bryly (telefon i akapit),
   wiec trzecia forma odroznia dane techniczne od tresci. */
.proof__tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    aspect-ratio: 1;
    padding: var(--tile-pad);
    /* Obrys przerywany, taki sam jak okrag pod scena 3D w hero (.stage__ring) -
       ta sama grubosc, ten sam kolor. Sekcja nalezy przez to do tej samej
       rodziny form, co gorna czesc strony. */
    border: 1.5px dashed var(--c-ring-dash);
    border-radius: 50%;
    background: radial-gradient(circle at 50% 40%,
                rgba(var(--rgb-accent-deep), 0.14) 0%, transparent 72%);
    text-align: center;
}

/* Ikona siedzi we WLASNYM okregu, jak w projekcie: mniejsze kolo w duzym kole
   porzadkuje kafelek i daje znakowi stale pole, niezalezne od dlugosci napisu. */
.proof__icon {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--tile-icon);
    height: var(--tile-icon);
    margin-bottom: var(--sp-2);
    border: 1px solid var(--c-border-accent-dim);
    border-radius: 50%;
    color: var(--c-accent);
}

/* Drugi, wewnetrzny okrag - rysowany, a nie obramowaniem. Proba z ramka
   pseudoelementu zmienila wyglad (kreska wychodzila rownej grubosci co
   zewnetrzna), a chodzi wlasnie o to, ze wewnetrzna jest CIENSZA: to obrys
   o grubosci 1 w skali 24 jednostek, czyli po przeliczeniu na 70 px ramki
   ok. 0,3 px - i do tego przygaszony. */
.proof__mark-halo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0.35;
    pointer-events: none;
}

.proof__tile-title {
    font-size: var(--fs-sm);
    font-weight: 600;
    line-height: 1.3;
    color: var(--c-text-85);
    text-wrap: balance;
}

.proof__tile-sub {
    font-size: var(--fs-micro);
    color: var(--c-text-45);
}


/* ==========================================================================
   21. KALKULATOR WYCENY
   ==========================================================================
   Panel okna modalnego na `oferta.html`. Kolejne wiersze pytan po lewej,
   przyklejone podsumowanie po prawej. Kontrolki noszą to samo swiatlo, co pola
   formularza kontaktowego (--glow-field): zielona gorna krawedz i miekka
   poswiata - to jest ten "hologram", ktory ma spinac obie sekcje w jedno.
   ========================================================================== */

.calc__head { margin-bottom: var(--sp-7); }

.calc__title {
    margin-bottom: var(--sp-3);
    font-size: var(--fs-h3);
    font-weight: 700;
    letter-spacing: var(--ls-tight);
    line-height: 1.15;
}

.calc__lead {
    max-width: 44rem;
    font-size: var(--fs-sm);
    line-height: 1.6;
    color: var(--c-text-60);
}

.calc__grid {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    gap: var(--sp-7);
    align-items: start;
}

/* Wiersze rozdziela kreska, a nie ramka kazdego z nich: pytania sa jedna lista,
   a nie szescioma osobnymi kartami. */
.calc__row + .calc__row {
    margin-top: var(--sp-5);
    padding-top: var(--sp-5);
    border-top: 1px solid var(--c-border-faint);
}

.calc__line {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-4);
}

.calc__label {
    display: block;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--c-text-85);
}

.calc__hint {
    margin-top: 0.25rem;
    font-size: var(--fs-micro);
    line-height: 1.5;
    color: var(--c-text-45);
}

/* Biezaca wartosc suwaka. Cyfry o stalej szerokosci, zeby pigulka nie drgala
   przy przechodzeniu z 99 na 100. */
.calc__pill {
    flex: 0 0 auto;
    padding: 0.3125rem 0.75rem;
    border: 1px solid var(--c-border-accent-dim);
    border-radius: var(--r-pill);
    background: var(--c-fill-accent);
    color: var(--c-accent-soft);
    font-size: var(--fs-micro);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.calc__inputs,
.calc__stepper {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    margin-top: var(--sp-4);
}

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

/* WIDOCZNOSC KONTROLEK KALKULATORA. Wszystkie stoja w oknie modalnym na
   ciemnym tle, a obrysy mialy krycie 0,10 (--c-border-soft) - zgloszenie
   wlasciciela: "te suwaki i niektore pola sa po prostu niewidoczne".
   Podniesione na tokeny, ktore projekt juz ma, w tej samej rodzinie kolorow:
   pola dostaja bialy obrys o kryciu 0,18, a KONTROLKI, ktore trzeba znalezc
   palcem (krok, suwak, przelacznik) - zielony 0,32, czyli ten sam, ktorym
   domyka sie gorna krawedz kart. Zadnego nowego koloru nie wprowadzam. */
.calc__num {
    width: 5rem;
    padding: 0.5rem 0.625rem;
    border: 1px solid var(--c-border-mid);
    border-top-color: var(--c-border-accent-soft);
    border-left-color: var(--c-border-accent-dim);
    border-radius: var(--r-sm);
    background: rgba(var(--rgb-bg-input), 0.5);
    box-shadow: var(--glow-field);
    color: var(--c-text-strong);
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    text-align: center;
    outline: none;
    transition: border-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease);
}

.calc__num:focus {
    border-color: var(--c-border-focus);
    box-shadow: var(--glow-field-focus);
}

.calc__step {
    width: 2.375rem;
    height: 2.375rem;
    border: 1px solid var(--c-border-accent-soft);
    border-radius: var(--r-pill);
    background: rgba(var(--rgb-bg-pill), 0.45);
    color: var(--c-accent-soft);
    font-size: var(--fs-body);
    line-height: 1;
    transition: border-color var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease);
}

.calc__step:hover { border-color: var(--c-accent); background: var(--c-fill-accent); }

/* Suwak: tor bierze kolor pol formularza, a uchwyt swieci jak aktywne pole. */
.calc__range {
    flex: 1 1 auto;
    min-width: 0;
    height: 1.5rem;
    margin: 0;
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    cursor: pointer;
}

.calc__range:focus-visible { outline: 2px solid var(--c-accent-vivid); outline-offset: 4px; }

.calc__range::-webkit-slider-runnable-track {
    height: 0.375rem;
    border: 1px solid var(--c-border-accent-soft);
    border-radius: var(--r-pill);
    background: rgba(var(--rgb-bg-pill), 0.55);
}

.calc__range::-webkit-slider-thumb {
    -webkit-appearance: none;
    /* Uchwyt jest wyzszy od toru, wiec trzeba go podniesc o polowe roznicy:
       (1.125rem - 0.375rem) / 2, plus 1 px obramowania toru. */
    margin-top: -0.4375rem;
    width: 1.125rem;
    height: 1.125rem;
    border: 0;
    border-radius: 50%;
    background: var(--c-accent);
    box-shadow: var(--glow-field-focus);
}

.calc__range::-moz-range-track {
    height: 0.375rem;
    border: 1px solid var(--c-border-accent-soft);
    border-radius: var(--r-pill);
    background: rgba(var(--rgb-bg-pill), 0.55);
}

.calc__range::-moz-range-thumb {
    width: 1.125rem;
    height: 1.125rem;
    border: 0;
    border-radius: 50%;
    background: var(--c-accent);
    box-shadow: var(--glow-field-focus);
}

/* --- Grupy przyciskow ---------------------------------------------------- */

.calc__seg {
    display: grid;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
}

.calc__seg--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.calc__seg--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.calc__opt {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    padding: 0.625rem 0.75rem;
    border: 1px solid var(--c-border-soft);
    border-radius: var(--r-sm);
    background: rgba(var(--rgb-bg-pill), 0.18);
    text-align: left;
    transition: border-color var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease);
}

.calc__opt strong { font-size: var(--fs-sm); font-weight: 700; color: var(--c-text-85); }
.calc__opt span   { font-size: var(--fs-micro); color: var(--c-text-45); }

.calc__opt:hover { border-color: var(--c-border-mid); background: var(--c-fill-hover); }

.calc__opt[aria-pressed='true'] {
    border-color: var(--c-accent);
    background: var(--c-fill-accent);
    box-shadow: var(--shadow-tab);
}

.calc__opt[aria-pressed='true'] strong { color: var(--c-accent-soft); }

.calc__level { margin-top: var(--sp-3); }

/* --- Podsumowanie -------------------------------------------------------- */

/* Przyklejone do gornej krawedzi panelu: przy dlugiej liscie pytan wycena
   zostaje na oczach, bo to ona jest powodem, dla ktorego gosc tu wszedl. */
.calc__summary {
    position: sticky;
    top: 0;
    padding: var(--sp-6);
    border: 1px solid var(--c-border-soft);
    border-top-color: var(--c-border-accent-soft);
    border-left-color: var(--c-border-accent-dim);
    border-radius: var(--r-lg);
    background: var(--grad-card);
    box-shadow: var(--glow-field);
}

.calc__badge {
    margin-bottom: var(--sp-4);
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--c-accent);
}

.calc__package {
    font-size: 1.5rem;
    font-weight: 800;
    letter-spacing: var(--ls-tight);
}

.calc__package-sub {
    margin-top: 0.25rem;
    font-size: var(--fs-micro);
    color: var(--c-text-45);
}

.calc__price {
    margin-top: var(--sp-5);
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--sp-2);
}

.calc__amount {
    font-size: 2rem;
    font-weight: 800;
    letter-spacing: var(--ls-tight);
    color: var(--c-accent-soft);
    font-variant-numeric: tabular-nums;
}

.calc__period { font-size: var(--fs-xs); color: var(--c-text-60); }

.calc__note {
    margin-top: 0.375rem;
    font-size: var(--fs-micro);
    color: var(--c-text-45);
}

/* Komunikat o wycenie indywidualnej. Zolta krawedz, ta sama co przy
   ostrzezeniach w dokumentach - to informacja, nie blad. */
.calc__alert {
    margin-top: var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    border-left: 3px solid var(--c-gold);
    background: var(--c-fill-hover);
    font-size: var(--fs-micro);
    line-height: 1.55;
    color: var(--c-text-70);
}

.calc__costs {
    margin-top: var(--sp-5);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

.calc__cost {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
    font-size: var(--fs-micro);
}

.calc__cost dt { color: var(--c-text-60); }

.calc__cost dd {
    margin: 0;
    font-weight: 700;
    color: var(--c-text-85);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.calc__total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
    margin-top: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--c-border-soft);
    font-size: var(--fs-sm);
    color: var(--c-text-70);
}

.calc__total strong { color: var(--c-accent-soft); font-variant-numeric: tabular-nums; }

.calc__features {
    margin-top: var(--sp-5);
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}

.calc__feature {
    padding-left: var(--sp-4);
    position: relative;
    font-size: var(--fs-micro);
    color: var(--c-text-50);
}

.calc__feature::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 0.3125rem;
    height: 0.3125rem;
    margin-block: auto;
    border-radius: 50%;
    background: rgb(var(--rgb-accent-deep));
}

/* --- Przelacznik netto / brutto ------------------------------------------ */

.calc__vat { margin-top: var(--sp-5); }

.calc__switch {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    cursor: pointer;
}

.calc__checkbox {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.calc__track {
    flex: 0 0 auto;
    position: relative;
    width: 2.625rem;
    height: 1.5rem;
    border: 1px solid var(--c-border-accent-soft);
    border-radius: var(--r-pill);
    background: rgba(var(--rgb-bg-pill), 0.5);
    transition: border-color var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease);
}

/* Gałka wysrodkowana pionowo przez top/bottom + margin auto, wiec zmiana
   wysokosci toru nie wymaga poprawiania odstepu. */
.calc__track::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0.1875rem;
    width: 1rem;
    height: 1rem;
    margin-block: auto;
    border-radius: 50%;
    background: var(--c-switch-knob);
    transition: transform var(--t-fast) var(--ease),
                background-color var(--t-fast) var(--ease);
}

.calc__checkbox:checked + .calc__track {
    border-color: var(--c-accent);
    background: var(--c-fill-accent);
}

.calc__checkbox:checked + .calc__track::after {
    transform: translateX(1.0625rem);
    background: var(--c-accent);
}

.calc__checkbox:focus-visible + .calc__track {
    outline: 2px solid var(--c-accent-vivid);
    outline-offset: 3px;
}

.calc__switch-text { font-size: var(--fs-micro); color: var(--c-text-70); }

.calc__cta {
    display: flex;
    margin-top: var(--sp-5);
    padding: 0.875rem 1.25rem;
    font-size: var(--fs-sm);
}

.calc__footnote {
    margin-top: var(--sp-4);
    font-size: var(--fs-micro);
    line-height: 1.5;
    color: var(--c-text-45);
}


/* ==========================================================================
   22. ZAPYTANIA MEDIALNE
   ==========================================================================
   Progi odpowiadaja --bp-lg / --bp-md / --bp-sm z sekcji 1. Kolejnosc od
   najszerszego do najwezszego, zeby kazdy nastepny prog tylko dokladal
   zmiany zamiast cofac poprzednie.
   ========================================================================== */

/* --- do 1080 px: uklady dwukolumnowe schodza do jednej kolumny ------------ */
@media (max-width: 1080px) {
    :root {
        --container-pad: 1.75rem;
        /* Zmierzone: panel ma tu 156 px + 16 px marginesu. 208 px zostawia
           ok. 36 px luzu do pasma FAQ - bez tego panel wchodzil pod nie. */
        --panel-drop: 13rem;
        --head-pad-top: 3.25rem;
        --head-pad-bottom: 3rem;
        /* Scena przestaje byc liczona z szerokosci okna - w jednej kolumnie
           `50vw` dawaloby obiekt na pol ekranu przy pelnej dostepnej szerokosci. */
        /* 31rem, nie 32rem - wskazanie wlasciciela ("czym bardziej sciśniete,
           tym lepiej"). Pudelko sceny jest SAMA REZERWA MIEJSCA w ukladzie:
           obiekt ma wlasny rozmiar (--stage-dish) i skrocenie pudelka go nie
           zmniejsza. Zabiera za to 16 px pustego pola u dolu pasma, a razem
           z podniesieniem nizej ciagnie w gore takze panel miniatur i akapit
           pod scena - caly modul zbliza sie do naglowka o te same 16 px. */
        --stage-h: min(86vw, 31rem);
        --stage-dish: min(84vw, 31rem);
        --stage-ring: min(88vw, 33rem);
        --stage-glow: min(104vw, 40rem);
        --stage-lift: 2rem;
    }

    .hero__grid,
    .faq__grid,
    .contact__grid {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Zmienne, na ktorych stoi uklad hero. MUSZA byc w tym samym progu, co on:
       wczesniej siedzialy w progu 860 i w pasmie 861-1080 px byly
       niezdefiniowane, przez co tytul stykal sie ze scena, a panel miniatur
       odskakiwal od niej o kilkadziesiat pikseli. */
    :root {
        /* Odstep miedzy elementami hero. Zmienna, a nie liczba w regule, bo
           wchodzi takze do wyliczenia srodka sceny nizej. */
        --hero-gap: 1.625rem;

        /* O tyle panel z miniaturami podjezdza pod scene - ma NACHODZIC na
           jej dolna czesc, zeby caly modul nawigacji miescil sie na ekranie
           razem z daniem. */
        /* 17rem, nie 6.5rem jak na telefonie - zgloszenie wlasciciela, ze
           przerwa miedzy daniem a modulem miniatur jest za duza.
           Zmierzone przy 700 px: plotno sceny ma 512 px, a sama bryla dania
           zajmuje z tego okolo 205 px, wiec pod nia zostawalo 213 px pustego
           pola. Zmniejszenie sceny NIC BY NIE DALO - model-viewer skaluje
           obiekt do rozmiaru plotna, wiec danie zmalaloby razem z nim
           i proporcja powietrza zostalaby ta sama. Jedyna dzwignia, ktora
           realnie zamyka te przerwe, to podjazd panelu. */
        /* 10.5rem, nie 6.5rem jak na telefonie - zgloszenie wlasciciela, ze
           przerwa miedzy daniem a modulem miniatur jest za duza.
           Zmierzone przy 700 px z zaladowanym modelem: plotno sceny ma 512 px,
           a bryla dania konczy sie 315 px od jej gory, wiec pod daniem
           zostawalo 213 px pustego pola. 168 px podjazdu zostawia ok. 30 px.
           GORNA GRANICA jest twarda i wynika z komentarza przy regule
           top: 100% wyzej - panel nie moze wejsc NA danie. Przy 17rem, ktore
           probowalem najpierw, zaczynalby sie 75 px nad dolna krawedzia bryly. */
        --panel-lift: 10.5rem;

        /* --stage-lift PRZYPIETY, a nie liczony ze wzoru. To istotne: wzor
           bazowy bierze --panel-lift do rachunku, wiec podniesienie panelu
           pociagneloby ZA SOBA scene i przerwa wrocilaby w tym samym miejscu.
           Wartosc to dokladnie to, co wzor dawal przy panelu 6.5rem, czyli
           67 px - polozenie sceny zostaje wiec nietkniete. */
        --stage-nudge: 0.625rem;   /* 10 px */
        /* 75 px zamiast 67 - danie blizej naglowka (polecenie wlasciciela).
           Razem ze skroceniem pudelka sceny o 16 px daje to obiekt podniesiony
           o rowne 16 px, a nie o 8: pudelko traci wysokosc symetrycznie, wiec
           jego srodek - a z nim obiekt - jedzie w gore o polowe tej straty.
           GORNA GRANICA jest zmierzona, nie dobrana na oko. Modele nie maja
           jednej sylwetki: w tym samym plotnie 496 px najplaskszy talerz ma
           105 px wysokosci, a najwyzszy burger 393 px, i to on wyznacza limit.
           Przy 900 px zostawalo mu 12 px do dolnej krawedzi naglowka; po
           podniesieniu wchodzi 4 px w jego pole wiersza - dokladnie tyle samo,
           ile ma na telefonie (zmierzone -3 px), czyli w granicach, ktore
           wlasciciel juz zatwierdzil. Kazde kolejne kilkanascie pikseli
           wsadza czubek burgera w litery slowa "restauracji". */
        --stage-lift: 4.6875rem;   /* 75 px */
    }

    /* Przerywany okrag pod obiektem 3D schodzi ze sceny ponizej tego progu -
       wskazanie wlasciciela. Wyzej zostaje: tam scena stoi w swojej kolumnie
       i okrag ja domyka, a w ukladzie jednokolumnowym tylko dodaje wysokosci
       miedzy daniem a panelem miniatur. */
    .stage__ring { display: none; }

    /* ======================================================================
       UKLAD HERO JAK NA TELEFONIE - JUZ OD TEGO PROGU
       ======================================================================
       Blok przeniesiony z progu 860 px. Powod jest zmierzony: siatka hero
       sklada sie do jednej kolumny WLASNIE TUTAJ (regula wyzej), wiec scena
       spadala pod tekst juz przy 1080 px, ale uklad mobilny - wysrodkowany
       naglowek, kolejnosc ze scena pod nim, przyciski na osi - wchodzil
       dopiero przy 860. W pasmie 861-1080 px zostawal przez to uklad
       desktopowy na jednokolumnowej siatce: naglowek do lewej, rozciagniety
       na CALA szerokosc strony (zmierzone: 805 px przy oknie 861 i 1024 px
       przy 1080), a scena pod nim.
       Teraz oba progi to ten sam prog. */

    /* --- HERO NA TELEFONIE: inna kolejnosc niz w markupie ------------------
       Polecenie wlasciciela: naglowek, ZARAZ pod nim obracajace sie dania,
       pod nimi modul nawigacji (miniatury), a dopiero na koncu akapit,
       przyciski i lista cech.
       Powod jest mierzalny: przy kolejnosci z markupu scena zaczynala sie
       738 px od gory okna, czyli ponizej krawedzi ekranu telefonu (844 px
       wysokosci, z czego 80 px zjada belka) - gosc widzial sam tekst i musial
       zgadnac, ze nizej cos sie obraca.
       `display: contents` na kolumnie tekstu, bo inaczej naglowka nie da sie
       oddzielic od reszty kolumny: siatka widzi jeden element, a nie cztery.
       Kolumna nie ma wlasnego tla, ramki ani cienia, wiec nie ma czego stracic;
       jej odstepy przejmuje `row-gap` siatki, a gorne dopelnienie - sama siatka. */
    .hero__grid {
        padding-block: var(--sp-7) 0;
        row-gap: var(--hero-gap);
    }

    .hero__col { display: contents; }

    /* Kolejnosc na telefonie: naglowek, scena, PRZYCISKI, akapit, lista cech.
       Akapit zszedl pod przyciski - wskazanie wlasciciela. Odstepy sie przez to
       nie zmieniaja i to jest wlasnie powod, dla ktorego uklad stoi na `order`,
       a nie na marginesach: swiatlo miedzy wszystkimi klockami hero wnosi jeden
       `row-gap` siatki, wiec kolejnosc mozna przestawiac bez dotykania odstepow.
       Markup zostaje nietkniety, czyli czytnik ekranu i szeroki ekran maja
       dalej kolejnosc z HTML-a. */
    .hero__title    { order: 1; }
    .hero__stage    { order: 2; }
    .hero__actions  { order: 3; }
    .hero__lead     { order: 4; }
    .hero__features { order: 5; }

    /* Zdanie-obietnica ("Pierwsze takie menu w Polsce!") ZOSTAJE W AKAPICIE,
       jako jego dalszy ciag - decyzja wlasciciela, cofniecie wczesniejszej.
       Stalo tu wyodrebnienie: `display: block`, wlasny odstep i os ekranu.
       Na telefonie czytalo sie to jak osobne haslo oderwane od zdania, ktore
       je wprowadza.
       Teraz obowiazuje sama regula bazowa (sekcja 10): element liniowy, ktory
       zmienia wylacznie kolor i grubosc frazy, wiec plynie razem z tekstem
       i nie rusza ukladu.
       ROLLBACK: przywrocic tu `display: block`, `margin-top:
       calc(var(--hero-gap) - 0.5rem)` i `text-align: center`. */

    /* Naglowek na osi ekranu - polecenie wlasciciela. Stoi teraz sam nad
       scena, wiec wyrownanie do lewej zostawialoby go w rogu kadru.
       Stopien pisma o 10% wiekszy, niz daje clamp: wlasciciel sprawdzil to
       u siebie transformem `scale(1.1)` i taka wielkosc zatwierdzil. U nas
       robi to font-size, a nie transform, i to jest roznica techniczna, nie
       kosmetyczna: transform skaluje OBRAZ gotowego bloku, zostawiajac jego
       pudelko w starym rozmiarze - naglowek zachodzilby wtedy na scene, bo
       siatka dalej liczylaby go jako mniejszy. */
    .hero__title {
        text-align: center;
        font-size: calc(var(--fs-h1-hero) * 1.1);
    }

    /* Podjazd panelu stoi NIZEJ w tym samym progu - musi stac ZA regula
       ustawiajaca mu top: 100%, ktora inaczej wygrywa kolejnoscia. */

    /* Przyciski na osi ekranu, obok siebie, w swoich wlasnych rozmiarach -
       wskazanie wlasciciela dla tego pasma. Ponizej 560 px osobna regula
       sklada je w kolumne na pelna szerokosc kontenera i to zostaje.
       `justify-content`, a nie `margin-inline: auto`: to jest pudelko flex,
       wiec o polozeniu dzieci decyduje os glowna, nie ich wlasne marginesy. */
    /* Naglowek LAMIE SIE SAM, zamiast miec z gory narzucone trzy wiersze -
       wskazanie wlasciciela. Regula bazowa daje `.hero__title-line`
       `display: block`, wiec kazda czesc siedziala we wlasnym wierszu takze
       tam, gdzie caly napis zmiescilby sie w jednym albo dwoch. Trzy wiersze
       niepotrzebnie spychaly scene 3D w dol.
       `display: inline` oddaje lamanie przegladarce: przy szerszym oknie
       tekst idzie w jednej linii, przy wezszym schodzi do dwoch, a ponizej
       progu telefonu wraca sztywny uklad trzywierszowy (regula w progu 560).
       Kolory zostaja - siedza na tych samych spanach i nie zaleza od tego,
       czy element jest blokiem, czy plynie w linii. */
    .hero__title-line { display: inline; }

    /* Indeks "AR" MUSI wtedy wrocic do przeplywu. Regula bazowa daje mu
       `position: absolute`, czyli zerowa szerokosc w ukladzie - przy wierszach
       blokowych to nie przeszkadzalo, bo za nim i tak konczyl sie blok.
       Odkad wiersze plyna w linii, nastepny z nich zaczynal sie DOKLADNIE
       w miejscu, gdzie AR jest rysowane, i litery nachodzily na siebie
       (zgloszenie wlasciciela).
       `vertical-align: super` zamiast `top`: przy elemencie w przeplywie
       podniesienie liczy sie od linii pisma, a nie od gornej krawedzi
       pudelka wiersza - i tylko wtedy indeks trzyma sie tekstu niezaleznie
       od tego, ile wierszy ma naglowek. */
    .hero__title-sup {
        position: static;
        vertical-align: super;

        /* ...i wtedy MUSI oddac spacje, ktora wczesniej byla dla ukladu
           niewidoczna. Dopoki indeks stal `absolute`, byl poza przeplywem
           i spacja z tresci nie zajmowala miejsca - caly odstep "3D" -> "AR"
           robil sam margines, czyli 0,294em stopnia indeksu (zmierzone na
           telefonie: 5,6 px). Po powrocie do przeplywu spacja doliczyla sie do
           marginesu i ten sam odstep uroslby ponad dwukrotnie (zmierzone
           13,9 px, czyli 0,362em stopnia naglowka). W tym pasmie za "AR" stoi
           juz "dla Twojej", wiec indeks przestawal sie kleic do "3D" i wisial
           w polowie drogi miedzy slowami - zgloszenie wlasciciela.
           Odejmowana wartosc to szerokosc spacji Inter przy tym stopniu,
           przeliczona na stopien indeksu: 0,217em naglowka / 0,494 = 0,437em.
           Wynik wraca do 0,294em, czyli do odstepu, ktory obowiazywal na
           telefonie i ktory wlasciciel zatwierdzil na zrzucie. Wlasciciel
           dobral to samo recznie jako -2 px w inspektorze; tutaj stoi w `em`,
           zeby proporcja trzymala sie przy plynnym stopniu naglowka. */
        margin-left: calc(0.294em - 0.437em);
    }

    .hero__actions { justify-content: center; }

    /* SZEROKOSC TRESCI pod kontrola - wskazanie wlasciciela ("pilnujemy
       szerokosci kontenerow"). Sam `text-align: center` nie wystarcza: przy
       1080 px wysrodkowany naglowek nadal biegl przez 1024 px, czyli wiersze
       byly dwa razy dluzsze, niz da sie wygodnie czytac.
       42rem to ok. 672 px, czyli okolice granicy 65 znakow w wierszu. */
    /* .hero__claim NIE nalezy juz do tej listy: wrocilo do przeplywu akapitu,
       a element liniowy nie przyjmuje ani szerokosci, ani marginesow bocznych -
       reguly nizej byly na nim martwe. Miare wiersza narzuca mu teraz akapit,
       w ktorym stoi. */
    .hero__title,
    .hero__lead,
    .hero__features {
        /* `width: 100%` jest tu KONIECZNE, nie ozdobne. Kolumna tekstu ma
           `display: contents`, wiec te elementy sa bezposrednimi dziecmi
           siatki - a element siatki z `margin-inline: auto` kurczy sie do
           szerokosci wlasnej tresci zamiast wypelnic pas. Zmierzone: naglowek
           schodzil przez to z pelnej szerokosci do 226 px i lamal sie na
           kilka wierszy. Ze 100% bierze pas i dopiero sufit go ogranicza. */
        width: 100%;
        max-width: 42rem;
        margin-inline: auto;
    }

    .hero__grid { min-height: 0; }

    .hero__col {
        padding-right: 0;
        padding-block: var(--sp-7) 0;
    }

    /* ...i dolne dopelnienie kolumny przejmuje CALE pasmo. Regula wyzej zeruje
       je w kolumnie (w ukladzie jednokolumnowym odstepy niesie `row-gap`
       siatki), wiec bez tej linii lista cech dotykala kreski konczacej hero -
       zgloszenie wlasciciela, zmierzone przy 1054 px: zostawaly 6 px z reguly
       bazowej `.hero`. Wartosc to --hero-gap, czyli ten sam odstep, ktory
       w tym ukladzie dzieli akapit od przyciskow; przeniesiona z progu 860
       razem z reszta ukladu mobilnego. */
    .hero { padding-bottom: var(--hero-gap); }

    /* ...i tyle samo POD kreska, zeby swiatlo po obu jej stronach bylo rowne.
       Przeniesione z progu 860 razem z dopelnieniem wyzej - inaczej w pasmie
       861-1080 px kreska miala nad soba 26 px, a pod soba 88 px z reguly
       bazowej. Kreska jest na witrynie jedna (koniec hero na stronie glownej),
       wiec zmiana nie siega nigdzie indziej. Powyzej progu zostaje --sp-11:
       tam pasma sa wysokie i 26 px sklejaloby je ze soba. */
    .rule { margin-bottom: var(--hero-gap); }

    .hero__stage { min-height: 0; }

    .stage {
        width: 100%;
        max-width: 32rem;
        margin-inline: auto;
        /* Ujemne marginesy mialy sens tylko w siatce dwukolumnowej. */
        margin-right: auto;
        margin-left: auto;
        /* Miejsce na panel demo, ktory schodzi PONIZEJ sceny. */
        margin-bottom: var(--panel-drop);
    }

    .stage__glow--floor {
        width: min(86vw, 32rem);
        height: min(42vw, 15rem);
    }

    /* W jednej kolumnie panel nie moze juz lezec NA scenie - danie jest wtedy
       mniejsze, a przyciski zaslanialyby jego dolna polowe. Zostaje
       pozycjonowany bezwzglednie i schodzi pod scene. `position:static`
       wrzucilby go na GORE sceny, bo talia dan i poswiaty sa poza normalnym
       przeplywem i nie zajmuja w niej miejsca. */
    .demo-panel {
        top: 100%;
        bottom: auto;
        width: 100%;
        max-width: var(--panel-w);
        margin-top: var(--sp-4);
    }

    /* ...ale panel WRACA na scene o --panel-lift. Regula wyzej opisuje stan,
       w ktorym panel schodzi calkiem pod scene, i to bylo sluszne, dopoki
       podjazd zyl w progu 860: stal wtedy PO niej w zrodle i wygrywal.
       Po przeniesieniu ukladu hero do tego progu trafil PRZED nia i przestal
       dzialac - zmierzone top wynosilo dokladnie 100% zarowno przy 700 px,
       jak i przy 390 px, czyli takze telefon stracil swoj podjazd.
       top, a nie ujemny margin-top: odstep nad panelem zostaje wtedy jedna
       wartoscia (--sp-4), a wysokosc podjazdu druga, i da sie je stroic
       osobno. */
    .demo-panel { top: calc(100% - var(--panel-lift)); }

    /* Przycisk AR moze sie tu pokazac - ale o tym, czy sie pokaze, decyduje
       `is-ready` od skryptu, nie ten prog. Patrz .stage__ar w sekcji 11. */
    .stage__ar.is-ready { display: block; }

    .faq__intro { padding: 0 0 var(--sp-7); }

    /* Przycisk kalkulatora na osi kolumny - wskazanie wlasciciela dla tego
       progu. `align-self`, a nie `margin-inline: auto`: kolumna jest pudelkiem
       flex z `align-items: flex-start`, wiec o polozeniu dziecka decyduje os
       poprzeczna, a nie jego wlasne marginesy - te ostatnie nie zrobilyby tu
       nic. Naglowek i akapit ZOSTAJA do lewej, tak samo jak w hero: tekst
       wielolinijkowy czyta sie na osi gorzej, a przycisk jest jednym klockiem
       i wysrodkowany domyka sekcje. */
    .faq__intro > .btn { align-self: center; }

    /* ======================================================================
       JEDEN STANDARD NADKRESLENIA - OD PROGU 1080
       ======================================================================
       Napis na osi kolumny, kreska po OBU stronach, 20 px do naglowka pod
       spodem. Wzorzec pochodzi z sekcji "AR W PRAKTYCE" i jest wskazaniem
       wlasciciela; FAQ dostal go jako drugi, a nadkreslenia naglowkow
       podstron ("OFERTA", "WDROZENIE - 3DMENU", "KONTAKT") jako trzecie.

       PRZENIESIONE z progu 860 - polecenie wlasciciela, ta sama sprawa, co
       z ukladem hero wyzej w tym bloku: sekcje skladaja sie do jednej kolumny
       WLASNIE TUTAJ, wiec od tego progu ma obowiazywac uklad mobilny. Zanim
       to zrobilem, pasmo 861-1080 px mialo stan posredni - jedna kolumna,
       ale naglowki sekcji dalej w wersji desktopowej (kreska tylko w prawo).

       Regula jest WSPOLNA celowo. Wczesniej ten sam zestaw deklaracji stal
       w arkuszu dwa razy osobno i trzeci przypadek byl by trzecia kopia -
       a wtedy zmiana odstepu albo koloru kreski wymaga pamietania o wszystkich
       miejscach naraz. Tu wystarczy dopisac selektor do listy.

       Powyzej progu standard NIE obowiazuje i to jest swiadome: tam napis
       otwiera kolumne tekstu i trzyma sie jej lewej krawedzi, wiec kreska
       idzie tylko w prawo (patrz .proof__eyebrow::after w sekcji 20a).
       ====================================================================== */
    .faq__intro > .eyebrow,
    .proof__eyebrow,
    .page-head .eyebrow,
    .eyebrow--strong {
        display: flex;
        align-items: center;
        gap: var(--sp-5);
        /* Odstep pod nadkresleniem jest ZMIENNA, bo nie jest jednakowy we
           wszystkich trzech miejscach - i to jest swiadome. Domyslne 20 px
           obowiazuje w sekcjach tresci (FAQ, "AR W PRAKTYCE"), gdzie pod
           nadkresleniem stoi naglowek sekcji. Naglowek podstrony ma pod nim
           duzo wiekszy tytul strony i tam wlasciciel zmierzyl 16 px - patrz
           nadpisanie nizej.
           Historia wartosci domyslnej: stalo tu kiedys 16 px (za malo wobec
           22 px pod naglowkiem sekcji) i przez chwile 26 px z rachunku na
           interlinii - obie odrzucone przez wlasciciela na telefonie. */
        margin-bottom: var(--eyebrow-gap, 1.25rem);
    }

    /* 16 px zamiast 20 px pod "OFERTA", "WDROZENIE", "KONTAKT" - wartosc
       dobrana przez wlasciciela na telefonie (wpisana u siebie w element.style
       i zatwierdzona). Nie zmieniam przez to sekcji tresci: FAQ i "AR
       W PRAKTYCE" byly zatwierdzone osobno na 20 px i zostaja przy swoim.
       Wspolna zostaje CALA reszta wzorca - uklad, kreski po bokach, odstep
       miedzy napisem a kreska - a rozni sie jedna liczba, ktora ma teraz
       nazwe zamiast siedziec zduplikowana w drugiej regule. */
    .page-head .eyebrow { --eyebrow-gap: 1rem; }

    .faq__intro > .eyebrow::before,
    .faq__intro > .eyebrow::after,
    .proof__eyebrow::before,
    .page-head .eyebrow::before,
    .page-head .eyebrow::after,
    .eyebrow--strong::before,
    .eyebrow--strong::after {
        content: '';
        flex: 1 1 auto;
        height: 1px;
        background: var(--c-eyebrow-line);
    }

    /* Wyjatek dla FAQ: jego kolumna jest pudelkiem flex z `align-items:
       flex-start`, wiec nadkreslenie mialo szerokosc samego napisu i kreskom
       nie bylo gdzie rosnac (zmierzone: obie po 0 px). Sekcja "Skala 1:1"
       i naglowki podstron tego nie potrzebuja - tam rodzic jest zwyklym
       blokiem. */
    .faq__intro > .eyebrow { align-self: stretch; }

    /* Tytul podstrony na osi kolumny - tak samo, jak .hero__title wyzej w tym
       progu. Nadkreslenie nad nim jest juz wysrodkowane przez wspolny standard,
       wiec tytul wyrownany do lewej zostawal jako jedyny element naglowka poza
       osia. Akapit pod spodem ZOSTAJE do lewej: wysrodkowany tekst
       wielolinijkowy czyta sie gorzej, a tam jest to zdanie na trzy-cztery
       wiersze. */
    .page-head__title { text-align: center; }
    /* Uklad jednokolumnowy: wolnego miejsca nie ma, wiec oba `auto` schodza do
       zera - odstep nad maskotka trzeba podac wprost, a zapas pod nia (sluzacy
       tylko podnoszeniu) jest tu niepotrzebny. */
    .faq__robot { margin-top: var(--sp-9); padding-bottom: 0; }
    .faq__title span { display: inline; white-space: normal; }
    .faq__grid { padding-block: var(--sp-10) var(--sp-11); }
    .faq__robot img { width: 22rem; }
    .faq__answer p { padding-right: var(--sp-5); }

    .pricing__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    /* ======================================================================
       JEDNA MIARA TEKSTU NA CALEJ STRONIE
       ======================================================================
       Wskazanie wlasciciela: "musisz sie zdecydowac na ustawienie tekstu
       konsekwentne". Hero dostal sufit 42rem i os kolumny juz wczesniej (patrz
       wyzej w tym progu), a sekcje ponizej zostawaly przy pelnej szerokosci
       kontenera. Zjezdzajac strona w dol tekst przeskakiwal wiec z osi do lewej
       krawedzi, a wiersz akapitu w "AR W PRAKTYCE" mial przy 1000 px 944 px -
       dwa razy wiecej, niz da sie wygodnie czytac.
       Ten sam sufit i to samo `margin-inline: auto`, co w hero: jedna wartosc
       z jednym uzasadnieniem (okolice 65 znakow w wierszu).
       `width: 100%` jest tu KONIECZNE z tego samego powodu, co tam - element
       siatki z `margin-inline: auto` kurczy sie do szerokosci wlasnej tresci
       zamiast wypelnic pas.
       ====================================================================== */
    .proof__text,
    .faq__intro,
    .faq__grid > [data-faq] {
        width: 100%;
        max-width: 42rem;
        margin-inline: auto;
    }

    .proof__grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-8); }

    /* Telefon na osi kolumny, a podpis NAD nim - polecenie wlasciciela dla tego
       progu (PRZENIESIONE z 860 razem z reszta ukladu mobilnego). W ukladzie
       dwukolumnowym telefon trzyma sie lewej krawedzi swojej kolumny, bo obok
       stoi tekst; gdy kolumna jest jedna, cala szerokosc nalezy do niego
       i lewe wyrownanie zostawialo caly zapas po prawej stronie.
       Podpis nad obudowa dlatego, ze ma sie czytac jak zapowiedz, a nie jak
       podpis pod obrazkiem. Odstepy w tej kolumnie schodza do --hero-gap,
       czyli do rytmu calej strony - droga "kreska -> podpis -> telefon" ma
       wtedy dwa rowne kroki. */
    .proof__phone {
        align-items: center;
        gap: var(--hero-gap);
    }

    .proof__caption {
        order: -1;
        /* 10 px blizej obudowy, niz wynika z rytmu pasma - korekta OPTYCZNA
           dobrana przez wlasciciela. Powod ten sam, co przy zielonym zdaniu
           w hero: napis ma pod soba wlasne swiatlo wiersza, a obudowa zaczyna
           sie zaokraglonym narozem, wiec rowny odstep czytal sie jako wiekszy
           od tego nad napisem. Zmierzone po korekcie: 26 px nad podpisem,
           16 px pod nim. */
        margin-bottom: -0.625rem;
    }

    /* Ponizej tego progu kolo przestaje byc kolem: przy trzech kafelkach na
       szerokosc telefonu wychodzilyby waskie owale (zmierzone 109x186 px).
       Zostaje ta sama tresc w wierszu - ikona obok napisu. */
    .proof__tiles {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--sp-3);

        /* Kierunek zostaje ten sam - z prawej - bo to on jest istota efektu.
           Zmienia sie dlugosc trasy: kafelki stoja tu jeden POD drugim, wiec
           kazdy ma to samo miejsce docelowe i roznicowanie dystansu nie mialoby
           czego pokazac. Zamiast tego kazdy leci ten sam, krotszy odcinek -
           na szerokosci telefonu dluzszy lot to samo rozmycie ruchu, nie efekt. */
        --reveal-slot: 60%;
    }

    .proof__tiles.is-reveal .proof__tile:nth-child(1),
    .proof__tiles.is-reveal .proof__tile:nth-child(2),
    .proof__tiles.is-reveal .proof__tile:nth-child(3) { --reveal-steps: 1; }

    /* Kafelek w ukladzie poziomym: SIATKA, nie wiersz flex. Dwie rzeczy naraz
       tego wymagaja (obie zgloszone przez wlasciciela):
       - kolo z ikona przestaje byc "jajowate". W wierszu flex kurczylo sie,
         bo mialo domyslne `flex-shrink: 1`, a napis obok bral cala szerokosc:
         wysokosc zostawala 72 px, szerokosc schodzila nizej. W siatce kolumna
         `auto` daje mu jego wlasna szerokosc i nikt jej nie negocjuje.
       - drobny podpis idzie POD napis, a nie obok niego. W wierszu byl trzecim
         elementem w tej samej linii, przez co lamal sie na dwie-trzy linijki
         przy prawej krawedzi. */
    .proof__tile {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        align-items: center;
        column-gap: var(--sp-4);
        row-gap: var(--sp-1);
        aspect-ratio: auto;
        padding: var(--sp-4) var(--sp-5);
        border-radius: var(--r-lg);
        text-align: left;
    }

    /* Kolo zajmuje oba wiersze - napis i podpis stoja obok niego, jeden pod
       drugim. Zapas pod ikona byl potrzebny w ukladzie pionowym; tutaj
       rozjezdzalby srodki. */
    .proof__icon {
        grid-row: 1 / span 2;
        margin-bottom: 0;
        /* Rozmiar WRACA do stalego. Plynna wartosc z :root pilnuje proporcji
           KOLA, a tutaj kola juz nie ma - kafelek jest wierszem, wiec ikona
           nie ma czego rozpychac i ma zostac taka, jaka wlasciciel zatwierdzil
           na telefonie. Bez tej linii zeszlaby przy 900 px z 72 na 56 px. */
        width: 4.5rem;
        height: 4.5rem;
    }

    /* Napis i podpis maja stac TUZ przy sobie. Sama `row-gap` tego nie zalatwia:
       wiersze siatki sa rozciagniete przez kolo (72 px na dwa wiersze), a przy
       `align-items: center` kazdy tekst stawal na srodku SWOJEGO wiersza -
       zmierzone 19 px przerwy przy 4 px odstepu siatki. Dosuniecie napisu do
       dolu jego wiersza, a podpisu do gory jego wiersza zostawia miedzy nimi
       sam odstep siatki; nadmiar wysokosci ucieka nad i pod pare, czyli tam,
       gdzie ma byc - blok tekstu zostaje wysrodkowany wzgledem kola. */
    .proof__tile-title,
    .proof__tile-sub { grid-column: 2; }

    .proof__tile-title { align-self: end; }
    .proof__tile-sub   { align-self: start; }
    /* Karty ida w kolejnosci Z MARKUPU: NA START, GASTRO, PREMIUM - wskazanie
       wlasciciela. Stalo tu `.plan--popular { order: -1 }`, ktore wypychalo
       GASTRO na gore, bo w kolumnie widac naraz jedna karte i wersja
       popularna miala byc pierwsza. Wlasciciel woli naturalna sciezke ceny:
       od najtanszej w gore - gosc porownuje wtedy rosnaco, a nie od srodka.
       ROLLBACK: przywrocic `.plan--popular { order: -1; }` w tym miejscu. */

    .enterprise {
        flex-direction: column;
        align-items: flex-start;
        padding: var(--sp-7) var(--sp-6);
        /* Ten sam odstep, co MIEDZY kartami pakietow (--sp-5, 22 px) zamiast
           --sp-9 z reguly bazowej (46 px) - wskazanie wlasciciela. Na szerokim
           ekranie wieksza przerwa ma sens, bo franczyza stoi POD calym rzedem
           trzech kart i czyta sie jak osobne pasmo. Gdy karty ustawiaja sie
           jedna pod druga, franczyza jest po prostu kolejnym klockiem w tej
           samej kolumnie i podwojny odstep wygladal, jakby wypadla z rytmu. */
        margin-top: var(--sp-5);
    }

    /* Przycisk na CALA szerokosc skrzynki. Regula bazowa liczy jego szerokosc
       wzorem dopasowujacym go do przycisku trzeciej karty cennika - ten wzor
       zaklada dopelnienie 2.125rem, a tutaj dopelnienie zmienia sie na
       --sp-6, wiec wynik przestaje znaczyc to, co mowi jego komentarz.
       W kolumnie nie ma juz zreszta do czego rownac: karty stoja jedna pod
       druga, a nie obok. */
    .enterprise__cta { width: 100%; }

    /* Kolo bierze szerokosc kolumny, w ktorej stoi. Regula bazowa liczy ja
       z `20.5vw`, co przy czterech kolumnach jest sluszne, ale przy dwoch
       zostawia kolo waskie na 240 px w kolumnie majacej ponad 400 px - tekst
       nie mial sie gdzie zmiescic w poziomie i rozpychal ksztalt w pionie
       (zmierzone przy 900 px: 240 x 410, czyli owal 1:1,7 zamiast kola).
       Szersze kolo lamie ten sam tekst na mniej wierszy, wiec wraca blizej
       okragłego. Sufit 22rem, zeby na granicy progu nie urosly nadmiernie. */
    /* Dwie kolumny: srednica liczona od OKNA, nie od kontenera. */
    :root { --step-d: min(42vw, 20rem); }

    /* Schodek czterech okregow nie miesci sie ponizej 1080 px - kroki ustawiaja
       sie w dwie kolumny bez opadania. */
    .steps__grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--sp-7) 0;
        margin-inline: 0;
    }

    .step:nth-child(2),
    .step:nth-child(3),
    .step:nth-child(4) { margin-top: 0; }

    /* TRESC KROKU WYZEJ W KOLE - wskazanie wlasciciela: numer ma pojsc troche
       wyzej, a reszta za nim, zeby wszystko miescilo sie w okregu.
       Dopelnienie jest PRZENIESIONE z gory na dol, a nie dolozone: 12 px mniej
       u gory i 12 px wiecej u dolu. Suma zostaje ta sama, wiec kolo nie rosnie,
       a tresc - wysrodkowana miedzy dopelnieniami - jedzie o te 12 px w gore.
       Pierwsze podejscie DOKLADALO 24 px na dole i to byl blad: przy 320 px
       kolo urastalo z 269x273 na 269x293, czyli z lekko owalnego robilo sie
       jajem. Tresc krok ma 188 px, wiec przy dopelnieniu 0,15 srednicy z obu
       stron kolo musi miec co najmniej 269 px - kazdy dolozony piksel wychodzi
       poza okrag. */
    .step__disc {
        padding-top: calc(var(--step-d) * 0.15 - 0.75rem);
        padding-bottom: calc(var(--step-d) * 0.15 + 0.75rem);
    }

    /* ...i "Menu, materialy i panel." blizej naglowka kroku - druga polowa tego
       samego wskazania. 8 -> 4 px. */
    .step__title { margin-bottom: var(--sp-1); }

    /* Ciasniejsze interlinie w kroku - wskazanie wlasciciela ("rozstrzelone,
       a na telefonie trzeba szanowac przestrzen, o jeden-dwa piksele mniej").
       Policzone, nie dobrane na oko - wysokosc wiersza przed i po:
         .step__title  21   px pisma:  25,2 -> 23,5 px  (1,2  -> 1,12)
         .step__lead   15,5 px pisma:  22,5 -> 20,9 px  (1,45 -> 1,35)
         .step__desc   15   px pisma:  24,0 -> 22,5 px  (1,6  -> 1,5)
       Kazdy schodzi o okolo 1,5 px, czyli w zadanym przedziale. Przy czterech
       krokach i kilku wierszach na krok daje to kilkadziesiat pikselow mniej
       przewijania, a tekst nadal oddycha.
       UWAGA na przyszlosc: to sa kolejne SUROWE interlinie w arkuszu, ktory
       nie ma jeszcze zadnego tokena --lh-*. Przy porzadkowaniu typografii te
       trzy wartosci maja wejsc do wspolnej skali razem z reszta. */
    .step__title { line-height: 1.12; }
    .step__lead  { line-height: 1.35; }
    .step__desc  { line-height: 1.5; }

    /* Dopelnienie dolne kolumny kontaktu ZDJETE, a odstep miedzy nia
       a formularzem sprowadzony do 20 px - tyle, ile dzieli nadkreslenie
       "KONTAKT BEZPOSREDNI" od pierwszej pozycji listy (wskazanie
       wlasciciela: ta sama odleglosc po obu stronach listy).
       Bylo 64 px, bo skladaly sie na nie DWIE wartosci naraz: dopelnienie
       kolumny (32) i `gap` siatki (32). Zostaje sam `gap`. */
    /* "KONTAKT BEZPOSREDNI" schodzi z ekranu na telefonie - wskazanie
       wlasciciela: naglowek podstrony mowi juz "KONTAKT", wiec drugi raz to
       samo tuz pod nim nic nie wnosi. Zostaje "FORMULARZ", bo ten nazywa
       INNA rzecz.
       Znowu technika .sr-only, a nie `display: none`: etykieta zostaje dla
       czytnika ekranu, ktory bez niej dostalby liste telefonow i adresow bez
       zapowiedzi, czym ona jest. */
    .contact__aside > .eyebrow--strong {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
    }

    /* TLO KARTY FORMULARZA GESTSZE na telefonie - zgloszenie wlasciciela:
       "za bardzo jest przejrzyste". Wspolny --grad-card ma u dolu 0,50 krycia,
       co na szerokim ekranie jest w porzadku, bo karta stoi tam na spokojnej
       czesci kadru. Na telefonie zajmuje niemal caly ekran i przechodzi przez
       najjasniejsze miejsca zdjecia, wiec przez pola formularza przebijaly
       smugi swiatla.
       WLASNY token, a nie zmiana --grad-card: tamten nosi siedem elementow
       (karty pakietow, obudowa telefonu, kafelki), a chodzi o jeden. Zmienia
       sie samo krycie dna - kierunek gradientu i zielona poswiata u gory
       zostaja te same, wiec karta nalezy dalej do tej samej rodziny. */
    :root {
        --grad-form: linear-gradient(160deg,
                        rgba(0, 107, 69, 0.12) 0%,
                        rgba(var(--rgb-bg-card), 0.85) 62%);
    }

    .form-card { background: var(--grad-form); }

    /* Obrys karty formularza wyrazniejszy na telefonie - wskazanie
       wlasciciela: na desktopie karta odcina sie od tla, a tu gubila sie
       w zdjeciu. Uzyty ISTNIEJACY token --c-border-mid (0,18) zamiast nowej
       liczby; policzone po zlozeniu na tle sekcji: jasnosc obrysu z 47 na
       ok. 65, czyli widac go, ale nie robi sie z tego ramka.
       Gorna krawedz ZOSTAJE akcentowa (--c-border-accent-soft) - to ona daje
       karcie ten swietlny akcent u gory i nie ma powodu jej ruszac. */
    .form-card { border-color: var(--c-border-mid); }

    /* Gorna krawedz WIDOCZNA - zgloszenie wlasciciela. Stal tu
       --c-border-accent-soft, czyli ciemna zielen przy kryciu 0,32.
       Policzone na tle karty: ta krawedz ma kontrast 1,89, a boki 1,73, wiec
       formalnie byla nawet WYRAZNIEJSZA od nich - a mimo to znikala. Powod
       jest w odcieniu, nie w jasnosci: boki sa bialawe i odcinaja sie od
       zielonego tla, a ciemna zielen na ciemnej zieleni gubi sie niezaleznie
       od tego, co mowi rachunek kontrastu.
       Jasny akcent marki (0,42) daje 2,95 i jest widoczny jako celowa linia -
       a o taka wlasnie chodzilo, bo to ona daje karcie swietlny akcent u gory.
       PRZYCZYNA byla po mojej stronie: przy zageszczaniu tla podniosłem boki
       z 0,10 na 0,18 i to one wysunely sie przed gore. */
    .form-card { border-top-color: var(--c-border-accent); }

    .contact__aside { padding: 0; }

    /* Uklad jednokolumnowy: polozenie z siatki desktopowej trzeba ZDJAC.
       Wymuszaloby tu wiersze, ktorych nie ma - o kolejnosci decyduje sam
       markup (maskotka stoi w nim po formularzu) i regula `order` nizej. */
    .contact__grid > .contact__aside,
    .contact__grid > .form-card,
    .contact__grid > .robot--inline {
        grid-column: auto;
        grid-row: auto;
    }

    /* 34 px, czyli poprzednie 20 plus 14 zamowione przez wlasciciela. */
    .contact__grid { row-gap: 2.125rem; }

    /* ======================================================================
       MASKOTKA KONTAKTU POD FORMULARZEM
       ======================================================================
       W markupie stoi PRZED karta formularza, bo na szerokim ekranie zajmuje
       lewa kolumne obok niego. Na telefonie kolumny skladaja sie w jedna
       i maskotka wypychala formularz w dol - gosc musial przewijac przez cala
       grafike, zanim doszedl do pol. Wlasciciel chce ja pod spodem.
       Zrobione przez `order` w siatce, wiec MARKUP ZOSTAJE NIETKNIETY: ta sama
       kolejnosc obowiazuje czytnik ekranu i szeroki ekran.

       Swiatlo nad i pod maskotka rowne, przez --sp-7 z obu stron. Regula
       bazowa dokladala do tego jeszcze `padding-top: 3.25rem` i `margin-top:
       auto` (dosuwaly ja do dolu kolumny obok formularza) - oba tutaj
       zerowane, bo w kolumnie nie ma juz czego dosuwac, a razem dawaly nad
       maskotka ponad 80 px przy zerze pod nia. */
    .robot--inline {
        /* Swiatlo nad maskotka daje `gap` siatki (--sp-7 = 32 px). Wlasciciel
           chce polowy, wiec ujemny margines zdejmuje 16 px.
           Pod maskotka bylo DUZO wiecej niz nad nia i to nie bylo widac
           w pomiarze do konca siatki: doliczalo sie jeszcze dopelnienie dolne
           calej sekcji (--sp-11 = 88 px), czyli realnie 120 px przy 32 px
           nad. Margines dolny schodzi wiec do zera, a dopelnienie sekcji
           dostaje te same 16 px - regula ponizej. */
        /* Korekta nad maskotka idzie ZA `gap` siatki, zeby swiatlo nad nia
           zostawalo tymi samymi 16 px niezaleznie od tego, jak daleko odsuniemy
           formularz. Przy gap 34 px odejmujemy 18. */
        margin-top: -1.125rem;
        margin-bottom: 0;
        padding-top: 0;

        /* O 15% mniejsza - wskazanie wlasciciela: na tle pozostalych maskotek
           wydawala sie za duza. Regula bazowa daje jej 31.75rem z ograniczeniem
           `max-width: 100%`, czyli na telefonie brala CALA szerokosc kolumny.
           `margin-inline: auto` jest tu konieczne - bez niego 85% zostaloby
           dosuniete do lewej zamiast na osi. */
        /* SUFIT jest tu konieczny, nie ozdobny. Samo `85%` liczy sie od
           kontenera, wiec maskotka rosla razem z ekranem: przy 390 px dawalo
           to zatwierdzone 298 px, ale juz przy 860 px okolo 700 px - i wtedy
           zajmowala pol widoku. To ten sam blad, co wczesniej przy maskotkach
           Oferty i Wdrozenia, tylko z drugiej strony: tam wartosc zamarzala
           na sufcie, tu rosla bez zadnego.
           20rem = 320 px, czyli tuz nad wartoscia z telefonu, wiec na telefonie
           nic sie nie zmienia, a wyzej maskotka przestaje puchnac. */
        width: min(85%, 20rem);
        margin-inline: auto;
    }

    /* Dol sekcji kontaktu rowny swiatlu nad maskotka. Dotyczy TYLKO Kontaktu:
       na innych podstronach pod ostatnim blokiem nie stoi grafika. */
    .page-kontakt .contact { padding-bottom: 1rem; }

    .robot--head { bottom: -6.4375rem; width: clamp(11rem, 22vw, 16rem); }
    .robot--steps { bottom: -8.4375rem; width: clamp(13rem, 30vw, 22rem); }

    /* Tytul formularza na osi karty i z ciasniejsza interlinia - wartosci
       dobrane przez wlasciciela w inspektorze (text-align: center,
       line-height: 32px przy stopniu 26 px na telefonie).
       Interlinia zapisana jako WSPOLCZYNNIK, nie 32 px: --fs-h3 ma inna
       wartosc na tym progu (32 px) i inna ponizej 560 px (26 px), wiec sztywne
       piksele znaczylyby tu co innego niz to, co wlasciciel zatwierdzil.
       1,23 oddaje na telefonie dokladnie jego 32 px. */
    .form-card__title {
        text-align: center;
        line-height: 1.23;
    }

    .form-card { padding: var(--sp-7) var(--sp-6); }
}

/* --- 561-1080 px: maskotka kontaktu w luce obok listy kontaktow -----------
   Pasmo, w ktorym formularz zszedl juz pod kontakty, a lista kontaktow nie
   zajmuje jeszcze calej szerokosci: na prawo od niej zostawalo puste pole
   szerokie na kilkaset pikseli (zgloszenie wlasciciela, zrzut przy ok. 960 px).
   Maskotka wchodzi w te luke zamiast stac pod formularzem.
   ZAKRES, a nie kolejny prog `max-width`: ponizej 560 px lista zajmuje niemal
   cala szerokosc telefonu i luki juz nie ma - tam zostaje uklad z progu 1080
   (maskotka pod formularzem), ktorego ten blok nie cofa, tylko omija.
   Zapis progu jak przy 960 px wyzej: .02 px, zeby zakresy sie nie stykaly. */
@media (min-width: 560.02px) and (max-width: 1080px) {
    /* Pierwsza kolumna ma szerokosc tresci (tytul albo lista), druga bierze
       reszte. Formularz zajmuje caly drugi wiersz. */
    .contact__grid { grid-template-columns: auto minmax(0, 1fr); }

    .contact__grid > .contact__aside { grid-column: 1; grid-row: 1; }
    .contact__grid > .robot--inline  { grid-column: 2; grid-row: 1; }
    .contact__grid > .form-card      { grid-column: 1 / -1; grid-row: 2; }

    /* Pudelko maskotki rozciaga sie na wysokosc kolumny kontaktow, a obrazek
       jest w nie WPISANY (absolute + contain). Maskotka nie wplywa wiec na
       wysokosc wiersza: nie odpycha formularza w dol i sama dopasowuje sie
       do luki - wieksza, gdy nad lista stoi tytul (861-1080), mniejsza, gdy
       tytul schodzi z ekranu (ponizej 860). */
    .robot--inline {
        position: relative;
        align-self: stretch;
        width: auto;
        margin: 0;
        padding: 0;
    }

    /* Odbicie w poziomie, zeby reka wskazywala numery po lewej. Ta sama
       technika co .robot--flip (osobna wlasciwosc `scale`), ale bez tej klasy
       w markupie: na szerokim ekranie maskotka ma dalej patrzec w prawo,
       w strone formularza. */
    .robot--inline .robot__img {
        position: absolute;
        inset: 0;
        height: 100%;
        object-fit: contain;
        scale: -1 1;
        /* Sufit: nigdy wieksza niz na desktopie - wskazanie wlasciciela.
           Przy 1080 px pole obok listy ma 667 x 357 px i bez sufitu maskotka
           wpisana w nie wychodzila 531 x 357, wyzej niz 508 x 342 na 1440.
           `margin: auto` przy `inset: 0` ustawia przyciete pudelko na srodku
           pola, wiec maskotka dalej siedzi w luce, a nie przy jej krawedzi. */
        max-width: var(--robot-inline-w);
        max-height: var(--robot-inline-h);
        margin: auto;
    }

    /* Ostatnim elementem sekcji jest tu formularz, nie maskotka - wraca wiec
       dopelnienie dolne z reguly bazowej .contact. Zmniejszenie z progu 1080
       bylo liczone pod maskotke stojaca na samym dole. */
    .page-kontakt .contact { padding-bottom: var(--sp-11); }
}

/* --- do 860 px: nawigacja zmienia sie w szuflade -------------------------- */
@media (max-width: 860px) {
    :root {
        --header-h: 4.5rem;
        --logo-h: 2.25rem;
        /* O tyle panel z miniaturami podjezdza pod scene. Ma NACHODZIC na dolny
           luk przerywanego okregu (polecenie wlasciciela): dzieki temu caly
           modul nawigacji miesci sie na ekranie razem z daniem. Panel ma wlasne
           rozmycie tla, wiec okrag pod nim nie znika - przechodzi w jego tlo. */
        /* --hero-gap i --panel-lift PRZENIESIONE do progu 1080 razem z blokiem
           hero, ktory na nich stoi. Zostawione tutaj powodowaly, ze w pasmie
           861-1080 px byly NIEZDEFINIOWANE: `row-gap: var(--hero-gap)` dawalo
           zero (tytul stykal sie ze scena), a `top: calc(100% - var(--panel-lift))`
           bylo nieprawidlowe i panel miniatur odskakiwal pod scene. */
        /* Miejsce rezerwowane pod scena. Panel jest pozycjonowany bezwzglednie,
           wiec nie zajmuje w ukladzie ani piksela - te rezerwe trzeba mu dac
           recznie, a jej wielkosc wynika z rownania: odstep miedzy panelem
           a akapitem ma byc TAKI SAM, jak miedzy akapitem a pierwszym
           przyciskiem, czyli rowny --hero-gap (polecenie wlasciciela:
           "to musi byc jednolite").
             lead = scenaDol + drop + hero-gap
             panelDol = scenaDol - panel-lift + sp-4 + wysokosc panelu
           Po podstawieniu: drop = wysokosc panelu + sp-4 - panel-lift.
           Zmierzone przy 390 px: 121 + 16 - 104 = 33 px. Ta liczba jest tu
           WARTOSCIA ZAPASOWA na wypadek braku skryptu - wlasciwa wpisuje
           Showcase.fitPanel z realnej wysokosci panelu, bo ta zmienia sie
           z szerokoscia ekranu (przy 320 px podpis lamie sie na dwie linie
           i panel rosnie o 19 px). */
        /* TEKST OPISOWY - jeden stopien dla wszystkiego, co jest akapitem
           wyjasniajacym: zdania wprowadzajace pod naglowkami sekcji, tresc
           odpowiedzi w FAQ i teksty w stopce. 15 px na malym telefonie, 17 px
           od 430 px w gore.
           Deklaracja stoi w progu 860, a nie 560, bo korzysta z niej takze
           stopka, ktorej reguly sa w TYM progu. Gdy token siedzial nizej,
           w pasmie 561-860 px byl nieokreslony, `font-size: var(--fs-opis)`
           stawalo sie nieprawidlowe i tekst stopki dziedziczyl 16 px zamiast
           swoich 14,5 - zmierzone. */
        --fs-opis: clamp(0.9375rem, 4vw, 1.0625rem);

        --panel-drop: 2rem;
        /* Obiekt 3D ma stac DOKLADNIE na srodku miedzy dolna krawedzia naglowka
           a gorna krawedzia panelu z miniaturami - polecenie wlasciciela.
           Wyprowadzenie: scena zaczyna sie `--hero-gap` pod naglowkiem, panel
           stoi `--sp-4` ponizej jej dolu, podniesiony o `--panel-lift`. Srodek
           przedzialu wypada wtedy o polowe sumy (lift - odstep panelu + odstep
           hero) powyzej srodka sceny - i tyle wlasnie wynosi podniesienie.
           Wysokosc sceny sie SKRACA w tym rachunku, wiec wartosc jest ta sama
           na kazdej szerokosci ekranu; zmienia sie tylko wtedy, gdy zmieni sie
           ktoras z trzech zmiennych powyzej. */
        /* Podniesienie sceny ponad geometryczny srodek. Wartosc optyczna,
           poproszona po usunieciu okregu: bez jego dolnego luku obiekt czyta
           sie jako osadzony odrobine za nisko. Osobna zmienna, zeby bylo widac,
           ze srodek nadal liczy sie sam, a to jest do niego poprawka. */
    }

    /* Przerywany okrag NIE wchodzi na telefon - decyzja wlasciciela. Na tej
       szerokosci nie robi tego, co na desktopie (nie obudowuje sceny, tylko
       przecina ja w poprzek), a liczy sie wylacznie wyeksponowany obiekt 3D.
       `display: none`, a nie zmiana rozmiaru: element z ramka i animacja
       obrotu, ktorego nie widac, nadal kosztowalby klatki. */
    .stage__ring { display: none; }

    /* MASKOTKA FAQ wraca NAD liste pytan, czyli tam, gdzie stala pierwotnie -
       decyzja wlasciciela po obejrzeniu obu wariantow z tekstem obok (najpierw
       oplywanie, potem dwie kolumny). Kolumna zostaje pudelkiem flex, a `order`
       z reguly bazowej odsyla maskotke na koniec - to samo, co na desktopie.
       W markupie stoi przed akapitem (zostalo po probie z `float`), wiec bez
       tego `order` wyladowalaby nad tekstem.
       Nowy jest sam obrot: maskotka pochyla sie w strone pytan pod soba
       (polecenie wlasciciela). Doklejony do `translateZ(0)` z reguly bazowej,
       bo `transform` jest jedna wlasciwoscia - podanie samego obrotu skasowalo
       by tamta warstwe. */
    .faq .faq__robot img { transform: translateZ(0) rotate(45deg); }

    /* Odstepy wokol maskotki liczone do jej WIDOCZNEJ sylwetki, a nie do
       pudelka. Po obrocie o 45 stopni w pudelku zostaja puste naroza - okiem
       widac wtedy odstep o kilkadziesiat pikselow wiekszy, niz mowi uklad
       (zmierzone: 58 px nad sylwetka i 93 px pod nia przy 46 px i 56 px
       w pudelkach). Ujemny margines dolny sciaga te roznice; ma byc wiekszy niz
       gorny, bo pusty naroznik pod sylwetka jest wiekszy. Cel: po ok. 34 px
       z obu stron, czyli tyle, ile miedzy akapitem a przyciskiem wyzej. */
    .faq__robot {
        margin-top: var(--sp-5);
        margin-bottom: -3.75rem;
    }

    /* Kostka ladowania ma stac TAM, GDZIE POJAWIA SIE DANIE, czyli na srodku
       okna sceny (zgloszenie wlasciciela: "jest bardzo mocno na dol
       przesunieta"). Regula bazowa cofa ja o --stage-lift, bo pierwotnie
       kostka miala siedziec w geometrycznym srodku PASMA, a nie okna; na
       telefonie ten lift urosl do 67 px (scena jest teraz wysrodkowana miedzy
       naglowkiem a panelem miniatur) i kostka wyladowala wyraznie ponizej
       miejsca, w ktorym za chwile staje danie. */
    .stage__cube { transform: none; }

    /* Poswiaty sceny sa szersze od ekranu (--stage-glow: 112vw), wiec pasmo
       hero je przycina - inaczej strona dostalaby przewijanie w poziomie.
       `overflow-x`, nie `overflow`, i tylko tutaj: na szerokim ekranie nic
       z hero nie wystaje.
       Dopelnienie dolne w rytmie hero: kreska pod lista cech ma stac tak samo
       daleko od niej, jak akapit od przycisku (polecenie wlasciciela:
       "musimy miec jednolitosc zachowana"). */
    /* Dopelnienie dolne pasma stoi w progu 1080 - patrz tam. */
    .hero { overflow-x: hidden; }

    /* Odstep pod kreska stoi w progu 1080 - patrz tam. */

    /* Telefon ma sie miescic na jednym ekranie razem z podpisem - dzis byl na
       to za duzy. Trzecim ograniczeniem szerokosci jest wysokosc OKNA: przy
       proporcji ekranu 288:618 obudowa szerokosci 36vh ma ok. 62vh wysokosci,
       wiec przy oknie 844 px zostaje ponad 100 px zapasu pod belka naglowka,
       a na niskim telefonie (667 px) blok dalej sie miesci. */
    :root { --phone-w: min(21rem, 100%, 36vh); }

    /* Telefon i podpis PRZENIESIONE do progu 1080 - wskazanie wlasciciela:
       uklad mobilny ma obowiazywac od tego samego progu, na ktorym sekcja
       sklada sie do jednej kolumny. Zostawione tutaj dawaly w pasmie
       861-1080 px stan posredni: jedna kolumna, ale telefon dosuniety do lewej
       krawedzi z calym zapasem po prawej. */

    /* Swiatlo POD ostatnim kafelkiem rowne temu, ktore ma nad soba kreska
       konczaca hero - ale rowne OKIEM, nie pudelkami. Nad pierwsza kreska stoi
       lista cech, czyli TEKST: jego pudelko konczy sie ok. 6 px ponizej liter
       (polowa interlinii plus miejsce na ogonki), wiec te same 26 px czytaja
       sie tam jako 32 px. Kafelek konczy sie przerywanym obrysem, czyli twarda
       krawedzia - zeby przerwa wygladala tak samo, musi byc o te 6 px wieksza.
       Kreska to dolne obramowanie samej sekcji (.band--line), wiec odstep
       ustawia jej dopelnienie. Bylo --sp-11 (88 px). */
    /* Dopelnienie dolne sekcji schodzi do ZERA. Odkad nie ma kreski konczacej
       pasmo, przerwe miedzy ostatnim kafelkiem a nadkresleniem FAQ ustala juz
       tylko jedna wartosc - dopelnienie gorne FAQ nizej. Trzymanie jej w dwoch
       miejscach znaczylo, ze kazda zmiana wymaga dwoch edycji i rachunku
       w pamieci (bylo 32 + 26 = 58 px, wlasciciel prosil o 28 px mniej). */
    .proof { padding-bottom: 0; }

    /* ...i tyle samo POD kreska, zanim zacznie sie FAQ. Te dwa odstepy razem
       z para przy pierwszym separatorze daja cztery rowne przejscia miedzy
       pasmami na calej stronie glownej. */
    /* Gora: cala przerwa miedzy sekcja "Skala 1:1" a FAQ - 30 px (bylo 58 px,
       wlasciciel prosil o 28 px mniej).
       Dol: --hero-gap, czyli tyle samo, ile zostaje NAD przyciskiem skrotu do
       kontaktu. Dzieki temu przycisk ma po obu stronach rowne swiatlo, tak samo
       jak przyciski w hero. */
    .faq__grid { padding-block: calc(var(--hero-gap) + var(--sp-1)) var(--hero-gap); }

    /* Kreski miedzy pytaniami o wlos jasniejsze - 0,14 zamiast 0,10 krycia
       (wskazanie wlasciciela: "ciutke"). Tylko na telefonie: to tam wiersze sa
       niskie i gesto jedne pod drugimi, wiec slaba kreska gubila sie w tle.
       Na szerokim ekranie zostaje --c-border-soft - tam lista ma osiemnascie
       pozycji i mocniejsza linia zaczynala dominowac nad trescia (patrz
       komentarz przy .faq__item w sekcji 12). */
    /* ======================================================================
       PONIZEJ TEGO PROGU TEKST NIE MA JUZ SUFITU SZEROKOSCI
       ======================================================================
       Polecenie wlasciciela: "tego typu teksty juz nie powinny miec ogranicznika
       szerokosci, powinny sie rozlewac na caly kontener".
       Sufity maja sens tam, gdzie kontener jest szerszy od wygodnego wiersza -
       stad 40rem pod naglowkiem podstrony, 44rem w "AR W PRAKTYCE" czy 42rem,
       ktore wchodzi od progu 1080 i trzyma tresc na osi. Ponizej 860 px
       kontener sam jest juz wezszy albo prawie rowny tym wartosciom, wiec sufit
       przestaje cokolwiek chronic, a zaczyna zostawiac pas pustego pola po
       prawej: przy 800 px kontener ma 744 px, a wiersz lamal sie na 640.
       Zdjete RAZEM dla calej rodziny tekstow wprowadzajacych - inaczej czesc
       z nich rozlewalaby sie, a czesc nie, i wrocilby ten sam brak jednolitosci,
       ktory zdejmowalismy wyzej w progu 1080.
       ====================================================================== */
    .hero__title,
    .hero__lead,
    .hero__features,
    .page-head__lead,
    .page-head__lead--wide,
    .proof__text,
    .proof__lead,
    .faq__intro,
    .faq__lead,
    .faq__grid > [data-faq],
    .calc__lead { max-width: none; }

    .faq__item { border-bottom-color: var(--c-border-thin); }

    /* Skrot do kontaktu: te same klasy, co przycisk kalkulatora, wiec i ten sam
       wyglad oraz wymiary. Swiatlo nad nim i pod nim rowne --hero-gap, czyli
       temu, ktore w hero dzieli akapit od przycisku - o taka jednolitosc
       prosil wlasciciel. Dolne swiatlo wnosi dopelnienie sekcji (wyzej).
       Ostatnia kreska listy WRACA: przycisk zamyka teraz sekcje, wiec lista
       potrzebuje wlasnego domkniecia. */
    .faq__cta {
        display: flex;
        margin-top: var(--hero-gap);
    }

    /* Kafelki bliżej akapitu o 9 px, niz wynika z odstepu sekcji (--sp-8) -
       wskazanie wlasciciela. Zapisane jako odjecie od tamtej wartosci z tego
       samego powodu, co przy odstepie pod telefonem nizej: gdy zmieni sie
       odstep sekcji, korekta pojedzie razem z nim. */
    .proof__tiles { margin-top: calc(var(--sp-8) - 0.5625rem); }

    /* Odstep pod telefonem mniejszy o 8 px, niz wynika z siatki sekcji
       (--sp-8) - wskazanie wlasciciela. Zapisane jako odjecie od tamtej
       wartosci, a nie jako nowa liczba: gdy zmieni sie odstep sekcji, ta
       korekta pojedzie razem z nim. */
    .proof__grid { row-gap: calc(var(--sp-8) - 0.5rem); }

    /* Podpis nad telefonem - patrz prog 1080. */

    /* Kreski po bokach podpisu jasniejsze: 0,10 krycia gubi sie na ekranie
       telefonu (zgloszenie wlasciciela - "nie widac ich"). Na monitorze zostaja
       jak byly; tam kontrast otoczenia jest inny i sa czytelne. */
    .proof__caption::before,
    .proof__caption::after { background: var(--c-border-strong); }

    /* PODPIS MUSI SIE ZMIESCIC W SZEROKOSCI TELEFONU - inaczej znikaja kreski
       po bokach, a sam napis wypada z osi.
       Zmierzone przy 320x600 (maly telefon z paskami przegladarki): pudelko
       podpisu ma tam 216 px, bo --phone-w liczy sie takze z wysokosci okna
       (36vh), a napis z rozstrzeleniem 0,20em i dwoma odstepami po 16 px
       potrzebowal 223 px. Kreski dostawaly 0 px, a tekst - ustawiony od lewej,
       bo `nowrap` nie pozwala mu sie skurczyc - stal 7 px obok srodka
       obudowy (zgloszenie wlasciciela: "widok z telefonu nie jest wycentrowany
       w stosunku do telefonu, a kreseczki nam znikly").
       Trzy oszczednosci naraz, kazda mala: stopien o 12% mniejszy (wlasciciel
       dopuscil delikatne pomniejszenie), rozstrzelenie jak w podpisie panelu
       miniatur (0,12em) i o polowe wezsze odstepy przy kreskach. */
    .proof__caption {
        gap: var(--sp-2);
        font-size: 0.6875rem;   /* 11 px zamiast 12,5 */
        letter-spacing: 0.12em;
    }

    /* Kreska konczaca sekcje "Skala 1:1" znika - decyzja wlasciciela: rolę
       podzialu przejmuje samo nadkreslenie FAQ, ktore ma teraz kreski po bokach
       (nizej). Dwie linie jedna pod druga w odstepie 26 px robily z tego miejsca
       plot, a nie przejscie miedzy sekcjami. */
    .proof { border-bottom: 0; }

    /* Standard nadkreslenia PRZENIESIONY do progu 1080 - patrz tam. */
    /* ======================================================================
       JEDEN RYTM WOKOL SEPARATORA - wskazanie wlasciciela
       ======================================================================
       Wzorcem jest strona glowna: tam `hr.rule` ma 26 px nad soba i 26 px pod
       soba (zmierzone), a 26 px to --hero-gap, czyli ten sam odstep, ktory
       w hero dzieli akapit od przyciskow. Podstrony mialy to rozjechane
       i kazda inaczej:
         nad separatorem  49 px wszedzie (--head-pad-bottom: 3rem + 1 px kreski)
         pod separatorem  46 px na Ofercie (--sp-9),
                          72 px na Wdrozeniu i Kontakcie (4.5rem)
       Trzy podstrony, trzy rozne wartosci - stad wrazenie, ze separator
       "nie zgadza sie" ze strona glowna.

       Obie strony kreski schodza teraz do --hero-gap. Jesli ten rytm ma sie
       kiedys zmienic, zmienia sie JEDNA zmienna, a nie cztery reguly.

       Odstep NAD nadkresleniem idzie tym samym tropem: bylo 52 px pustego pola
       miedzy belka a napisem "OFERTA", czyli wiecej niz cala reszta rytmu.
       Polowa tego to 26 px - i to jest znowu --hero-gap, ta sama wartosc,
       ktora rzadzi separatorem. Naglowek podstrony na telefonie stoi wiec
       w calosci na jednej zmiennej: --hero-gap z gory, --hero-gap z dolu,
       a w srodku 20 px miedzy nadkresleniem a tytulem. */
    /* Odstep przy samej kresce jest o 8 px wiekszy od --hero-gap - wartosc
       DOBRANA PRZEZ WLASCICIELA na telefonie ("dodalem osiem pikseli, wtedy
       jest dobrze"). Zapisana jako calc od --hero-gap, a nie jako 34 px na
       sztywno: gdy rytm strony glownej sie kiedys zmieni, korekta pojedzie
       razem z nim zamiast zostac osierocona liczba.
       Odstep NAD nadkresleniem zostaje przy samym --hero-gap - tam kreski
       nie ma, wiec i korekty nie potrzeba. */
    :root {
        --sep-gap: calc(var(--hero-gap) + 0.5rem);

        /* Gradient kart cennika przekonfigurowany na telefonie - wskazanie
           wlasciciela ("na dole za czarne, przy przycisku ma byc delikatniej").
           Wersja bazowa dochodzi do pelnej ciemnosci juz na 60% wysokosci
           karty. Na szerokim ekranie karty stoja obok siebie i sa niskie, wiec
           te 60% wypada nisko i nie przeszkadza. Na telefonie karta jest
           kolumna na kilkaset pikseli - 60% wypada dokladnie na bullet
           pointach, a cala reszta, z przyciskiem wlacznie, jest juz plaskim
           czernidlem.
           Zmiana jest dwojaka: ciemnienie rozlozone na CALA wysokosc (stopien
           posredni na 45%, konac na 100% zamiast 60%) i slabsze u samego dolu
           (0,60 zamiast 0,78). Gora zostaje bez zmian - zielen na szczycie
           karty byla dobra. */
        --grad-plan:     linear-gradient(180deg,
                            rgba(var(--rgb-accent), 0.14) 0%,
                            rgba(var(--rgb-bg-plan), 0.30) 45%,
                            rgba(var(--rgb-bg-plan), 0.60) 100%);
        --grad-plan-pop: linear-gradient(180deg,
                            rgba(var(--rgb-accent), 0.20) 0%,
                            rgba(var(--rgb-bg-plan), 0.34) 45%,
                            rgba(var(--rgb-bg-plan), 0.64) 100%);
    }

    .page-head__inner {
        padding-top: var(--hero-gap);
        /* O 6 px mniej niz pod kreska i jest to korekta OPTYCZNA, nie
           niedokladnosc - dokladnie ta sama sprawa, co przy zdaniu w hero
           (patrz `margin-top: calc(var(--hero-gap) - 0.5rem)` nizej w tym
           progu). Akapit ma interlinie 1,6, wiec pod jego ostatnim wierszem
           zostaje wlasne swiatlo wiersza; pod kreska odstep zaczyna sie od
           razu. Rowne wartosci po obu stronach dawaly wiec NIEROWNE swiatlo.
           Szesc pikseli dobral wlasciciel okiem na telefonie. */
        padding-bottom: calc(var(--sep-gap) - 0.375rem);
    }
    .pricing, .steps, .contact { padding-top: var(--sep-gap); }

    /* SEPARATOR ZDJETY na Ofercie i Wdrozeniu - wskazanie wlasciciela: pod
       naglowkiem i zdaniem wprowadzajacym nie ma czego oddzielac, bo zaraz
       nizej i tak zaczyna sie wyrazny obiekt (przelacznik cennika, kolo
       pierwszego kroku).
       Tresc pod spodem wjezdza DOKLADNIE na miejsce kreski, zeby odstep od
       zdania zostal ten sam, co mial separator. W liczbach: kreska stala
       28 px pod tekstem (dopelnienie .page-head__inner), a pod nia bylo
       kolejne 34 px (--sep-gap). Teraz zostaje samo te 28 px, a --sep-gap
       schodzi do zera - gorna krawedz przelacznika i kola stoi tam, gdzie
       stala kreska.
       Kontakt ZOSTAJE z separatorem: wlasciciel wymienil dwie podstrony, a tam
       pod kreska zaczyna sie blok danych kontaktowych, ktory sam z siebie nie
       odcina sie od tekstu tak wyraznie. */
    .page-oferta .page-head,
    .page-wdrozenie .page-head { border-bottom: 0; }

    .page-oferta .pricing,
    .page-wdrozenie .steps { padding-top: 0; }

    /* KONTAKT ZATRZYMUJE SEPARATOR - wlasciciel chce go zobaczyc z powrotem.
       Rozni sie tym od Oferty i Wdrozenia, i jest ku temu powod: tam pod
       kreska zaczyna sie od razu wyrazny obiekt z wlasnym obrysem (przelacznik,
       kolo kroku), ktory sam odcina sie od tekstu. Tutaj zaczyna sie lista
       telefonow bez zadnej ramki.
       Marginesy ROWNE po obu stronach kreski i wziete z rytmu, ktory wlasciciel
       wskazal jako wzorzec: odstep od nadkreslenia KONTAKT do tytulu, czyli
       20 px. Nie --sep-gap (34 px), bo tamten byl dobrany dla Oferty,
       gdzie pod kreska stoi przelacznik z wlasnym swiatlem. */
    .page-kontakt .page-head__inner { padding-bottom: 1.25rem; }
    /* Odstep POD kreska o 11 px wiekszy niz nad nia - wskazanie wlasciciela.
       Zapisany jako dodanie do wartosci znad kreski, zeby bylo widac, ze to
       ta sama baza z korekta, a nie druga niezalezna liczba. Asymetria jest
       tu celowa: nad kreska konczy sie akapit, ktory ma pod ostatnim wierszem
       wlasne swiatlo interlinii, a pod kreska zaczyna sie od razu etykieta
       listy - rowne wartosci dawaly wiec NIEROWNE swiatlo optyczne. To ten
       sam mechanizm, co przy naglowkach podstron w tym samym progu. */
    .page-kontakt .contact { padding-top: calc(1.25rem + 11px); }

    /* Przycisk pod krokami ma miec TE SAMA odleglosc z czterech stron -
       wskazanie wlasciciela. Zmierzone przed zmiana: boki po 56 px, gora 64,
       dol 88. Wyrownane do 56 px, czyli do wartosci, ktora i tak juz byla.
       Uczciwa uwaga: te 56 px po bokach NIE jest ustawionym marginesem, tylko
       skutkiem szerokosci samego przycisku (277 px w kontenerze 390 px). Gora
       i dol sa wiec wpisane tak, zeby zgadzaly sie na szerokosciach telefonu,
       gdzie przycisk trzyma swoj rozmiar. Na innej szerokosci przycisk zmieni
       szerokosc, a z nia boczne swiatlo - rownosc we wszystkich przypadkach
       naraz wymagalaby liczenia w skrypcie, bo CSS nie zna szerokosci tresci
       przycisku. */
    .steps__cta { margin-top: 3.5rem; }
    .page-wdrozenie .steps { padding-bottom: 3.5rem; }

    /* Dol sekcji cennika rowny jej BOKOM - wskazanie wlasciciela ("margines
       dolnego bordera do konca zielonego taki jak po lewej i prawej").
       Bylo --sp-11, czyli 88 px przy 20 px po bokach: skrzynka franczyzy
       wygladala, jakby wisiala w polprozni nad stopka.
       Wiazanie z --container-pad, a nie wpisanie 20 px, jest tu istotne:
       ta zmienna ma inna wartosc na kazdym progu (28 px od 1080, 20 px od
       560), wiec dol zostaje rowny bokom sam z siebie, bez drugiej reguly. */
    .pricing { padding-bottom: var(--container-pad); }

    /* Tytul podstrony na osi ekranu - patrz prog 1080. */

    /* Tytuly "Zbudujmy cos razem." i "Zarezerwuj darmowe demo" schodza z ekranu
       na telefonie - wskazanie wlasciciela. NIE `display: none`, tylko technika
       .sr-only: naglowek zostaje w drzewie dostepnosci i w konspekcie strony,
       a tylko przestaje zajmowac miejsce. Roznica jest realna - `display: none`
       wycialby h2 takze czytnikowi ekranu, a sekcja i karta formularza
       zostalyby bez wlasnych naglowkow.
       Nad formularzem zostaje nadkreslenie "FORMULARZ", wiec gosc dalej wie,
       co ma przed soba - to ono niesie teraz te informacje wzrokowo. */
    .contact__title,
    .form-card__title {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
    }


    /* Obrys NIEAKTYWNYCH miniatur odrobine jasniejszy i bardziej nasycony -
       wskazanie wlasciciela, tylko na telefonie. Zmiana idzie z bieli krawedzi
       na zielen marki przy niskim kryciu, bo o nasycenie wlasnie chodzilo:
       sama biel mozna tylko rozjasnic, nasycenia jej nie przybedzie.
       Policzone po zlozeniu na tle strony:
         bylo  rgba(biel, 0,14)   -> rgb(39,62,50), jasnosc 56, nasycenie 37%
         jest  rgba(akcent, 0,26) -> rgb(16,83,57), jasnosc 67, nasycenie 81%
       Miniatura AKTYWNA zostaje przy pelnym --c-accent, wiec roznica miedzy
       nia a reszta pozostaje wyrazna - a o to chodzi w tym pasku. */
    .thumb { border-color: rgba(var(--rgb-accent), 0.26); }

    .nav-toggle { display: block; }
    .lang-btn    { display: block; }

    /* Pasek na telefonie: hamburger po LEWEJ, logo NA SRODKU, jezyki po PRAWEJ.
       Boczne kolumny maja rowna szerokosc (--tap-min) i to one trzymaja logo
       na osi ekranu: liczone od krawedzi hamburgera "srodek" wypadalby
       zauwazalnie w prawo.
       `.site-nav` jest tu poza ukladem (szuflada stoi `fixed` wzgledem okna),
       wiec siatka go nie dotyczy. */
    .site-header__inner {
        display: grid;
        grid-template-columns: var(--tap-min) 1fr var(--tap-min);
        gap: var(--sp-3);
    }

    /* Wiersz podany JAWNIE. Sama kolumna nie wystarczy: `.brand` stoi
       w markupie przed `.site-header__side`, wiec automatyczne rozmieszczanie
       widzialo kolumne 1 w pierwszym wierszu jako juz minieta i spychalo
       hamburger pietro nizej. Zmierzone: pasek rosl z 80 do 128 px. */
    .site-header__side { grid-area: 1 / 1; justify-self: start; }
    .brand             { grid-area: 1 / 2; justify-self: center; }
    /* Trzecia kolumna byla od poczatku zarezerwowana wlasnie na to - dolozenie
       jezykow nie rusza ani szerokosci kolumn, ani pozycji logo. */
    .lang-btn          { grid-area: 1 / 3; justify-self: end; }

    /* Pigulka wraca do KOLKA: na tej szerokosci pasek ma miec dwa jednakowe
       kolka po bokach znaku, wiec skrot jezyka i strzalka schodza, a glob
       zostaje sam i urasta do 26 px (patrz komentarz przy .lang-btn__icon).
       Wymiary i obramowanie sa te same, co u hamburgera - stad powrot do
       wartosci ze wspolnej reguly wyzej. */
    .lang-btn {
        width: var(--tap-min);
        height: var(--tap-min);
        padding-inline: 0;
        border: 1px solid var(--c-border-mid);
        color: var(--c-text);
        order: 0;
        margin-right: 0;
    }

    .lang-btn__code,
    .lang-btn__caret { display: none; }

    .lang-btn__icon { width: 1.625rem; height: 1.625rem; }

    /* Przycisk demo znika z paska - w jednym rzedzie z hamburgerem robi sie
       ciasno juz przy 400 px. Klon trafia do szuflady (JS). */
    .site-header__cta { display: none; }

    /* SZUFLADA: panel wjezdza z lewej krawedzi ekranu, a nie rozwija sie
       z belki w dol. Uklad, rozmiar i ruch odwzorowane z aplikacji 3DMenu
       (static/v1/css/main.css, .mobile-drawer) - decyzja wlasciciela, zeby
       witryna ofertowa otwierala menu tak samo, jak sam produkt. Kolory sa
       nasze, z palety sekcji 1.
       Panel jest `fixed`, wiec siatka belki (wyzej w tym progu) go nie
       dotyczy - w ukladzie zostaje po nim puste miejsce, tak samo jak przy
       poprzednim `absolute`. */
    .site-nav {
        position: fixed;
        top: 0;
        left: 0;
        right: auto;
        z-index: var(--z-drawer);
        display: flex;
        flex-direction: column;
        gap: var(--sp-3);
        /* CALA SZEROKOSC EKRANU i paleta z aplikacji - decyzja wlasciciela.
           Panel zakrywa strone w calosci, wiec wyjsciem jest krzyzyk w jego
           pasku (zaslona zostaje pod spodem: nadal wygasza tlo w trakcie wjazdu
           i lapie klikniecie, gdyby panel byl kiedys wezszy). Obrys po prawej
           znika razem z wolnym miejscem - nie ma juz czego oddzielac.
           ROLLBACK do waskiej szuflady: width 20.1875rem, max-width 85vw,
           background-color var(--c-bg-deep), border-right 1px solid var(--c-border). */
        width: 100%;
        max-width: none;
        height: 100%;
        padding: var(--sp-6) var(--sp-5);
        /* Wysokie telefony w poziomie albo powiekszona czcionka moga wypchnac
           liste ponizej ekranu - panel przewija sie wtedy sam, a `contain`
           trzyma to przewijanie w nim i nie oddaje go stronie pod spodem. */
        overflow-y: auto;
        overscroll-behavior: contain;
        background-color: var(--c-drawer-bg);
        border-right: 0;
        box-shadow: var(--shadow-drawer);
        /* Nie display:none - przejscie ma byc animowane, a element musi zostac
           mierzalny. visibility zdejmuje go z kolejnosci fokusu na czas zwiniecia
           (w aplikacji tego nie ma i odsunieta szuflada lapie tam Tab). */
        visibility: hidden;
        transform: translateX(-100%);
        transition: transform var(--t-drawer) var(--ease-drawer),
                    visibility 0s linear var(--t-drawer);
    }

    .site-nav.is-open {
        visibility: visible;
        transform: translateX(0);
        transition-delay: 0s;
    }

    /* Panel lekko przezroczysty, z rozmyciem tego, co pod nim. Jedno i drugie
       WLACZA SIE DOPIERO w stanie otwartym: `backdrop-filter` trzyma wlasna
       warstwe kompozytora takze na elemencie niewidocznym, a szuflada wisi
       w drzewie przez cala wizyte (to ta sama pulapka, co przy zaslonie -
       w aplikacji kilkanascie takich warstw kosztowalo setki MB pamieci karty).
       Zamknieta szuflada zostaje wiec zwyklym, nieprzezroczystym pudelkiem. */
    .site-nav.is-open {
        background-color: var(--c-drawer-glass);
        backdrop-filter: blur(18px);
        -webkit-backdrop-filter: blur(18px);
    }

    /* Zaslona pod szuflada nie ma juz czego pokazywac - panel zakrywa caly
       ekran - a jej wlasne rozmycie kosztowaloby DRUGA warstwe kompozytora
       dokladnie w chwili wjazdu. Zostaje sam element (lapie klikniecie i trzyma
       przejscie), bez tla i bez filtra. */
    .site-nav__overlay,
    .site-nav__overlay.is-open {
        background: transparent;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    /* Kolor kreski stoi przy samej regule .site-nav__head nizej w tym bloku.
       Wczesniej byl tutaj, ale tamta regula ustawia `border-bottom` SKROTEM,
       wiec nadpisywala go kolorem domyslnym - --c-drawer-rule nie dzialal. */

    /* Pozycje menu to SAME linki - bez kafelkow pod spodem (decyzja
       wlasciciela; produkt ma tam kafelki, bo kazda pozycja niesie tam ikone
       i podpis, a u nas to jedno slowo). Z palety aplikacji zostaje kolor
       tekstu; stan "biezaca podstrona" nadal maluje sie nasza zielenia. */
    .site-nav__link { color: var(--c-drawer-text); }

    /* Zaslona kryje CALY ekran razem z belka naglowka, wiec strona pod spodem
       znika pod panelem i przyciemnieniem - dokladnie jak w aplikacji.
       Rozmycie wlacza sie dopiero na czas otwarcia: staly `backdrop-filter`
       trzyma wlasna warstwe kompozytora nawet na elemencie niewidocznym,
       a to w aplikacji potrafilo kosztowac setki MB pamieci karty. */
    .site-nav__overlay,
    .lang-menu__overlay {
        display: block;
        position: fixed;
        inset: 0;
        z-index: var(--z-drawer-scrim);
        background: var(--c-overlay);
        visibility: hidden;
        opacity: 0;
        transition: opacity var(--t-drawer) var(--ease),
                    visibility 0s linear var(--t-drawer);
    }

    /* Zaslona pod jezykami jest ta sama, co w aplikacji - patrz --c-scrim-app. */
    .lang-menu__overlay { background: var(--c-scrim-app); }

    .site-nav__overlay.is-open,
    .lang-menu__overlay.is-open {
        visibility: visible;
        opacity: 1;
        backdrop-filter: blur(4px);
        -webkit-backdrop-filter: blur(4px);
        transition-delay: 0s;
    }

    /* SZUFLADA ZNIKA BEZ RUCHU po kliknieciu odnosnika prowadzacego pod inny
       adres - zgloszenie wlasciciela: wyjazd panelu zaczynal sie dokladnie
       w chwili, gdy przegladarka zaczynala ladowac nowa strone, wiec nie mial
       kiedy dobiec konca i widac bylo zacinajaca sie klatke.
       Klase zaklada i zdejmuje skrypt (Navigation.close / .open w
       components.js) - tutaj jest tylko skutek: zaden z trzech przejsc
       (przesuniecie panelu, krycie zaslony, widocznosc) nie ma czasu trwania.
       Kotwica na tej samej stronie ("#faq") klasy NIE dostaje: tam nic sie nie
       przeladowuje, wiec ruch jest na miejscu i zostaje. */
    .site-nav.is-instant,
    .site-nav__overlay.is-instant { transition: none; }

    /* ...i TO SAMO dla samego hamburgera. Przycisk przechodzi w krzyzyk
       obrotem dwoch kresek, wiec przy zamknieciu wraca ta sama droga - a jesli
       strona akurat sie przeladowuje, gosc widzi kreski zatrzymane w polowie
       obrotu, pod przypadkowym katem (zgloszenie wlasciciela: "ikonka zmienia
       sie w jakies inne znaki na moich oczach"). Panel dostal to zabezpieczenie
       wczesniej, przycisk zostal pominiety - to jest ta sama poprawka. */
    .nav-toggle.is-instant .nav-toggle__bars,
    .nav-toggle.is-instant .nav-toggle__bars::before,
    .nav-toggle.is-instant .nav-toggle__bars::after { transition: none; }

    /* LISTA JEZYKOW: zjezdza spod paska, wysrodkowana na osi EKRANU (a nie
       pod samym globem) - tak stoi w wersji mobilnej aplikacji i tak prosil
       wlasciciel. `left: 50%` plus ujemna polowa szerokosci w transformie:
       ten sam transform niesie potem zjazd w pionie, wiec obie rzeczy musza
       byc w jednej wlasciwosci.
       Wjazd jest dluzszy od zejscia i po krzywej z przestrzeleniem - panel
       wpada z odbiciem, dokladnie jak w aplikacji. Wyjscie krotkie i gladkie:
       zamkniecie ma byc niezauwazalne, a nie drugim pokazem. */
    /* Kontener 1:1 z aplikacja (.mobile-lang-overlay .lang-dropdown): 230 px
       szerokosci, 8 px dopelnienia, 4 px miedzy pozycjami, promien 20 px,
       obrys #094A2B i cien 0 10px 40px.
       Kluczowe jest to, czego tu NIE MA: koloru tla. Panel bierze cala swoja
       barwe z rozmycia tego, co pod nim - `blur(16px) brightness(1.25)`, przy
       czym jasnosc jest DOPISANA do tego samego filtra, a nie osobna
       wlasciwoscia: to ten sam przebieg kompozycji, wiec nie kosztuje drugiej
       warstwy. Dlatego tez rozmycie wlacza sie dopiero w stanie otwartym. */
    .lang-menu {
        display: flex;
        flex-direction: column;
        gap: 0.25rem;
        position: fixed;
        top: calc(var(--header-h) + var(--sp-3));
        left: 50%;
        /* Zerowane po ukladzie szerokiego ekranu, gdzie lista wisi przy prawej
           krawedzi paska. Tutaj stoi na SRODKU ekranu, wiec zostawione prawe
           przyleganie rozciagaloby ja miedzy oba boki, a gorny margines zsuwal
           o kolejne 14 px pod juz policzone przyleganie gorne. */
        right: auto;
        margin-top: 0;
        z-index: var(--z-drawer);
        min-width: var(--lang-menu-w);
        max-width: calc(100vw - 2 * var(--container-pad));
        padding: 0.5rem;
        border: 1px solid var(--c-lang-border);
        border-radius: 1.25rem;
        box-shadow: var(--shadow-lang);
        visibility: hidden;
        opacity: 0;
        transform: translateX(-50%) translateY(calc(-1 * var(--lang-menu-shift)));
        transition: transform var(--t-lang-out) ease-in,
                    opacity var(--t-lang-out) ease-in,
                    visibility 0s linear var(--t-lang-out);
    }

    .lang-menu.is-open {
        visibility: visible;
        opacity: 1;
        transform: translateX(-50%) translateY(0);
        backdrop-filter: blur(16px) brightness(1.25);
        -webkit-backdrop-filter: blur(16px) brightness(1.25);
        transition: transform var(--t-lang-in) var(--ease-lang),
                    opacity var(--t-fast) ease-out,
                    visibility 0s linear 0s;
    }

    /* Pasek szuflady: logo po lewej, krzyzyk po prawej - jak naglowek
       szuflady w aplikacji. */
    .site-nav__head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--sp-3);
        padding-bottom: var(--sp-4);
        /* --c-drawer-rule, nie --c-border: kolor przeniesiony 1:1 z naglowka
           szuflady w aplikacji 3DMenu. */
        border-bottom: 1px solid var(--c-drawer-rule);
    }

    .site-nav__list {
        flex-direction: column;
        align-items: stretch;
        gap: var(--sp-1);
    }

    .site-nav__link {
        min-height: 3rem;
        display: flex;
        align-items: center;
        padding-inline: var(--sp-4);
        border-radius: var(--r-sm);
        font-size: var(--fs-lead);
    }

    .site-nav__cta {
        display: flex;
        margin-top: var(--sp-4);
    }

    /* Wjazd kafelkow "proof" krotszy niz na desktopie - polecenie wlasciciela.
       Na telefonie kafelki lecą jeden pod drugim i ten sam, krotszy odcinek,
       wiec 0,7 s czytalo sie jak ociaganie. Odstep miedzy nimi tez maleje,
       zeby cala trojka wjezdzala jako jeden gest, a nie trzy osobne. */
    :root {
        --t-reveal: 0.55s;
        --t-reveal-step: 0.1s;
    }

    /* Droga skrocona z 60% do 18% szerokosci kafelka: przy trzech kafelkach
       jeden pod drugim dluzszy lot nie ma czego pokazac, a im krotszy, tym
       spokojniej sie czyta.
       Zmienna MUSI stac na `.proof__tiles`, a nie na `:root` - prog 1080 px
       ustawia ja wlasnie na tym elemencie, a wartosc z elementu zawsze wygrywa
       z odziedziczona. */
    .proof__tiles { --reveal-slot: 18%; }

    /* Stan poczatkowy dostaje jeszcze przezroczystosc - kafelek nie tyle
       przyjezdza, co sie pojawia. */
    .proof__tiles.is-reveal .proof__tile { opacity: 0; }

    /* Same klatki inne (bez przestrzelenia), reszta parametrow z reguly
       bazowej - podmieniamy wylacznie nazwe animacji. */
    .proof__tiles.is-reveal.is-in .proof__tile { animation-name: revealSoft; }

    /* Lista cech traci wlasny zapas u gory: odstep nad nia ma byc taki sam, co
       nad przyciskami i nad zielonym zdaniem, czyli --hero-gap z siatki hero
       (polecenie wlasciciela). Na szerokim ekranie zapas zostaje - tam lista
       stoi w kolumnie tekstu, a nie w rytmie pionowym calego hero. */
    .hero__features { margin-top: 0; }

    /* Robot na naglowku podstrony zabiera miejsce naglowkowi H1 - ponizej
       tego progu schodzi ze sceny. */
    /* .robot--head NIE jest juz chowana: wlasciciel chce maskotke takze na
       Ofercie, pod zdaniem wprowadzajacym. Obie ida ta sama regula nizej. */

    /* ======================================================================
       PROBA: MASKOTKA WRACA NA TELEFON, NAD PIERWSZE KOLO
       ======================================================================
       Zamowienie wlasciciela do obejrzenia. Dwie rzeczy trzeba tu zmienic
       wzgledem szerokiego ekranu:

       1. Maskotka jest tam pozycjonowana ABSOLUTNIE i zwisa poza pasmo
          naglowka (bottom: -13.3rem). Na telefonie nie ma z czego zwisac -
          wraca wiec do normalnego przeplywu i sama zajmuje swoje miejsce.
       2. W markupie stoi PRZED nadkresleniem, wiec w przeplywie wyladowalaby
          na samej gorze pasma, nad napisem WDROZENIE. Wlasciciel chce ja nad
          pierwszym kolem, czyli na koncu naglowka - stad kolumna flex
          i `order`. Markup zostaje nietkniety.

       KOSZT, ten sam co przy rozmyciu kol: maskotka ma dwie wieczne animacje
       (robotFloat i robotJitter) i `will-change: transform` na sobie oraz na
       obrazku, czyli dwie stale warstwy kompozytora z tekstura 974x889.
       Na telefonie dochodzi to do czterech warstw rozmycia kol na tej samej
       podstronie. Jesli cos ma tu zamulac, to wlasnie ta para.

       ROLLBACK: skasowac ten blok i dopisac `.robot--steps` z powrotem do
       reguly `display: none` wyzej. */
    .page-head__inner {
        display: flex;
        flex-direction: column;
    }

    .robot--steps,
    .robot--head {
        display: block;
        position: static;
        order: 1;
        /* Szerokosc W JEDNOSTKACH OKNA, zeby maskotka skalowala sie razem
           z ekranem - zasada wlasciciela. Tu obowiazuje wartosc TABLETOWA;
           telefon nadpisuje ja nizej wlasna, wieksza.
           Powod rozdzielenia jest zmierzony: pojedyncze `min(58vw, 14rem)`
           zamarzalo na suficie 226 px juz od okna ok. 386 px i trzymalo te
           sama liczbe az do 860 px - maskotka schodzila przez to z 47%
           szerokosci okna do 26%, czyli przestawala byc proporcjonalna
           dokladnie w tym zakresie, ktory wlasciciel zglosil. 24vw jest
           30vw to DOKLADNIE ta sama proporcja, co na desktopie (prog 1080),
           wiec przejscie przez 860 px jest bezszwowe: 258 px tuz pod progiem
           i 261 px tuz nad nim. Od 561 px w gore maskotka trzyma wiec stale
           30% szerokosci okna, czyli dokladnie to, o co prosil wlasciciel.
           Sufit 14rem celowo ZDJETY - to on wczesniej zamrazal szerokosc. */
        width: 30vw;
        /* Ujemny margines DOLNY podsuwa pierwsze kolo o 20 px w gore -
           wskazanie wlasciciela. Zapisany tutaj, a nie jako mniejsze
           dopelnienie naglowka, bo to dopelnienie jest wspolne z Oferta,
           gdzie odstep do przelacznika zostal juz zatwierdzony. Ujemny
           margines pod maskotka rusza wylacznie to, co stoi pod NIA.
           Obrazek ma u dolu troche pustego pola (smuga konczy sie wyzej niz
           ramka pliku), wiec optycznie zblizenie jest mniejsze niz 20 px. */
        /* Margines gorny o 5 px mniejszy od --hero-gap - wskazanie
           wlasciciela ("robocika 5 pikseli do gory"). Zapisany jako odjecie
           od tamtej zmiennej, a nie jako 21 px, zeby przy zmianie rytmu
           strony korekta pojechala razem z nim. */
        margin: calc(var(--hero-gap) - 0.3125rem) auto 0;
    }

    .pricing__grid { grid-template-columns: minmax(0, 1fr); }
    .pricing__tabs { width: 100%; }

    /* Odstep pod przelacznikiem rowny temu NAD nim - wskazanie wlasciciela.
       Nad przelacznikiem stoi --sep-gap (odstep od separatora), a pod nim
       bylo 3.25rem, czyli 52 px. Przy karcie na cala szerokosc ta nierownosc
       rzucala sie w oczy. */
    .pricing__switch { margin-bottom: var(--sep-gap); }

    /* Odstep nad karta polecana liczony OD PLAKIETKI, nie od obrysu karty -
       wskazanie wlasciciela. Plakietka "NAJPOPULARNIEJSZY" jest pozycjonowana
       absolutnie z `top: -1.125rem`, czyli wystaje 18 px ponad obrys. Siatka
       liczy swoj odstep miedzy OBRYSAMI, wiec nad sama plakietka zostawaly
       realnie 4 px z 22 px odstepu - wygladalo to, jakby dotykala karty wyzej.
       Przesuniecie karty w dol o dokladnie tyle, o ile plakietka wystaje,
       przywraca pelny odstep NAD PLAKIETKA.
       Tylko w tym progu, bo tylko tu karty stoja jedna pod druga. Wyzej sa
       dwie albo trzy w rzedzie i karta polecana nie ma nad soba zadnej. */
    .plan--popular { margin-top: 1.125rem; }

    /* Kroki schodza do JEDNEJ kolumny juz tutaj, nie dopiero przy 560 px.
       Powod jest mierzalny: kolo ma byc kolem, a przy dwoch kolumnach ponizej
       tego progu kolumna ma okolo 260 px i tekst nie miesci sie w kole tej
       srednicy - ksztalt robil sie jajem (zmierzone przy 561 px: 241 x 290).
       Dwa kola obok siebie potrzebuja realnie okolo 660 px szerokosci.
       Efekt sklejania idzie razem z ukladem, zeby nie bylo progu, na ktorym
       kola stoja w kolumnie, ale sie nie sklejaja. */
    .steps__grid { grid-template-columns: minmax(0, 1fr); }

    /* Jedna kolumna: kolo bierze niemal cala szerokosc ekranu. */
    /* Kolo odrobine szersze na malym telefonie - wskazanie wlasciciela.
       84 -> 87vw, czyli 278 px zamiast 269 przy szerokosci 320 px. Wiecej sie
       nie da: kontener ma tam 280 px, a kolo nie moze z niego wystac, bo
       strona dostalaby przewijanie w poziomie. Te 9 px to jednoczesnie zapas
       dla tresci, ktora przy poprzedniej srednicy wypelniala kolo co do
       piksela. */
    :root { --step-d: min(87vw, 20.5rem); }

    /* ======================================================================
       KOLA KROKOW SKLEJAJA SIE PRZY PRZEWIJANIU
       ======================================================================
       Ten sam patent, co kafelki "AR W PRAKTYCE" na stronie glownej -
       wskazanie wlasciciela. Czysty `position: sticky`, zero skryptu: nic nie
       kosztuje przy przewijaniu, zwijanie przy powrocie w gore wychodzi za
       darmo, a starsza przegladarka dostaje zwykla liste zamiast zepsutej
       animacji. Ten sam `top`, co tam, wiec oba efekty w witrynie zatrzymuja
       sie na tej samej wysokosci.

       Regula stoi w progu JEDNOKOLUMNOWYM celowo. Wyzej kroki stoja po dwa
       i po cztery w rzedzie - sklejanie nie mialoby tam czego pokazac, bo
       kolo nie ma nad soba drugiego.

       TLO KOL ZOSTAJE PRZEZROCZYSTE - decyzja wlasciciela, i rozni sie tym
       od kafelkow na stronie glownej. Tam kafelek dostal nieprzezroczysta
       podstawe, zeby przy nachodzeniu zakrywal ten pod spodem. Tutaj probowalem
       tego samego i zostalo to odrzucone: kolo ma pokazywac tlo strony, a przy
       nachodzeniu widac obrysy obu naraz. To jest swiadomie inny efekt -
       przenikajace sie okregi zamiast podmiany jednego na drugi - a nie
       niedokonczona wersja tamtego. Nie dokladac tu tla "dla poprawy".

       ROLLBACK: skasowac te regule. */
    /* Ujemny margines DOLNY tylko pod maskotka Wdrozenia - podsuwa pierwsze
       kolo o 20 px w gore (wskazanie wlasciciela). Na Ofercie go NIE MA:
       tam pod maskotka stoi przelacznik cennika i ten sam ujemny margines
       zostawial pod nia 8 px przy 26 px nad - zmierzone. */
    .robot--steps { margin-bottom: -1.25rem; }

    .step {
        position: sticky;
        top: calc(var(--header-h) + var(--sp-8));
    }

    /* Kolo zakryte przez nastepne gasnie do zera - rozwiazanie wskazane przez
       wlasciciela i jedyne, ktore godzi przezroczyste tlo ze sklejaniem.
       Klase dokłada komponent StepStack: CSS nie ma selektora na "element
       zasloniety przez rodzenstwo", a przy przezroczystym tle nachodzenie
       samo z siebie niczego nie zakrywa i teksty obu krokow drukowalyby sie
       jeden na drugim.
       Regula stoi w progu jednokolumnowym, wiec klasa jest nieszkodliwa tam,
       gdzie kola stoja obok siebie i nigdy sie nie nachodza - skrypt moze
       dzialac na kazdej szerokosci bez sprawdzania progu. */
    /* Krycie kola idzie ZA PRZEWIJANIEM: wartosc liczy komponent StepStack
       i podaje ja przez --step-krycie. Nie ma tu `transition` i to jest
       swiadome - przejscie dokladaloby wlasne opoznienie do ruchu, ktory jest
       juz sterowany palcem, i kolo gaslo by z obsuwa wzgledem przewijania.
       Wartosc domyslna 1: bez skryptu (albo zanim ruszy) kola sa widoczne. */
    .step { opacity: var(--step-krycie, 1); }

    .form__grid {
        grid-template-columns: minmax(0, 1fr);
        /* Odstep MIEDZY polami rowny odstepowi etykieta-pole (9 px) -
           wskazanie wlasciciela. Na telefonie liczy sie, ile formularza widac
           na jednym ekranie, a rytm 22/9 dawal wrazenie, ze etykieta nalezy
           do pola NAD nia, nie pod. Teraz odstep jest jednakowy z obu stron
           etykiety, wiec wiadomo, co do czego nalezy. */
        row-gap: 0.5625rem;
    }

    /* Odstep od zdania nad formularzem do pierwszej etykiety mniejszy o 7 px
       (wskazanie wlasciciela). Zapisany jako odjecie, zeby przy zmianie
       tamtej wartosci korekta pojechala razem z nia. */
    .form-card__lead { margin-bottom: calc(2.125rem - 0.4375rem); }

    /* JEDNA kolumna juz od tego progu, nie dopiero od 560 px - wskazanie
       wlasciciela. Uklad 2x2 wygladal na tablecie na rozstrzelony: znak marki
       z czterolinijkowym zdaniem stal obok listy "Start, Oferta, Wdrozenie,
       FAQ", ktora zajmuje jedna linijke, wiec prawa kolumna byla w wiekszosci
       pusta, a pod spodem powtarzalo sie to samo. Wszystko na osi ekranu czyta
       sie tu tak samo dobrze jak na telefonie, a rytm jest jeden zamiast
       dwoch. Reszta wysrodkowania jest juz w tym progu (znak marki, listy
       "po przecinku"), wiec wystarczy zdjac drugi slupek. */
    .site-footer__grid { grid-template-columns: minmax(0, 1fr); }

    /* Swiatlo NAD znakiem marki rowne temu pod ostatnim odnosnikiem (--sp-7):
       bylo --sp-10, czyli dwa razy tyle, i stopka zaczynala sie dziura. */
    .site-footer__grid { padding-block: var(--sp-7); }

    /* --- STOPKA NA TELEFONIE (pierwsze podejscie, do obejrzenia) -----------
       Wskazowki wlasciciela: znak marki i zdanie pod nim na osi ekranu, a listy
       odnosnikow "po przecinku" zamiast jeden pod drugim - kolumn na tej
       szerokosci nie ma jak rozstawic, a pozycji jest duzo. */
    .site-footer__brand {
        max-width: none;
        text-align: center;
    }

    /* Krycie 0,55 wnosi regula bazowa (sekcja 18); tutaj dochodzi to, co jest
       wlasciwoscia telefonu: wysrodkowanie i ODBARWIENIE. Na telefonie pasek
       z logo jest przyklejony do gory ekranu, wiec oba znaki bywaja w kadrze
       naraz i kolorowy duplikat na dole wygladal jak pomylka - odbarwiony czyta
       sie jako podpis, a nie jako drugi znak. Na desktopie tego problemu nie ma
       i wlasciciel chce tam znak w kolorach. */
    .site-footer__logo {
        margin-inline: auto;
        filter: grayscale(1);
        /* WYSOKOSC ROWNA znakowi w belce - wskazanie wlasciciela: oba znaki
           maja byc jednej wielkosci. Znak w belce zszedl na malym telefonie
           do 31 px (--logo-h w progu 560), a stopka zostala przy swoich 36 px
           i przez to byla wieksza od oryginalu. Uzyty TEN SAM token, a nie
           przepisana liczba: przy kolejnej zmianie wielkosci znaku w belce
           stopka pojedzie za nim sama. */
        height: var(--logo-h);
    }

    .site-footer__title { text-align: center; }

    /* Wiersze list maja miec TAKI SAM rytm, co zdanie pod znakiem marki
       (line-height 1,6) - polecenie wlasciciela. Odstep miedzy wierszami
       schodzi wiec do zera i o wysokosci wiersza decyduje juz sama interlinia
       odnosnika; w poziomie zostaje swiatlo po przecinku. */
    .site-footer__list {
        flex-direction: row;
        flex-wrap: wrap;
        justify-content: center;
        column-gap: var(--sp-2);
        row-gap: 0;
    }

    /* STOPKA PRZEJMUJE STOPIEN TRESCI - zasada wlasciciela: "nie mozemy miec
       innych fontow w roznych sekcjach; przewaznie w stopce teksty przejmuja
       wielkosc fontow z tresci". Zdanie pod znakiem marki, pozycje list
       i pasek na dole schodza wiec z --fs-xs (14,5 px) na --fs-opis, czyli
       na ten sam stopien, co odpowiedzi w FAQ i zdania wprowadzajace.
       NADKRESLENIA kolumn (.site-footer__title) zostaja przy --fs-micro:
       to etykiety, ta sama rodzina, co `.eyebrow` w sekcjach - nie tresc. */
    .site-footer__tag,
    .site-footer__link { font-size: var(--fs-opis); }

    /* PASEK ZAMYKAJACY (prawa autorskie plus jedno zdanie) to drobny druk,
       a nie tresc - 12 px, wartosc wlasciciela. Stal chwile razem ze zdaniem
       i odnosnikami na --fs-opis, ale tamta zasada dotyczy tekstu, ktory sie
       CZYTA; ten pasek sie odnotowuje. */
    .site-footer__bar { font-size: 0.75rem; }

    /* Zdanie pod znakiem marki: na szerokim ekranie stoja CZTERY zdania,
       kazde we wlasnym wierszu, i tak ma zostac - tam wieksza tresc wyglada
       lepiej (wskazanie wlasciciela). Na telefonie zostaje samo pierwsze:
       lamania wypadaly w przypadkowych miejscach, bo zdania i tak sie
       zawijaly, i wychodzily z tego wiersze dlugi-krotki-dlugi-krotki.

       Chowamy zdania 2-4 ARKUSZEM, a nie usuwamy ich z markupu: tresc ma
       stac w HTML w calosci, bo czyta ja robot i zapowiedz linku, a to,
       ile jej widac, jest sprawa ukladu. `<br>` sa w srodku ukrywanego
       elementu, wiec znikaja razem z nim - zadne lamanie nie zostaje sierota. */
    .site-footer__tag-more { display: none; }

    .site-footer__link { line-height: 1.6; }

    /* Separator stawia ARKUSZ, a nie markup: w HTML pozycje maja zostac lista,
       zeby czytnik ekranu dalej slyszal "lista, cztery pozycje", a nie jedno
       zdanie z separatorami. */
    .site-footer__list li:not(:last-child)::after {
        content: ',';
        color: var(--c-text-45);
    }

    /* PIERWSZA kolumna ("Serwis") dostaje pionowa kreske zamiast przecinka -
       wskazanie wlasciciela; "Obsluga klienta" i "Dokumenty" zostaja przy
       przecinku. Rozroznia je modyfikator w markupie, a nie pozycja w siatce:
       selektor pozycyjny (`nav:first-of-type`) dzialalby dzis, ale po
       przestawieniu kolumn zmienilby znaczenie po cichu.
       Kreska ma byc DELIKATNA, wiec dwie rzeczy naraz: --c-text-35 zamiast
       --c-text-45 (0,35 zamiast 0,52 krycia) i wlasne swiatlo z lewej rowne
       odstepowi listy, wiec znak stoi dokladnie posrodku miedzy pozycjami,
       a nie doklejony do poprzedniej - przecinek mial sie kleic, kreska ma
       oddzielac. */
    .site-footer__list--bars li:not(:last-child)::after {
        content: '|';
        margin-left: var(--sp-2);
        color: var(--c-text-35);
    }

    /* Pasek zamykajacy stopke na osi ekranu - tak samo, jak wszystko powyzej
       niego (znak marki, zdanie, listy odnosnikow). */
    .site-footer__bar {
        flex-direction: column;
        align-items: center;
        /* Zero odstepu miedzy wierszami i interlinia 1,6 - ten sam rytm, co
           w zdaniu pod znakiem marki i w listach odnosnikow. Nie ma powodu,
           zeby ostatnie dwa wiersze stopki oddychaly inaczej niz reszta. */
        gap: 0;
        line-height: 1.6;
        text-align: center;
        /* Kreska nad paskiem jasniejsza: 0,14 zamiast 0,08 krycia. Ten sam
           token i ten sam powod, co przy kreskach FAQ w tym samym progu -
           na telefonie slaba linia gubi sie w tle, a tu dzieli dwie rzeczy,
           ktore po zlozeniu do jednej kolumny stoja tuz nad soba. Na szerokim
           ekranie zostaje --c-border-faint, bo tam pasek ma wlasne swiatlo
           po bokach i mocniejsza linia zaczyna ciazyc. */
        border-top-color: var(--c-border-thin);

        /* Pasek zamykajacy ma miec DOKLADNIE wysokosc belki naglowka, a tekst
           stac w jego srodku w obu osiach - wskazanie wlasciciela.

           Wysokosc liczona ZE SKLADNIKOW belki, nie wpisana jako 80 px ani
           wziecia z --header-h. Powod: --header-h mowi 4.5rem (72 px), a belka
           ma realnie 80 px, bo jej wnetrze to dopelnienie 2 x --header-pad-y
           (36 px) plus okragle przyciski o rozmiarze --tap-min (44 px). Token
           jest o te 8 px nieaktualny; nie ruszam go tutaj, bo wisi na nim
           pozycja kadru tla, przyklejanie kafelkow i lista jezykow - ale ta
           sama arytmetyka uzyta tu wprost trzyma stopke rowna belce takze
           wtedy, gdy zmieni sie ktorykolwiek ze skladnikow.

           `justify-content` przy kierunku `column` rzadzi osia PIONOWA, wiec
           to on wysrodkowuje w pionie; `align-items` robi to w poziomie.
           Dopelnienie musi zejsc do zera, inaczej doliczaloby sie do
           zadanej wysokosci. */
        /* Plus 1 px za kreske. Arkusz stoi na `box-sizing: border-box`, wiec
           `min-height` obejmuje takze obrys - a pasek stopki ma u gory kreske
           1 px, ktorej belka naglowka nie ma. Bez tej korekty wnetrza obu
           pasm roznily sie o te wlasnie 1 px (79 wobec 80) i to bylo widac.
           Doliczone jako `+ 1px` przy kresce, a nie wpisane w wynik, zeby bylo
           jasne, skad sie bierze - gdyby kreska kiedys znikla albo zmienila
           grubosc, widac tu od razu, co poprawic. */
        min-height: calc(2 * var(--header-pad-y) + var(--tap-min) + 1px);
        justify-content: center;
        padding-block: 0;
    }

    .site-footer__legal { text-align: center; }
}

/* --- do 560 px: zageszczenie ukladu na telefonie -------------------------- */
@media (max-width: 560px) {
    :root {
        --container-pad: 1.25rem;
        /* Logo o 15% mniejsze - zgloszenie wlasciciela z iPhone'a mini: znak
           stal za blisko hamburgera i globu. Zmierzone przy 320 px (tyle ma
           to urzadzenie z powiekszeniem ekranu): swiatlo z obu stron roslo
           z 20 na 30 px. Na szerszych telefonach miejsca bylo dosc juz
           wczesniej, ale znak schodzi razem z cala reszta tego progu. */
        --logo-h: 1.9375rem;   /* 31 px zamiast 36 */
        --stage-h: min(92vw, 24rem);
        --stage-dish: min(90vw, 23rem);
        /* --stage-ring celowo BEZ wlasnej wartosci: od progu 860 px okrag jest
           elementem tla (140vw) i ma wychodzic poza ekran, wiec zmniejszanie go
           na telefonie przeczyloby calej zmianie. Wczesniej stalo tu
           min(94vw, 25rem). */
        --stage-glow: min(112vw, 30rem);
        /* Wieksze kafelki - polecenie wlasciciela: na telefonie ma byc widac,
           w co sie klika, i ma sie w to latwo trafic palcem. Miejsce na nie
           bierze sie z dwoch rzeczy nizej: wezszego dopelnienia panelu
           i strzalek odsunietych na jego krawedzie. */
        --thumb-size: 3.5rem;
        /* --panel-drop celowo BEZ wlasnej wartosci: od progu 860 px panel
           podjezdza pod scene i rezerwa jest wszedzie ta sama. Wczesniej stalo
           tu 11,5rem - to nie przeoczenie, tylko skutek podniesienia panelu. */
        /* NAGLOWEK SEKCJI: 24 px na malym telefonie, do 26 px na wiekszym.
           Plynnie, a nie skokiem na progu - wskazanie wlasciciela: "jak juz
           jest wiekszy telefon z wiekszym ekranem, to mozemy troche wiekszy
           font zostawic". 6,4vw daje dokladnie 24 px przy 375 px (iPhone mini),
           ponizej trzyma dolna granica, a od 407 px wraca dotychczasowe 26 px.
           Token, a nie regula na selektorach: --fs-h2 nosza wylacznie naglowki
           sekcji (.faq__title, .proof__title przez --fs-h2-proof), wiec zmiana
           obejmuje dokladnie ten rodzaj tekstu i nic wiecej. */
        /* NAGLOWEK PODSTRONY - 2rem, wartosc wlasciciela dla telefonu, ta sama
           co naglowek hero. Bylo 36 px (dolna granica --fs-h1). Token, a nie
           regula na selektorach: --fs-h1 nosza dokladnie dwa naglowki tej
           rangi - tytul podstrony i tytul strony bledu - wiec zmiana obejmuje
           ten rodzaj tekstu i nic wiecej. */
        /* Kostka ladowania sceny 3D o 20% mniejsza - wskazanie wlasciciela.
           3,3rem to 52,8 px, czyli na telefonie prawie jedna szosta szerokosci
           ekranu; scena jest tu duzo mniejsza niz na desktopie, wiec kostka
           robila sie w niej nieproporcjonalnie duza. 2,64rem = 42,2 px.
           Zmienna, nie transform: `scale` skalowalby takze grubosc krawedzi
           i cien, a chodzi o sam rozmiar bryly - jej sciany licza polozenie
           z tej samej zmiennej (translateZ o polowe boku).
           DRUGIE zejscie o 20% (2,64 -> 2,112rem, czyli 42,2 -> 33,8 px) -
           wlasciciel obejrzal pierwsze i poprosil o tyle samo jeszcze raz.
           Lacznie kostka ma na telefonie 64% swojego rozmiaru z desktopu. */
        --cube-size: 2.112rem;

        --fs-h1: 2rem;

        --fs-h2: clamp(1.5rem, 6.4vw, 1.625rem);

        --fs-h3: 1.625rem;
    }

    /* Przyciski na pelna szerokosc: dwa obok siebie przy 360 px lamalyby
       etykiety w srodku slowa. */
    /* Na telefonie maskotka ma byc wyrazna: 57vw daje przy 390 px te same
       ok. 222 px, ktore wlasciciel zatwierdzil na Wdrozeniu. Sufit ten sam.
       MUSI stac w progu 560, a nie 860: w tamtym bloku ta regula stalaby PO
       wartosci tabletowej i wygrywalaby kolejnoscia na calej szerokosci
       do 860 px - czyli tablet nigdy nie dostalby swojej mniejszej maskotki
       (zmierzone: 226 px zamiast 168 px przy oknie 700 px). */
    .robot--steps,
    .robot--head { width: min(57vw, 14rem); }

    .hero__actions { flex-direction: column; align-items: stretch; }
    .btn--fixed { width: 100%; }

    /* Na telefonie wraca sztywny uklad trzywierszowy - taki, jaki wlasciciel
       zatwierdzil na zrzucie. Wyzej naglowek lamie sie sam (patrz prog 1080). */
    /* Telefon wraca do wartosci zatwierdzonych wczesniej przez wlasciciela.
       Wyzej panel podjezdza duzo mocniej, bo tam plotno sceny jest o polowe
       wyzsze przy tej samej bryle dania. */
    :root {
        --panel-lift: 6.5rem;
        --stage-lift: calc((var(--panel-lift) - var(--sp-4) + var(--hero-gap)) / 2
                           + var(--stage-nudge));
    }

    /* Naglowek hero na telefonie: 32 px i o 12 px wyzej - wartosci dobrane
       przez wlasciciela na iPhone 13 mini.

       Stopien podany WPROST, a nie przez `calc(--fs-h1-hero * 1.1)` z progu
       1080: tamten wzor daje tutaj 38,5 px, bo `clamp` stoi juz na swojej
       dolnej granicy i nie schodzi nizej niezaleznie od szerokosci okna.
       Mnoznik 1,1 zostaje tam, gdzie mial sens - w pasmie, w ktorym stopien
       jeszcze plynie. */
    .hero__title { font-size: 2rem; }

    /* Podniesienie realizuje DOPELNIENIE siatki, a nie ujemny margines na
       naglowku. Wlasciciel dobral to w inspektorze jako `margin-top: -12px`
       i skutek jest ten sam - siatka ma jeden wiersz na element, wiec cala
       zawartosc hero jedzie w gore o te 12 px - ale ujemny margines zostawia
       w arkuszu wartosc, ktora walczy z inna wartoscia zamiast ja zastapic.
       32 px z --sp-7 minus 12 px daje 20 px. */
    .hero__grid { padding-block: calc(var(--sp-7) - 0.75rem) 0; }

    .hero__title-line { display: block; }

    .hero__title-line--nowrap { white-space: normal; }
    /* Cechy JEDNA POD DRUGA - wskazanie wlasciciela. Zawijanie w wiersze dawalo
       na waskim ekranie uklad przypadkowy: dwie pozycje w jednym wierszu, jedna
       w nastepnym, zaleznie od dlugosci napisow - i to wygladalo na usterke,
       a nie na uklad. Kolumna jest przewidywalna niezaleznie od tresci.
       `gap` zostaje w obu osiach, bo `flex-wrap` dalej obowiazuje; w praktyce
       liczy sie juz tylko odstep pionowy. */
    /* NAGLOWEK SEKCJI - jedna regula dla wszystkich, ktore maja te range.
       Stopien, interlinia i odstep pod spodem w jednym miejscu, bo dokladnie
       o to chodzilo we wskazaniu wlasciciela: "tego typu naglowki nie sa
       dociagniete do tego, co ustalilismy na stronie glownej".
       Zmierzone przed zmiana przy 375 px: FAQ 24 px/1,15 i odstep 18,
       "Skala 1:1" 24 px/1,1 i odstep 22, a "Sieci i franczyzy" 26 px przy
       interlinii 1,5 (odziedziczonej po akapicie) i odstepie 8 - stad tamten
       naglowek wygladal na rozstrzelony i wiekszy od reszty. */
    .faq__title,
    .proof__title,
    .enterprise__title {
        font-size: var(--fs-h2);
        line-height: 1.15;
        margin-bottom: var(--sp-4);
    }

    /* ZDANIE WPROWADZAJACE pod naglowkiem: 15 px na malym telefonie, do 17 px
       na wiekszym - ta sama zasada, co przy naglowku wyzej. 4vw daje 15 px
       przy 375 px, a od 425 px wraca dotychczasowe 17 px.
       Wartosc stoi na SELEKTORACH, a nie na tokenie --fs-lead, i to jest
       swiadome: tamten token nosza takze `.btn--ghost` i `.site-nav__link`,
       czyli przyciski i pozycje menu. Zejscie z 17 na 15 px zmniejszyloby
       etykiety kontrolek, o co wlasciciel nie prosil - a chodzilo o teksty. */
    .page-head__lead,
    .faq__lead,
    .proof__lead,
    .notfound__lead { font-size: var(--fs-opis); }

    /* Akapit hero tym samym stopniem, co lista cech pod nim - wskazanie
       wlasciciela. Uzyty TEN SAM token (--fs-sm), a nie przepisana liczba:
       dzieki temu oba teksty zostana rowne takze wtedy, gdy kiedys zmieni sie
       sama wartosc. Na telefonie 19 px akapitu obok 15,5 px listy czytalo sie
       jak dwa rozne poziomy tresci, a to jest jedna wypowiedz.
       Powyzej progu akapit wraca do --fs-lead-hero: tam ma obok siebie scene
       i wiecej miejsca, wiec moze byc wyrazniejszy. */
    .hero__lead { font-size: var(--fs-sm); }

    /* Lista cech hero ZNIKA na telefonie - wskazanie wlasciciela: te same
       informacje stoja nizej na stronie, a na malym ekranie kazde powtorzenie
       to wiecej przewijania. Zostaje w markupie (dla wyszukiwarki i szerokich
       ekranow), znika tylko z ukladu. `display: none`, a nie technika .sr-only
       jak przy naglowkach: czytnik ekranu dostanie te same tresci nizej.
       Wczesniej stal tu uklad listy w kolumnie - na telefonie juz zbedny. */
    .hero__features { display: none; }

    /* Dopelnienie po bokach ROWNE dolnemu (polecenie wlasciciela): strzalki
       maja stac tak samo blisko krawedzi panelu, jak jego dolny brzeg stoi pod
       miniaturami. Gora zostaje wieksza - tam siedzi podpis. */
    /* Dopelnienie GORNE o polowe mniejsze (8 px zamiast 16) - wskazanie
       wlasciciela: panel ma podejsc do gory, zeby miniatury dalo sie obslugiwac
       bez przewijania na malym telefonie. Boki i dol zostaja bez zmian: to one
       trzymaja strzalki i kafelki przy krawedziach panelu. */
    .demo-panel { padding: var(--sp-2) var(--sp-3) var(--sp-3); }

    /* Kolka strzalek odrobine wieksze - wskazanie wlasciciela: obok nich zostaje
       sporo wolnego pola, a mniejszy palec trafia w nie gorzej niz w miniature.
       34 -> 38 px, czyli tyle, ile daje sie dolozyc bez zabierania miejsca
       kafelkom: okno miniatur pokazuje wylacznie CALE kafelki (Showcase.fitThumbs),
       wiec kazde 56 px zabrane z wiersza kosztuje jedna miniature. Zmierzone
       po zmianie - liczba widocznych kafelkow nie spadla na zadnej szerokosci. */
    .demo-panel__arrow {
        width: 2.375rem;
        height: 2.375rem;
    }

    /* Strzalki maja stac DOKLADNIE w polowie drogi miedzy obrysem panelu
       a oknem miniatur - po obu stronach tak samo (polecenie wlasciciela).
       Robia to dwie rzeczy razem:
       - `space-evenly` dzieli wolne miejsce na RÓWNE odcinki: przed strzalka,
         miedzy strzalka a oknem, i tak samo z drugiej strony; `gap` musi przy
         tym zejsc do zera, bo doliczalby sie tylko do odcinkow wewnetrznych,
       - ujemny margines w poziomie wyprowadza wiersz az na obrys panelu.
         Bez niego "wolne miejsce" liczyloby sie od krawedzi dopelnienia i przy
         obrysie zostawaloby o te 12 px wiecej niz przy miniaturach.
       Zmierzone przy 390 px: 24 px od obrysu do strzalki i 24 px od strzalki
       do okna. */
    .demo-panel__row {
        justify-content: space-evenly;
        gap: 0;
        margin-inline: calc(-1 * var(--sp-3));
    }

    /* Rozstrzelenie liter kosztuje przy tej dlugosci napisu ok. 40 px, czyli
       dokladnie tyle, ile brakuje do jednej linii przy 360 px (zmierzone:
       287 px napisu w 288 px swiatla panelu). Zwezone, a nie skrocone - napis
       ma zostac ten sam na kazdej szerokosci. */
    .demo-panel__label { letter-spacing: 0.12em; }

    /* Podpis ma stac na srodku miedzy gorna krawedzia panelu a miniaturami.
       Wystarczy do tego jedna rzecz: odstep POD nim rowny dopelnieniu panelu
       NAD nim - pole wiersza tekstu ma tyle samo swiatla u gory, co u dolu,
       wiec rowne marginesy daja rowne odstepy takze optycznie. Wlasciciel
       doszedl do tego samego recznie, ujemnym marginesem -7 px do odstepu
       22 px; tutaj wychodzi to z tej samej zmiennej, co dopelnienie panelu,
       wiec zmiana dopelnienia nie rozjedzie podpisu.
       Wartosc zeszla razem z dopelnieniem panelu wyzej - z 16 na 8 px. Rownosc
       jest tu istotniejsza niz sama liczba: gdyby zmienil sie tylko jeden z tych
       dwoch odstepow, podpis przestalby stac na srodku miedzy krawedzia panelu
       a miniaturami. */
    .demo-panel__head { margin-bottom: var(--sp-2); }

    /* Ponizej tego progu miniatury przewijaja sie w poziomie zamiast siedziec
       w oknie pieciu - piec kafelkow po 58 px nie miesci sie przy 360 px.
       Okno traci stala szerokosc i samo staje sie przewijanym paskiem; pasek
       WSZYSTKICH miniatur przestaje sie przesuwac transformem (o to teraz
       dba wylacznie natywny scroll, sterowany przez scrollThumbIntoView). */
    .demo-panel__thumbs {
        /* Na telefonie okno nie dzieli sie na strony, tylko przewija natywnie -
           nie trzyma sie wiec piatki kafelkow. Szerokosc dostaje ze zmiennej,
           ktora wpisuje Showcase.fitThumbs: tyle, ile miesci sie CALYCH
           kafelkow, ani piksela wiecej. Bez tego na krawedzi okna zawsze lezal
           kawalek nastepnej miniatury - przy kazdej szerokosci ekranu inny.
           `flex-grow: 0`, bo przy `1` okno rozepchnelo by sie z powrotem do
           calej szerokosci wiersza i wyliczona wartosc nic by nie dala;
           `flex-shrink: 1` zostaje, wiec bez skryptu pasek zachowuje sie jak
           dotad - wypelnia wiersz i przewija sie natywnie. */
        flex: 0 1 auto;
        width: var(--thumbs-window, auto);
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
    }

    .demo-panel__thumbs::-webkit-scrollbar { display: none; }

    .demo-panel__track {
        transform: none;
        transition: none;
    }

    /* Na telefonie pasek przewija sie natywnie, wiec druga kopia kafelkow
       tylko dublowalaby dania na liscie - patrz Showcase.cloneStrip. */
    .thumb--clone { display: none; }

    /* Przystanki na POCZATKU kafelka, nie na jego srodku. Srodkowanie zostawialo
       przy obu krawedziach okna polowki sasiadow - a okno ma pokazywac wylacznie
       cale miniatury (patrz Showcase.fitThumbs). */
    .thumb { scroll-snap-align: start; }

    /* Pytania: dopelnienie w pionie o polowe mniejsze (8 px zamiast 16 px) -
       polecenie wlasciciela, bo przerwa miedzy tekstem a kreskami byla wieksza
       niz sam tekst. Zeby wiersze nie zaczely przez to "skakac" wysokoscia,
       kazdy dostaje MINIMUM rowne wysokosci pytania DWUWIERSZOWEGO: dwa wiersze
       pisma o interlinii 1,5 plus oba dopelnienia, czyli 2*1,5*16 + 2*8 = 64 px.
       Liczba nie jest wpisana - stoi jako rachunek z tych samych zmiennych,
       wiec zmiana stopnia pisma albo dopelnienia przeliczy ja sama.
       Pytania jednowierszowe sa w tym pudelku wysrodkowane (align-items z reguly
       bazowej), a trzywierszowe - gdyby kiedys powstaly - urosna ponad minimum. */
    /* PYTANIE nosi ten sam stopien, co akapit hero i lista cech (--fs-sm,
       15,5 px) - wskazanie wlasciciela, zeby teksty tej samej rangi mialy
       jedna wielkosc. Hierarchie wobec odpowiedzi trzyma grubosc pisma
       i kolor, a nie rozmiar.
       Minimum wysokosci liczy sie DALEJ z tego samego stopnia, co pismo
       pytania - inaczej rachunek przestalby opisywac to, co mierzy. */
    .faq__question {
        font-size: var(--fs-sm);
        padding-block: var(--sp-2);
        min-height: calc(2 * 1.5 * var(--fs-sm) + 2 * var(--sp-2));
    }

    /* ODPOWIEDZ to tekst opisowy, tak samo jak zdania wprowadzajace pod
       naglowkami sekcji - stad wspolny token.

       Dolne dopelnienie ROWNE gornemu dopelnieniu pytania (--sp-2, 8 px) -
       wskazanie wlasciciela: po rozwinieciu odstep do kreski nad pytaniem
       i do kreski pod odpowiedzia ma byc ten sam, w wersji mniejszej. Stalo
       tu --sp-6 (24 px), wiec rozwiniete pytanie mialo 8 px u gory i 24 px
       u dolu - czytalo sie jak przechylone w dol.
       Zwijanie na tym nie ucierpi: podloge z dopelnienia zdjelo juz
       `minmax(0, ...)` na .faq__answer (patrz komentarz przy tamtej regule). */
    .faq__answer p {
        font-size: var(--fs-opis);
        padding-right: 0;
        padding-bottom: var(--sp-2);
    }
    .faq__robot img { width: 15rem; }

    .site-footer__grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }

    /* Na telefonie kazdy piksel szerokosci idzie na nagranie - okno traci
       dopelnienie, a krzyzyk podjezdza do krawedzi ramki. */
    .modal { padding: var(--sp-3); }
    .modal__video { max-height: 80vh; }

    /* Ten sam kadr w wariancie 960 px - 22 kB zamiast 528 kB. */
    :root { --modal-photo: url('../img/bg-cennik-960.webp'); }

    .modal__bar { padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-5); }
    .modal__pane--calc { padding: var(--sp-5); }

    /* Podsumowanie schodzi pod pytania i przestaje byc przyklejone - na
       telefonie przyklejona karta zabralaby polowe ekranu. */
    .calc__grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }
    .calc__summary { position: static; padding: var(--sp-5); }
    /* Przycisk kalkulatora: ciasniejszy napis, zeby zmiescil sie w swoim
       dopelnieniu. Zmierzone przy 320 px: pole miedzy dopelnieniami ma 164 px,
       a napis "POROZMAWIAJMY" - 169, wiec wchodzil w dopelnienie i dotykal
       obrysu (zgloszenie wlasciciela).
       Dwie male oszczednosci zamiast jednej duzej: rozstrzelenie z 0,10 na
       0,05em (13 znakow razy pol punktu to okolo 10 px) i stopien o pol punktu
       mniejszy. Sam napis zostaje - jest wezwaniem do dzialania i ma brzmiec
       tak samo na kazdej szerokosci. */
    .calc__cta {
        font-size: 0.9375rem;
        letter-spacing: 0.05em;
    }

    .calc__seg--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    .plan { padding: var(--sp-6) var(--sp-5) var(--sp-7); }
    .form-card { padding: var(--sp-6) var(--sp-5); }
    /* Na telefonie kolo bierze niemal cala szerokosc ekranu: przy sztywnym
       ksztalcie to jedyny sposob, zeby tekst zmiescil sie w kole zamiast z niego
       wyciekac. 88vw zamiast 78vw daje przy 390 px srednice 343 zamiast 272 px,
       czyli o polowe wiecej pola na tresc. */

    .contact__value { font-size: var(--fs-lead); }
}

/* --- Lzejsze zdjecia tla na waskim ekranie --------------------------------
   Prog 960 px, nie jeden z --bp-*: chodzi o szerokosc SAMEGO ZDJECIA, nie
   o zmiane ukladu. Wariant 960 px wazy ok. 8 kB zamiast 25 kB i - co wazniejsze
   na telefonie - zajmuje po dekompresji szesciokrotnie mniej pamieci. */
@media (max-width: 960px) {
    /* Na telefonie zostaja warianty 960 px w WebP q95: nie chodzi o wage pliku,
       tylko o pamiec - kadr 4118 px to ok. 26 MB mapy bitowej po dekompresji,
       a na ekranie widac z niego pasek szerokosci 390 px. */
    /* Pasmo FAQ NIE dostaje juz zdjecia takze tutaj. Regula wyzej (.faq)
       odebrala mu kadr przy zmianie tla strony glownej, ale ta linia zostala
       i telefon dalej malowal STARY kadr - w efekcie pod sekcja "Skala 1:1"
       zaczynalo sie zdjecie, ktorego na szerokim ekranie dawno nie ma
       (zgloszenie wlasciciela). Wartosc `none` stoi przy .faq w sekcji 3.
       ROLLBACK: --band-photo: url('../img/bg-faq-960.webp'); */
    /* Ten sam nowy kadr w wariancie 960 px.
       ROLLBACK: .page-start { --page-bg: url('../img/bg-faq-960.webp'); } */
    .page-start     { --page-bg:    url('../img/bg-faq-2-960.webp?v=11'); }   /* EKSPERYMENT */
    .page-oferta    { --page-bg:    url('../img/bg-cennik-960.webp'); }
    .page-wdrozenie { --page-bg:    url('../img/bg-wdrozenie-960.webp'); }
    .page-kontakt   { --page-bg:    url('../img/bg-kontakt-960.webp'); }

    /* Prog z body::before tu nie obowiazuje: plik ma 960 px szerokosci,
       rozciaganie go do 2000 px daloby rozmycie i 4x wiecej pamieci po
       dekompresji, a widac z niego i tak rozmyty gradient. */
    body::before {
        background-size: 100% auto;
        background-position: center top;
    }

    /* Kadry podstron powiekszone tak samo, jak na stronie glownej - wskazanie
       wlasciciela ("na mobilnej slabo to widac").
       Rachunek jest ten sam: przy 100% kadr o proporcji ok. 2,5:1 dawal przy
       390 px okna pasek wysokosci ok. 157 px, czyli mniej niz jedna piata
       ekranu - tlo praktycznie nie pracowalo.
       DOKLADNIE te same wartosci, co na stronie glownej - polecenie
       wlasciciela. Probowalem tu wczesniej 170% z kotwica na srodku, licząc,
       ze przy symetrycznym kadrze srodek jest bezpieczniejszy; wlasciciel
       odrzucil ten wariant. Ma byc jedno ustawienie dla calej witryny.
       Sufit `min(..., 960px)` to szerokosc SAMEGO PLIKU: powyzej niej
       przegladarka juz nie powieksza, tylko oddaje kadr jeden do jednego -
       kolejne procenty tylko rozmywalyby 960-pikselowe zrodlo. */
    body.page-oferta::before,
    body.page-wdrozenie::before,
    body.page-kontakt::before {
        /* 242% i przesuniecie -36 px dobral wlasciciel w przegladarce.
           Jego zapis mial `background-size` dwa razy (najpierw z sufitem,
           potem samo 242%), wiec druga deklaracja kasowala pierwsza - tutaj
           sufit WRACA i to nie jest kosmetyka. Plik zrodlowy ma 960 px
           szerokosci: przy oknie 390 px, na ktorym wartosc byla dobierana,
           242% to 944 px i miesci sie w zrodle, ale juz przy 412 px wychodzi
           997 px, a przy 430 px - 1041 px. Bez sufitu telefony tej klasy
           dostawalyby kadr rozciagniety ponad rozdzielczosc pliku, czyli
           rozmyty i drozszy w pamieci. Z sufitem wyglad u wlasciciela jest
           identyczny, a szersze ekrany sa chronione.
           Przesuniecie w PIKSELACH, nie w rem, celowo: kadr jest skalowany
           procentem od szerokosci okna, wiec jego wielkosc nie zalezy od
           stopnia pisma - a rem by zalezal i przy powiekszonej czcionce kadr
           jechalby w bok bez powodu. */
        --page-bg-nudge: -36px;

        background-size: min(242%, 960px) auto;
        background-position: var(--page-bg-x, var(--page-bg-nudge)) var(--header-h);
    }

    /* Wdrozenie kotwiczy sie od PRAWEJ, nie od lewej. To nie kaprys, tylko
       zawartosc tego konkretnego kadru: kopula, smugi swiatla i roslina stoja
       w jego prawej trzeciej, a lewa polowa jest niemal pusta. Przy szerokosci
       242% na telefonie widac tylko okolo 41% obrazu - kotwica od lewej
       pokazywala wiec dokladnie te pusta czesc i stad zgloszenie, ze "tla
       prawie nie widac". Rozmiar zostaje ten sam, zmienia sie wylacznie to,
       ktory fragment jest w kadrze.
       Kadry Oferty i Kontaktu maja tresc rozlozona inaczej i zostaja przy
       wspolnym ustawieniu. */
    body.page-wdrozenie::before { --page-bg-x: 100%; }

    /* Strona glowna trzyma przesuniecie spod belki takze tutaj - regula wyzej
       ustawia `background-position` wprost, wiec zmienna --page-bg-pos jej nie
       dosiega. Powod ten sam: belka jest nieprzezroczysta i bez tego zjadalaby
       gorna czesc kadru, w ktorej stoi glowa goscia. */
    body.page-start::before {
        /* Kadr powiekszony do 200% szerokosci okna - polecenie wlasciciela
           (najpierw 160%, potem podniesione, bo nadal bylo za male).
           Przy 100% zdjecie o proporcji 1,97:1 dawalo przy 390 px okna pasek
           wysokosci ok. 199 px, czyli mniej niz jedna czwarta ekranu telefonu:
           tlo praktycznie nie pracowalo. 200% daje ok. 397 px, czyli kadr
           siega pod scene z daniem.
           Sufit `min(..., 960px)` to szerokosc SAMEGO PLIKU: powyzej niej
           przegladarka juz nie powieksza kadru, tylko oddaje go jeden do
           jednego. Bez tego przy 900 px okna rysunek szedlby na 2160 px, czyli
           ponad dwukrotne powiekszenie 960-pikselowego zrodla - rozmyte
           i drozsze w pamieci. Przy 240% sufit zaczyna dzialac od 400 px okna:
           telefony 390 px dostaja dokladnie zamowione 240%, a szersze (412,
           430 px) od 233% w dol. Roznica jest niewidoczna, ale gdyby kadr mial
           rosnac dalej, to jest moment na wariant 1440 px pliku - kolejne
           procenty juz tylko rozmywaja. Gdyby kadr mial byc wiekszy
           takze na szerszych ekranach, tanszym wyjsciem jest wariant 1440 px
           pliku niz kolejne procenty.
           Pozycja: kotwica po LEWEJ - tak samo, jak na szerokim ekranie (patrz
           --page-bg-pos przy .page-start) i tak, jak chce wlasciciel; kadr jest
           szerszy od okna, wiec to ona decyduje, ktora jego czesc widac.
           W pionie tuz pod belka, ktora jest nieprzezroczysta i inaczej
           zjadalaby gorna czesc zdjecia. */
        background-size: min(240%, 960px) auto;
        background-position: left var(--header-h);
    }

    /* ======================================================================
       PROBA: POSWIATA STRONY GLOWNEJ ROZEPCHNIETA NA TELEFONIE
       ======================================================================
       Zgloszenie wlasciciela: na telefonie ciemna czesc dominuje, jasnej
       zieleni jest mniej niz na desktopie, a ciemne "wylewa sie" na naglowek.

       Przyczyna jest w jednostkach. `--glow-hero` (sekcja 1) nie mial ZADNEGO
       nadpisania mobilnego, a jego procenty licza sie od okna, nie od zdjecia.
       Ta sama deklaracja daje wiec zupelnie inny obraz:
         desktop 1440x900 -> elipsa 1670x810, czyli szersza niz okno,
         telefon  390x844 -> elipsa  452x760, czyli waska i wysoka.
       Zielen wygasa przez to wysoko i dolne dwie trzecie ekranu zostaja przy
       samym kolorze tla. Do tego pierwsza warstwa jest CIEMNA (linear od gory)
       i na malym ekranie ciazy tak samo mocno, tyle ze na duzo mniejszej
       powierzchni - stad wrazenie, ze zlewa sie z belka.

       Co zmienione, warstwa po warstwie:
       1. ciemna warstwa od gory: krycie 0,55 -> 0,28 i krotsza (26% -> 20%).
          Ma zostac, bo pod belka nadal potrzebne jest oparcie, ale ma przestac
          byc pierwszym, co widac.
       2. glowna zielen: szerokosc 116% -> 200% i srodek z 42% na 50%. To jest
          to "rozepchniecie poza ekran" - wygaszanie elipsy wypada teraz DALEKO
          za lewa i prawa krawedzia, wiec na ekranie zostaje jej jasny srodek,
          a nie ciemniejacy brzeg.
       3. druga zielen: 80% -> 170% szerokosci i dluzszy zasieg (65% -> 72%),
          zeby zielen siegala nizej niz do polowy ekranu.

       ROLLBACK: skasowac ta jedna regule. --glow-hero z sekcji 1 wraca wtedy
       do uzycia na kazdej szerokosci, dokladnie jak dzis.
       ====================================================================== */
    /* TA SAMA POSWIATA NA WSZYSTKICH PODSTRONACH - wskazanie wlasciciela.
       Rozepchniecie dostala najpierw sama strona glowna, a podstrony zostaly
       przy --glow-page (110% szerokosci, srodek w 68%) i --glow-doc, czyli
       przy dokladnie tym samym problemie: waska elipsa na waskim ekranie,
       zielen wygasajaca wysoko i ciemne tlo w dolnych dwoch trzecich. Rozne
       konfiguracje na roznych podstronach bylo widac przy przechodzeniu
       miedzy nimi.
       Selektor wymienia klasy z premedytacja: samo `body` ma nizsza
       szczegolowosc niz `.page-start` z sekcji 3 i by z nia przegralo. */
    body.page-start,
    body.page-oferta,
    body.page-wdrozenie,
    body.page-kontakt,
    body.page-doc {
        /* Krycie obnizone, zeby zdjecie bylo lepiej widoczne - wskazanie
           wlasciciela. Samo zdjecie NIE MA zadnej przezroczystosci ani filtra
           (`opacity` przy body::before sluzy wylacznie wejsciu po zejsciu
           zaslony i w spoczynku wynosi 1), a welon --page-veil jest na
           podstronach wylaczony. Jedyne, co je przykrywalo, to wlasnie ta
           poswiata - wiec podniesienie widocznosci znaczy tu zdjecie krycia
           z niej, a nie ruszanie obrazu.
           Zeszlismy: ciemna warstwa 0,28 -> 0,18, zielen glowna 0,68 -> 0,56,
           zielen druga 0,45 -> 0,38. Geometria (200%/170%, srodek w 50%) bez
           zmian - to ona odpowiada za to, ze nie jest ciemno, wiec nie ma
           powodu jej dotykac. */
        /* CZTERY warstwy, kolejnosc od wierzchu do spodu - w CSS pierwsza
           z listy maluje sie NA WIERZCHU.
           Zmiana wzgledem poprzedniej wersji: zielen siegala do 60% i 72%
           wysokosci OKNA (warstwa jest `fixed`, wiec liczy sie wzgledem
           ekranu, a nie strony) i ponizej tego zostawal sam kolor tla, czyli
           ciemne przez dolna jedna trzecia ekranu przez caly czas przewijania.
           Teraz zielen dochodzi do 82% i 92%, a ciemne wraca WYLACZNIE przy
           samej dolnej krawedzi, jako osobna warstwa zaczynajaca sie dopiero
           na 72% i nabierajaca sily do konca. Dzieki temu ciemny pas jest tam,
           gdzie ma byc - domyka kadr u dolu - zamiast przykrywac trzecia
           ekranu. */
        --page-glow:
            /* 1. oparcie pod belka naglowka */
            linear-gradient(180deg, rgba(9, 41, 24, 0.18) 0%, transparent 20%),
            /* 2. domkniecie kadru przy DOLNEJ krawedzi ekranu */
            linear-gradient(180deg, transparent 72%, rgba(var(--rgb-bg-deep), 0.45) 100%),
            /* 3. zielen glowna - rozciagnieta nizej (bylo 60%) */
            radial-gradient(200% 110% at 50% 18%, rgba(0, 107, 69, 0.56) 0%, transparent 82%),
            /* 4. zielen dolna - rozciagnieta nizej (bylo 72%) */
            radial-gradient(170% 190% at 50% 29%, rgba(15, 80, 37, 0.38) 0%, transparent 92%);
    }
}

/* --- Ograniczony ruch -----------------------------------------------------
   Bloku `prefers-reduced-motion` tu NIE MA i to jest decyzja wlasciciela, nie
   przeoczenie. Gasil maskotke, poswiate karty cennika i przerywane okregi -
   ruch ma zostac widoczny. Scena 3D byla spod niego wyjeta od poczatku. */
/* Bloku `prefers-reduced-motion` juz tu nie ma i to jest decyzja, nie
   przeoczenie. Gasil on maskotke, pulsujaca poswiate karty polecanego pakietu
   i przerywane okregi przy krokach. Wlasciciel wybral inaczej: ruch ma zostac
   widoczny, a jedyna podstrona, na ktorej mialby ciazyc, jest strona startowa -
   tam zadnej z tych dekoracji nie ma (jest scena 3D, wyjeta spod tej reguly od
   poczatku). Maskotka rusza sie teraz wylacznie na Wdrozeniu, czyli tak samo,
   jak w oryginalnym eksporcie z Claude Design, ktory tej reguly nie ma wcale. */


/* ==========================================================================
   23. STRONA BLEDU 404
   ==========================================================================
   Osobny, lekki uklad: 404.html nie powiela naglowka ani stopki, bo te stoja
   w szesciu plikach pilnowanych przez tools/sprawdz.mjs i siodma kopia
   rozjechalaby sie z nimi po cichu. Zamiast tego znak marki, komunikat
   i skroty do glownych dzialow - tyle, ile potrzeba, zeby gosc z martwego
   odnosnika mial dokad pojsc.
   ========================================================================== */

.notfound {
    flex: 1 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-12) var(--sp-5);
    text-align: center;
}

.notfound__inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
    max-width: 34rem;
}

.notfound__brand { display: block; margin-bottom: var(--sp-4); }

/* Kod bledu jako znak graficzny, nie naglowek: naglowkiem strony jest zdanie
   pod nim, bo to ono niesie tresc. */
.notfound__code {
    margin: 0;
    font-size: var(--fs-price);
    font-weight: 800;
    line-height: 1;
    letter-spacing: var(--ls-tight);
    color: var(--c-accent);
    opacity: 0.35;
}

.notfound__title {
    font-size: var(--fs-h1);
    font-weight: 700;
    letter-spacing: var(--ls-tight);
    color: var(--c-text-strong);
}

.notfound__lead {
    max-width: 32rem;
    color: var(--c-text-70);
    font-size: var(--fs-lead);
}

.notfound__nav {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
    margin-top: var(--sp-4);
}

.notfound__link {
    color: var(--c-text-70);
    font-size: var(--fs-sm);
}

.notfound__link:hover,
.notfound__link:focus-visible { color: var(--c-accent-lit); }
