/* ============================================================================
   PL:ON — sklep biletowy · warstwa wizualna
   Tokeny: plon-tokens.css (kopia startowa z clients/plon/brand — parity-manifest.yaml).
   Dark-first, akcent miętowy. ŻADNEGO koloru poza var(--token).

   Makiety lo-fi (docs/bilety/architektura/makiety/index.html) były CELOWO bez brandu —
   to jest warstwa, którą tamten dokument zapowiadał jako „powstaje później, na tokenach marki".
   ========================================================================== */

/* ── Kontrast tekstu pomocniczego — WCAG AA, zmierzony ──────────────────────────
   🔴 `--fg-3` z tokenów marki wskazuje `--plon-gray-500` (`#6B6B6B`) i NIE PRZECHODZI AA
   dla tekstu zwykłego. Zmierzone na produkcji 2026-09-08, pięć ekranów × trzy szerokości:
   `#6B6B6B` na `#0A0A0A` = **3,72**, na `#2A2A2A` (karty, ramka koszyka) = **2,69**,
   przy progu **4,5**. To była JEDYNA przyczyna wszystkich 7–10 padnięć kontrastu na każdym
   ekranie — nic innego nie padało.

   ⚠️ Poprawka NIE dotyka `plon-tokens.css`: to kopia `verbatim` z `clients/plon/brand`
   (`parity-manifest.yaml`), czyli źródło prawdy o marce, a nie plik do łatania po stronie
   sklepu. Nie wprowadza też ŻADNEGO nowego koloru — przesuwa alias o jeden szczebel wyżej
   po TEJ SAMEJ rampie marki: `--plon-gray-400` (`#9A9A9A`) daje **7,04** na `#0A0A0A`,
   **5,10** na `#2A2A2A` i **6,19** na `#1A1A1A`.

   ⚠️ To NIE jest defekt marki. Zmierzone tego samego dnia na `https://plon.info`: `#6B6B6B`
   ma tam **zero wystąpień jako kolor tekstu** — sklep był jedyną powierzchnią, która sięgnęła
   po ten szczebel do treści czytanej. Dlatego poprawka należy do sklepu, a `--plon-gray-500`
   zostaje w tokenach nietknięty dla zastosowań nietekstowych.

   Hierarchia zostaje trzystopniowa: `--fg-1` biały · `--fg-2` `#C4C4C4` · `--fg-3` `#9A9A9A`.
   ========================================================================== */
:root { --fg-3: var(--plon-gray-400); }

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

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg-1);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-relaxed);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent-text); text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent); text-underline-offset: 3px; }
a:hover { color: var(--accent-hover); }

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

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: var(--accent-fg);
  padding: var(--space-3) var(--space-5); font-weight: var(--fw-bold);
}
.skip-link:focus { left: 0; }

.wrap { max-width: var(--max-w-content); margin: 0 auto; padding: 0 var(--space-5); }

/* ── nagłówek ───────────────────────────────────────────────────────────── */
.topbar {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--plon-black) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border-soft);
}
.topbar__in { display: flex; align-items: center; gap: var(--space-5); min-height: 80px; }
.topbar__logo { display: flex; align-items: center; gap: var(--space-3); }
/* +50% względem pierwotnych 26px (decyzja właściciela 2026-09-10). `min-height` paska
   podniesione razem z logo, żeby tabliczka nie dotykała krawędzi `.topbar`. */
.topbar__logo img { height: 39px; width: auto; display: block; }
.topbar__back { margin-left: auto; font-size: var(--fs-caption); color: var(--fg-3); letter-spacing: var(--tracking-wide); text-transform: uppercase; text-decoration: none; }
.topbar__back:hover { color: var(--fg-2); }

.cartpill {
  display: inline-flex; align-items: center; gap: var(--space-2);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: 6px var(--space-4); font-size: var(--fs-caption);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--fg-2); text-decoration: none;
}
.cartpill[data-count]:not([data-count="0"]) { border-color: var(--accent); color: var(--accent-text); }

/* ── hero landingu ──────────────────────────────────────────────────────── */
.hero { position: relative; overflow: hidden; padding: var(--space-9) 0 var(--space-8); }
.hero__kv {
  position: absolute; inset: -20% -10% auto auto; width: min(760px, 74vw);
  opacity: var(--kv-op-glow); filter: blur(var(--kv-blur-glow));
  pointer-events: none; z-index: 0;
}
/* Ostry kontur KV na aurze — `--kv-op-glow-sharp`, token nazwany wprost dla tej warstwy.
   Mniejszy i przesunięty względem poświaty (nie kopia 1:1): aurora ma być łuną ZA konturem. */
.hero__kv--sharp { opacity: var(--kv-op-glow-sharp); filter: none; inset: -14% -8% auto auto; width: min(560px, 56vw); }
.hero > .wrap { position: relative; z-index: 1; }

/* ② Eyebrow z kropką akcentu — AUGMENTACJA bazy z `plon-tokens.css` (`.eyebrow, .kicker`),
   wzorzec przeniesiony z plon.info (`sections.css:137-138`).

   🔴 KOLORU ANI WAGI TU NIE NADPISUJEMY, I TO JEST CAŁA TREŚĆ TEJ ZMIANY. Baza w tokenach
   daje `--fg-2` przy wadze 600; mięta należy WYŁĄCZNIE do kropki. Poprzednie brzmienie stawiało
   cały eyebrow na `--accent-text` + `--fw-bold` — dołożenie miętowej kropki do miętowego tekstu
   znosi sens wzorca, bo kropka przestaje być akcentem, a staje się myślnikiem w tym samym kolorze.
   🔴 KROPKA JEST KWADRATEM — zero `border-radius`, zgodnie z geometrią marki (`--radius-0`
   jako domyślny dla kart). Zaokrąglenie jej jest zmianą brandową, nie kosmetyką. */
.eyebrow { display: inline-flex; align-items: center; margin: 0 0 var(--space-4); }
.eyebrow .dot {
  display: inline-block; width: 6px; height: 6px;
  background: var(--accent); margin-right: 10px; flex: none;
}
/* 🔴 `display: flex` i BRAK `max-width` — nagłówek ma zmieścić tekst i sygnaturę w JEDNEJ
   linii. `max-width: 15ch` liczy się w znakach tekstu i o szerokości lockupu nie wie nic,
   więc łamał wiersz przed obrazkiem. `flex-wrap` zostawia zejście do dwóch linii na wąskim
   ekranie, gdzie jedna linia i tak się nie mieści. */
.hero h1 {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0 0.3em;
  font-size: var(--fs-h1); line-height: var(--lh-tight); letter-spacing: var(--tracking-tight);
  font-weight: var(--fw-black); margin: 0 0 var(--space-4);
}
/* Sygnatura subbrandu w miejscu dawnego `<span class="haslo">`. Lockup 401 × 88 niesie
   WŁASNE TŁO (biała tabliczka + miętowa płyta) i stoi w linii z tekstem jako element flex —
   `align-items: center` na `h1` daje mu środkowanie do wiersza, bo linia bazowa obrazka
   z tabliczką ustawiałaby go za nisko. Skala idzie z `--fs-h1`, żeby lockup rósł razem
   z nagłówkiem, a nie obok niego. */
.hero__sygnatura {
  display: block; height: calc(var(--fs-h1) * 0.78); width: auto; max-width: 100%;
}
.hero__lead { font-size: var(--fs-lead); color: var(--fg-2); max-width: var(--max-w-prose); margin: 0; }
.haslo { font-family: var(--font-serif); font-style: italic; font-weight: 600; color: var(--accent-text); }

/* ── ③ dekor KV za ekranami lejka — `.kv-scene` + `.kv-bg` ─────────────────── */
/*
   Prymitywy przeniesione z plon.info (`sections.css:647-650`, `:730-731`, `:953-962`).
   Key visual wchodzi MASKĄ, nie `<img>`: pusty `<span>` wypełniony kolorem z tokenu i przycięty
   maską SVG. Dzięki temu KV barwi się akcentem (a przy `[data-sb]` — subbrandem) bez osobnych
   plików mint/stroke, a znacznik nie niesie treści, której czytnik ekranu miałby czytać.

   🔴 `mix-blend-mode: screen` ROZJAŚNIA TŁO POD SPODEM — i to jest jedyny powód, dla którego
   ten blok ma tyle komentarza co reguł. Dla JASNEGO tekstu rozjaśnione tło znaczy kontrast
   NIŻSZY, a `tools/check-tresc.php` liczył do tej pory pary tokenów i o tej warstwie nie wiedział:
   dekor mógł zbić realny kontrast BEZ czerwonego światła. Pomiar (plon-website, 08.09, model
   `screen()` w sRGB + alfa) na `--kv-bg-op: .12`:
       na `--plon-black  #0A0A0A` → tło `#11251E`: `--fg-3` 5,72 · `.codemsg--err` 6,22   ✅
       na `--plon-graphite #2A2A2A` → tło `#30413C`: `--fg-3` 3,82 · `.codemsg--err` 4,16  🔴 FAIL
       na `.conditional #2B3532` → tło `#314B43`: `--fg-3` 3,36 · `.codemsg--err` 3,66     🔴 FAIL
   Stąd REGUŁA, której ten arkusz pilnuje razem z bramką `[KV]`:

   1. KV stoi WYŁĄCZNIE za sekcją na `--bg` (`#0A0A0A`). Nigdy pod kartą (`--bg-elevated`)
      ani pod blokiem firmowym (`.conditional`) — tam ten sam dekor zbija odmowę poniżej AA.
      Trzyma to `.kv-scene > * { position: relative; z-index: 1 }`: karty malują się NAD dekorem
      i są nieprzezroczyste, więc tekst w nich nie ma KV w swoim tle w ogóle.
   2. Geometria jest narożnikiem, nie myciem sekcji: `width: 26%`, `aspect-ratio: 1`, wypchnięte
      `left/right: -6%` poza krawędź. To ona, a nie krycie, trzyma kolumnę tekstu poza dekorem.
   3. Krycie `≤ .12` (warunek L2), na wąskim ekranie ×0,6 — te same wartości co na plon.info.
   ⚠️ Pomiar tokenowy tego NIE zamyka: po wdrożeniu kontrast czyta się ZE ZRZUTU ekranu.
*/
.kv-scene { position: relative; overflow: hidden; }
/* Wszystko, co scena niesie jako treść, stoi NAD dekorem. Selektor jest szeroki celowo:
   węższy (`> .wrap`) przepuściłby pierwszy szablon, który owinie treść w coś innego. */
.kv-scene > *:not(.kv-bg) { position: relative; z-index: 1; }
.kv-bg {
  position: absolute; z-index: 0; pointer-events: none;
  top: 6%; width: 26%; aspect-ratio: 1;
  opacity: var(--kv-bg-op, .12);
  mix-blend-mode: screen;
  background: var(--sb-color, var(--accent));
  -webkit-mask: url(../key-visuals/kv-101-supporting.svg) center/contain no-repeat;
          mask: url(../key-visuals/kv-101-supporting.svg) center/contain no-repeat;
}
.kv-bg--left  { left:  -6%; }
.kv-bg--right { right: -6%; }
@media (max-width: 860px) { .kv-bg { opacity: calc(var(--kv-bg-op, .12) * .6); } }
/* Dekor jest ozdobą — przy `prefers-reduced-transparency` i tak nic się nie rusza, ale przy
   wymuszonych kolorach systemowych maska bez treści potrafi zostać jako plama. */
@media (forced-colors: active) { .kv-bg { display: none; } }

/* ── siatka katalog + koszyk ────────────────────────────────────────────── */
.shop { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: var(--space-7); align-items: start; padding-bottom: var(--space-9); }
@media (max-width: 960px) { .shop { grid-template-columns: 1fr; } }

.section-head { margin: 0 0 var(--space-5); }
.section-head h2 { font-size: var(--fs-h2); font-weight: var(--fw-black); letter-spacing: var(--tracking-tight); margin: 0 0 var(--space-2); }
.section-head p { color: var(--fg-3); margin: 0; font-size: var(--fs-caption); letter-spacing: var(--tracking-wide); text-transform: uppercase; }

/* ── karta typu biletu ──────────────────────────────────────────────────── */
.tier {
  position: relative; background: var(--bg-elevated); border: 1px solid var(--border);
  padding: var(--space-6); margin-bottom: var(--space-5);
  transition: border-color var(--dur-fast) var(--ease-out-quart);
}
.tier:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.tier--featured { border-color: var(--accent); box-shadow: var(--shadow-mint); }
.tier__top { display: flex; align-items: baseline; gap: var(--space-4); flex-wrap: wrap; }
.tier__name { font-size: var(--fs-h3); font-weight: var(--fw-bold); margin: 0; }
/* ① Cena — serif italic miętowa, wzorzec `.tier-price` z plon.info (`sections.css:1192`).
   `--fs-h2` zamiast tamtejszych 24 px: na stronie cena jest zajawką, w sklepie jest kotwicą
   decyzji i ma dominować nad nazwą typu (`--fs-h3`). Rozjazd świadomy, nie przeoczenie.
   `tabular-nums` ZOSTAJE — kolumna kwot nie ma skakać przy zmianie liczby, a serif italic
   tego nie wyklucza. `overflow-wrap: anywhere` przeniesione razem z regułą: bez niego długa
   etykieta przy cenie rozjeżdżała kartę (zmierzone po tamtej stronie, „gotcha P1”).
   `--tracking-tight` ZDJĘTE — ujemny odstęp projektowano dla Intera 800, a nie dla kursywy
   szeryfowej, w której skleja sąsiadujące cyfry. */
.tier__price {
  margin-left: auto;
  font-family: var(--font-serif); font-style: italic; font-weight: var(--fw-bold);
  font-size: var(--fs-h2); line-height: 1.15; color: var(--accent-text);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.tier__desc { color: var(--fg-2); margin: var(--space-4) 0 0; max-width: 58ch; }
/* Dopisek „brutto" — waga i wielkość cofnięte do captiona, żeby nie konkurował z kwotą.
   Kolor: `--fg-2`, NIE `--plon-gray`. `--plon-gray` (#6B6B6B) na `--bg-elevated` (#2A2A2A)
   daje kontrast 2,7:1 — poniżej progu WCAG AA 4,5:1 dla tekstu 12 px. `--fg-2` daje 8,2:1.
   Ta sama zamiana, co w `.tier__desc` wyżej. */
.tier__price-note { font-size: var(--fs-caption); font-weight: var(--fw-regular); letter-spacing: var(--tracking-normal); color: var(--fg-2); }
.tier__desc strong { color: var(--fg-1); }
.tier__foot { display: flex; align-items: center; gap: var(--space-4); margin-top: var(--space-6); flex-wrap: wrap; }

.badge {
  display: inline-flex; align-items: center; gap: 6px; border-radius: var(--radius-pill);
  padding: 4px 12px; font-size: var(--fs-caption); font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
}
.badge--ok   { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent-text); }
.badge--none { background: color-mix(in srgb, var(--plon-red) 16%, transparent); color: var(--plon-red); }
.badge--info { background: rgba(255,255,255,.06); color: var(--fg-2); }

/* stepper ilości */
.stepper { display: inline-flex; align-items: stretch; border: 1px solid var(--border); }
.stepper button {
  width: 42px; background: transparent; border: 0; color: var(--fg-1);
  font-size: 18px; line-height: 1; cursor: pointer; font-family: inherit;
}
.stepper button:hover:not(:disabled) { background: rgba(255,255,255,.06); color: var(--accent-text); }
.stepper button:disabled { color: var(--fg-3); cursor: not-allowed; }
.stepper input {
  width: 56px; text-align: center; background: transparent; border: 0;
  border-left: 1px solid var(--border); border-right: 1px solid var(--border);
  color: var(--fg-1); font-family: var(--font-mono); font-size: var(--fs-body);
  -moz-appearance: textfield;
}
.stepper input::-webkit-outer-spin-button, .stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* ── przyciski ──────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: 13px var(--space-6); border: 1px solid transparent; border-radius: var(--radius-2);
  font-family: inherit; font-size: var(--fs-body); font-weight: var(--fw-bold);
  cursor: pointer; text-decoration: none; text-align: center;
  transition: background var(--dur-fast) var(--ease-out-quart), color var(--dur-fast) var(--ease-out-quart);
}
.btn--primary { background: var(--accent); color: var(--accent-fg); }
.btn--primary:hover { background: var(--accent-hover); color: var(--accent-fg); }
/* --border to ten sam kolor co --bg-elevated, więc na panelu ghost znikał.
   Obrys liczony od koloru tekstu jest widoczny na obu powierzchniach. */
.btn--ghost { background: transparent; border-color: color-mix(in srgb, var(--fg-1) 34%, transparent); color: var(--fg-1); }
.btn--ghost:hover { border-color: var(--accent); color: var(--accent-text); }
.btn--block { width: 100%; }
.btn--lg { padding: 16px var(--space-7); font-size: var(--fs-lead); }
/* 🔴 PRZYCISK PEŁNEJ SZEROKOŚCI NIE POTRZEBUJE BOCZNEGO ODDECHU — a `btn--lg` daje mu 48 px
   z każdej strony. Zmierzone 2026-09-10 przy 390 px: przycisk ma 244 px, z czego 96 px zjada
   własny `padding`, więc na napis zostaje 148 px i „Zamawiam i płacę" łamie się na DWIE LINIE.
   Ta sama wada dotyczyła poprzedniego brzmienia („Przejdź do płatności" — też dwa wiersze),
   więc to STAN ZASTANY, nie skutek zmiany napisu. Naprawiona tutaj, bo `AC-3` `U5` jej
   zabrania, a przycisk składający zamówienie ma być czytelny w jednym rzucie.
   ⚠️ Reguła MUSI stać PO `.btn--lg`: tamta ustawia `padding` SKRÓTEM, więc przy tej samej
   wadze selektora rozstrzyga kolejność w pliku. Postawiona wyżej byłaby cichym no-opem.
   ⚠️ Pion zostaje z `.btn--lg` — zmieniamy oddech boczny, nie wysokość pola dotyku. */
.btn--block { padding-left: var(--space-4); padding-right: var(--space-4); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }

.linkish { background: none; border: 0; padding: 0; font: inherit; color: var(--fg-3); cursor: pointer; text-decoration: underline; text-underline-offset: 3px; }
.linkish:hover { color: var(--accent-text); }

/* ── koszyk ─────────────────────────────────────────────────────────────── */
.cart { position: sticky; top: 92px; background: var(--bg-elevated); border: 1px solid var(--border); }
.cart__head { padding: var(--space-5) var(--space-5) 0; }
.cart__head h2 { font-size: var(--fs-h3); font-weight: var(--fw-bold); margin: 0; }
.cart__body { padding: var(--space-5); }
.cart__empty { color: var(--fg-3); margin: 0; }

.line { display: flex; align-items: baseline; gap: var(--space-3); padding: var(--space-3) 0; border-bottom: 1px solid var(--divider); }
.line__name { flex: 1 1 auto; min-width: 0; }
.line__qty { font-family: var(--font-mono); color: var(--fg-3); font-size: var(--fs-caption); }
.line__sum { font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: var(--fw-bold); }
/* B2: usuwanie pozycji jest ikoną W WIERSZU, nie osobnym blokiem pod nazwą — `inline-flex`
   plus `vertical-align` trzymają „×" przy nazwie, zamiast łamać wiersz i podwajać wysokość
   każdej pozycji koszyka. */
.line__ops { display: inline-flex; margin-left: var(--space-3); vertical-align: middle; }
.line__x { background: none; border: 0; padding: 0 4px; line-height: 1; font-size: 20px;
           color: var(--fg-3); cursor: pointer; }
.line__x:hover { color: var(--accent-text); }
.line--discount .line__sum { color: var(--accent-text); }
.line--total { border-bottom: 0; padding-top: var(--space-5); font-size: var(--fs-h3); font-weight: var(--fw-black); }
.line--total .line__sum { font-size: var(--fs-h2); letter-spacing: var(--tracking-tight); }

/* Nagłówek pola kodu w ramce podsumowania (R4(1)) — kod stoi przy kwocie, na którą wpływa.
   Kreska u góry oddziela go od sumy: bez niej „Zastosuj kod" czyta się jak przycisk płatności. */
.cart__subh { margin: var(--space-5) 0 var(--space-3); padding-top: var(--space-5); border-top: 1px solid var(--divider);
              font-size: var(--fs-body); font-weight: var(--fw-bold); }

/* ── noty ───────────────────────────────────────────────────────────────── */
.note {
  border-left: 2px solid var(--border); padding: var(--space-3) 0 var(--space-3) var(--space-4);
  color: var(--fg-3); font-size: var(--fs-caption); line-height: var(--lh-normal); margin: var(--space-5) 0 0;
}
.note--accent { border-left-color: var(--accent); color: var(--fg-2); }
.note--warn   { border-left-color: var(--plon-orange); color: var(--fg-2); }
.note strong { color: var(--fg-2); }

/* ── kroki checkoutu ────────────────────────────────────────────────────── */
.steps { display: flex; gap: var(--space-3); align-items: center; margin: 0 0 var(--space-6); font-size: var(--fs-caption); letter-spacing: var(--tracking-wide); text-transform: uppercase; color: var(--fg-3); }
.steps__item { display: inline-flex; align-items: center; gap: var(--space-2); }
.steps__num { width: 22px; height: 22px; display: grid; place-items: center; border: 1px solid var(--border); border-radius: var(--radius-pill); font-family: var(--font-mono); }
.steps__item[aria-current="step"] { color: var(--accent-text); }
.steps__item[aria-current="step"] .steps__num { border-color: var(--accent); background: var(--accent); color: var(--accent-fg); }
.steps__sep { flex: 0 0 24px; height: 1px; background: var(--border); }

.checkout { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: var(--space-7); align-items: start; padding-bottom: var(--space-9); }
@media (max-width: 960px) { .checkout { grid-template-columns: 1fr; } }

.panel { background: var(--bg-elevated); border: 1px solid var(--border); padding: var(--space-7); }
.panel h1 { font-size: var(--fs-h2); font-weight: var(--fw-black); letter-spacing: var(--tracking-tight); margin: 0 0 var(--space-6); }
.panel h2 { font-size: var(--fs-h3); font-weight: var(--fw-bold); margin: 0 0 var(--space-4); }

/* ── formularz ──────────────────────────────────────────────────────────── */
.field { margin-bottom: var(--space-5); }
.field > label { display: block; font-weight: var(--fw-bold); margin-bottom: 6px; font-size: 15px; }
.field__hint { color: var(--fg-3); font-size: var(--fs-caption); line-height: var(--lh-normal); margin: 6px 0 0; }
/* Grupa pól ekranu przyjęcia (`PRZYJ-DANE`). `<fieldset>` niesie tu ZNACZENIE, nie ramkę:
   pola dotyczą jednej rzeczy (uczestnika), a `<legend>` daje im wspólną nazwę dostępną —
   czytnik ekranu ogłasza ją przy każdym polu. Domyślną ramkę i marginesy przeglądarki zdejmujemy,
   bo oprawę daje `.panel`. */
.fieldset { border: 0; margin: 0; padding: 0; }
.fieldset > legend { font-weight: var(--fw-bold); font-size: 15px; padding: 0; margin-bottom: var(--space-3); }
/* Znacznik nieobowiązkowości STOI W ETYKIECIE, nie w podpowiedzi pod polem: `organization`
   i `job_title` nie mają `required` (`AC-8`, decyzja 405), a człowiek musi to wiedzieć ZANIM
   zacznie wypełniać, nie po. Waga normalna — etykieta jest pogrubiona, więc dopisek nie ma
   udawać jej części. */
.field__opt { font-weight: var(--fw-normal); color: var(--fg-3); }
.input, .select {
  width: 100%; background: var(--plon-gray-850); border: 1px solid var(--border);
  color: var(--fg-1); padding: 12px var(--space-4); font-family: inherit; font-size: var(--fs-body);
  border-radius: var(--radius-1);
}
.input::placeholder { color: var(--fg-3); }
.input:focus, .select:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
.input--short { max-width: 260px; }

/* `GUS-1`: pole NIP i przycisk „Pobierz z GUS" w jednym rzędzie, z zawijaniem.
   ⚠️ `flex-wrap` nie jest ozdobą — przy 400 px przycisk obok pola o `max-width: 260px` wychodzi
   poza kolumnę i tnie się o krawędź. Zawinięty ląduje pod polem i nadal jest osiągalny. */
.gus { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.gus .input { flex: 0 1 260px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
@media (max-width: 620px) { .grid-2 { grid-template-columns: 1fr; } }

.sep { border: 0; border-top: 1px solid var(--divider); margin: var(--space-6) 0; }

/* obowiązek informacyjny — TEKST, nie pole wyboru (W-3.3) */
.rodo { background: rgba(255,255,255,.03); border: 1px solid var(--border-soft); padding: var(--space-5); color: var(--fg-2); font-size: 13px; line-height: var(--lh-normal); }
.rodo p { margin: 0 0 var(--space-3); }
.rodo p:last-child { margin: 0; }

.check { display: flex; gap: var(--space-4); align-items: flex-start; padding: var(--space-4) 0; }
/* Kontrolki wyboru rysujemy sami.
   Powód jest wymaganiem, nie estetyką: `accent-color` na ciemnym tle renderuje pole
   NIEZAZNACZONE jako jasny, wypełniony krążek — czyli dokładnie tak, jak zaznaczone.
   W-3.5 żąda, żeby przy „Kupuję jako" ŻADNA opcja nie była zaznaczona domyślnie, a stan
   nieodróżnialny od zaznaczonego jest tym samym błędem co domyślny wybór. */
.check input[type="checkbox"] {
  flex: none; appearance: none; width: 20px; height: 20px; margin: 2px 0 0;
  border: 1.5px solid var(--fg-3); background: transparent; border-radius: var(--radius-1);
  cursor: pointer; position: relative;
}
.check input[type="checkbox"]:hover { border-color: var(--accent); }
.check input[type="checkbox"]:checked { background: var(--accent); border-color: var(--accent); }
.check input[type="checkbox"]:checked::after {
  content: ""; position: absolute; left: 5.5px; top: 1.5px; width: 5px; height: 10px;
  border: solid var(--accent-fg); border-width: 0 2.5px 2.5px 0; transform: rotate(43deg);
}
.check__txt { font-size: 14px; line-height: var(--lh-normal); color: var(--fg-2); }
.check__txt strong { color: var(--fg-1); }
/* Akapity klauzuli z `E26` (`TRESC-1`). Treść ma dwa akapity o różnej roli — zakres zgody
   i jej odwoływalność — a zlane w jeden blok czytają się jak jedno zdanie na siedem linijek.
   `span` zamiast `p`, bo blok stoi wewnątrz treści pola wyboru. */
.check__akapit { display: block; }
.check__akapit + .check__akapit { margin-top: var(--space-3); }
/* `U16-ODBIÓR`: zdanie z odnośnikami stoi obok `<label>`, nie w nim — sąsiedztwo akapitów go nie łapie. */
.check__dopisek { display: block; margin-top: var(--space-3); }
.check--required .check__txt strong::after { content: " *"; color: var(--accent-text); }
.doclinks { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); margin-top: var(--space-3); font-size: var(--fs-caption); }

.radio-row { display: flex; gap: var(--space-6); flex-wrap: wrap; }
.radio { display: inline-flex; align-items: center; gap: var(--space-3); cursor: pointer; }
.radio input {
  appearance: none; width: 18px; height: 18px; margin: 0;
  border: 1.5px solid var(--fg-3); border-radius: 50%; background: transparent;
  cursor: pointer; position: relative;
}
.radio:hover input { border-color: var(--accent); }
.radio input:checked { border-color: var(--accent); }
.radio input:checked::after {
  content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--accent);
}

/* blok warunkowy — widoczny tylko przy spełnionym warunku */
.conditional { border: 1px dashed var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); padding: var(--space-5); margin: var(--space-5) 0; }
/* ⚠️ `.conditional__tag` ZDJĘTE 2026-09-08 razem z dwiema etykietami rusztowania w `krok2.twig`
   (`R2-2`) — reguła nie ma już ani jednego użycia w szablonach. Zostawiona reguła bez użycia
   wraca jako zaproszenie: ktoś zobaczy klasę w arkuszu i uzna, że etykieta jest przewidziana. */

.codebox { display: flex; gap: var(--space-3); flex-wrap: wrap; }
.codebox .input { flex: 1 1 200px; font-family: var(--font-mono); letter-spacing: .08em; text-transform: uppercase; }
.codemsg { margin: var(--space-3) 0 0; font-size: var(--fs-caption); }
.codemsg--ok { color: var(--accent-text); }
.codemsg--no { color: var(--plon-orange); }
/* 🔴 `.codemsg--err` NIE MIAŁA DOTĄD ANI JEDNEJ REGUŁY, a używa jej DZIESIĘĆ szablonów
   (`krok1`, `krok2`, `katalog` ×2, `blad`, `wznowienie`, `podsumowanie`, `panel/panel`,
   `panel/link`, `panel/przekazanie`). Komunikaty odmów renderowały się kolorem tekstu
   podstawowego — czytelne, ale NIEODRÓŻNIALNE od treści zwykłej. Zmierzone 2026-09-08
   (`grep 'codemsg--err' sklep.css` = 0). Bliźniaczy przypadek — martwe `field__lab`/`field__in`
   w panelu i w `panel/link.twig` — zamknięty 09.09 w `PAN-2` przez zamianę na kanon `.field`
   + `.input`, a nie przez dopisanie im reguł: drugi idiom formantu w jednym arkuszu byłby
   kosztem, nie poprawką.

   ⚠️ KOLOR LICZONY, NIE WYBRANY. Sam `--plon-red` (`#F5333A`) daje **3,70** na `--plon-graphite`
   (`#2A2A2A`) przy progu **4,5** — a `.codemsg--err` stoi na grafitcie w ramce koszyka
   i na kartach panelu, więc na najważniejszej z dziesięciu powierzchni NIE PRZESZEDŁBY AA.
   `--plon-orange` przechodzi (5,55), ale nosi już `.codemsg--no` („ten kod nie zmienia ceny")
   i `.note--warn`: jeden kolor dla odmowy i dla uwagi zniósłby różnicę, którą ta poprawka wnosi.

   Dlatego ten sam ruch co przy `--fg-3`: przesunięcie po WŁASNEJ rampie marki, bez nowego
   koloru i bez dotykania `plon-tokens.css` (kopia `verbatim`, `parity-manifest.yaml`).
   🔴 TŁA SĄ TRZY, NIE DWA — i trzecie jest tym, na którym komunikat stoi NAJCZĘŚCIEJ.
   `.conditional` (blok firmowy Kroku 2, gdzie ląduje odmowa NIP-u) ma `background:
   color-mix(in srgb, var(--accent) 6%, transparent)` NA `--bg-elevated`, czyli efektywnie
   `#2B3532` — jaśniejsze od grafitu, więc dla jasnego tekstu jest to warunek OSTRZEJSZY.
   Pierwsze wydanie tej reguły (`70%`, `#F87075`) liczyło tylko czerń i grafit i dawało tam
   **4,56** przy progu 4,5 — przechodziło, ale zapasem 0,06, na powierzchni, której bramka
   w ogóle nie mierzyła. Zmierzone dopiero na zrzucie z produkcji.

   `color-mix` 65/35 z bielą marki daje `#F97A7F` — **7,65** na `#0A0A0A`, **5,55** na `#2A2A2A`
   i **4,90** na `#2B3532`; wciąż jednoznacznie czerwone i odróżnialne od `--plon-orange`.
   Liczy to bramka `check-tresc.php` na WSZYSTKICH TRZECH tłach, mieszając tokeny tak samo jak
   przeglądarka (`in srgb`) — asercja mierzy SKUTEK, nie zapis. */
.codemsg--err { color: color-mix(in srgb, var(--plon-red) 65%, var(--plon-white)); }

/* ── ekrany stanu ───────────────────────────────────────────────────────── */
.state { max-width: 640px; margin: 0 auto; padding: var(--space-9) 0; text-align: center; }
.state__icon { width: 72px; height: 72px; margin: 0 auto var(--space-6); display: grid; place-items: center; border-radius: var(--radius-pill); border: 1px solid var(--border); }
.state__icon svg { width: 32px; height: 32px; }
.state--ok .state__icon   { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent-text); }
.state--no .state__icon   { border-color: var(--plon-red); background: color-mix(in srgb, var(--plon-red) 12%, transparent); color: var(--plon-red); }
.state--wait .state__icon { color: var(--fg-2); }
.state h1 { font-size: var(--fs-h2); font-weight: var(--fw-black); letter-spacing: var(--tracking-tight); margin: 0 0 var(--space-4); }
.state p { color: var(--fg-2); margin: 0 auto var(--space-6); max-width: 48ch; }
.state__ref { font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--fg-3); letter-spacing: .06em; }
/* Formularz odnośnika do panelu na ekranie powrotu (R2-4). Wąski i wyśrodkowany, bo stoi
   pod treścią wyśrodkowanej sekcji stanu — pełna szerokość rozjeżdżałaby jej oś. */
.state__form { max-width: 380px; margin: var(--space-6) auto 0; text-align: left; }
.state__form .btn { width: 100%; margin-top: var(--space-4); }

.spinner { width: 32px; height: 32px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 900ms linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ── porównanie cen (M4) ────────────────────────────────────────────────── */
.pricecmp { display: grid; grid-template-columns: 1fr auto; gap: var(--space-3) var(--space-5); border: 1px solid var(--plon-orange); background: color-mix(in srgb, var(--plon-orange) 8%, transparent); padding: var(--space-5); margin: var(--space-6) 0; }
.pricecmp dt { color: var(--fg-2); font-size: 14px; }
.pricecmp dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; font-weight: var(--fw-bold); }
.pricecmp .was { color: var(--fg-3); text-decoration: line-through; }
.pricecmp .now { color: var(--plon-orange); font-size: var(--fs-h3); }

/* 🔴 `[hidden]` MUSI BYĆ REGUŁĄ, BO KAŻDE WŁASNE `display` JĄ ZNOSI — i to nie jest teoria.
   Atrybut `hidden` działa przez arkusz przeglądarki (`display: none`), więc PRZEGRYWA z każdą
   naszą regułą ustawiającą `display` — a ustawiają go `.btn` (`inline-flex`), `.consent__opt`
   i `.consent__det` (`flex`). Skutek zmierzony 2026-09-10 w żywej przeglądarce z wyłączonym JS:
   przycisk „Pobierz z GUS" z atrybutem `hidden` miał `display: flex` i BYŁ WIDOCZNY, a kontener
   `#consent-details` z `hidden` pokazywał pola kategorii mimo atrybutu.
   ⚠️ Objawu nie widać w asercji na ATRYBUCIE — `el.hidden` jest wtedy `true` i wszystko wygląda
   poprawnie. Widać go wyłącznie w geometrii (`offsetParent`). Dlatego reguła stoi tu raz,
   dla całego arkusza, zamiast po jednym `display: none` przy każdym formancie.
   `!important` jest konieczny: bez niego przegrywa z każdą regułą klasową o wyższej wadze. */
[hidden] { display: none !important; }

/* ── baner zgód ─────────────────────────────────────────────────────────── */
.consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  background: var(--plon-gray-850); border-top: 1px solid var(--border);
  padding: var(--space-5); box-shadow: var(--shadow-2);
}
.consent__in { max-width: var(--max-w-content); margin: 0 auto; display: flex; gap: var(--space-5); align-items: center; flex-wrap: wrap; }
.consent__txt { flex: 1 1 380px; font-size: 13px; color: var(--fg-2); line-height: var(--lh-normal); margin: 0; }
.consent__ops { display: flex; gap: var(--space-3); flex-wrap: wrap; }
/* Przełączniki kategorii opcjonalnych (`W-2.16` rev. 2 — od 2026-09-08 są DWA: pomiar zachowania
   i pomiar reklam; atrybucja przestała być wybieralna). `flex-basis` pełnej szerokości, żeby przy
   wąskim ekranie pole wyboru nie wciskało się między tekst a przyciski i nie dało się kliknąć obok. */
.consent__opt { flex: 1 1 100%; display: flex; gap: var(--space-3); align-items: flex-start;
  font-size: 13px; color: var(--fg-2); line-height: var(--lh-normal); cursor: pointer; }
.consent__opt input { margin-top: 2px; flex: 0 0 auto; }
/* `A3`: kontener szczegółów.
   ⚠️ `flex: 1 1 100%` DOŁOŻONE ZA PLANEM, ALE POMIAR GO NIE POTWIERDZA — i tak to zapisuję,
   zamiast powtarzać cudze uzasadnienie. Plan ostrzegał, że bez tej reguły „kategorie wjadą
   w rząd obok tekstu"; pomiar 2026-09-10 (Chrome 152, 1920 px i 390 px, geometria z
   `getBoundingClientRect`) daje z regułą i bez niej wynik CO DO PIKSELA ten sam: kontener
   ma szerokość `.consent__in` w obu przypadkach, bo etykiety w środku nadal niosą
   `flex: 1 1 100%` i rozpychają go same. Reguła zostaje jako zabezpieczenie tego sprzężenia
   — gdyby `.consent__opt` kiedyś je straciło — ale ŻADNA bramka jej nie chroni i nie wolno
   twierdzić, że chroni: mutacja usuwająca ją przechodzi na zielono.
   🔴 `hidden` NA TYM KONTENERZE działa wyłącznie dzięki globalnej regule `[hidden]` wyżej.
   Ten element ma własne `display: flex`, które samo znosiłoby `display: none` z arkusza
   przeglądarki — zmierzone, nie przewidziane: pierwsza wersja tego bloku pokazywała pola
   kategorii mimo ustawionego `hidden`. */
.consent__det { flex: 1 1 100%; display: flex; gap: var(--space-3); flex-wrap: wrap; }
/* Przełącznik szczegółów: tekstowy, nie `btn` — obok trzech przycisków decyzji czwarty
   przycisk o tej samej wadze wizualnej czytałby się jak czwarta decyzja. */
.consent__more { background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--accent-text); text-decoration: underline; }
.consent__more:hover { color: var(--accent); }

/* ── stopka ─────────────────────────────────────────────────────────────── */
.foot { border-top: 1px solid var(--border-soft); padding: var(--space-7) 0 var(--space-8); color: var(--fg-3); font-size: var(--fs-caption); }
.foot__in { display: flex; gap: var(--space-5) var(--space-7); flex-wrap: wrap; align-items: flex-start; }
.foot__links { display: flex; gap: var(--space-4); flex-wrap: wrap; margin-left: auto; }
.foot a { color: var(--fg-3); }
.foot a:hover { color: var(--accent-text); }
/* `A1`: „Ustawienia cookies" w stopce. 🔴 Bez własnej reguły goły `<button>` wjeżdża w ciemną
   stopkę z domyślną chromą przeglądarki (jasny prostokąt), bo `.foot a` stylizuje odnośniki,
   nie przyciski. Wygląd ma być NIEODRÓŻNIALNY od sąsiednich odnośników — obok „Polityki
   Cookies" stoi czynność z tej samej rodziny, nie formant formularza. */
.foot__cookies { background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: var(--fg-3); text-decoration: underline; }
.foot__cookies:hover { color: var(--accent-text); }

/* ── plakietka prototypu ────────────────────────────────────────────────── */
.protobar {
  background: var(--plon-orange); color: var(--plon-black);
  font-size: var(--fs-caption); font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  text-align: center; padding: 6px var(--space-4);
}
/* Panel uczestnika (S.8). Formularz danych uczestnika w kafelku biletu — cztery pola obok siebie
   na szerokim ekranie, jedno pod drugim na wąskim, bez własnej siatki. */
/* ⚠️ `align-items: flex-start`, NIE `flex-end`. Od `PAN-2` pod formantem bywa komunikat odmowy
   (`.codemsg--err` przy polu, nie na górze ekranu) — przy wyrównaniu do dołu pole z komunikatem
   podnosi sąsiadów i rząd się rozjeżdża. Wyrównanie do góry trzyma etykiety w jednej linii
   niezależnie od tego, które pole dostało komunikat. */
.panel__form { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-top: var(--space-5); align-items: flex-start; }
.panel__form .field { flex: 1 1 200px; }
/* `PAN-1`. Łamanie rzędu i nota pod parą pól nieobowiązkowych. Nota MUSI stać pod organizacją
   i stanowiskiem, a nie pod całym formularzem: zdanie właściciela mówi „te dane … nie jest
   obowiązkowe", a w jednym rzędzie z imieniem i nazwiskiem wskazywałoby też je — czyli mówiłoby
   nieprawdę o regule, którą ten sam formularz egzekwuje. `height: 0` zamiast `<br>`, bo element
   ma łamać rząd, a nie zajmować wiersz. */
.panel__form-lamanie { flex: 1 1 100%; height: 0; margin: 0; }
.panel__form .field__hint { flex: 1 1 100%; margin: 0; }
