/* base.css — OperatorGO
   Reset + custom properties (tokeny z brief/04-STYLEGUIDE.md) + bazowe komponenty
   + SYSTEM DESIGNU niszy (sekcja `og-*` na końcu pliku, krok `design`).
   ⛔ ZERO stylowania ad hoc poza tym plikiem: żadnych style="…" w HTML, żadnych hexów w JS.
   Podgląd komponentów: design-system/preview.html (karty @dsCard + 3 ekrany wzorcowe). */

/* ---- Archivo (self-host, font ZMIENNY 400–800) ------------------------------
   ⛔ PUŁAPKA L-069: `css2?family=Archivo:...opsz@…` gubi subset latin-ext → z UI znikają
   „ą ć ę ł ń ó ś ź ż". Dlatego font jest REHOSTOWANY lokalnie (feedback-cdn-pin-sri-rehost-csp):
   dwa pliki = ten sam font zmienny w dwóch subsetach (potwierdzone md5), unicode-range decyduje
   który pobrać. Zero zależności od CDN, zero SRI do pilnowania, zero FOUT z obcego hosta. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;            /* oś wght fontu zmiennego */
  font-stretch: 100%;
  font-display: swap;
  src: url("/fonts/archivo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
                 U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                 U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 100%;
  font-display: swap;
  src: url("/fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---- Custom properties — tokeny z brief/04-STYLEGUIDE.md §8 ----
   KANON = manifest makiet (`brief/zrodla/MAKIETY-MANIFEST.md` → preview_brief.design).
   ⛔ Nowy kolor bez wiersza w tabeli kontrastów §2 styleguide'u NIE wchodzi do repo. ---- */
:root {
  color-scheme: dark;                 /* motyw ciemny = jedyny w v1 (R-3): kontrolki i scrollbary systemowe */

  /* Kolory — plac budowy: ciepły grafit + żółty bezpieczeństwa */
  --color-bg:            #171A1D;     /* kanon manifestu */
  --color-surface:       #24292E;     /* kanon manifestu */
  --color-text:          #F5F7F8;     /* 16,26:1 na bg */
  --color-text-muted:    #B4BDC6;     /* 9,18:1 na bg — ⛔ nic ciemniejszego (słońce) */
  --color-accent:        #FFC400;     /* kanon manifestu — 10,94:1 */
  --color-accent-hover:  #FFD24A;
  --color-accent-text:   #12161A;     /* atrament NA żółtym/zielonym/czerwonym — 11,38:1 */
  --color-border:        #4A535B;     /* linia siatki (NIE granica komponentu) */
  --color-border-strong: #7A848E;     /* granica komponentu, 3,85:1 (WCAG 1.4.11) */
  --color-success:       #46C37B;     /* kanon manifestu — TYLKO potwierdzenia (R-2) */
  --color-warning:       #FF8C1A;     /* ostrzeżenie z zadaniem do wykonania */
  --color-error:         #FF6B5E;     /* TYLKO blokada/błąd/sankcja (R-1); #C02626 = 2,95:1 FAIL */
  --color-disabled-text: #8B949E;     /* 4,77:1 na surface — korekta D (⛔ zamiast opacity) */

  /* Typografia — Archivo (self-host, wagi 400–800; ⛔ bez osi opsz — L-069) */
  --font-head: "Archivo", "Bahnschrift", "Segoe UI Variable Text", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-body: "Archivo", "Bahnschrift", "Segoe UI Variable Text", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Geometria — 6 px, mocne obrysy, krótkie cienie „jak na tablicach technicznych" */
  --radius: 6px;
  --radius-dot: 2px;                  /* kropka statusu = kwadrat 8×8, ⛔ nigdy 999px */
  --border-w: 1px;
  --border-w-strong: 2px;             /* obrys komponentu (karta, .input, .btn-ghost, plakietka) */
  --shadow-sm: 0 2px 0 rgba(0, 0, 0, .55);   /* twardy, BEZ rozmycia */
  --shadow-md: 0 4px 0 rgba(0, 0, 0, .55);

  /* Spacing scale */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;

  --maxw: 1080px;
  --maxw-op: 560px;                   /* ekran operatora: jedna kolumna, jedna decyzja */

  /* Dotyk (UX-BENCHMARK §5: rękawica = efektywny promień 20–25 mm) */
  --tap-min: 44px;                    /* podłoga dla WSZYSTKIEGO */
  --tap-key: 56px;                    /* decyzje operatora, potwierdzenia, paywall */

  /* Ruch — starter używał var(--dur-base,.25s)/var(--ease-out) w .fab, ale ich NIE definiował */
  --dur-fast: .12s;
  --dur-base: .22s;
  --dur-slow: .3s;
  --ease-out: cubic-bezier(.2, .7, .3, 1);

  /* Skala z-index (S27) — JEDNA prawda o warstwach; zero magicznych liczb w komponentach.
     Kolejność rośnie z „ważnością" nakładki. Paywall MUSI być NAD overlayem gatingu
     (canceled/past_due), inaczej user nie wybierze planu (bug Fachmata). */
  --z-sticky:  50;
  --z-fab:     900;
  --z-aha:     940;
  --z-overlay: 1000;
  --z-modal:   1050;
  --z-paywall: 1100;
  --z-banner:  1150;
  --z-toast:   1200;
  --z-maintenance: 1300;  /* plansza przerwy technicznej — NAD wszystkim (B10) */
}

/* ---- Reset ---- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-bg);
  line-height: 1.55;
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--font-head); line-height: 1.2; font-weight: 700; }
h1 { font-size: clamp(2rem, 5vw, 3rem); }
h2 { font-size: clamp(1.5rem, 4vw, 2.25rem); }
h3 { font-size: 1.25rem; }
a { color: var(--color-accent); text-decoration: none; }
a:hover { text-decoration: underline; }
img { max-width: 100%; height: auto; display: block; }
ul { list-style: none; }

/* ---- Layout helpers ---- */
.container { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--sp-4); }
.section { padding: var(--sp-8) 0; }
/* Kotwice (#cennik itp.) nie chowają nagłówka pod sticky-headerem/banerem przy skoku.
   Domyślnie 0px (topnav NIE jest sticky → brak sztucznej luki). Apka, która USTAWI sticky
   nawigację landingu, definiuje --sticky-offset (= wysokość paska) na :root i kotwice
   automatycznie dostają oddech. Wzorzec: dobrywstep .lp-section[id] scroll-margin-top:96px. */
.section[id] { scroll-margin-top: var(--sticky-offset, 0px); }
.stack > * + * { margin-top: var(--sp-4); }
.center { text-align: center; }
.muted { color: var(--color-text-muted); }
.hidden { display: none !important; }

/* ---- Button ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  font-family: var(--font-body); font-size: 1rem; font-weight: 600;
  padding: var(--sp-3) var(--sp-5);
  border: var(--border-w) solid transparent; border-radius: var(--radius);
  cursor: pointer; text-decoration: none; transition: background .15s, border-color .15s, opacity .15s;
  background: var(--color-surface); color: var(--color-text);
}
.btn:hover { text-decoration: none; }
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); }
.btn-primary:hover { background: var(--color-accent-hover); }
.btn-ghost { background: transparent; border-color: var(--color-border); }
.btn-ghost:hover { background: var(--color-surface); }
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn-block { width: 100%; }

/* ---- Card ---- */
.card {
  background: var(--color-surface);
  border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--sp-5);
  box-shadow: var(--shadow-sm);
}

/* ---- Input / form ---- */
.field { display: block; margin-bottom: var(--sp-4); }
.field label { display: block; font-size: .875rem; font-weight: 600; margin-bottom: var(--sp-2); }
.input {
  width: 100%; font-family: var(--font-body); font-size: 1rem;
  padding: var(--sp-3); color: var(--color-text); background: var(--color-bg);
  border: var(--border-w) solid var(--color-border); border-radius: var(--radius);
}
.input:focus { outline: 2px solid var(--color-accent); outline-offset: 1px; }

/* ---- Feedback message (auth/errors) ---- */
.msg { padding: var(--sp-3); border-radius: var(--radius); font-size: .9rem; }
.msg-error   { background: #fdecec; color: var(--color-error); }
.msg-success { background: #e8f6ee; color: var(--color-success); }

/* ---- Badge (lifecycle status) ---- */
.badge {
  display: inline-block; font-size: .75rem; font-weight: 600;
  padding: 2px var(--sp-2); border-radius: 999px; border: var(--border-w) solid var(--color-border);
}
/* Warianty statusu (np. dostarczalność maili w Wiadomościach): OK / oczekujący / błąd. */
.badge-ok     { border-color: var(--color-success); color: var(--color-success); }
.badge-warn   { border-color: var(--color-warning); color: var(--color-warning); }
.badge-danger { border-color: var(--color-error);   color: var(--color-error); }

/* ---- Top nav (app/admin) ---- */
.topnav {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--border-w) solid var(--color-border);
  background: var(--color-bg);
}
.topnav .brand { font-weight: 700; font-family: var(--font-head); }
.topnav nav { display: flex; gap: var(--sp-4); align-items: center; }

/* Stopka nawigacyjna: linki tekstowe muszą mieć cel dotykowy ≥44px (WCAG 2.5.8 wymaga ≥24px;
   44px = komfort). inline-flex, by separatory „·" zostały w tekście, a tap-area linku objęła
   cały jego wiersz bez rozpychania układu w poziomie. Klasa `foot-nav` na <footer>. */
.foot-nav a { display: inline-flex; align-items: center; min-height: 44px; }
/* Wąskie telefony (≤560px): nagłówek nie może rozpychać strony (poziomy scroll całej appki — lekcja S4a).
   Zawijamy pasek i skracamy e-mail usera z wielokropkiem zamiast wypychać „Wyloguj" poza ekran. */
@media (max-width: 560px) {
  .topnav { flex-wrap: wrap; row-gap: var(--sp-2); }
  .topnav nav { flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); min-width: 0; justify-content: flex-end; }
  .topnav nav #user-name { max-width: 42vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* ---- Empty state ---- */
.empty {
  text-align: center; padding: var(--sp-8) var(--sp-4);
  border: var(--border-w) dashed var(--color-border); border-radius: var(--radius);
  color: var(--color-text-muted);
}

/* ---- Table (admin) ---- */
/* Afordancja przewijania poziomego (mobile): subtelny cień na krawędzi, gdy tabela ma więcej
   kolumn w poziomie niż widać (np. Historia wiadomości — kolumna Status po prawej). Cień znika po
   dosunięciu do końca dzięki background-attachment: local. Baza = --color-surface (białe karty,
   motyw light-only — bez wariantu dark). Wszystkie .table-wrap to białe .card panelu admina.
   Backport S-W1 Dobry Wstęp. */
.table-wrap {
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--color-surface) 30%, transparent) 0 0 / 28px 100% no-repeat local,
    linear-gradient(to left, var(--color-surface) 30%, transparent) 100% 0 / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(31, 25, 15, 0.10), transparent) 0 0 / 12px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(31, 25, 15, 0.10), transparent) 100% 0 / 12px 100% no-repeat scroll,
    var(--color-surface);
}
table { width: 100%; border-collapse: collapse; font-size: .9rem; }
th, td { text-align: left; padding: var(--sp-3); border-bottom: var(--border-w) solid var(--color-border); }
th { font-weight: 600; color: var(--color-text-muted); }

/* ---- Grid ---- */
.grid { display: grid; gap: var(--sp-4); }
@media (min-width: 720px) {
  .grid-2 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(3, 1fr); }
  .grid-4 { grid-template-columns: repeat(4, 1fr); }
}

/* ---- Separator „albo" (OAuth ↔ formularz e-mail) ---- */
.or-sep { display: flex; align-items: center; gap: var(--sp-3); margin: 0 0 var(--sp-4); color: var(--color-text-muted); font-size: .8rem; }
.or-sep::before, .or-sep::after { content: ""; flex: 1; height: 1px; background: var(--color-border); }

/* ---- Baner trybu podglądu operatora (impersonation) — TRWAŁY, u góry, czerwony ---- */
.impersonation-banner {
  position: sticky; top: 0; z-index: var(--z-banner);
  background: var(--color-error); color: #fff;
  padding: var(--sp-2) var(--sp-4); text-align: center;
  font-size: .85rem; font-weight: 600;
}

/* ---- TRYB PRZERWY TECHNICZNEJ (maintenance mode, B10) — plansza + baner (maintenance.js) ----
   Plansza (mode 'app'): pełny ekran NAD wszystkim, user nie działa. Baner (mode 'admin'): sticky,
   panel operatora działa dalej. Landing (index.html) świadomie bez bramki. Wszystko na tokenach DS. */
.maintenance-locked { overflow: hidden; }
.maintenance-plane {
  position: fixed; inset: 0; z-index: var(--z-maintenance);
  display: flex; align-items: center; justify-content: center; padding: var(--sp-5);
  background: var(--color-bg);
}
.maintenance-box {
  max-width: 420px; width: 100%; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-4);
}
.maintenance-box .maintenance-brand { transform: scale(1.1); margin-bottom: var(--sp-2); }
.maintenance-ico { width: 44px; height: 44px; color: var(--color-accent); }
.maintenance-title { font-size: 1.5rem; }
.maintenance-sub { font-size: 1rem; margin: 0; }

.maintenance-banner {
  position: sticky; top: 0; z-index: var(--z-banner);
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3); flex-wrap: wrap;
  background: var(--color-warning, #C08A16); color: #fff;
  padding: var(--sp-2) var(--sp-4); text-align: center;
  font-size: .85rem; font-weight: 600;
}
.maintenance-banner-link { color: #fff; text-decoration: underline; white-space: nowrap; }

/* ---- Zgoda (checkbox z linkami przy rejestracji) ---- */
.check-line { display: flex; gap: var(--sp-2); align-items: flex-start; font-size: .9rem; margin-bottom: var(--sp-4); }
.check-line input { margin-top: 3px; }

/* ---- Onboarding: wiersz kroku „Pierwsze kroki" (stack na wąskich telefonach) ----
   Forward-compatible (S27): gdy apka zbuduje POZIOMY wiersz kroku (.onb-step z przyciskiem .btn),
   poniżej 460px przycisk schodzi pod treść na całą szerokość zamiast się ściskać.
   Starter renderuje kroki jako listę .check-line (już pionowo) — reguła aktywuje się dopiero
   gdy apka doda .onb-step (jak Fachmat). Zero efektu dla obecnego #onboarding-card. */
@media (max-width: 460px) {
  .onb-step { flex-wrap: wrap; }
  .onb-step .btn { flex-basis: 100%; width: 100%; margin-top: var(--sp-2); }
}

/* ---- Checklist „Pierwsze kroki" — @dsChecklist (STANDARD FABRYKI) ----------
   Renderowana przez js/onboard.js (initChecklist). Pasek postępu x/N, 1. krok
   PRE-CHECKED (endowed progress), odhaczanie po evencie/akcji, celebracja
   papierowa (@dsCelebrate), znika po ukończeniu. Kroki = KONFIGURACJA per apka
   (ONBOARDING_STEPS). Klasa .ds-checklist na kontenerze karty. ------------- */
.ds-checklist { padding: var(--sp-5); }
/* Nagłówek oczekiwań (§1.3) — ZAWSZE nad checklistą, niezależny od survey: jedno zdanie wartości + czas. */
.ds-onb-welcome { font-size: .95rem; color: var(--color-text); margin: 0 0 var(--sp-4); line-height: 1.5; }
.ds-onb-welcome-time { color: var(--color-text-muted); font-weight: 600; white-space: nowrap; }
.ds-checklist-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.ds-checklist-head h3 { margin: 0; }
.ds-checklist-count {
  font-size: .8rem; font-weight: 700; color: var(--color-text-muted);
  background: var(--color-bg); border: var(--border-w) solid var(--color-border);
  border-radius: 999px; padding: 2px var(--sp-2);
}
.ds-checklist-sub { font-size: .9rem; margin: var(--sp-2) 0 var(--sp-3); }

/* Pasek postępu — @dsProgress. Track + fill (szerokość ustawia JS w %). */
.ds-progress { height: 8px; border-radius: 999px; background: var(--color-border); overflow: hidden; }
.ds-progress-fill {
  height: 100%; border-radius: 999px; background: var(--color-accent);
  transition: width .35s cubic-bezier(.4, 0, .2, 1); min-width: 6px;
}

.ds-checklist-list { list-style: none; margin: var(--sp-4) 0 0; padding: 0; }
.ds-checklist-item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) 0; border-top: var(--border-w) solid var(--color-border);
  font-size: .95rem;
}
.ds-checklist-item:first-child { border-top: 0; }
.ds-check-ico {
  flex: none; width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: var(--border-w) solid var(--color-border); background: var(--color-bg);
  color: var(--color-accent-text); font-size: .8rem; font-weight: 700;
}
.ds-checklist-item.is-done .ds-check-ico { background: var(--color-success); border-color: transparent; }
.ds-check-label { flex: 1 1 auto; min-width: 0; }
.ds-checklist-item.is-done .ds-check-label { color: var(--color-text-muted); text-decoration: line-through; }
.ds-check-cta { flex: none; margin-left: auto; }
.ds-check-box { flex: none; margin-left: auto; width: 18px; height: 18px; }
/* Krok przyszły — wskaźnik nieaktywny (nie przycisk): „jeden oczywisty następny ruch" ma TYLKO pierwszy
   nieukończony krok (§1.4). Wyszarzony, bez interakcji. Tokeny DS (spójny z @dsChecklist), nie hardkod. */
.ds-check-cta-later {
  flex: none; margin-left: auto; display: inline-flex; align-items: center; justify-content: center;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--radius);
  font-size: .85rem; font-weight: 600; color: var(--color-text-muted);
  background: var(--color-border); cursor: default; pointer-events: none; user-select: none;
}

/* Krok „awaiting" (§6 pkt 5) — user zrobił swoją część, wartość domknie ŚWIAT (event serwerowy). Stan
   „✓ Zrobione po Twojej stronie — czekamy na X" ma NIE wyglądać jak porażka: ikona = subtelny check (nie
   sukces-zielony, bo krok jeszcze trwa), status wyszarzony po prawej. Odhaczenie robi realny event. */
.ds-checklist-item.is-await .ds-check-ico { background: var(--color-surface); border-color: var(--color-border); color: var(--color-text-muted); }
.ds-checklist-item.is-await .ds-check-ico::after { content: "\2713"; } /* ✓ — Twoja część zrobiona */
.ds-check-await {
  flex: 1 1 auto; margin-left: auto; text-align: right;
  font-size: .85rem; color: var(--color-text-muted); font-style: italic; line-height: 1.35;
}

/* Celebracja papierowa — @dsCelebrate. Lekki stempel „Gotowe!" (NIE gadżet). */
.ds-celebrate { text-align: center; padding: var(--sp-6) var(--sp-4); }
.ds-celebrate-stamp {
  display: inline-block; font-family: var(--font-head); font-weight: 800;
  font-size: 1.35rem; letter-spacing: .04em; text-transform: uppercase;
  color: var(--color-success); border: 2.5px solid var(--color-success);
  border-radius: var(--radius); padding: var(--sp-2) var(--sp-4);
  transform: rotate(-4deg); opacity: .92;
  animation: ds-stamp-in .32s cubic-bezier(.34, 1.56, .64, 1) both;
}
.ds-celebrate-msg { margin: var(--sp-4) 0 0; color: var(--color-text-muted); font-size: .95rem; }
@keyframes ds-stamp-in { from { transform: rotate(-4deg) scale(1.35); opacity: 0; } to { transform: rotate(-4deg) scale(1); opacity: .92; } }
@media (prefers-reduced-motion: reduce) { .ds-celebrate-stamp { animation: none; } .ds-progress-fill { transition: none; } }

@media (max-width: 460px) {
  .ds-checklist-item { flex-wrap: wrap; }
  .ds-check-cta, .ds-check-cta-later, .ds-check-box { margin-left: calc(24px + var(--sp-3)); }
  .ds-check-await { margin-left: calc(24px + var(--sp-3)); text-align: left; flex-basis: 100%; }
}

/* ---- Empty-state uczący — @dsEmptyOnboard (helper emptyOnboard() w onboard.js) ----
   Rozszerza .empty: (a) nazwa WARTOŚCI, (b) ghost-row/preview (aria-hidden),
   (c) 1 CTA czasownik, (d) furtka (dane przykładowe/import). ZERO przeprosin. */
.ds-empty-onb { text-align: center; }
.ds-empty-onb-value { margin: 0; color: var(--color-text); }
.ds-empty-onb-hint { margin: var(--sp-2) auto 0; max-width: 46ch; }
.ds-empty-onb-cta { margin-top: var(--sp-5); display: flex; gap: var(--sp-3); justify-content: center; flex-wrap: wrap; }
/* Ghost-row: kształt sukcesu, wyszarzony (to atrapa, nie dane → aria-hidden w JS). */
.ds-ghost { max-width: 340px; margin: 0 auto var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-2); }
.ds-ghost-row {
  height: 14px; border-radius: 6px;
  background: linear-gradient(90deg, var(--color-border) 0%, var(--color-surface) 50%, var(--color-border) 100%);
  opacity: .6;
}
.ds-ghost-row:nth-child(2) { width: 86%; }
.ds-ghost-row:nth-child(3) { width: 68%; }

/* ---- Welcome survey (JTBD) — @dsSurvey. Modal 1-3 pytania (initWelcomeSurvey). ---- */
.ds-survey { max-width: 460px; }
.ds-survey-q { margin-top: var(--sp-4); }
.ds-survey-qlabel { font-weight: 600; margin: 0 0 var(--sp-2); font-size: .95rem; }
.ds-survey-opts { display: flex; flex-direction: column; gap: var(--sp-2); }
.ds-survey-opt { justify-content: flex-start; text-align: left; }
.ds-survey-opt.is-selected { border-color: var(--color-accent); box-shadow: 0 0 0 2px rgba(37, 99, 235, .18); }
.ds-survey-actions { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-top: var(--sp-5); }

/* ---- Lejek onboardingu (panel operatora, Aktywacja) — paski per krok + drop-off ---- */
.onb-funnel-row { padding: var(--sp-3) 0; border-top: var(--border-w) solid var(--color-border); }
.onb-funnel-row:first-child { border-top: 0; }
.onb-funnel-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); font-size: .9rem; margin-bottom: var(--sp-2); }
.onb-funnel-bar { height: 10px; border-radius: 999px; background: var(--color-border); overflow: hidden; }
.onb-funnel-fill { height: 100%; border-radius: 999px; background: var(--color-accent); }
.onb-funnel-row.is-bottleneck .onb-funnel-fill { background: var(--color-warning); }
.onb-funnel-row.is-bottleneck .onb-funnel-head { font-weight: 600; }

/* ---- Kontekstowy tooltip — @dsTooltip (showTip). 1 naraz, action-triggered (2-5 s). ---- */
.ds-tip {
  position: fixed; z-index: var(--z-banner); max-width: 260px;
  display: flex; align-items: flex-start; gap: var(--sp-2);
  background: var(--color-text); color: var(--color-bg);
  border-radius: var(--radius); padding: var(--sp-2) var(--sp-3);
  font-size: .85rem; line-height: 1.4; box-shadow: 0 6px 20px rgba(10, 12, 16, .22);
  animation: ds-tip-in .18s ease-out both;
}
.ds-tip-close { flex: none; background: transparent; border: 0; color: inherit; opacity: .7; cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 0; }
.ds-tip-close:hover { opacity: 1; }
@keyframes ds-tip-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .ds-tip { animation: none; } }

/* ---- Pas zaufania — @dsTrustBar (trustBar() w onboard.js) — §7 „pas zaufania" (lęk#1=scam) ----
   Stały, widoczny sygnał legitymacji na ekranie onboardingu: kto stoi za produktem / co z danymi (link
   trust page) / 1 dowód / 1 żywy kanał kontaktu. Papierowy motyw, tokeny DS. Zawija się na wąskich. */
.ds-trustbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-4);
  background: var(--color-surface); border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius); padding: var(--sp-3) var(--sp-4);
  font-size: .85rem; color: var(--color-text-muted);
}
.ds-trust-item { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.ds-trust-ico { flex: none; font-size: .95rem; line-height: 1; color: var(--color-accent); }
.ds-trust-text { color: var(--color-text); }
.ds-trust-link {
  display: inline-flex; align-items: center; min-height: 24px; /* touch-target ≥24px (WCAG 2.5.8) */
  color: var(--color-accent); font-weight: 600;
}
.ds-trust-link:hover { text-decoration: underline; }

/* ---- Pomiar jakościowy drop-offu — @dsFriction (askFriction() w onboard.js) — §7 ----
   Mikro-prompt „Co Cię zatrzymało?" (3-4 powody 1-tap + opcjonalne pole). Lekki, papierowy, pomijalny.
   Wariant pływający (.ds-friction-floating) = karta u dołu ekranu; wariant osadzony (opts.mount) = w karcie. */
.ds-friction {
  background: var(--color-bg); border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius); padding: var(--sp-4); box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: var(--sp-3);
}
.ds-friction-floating {
  position: fixed; right: var(--sp-4); bottom: var(--sp-4); z-index: var(--z-banner);
  width: min(360px, calc(100vw - 2 * var(--sp-4)));
  animation: ds-friction-in .18s ease-out both;
}
.ds-friction-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); }
.ds-friction-title { margin: 0; font-weight: 700; font-size: .95rem; color: var(--color-text); }
.ds-friction-close {
  flex: none; background: transparent; border: 0; color: var(--color-text-muted); cursor: pointer;
  font-size: 1.2rem; line-height: 1; padding: 0;
  min-width: 24px; min-height: 24px; display: inline-flex; align-items: center; justify-content: center;
}
.ds-friction-close:hover { color: var(--color-text); }
.ds-friction-opts { display: flex; flex-direction: column; gap: var(--sp-2); }
.ds-friction-opt { justify-content: flex-start; text-align: left; min-height: 24px; }
.ds-friction-note { resize: vertical; font-size: .9rem; }
@keyframes ds-friction-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .ds-friction-floating { animation: none; } }

/* ---- A11y baseline onboardingu — WCAG 2.2 AA (§7 ONBOARDING-FABRYKA) ---------------------
   Prawnie wymagane w UE od 28.06.2025 (EAA/WCAG 2.2 AA). Widoczny, NIE-zasłonięty focus na
   WSZYSTKICH interaktywnych elementach onboardingu (checklist CTA, survey, empty-state CTA,
   tooltip) + touch-target ≥24×24 px (WCAG 2.5.8 Target Size). Tokeny DS, jasny motyw;
   prefers-reduced-motion respektowany wyżej (animacje @dsCelebrate/@dsTooltip). ----------- */
/* SR-only — status postępu (aria-live) ogłaszany czytnikowi bez zmiany layoutu (onboard.js renderChecklist). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* Widoczny focus-visible — część elementów onboardingu (checkbox, tip-close) nie dziedziczy stylu .btn. */
.ds-check-cta:focus-visible,
.ds-check-box:focus-visible,
.ds-survey-opt:focus-visible,
.ds-survey-actions .btn:focus-visible,
.ds-empty-onb-cta .btn:focus-visible,
.ds-empty-onb-escape:focus-visible,
.ds-tip-close:focus-visible,
.ds-trust-link:focus-visible,
.ds-friction-opt:focus-visible,
.ds-friction-close:focus-visible {
  outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: var(--radius);
}
/* Touch-target ≥24×24 px (WCAG 2.5.8) na interaktywnych elementach onboardingu. */
.ds-check-cta, .ds-survey-opt, .ds-empty-onb-cta .btn, .ds-empty-onb-escape { min-height: 24px; }
.ds-check-box { min-width: 24px; min-height: 24px; }
.ds-tip-close { min-width: 24px; min-height: 24px; display: inline-flex; align-items: center; justify-content: center; }

/* ---- Przycisk destrukcyjny + strefa czerwona (usunięcie konta) ---- */
.btn-danger { background: var(--color-error); color: #fff; }
.btn-danger:hover { background: #a01f1f; }
.danger-zone { border-color: var(--color-error); }

/* ---- Overlay pełnoekranowy (subscription gating: past_due / canceled) ----
   UWAGA: to warstwa UX. Twardy gate = RLS/edge (patrz app.js requireActiveSub). */
.overlay {
  position: fixed; inset: 0; z-index: var(--z-overlay);
  display: flex; align-items: center; justify-content: center; padding: var(--sp-4);
  background: rgba(10, 12, 16, .72); backdrop-filter: blur(2px);
}
.overlay-box { max-width: 440px; text-align: center; background: var(--color-bg); }

/* ---- Pływający przycisk „Zgłoś uwagę" (feedback widget) ----
   Desktop (≥1024px): kompaktowa pigułka-ikona (52×52) bez etykiety — nie zasłania treści.
   Mobile: pełna pigułka z ikoną + etykietą (czytelny cel dotyku). */
.fab {
  position: fixed; right: var(--sp-4); bottom: var(--sp-4); z-index: var(--z-fab);
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5); border-radius: 999px; border: none;
  background: var(--color-accent); color: var(--color-accent-text);
  font-weight: 600; cursor: pointer; box-shadow: var(--shadow-md);
  transition: transform var(--dur-base, .25s) var(--ease-out, ease), opacity var(--dur-base, .25s) ease;
}
.fab:hover { background: var(--color-accent-hover); }
/* KLASA BŁĘDU „FAB zakrywa wartości/treść" (backport DW S4b/S4d/S4e — 3× kolizja FAB↔treść w jednej apce)
   → jedna GLOBALNA reguła zamiast łatania per-ekran: pływający FAB CHOWA SIĘ przy przewijaniu w dół
   (czytanie) i wraca przy przewijaniu w górę / u góry (js: toggle .fab-tucked wg kierunku scrolla —
   app.js initFabAutoHide). Dzięki temu FAB NIGDY nie „siedzi" nad treścią, którą user właśnie czyta —
   działa DOMYŚLNIE na KAŻDYM ekranie każdej apki. reduced-motion respektowane. */
.fab.fab-tucked { transform: translateY(160%); opacity: 0; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .fab { transition: opacity .01ms; } }
.fab .fab-ico { flex: none; }
@media (min-width: 1024px) {
  .fab { width: 52px; height: 52px; padding: 0; justify-content: center; }
  .fab .fab-label { display: none; }
}

/* ---- Modal (feedback) ---- */
.modal-backdrop {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: flex; align-items: center; justify-content: center; padding: var(--sp-4);
  background: rgba(10, 12, 16, .55);
}
/* Paywall = modal wyboru planu OTWIERANY Z overlaya gatingu → MUSI być nad overlayem
   (canceled/past_due), inaczej plany są zasłonięte i user nie wznowi subskrypcji. */
.modal-backdrop.paywall { z-index: var(--z-paywall); }
.modal { width: 100%; max-width: 480px; background: var(--color-bg); }
.modal textarea.input { resize: vertical; }

/* ============================================================
   SHELL nawigacji w tabach — panel operatora (admin) + wejście operatora (app).
   Standard fabryki: kanoniczny topnav z zakładkami + hash-routing + menu mobilne.
   Reużywany przez admin.html (6 zakładek) i app.html (link „Panel operatora").
   Wszystko na tokenach; #fff = biel na grafitowym tle (jak reszta pliku, brak tokenu bieli).
   ============================================================ */

/* ---- Warianty przycisków używane w topnav ---- */
.btn-sm { padding: var(--sp-2) var(--sp-4); font-size: .85rem; }
.btn-neutral { background: var(--color-text); color: #fff; border-color: var(--color-text); }
.btn-neutral:hover { background: var(--color-text); opacity: .88; }

/* ---- Nagłówek widoku (tytuł zakładki) ---- */
.page-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); margin-bottom: var(--sp-4); flex-wrap: wrap; }

/* ---- Plakietka „PANEL OPERATORA" (odróżnienie panelu od aplikacji) ---- */
.plaq {
  display: inline-flex; align-items: center; gap: var(--sp-2); flex: none;
  background: var(--color-text); color: #fff; border-radius: var(--radius);
  padding: var(--sp-1) var(--sp-3);
  font-weight: 700; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase;
}
.plaq-op::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--color-accent); flex: none; }

/* ---- Zakładki topnav (hash-routing; aktywna = podkreślenie akcentu) ---- */
.navtabs { display: flex; gap: var(--sp-4); align-items: center; flex: 1; justify-content: center; }
.navtab {
  font-weight: 600; font-size: .9rem; color: var(--color-text-muted);
  padding: var(--sp-2) 0; border-bottom: 2px solid transparent; text-decoration: none;
  transition: color .2s, border-color .2s;
}
.navtab:hover { color: var(--color-text); text-decoration: none; }
.navtab-active { color: var(--color-text); border-bottom-color: var(--color-accent); }
/* Link „Panel operatora" w app (widoczny tylko dla operatora) — wyróżniony obrys-pigułka */
.navtab-op {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1.5px solid var(--color-text); border-radius: 999px;
  padding: 5px var(--sp-3); color: var(--color-text);
  transition: background .15s, color .15s;
}
.navtab-op:hover { background: var(--color-text); color: #fff; text-decoration: none; }
.navtab-op svg { flex: none; }

/* ---- CTA topnav (← Aplikacja) + przycisk MENU (mobile) ---- */
.nav-cta { flex: none; }
.nav-menu-mobile { display: none; flex: none; }

/* ---- Rozwijane menu (mobile ≤860px) — lista zakładek pod topnavem (vanilla) ---- */
.nav-menu-panel {
  display: flex; flex-direction: column; gap: 2px;
  background: var(--color-surface); border-bottom: var(--border-w) solid var(--color-border);
  box-shadow: var(--shadow-sm); padding: var(--sp-2);
}
.nav-menu-panel a {
  display: block; text-align: left; font-weight: 600; font-size: .95rem; color: var(--color-text);
  padding: var(--sp-3) var(--sp-4); border-radius: var(--radius); text-decoration: none;
}
.nav-menu-panel a:hover { background: var(--color-bg); text-decoration: none; }
.nav-menu-panel a.navtab-active { color: var(--color-accent); }
.nav-menu-panel .nav-menu-op, .nav-menu-panel .nav-cta-m { color: var(--color-accent); }
.nav-menu-head { display: block; font-size: .7rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--color-text-muted); padding: var(--sp-2) var(--sp-4) var(--sp-1); }

/* ---- Pasek konta operatora (Pulpit): kto zalogowany + Wyloguj ---- */
.admin-userbar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-5); }

/* ---- Widok routera (pokazywanie/ukrywanie przez .hidden) ---- */
.view { animation: fade-in .18s ease-out; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ---- Fix overflow poziomy tabel na mobile ----
   Karty gridu (grid-3/grid-4) mają domyślnie min-width:auto → nie schodzą poniżej
   min-content i rozpychają viewport. Zerujemy min-width, a szerokie tabele scrollują
   REALNIE wewnątrz .table-wrap (overflow-x:auto) zamiast rozciągać stronę. */
.grid > * { min-width: 0; }
[data-view] .table-wrap { max-width: 100%; }

/* ---- Mobile ≤860px: topnav = brand + MENU; zakładki/CTA/plakietkę chowamy ----
   Breakpoint 860 (nie 720): desktopowy topnav (zakładki + CTA + plakietka) nie mieści się
   poniżej ~860px → w strefie 721–860px rozpychał viewport i dawał poziomy scroll (S27). */
@media (max-width: 860px) {
  .topnav .navtabs, .topnav .nav-cta, .topnav .plaq-op { display: none; }
  .nav-menu-mobile { display: inline-flex; }
}

/* ============================================================
   MODUŁ WIADOMOŚCI (panel operatora — tab „Wiadomości"): pod-zakładki, edytor szablonów,
   podgląd renderu w iframe. Wszystko na tokenach. */
.subtabs { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-5);
  border-bottom: var(--border-w) solid var(--color-border); }
.subtab {
  background: transparent; border: none; border-bottom: 2px solid transparent;
  padding: var(--sp-3) var(--sp-3); font-family: var(--font-body); font-size: .9rem; font-weight: 600;
  color: var(--color-text-muted); cursor: pointer; transition: color .15s, border-color .15s;
}
.subtab:hover { color: var(--color-text); }
.subtab-active { color: var(--color-text); border-bottom-color: var(--color-accent); }

/* Szablony: lista + edytor obok siebie (na wąskich = jedna kolumna). */
.msg-tpl-grid { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; }
@media (min-width: 900px) { .msg-tpl-grid { grid-template-columns: 260px 1fr; align-items: start; } }
.msg-tpl-editor { display: block; }
#tpl-list li { list-style: none; }
.tpl-item {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  width: 100%; text-align: left; background: transparent; border: var(--border-w) solid transparent;
  border-radius: var(--radius); padding: var(--sp-2) var(--sp-3); cursor: pointer;
  font-family: var(--font-body); font-size: .9rem; color: var(--color-text);
}
.tpl-item:hover { background: var(--color-bg); }
.tpl-item-active { background: var(--color-bg); border-color: var(--color-border); font-weight: 600; }
.tpl-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--color-text-muted); }
.tpl-dot.on { background: var(--color-success); }
.tpl-dot.off { background: var(--color-warning); }

/* Podgląd renderu w izolowanym iframe (sandbox="" — brak skryptów, XSS-safe). */
.msg-preview-wrap { margin-top: var(--sp-4); }
.msg-preview {
  width: 100%; height: 420px; border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius); background: #f4f5f7;
}

/* ============================================================
   MODUŁ POLECENIA — prompt aha (pasek), licznik, panel operatora. Wszystko na tokenach. */
.aha-ref {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  background: var(--color-surface); border: var(--border-w) solid var(--color-border);
  border-left: 3px solid var(--color-accent); border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-5); box-shadow: var(--shadow-sm);
}
.aha-ref #aha-referral-text { flex: 1 1 220px; font-size: .95rem; color: var(--color-text); }
.aha-ref-close {
  background: transparent; border: none; color: var(--color-text-muted); cursor: pointer;
  font-size: 1.3rem; line-height: 1; padding: 0 var(--sp-1);
}
.aha-ref-close:hover { color: var(--color-text); }
.ref-stats { color: var(--color-text-muted); }
.ref-stats b { color: var(--color-text); }
.ref-stat-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 720px) { .ref-stat-grid { grid-template-columns: repeat(5, 1fr); } }
.ref-stat { background: var(--color-surface); border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius); padding: var(--sp-3) var(--sp-4); }
.ref-stat .n { font-family: var(--font-head); font-size: 1.5rem; color: var(--color-text); }
.ref-stat .l { font-size: .8rem; color: var(--color-text-muted); }
.ref-chip { display: inline-block; padding: 2px var(--sp-2); border-radius: 999px;
  font-size: .78rem; font-weight: 600; border: var(--border-w) solid var(--color-border); }
.ref-chip.invited  { color: var(--color-text-muted); }
.ref-chip.paid     { color: var(--color-warning); border-color: var(--color-warning); }
.ref-chip.rewarded { color: var(--color-success); border-color: var(--color-success); }
.ref-chip.refunded, .ref-chip.rejected { color: var(--color-error); border-color: var(--color-error); }

/* ---- Kreator etapowy — @dsWizard (STANDARD FABRYKI) -------------------------
   Formularz RDZENIA (>5–6 pól) rozbij na kroki „jedna myśl / jedno pytanie na
   ekran" — nie ściana pól. Ta sama zasada „jedna myśl" co landing, dla WEJŚCIA
   danych. Składniki: pasek postępu (żetony + etykiety, klik w osiągnięty krok =
   skok wstecz) + kroki (.wiz-step, przełączane .hidden) + nawigacja (.wiz-nav:
   Wstecz/Dalej + submit na ostatnim) + walidacja PER KROK (.field-err pod polem)
   + autozapis szkicu przy „Dalej" (logika w js/app.js — upsert draft). Aktywny
   krok = akcent, ukończony = sukces (oliwka) z checkiem. Mobile: jedno pytanie
   na ekran, etykiety pasku chowane, nawigacja pełnej szerokości.
   GOTCHA: przełączaj przyciski nawigacji klasą .hidden (display:none !important),
   NIE atrybutem [hidden] — .btn{display:...} (specyficzność klasy) bije UA
   [hidden]{display:none}, więc [hidden] na .btn NIE ukrywa (oba CTA naraz). ---- */
.wizard-steps { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: nowrap; margin-bottom: var(--sp-5); }
.wizard-step {
  display: inline-flex; align-items: center; gap: var(--sp-2); flex: 0 1 auto; min-width: 0;
  background: transparent; border: 0; padding: 2px; margin: -2px; border-radius: var(--radius);
  font-family: var(--font-body); font-size: .875rem; font-weight: 600;
  color: var(--color-text-muted); cursor: default; text-align: left;
}
.wizard-step:not(:disabled) { cursor: pointer; }
.wizard-step .wiz-dot {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--color-surface); color: var(--color-text-muted);
  font-weight: 700; font-size: .875rem; line-height: 1; border: var(--border-w) solid var(--color-border);
}
.wizard-step .wiz-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wizard-step.is-active { color: var(--color-text); }
.wizard-step.is-active .wiz-dot { background: var(--color-accent); color: var(--color-accent-text); border-color: transparent; }
.wizard-step.is-done { color: var(--color-success); }
.wizard-step.is-done .wiz-dot { background: var(--color-success); color: #fff; border-color: transparent; }
.wizard-step:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.wizard-sep { flex: 1 1 auto; height: 2px; min-width: 10px; background: var(--color-border); border-radius: 2px; }

.wiz-q { font-family: var(--font-head); font-size: 1.5rem; line-height: 1.15; color: var(--color-text); margin: 0; }
.wiz-q-sub { color: var(--color-text-muted); font-size: 1rem; margin: 0 0 var(--sp-2); max-width: 48ch; }

.wiz-nav { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-2); }
.wiz-nav .wiz-spacer { flex: 1 1 auto; }
.wiz-draft-row { text-align: center; margin: var(--sp-1) 0 0; }

/* Błąd walidacji pod polem — @dsFieldError (inline, per pole). */
.field-err { display: block; font-size: .8125rem; color: var(--color-error); margin-top: var(--sp-2); font-weight: 600; }
.input.is-invalid, textarea.input.is-invalid, select.input.is-invalid {
  border-color: var(--color-error); box-shadow: 0 0 0 3px rgba(192, 38, 38, .15);
}

@media (max-width: 430px) {
  .wizard-steps { gap: var(--sp-1); }
  /* ⛔ Nie chowamy etykiety kroku AKTYWNEGO: bez słowa stan kroku niosłaby sama barwa żetonu (R-8).
     Chowają się wyłącznie kroki, które nie niosą teraz żadnej informacji. */
  .wizard-step .wiz-label { display: none; }
  .wizard-step.is-active .wiz-label { display: inline; white-space: normal; }
  /* Przyciski nawigacji JEDEN POD DRUGIM — w rękawicy sąsiadujące przyciski to przypadkowa decyzja. */
  .wiz-nav { flex-direction: column-reverse; align-items: stretch; }
  .wiz-nav .btn { flex: 1 1 auto; width: 100%; }
  .wiz-nav .wiz-spacer { display: none; }
}

/* ---- Spinner (obrotowy wskaźnik ładowania) — @dsSpinner ----
   Zależność komponentu generacji (js/generation.js) oraz dowolnej akcji „to potrwa chwilę".
   Ruch respektuje prefers-reduced-motion (zwalnia zamiast znikać — wskaźnik nadal żyje). */
.spinner {
  display: inline-block; width: 18px; height: 18px; flex: none; border-radius: 50%;
  border: 2px solid var(--color-border); border-top-color: var(--color-accent);
  animation: ds-spin .7s linear infinite;
}
@keyframes ds-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 1.6s; } }

/* ---- Generacja AI: nazwane etapy + skeleton artefaktu + panel porażki (js/generation.js) — @dsGeneration
   §4b standardu fabryki: „nagi spinner" → rosnąca checklista czynności w 1. osobie, skeleton w kształcie
   wyniku, panel „wina systemu" z retry + fallback. Wszystko na tokenach DS; ruch respektuje reduced-motion;
   jasny „papierowy" motyw (ZAKAZ ciemnych teł). APKA wypełnia etapy/estimate/skeleton w kroku onboarding. */
.gen-wrap { display: flex; flex-direction: column; gap: var(--sp-3); width: 100%; }
.gen-stages { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.gen-stage { display: flex; align-items: center; gap: var(--sp-3); font-family: var(--font-body); font-size: .9rem;
  color: var(--color-text-muted); transition: color .18s ease; }
.gen-stage.is-current { color: var(--color-text); font-weight: 600; }
.gen-stage.is-done { color: var(--color-text-muted); }
.gen-ico { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
  border-radius: 50%; background: var(--color-surface); color: var(--color-text-muted);
  font-size: .75rem; font-weight: 700; line-height: 1; }
.gen-stage.is-done .gen-ico { background: var(--color-success); color: #fff; }
.gen-stage .gen-ico .spinner { width: 13px; height: 13px; border-width: 2px; }
.gen-eta { font-size: .8rem; margin: 0; }

/* Skeleton w kształcie artefaktu — szare placeholdery z delikatnym shimmerem (APKA buduje węzły). */
.gen-skel { display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-2); opacity: .75; }
.gen-skel-row { display: flex; gap: var(--sp-3); align-items: flex-start; }
.gen-skel-num { flex: none; width: 26px; height: 26px; border-radius: 50%; }
.gen-skel-lines { flex: 1 1 auto; display: flex; flex-direction: column; gap: var(--sp-2); }
.gen-skel-bar { height: 10px; border-radius: 999px; }
.gen-skel-bar.w-40 { width: 40%; }
.gen-skel-bar.w-70 { width: 70%; }
.gen-skel-bar.w-90 { width: 90%; }
.gen-skel-num, .gen-skel-bar {
  background: linear-gradient(90deg, var(--color-border) 25%, var(--color-surface) 37%, var(--color-border) 63%);
  background-size: 400% 100%; animation: gen-shimmer 1.4s ease infinite;
}
@keyframes gen-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .gen-skel-num, .gen-skel-bar { animation: none; } }

/* Panel porażki generacji — wina po stronie SYSTEMU, retry + fallback (bez żargonu). */
.gen-fail { display: flex; flex-direction: column; gap: var(--sp-3); }
.gen-fail-msg { margin: 0; color: var(--color-text); font-size: 1rem; }
.gen-fail-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.gen-fail-note { margin: 0; font-size: .9rem; }

/* ---- Value-before-signup: podgląd wartości + bramka rejestracji — @dsDemoGate (demoAhaGate() w onboard.js).
   §7 „value-before-signup + keepable artifact": apka pokazuje PRÓBKĘ wartości PRZED rejestracją (na landingu),
   z bramką „zarejestruj, by zapisać / pełny wynik". DOMYŚLNIE off (apka włącza przez previewFn). Jasny motyw,
   tokeny DS; ruch respektuje reduced-motion. ------------------------------------------------------------------ */
.ds-demo { display: flex; flex-direction: column; gap: var(--sp-4); width: 100%; }
.ds-demo-preview { display: flex; flex-direction: column; gap: var(--sp-3); }
.ds-demo-gate {
  border: var(--border-w) solid var(--color-border); border-radius: var(--radius);
  background: var(--color-surface); padding: var(--sp-4);
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3);
  animation: ds-demo-reveal .2s ease-out both;
}
.ds-demo-gate-msg { margin: 0; color: var(--color-text); font-size: .95rem; flex: 1 1 220px; }
.ds-demo-cta { flex: none; }
@keyframes ds-demo-reveal { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .ds-demo-gate { animation: none; } }

/* ---- Agent domyka setup „za mnie": ekran akceptacji przygotowanej konfiguracji — @dsAgentSetup
   (agentSetup() w onboard.js). §7/§4b: gdy setup >2 pól, DOMYŚLNĄ ścieżką jest agent (edge AI) wypełniający
   konfigurację z inputu usera → user tylko sprawdza i AKCEPTUJE (aha = akceptacja). Faza czekania = runGeneration
   (@dsGeneration, ten sam UX). DOMYŚLNIE off. ------------------------------------------------------------------ */
.ds-agentsetup { display: flex; flex-direction: column; gap: var(--sp-4); width: 100%; }
.ds-agentsetup-intro { margin: 0; color: var(--color-text); font-size: .95rem; line-height: 1.5; }
.ds-agentsetup-fields { display: flex; flex-direction: column; gap: var(--sp-4); }
.ds-agentsetup-field { display: flex; flex-direction: column; gap: var(--sp-2); }
.ds-agentsetup-label { font-weight: 600; font-size: .9rem; color: var(--color-text); }
.ds-agentsetup-input { width: 100%; }
.ds-agentsetup-actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; }

/* A11y (§7 WCAG 2.2 AA): widoczny focus-visible + touch-target ≥24 px dla nowego UI (demo-gate / agent-setup).
   Przyciski dziedziczą .btn, ale dokładamy jawny floor (spójnie z resztą onboardingu, patrz .ds-check-cta). */
.ds-demo-cta:focus-visible,
.ds-agentsetup-accept:focus-visible,
.ds-agentsetup-input:focus-visible {
  outline: 2px solid var(--color-accent); outline-offset: 2px;
}
.ds-demo-cta, .ds-agentsetup-accept { min-height: 24px; }

/* ---- Druk (S31): przy Ctrl+P chowamy chrome aplikacji (nawigacja, akcje, warstwy nakładkowe),
   żeby na wydruku/PDF została wyłącznie treść. Uniwersalny podzbiór — TYLKO selektory, które
   starter faktycznie ma. Rozszerzaj per apka o własne elementy sticky/floating (paski sum,
   szybkie skoki, toasty), gdy je dodasz. */
@media print {
  .topnav,
  .nav-menu-panel,
  .nav-menu-mobile,
  .impersonation-banner,
  .fab,
  .aha-ref,
  .ds-tip,
  .ds-friction-floating,
  .overlay,
  .modal-backdrop,
  .og-appbar,
  .og-sticky {
    display: none !important;
  }
}

/* ============================================================================
   ███ SYSTEM DESIGNU OperatorGO — krok `design`. Kanon: brief/04-STYLEGUIDE.md.
   Podgląd: design-system/preview.html (karty @dsCard + 3 ekrany wzorcowe).
   Kolejność ma znaczenie: NADPISANIA startera muszą stać PO regułach bazowych
   (ta sama specyficzność → wygrywa późniejsza). Reguły bazowe PRZED @media (L-070b).
   ⛔ ZERO stylowania ad hoc: nowy wygląd = nowy komponent TUTAJ, nie style="…".
   ============================================================================ */

/* ---------------------------------------------------------------------------
   1. NADPISANIA STARTERA (04-STYLEGUIDE §9) — starter jest jasny (light-only);
   poniższe reguły łamałyby się na ciemnym motywie. To policzone FAIL-e, nie opinie.
   --------------------------------------------------------------------------- */

/* Skala typografii (§3) — nagłówki gęste, technicznie zwarte. */
h1 { font-size: clamp(28px, 7vw, 40px); font-weight: 800; line-height: 1.08; letter-spacing: -.02em; }
h2 { font-size: clamp(22px, 5vw, 28px); font-weight: 700; line-height: 1.15; letter-spacing: -.01em; }
h3 { font-size: 18px; font-weight: 700; line-height: 1.25; }
body { line-height: 1.5; }
small, .small { font-size: 14px; font-weight: 500; }   /* ⛔ nigdy 400 przy 14 px */

/* Przyciski: cel dotykowy, gruby napis, „wciśnięty klawisz" zamiast cienia-poduszki. */
.btn {
  min-height: var(--tap-min); font-weight: 700; letter-spacing: .01em;
  border-radius: var(--radius);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.btn:active { transform: translateY(2px); }
.btn-ghost { border-width: var(--border-w-strong); border-color: var(--color-border-strong); color: var(--color-text); }
.btn-ghost:hover { background: transparent; border-color: var(--color-text); }
/* Korekta D (§2): disabled BEZ opacity — mnożenie alfy zbija kontrast pod próg i w słońcu element znika. */
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 1; background: transparent;
  border-color: var(--color-border); color: var(--color-disabled-text);
  cursor: not-allowed; transform: none;
}
/* R-1: przycisk NIGDY nie jest czerwony. Akcja destrukcyjna = obrys + tekst. */
.btn-primary:disabled, .btn-primary[aria-disabled="true"] { border-color: var(--color-accent); }
.btn-danger { background: transparent; border-color: var(--color-error); color: var(--color-error); border-width: var(--border-w-strong); }
.btn-danger:hover { background: rgba(255, 107, 94, .12); border-color: var(--color-error); }
.btn-neutral { background: var(--color-text); color: var(--color-accent-text); border-color: var(--color-text); }
.btn-neutral:hover { background: var(--color-text); opacity: 1; color: var(--color-accent-text); }

/* Focus — na KAŻDYM elemencie interaktywnym, żółty obrys 2 px + offset (§4). */
a:focus-visible, button:focus-visible, .btn:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible,
summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: var(--radius);
}

/* Karta = mocny obrys + twardy cień (manifest: „tablice techniczne"). */
.card { border-width: var(--border-w-strong); border-color: var(--color-border-strong); box-shadow: var(--shadow-sm); }

/* Pole = „wgłębienie": tło strony, nie powierzchnia karty. */
.input {
  background: var(--color-bg); border-width: var(--border-w-strong); border-color: var(--color-border-strong);
  min-height: var(--tap-min); font-weight: 500;
}
.input::placeholder { color: var(--color-disabled-text); }
.input:focus { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* Korekta D obowiązuje TAKŻE pola — bez tego `disabled` wygląda pixel w pixel jak pole czynne
   i user stuka w martwy element bez wyjaśnienia. ⛔ Zero `opacity` (zbija kontrast pod próg). */
.input:disabled, select.input:disabled, textarea.input:disabled {
  opacity: 1; border-color: var(--color-border); color: var(--color-disabled-text);
  cursor: not-allowed;
}
.input.is-invalid, textarea.input.is-invalid, select.input.is-invalid {
  border-color: var(--color-error); box-shadow: none;
  outline: 2px solid var(--color-error); outline-offset: 2px;   /* ta sama fizyka co focus, ⛔ zero poświaty */
}
select.input { appearance: none; padding-right: var(--sp-7); background-image:
  linear-gradient(45deg, transparent 50%, var(--color-text-muted) 50%),
  linear-gradient(135deg, var(--color-text-muted) 50%, transparent 50%);
  background-position: calc(100% - 20px) calc(50% + 2px), calc(100% - 14px) calc(50% + 2px);
  background-size: 6px 6px, 6px 6px; background-repeat: no-repeat; }
.field label { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-muted); }

/* Komunikaty: obrys + pasek, ⛔ zero jasnych teł wbitych na sztywno. */
.msg { border: var(--border-w) solid var(--color-border); background: transparent; color: var(--color-text); font-weight: 500; }
.msg-error   { background: transparent; border-left: 3px solid var(--color-error);   color: var(--color-text); }
.msg-success { background: transparent; border-left: 3px solid var(--color-success); color: var(--color-text); }

/* Plakietka statusu: ⛔ pigułka 999 px (sprzeczna z „tablicami technicznymi"). */
.badge {
  border-radius: var(--radius); border-width: var(--border-w-strong); border-color: var(--color-border-strong);
  font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 3px var(--sp-2);
}
.ds-checklist-count, .ref-chip { border-radius: var(--radius); border-width: var(--border-w-strong); }
.ds-progress, .ds-progress-fill, .onb-funnel-bar, .onb-funnel-fill, .gen-skel-bar { border-radius: var(--radius); }
.fab { border-radius: var(--radius); box-shadow: var(--shadow-md); }
.navtab-op { border-radius: var(--radius); border-color: var(--color-border-strong); }
.navtab-op:hover { background: var(--color-text); color: var(--color-accent-text); }
.tpl-dot { width: 8px; height: 8px; border-radius: var(--radius-dot); }
/* Żetony kroków/checklisty: kwadrat 6 px zamiast koła — spójne z geometrią tablic. */
.ds-check-ico, .gen-ico, .wizard-step .wiz-dot, .gen-skel-num { border-radius: var(--radius); }
.ds-check-ico { background: var(--color-bg); border-color: var(--color-border-strong); }
.ds-checklist-item.is-done .ds-check-ico { color: var(--color-accent-text); }
/* ⛔ Krok ukończony NIE jest zielony: wypełniony formularz to nie „przyjęte / opłacone / obsada kompletna".
   Zieleń wycieka wtedy z semantyki POTWIERDZENIA do semantyki postępu i osłabia pasek obsady (R-2). */
.wizard-step.is-done { color: var(--color-text-muted); }
.wizard-step.is-done .wiz-dot {
  background: var(--color-surface); border-color: var(--color-border-strong); color: var(--color-accent);
}
.wizard-sep { border-radius: 0; }
.gen-stage.is-done .gen-ico { color: var(--color-accent-text); }  /* biel na zieleni = 2,25:1 FAIL */
.ds-survey-opt.is-selected { border-color: var(--color-accent); box-shadow: 0 0 0 2px rgba(255, 196, 0, .28); }

/* Banery: biel na żółtym/pomarańczowym/czerwonym = FAIL → atrament. */
.impersonation-banner { color: var(--color-accent-text); }
.maintenance-banner { color: var(--color-accent-text); }
.maintenance-banner-link { color: var(--color-accent-text); }
.plaq { color: var(--color-accent-text); }

/* Nakładki i modale: na ciemnym scrimie karta MUSI mieć obrys, inaczej wtapia się w tło. */
.modal, .overlay-box { border: var(--border-w-strong) solid var(--color-border-strong); border-radius: var(--radius); box-shadow: var(--shadow-md); }
.ds-tip { box-shadow: var(--shadow-md); border-radius: var(--radius); }

/* ⛔ „shimmer" = AI-sztampa (R-6). Szkielety są STATYCZNE. */
.gen-skel-num, .gen-skel-bar, .ds-ghost-row {
  animation: none; background: var(--color-surface);
  border: var(--border-w) solid var(--color-border); opacity: 1;
}
.gen-skel { opacity: 1; }

/* Tabele: cienie krawędzi na grafitcie + nagłówek techniczny. */
.table-wrap {
  background:
    linear-gradient(to right, var(--color-surface) 30%, transparent) 0 0 / 28px 100% no-repeat local,
    linear-gradient(to left, var(--color-surface) 30%, transparent) 100% 0 / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .45), transparent) 0 0 / 12px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .45), transparent) 100% 0 / 12px 100% no-repeat scroll,
    var(--color-surface);
  border: var(--border-w-strong) solid var(--color-border-strong); border-radius: var(--radius);
}
th { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
td { font-weight: 500; }

/* Pusty stan + cele dotykowe (WCAG 2.5.8 chce ≥24 px; nasza podłoga to 44 px — rękawica). */
.empty { border-width: var(--border-w-strong); border-color: var(--color-border-strong); }
.foot-nav a { min-height: var(--tap-min); }
.navtab, .subtab, .wizard-step, .tpl-item, .nav-menu-panel a { min-height: var(--tap-min); }
.navtab, .subtab { display: inline-flex; align-items: center; }

/* ---------------------------------------------------------------------------
   2. CYFRY TECHNICZNE (§3) — „parametry maszyny i terminy zapisane dużymi cyframi".
   Tabelaryczne ZAWSZE: odliczanie T-2 h nie może skakać, kolumny kwot muszą się kryć.
   --------------------------------------------------------------------------- */
.num, .num-md, .num-lg, .num-xl {
  font-family: var(--font-head);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
  letter-spacing: -.01em; font-weight: 800; color: var(--color-text);
  line-height: 1.02; display: inline-block;
}
.num-xl { font-size: clamp(38px, 11vw, 46px); }   /* korekta rundy 2: na 390 px stawka miała 35 px i przegrywała z timerem */
.num-lg { font-size: 28px; }
.num-md { font-size: 20px; font-weight: 700; }
.num-unit { font-size: .5em; font-weight: 600; color: var(--color-text-muted); margin-left: .18em; letter-spacing: 0; }
.num-md .num-unit, .num .num-unit { font-size: .62em; }

/* Wordmark tekstowy — fallback marki, dopóki krok `logo` nie wypełni app_settings.brand_logo_url. */
.og-wordmark { font-family: var(--font-head); font-weight: 800; font-size: 22px; letter-spacing: -.02em; color: var(--color-text); }
.og-wordmark b { color: var(--color-accent); font-weight: 800; }

/* Link tekstowy — trzeci poziom akcji, gdy przycisk obrysowy byłby drugim równorzędnym wyjściem
   (pusty stan ma mieć JEDNO wyjście + ewentualną furtkę). Cel dotykowy pełne 44 px. */
.og-link {
  display: inline-flex; align-items: center; min-height: var(--tap-min);
  color: var(--color-accent); font-weight: 700; font-size: 14px; text-decoration: underline;
  text-underline-offset: 3px; background: none; border: 0; cursor: pointer; padding: 0 var(--sp-1);
}
.og-link:hover { color: var(--color-accent-hover); }

/* Etykieta techniczna — JEDYNE miejsce z uppercase + tracking. */
.og-label {
  display: block; font-size: 12px; font-weight: 700; line-height: 1.3;
  letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-muted);
  /* Etykieta w wąskim kaflu KPI (390 px / 3 kolumny) nie ma prawa rozpychać kontenera —
     uppercase + tracking robi z „POTWIERDZONE" słowo szersze niż kolumna. */
  overflow-wrap: anywhere;
}
.og-sub { font-size: 14px; font-weight: 500; color: var(--color-text-muted); }
/* Podpis pod liczbą/wartością idzie do WŁASNEJ linii — inaczej „ocena 4,8 z 12" doklejałaby się
   do nazwy firmy i wiersz przestawałby się skanować wzrokiem. */
.num-md + .og-sub, .num-lg + .og-sub, .num-xl + .og-sub { display: block; }

/* Ikony — SVG inline, 1,5 px stroke, currentColor (kanon kształtów: design-system/icons.svg).
   ⛔ zero bibliotek ikon z CDN, ⛔ zero emoji w UI (R-6). Ikona NIGDY bez słowa obok (R-8).
   Kreski są w CSS, nie w atrybutach — dzięki temu HTML ikony to jedna linijka bez stylowania ad hoc. */
.og-ico {
  width: 20px; height: 20px; flex: none; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
.og-ico--24 { width: 24px; height: 24px; }
.og-ico--32 { width: 32px; height: 32px; }

/* Powiększanie celu dotykowego — gotcha L-070c: przezroczystym ::after, NIGDY zmianą wyglądu.
   ⛔ @media (hover:none) wolno użyć TYLKO do powiększania celu, nigdy do ukrywania sterowników. */
.og-tap { position: relative; }
@media (hover: none) {
  .og-tap::after {
    content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 100%; height: 100%; min-width: var(--tap-min); min-height: var(--tap-min);
  }
}

/* ---------------------------------------------------------------------------
   3. ZNACZNIK PILNOŚCI (§5) — jedyny element czysto podpisowy. Statyczny, zawsze ze
   SŁOWEM obok (R-8). MAKS. JEDEN akcent pilności na widok — drugi zabija pierwszy.
   --------------------------------------------------------------------------- */
.og-hazard {
  height: 10px; border-radius: 0; flex: none;
  background: repeating-linear-gradient(45deg,
    var(--color-accent) 0 12px, var(--color-accent-text) 12px 24px);
}
.og-hazard--slim { height: 8px; }
.og-hazard--wide { height: 14px; }

/* ---------------------------------------------------------------------------
   4. KARTY NISZY (§6.1 / §6.2)
   --------------------------------------------------------------------------- */
.og-card {
  position: relative; display: block; width: 100%; text-align: left;
  background: var(--color-surface);
  border: var(--border-w-strong) solid var(--color-border-strong);
  border-radius: var(--radius); padding: var(--sp-4);
  box-shadow: var(--shadow-sm); color: var(--color-text);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
a.og-card:hover, button.og-card:hover { text-decoration: none; }
@media (hover: hover) {
  /* ⛔ Hover NIE bierze żółtego obrysu — to sygnał zarezerwowany dla PILNOŚCI (inaczej najechanie
     kursorem zamienia zwykłą kartę w pilną i kolor niesie dwa sprzeczne znaczenia). */
  a.og-card:hover, button.og-card:hover { border-color: var(--color-text); box-shadow: var(--shadow-md); }
}
.og-card-head { display: flex; align-items: flex-start; gap: var(--sp-3); justify-content: space-between; }
.og-card-title { font-family: var(--font-head); font-size: 18px; font-weight: 800; margin: 0; line-height: 1.2; }
.og-card-sep { height: var(--border-w); background: var(--color-border); margin: var(--sp-4) 0; border: 0; }

/* Pilne (start ≤ 24 h) — pas ukosu na górnej krawędzi + obrys akcentu. */
/* ⛔ Karta pilna NIE dostaje żółtego obrysu: pas ukosu + plakietka OBRYSOWA w zupełności niosą pilność,
   a jedynym pełnym żółtym blokiem na widoku ma zostać PRZYCISK (inaczej akcent zjada sam siebie). */
.og-card--pilne { padding-top: calc(var(--sp-4) + 10px); overflow: hidden; }
.og-card--pilne::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 10px;
  background: repeating-linear-gradient(45deg,
    var(--color-accent) 0 12px, var(--color-accent-text) 12px 24px);
}
/* Wygasła / „obsada wybrana" — karta wyszarzona, akcje zastąpione zdaniem. */
.og-card--wygasla { border-color: var(--color-border); color: var(--color-text-muted); box-shadow: none; }
.og-card--wygasla .og-card-title, .og-card--wygasla .num,
.og-card--wygasla .num-md, .og-card--wygasla .num-lg, .og-card--wygasla .num-xl { color: var(--color-text-muted); }
/* Błąd publikacji — obrys czerwony, ⛔ NIGDY czerwone tło całej karty. */
.og-card--blad { border-color: var(--color-error); }

/* Siatka parametrów w karcie (LOKALIZACJA | TERMIN) — separator pionowy jak na tablicy. */
.og-meta { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.og-meta > * + * { border-left: var(--border-w) solid var(--color-border); padding-left: var(--sp-4); }
/* Para pól obok siebie (start + długość, stawka + liczba osób) — BEZ pionowej linii siatki. */
.og-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.og-pair > .field { margin-bottom: 0; min-width: 0; }
.og-meta-cell { display: flex; gap: var(--sp-3); align-items: flex-start; min-width: 0; }
.og-meta-cell .og-ico { color: var(--color-text-muted); margin-top: 2px; }
.og-meta-val { display: block; margin-top: 2px; }

/* ---------------------------------------------------------------------------
   5. KARTA PROPOZYCJI (§6.2) — kolejność WIĄŻĄCA: stawka → dojazd → termin →
   maszyna → prace/BHP → forma → blok zablokowany → decyzja. Operator decyduje w 10 s.
   --------------------------------------------------------------------------- */
.og-prop-stawka { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.og-prop-stawka .num-xl { color: var(--color-text); }
/* Karta martwa wygasza TAKŻE stawkę — inaczej najjaśniejsza liczba ekranu należy do oferty, której nie ma
   (selektor musi być mocniejszy niż `.og-prop-stawka .num-xl`, bo tamta reguła stoi wyżej). */
.og-card--wygasla .og-prop-stawka .num-xl { color: var(--color-text-muted); }
.og-req { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); padding: 0; }
.og-req li {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: 14px; font-weight: 500; color: var(--color-text);
  border: var(--border-w) solid var(--color-border); border-radius: var(--radius);
  padding: 4px var(--sp-2);
}
.og-req li .og-ico { width: 16px; height: 16px; color: var(--color-text-muted); }

/* ---------------------------------------------------------------------------
   6. DECYZJA OPERATORA (§6.3) — dwa wielkie przyciski, JEDEN pod drugim.
   ⛔ „Nie mogę" NIGDY nie jest czerwony (R-1): odmowa to normalna operacja.
   ⛔ Nigdy obok siebie — w rękawicy sąsiadujące przyciski = przypadkowa decyzja.
   --------------------------------------------------------------------------- */
.og-decide { display: flex; flex-direction: column; gap: var(--sp-3); }
.btn-key { min-height: var(--tap-key); width: 100%; font-size: 20px; font-weight: 800; letter-spacing: .01em; }
.og-decide-note { margin: 0; font-size: 14px; font-weight: 500; color: var(--color-text-muted); }
.og-decide-note + .btn { margin-top: var(--sp-1); }

/* ---------------------------------------------------------------------------
   7. PASEK OBSADY (§6.4) — licznik MIEJSC, nie procent. Segmenty stykają się bokami.
   --------------------------------------------------------------------------- */
.og-obsada-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.og-obsada-track { display: flex; height: 12px; max-width: 420px; border-radius: 0; overflow: hidden; background: var(--color-border); }
.og-obsada-slot { flex: 1 1 0; background: var(--color-border); }
.og-obsada-slot + .og-obsada-slot { border-left: 2px solid var(--color-bg); }
.og-obsada-slot.is-done { background: var(--color-success); }           /* zatwierdzony i OPŁACONY (R-2) */
.og-obsada-slot.is-pending { background: var(--color-surface); box-shadow: inset 0 0 0 2px var(--color-accent); }
.og-obsada--komplet .og-obsada-label { color: var(--color-success); }

/* ---------------------------------------------------------------------------
   8. PLAKIETKI (§6.9) + STATUS DOKUMENTU (§6.5)
   ⛔ ZAKAZ frazy „zweryfikowany" w każdej odmianie (D-12) — detektor w audit-static.
   --------------------------------------------------------------------------- */
.og-status {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  border: var(--border-w-strong) solid var(--color-border-strong); border-radius: var(--radius);
  padding: 4px var(--sp-2); color: var(--color-text); background: transparent; white-space: nowrap;
}
.og-status .og-ico { width: 14px; height: 14px; }
.og-status--pilne   { background: var(--color-accent);  color: var(--color-accent-text); border-color: var(--color-accent); }
/* Wariant OBRYSOWY pilności: obowiązkowy na widoku, który ma już żółty przycisk główny — wypełniona
   żółć jest w tym systemie językiem AKCJI, więc pełna plakietka czyta się jak drugi przycisk. */
.og-status--pilne-obrys { background: transparent; color: var(--color-accent); border-color: var(--color-accent); }
.og-status--ok      { border-color: var(--color-success); color: var(--color-success); }
.og-status--uwaga   { border-color: var(--color-warning); color: var(--color-warning); }
.og-status--blokada { border-color: var(--color-error);   color: var(--color-error); }
.og-status--cichy   { border-color: var(--color-border);  color: var(--color-text-muted); }

/* Status dokumentu: „dodany" jest NEUTRALNY — zieleń przeczytałaby się jako „zweryfikowany" (R-2 + D-12). */
.og-doc { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--tap-min);
  border: var(--border-w-strong) solid var(--color-border-strong); border-radius: var(--radius);
  padding: 6px var(--sp-3); font-size: 14px; font-weight: 600; color: var(--color-text); }
.og-doc .og-ico { color: var(--color-text-muted); }
.og-doc--minela { border-color: var(--color-warning); color: var(--color-warning); }
.og-doc--minela .og-ico { color: var(--color-warning); }
.og-doc--brak { border-style: dashed; border-color: var(--color-border); color: var(--color-disabled-text); }
/* ⛔ Nie ubieraj własnej awarii w pomarańcz „ważność minęła" — obwiniasz usera za swój błąd. */
.og-doc--blad { border-color: var(--color-border-strong); color: var(--color-text-muted); }
.og-doc--blad .og-ico { color: var(--color-text-muted); }
.og-doc--brak .og-ico { color: var(--color-disabled-text); }

/* ---------------------------------------------------------------------------
   9. TIMER POTWIERDZENIA T-24 h / T-2 h (§6.10) — informacja, nigdy disabled.
   --------------------------------------------------------------------------- */
.og-timer {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  border: var(--border-w-strong) solid var(--color-border-strong); border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-3); color: var(--color-text);
}
.og-timer .num, .og-timer .num-md, .og-timer .og-label { color: inherit; }
.og-timer .og-ico { flex: none; }
.og-timer--t24 { border-color: var(--color-warning); color: var(--color-warning); }
.og-timer--t2  { border-color: var(--color-error);   color: var(--color-error); }
.og-timer--ok  { border-color: var(--color-success); color: var(--color-success); }

/* ---------------------------------------------------------------------------
   10. LICZNIK PODAŻY W FORMULARZU (§6.6) — ⛔ nigdy fałszywe dane demo.
   --------------------------------------------------------------------------- */
.og-podaz {
  background: var(--color-surface); border: var(--border-w) solid var(--color-border);
  border-radius: var(--radius); padding: var(--sp-3) var(--sp-4);
  font-size: 14px; font-weight: 500; color: var(--color-text-muted); line-height: 1.6;
}
.og-podaz .num-lg { vertical-align: -2px; margin-right: var(--sp-2); color: var(--color-text); }
/* Liczba OPERATORÓW jest obietnicą; promień i termin to tylko podpis ustawień — muszą być NIŻEJ
   w hierarchii, inaczej wzrok ląduje na „50 km" zamiast na „17 osób". */
.og-podaz .og-sub { display: block; margin-top: var(--sp-1); }
.og-podaz--niska .num-lg, .og-podaz--zero .num-lg { color: var(--color-warning); }
.og-podaz--zero { border: var(--border-w-strong) solid var(--color-warning); }
.og-podaz-out { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }

/* ---------------------------------------------------------------------------
   11. ODSŁONA PO OPŁACIE (§6.7) — og-locked / og-paywall / og-reveal.
   ⛔ ZAKAZ maskowania rozmyciem/gwiazdkami: dane NIE przychodzą z serwera (D-05
   egzekwowany w RLS + edge). Pusta ramka mówi prawdę o architekturze.
   --------------------------------------------------------------------------- */
/* Obrys CIĄGŁY: „to istnieje, ale zobaczysz po opłacie". Przerywany zostaje wyłącznie dla PUSTKI
   (`og-empty`, `og-doc--brak`) — inaczej paywall i pusty stan są tym samym obiektem wizualnym. */
.og-locked {
  display: flex; align-items: center; gap: var(--sp-3);
  border: var(--border-w-strong) solid var(--color-border-strong); border-radius: var(--radius);
  padding: var(--sp-4); color: var(--color-text-muted); font-size: 14px; font-weight: 500;
}
.og-locked .og-ico { color: var(--color-text-muted); }
.og-paywall { display: flex; flex-direction: column; gap: var(--sp-4); }
.og-paywall-sum { display: flex; flex-direction: column; gap: var(--sp-2); }
/* Rozwinięcie bloku kontaktu = jedyna animacja „odsłony" (narracyjna, §6.11). */
.og-reveal { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows var(--dur-slow) var(--ease-out), opacity var(--dur-slow) var(--ease-out); }
.og-reveal > * { overflow: hidden; }
.og-reveal.is-open { grid-template-rows: 1fr; opacity: 1; }

/* ---------------------------------------------------------------------------
   12. PUSTY STAN (§6.8) — pusta lista MUSI proponować ruch. ⛔ „Brak wyników", „Ups!".
   --------------------------------------------------------------------------- */
.og-empty {
  border: var(--border-w-strong) dashed var(--color-border-strong); border-radius: var(--radius);
  padding: var(--sp-6) var(--sp-4); text-align: center; color: var(--color-text-muted);
}
.og-empty-title { font-family: var(--font-head); font-size: 18px; font-weight: 700; color: var(--color-text); margin: 0 auto; max-width: 34ch; }
.og-empty-num { margin: var(--sp-3) 0 0; font-size: 14px; font-weight: 500; }
.og-empty-out { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: center; margin-top: var(--sp-5); }
.og-empty-promise { margin: var(--sp-4) 0 0; font-size: 14px; font-weight: 500; }

/* ---------------------------------------------------------------------------
   13. SZKIELET ŁADOWANIA — STATYCZNY (⛔ shimmer, R-6). Ten sam kształt co wynik,
   żeby układ nie skakał po dociągnięciu danych.
   --------------------------------------------------------------------------- */
.og-skel { background: var(--color-surface); border: var(--border-w) solid var(--color-border); border-radius: var(--radius); }
.og-skel-line { height: 14px; }
.og-skel-line--lg { height: 34px; }
.og-skel-w40 { width: 40%; } .og-skel-w60 { width: 60%; } .og-skel-w80 { width: 80%; }

/* ---------------------------------------------------------------------------
   14. KAFLE KPI (pulpit firmy) — pełne rzędy, duże cyfry, słowo pod liczbą.
   --------------------------------------------------------------------------- */
.og-kpi-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3); }
.og-kpi {
  background: var(--color-surface); border: var(--border-w-strong) solid var(--color-border-strong);
  border-radius: var(--radius); padding: var(--sp-3); box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0;
}
.og-kpi .num-lg { font-size: clamp(26px, 8vw, 34px); }
.og-kpi--pilne .num-lg { color: var(--color-accent); }
.og-kpi--ok .num-lg { color: var(--color-success); }
.og-kpi .og-ico { color: var(--color-text-muted); }
/* Kafel KPI MUSI prowadzić do filtra listy — nieklikalny licznik to martwy dashboard SaaS (R-6 w duchu). */
a.og-kpi { text-decoration: none; transition: border-color var(--dur-fast) var(--ease-out); }
a.og-kpi:hover { border-color: var(--color-accent); text-decoration: none; }
.og-kpi-head { display: flex; align-items: center; gap: var(--sp-2); }

/* ---------------------------------------------------------------------------
   15. SHELL EKRANU — pasek górny, kolumna operatora, przyklejony pas decyzji.
   --------------------------------------------------------------------------- */
.og-screen { max-width: var(--maxw-op); margin: 0 auto; padding: var(--sp-4); }
.og-screen--wide { max-width: var(--maxw); }
.og-appbar {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; align-items: center; gap: var(--sp-3);
  background: var(--color-bg); border-bottom: var(--border-w) solid var(--color-border);
  padding: var(--sp-2) var(--sp-4); min-height: var(--tap-key);
}
.og-appbar-title { font-family: var(--font-head); font-size: 16px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; margin: 0; white-space: nowrap; }
.og-appbar .og-hazard, .og-appbar-spacer { flex: 1 1 auto; min-width: 12px; }
.og-back {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--tap-min); height: var(--tap-min);
  border: var(--border-w-strong) solid var(--color-border-strong); border-radius: var(--radius);
  background: transparent; color: var(--color-text); cursor: pointer;
}
.og-back:hover { border-color: var(--color-text); }
.og-sticky {
  position: sticky; bottom: 0; z-index: var(--z-sticky);
  background: var(--color-bg); border-top: var(--border-w) solid var(--color-border);
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}
.og-stack { display: flex; flex-direction: column; gap: var(--sp-4); }
.og-stack--tight { gap: var(--sp-3); }
.og-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.og-row--between { justify-content: space-between; }
.og-sechead { display: flex; align-items: center; gap: var(--sp-3); }
.og-sechead .og-hazard { flex: 0 1 auto; width: 100%; max-width: 220px; min-width: 96px; }

/* Sekcja „Więcej opcji" — natywne <details>, zero JS (§ wzorzec (f)). */
.og-more { border-top: var(--border-w) solid var(--color-border); padding-top: var(--sp-3); }
.og-more > summary {
  display: flex; align-items: center; gap: var(--sp-2); cursor: pointer; list-style: none;
  min-height: var(--tap-min); font-size: 14px; font-weight: 700; color: var(--color-text);
}
.og-more > summary::-webkit-details-marker { display: none; }
.og-more > summary::before { content: "+"; font-family: var(--font-head); font-weight: 800; font-size: 20px; color: var(--color-accent); width: 16px; text-align: center; }
.og-more[open] > summary::before { content: "\2212"; }

/* Lista dopasowań (widok firmy) — wiersz kandydata w PIONOWEJ siatce kolumn. */
.og-cand { display: grid; grid-template-columns: 1fr auto; gap: var(--sp-3) var(--sp-4); align-items: start; }
.og-cand-main { min-width: 0; }
.og-cand-dist { text-align: right; white-space: nowrap; }
.og-cand-tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); }
.og-cand-acts { grid-column: 1 / -1; display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.og-cand-acts .btn { flex: 1 1 180px; }

/* ---------------------------------------------------------------------------
   16. RUCH (§6.11) — animacja NARRACYJNA, nigdy dekoracyjna. Pełne wyciszenie
   przy prefers-reduced-motion (rozszerzenie wzorca startera na klasy og-*).
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .og-card, .btn, .og-reveal { transition: none; }
  .btn:active { transform: none; }
  .og-reveal { transition: none; }
}

/* ---------------------------------------------------------------------------
   17. SZEROKOŚCI — mobile-first (375 px bazowo), potem 460 / 720 / 1024.
   Reguły bazowe stoją WYŻEJ (L-070b: @media przed bazą = martwy override).
   --------------------------------------------------------------------------- */
@media (max-width: 400px) {
  .og-meta { grid-template-columns: 1fr; gap: var(--sp-3); }
  .og-meta > * + * { border-left: 0; padding-left: 0; border-top: var(--border-w) solid var(--color-border); padding-top: var(--sp-3); }
  .og-kpi .og-label { font-size: 11px; letter-spacing: .06em; }
}
@media (min-width: 720px) {
  .og-screen { padding: var(--sp-6) var(--sp-5); }
  .og-kpi { padding: var(--sp-4); }
  /* RYTM WIZUALNY (lekcja „krzywa lista ofert"): kolumny dystansu i akcji są STAŁE, żeby liczby
     i przyciski układały się w PIONOWE linie między wierszami. ⛔ `auto` = każda karta liczy własną
     szerokość i lista się „krzywi". */
  .og-cand { grid-template-columns: minmax(0, 1fr) 92px 336px; align-items: center; }
  .og-cand-acts { grid-column: auto; justify-content: flex-end; }
  .og-cand-acts .btn { flex: 0 1 auto; }
}
