/**
 * Komponenty — utility CSS classes pro opakované UI prvky.
 * Filozofie: minimum classes, kompozitelné. Inspirováno shadcn/ui patternem.
 */

/* =====================
 * BUTTON
 * ===================== */
/* CZ: Tvar tlačítka podle vzoru (F2, 6. 9. 2026): PILULKA, plná výplň, 600 váha,
       tracking −0.1px. Přechod je jen barva + drobné zmáčknutí — vzor zakazuje
       „bounce, spring, parallax".

       ⚠️ Odchylka od vzoru u VELIKOSTI, vědomá: vzor má `md` = 16 px / 14–28 px padding,
       což je proti dnešnímu tlačítku (14 px / 40 px výška) o dost větší a rozhodilo by
       husté provozní obrazovky. `.btn` proto odpovídá velikosti `sm` ze vzoru
       (14 px / 10–20 px), `.btn-lg` teprve `md`. Tvar a barvy jsou přitom ze vzoru přesně —
       a právě ty dělají vzhled, ne pár pixelů výšky.
   EN: The button's shape per the sample (F2, 6 Sep 2026): a PILL, solid fill, weight 600,
       −0.1px tracking. The transition is colour plus a slight press only — the sample forbids
       "bounce, spring, parallax".

       ⚠️ A deliberate deviation on SIZE: the sample's `md` is 16 px with 14–28 px padding, much
       larger than today's button (14 px, 40 px tall), and it would blow up the dense ops screens.
       `.btn` therefore matches the sample's `sm` (14 px / 10–20 px) and `.btn-lg` its `md`. The
       shape and the colours are exactly the sample's — and those, not a few pixels of height,
       are what the look is made of. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: 10px 20px;
  font-family: var(--font-core);
  font-size: var(--text-sm);
  font-weight: var(--weight-subhead);
  letter-spacing: -0.1px;
  line-height: 1.2;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  transition:
    background-color var(--transition-normal),
    border-color var(--transition-normal),
    color var(--transition-normal),
    transform var(--transition-fast);
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  /* CZ: Základ `.btn` byl do 17. 8. 2026 bez pozadí, rámečku i barvy textu — tlačítko bez
     varianty (`class="btn btn-sm"`) tak splynulo s podkladem a vypadalo jako text. Přesně na to
     narazil majitel: „brief a research pořád nemá button pro generování" — tlačítko tam bylo,
     jen nebylo vidět. Základ proto vypadá jako `.btn-outline`; varianty (`primary`, `danger`,
     `ghost`, `outline`) ho dál přepisují, protože stojí v souboru níž.
     EN: Until 17 Aug 2026 the `.btn` base had no background, border or text colour — a button with
     no variant (`class="btn btn-sm"`) blended into the surface and looked like text. That is
     exactly what the owner hit: "brief and research still has no generate button" — the button was
     there, it just was not visible. The base therefore looks like `.btn-outline`; the variants
     (`primary`, `danger`, `ghost`, `outline`) still override it, being further down the file. */
  background-color: var(--surface-page);
  border-color: var(--line-card);
  color: var(--text-primary);
}
.btn:hover:not(:disabled) { background-color: var(--gray-tint); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn:hover { text-decoration: none; }

.btn-primary {
  /* CZ: Popředí je `--white` (bílá v OBOU režimech), ne `--white`.
         Ten se v tmavém režimu překlápí na tmavou, takže popisek tlačítka seděl na sytém
         pozadí s kontrastem 3.37:1 (dřív 3.17:1 na cihlové) — propadalo to i před redesignem,
         jen to nikdo neměřil. Odhalil to kontrastní test rozšířený na tmavou paletu
         (6. 9. 2026, F1). Bílá na `--action-primary` (#0069BC) dává 5.60:1 v obou režimech.
     EN: The foreground is `--white` (white in BOTH themes), not `--white`.
         The latter flips dark in dark mode, so the button label sat on a saturated background at
         3.37:1 (previously 3.17:1 on the brick shade) — it was failing before the redesign too,
         nobody had measured it. Surfaced by the contrast test once it was extended to the dark
         palette (6 Sep 2026, F1). White on `--action-primary` (#0069BC) gives 5.60:1 in both. */
  background-color: var(--action-primary);
  color: var(--text-on-primary);
  box-shadow: var(--shadow-none);
}
.btn-primary:hover:not(:disabled) { background-color: var(--action-primary-hover); }
/* CZ: Stisknutý stav — vzor nepoužívá posun, ale zmenšení (`--press-scale: .98`).
   EN: Pressed state — the sample does not shift, it scales down (`--press-scale: .98`). */
.btn-primary:active:not(:disabled) {
  background-color: var(--action-primary-press);
  transform: scale(var(--press-scale));
}

/* CZ: Hierarchie akcí ze vzoru: 1 modrá > 2 ORANŽOVÁ > 3 zelená. `.btn-secondary` proto přechází
       z navy na oranžovou (34 výskytů v šablonách). Text je ČERNÝ, ne bílý — bílá by na
       `#F59926` dala 2.22:1, černá dává 9.47:1. Že to takhle funguje, ukazuje sám vzor:
       jeho odznak upozornění je `background:#F59926;color:#000` a `PillBadge` má `color: var(--black)`.
   EN: The sample's action hierarchy: 1 blue > 2 ORANGE > 3 green. `.btn-secondary` therefore moves
       from navy to orange (34 uses across the templates). Its text is BLACK, not white — white on
       `#F59926` would be 2.22:1, black gives 9.47:1. The sample itself shows this works: its
       notification badge is `background:#F59926;color:#000` and `PillBadge` uses `color: var(--black)`. */
.btn-secondary {
  background-color: var(--action-secondary);
  color: var(--text-on-secondary);
  box-shadow: var(--shadow-none);
}
.btn-secondary:hover:not(:disabled) { background-color: var(--action-secondary-hover); }
.btn-secondary:active:not(:disabled) {
  background-color: var(--action-secondary-press);
  transform: scale(var(--press-scale));
}

/* CZ: Třetí stupeň hierarchie. V aplikaci zatím nikde — přidán, aby byl vzor kompletní a
       aby se pro „potvrzující/kladnou" akci nesahalo po primárním modrém tlačítku.
   EN: The hierarchy's third step. Not used anywhere in the app yet — added so the sample is
       complete and so a "confirming/positive" action need not reach for the primary blue. */
.btn-tertiary {
  background-color: var(--action-tertiary);
  color: var(--text-on-tertiary);
  box-shadow: var(--shadow-none);
}
.btn-tertiary:hover:not(:disabled) { background-color: var(--action-tertiary-hover); }
.btn-tertiary:active:not(:disabled) {
  background-color: var(--action-tertiary-press);
  transform: scale(var(--press-scale));
}

/* CZ: Společný stisknutý stav pro ostatní varianty. EN: Shared pressed state for other variants. */
.btn-outline:active:not(:disabled),
.btn-ghost:active:not(:disabled),
.btn-danger:active:not(:disabled) { transform: scale(var(--press-scale)); }

/* CZ: Obrysové tlačítko má ve vzoru VLASTNÍ tloušťku (`Button.jsx`:
       `border: var(--border-ghost) solid`), silnější než 1px karty — obrys je tu nositel
       tlačítka, ne jen ohraničení plochy.
   EN: The outline button has its OWN border width in the sample (`Button.jsx`:
       `border: var(--border-ghost) solid`), heavier than a card's 1px — here the outline
       carries the button rather than merely bounding a surface. */
.btn-outline {
  background-color: transparent;
  border-width: var(--border-ghost);
  border-color: var(--line-card);
  color: var(--text-primary);
}
.btn-outline:hover:not(:disabled) { background-color: var(--gray-tint); }

.btn-ghost {
  background-color: transparent;
  color: var(--text-primary);
}
.btn-ghost:hover:not(:disabled) { background-color: var(--gray-tint); }

/* CZ: `--white` se v tmavém režimu překlápí na tmavou — na sytém červeném pozadí
       by popisek zčernal (stejná past jako u `.btn-primary`, viz F1). `--text-on-danger` je na
       to určený a v obou režimech drží čitelnost.
   EN: `--white` flips dark in dark mode — on the saturated red the label would turn
       black (the same trap as `.btn-primary`, see F1). `--text-on-danger` exists for this and
       keeps legibility in both themes. */
.btn-danger {
  background-color: var(--state-danger);
  color: var(--text-on-danger);
}
.btn-danger:hover:not(:disabled) { opacity: 0.9; }

/* CZ: Stupnice velikostí. `.btn` = `sm` ze vzoru, `.btn-lg` = jeho `md` (16 px / 14–28 px).
       `.btn-sm` je o stupeň níž, než vzor má — hustá provozní rovina ho potřebuje.
   EN: The size scale. `.btn` = the sample's `sm`, `.btn-lg` = its `md` (16 px / 14–28 px).
       `.btn-sm` sits one step below anything in the sample — the dense ops plane needs it. */
.btn-sm { padding: 7px 14px; font-size: var(--text-xs); }
.btn-lg { padding: 14px 28px; font-size: var(--text-base); }
/* CZ: Čtvercové tlačítko jen s ikonou — kruh, ať drží pilulkový jazyk vzoru.
   EN: A square icon-only button — a circle, to keep the sample's pill language. */
.btn-icon { width: 2.5rem; height: 2.5rem; padding: 0; border-radius: var(--radius-pill); }

.btn-block { width: 100%; }

/* CZ: Na dotykových zařízeních zvětši malé ikonové/kompaktní cíle na WCAG 2.5.5 minimum
       (44×44px) — desktop myš zůstává kompaktní. EN: On touch devices enlarge small icon /
       compact targets to the WCAG 2.5.5 minimum (44×44px) — desktop mouse stays compact. */
@media (pointer: coarse) {
  .btn-icon, .btn-sm { min-height: 44px; min-width: 44px; }
}

/* =====================
 * INPUT
 * ===================== */
/* CZ: Formulářové pole (F6, 6. 9. 2026).
 *
 *     ⚠️ TOHLE JE ODVOZENÍ, NE PŘEVZETÍ. Vzor má v celém prototypu JEDEN input (chatový řádek)
 *     a JEDNU textareu — žádný select, checkbox, radio, datum ani upload. Aplikace jich má 103.
 *     Muselo se to proto dopočítat z pravidel vzoru, ne opsat:
 *
 *       · Obrys 1.25 px stejný jako karta — pole je plocha jako každá jiná, ne výjimka.
 *       · Radius 12 px (`--radius-container`). Vzorový chatový řádek má pilulku (999 px), ale
 *         ta patří k JEDNOMU hledacímu poli, ne k formuláři: pilulková textarea vypadá rozbitě
 *         a deset pilulek pod sebou se čte jako seznam tlačítek. Pilulka proto zůstává jako
 *         volitelná varianta `.input--pill` pro hledání a příkazový řádek.
 *       · Focus = MODRÝ obrys, jak to má vzor (`style-focus="border-color:#0092FF"`), plus
 *         `--focus-ring` navíc — vzor focus vůbec neřeší (SM-10) a projekt ho vyžaduje.
 *       · Velikost textu `--body-size-sm` (15 px): pole musí být čitelné, ne drobné.
 * EN: A form field (F6, 6 Sep 2026).
 *
 *     ⚠️ THIS IS DERIVED, NOT ADOPTED. The whole prototype contains ONE input (the chat bar) and
 *     ONE textarea — no select, checkbox, radio, date or upload. The app has 103 of them. It had
 *     to be derived from the sample's rules rather than copied:
 *
 *       · A 1.25 px outline, like a card — a field is a surface like any other, not an exception.
 *       · A 12 px radius (`--radius-container`). The sample's chat bar is a pill (999 px), but
 *         that belongs to ONE search field, not to a form: a pill textarea looks broken and ten
 *         pills stacked read as a list of buttons. The pill therefore survives as an optional
 *         `.input--pill` variant for search and command bars.
 *       · Focus = a BLUE outline, as the sample has it (`style-focus="border-color:#0092FF"`),
 *         plus `--focus-ring` on top — the sample ignores focus entirely (SM-10) and the project
 *         requires it.
 *       · Text size `--body-size-sm` (15 px): a field must be legible, not tiny. */
/* CZ: ⚠️ OPRAVA PŘEDCHOZÍHO KOMENTÁŘE (16. 9. 2026). Text nad tímhle blokem tvrdil, že vzor
       obsahuje JEDEN input a JEDNU textareu, a proto se pole muselo odvodit. Není to pravda:
       vzor má **dvanáct** polí a všechna sdílejí `border: 1px solid #D0D0D0`. Liší se jen
       radiusem podle velikosti (R29) — a právě proto vypadal spor R6 („radius 8") proti
       `CLAUDE.md` („pole 12") jako spor dvou dokumentů, zatímco to je jedna a táž věc pro dvě
       různě velká pole.
   EN: ⚠️ CORRECTION TO THE COMMENT ABOVE (16 Sep 2026). It claimed the sample holds ONE input
       and ONE textarea, so a field had to be derived. That is untrue: the sample has **twelve**
       fields and all share `border: 1px solid #D0D0D0`. Only the radius differs, by field size
       (R29) — which is exactly why R6's "radius 8" looked like a conflict with `CLAUDE.md`'s
       "field 12", when in fact it is one rule for two different field sizes. */
.input,
select.input,
textarea.input {
  display: block;
  width: 100%;
  padding: var(--app-field-padding);
  font-family: var(--font-core);
  font-size: var(--app-field-size);
  line-height: 1.4;
  background-color: var(--surface-card);
  border: var(--app-field-border-width) solid var(--line-card);
  border-radius: var(--radius-chip);
  color: var(--text-primary);
  transition: border-color var(--transition-normal), box-shadow var(--transition-normal);
}
/* CZ: Jednořádkový input a select drží pevnou výšku, aby se v řadě zarovnaly s tlačítky;
       textarea roste podle obsahu. EN: A single-line input and a select keep a fixed height so
       they line up with buttons in a row; a textarea grows with its content. */
.input:not(textarea) { height: 2.75rem; }
/* CZ: Velká textarea je druhá velikost pole podle R29 — vzor jí dává radius 12 a vnitřní
       odsazení 14-16px, protože se do ní píše odstavec, ne řádek.
   EN: The large textarea is the second field size per R29 — the sample gives it a 12px radius
       and 14-16px padding, because a paragraph is written into it, not a line. */
textarea.input {
  min-height: 6rem;
  resize: vertical;
  padding: var(--app-field-padding-lg);
  border-radius: var(--radius-container);
}

.input::placeholder { color: var(--text-secondary); }
.input:focus { outline: none; border-color: var(--action-primary); box-shadow: var(--focus-ring); }
.input:disabled { opacity: 0.5; cursor: not-allowed; background-color: var(--surface-panel); }
/* CZ: Chybový rám je ORANŽOVÝ (R19, rozhodnutí majitele). Text chyby zůstává červený —
       `--action-secondary` má jako text na bílé 2.22:1 a propadl by WCAG AA (R30).
   EN: The error border is ORANGE (R19, owner's decision). The error text stays red —
       `--action-secondary` as text on white is 2.22:1 and would fail WCAG AA (R30). */
.input.is-invalid { border-color: var(--app-field-error-border); }
.input.is-invalid:focus { box-shadow: var(--focus-ring-error); }

/* CZ: Pilulková varianta — chatový řádek a hledání, přesně jak je má vzor.
   EN: The pill variant — the chat bar and search, exactly as the sample has them. */
.input--pill { border-radius: var(--radius-pill); padding-inline: 20px; }

/* CZ: Nativní šipka selectu se ZÁMĚRNĚ nechává. Vlastní by se musela nakreslit obrázkem
       v `background-image`, a barvu v datovém URI nejde vyjádřit tokenem — vznikl by ostrůvek
       mimo systém, který by se navíc v tmavém režimu nepřebarvil. Nativní šipka jde s
       `color-scheme` a s motivem systému, takže v obou režimech sedí sama.
   EN: The select's native arrow is DELIBERATELY kept. A custom one would have to be drawn as a
       `background-image`, and a colour inside a data URI cannot be expressed by a token — it
       would create an island outside the system that also fails to repaint in dark mode. The
       native arrow follows `color-scheme` and the OS theme, so it fits both themes on its own. */
select.input { padding-inline-end: var(--space-4); }

/* CZ: Zaškrtávátko a přepínač — `accent-color` obarví NATIVNÍ prvek, takže zůstane přístupný
       (čtečky, klávesnice, vysoký kontrast) a přitom nese barvu vzoru. Vlastní nakreslené
       zaškrtávátko z `<div>` by tohle všechno rozbilo.
   EN: Checkbox and radio — `accent-color` tints the NATIVE control, so it stays accessible
       (screen readers, keyboard, high contrast) while carrying the sample's colour. A custom
       checkbox built from a `<div>` would break all of that. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--action-primary);
  width: 1.125rem;
  height: 1.125rem;
  cursor: pointer;
}
input[type="checkbox"]:disabled,
input[type="radio"]:disabled { cursor: not-allowed; opacity: 0.5; }

/* CZ: Nahrání souboru — stylovat jde jen tlačítko uvnitř (`::file-selector-button`), samotné
       pole si prohlížeč kreslí sám. Tlačítko dostane vzhled `.btn-outline`, ať to není jediný
       prvek formuláře, který vypadá jako z jiné aplikace.
   EN: File upload — only the inner button can be styled (`::file-selector-button`); the field
       itself is drawn by the browser. The button takes the `.btn-outline` look so it is not the
       one form control that looks like it came from another app. */
input[type="file"].input { height: auto; padding: 8px; }
input[type="file"].input::file-selector-button {
  font-family: var(--font-core);
  font-size: var(--text-sm);
  font-weight: var(--weight-subhead);
  padding: 7px 14px;
  margin-inline-end: var(--space-1);
  border: var(--border-card-style);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  color: var(--text-primary);
  cursor: pointer;
}
input[type="file"].input::file-selector-button:hover { border-color: var(--gray-base); }

textarea.input { height: auto; min-height: 5rem; padding: var(--space-1) var(--space-2); resize: vertical; }
select.input { appearance: none; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='%238A93A4' d='M3 5l5 5 5-5z'/></svg>"); background-repeat: no-repeat; background-position: right var(--space-2) center; padding-right: var(--space-4); }

/* =====================
 * FORM FIELD (label + input + error)
 * ===================== */
.field { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-2); }
/* CZ: ⚠️ ZDE SE ZÁMĚRNĚ NEDRŽÍ R6 (R38). R6 předepisuje popisku „caption styl", tedy 12px
       verzálkami s prostrkem. VZOR to tak NEMÁ: všech pět jeho popisků je
       `font-size:13px; color:#555`, bez verzálek (`BrAIen-Pristav-X.dc.html`, blok onboardingu).
       Verzálky na 132 popiscích by byly změna proti vzoru, ne k němu. Platí vzor.
   EN: ⚠️ R6 IS DELIBERATELY NOT FOLLOWED HERE (R38). R6 prescribes "caption style" for the
       label — 12px uppercase with tracking. The SAMPLE does not do that: all five of its labels
       are `font-size:13px; color:#555`, no uppercase. Uppercasing 132 labels would move away
       from the sample, not towards it. The sample wins. */
.field-label {
  font-size: var(--app-text-secondary-size);
  font-weight: var(--weight-eyebrow);
  color: var(--text-body);
}
.field-required { color: var(--text-link); margin-inline-start: var(--space-1); }
.field-hint { font-size: var(--text-xs); color: var(--text-secondary); }
.field-error {
  font-size: var(--app-field-error-size);
  color: var(--app-field-error-text);
  font-weight: var(--weight-eyebrow);
}

/* =====================
 * PASSWORD STRENGTH (ukazatel síly hesla / password strength meter)
 * ===================== */
/* CZ: Vzhled sdíleného partialu `templates/ui/password_strength.html`. Používá ho registrace,
       přijetí pozvánky, nastavení hesla z e-mailu (reset i aktivace klienta) a změna hesla
       v profilu — jedna definice, ne čtyři kopie.
       ⚠️ Stav pravidla nesmí nést jen barva (barvoslepost) — značka mění TVAR (○ → ✓) a
       v `.sr-only` je k tomu slovo pro odečítač obrazovky. Stejné pravidlo jako u šipek
       delt výš v tomhle souboru.
   EN: Styling for the shared partial `templates/ui/password_strength.html`. Used by
       registration, invite acceptance, setting a password from an e-mail (both reset and
       client activation) and the profile password change — one definition, not four copies.
       ⚠️ A rule's state must not rely on colour alone (colour blindness) — the marker changes
       SHAPE (○ → ✓) and an `.sr-only` word accompanies it, the same rule as the delta arrows
       further up in this file. */
.pwd-strength {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-1);
  padding: var(--space-2);
  border: 1px solid var(--line-divider);
  border-radius: var(--radius-container);
  background: var(--surface-page);
}
.pwd-strength-status {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.pwd-strength-title { font-weight: var(--weight-eyebrow); color: var(--text-primary); }
.pwd-strength-level { font-weight: var(--weight-subhead); color: var(--text-primary); }
.pwd-strength-count { margin-inline-start: auto; font-variant-numeric: tabular-nums; }

.pwd-strength-track {
  height: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--gray-tint);
  overflow: hidden;
}
.pwd-strength-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: var(--radius-pill);
  background: var(--state-danger);
  transition: width var(--transition-normal), background-color var(--transition-normal);
}
/* CZ: Šířka podle POČTU splněných pravidel (0 až 5) — proto pět tříd místo inline stylu.
       Procenta nejsou barva ani tvar, do tokenů tedy nepatří.
   EN: Width by the NUMBER of met rules (0 to 5) — hence five classes instead of an inline
       style. Percentages are neither colour nor shape, so they do not belong in the tokens. */
.pwd-strength-fill.lvl-0 { width: 0; }
.pwd-strength-fill.lvl-1 { width: 20%; }
.pwd-strength-fill.lvl-2 { width: 40%; }
.pwd-strength-fill.lvl-3 { width: 60%; }
.pwd-strength-fill.lvl-4 { width: 80%; }
.pwd-strength-fill.lvl-5 { width: 100%; }
.pwd-strength-fill.is-empty { background: var(--gray-base); }
.pwd-strength-fill.is-weak { background: var(--state-danger); }
.pwd-strength-fill.is-medium { background: var(--state-warning); }
.pwd-strength-fill.is-strong { background: var(--state-positive); }
@media (prefers-reduced-motion: reduce) {
  .pwd-strength-fill { transition: none; }
}

.pwd-strength-intro { font-size: var(--text-xs); color: var(--text-secondary); }
.pwd-strength-rules { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.pwd-strength-rule {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.pwd-strength-rule.is-met { color: var(--state-positive-text); }
/* CZ: Značka nese informaci TVAREM, ne barvou: nesplněné pravidlo má kroužek, splněné fajfku.
   EN: The marker carries the information by SHAPE, not colour: an unmet rule gets a ring, a
       met one a check mark. */
.pwd-strength-mark {
  flex-shrink: 0;
  width: 1rem;
  text-align: center;
  font-weight: var(--weight-subhead);
}
.pwd-strength-mark::before { content: "\25CB"; }
.pwd-strength-rule.is-met .pwd-strength-mark::before { content: "\2713"; }

/* =====================
 * CARD
 * ===================== */
/* CZ: Karta podle vzoru (F2): bílá, obrys 1.25 px, radius 16 px, ŽÁDNÝ stín.
       Vzor to říká výslovně: „Elevation is intentionally near-absent in this brand."
       Stín zůstává jen plovoucím vrstvám (modal, rozbalovací menu) přes `--shadow-overlay`.
       `.card` má v šablonách 1094 výskytů — tahle jedna definice tedy překreslí většinu UI.
   EN: The card per the sample (F2): white, a 1.25 px outline, 16 px radius, NO shadow.
       The sample says so outright: "Elevation is intentionally near-absent in this brand."
       Shadows remain for floating layers only (modal, dropdown) via `--shadow-overlay`.
       `.card` appears 1094 times across the templates — this single definition therefore
       repaints most of the UI. */
.card {
  background-color: var(--surface-card);
  border: var(--border-card) solid var(--line-card);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-none);
  overflow: hidden;
  position: relative;
}

/* CZ: Oranžová čepička na horní hraně — ve vzoru označuje kartu, která je „teď důležitá".
       Je to `::before`, ne rámeček, aby nezasahovala do rozměrů a šla přidat samotnou třídou.
   EN: The orange cap on the top edge — in the sample it marks the card that is "important now".
       It is a `::before`, not a border, so it does not affect the box metrics and can be added
       by the class alone. */
.card--cap::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--cap-height);
  background: var(--accent-cap);
}
.card--cap > .card-header,
.card--cap > .card-body:first-child { padding-top: calc(var(--pad-card) + var(--cap-height)); }

/* CZ: ZELENÁ čepička — hotovo, splněno. Vzor má čepičku jen v oranžové („teď důležité"), ale
       aplikace má stav, který oranžová popsat neumí: dokončený onboarding. Než tahle varianta
       vznikla (10. 9. 2026), řešil to `wizard.html` inline `style="border:1px solid
       var(--state-positive)"` — a to bylo špatně dvakrát: 1 px se lišilo od `--border-card`
       (1.25 px), takže ta jediná karta měla jiný obrys než všechny ostatní, a inline styl
       obchází kaskádu i tmavý režim.

       Je to modifikátor NAD `.card--cap`, ne samostatná třída: přebírá se jím jen barva, výška
       i odsazení obsahu zůstávají v jednom pravidle výš.
   EN: A GREEN cap — done, completed. The sample has the cap in orange only ("important now"), but
       the app has a state orange cannot describe: finished onboarding. Before this variant existed
       (10 Sep 2026) `wizard.html` did it with an inline `style="border:1px solid
       var(--state-positive)"` — wrong twice over: 1px differed from `--border-card` (1.25px), so
       that one card had a different outline from every other, and an inline style bypasses both
       the cascade and dark mode.

       It is a modifier ON TOP of `.card--cap`, not a standalone class: only the colour is taken
       from it, while the height and the content offset stay in the one rule above. */
.card--cap-positive::before { background: var(--accent-positive); }

/* CZ: Tmavá karta — v rovině řízení a na tmavých pásech. Vzor: průsvitná bílá nad navy.
   EN: The dark card — in the management plane and on dark bands. The sample: translucent white
       over navy. */
.card--on-dark {
  background-color: var(--surface-card-dark);
  border-color: var(--line-on-dark);
  color: var(--text-on-dark);
}

.card-header { padding: var(--pad-card); border-bottom: 1px solid var(--line-divider); }
.card-body { padding: var(--pad-card); }
.card-footer { padding: var(--space-2) var(--pad-card); border-top: 1px solid var(--line-divider); background-color: var(--surface-panel); }
/* CZ: Nadpis karty ve vzoru: 20 px, váha 700, tracking −0.4 px. Pod ním mezera
       `--gap-heading-body`, ne libovolná. EN: The sample's card heading: 20 px, weight 700,
       −0.4 px tracking. Below it the `--gap-heading-body` gap, not an arbitrary one. */
.card-title {
  font-family: var(--font-core);
  font-size: var(--app-h2-size);
  font-weight: var(--weight-heading);
  line-height: 1.25;
  letter-spacing: -0.4px;
}
.card-description {
  font-size: var(--body-size-sm);
  line-height: var(--body-leading);
  color: var(--text-body);
  /* CZ: Mezera nadpis→tělo je ve vzoru VLASTNÍ token, ne obecný spacing (Card.jsx:
         `marginTop: 'var(--gap-heading-body)'`). Komentář u `.card-title` to tvrdil od
         6. 9. 2026, ale kód sahal po `--space-1` — token zůstal bez konzumenta a rytmus
         vzoru se nikdy neuplatnil.
     EN: The heading→body gap is its OWN token in the sample, not generic spacing
         (Card.jsx: `marginTop: 'var(--gap-heading-body)'`). The comment on `.card-title`
         has claimed this since 6 Sep 2026 while the code reached for `--space-1` — the
         token stayed unconsumed and the sample's rhythm never took effect. */
  margin-top: var(--gap-heading-body);
}

/* =====================
 * DATA CARD (KPI tile)
 * ===================== */
/* CZ: KPI dlaždice = StatBlock ze vzoru (F5, 6. 9. 2026). Vzor ji staví jako
       EYEBROW → velké číslo → popisek, na bílé s obrysem 1.25 px a radiem 16 px.

       ⚠️ Proč tady a ne v nové třídě: `.data-card` má v šablonách **133 výskytů ve 12
       souborech**, kdežto `.stat-block` z F2 nemá ani jeden. Zavést druhou třídu pro totéž
       by znamenalo dvě jména pro jednu věc — přesně ten rozpad, kvůli kterému se dělá
       redesign. `.stat-block` proto ZANIKL a StatBlock ze vzoru je tahle třída.

       Popisek je verzálkový (`--caption-*`), ne prostý text: ve vzoru nese dlaždice
       orientaci nahoře a hodnotu dole, ne naopak.
   EN: The KPI tile IS the sample's StatBlock (F5, 6 Sep 2026). The sample builds it as
       EYEBROW → a large number → a label, on white with a 1.25 px outline and 16 px radius.

       ⚠️ Why here and not in a new class: `.data-card` appears **133 times across 12 templates**,
       while F2's `.stat-block` appears not once. Introducing a second class for the same thing
       would mean two names for one idea — precisely the drift this redesign exists to remove.
       `.stat-block` is therefore GONE and the sample's StatBlock is this class.

       The label is uppercase (`--caption-*`), not plain text: in the sample a tile carries
       orientation on top and the value below, not the other way round. */
.data-card {
  background: var(--surface-card);
  border: var(--border-card-style);
  border-radius: var(--radius-card);
  padding: var(--pad-card);
  transition: border-color var(--transition-normal);
}
.data-card:hover { border-color: var(--gray-base); }
.data-card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-1); }
.data-card-label {
  font-size: var(--caption-size);
  font-weight: var(--caption-weight);
  letter-spacing: var(--caption-tracking);
  text-transform: uppercase;
  color: var(--text-secondary);
}
.data-card-value {
  font-family: var(--font-core);
  font-size: var(--h2-size-min);
  font-weight: var(--weight-heading);
  letter-spacing: var(--h2-tracking);
  line-height: 1.1;
  margin-top: 10px;
}
.data-card-delta { font-size: var(--body-size-sm); color: var(--text-body); margin-top: 2px; }
/* CZ: Odkaz na dně dlaždice („Otevřít →") — ve vzoru modrý, 13 px, váha 500.
   EN: The link at the tile's foot ("Open →") — blue in the sample, 13 px, weight 500. */
.data-card-link { display: block; font-size: var(--body-size-sm); color: var(--text-link); font-weight: var(--weight-eyebrow); margin-top: 14px; }
/* CZ: Klikatelná KPI dlaždice — karta, která je zároveň odkaz nebo tlačítko (rozbalí seznam
       pod sebou). Resetuje nativní vzhled <button>, aby vypadala stejně jako <a> varianta,
       a kurzorem + hoverem dává najevo, že se na ni dá kliknout (dřív to byl mrtvý <div>).
   EN: A clickable KPI tile — a card that is also a link or a button (expands a list below it).
       Resets the native <button> look so it matches the <a> variant, and signals clickability
       via cursor + hover (it used to be a dead <div>). */
.card-clickable {
  display: block;
  width: 100%;
  cursor: pointer;
  font: inherit;
  color: inherit;
  text-align: inherit;
  text-decoration: none;
  background: var(--surface-page);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.card-clickable:hover { border-color: var(--orange-base); box-shadow: var(--shadow-none); }
@media (prefers-reduced-motion: reduce) { .card-clickable { transition: none; } }
/* CZ: Směr růstu/poklesu nesmí být jen barvou (barvoslepost) — přidáváme i tvarový znak
       ▲/▼/— jako prefix. EN: Growth/drop direction must not rely on color alone (color
       blindness) — prepend a shape glyph ▲/▼/— as a non-color cue. */
.data-card-delta.up { color: var(--state-positive); }
.data-card-delta.up::before { content: "▲\00a0"; }
.data-card-delta.down { color: var(--state-danger); }
.data-card-delta.down::before { content: "▼\00a0"; }
.data-card-delta.neutral { color: var(--text-secondary); }
/* CZ: Pomlčka tu značí PRÁZDNOU hodnotu („není co zobrazit"), ne oddělovač — proto en-dash (–),
      ne em-dash (—), který brand manuál v uživatelském textu zakazuje. Vkládá se z CSS, takže
      projde kolem kontrol nad šablonami; kdo hledá zakázané znaky, musí se dívat i sem.
   EN: The dash marks an EMPTY value ("nothing to show"), not a separator, so it is an en-dash (–),
      not the em-dash (—) the brand manual forbids in user-facing text. It is injected from CSS and
      therefore slips past template-level checks; anyone hunting forbidden glyphs must look here too. */
.data-card-delta.neutral::before { content: "–\00a0"; }
.data-card-icon { width: 2.5rem; height: 2.5rem; display: flex; align-items: center; justify-content: center; background: var(--orange-pale); color: var(--action-primary); border-radius: var(--radius-chip); flex-shrink: 0; }

/* =====================
 * ALERT / BADGE
 * ===================== */
.alert { padding: var(--space-2) var(--space-2); border-radius: var(--radius-chip); font-size: var(--text-sm); border: 1px solid; }
/* CZ: Text alertu = tmavá „*-text" varianta (čitelná na světlém tintu, ~6:1); border zůstává
       sytá sémantická barva pro barevné odlišení. V dark modu se oba tokeny přepínají.
   EN: Alert text = the dark "*-text" variant (readable on the light tint, ~6:1); border keeps
       the saturated semantic color for hue cueing. Both tokens flip in dark mode. */
.alert-info    { background: var(--state-info-soft);    border-color: var(--state-info);    color: var(--state-info-text); }
.alert-success { background: var(--state-positive-soft); border-color: var(--state-positive); color: var(--state-positive-text); }
.alert-warning { background: var(--state-warning-soft); border-color: var(--state-warning); color: var(--state-warning-text); }
.alert-danger  { background: var(--state-danger-soft);  border-color: var(--state-danger);  color: var(--state-danger-text); }

.badge { display: inline-flex; align-items: center; padding: var(--space-1) var(--space-1); border-radius: var(--radius-pill); font-size: var(--text-xs); font-weight: var(--weight-eyebrow); }
.badge-primary { background: var(--navy); color: var(--white); }
.badge-accent  { background: var(--orange-pale); color: var(--text-link); }
.badge-muted   { background: var(--gray-tint); color: var(--text-body); }
/* CZ: Stavové badge — zelený „hotovo" a oranžový „varování" (stavové tokeny, dark-mode-safe).
   EN: Status badges — green "done" and orange "warning" (status tokens, dark-mode-safe). */
.badge-success { background: var(--state-positive-soft); color: var(--state-positive-text); }
.badge-warning { background: var(--state-warning-soft); color: var(--state-warning-text); }
/* CZ: Odznak pro zamítnuté / kritické události v auditu. Tokeny `--color-danger-*` už existovaly
       (mají i dark override), chyběla jen utility třída — proto se nezavádí žádná nová barva.
   EN: Badge for denied / critical audit events. The `--color-danger-*` tokens already existed (with a
       dark override too), only the utility class was missing — so no new colour is introduced. */
.badge-danger  { background: var(--state-danger-soft); color: var(--state-danger-text); }
/* CZ: Informační stav („zpracovává se") — modré stavové tokeny už existovaly, chyběla třída.
   EN: Informational state ("processing") — the blue status tokens existed, the class did not. */
.badge-info    { background: var(--state-info-soft); color: var(--state-info-text); }

/* =============================================
 * NAHRÁVÁNÍ VÝKAZŮ — zóna, řádek dokumentu (21. 9. 2026)
 * CZ: Karta „Výkazy" v Souhrnu podle vzoru: přerušovaně ohraničená zóna „přetáhni sem soubor"
 *     a řádky s dlaždicí typu (PDF / XLSX), názvem, datem a stavovou pilulkou.
 * EN: The "Statements" card in the Summary per the sample: a dashed "drop a file here" zone and
 *     rows with a type tile (PDF / XLSX), name, date and a status pill.
 * ============================================= */
.upload-zone {
  display: block;
  width: 100%;
  border: 1.25px dashed var(--border);
  border-radius: var(--radius-container);
  padding: var(--space-3) var(--space-2);
  text-align: center;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  background: transparent;
  cursor: pointer;
  transition: border-color var(--duration-fast) ease, background-color var(--duration-fast) ease;
}
.upload-zone:hover,
.upload-zone.is-dragover { border-color: var(--text-link); background-color: var(--surface-panel); }
.upload-zone .link { color: var(--text-link); text-decoration: underline; }
.upload-zone-hint { display: block; margin-top: var(--space-1); font-size: var(--text-xs); }
.upload-options { margin: var(--space-2) 0 0; padding-left: var(--space-2); font-size: var(--text-sm); color: var(--text-secondary); }
.upload-options li + li { margin-top: var(--space-1); }
.upload-rules { margin: 0 0 var(--space-2); padding-left: var(--space-2); font-size: var(--text-sm); }
.upload-rules li + li { margin-top: var(--space-1); }
.upload-file {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: var(--border-card-style);
  border-radius: var(--radius-chip);
  font-size: var(--text-sm);
  margin-bottom: var(--space-2);
  overflow-wrap: anywhere;
}
.doc-list { display: grid; }
.doc-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  padding: var(--space-2) 0;
  border-top: 1px solid var(--line-divider);
}
.doc-tile {
  flex: 0 0 36px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.25px solid var(--border);
  border-radius: var(--radius-chip);
  font-size: var(--text-xs);
  font-weight: var(--weight-heading);
  color: var(--text-secondary);
}
.doc-row-main { flex: 1; min-width: 0; }
.doc-row-name { font-weight: var(--weight-subhead); overflow-wrap: anywhere; }
.doc-row-meta { font-size: var(--text-xs); color: var(--text-secondary); margin-top: 2px; }
.doc-row-note { font-size: var(--text-xs); color: var(--text-secondary); margin-top: var(--space-1); }
.doc-row-actions { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; font-size: var(--text-sm); }
/* CZ: Na telefonu jdou stav a akce pod název — v jednom řádku by se název zúžil na pár písmen.
   EN: On a phone the status and actions drop below the name — in one line the name would shrink
       to a few letters. */
@media (max-width: 640px) {
  .doc-row { flex-wrap: wrap; }
  .doc-row-main { flex-basis: calc(100% - 36px - var(--space-2)); }
  .doc-row-side { flex-basis: 100%; padding-left: calc(36px + var(--space-2)); }
}
@media (prefers-reduced-motion: reduce) { .upload-zone { transition: none; } }
/* CZ: Pole s chybou místo hodnot období, které má ve výkazu chybu (majitel R3).
   EN: An error field instead of the values of a period whose statement has a problem (owner R3). */
.fin-issue {
  border: 1.25px dashed var(--state-danger);
  border-radius: var(--radius-container);
  background: var(--state-danger-soft);
  color: var(--state-danger-text);
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
}
.fin-issue + .fin-issue { margin-top: var(--space-1); }
td.fin-issue-cell { color: var(--state-danger-text); background: var(--state-danger-soft); font-size: var(--text-xs); }

/* =============================================
 * TYP AKCE — barevný štítek (cms.EventType)
 * =============================================
 * CZ: Rozlišení typů akcí v kalendáři (rozhodnutí majitele 14. 8.). Třídy `.event-type-<klíč>`
 *     NASTAVUJÍ jen dvojici lokálních proměnných; barvu z nich čte štítek `.event-type-chip`
 *     i řádek akce ve widgetu Komunita. Klíče odpovídají `cms.models.EventTypeColor` —
 *     přidání barvy = nová hodnota v TextChoices + jedna třída tady, žádný hex v šabloně.
 *     Dvojice je vždy „tint pozadí + tmavý/světlý text téže rodiny", takže kontrast drží AA
 *     v obou režimech (dark override řeší `tokens.css` u podkladových tokenů).
 * EN: Telling event types apart in the calendar (owner decision 14 Aug). The
 *     `.event-type-<key>` classes only SET a pair of local variables; the colour is read from
 *     them by the `.event-type-chip` and by the event row in the Community widget. The keys
 *     match `cms.models.EventTypeColor` — adding a colour means one more TextChoices value
 *     plus one class here, never a hex in a template. The pair is always "tint background +
 *     the family's dark/light text", so the contrast holds AA in both themes (the dark
 *     override lives on the underlying tokens in `tokens.css`). */
.event-type-neutral { --event-type-soft: var(--gray-tint); --event-type-text: var(--text-body); }
.event-type-brand   { --event-type-soft: var(--orange-pale);  --event-type-text: var(--orange-text); }
.event-type-lime    { --event-type-soft: var(--green-pale);   --event-type-text: var(--green-text); }
.event-type-orange  { --event-type-soft: var(--orange-pale); --event-type-text: var(--orange-text); }
.event-type-blue    { --event-type-soft: var(--blue-pale);   --event-type-text: var(--blue-text); }
.event-type-dark    { --event-type-soft: var(--gray-pale);   --event-type-text: var(--text-primary); }

.event-type-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-1);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-eyebrow);
  line-height: 1.2;
  background: var(--event-type-soft);
  color: var(--event-type-text);
}

/* CZ: Ikona se nesmí smrsknout ve flexu ani přetéct nad text. Rozměr je tu i v CSS jako
       POJISTKA: atribut `width` v SVG jde přebít neplatnou hodnotou z kontextu (přesně to se
       stalo 6. 9. 2026 — do partialu se propašovalo `size="s"` z widgetové plochy a ikona se
       roztáhla přes celou kartu). CSS neplatný atribut přebije vždy.
   EN: The icon must not shrink in the flex row nor overflow the text. The size is repeated here
       in CSS as a SAFETY NET: an SVG's `width` attribute can be overridden by an invalid value
       from the context (exactly what happened on 6 Sep 2026 — `size="s"` leaked in from the
       widget desk and the icon stretched across the whole card). CSS always wins over a broken
       attribute. */
.event-type-chip svg { flex: none; width: 14px; height: 14px; }

/* =====================
 * META LIST (definiční seznam „popisek → hodnota")
 * CZ: `<dl class="meta-list">` s páry `<dt>`/`<dd>` — firemní údaje, brief preview, karta
 *     leadu, koučský panel klienta. Třídu šablony používaly, ale v CSS chyběla (no-op),
 *     takže se páry vykreslovaly jako výchozí odsazený `<dl>` bez zarovnaného sloupce.
 *     Na velmi úzké obrazovce (≤480 px) se dvousloupcová mřížka sbalí pod sebe.
 * EN: `<dl class="meta-list">` with `<dt>`/`<dd>` pairs — company details, brief preview,
 *     lead card, coach client panel. Templates used the class but the CSS was missing
 *     (no-op), so the pairs rendered as a default indented `<dl>` with no aligned column.
 *     On very narrow screens (≤480px) the two-column grid collapses to stacked rows.
 * ===================== */
.meta-list {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: var(--space-1) var(--space-2);
  margin: 0;
  font-size: var(--text-sm);
}
.meta-list dt { color: var(--text-secondary); font-weight: var(--weight-eyebrow); }
/* CZ: `overflow-wrap` — dlouhé e-maily/URL nesmí roztáhnout mřížku přes šířku karty. */
/* EN: `overflow-wrap` — long emails/URLs must not stretch the grid past the card width. */
.meta-list dd { margin: 0; min-width: 0; color: var(--text-primary); overflow-wrap: anywhere; }
@media (max-width: 480px) {
  .meta-list { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .meta-list dd { margin-bottom: var(--space-1); }
}

/* =====================
 * SPINNER (točící se indikace probíhající operace)
 * CZ: Kroužek s jedním zvýrazněným segmentem; barva segmentu = `currentColor`, takže si
 *     bere barvu textu okolo (dark-mode-safe bez vlastního tokenu). Používá `_ai_job.html`
 *     u pollingu AI úlohy — element je `aria-hidden`, stav nese sousední text („Generuji…").
 * EN: A ring with one highlighted segment; the segment uses `currentColor`, so it inherits
 *     the surrounding text color (dark-mode-safe without its own token). Used by
 *     `_ai_job.html` while polling an AI job — the element is `aria-hidden`, the adjacent
 *     text carries the state ("Generating…").
 * ===================== */
.spinner {
  display: inline-block;
  flex: 0 0 auto;
  width: 1rem;
  height: 1rem;
  border: 2px solid var(--gray-base);
  border-top-color: currentColor;
  border-radius: var(--radius-pill);
  animation: spinner-rotate 0.8s linear infinite;
}
@keyframes spinner-rotate { to { transform: rotate(360deg); } }
/* CZ: Bez rotace, když si uživatel vyžádal omezení pohybu — kroužek zůstane statický
       (informaci nese text vedle, takže se nic neztrácí).
   EN: No rotation when the user asked for reduced motion — the ring stays static (the
       adjacent text carries the information, so nothing is lost). */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
}

/* =====================
 * UTILITIES
 * ===================== */
.container { max-width: var(--max-content-width); margin: 0 auto; padding: 0 var(--space-2); }
.container-narrow { max-width: 640px; margin: 0 auto; padding: 0 var(--space-2); }
.container-reading { max-width: var(--max-reading-width); margin: 0 auto; padding: 0 var(--space-2); }

.stack-1 > * + * { margin-top: var(--space-1); }
.stack-2 > * + * { margin-top: var(--space-1); }
.stack-3 > * + * { margin-top: var(--space-2); }
.stack-4 > * + * { margin-top: var(--space-2); }
.stack-6 > * + * { margin-top: var(--space-3); }
.stack-8 > * + * { margin-top: var(--space-4); }

.grid { display: grid; gap: var(--space-2); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 768px) {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}
@media (min-width: 769px) and (max-width: 1024px) {
  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
}

.text-xs { font-size: var(--text-xs); }
.text-sm { font-size: var(--text-sm); }
.text-base { font-size: var(--text-base); }
.text-lg { font-size: var(--text-lg); }
.text-muted { color: var(--text-secondary); }
.text-secondary { color: var(--text-body); }
.text-danger { color: var(--state-danger); }
/* CZ: `-text` krok, ne syrový `--state-positive`. `--green-base` (#92C01F) má na bílé jen
       ~2.4:1 a jako TEXT propadá WCAG AA (práh 4.5:1) — pravidlo 4 v `CLAUDE.md`: sytá barva
       patří na plochy, text bere `-text`. `--green-text` (#4A6B10) dává 6.17:1.

       Vedle sebe to bylo vidět: `.text-warning` i `.text-info` `-text` krok už používaly, jen
       `.text-success` zůstala na sytém odstínu. Pět míst v průvodci konzultací ji proto
       OBCHÁZELO inline stylem `style="color: var(--state-positive-text)"`, každé s vlastním
       komentářem, proč utilitu nepoužilo. Po téhle opravě inline styly mizí.

       `.text-danger` se NEMĚNÍ: `--state-danger` (#D92D20) dává na bílé 4.83:1, tedy AA splňuje.
   EN: The `-text` step, not the raw `--state-positive`. `--green-base` (#92C01F) is only ~2.4:1
       on white and fails WCAG AA as TEXT (4.5:1 threshold) — rule 4 in `CLAUDE.md`: the saturated
       shade belongs on surfaces, text takes `-text`. `--green-text` (#4A6B10) gives 6.17:1.

       It showed side by side: `.text-warning` and `.text-info` already used the `-text` step and
       only `.text-success` stayed on the saturated shade. Five places in the consultation wizard
       therefore BYPASSED it with an inline `style="color: var(--state-positive-text)"`, each with
       its own comment on why the utility was not used. This fix removes those inline styles.

       `.text-danger` is UNCHANGED: `--state-danger` (#D92D20) is 4.83:1 on white, so it meets AA. */
.text-success { color: var(--state-positive-text); }
.text-center { text-align: center; }
/* CZ: `.text-right` je logický protějšek `.text-center` (číselné sloupce tabulek).
   EN: `.text-right` is the logical counterpart of `.text-center` (numeric table columns). */
.text-right { text-align: right; }
.font-medium { font-weight: var(--weight-eyebrow); }
.font-semibold { font-weight: var(--weight-subhead); }
.font-bold { font-weight: var(--weight-heading); }
/* CZ: Přeškrtnutí splněného úkolu (wizard _actions) — dosud no-op třída bez efektu.
   EN: Strike-through for a completed task (wizard _actions) — previously a no-op class. */
.line-through { text-decoration: line-through; }
/* CZ: Hover podbarvení řádku (dotazníky, konzultace) — třída „hover:bg-surface-muted"
       se v šablonách používá, ale bez definice nic nedělala. EN: Row hover tint (surveys,
       consultations) — the "hover:bg-surface-muted" class was used but had no definition. */
.hover\:bg-surface-muted:hover { background: var(--gray-tint); }

.flex { display: flex; }
.flex-col { flex-direction: column; }
/* CZ: .flex-wrap / .items-start — šablony je používaly, ale utility v CSS chyběly (no-op).
       Doplněním se aktivuje zalamování akčních/filtračních lišt na mobilu (žádná změna na
       desktopu, kde se řádek vejde a nezalomí). EN: .flex-wrap / .items-start — templates
       already used these but the utilities were missing (no-op). Adding them activates
       wrapping of action/filter rows on mobile (no change on desktop, where the row fits). */
.flex-wrap { flex-wrap: wrap; }
/* CZ: Zalomení POUZE na mobilu (≤768px). Pro řádky s full-width `.input` (width:100%) — ty by
       při bezpodmínečném .flex-wrap skočily na vlastní řádek i na desktopu; takto zůstane
       desktop řádek nezměněný a na mobilu se pole přehledně nastackují pod sebe.
   EN: Wrap ONLY on mobile (≤768px). For rows with full-width `.input` (width:100%) — an
       unconditional .flex-wrap would push them to their own line on desktop too; this keeps the
       desktop row intact and stacks the fields on mobile. */
@media (max-width: 768px) { .flex-wrap-mobile { flex-wrap: wrap; } }
.items-start { align-items: flex-start; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.justify-center { justify-content: center; }
.gap-1 { gap: var(--space-1); }
.gap-2 { gap: var(--space-1); }
.gap-3 { gap: var(--space-2); }
.gap-4 { gap: var(--space-2); }
.gap-6 { gap: var(--space-3); }

.mb-2 { margin-bottom: var(--space-1); }
/* CZ: `.mb-3` / `.mt-3` scházely, přestože je šablony napříč aplikací používaly (wizard,
       obsah, finance, koučské partialy) — byly to no-op třídy, mezera se nevykreslila.
       Doplněno do škály, ať šablony nemusí sahat po inline stylu.
   EN: `.mb-3` / `.mt-3` were missing even though templates across the app used them (wizard,
       content, financials, coach partials) — they were no-op classes and no spacing rendered.
       Added to the scale so templates don't have to fall back to inline styles. */
.mb-3 { margin-bottom: var(--space-2); }
.mb-4 { margin-bottom: var(--space-2); }
.mb-6 { margin-bottom: var(--space-3); }
.mt-2 { margin-top: var(--space-1); }
.mt-3 { margin-top: var(--space-2); }
.mt-4 { margin-top: var(--space-2); }
.mt-6 { margin-top: var(--space-3); }
.mt-8 { margin-top: var(--space-4); }
.py-2 { padding-top: var(--space-1); padding-bottom: var(--space-1); }
.py-4 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.py-6 { padding-top: var(--space-3); padding-bottom: var(--space-3); }
.py-12 { padding-top: var(--space-6); padding-bottom: var(--space-6); }

.w-full { width: 100%; }
.max-w-md { max-width: 28rem; }
.max-w-lg { max-width: 32rem; }

/* ---------------------------------------------------------------------------
   CZ: Responzivní viditelnost — .desktop-only se skryje na mobilu (≤768px),
       .mobile-only se skryje na tabletu/desktopu (>768px). Pro hustá rozhraní
       (např. KB tabulka), která nedávají na malé obrazovce smysl → místo nich
       se ukáže .mobile-only hláška.
   EN: Responsive visibility — .desktop-only hides on mobile (≤768px), .mobile-only
       hides on tablet/desktop (>768px). For dense UIs (e.g. the KB table) that don't
       make sense on a small screen → a .mobile-only notice is shown instead.
   --------------------------------------------------------------------------- */
.mobile-only { display: none; }
@media (max-width: 768px) {
  .desktop-only { display: none !important; }
  .mobile-only { display: block; }
}

/* CZ: Alpine x-cloak — skryje element, dokud se Alpine neinicializuje (zamezí probliknutí).
   EN: Alpine x-cloak — hides the element until Alpine initializes (prevents flicker). */
[x-cloak] { display: none !important; }

/* ---------------------------------------------------------------------------
   CZ: .sr-only — vizuálně skryté, ale čtené screen readerem (labely, popisky).
   EN: .sr-only — visually hidden but exposed to screen readers (labels, hints).
   --------------------------------------------------------------------------- */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------------
   CZ: Skip link „Přeskočit na obsah" — skrytý, dokud nedostane focus klávesnicí
       (WCAG 2.4.1). Pak vyjede přes topbar a odkáže na <main id="main-content">.
   EN: "Skip to content" link — hidden until it receives keyboard focus (WCAG 2.4.1).
       Then it slides over the topbar and targets <main id="main-content">.
   --------------------------------------------------------------------------- */
.skip-link {
  position: fixed;
  top: var(--space-1);
  left: var(--space-1);
  z-index: var(--z-tooltip);
  padding: var(--space-1) var(--space-2);
  background: var(--action-primary);
  color: var(--white);
  border-radius: var(--radius-chip);
  box-shadow: var(--shadow-none);
  transform: translateY(-150%);
  transition: transform var(--transition-fast);
}
.skip-link:focus { transform: translateY(0); text-decoration: none; }

/* ---------------------------------------------------------------------------
   CZ: Modal / potvrzovací dialog ve stylu aplikace (nahrazuje nativní confirm()).
   EN: App-styled modal / confirmation dialog (replaces the native confirm()).
   --------------------------------------------------------------------------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2);
  /* CZ: Závoj je NAVY s alfou, ne černý — vzor má `rgba(15,17,21,.5)`, tedy tutéž barvu jako
         `--navy`. Čistá černá pod navy modalem vypadá jako díra; navy závoj vypadá jako
         ztlumená stránka. Alfu tokenem vyjádřit nejde, proto zůstává literál (výjimka
         s důvodem v `test_css_tokens.py`).
     EN: The scrim is NAVY with alpha, not black — the sample uses `rgba(15,17,21,.5)`, the same
         colour as `--navy`. Pure black under a navy modal reads as a hole; a navy scrim reads as
         a dimmed page. Alpha cannot be expressed by a token, hence the literal (an exception with
         a reason in `test_css_tokens.py`). */
  background-color: rgba(15, 17, 21, 0.5);
}
/* CZ: Modal podle vzoru (F6): radius 16 px jako karta, šířka 560 px, padding 32 px.
       Stín zůstává — je to JEDINÁ role, kterou vzor stínu přiznává („modals / floating menus
       only"). `--shadow-overlay` je proto na modalu na místě, kdežto na kartě ne.
       Zmizely hardcoded fallbacky `var(--shadow-overlay, var(--shadow-none))` a `var(--text-lg, 1.125rem)`
       — `CLAUDE.md` je zakazuje, protože obcházejí dark mode; oba tokeny navíc existují.
   EN: The modal per the sample (F6): a 16 px radius like a card, 560 px wide, 32 px padding.
       The shadow stays — it is the ONE role the sample grants a shadow ("modals / floating menus
       only"). `--shadow-overlay` therefore belongs on a modal but not on a card.
       The hardcoded fallbacks `var(--shadow-overlay, var(--shadow-none))` and `var(--text-lg, 1.125rem)`
       are gone — `CLAUDE.md` forbids them because they bypass dark mode, and both tokens exist. */
.modal {
  width: 92%;
  max-width: 560px;
  background-color: var(--surface-overlay);
  border: var(--border-card-style);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-overlay);
  padding: var(--space-4);
}
.modal-title {
  font-family: var(--font-core);
  font-size: var(--app-h2-size);
  font-weight: var(--weight-heading);
  letter-spacing: -0.3px;
  margin: 0 0 var(--space-1) 0;
}
.modal-body {
  color: var(--text-body);
  margin: 0 0 var(--space-3) 0;
}
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* CZ: PLOVOUCÍ PANEL (rozbalovací menu) — 9. 9. 2026.
       Vzniklo z nálezu: přepínač jazyka používal `class="card absolute"`. `.card` má
       `--surface-card`, což je v tmavém režimu `rgba(255,255,255,.08)` — pod panelem by
       prosvítal text stránky a nečitelné by byly OBĚ vrstvy. Přesně past, před kterou varuje
       `CLAUDE.md` (pravidlo 10); stráž `test_overlay_layers_have_an_opaque_background` ji
       nechytila, protože prohledává SELEKTORY v CSS, a tenhle panel žádný vlastní neměl.

       ⚠️ Cokoli, co PŘEKRÝVÁ obsah, má dostat tuhle třídu — ne `.card`. Panel si nese celou
       smlouvu překryvu: neprůhledné pozadí, obrys, radius kontejneru, stín (jediná role, kterou
       vzor stínu přiznává) a `z-index`.
   EN: A FLOATING PANEL (dropdown menu) — 9 Sep 2026.
       Born of a finding: the language switcher used `class="card absolute"`. `.card` carries
       `--surface-card`, which in dark mode is `rgba(255,255,255,.08)` — the page text would
       bleed through and BOTH layers would be unreadable. Exactly the trap `CLAUDE.md` warns
       about (rule 10); `test_overlay_layers_have_an_opaque_background` did not catch it because
       it scans CSS SELECTORS, and this panel had none of its own.

       ⚠️ Anything that COVERS content gets this class, not `.card`. The panel carries the whole
       overlay contract: an opaque background, a border, the container radius, a shadow (the one
       role the sample grants a shadow) and a `z-index`. */
.dropdown-panel {
  position: absolute;
  background: var(--surface-overlay);
  border: var(--border-card-style);
  border-radius: var(--radius-container);
  box-shadow: var(--shadow-overlay);
  z-index: var(--z-dropdown);
  padding: var(--space-1);
}

/* =====================
 * VOICE INPUT (diktování — mikrofon u textových polí)
 * Generuje JS partial templates/partials/voice_input.html.
 * ===================== */
.voice-input-wrapper {
  position: relative;
  display: block;
  width: 100%;
}
.voice-mic-btn {
  position: absolute;
  right: var(--space-1);
  bottom: var(--space-1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-1);
  width: 1.9rem;
  height: 1.9rem;
  color: var(--text-secondary);
  background-color: var(--surface-page);
  border: 1px solid var(--line-card);
  border-radius: var(--radius-chip);
  cursor: pointer;
  z-index: 5;
  transition: color var(--transition-fast), border-color var(--transition-fast),
    background-color var(--transition-fast);
}
/* CZ: U jednořádkového inputu je pole nižší — vycentruj mikrofon svisle. */
/* EN: Single-line inputs are shorter — vertically center the mic. */
.voice-input-wrapper > input.input + .voice-mic-btn { bottom: 50%; transform: translateY(50%); }
.voice-mic-btn:hover { color: var(--action-primary); border-color: var(--action-primary); }
.voice-mic-btn.recording {
  color: var(--state-danger);
  border-color: var(--state-danger);
  animation: voice-pulse 1.5s infinite;
}
.voice-mic-btn.processing { color: var(--action-primary); border-color: var(--action-primary); }
.voice-mic-btn.processing svg { animation: voice-spin 1s linear infinite; }
.voice-timer {
  position: absolute;
  right: 2.6rem;
  bottom: var(--space-1);
  font-size: var(--caption-size-sm);
  font-weight: var(--weight-eyebrow);
  font-family: var(--font-mono);
  color: var(--text-secondary);
  background-color: var(--surface-page);
  border: 1px solid var(--line-card);
  padding: 0 var(--space-1);
  border-radius: var(--radius-chip);
  z-index: 5;
}
.voice-timer.danger { color: var(--state-danger); border-color: var(--state-danger); }
@keyframes voice-pulse {
  0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.45); }
  70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
  100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
@keyframes voice-spin { to { transform: rotate(360deg); } }
/* CZ: Toast pro chyby hlasového vstupu (odmítnutý mikrofon, selhání přepisu, rate limit).
   EN: Toast for voice-input errors (denied mic, failed transcription, rate limit). */
.voice-toast {
  position: fixed;
  right: var(--space-2);
  bottom: var(--space-2);
  max-width: 20rem;
  z-index: 1000;
  padding: var(--space-1) var(--space-2);
  font-size: var(--app-text-secondary-size);
  line-height: 1.35;
  color: var(--text-on-danger);
  background-color: var(--state-danger);
  border-radius: var(--radius-chip);
  box-shadow: var(--shadow-none);
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.voice-toast.visible { opacity: 1; transform: translateY(0); }

/* CZ: Globální toast pro HTMX chyby (audit §2.2) — tiché selhání HTMX akce zobrazí uživateli
       chybu místo „nic se nestalo". Stohovatelný kontejner vpravo dole.
   EN: Global toast for HTMX errors (audit §2.2) — a silently failing HTMX action shows the user
       an error instead of "nothing happened". Stackable container in the bottom-right. */
.app-toast-container {
  position: fixed;
  right: var(--space-2);
  bottom: var(--space-2);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  pointer-events: none;
}
.app-toast {
  max-width: 20rem;
  padding: var(--space-1) var(--space-2);
  font-size: var(--app-text-secondary-size);
  line-height: 1.35;
  color: var(--text-on-danger);
  background-color: var(--state-danger);
  border-radius: var(--radius-chip);
  box-shadow: var(--shadow-none);
}

/* =============================================================================
 * KOMPONENTY ZE VZORU / COMPONENTS FROM THE SAMPLE
 * =============================================================================
 * CZ: Šest prvků, které dodaný vzor má a aplikace dosud neměla (F2, 6. 9. 2026).
 *     Zdroj: `_design_vzor/rozbaleno/design-system-komponenty.js` — přepsáno z JSX na
 *     třídy, protože projekt zakazuje React (`CLAUDE.md`, „žádný JS framework za HTMX
 *     a Alpine"). Jsou bezstavové, takže se `:hover` obejde bez JS.
 * EN: Six elements the supplied sample has and the app did not (F2, 6 Sep 2026).
 *     Source: `_design_vzor/rozbaleno/design-system-komponenty.js` — rewritten from JSX into
 *     classes, because the project forbids React (`CLAUDE.md`, "no JS framework beyond HTMX
 *     and Alpine"). They are stateless, so `:hover` needs no JS.
 * ============================================================================= */

/* CZ: Eyebrow — malý verzálkový popisek NAD nadpisem. Vzor: „Orientation, not message."
       Nese orientaci („Dění v přístavu"), ne sdělení; sdělení patří do nadpisu pod ním.
   EN: Eyebrow — a small uppercase label ABOVE a heading. The sample: "Orientation, not message."
       It carries orientation ("What's happening"), not the message; the message goes in the
       heading below it. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-core);
  font-size: var(--caption-size);
  font-weight: var(--caption-weight);
  letter-spacing: var(--caption-tracking);
  text-transform: uppercase;
  color: var(--text-secondary);
}
.eyebrow-orange { color: var(--orange-text); }
.eyebrow-blue   { color: var(--blue-text); }
.eyebrow-green  { color: var(--green-text); }

/* CZ: Marker — rovný oranžový pruh za JEDNÍM klíčovým slovem, protínající účaří.
       Vzor: „~1/3–40 % výšky písmene", proto `--marker-height`. `::before` leží POD textem
       (z-index), aby zvýraznění nepřekrylo písmena.
   EN: Marker — a straight orange bar behind ONE key word, crossing the baseline.
       The sample: "~1/3–40% of letter height", hence `--marker-height`. The `::before` sits
       BELOW the text (z-index) so the highlight does not cover the letters. */
.marker {
  position: relative;
  display: inline;
  z-index: 0;
}
.marker::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: -0.08em;
  right: -0.08em;
  bottom: var(--marker-offset);
  height: var(--marker-height);
  background: var(--marker);
}
/* CZ: Varianta `block` vyplní celé slovo — na tmavém pozadí a v nadpisech.
   EN: The `block` variant fills the whole word — on dark grounds and in headings. */
.marker-block::before { height: 100%; bottom: 0; }

/* CZ: PillBadge — malý plný odznak (počty, „za 12 dní", stav). Vždy pilulka, vždy ČERNÝ text:
       ve vzoru má `color: var(--black)` i na sytě oranžové. Právě odtud pochází důkaz, že
       černý text na oranžové je záměr vzoru, ne kompromis (viz F1, NR-07).
   EN: PillBadge — a small solid badge (counts, "in 12 days", a status). Always a pill, always
       BLACK text: in the sample it is `color: var(--black)` even on saturated orange. This is
       where the proof comes from that black-on-orange is the sample's intent, not a compromise
       (see F1, NR-07). */
.pill-badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-core);
  font-size: var(--caption-size);
  font-weight: var(--weight-subhead);
  letter-spacing: -0.1px;
  line-height: 1;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--orange-base);
  color: var(--black);
}
/* CZ: Tónové varianty mají text `--text-primary`, ne `--black` jako plná oranžová výše.
       Důvod je dark mode, který vzor neřeší: `--orange-base` zůstává v obou režimech sytě
       oranžová, takže na ní černá sedí vždy — ale `--orange-tint` je v tmavém režimu POZADÍ
       a ztmavne na #4E340B, kde černý text spadne na 1.82:1. `--text-primary` se překlápí
       s režimem, takže je černý na světlém tintu a bílý na tmavém.
       Chytil to kontrastní test až po rozšíření na tmavou paletu (F1) — okem to v light módu
       vypadalo správně.
   EN: The tint variants use `--text-primary`, not `--black` like the solid orange above.
       The reason is dark mode, which the sample does not cover: `--orange-base` stays saturated
       orange in both themes, so black always sits well on it — but `--orange-tint` is a
       BACKGROUND in dark mode and darkens to #4E340B, where black text drops to 1.82:1.
       `--text-primary` flips with the theme, so it is black on the light tint and white on the
       dark one. Caught by the contrast test only once it covered the dark palette (F1) — by eye,
       in light mode, it looked correct. */
.pill-badge-orange-tint { background: var(--orange-tint); color: var(--text-primary); }
.pill-badge-blue-tint   { background: var(--blue-tint);   color: var(--text-primary); }
.pill-badge-green-tint  { background: var(--green-tint);  color: var(--text-primary); }
.pill-badge-gray        { background: var(--gray-tint);   color: var(--text-primary); }

/* CZ: StatBlock ze vzoru NEMÁ vlastní třídu — je to `.data-card` (viz KPI dlaždice výš).
       Ve F2 tu `.stat-block` chvíli existoval, ale nikdy se nepoužil: aplikace už tutéž věc
       uměla pod jménem `.data-card` se 133 výskyty. Dvě jména pro jednu věc jsou horší než
       neshoda se jmenoslovím vzoru, takže zvítězilo to jméno, které má obsah.
   EN: The sample's StatBlock has NO class of its own — it is `.data-card` (see the KPI tile
       above). F2 briefly defined `.stat-block` here, but it was never used: the app already did
       the same thing under the name `.data-card` with 133 usages. Two names for one idea are
       worse than diverging from the sample's naming, so the name with content won. */

/* =============================================================================
 * DATOVÉ TABULKY / DATA TABLES (F6, 6. 9. 2026)
 * =============================================================================
 * CZ: ⚠️ ODVOZENO, NE PŘEVZATO. Vzor nemá ani JEDNU tabulku — a aplikace jich má 24 ve
 *     21 souborech, dosud bez jakéhokoli stylu (nativní vzhled prohlížeče). Odvození
 *     z pravidel vzoru:
 *
 *       · Záhlaví = EYEBROW (verzálky, tracking 2). Vzor tímhle značí orientaci všude jinde;
 *         název sloupce je přesně to.
 *       · Řádky dělí LINKA, ne pruhované pozadí. Vzor pracuje s obrysem a bílou plochou;
 *         zebra by přidala druhou plochu, kterou jeho jazyk nezná.
 *       · Žádný stín, žádný rámeček kolem celé tabulky — ta bydlí uvnitř karty, která obrys
 *         už má. Dvojitý rámeček je nejčastější vada tabulky v kartě.
 *
 *     Selektor míří na `table` uvnitř `.main-content`, ne na třídu: tabulky v šablonách
 *     žádnou společnou třídu nemají (`cf-table`, `table w-full`, `cms-table__table`, nic),
 *     takže třída by je minula. Takhle podědí vzhled všech 24, aniž bych otevřel 21 souborů.
 *     Specifičtější pravidla (`cms-table`, `.ai-content`) zůstávají nad tímhle a vyhrávají.
 * EN: ⚠️ DERIVED, NOT ADOPTED. The sample has not a SINGLE table — and the app has 24 across
 *     21 files, so far with no styling at all (the browser's default look). Derived from the
 *     sample's rules:
 *
 *       · The header is an EYEBROW (uppercase, 2 tracking). The sample marks orientation this
 *         way everywhere else; a column name is exactly that.
 *       · Rows are separated by a LINE, not by zebra striping. The sample works with an outline
 *         and a white surface; striping would add a second surface its language does not know.
 *       · No shadow and no frame around the whole table — it lives inside a card that already
 *         has an outline. A double frame is the commonest defect of a table inside a card.
 *
 *     The selector targets `table` inside `.main-content`, not a class: the templates share no
 *     common table class (`cf-table`, `table w-full`, `cms-table__table`, none), so a class would
 *     miss them. This way all 24 inherit the look without opening 21 files. More specific rules
 *     (`cms-table`, `.ai-content`) sit above this and still win.
 * ============================================================================= */
/* CZ: Text tabulky je 14px (`--app-body-size`), ne 15px. Do 16. 9. 2026 tu stálo
       `--body-size-sm`, což je značková hodnota pro web — tabulka pak byla o krok větší než
       zbytek aplikace. Výška řádku je nově TOKEN, ne výsledek sčítání: dokud vycházela
       z aritmetiky (12+12 padding + 15px × 1.6 = 48.0), znamenalo zmenšení textu tichý pokles
       na 46.4 px a celá mřížka roviny řízení by se posunula, aniž by to kdokoli napsal.
   EN: Table text is 14px (`--app-body-size`), not 15px. Until 16 Sep 2026 this read
       `--body-size-sm`, a brand value meant for the web — the table then ran one step larger
       than the rest of the app. The row height is now a TOKEN rather than a sum: while it fell
       out of arithmetic (12+12 padding + 15px × 1.6 = 48.0), shrinking the text meant a silent
       drop to 46.4px, shifting the whole management-plane grid with nobody having written it. */
.main-content table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--app-body-size);
}
.main-content thead th {
  text-align: left;
  padding: var(--space-1) var(--app-table-cell-padding);
  font-size: var(--app-caption-size);
  font-weight: var(--app-caption-weight);
  letter-spacing: var(--app-caption-tracking);
  text-transform: uppercase;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--line-divider);
  white-space: nowrap;
}
.main-content tbody td {
  height: var(--app-table-row-height);
  padding: var(--app-table-cell-padding);
  border-bottom: 1px solid var(--line-divider);
  vertical-align: top;
  color: var(--text-primary);
}
.main-content tbody tr:last-child td { border-bottom: 0; }
/* CZ: Hover celého řádku — v hustých provozních tabulkách je to jediné, co udrží oko na řádku.
   EN: A whole-row hover — in dense operational tables it is the one thing keeping the eye on
       the row. */
.main-content tbody tr:hover td { background: var(--surface-panel); }

/* CZ: Čísla se zarovnávají doprava a sázejí tabulkovými číslicemi, aby řády seděly pod sebou.
       Bez `tabular-nums` mají v Poppins různě široké číslice a sloupec se „vlní".
   EN: Numbers align right and use tabular figures so orders of magnitude line up. Without
       `tabular-nums` Poppins gives digits different widths and the column "waves". */
.main-content td.num,
.main-content th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* CZ: Tabulka se na úzké obrazovce nesmršťuje — roluje se vodorovně uvnitř svého obalu.
       Obal musí být v šabloně (`<div class="table-scroll">`), protože `overflow` na `table`
       nefunguje. EN: A table does not shrink on a narrow screen — it scrolls horizontally inside
       its wrapper. The wrapper must be in the template (`<div class="table-scroll">`), because
       `overflow` on a `table` has no effect. */
.table-scroll { overflow-x: auto; }

/* CZ: Výstražný pruh ze vzoru (F5) — oranžový tint přes celou šířku, uvozený tučným „Pozor:".
       Vzor ho používá v rovině řízení na věc, která NEČEKÁ: propadlé onboardingy, uvízlí
       klienti. Není to chybová hláška (ta má vlastní barvu), je to upozornění na náskok práce.

       Text je `--text-primary` na `--orange-pale`, ne oranžový na oranžové: hlídaná dvojice
       v `contrast_pairs.py`. Akce vpravo přes `margin-left: auto`, jak to má vzor.
   EN: The sample's alert strip (F5) — a full-width orange tint led by a bold "Pozor:" (Heads up:).
       The sample uses it in the management plane for something that WILL NOT WAIT: stalled
       onboardings, stuck clients. It is not an error message (that has its own colour); it flags
       a backlog of work.

       The text is `--text-primary` on `--orange-pale`, not orange on orange: a guarded pair in
       `contrast_pairs.py`. The action sits right via `margin-left: auto`, as the sample has it. */
.alert-strip {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: wrap;
  background: var(--orange-pale);
  border-radius: var(--radius-container);
  padding: 14px 20px;
  font-size: var(--body-size-sm);
  color: var(--text-primary);
}
.alert-strip-label { font-weight: var(--weight-subhead); }
.alert-strip-action { margin-left: auto; white-space: nowrap; }

/* CZ: SectionDivider ze vzoru NEMÁ vlastní třídu — je to `.cms-divider` v `blocks.css`.
       Ve F2 tu `.section-divider` chvíli existoval, ale nikdy se nepoužil; oddělovač, který
       v šablonách reálně je, se jmenuje `.cms-divider`. Stejné pravidlo jako u `.stat-block`:
       vyhrává jméno, které má obsah.
   EN: The sample's SectionDivider has NO class of its own — it is `.cms-divider` in `blocks.css`.
       F2 briefly defined `.section-divider` here, but it was never used; the divider the
       templates actually carry is `.cms-divider`. Same rule as for `.stat-block`: the name with
       content wins. */

/* CZ: Hlavička karty člena — kolečko s iniciálami + jméno. Vzor: 48 px kruh na šedém tintu,
       váha 600. Kolečko má `flex: 0 0 48px`, aby se u dlouhého jména nesmrsklo do elipsy.
   EN: A member card's header — a circle of initials plus the name. The sample: a 48 px circle on
       the grey tint, weight 600. The circle uses `flex: 0 0 48px` so a long name cannot squash it
       into an ellipse. */
.member-head { display: flex; gap: 14px; align-items: center; }
.member-head-main { min-width: 0; }
.member-avatar {
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-pill);
  background: var(--gray-tint);
  color: var(--text-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--app-h3-size);
  font-weight: var(--weight-subhead);
  letter-spacing: 0.5px;
}

/* CZ: Profilová fotka uvnitř kolečka (23. 9. 2026, `ui/avatar.html`). Velikost a tvar
       dědí z kolečka (`member-avatar`, `people-avatar`, `team-avatar`, `topbar-avatar`),
       `object-fit: cover` drží poměr stran. `overflow: hidden` na rodiči ořízne roh fotky.
   EN: The profile photo inside the circle (23 Sep 2026, `ui/avatar.html`). Size and shape come
       from the circle (`member-avatar`, `people-avatar`, `team-avatar`, `topbar-avatar`);
       `object-fit: cover` keeps the aspect ratio. `overflow: hidden` on the parent clips it. */
.has-photo { overflow: hidden; padding: 0; }

.avatar-photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* CZ: Náhled fotky v profilu — větší kolečko vedle tlačítek pro nahrání / odebrání.
   EN: The photo preview on the profile page — a larger circle next to the upload / remove buttons. */
.profile-avatar {
  flex: 0 0 96px;
  width: 96px;
  height: 96px;
  border-radius: var(--radius-pill);
  background: var(--gray-tint);
  color: var(--text-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--app-h3-size);
  font-weight: var(--weight-subhead);
}

/* CZ: EventCard — karta akce: oranžová čepička, eyebrow s typem, odznak s odpočtem,
       datum/místo jako caption a JEDNO CTA. Staví na `.card`, přidává jen rozvržení.
   EN: EventCard — an event card: an orange cap, an eyebrow with the type, a countdown badge,
       date/place as a caption and ONE CTA. Built on `.card`, adding only the layout. */
.event-card { display: flex; flex-direction: column; gap: var(--space-1); }
.event-card-head { display: flex; align-items: center; gap: var(--space-1); flex-wrap: wrap; margin-bottom: var(--space-1); }
.event-card-meta { font-size: var(--body-size-sm); color: var(--text-secondary); }
.event-card-footer { margin-top: var(--space-3); }

/* CZ: Chip podle vzoru (F2): tónované pozadí, tmavý text, radius 8 px (NE pilulka — vzor drží
       „jeden radius na roli" a pilulka patří tlačítkům a odznakům). Vybraný chip má o stupeň
       sytější tint, ne jinou barvu — proto zůstane čitelný a nepřepne se na inverzní plochu.
       Barva textu je vždy `-text` varianta rampy: sytý `base` odstín by na světlém tintu
       propadl AA.
   EN: The chip per the sample (F2): a tinted background, dark text, 8 px radius (NOT a pill —
       the sample keeps "one radius per role" and the pill belongs to buttons and badges). A
       selected chip gets one step deeper tint, not a different colour — so it stays legible and
       never flips to an inverse surface. The text colour is always the ramp's `-text` variant:
       the saturated `base` shade would fail AA on the light tint. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border-radius: var(--radius-chip);
  border: 1px solid transparent;
  background: var(--gray-pale);
  color: var(--gray-text);
  font-family: var(--font-core);
  font-size: var(--app-text-secondary-size);
  font-weight: var(--weight-eyebrow);
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--transition-normal), border-color var(--transition-normal), color var(--transition-normal);
}
.chip:hover { background: var(--gray-tint); color: var(--text-primary); text-decoration: none; }
.chip:focus-visible { outline: 2px solid var(--action-primary); outline-offset: 2px; }
.chip-active,
.chip-active:hover { background: var(--gray-tint); border-color: var(--gray-tint); color: var(--text-primary); }

/* CZ: Tónové varianty chipu — modrá / oranžová / zelená. Dvojice tint + `-text` jsou hlídané
       v `web/contrast_pairs.py`. EN: The chip's tone variants — blue / orange / green. Each
       tint + `-text` pair is guarded in `web/contrast_pairs.py`. */
.chip-blue   { background: var(--blue-pale);   color: var(--blue-text); }
.chip-blue:hover,
.chip-blue.chip-active   { background: var(--blue-tint);   border-color: var(--blue-tint);   color: var(--blue-text); }
.chip-orange { background: var(--orange-pale); color: var(--orange-text); }
.chip-orange:hover,
.chip-orange.chip-active { background: var(--orange-tint); border-color: var(--orange-tint); color: var(--orange-text); }
.chip-green  { background: var(--green-pale);  color: var(--green-text); }
.chip-green:hover,
.chip-green.chip-active  { background: var(--green-tint);  border-color: var(--green-tint);  color: var(--green-text); }

/* CZ: ŘADA FILTRŮ OŘÍZNUTÁ NA DVA ŘÁDKY (10. 9. 2026).

       Filtry jsou OBSAH, ne kód: kategorií videí je čtrnáct, kmenových skupin dvacet sedm a
       přibývají tím, že je někdo založí. `flex-wrap` sám o sobě nestačí — zalomí je sice, ale do
       pěti řádků, které sežerou obrazovku dřív, než začne to, kvůli čemu člověk přišel.

       Ořez je proto na dva řádky a zbytek se odkryje tlačítkem. `overflow: hidden` skrývá jen
       vizuálně, takže schované chipy zůstávají v DOM — najde je hledání v prohlížeči i čtečka.
       Ta se ale nesmí dozvědět, že „nic není": proto expanzi ovládá `aria-expanded` na tlačítku,
       ne `hidden` na obsahu.

       ⚠️ Bez JS zůstane řada oříznutá. Tlačítko je progresivní vylepšení (Alpine); kdyby se
       skript nenačetl, filtry v prvních dvou řádcích pořád fungují jako odkazy.
   EN: A FILTER ROW CLAMPED TO TWO LINES (10 Sep 2026).

       Filters are CONTENT, not code: there are fourteen video categories, twenty-seven tribe
       groups, and they grow whenever someone creates one. `flex-wrap` alone is not enough — it
       does wrap them, into five rows that eat the screen before the thing the user came for.

       So the row is clamped to two lines and the rest is revealed by a button. `overflow: hidden`
       hides only visually, so the clipped chips stay in the DOM — browser find and screen readers
       still reach them. A reader must not be told "there is nothing there", hence the state lives
       in `aria-expanded` on the button, not in `hidden` on the content.

       ⚠️ Without JS the row stays clamped. The button is progressive enhancement (Alpine); if the
       script never loads, the filters in the first two rows still work as plain links. */
.chip-rows {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  max-height: calc(var(--chip-height) * 2 + var(--space-2));
  overflow: hidden;
}
.chip-rows.is-expanded { max-height: none; overflow: visible; }

/* =====================
   CZ: Onboarding wizard (member-community v3) — progress lišta + krokové řádky.
       Vše přes design tokeny (dark-mode-safe), žádné hardcoded hodnoty.
   EN: Onboarding wizard (member-community v3) — progress bar + step rows.
       All via design tokens (dark-mode-safe), no hardcoded values.
   ===================== */
.onb-progress-track {
  height: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--gray-tint);
  overflow: hidden;
}
.onb-progress-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--state-positive);
  transition: width var(--transition-normal);
}
.onb-step {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--line-card);
  border-radius: var(--radius-container);
  background: var(--gray-pale);
}
.onb-step.is-done { border-color: var(--state-positive); }
.onb-step.is-next { border-color: var(--action-primary); }
.onb-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-pill);
  background: var(--gray-tint);
  color: var(--text-body);
  font-weight: var(--weight-subhead);
  font-size: var(--text-sm);
}
.onb-step.is-done .onb-step-num { background: var(--state-positive); color: var(--white); }
.onb-step-body { flex: 1 1 auto; min-width: 0; }
.onb-step-title { font-weight: var(--weight-subhead); font-size: var(--text-base); }
.onb-step-desc { font-size: var(--text-sm); color: var(--text-secondary); margin-top: var(--space-1); }
.onb-step-action { flex: 0 0 auto; align-self: center; }
@media (max-width: 640px) {
  .onb-step { flex-wrap: wrap; }
  .onb-step-action { align-self: stretch; width: 100%; }
  .onb-step-action .btn { width: 100%; }
}

/* =====================
   CZ: Konzultační wizard (consult-wizard UX) — dvousloupcový layout se sticky datovým
       panelem, sticky spodní lišta s primární akcí fáze, řádky otevřených závazků
       a sbalitelné sekce datového panelu. Vše přes tokeny (dark-mode-safe), bez hexů.
   EN: Consultation wizard (consult-wizard UX) — two-column layout with a sticky data
       panel, sticky bottom bar holding the phase's primary action, open-commitment rows
       and collapsible data-panel sections. All via tokens (dark-mode-safe), no hexes.
   ===================== */

/* CZ: Poměr 2:3 — datový panel je užší než pracovní plocha; `minmax(0, …)` brání přetečení
       dlouhým obsahem (grid item má implicitní min-width:auto).
   EN: 2:3 ratio — the data panel is narrower than the work area; `minmax(0, …)` prevents
       overflow from long content (a grid item defaults to min-width:auto). */
.wizard-split {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  gap: var(--space-3);
  align-items: start;
}
/* CZ: Sticky offset musí počítat s topbarem (`layout.css .topbar`: sticky, top:0,
       height `--header-height`, z-index `--z-sticky`). Datový panel nemá vlastní stacking
       context ani z-index, takže by se při scrollu zasunul POD lištu a schoval hlavičku
       karty i sbalovací tlačítko. Stejný vzor jako `.sidebar` (top: var(--header-height)).
       `max-height` je o topbar zkrácená, aby panel nepřetekl pod spodní hranu viewportu.
   EN: The sticky offset must account for the topbar (`layout.css .topbar`: sticky, top:0,
       height `--header-height`, z-index `--z-sticky`). The data panel has no stacking
       context and no z-index, so it would slide UNDER the bar and hide the card header and
       its collapse button. Same pattern as `.sidebar` (top: var(--header-height)).
       `max-height` is shortened by the topbar so the panel does not run past the viewport. */
.wizard-split-data {
  position: sticky;
  top: calc(var(--header-height) + var(--space-3));
  align-self: start;
  max-height: calc(100vh - var(--header-height) - var(--space-6));
  overflow-y: auto;
}
.wizard-split-work { min-width: 0; }
/* CZ: Pod 1024 px jeden sloupec a sticky vypnuté — na malé výšce by panel ukrajoval obrazovku.
   EN: Below 1024px a single column with sticky off — on short viewports it would eat the screen. */
@media (max-width: 1024px) {
  .wizard-split { grid-template-columns: 1fr; }
  .wizard-split-data { position: static; max-height: none; overflow-y: visible; }
}

/* CZ: Spodní lišta s jedinou primární akcí fáze („Zahájit" / „K výstupům" / „Publikovat").
       `bottom: 0` + `--z-sticky` ji drží nad obsahem při scrollu pracovního sloupce.
   EN: Bottom bar with the phase's single primary action ("Start" / "To outputs" / "Publish").
       `bottom: 0` + `--z-sticky` keeps it above the content while the work column scrolls. */
.wizard-actionbar {
  position: sticky;
  bottom: 0;
  z-index: var(--z-sticky);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-2);
  background: var(--gray-pale);
  border-top: 1px solid var(--line-card);
  border-radius: 0 0 var(--radius-container) var(--radius-container);
}
.wizard-actionbar-info {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
/* CZ: `flex-wrap: wrap` je povinné — `.btn` má `white-space: nowrap`, takže dvojice
       „Zrušit konzultaci" + primární akce by na 320–360 px lištu přetekla a stránka by
       dostala vodorovný scroll.
   EN: `flex-wrap: wrap` is required — `.btn` is `white-space: nowrap`, so the "Cancel
       consultation" + primary action pair would overflow the bar at 320–360px and give the
       whole page a horizontal scrollbar. */
.wizard-actionbar-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  flex: 0 0 auto;
}
/* CZ: Tlačítka NEJSOU přímé flex položky — „Zrušit konzultaci" je uvnitř `<div>` z
       `partials/confirm_modal.html` a primární akce uvnitř `<form>`. Roztahovat se proto
       musí ten obal, a tlačítko v něm dostane `width: 100%`. Selektory jdou jen o jednu
       úroveň hluboko, takže se NEtýkají tlačítek uvnitř `.modal-overlay` (ta jsou hlouběji).
   EN: The buttons are NOT direct flex items — "Cancel consultation" sits inside a `<div>`
       from `partials/confirm_modal.html` and the primary action inside a `<form>`. So the
       wrapper must stretch and the button inside it gets `width: 100%`. The selectors reach
       exactly one level deep, so buttons inside `.modal-overlay` (deeper) are unaffected. */
@media (max-width: 640px) {
  .wizard-actionbar { align-items: stretch; }
  .wizard-actionbar-actions { width: 100%; }
  .wizard-actionbar-actions > .btn,
  .wizard-actionbar-actions > form,
  .wizard-actionbar-actions > div { flex: 1 1 auto; min-width: 0; }
  .wizard-actionbar-actions > form > .btn,
  .wizard-actionbar-actions > div > .btn { width: 100%; }
}

/* CZ: Řádek otevřeného závazku (úkol z minulé konzultace / výzva). Levý pruh nese stav,
       aby se dal odlišit i periferním viděním v dlouhém seznamu.
   EN: Open-commitment row (task from a past consultation / challenge). The left rail carries
       the state so it is distinguishable at a glance in a long list. */
.commitment-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2);
  background: var(--surface-page);
  border: 1px solid var(--line-card);
  border-left: 3px solid var(--gray-base);
  border-radius: var(--radius-chip);
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.commitment-row:hover { background: var(--gray-pale); }
.commitment-row-body { flex: 1 1 auto; min-width: 0; }
.commitment-row-text {
  font-size: var(--text-sm);
  font-weight: var(--weight-eyebrow);
  color: var(--text-primary);
}
.commitment-row-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.commitment-row-due { font-weight: var(--weight-eyebrow); }
.commitment-row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  flex: 0 0 auto;
}

/* CZ: Po termínu — sytý stav: červený rám, tint pozadí a `*-text` varianta (na tintu ~6:1).
       Signál NENÍ jen barevný (barvoslepost) — termín dostane i tvarový prefix ⚠.
   EN: Overdue — loud state: danger border, tinted background and the `*-text` variant
       (~6:1 on the tint). The cue is not color-only (color blindness) — the due date also
       gets a ⚠ shape prefix. */
.commitment-row.commitment-overdue {
  background: var(--state-danger-soft);
  border-color: var(--state-danger);
  border-left-color: var(--state-danger);
}
.commitment-row.commitment-overdue:hover { background: var(--state-danger-soft); }
.commitment-overdue .commitment-row-due {
  color: var(--state-danger-text);
  font-weight: var(--weight-subhead);
}
.commitment-overdue .commitment-row-due::before { content: "⚠\00a0"; }

@media (max-width: 640px) {
  .commitment-row { flex-direction: column; align-items: stretch; }
  .commitment-row-actions { width: 100%; }
}

/* CZ: Hlavička sbalitelné sekce datového panelu (<button aria-expanded>) — šipka se otáčí
       podle `aria-expanded`, takže stav drží ARIA, ne CSS třída navíc.
   EN: Header of a collapsible data-panel section (<button aria-expanded>) — the caret rotates
       from `aria-expanded`, so ARIA owns the state instead of an extra CSS class. */
.wizard-section-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1);
  width: 100%;
  padding: var(--space-1) 0;
  background: transparent;
  border: 0;
  color: var(--text-primary);
  font-family: var(--font-core);
  font-size: var(--text-sm);
  font-weight: var(--weight-subhead);
  text-align: start;
  cursor: pointer;
}
.wizard-section-toggle:hover { color: var(--text-link); }
.wizard-section-toggle:focus-visible { outline: 2px solid var(--text-link); outline-offset: 2px; }
.wizard-section-toggle::after {
  content: "▾";
  flex: 0 0 auto;
  color: var(--text-secondary);
  transition: transform var(--transition-fast);
}
.wizard-section-toggle[aria-expanded="false"]::after { transform: rotate(-90deg); }

/* CZ: Seznamy poznámek / úkolů / navržených otázek ve wizardu. Šablony tyto třídy používaly,
       ale v CSS neexistovaly (no-op) — odrážky a odsazení proto řešil inline `style`.
       Definice je záměrně shodná s tím inline stylem, takže se nic nepřekreslí jinak;
       šablony můžou inline styl odstranit (viz požadavek na T2).
   EN: Note / action / suggested-question lists in the wizard. Templates used these classes
       but they did not exist in the CSS (no-op) — bullets and indentation were handled by an
       inline `style`. The definition deliberately matches that inline style, so nothing
       renders differently; the templates can then drop the inline style (see the T2 request). */
.note-list,
.action-list,
.question-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.note-item,
.action-item {
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--line-card);
}
/* CZ: Poslední řádek bez dělící linky — jinak vypadá seznam „useknutě" u paty karty. */
/* EN: No divider on the last row — otherwise the list looks cut off at the card footer. */
.note-item:last-child,
.action-item:last-child { border-bottom: 0; }

/* CZ: Bez animací, když si to uživatel přeje (prefers-reduced-motion).
   EN: No animation when the user asks for it (prefers-reduced-motion). */
@media (prefers-reduced-motion: reduce) {
  .commitment-row,
  .wizard-section-toggle::after { transition: none; }
}

/* ===========================================================================
 * STAVOVÉ KOMPONENTY / STATE COMPONENTS
 * CZ: Styly pro sdílené partialy `partials/empty_state.html`,
 *     `partials/htmx_indicator.html` a `partials/form_errors.html`.
 * EN: Styles for the shared partials `partials/empty_state.html`,
 *     `partials/htmx_indicator.html` and `partials/form_errors.html`.
 * =========================================================================== */

/* ---------------------------------------------------------------------------
   CZ: Prázdný stav seznamu — centrovaný blok s ikonou, nadpisem, popisem a CTA.
       Text je tlumený (muted), aby prázdno nekřičelo; nadpis zůstává v primární
       barvě, protože nese informaci, a CTA (.btn-primary) je vizuální kotva.
   EN: List empty state — a centered block with icon, title, description and CTA.
       The copy is muted so emptiness does not shout; the title keeps the primary
       color because it carries the information, and the CTA is the visual anchor.
   --------------------------------------------------------------------------- */
/* CZ: Prázdný stav PODLE VZORU (16. 9. 2026). Vzor ho staví jedním způsobem
       (`BrAIen-Pristav-X.dc.html:613`):
         `border:1px dashed #D0D0D0; border-radius:8px; padding:20px 12px;
          text-align:center; font-size:13px; color:#878786`
       — čárkovaný rám, BEZ výplně, BEZ ikony, drobným šedým písmem.

       Aplikace měla navíc šedou výplň, radius 12 a odsazení 40px, takže prázdný stav působil
       jako plná karta místo jako tiché místo, kde nic není. Výplň a větší radius jsou pryč.

       Nadpis a CTA ZŮSTÁVAJÍ, i když je vzor nemá: R8 vyžaduje, aby prázdný stav nabídl další
       krok. Vzor to řeší uvnitř věty („Žádná nová výzva · kouč ti další zadá na sezení."),
       aplikace k tomu má navíc tlačítko — to je přídavek NAD vzor, ne proti němu.
   EN: The empty state PER THE SAMPLE (16 Sep 2026), which builds it exactly one way
       (`BrAIen-Pristav-X.dc.html:613`): a dashed rule, NO fill, NO icon, small grey type.
       The app added a grey fill, a 12px radius and 40px padding, so an empty state read as a
       full card rather than a quiet place where nothing is. Fill and radius are corrected.
       The title and CTA STAY though the sample lacks them: R8 requires the next step. The
       sample carries it inside the sentence; the app adds a button — an addition above the
       sample, not against it. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: 20px 12px;
  text-align: center;
  border: 1px dashed var(--line-card);
  border-radius: var(--radius-chip);
}

/* CZ: Ikona je dekorace. Od 14. 9. 2026 je to růstový symbol značky (`ui/brand_symbol.html`),
       ne emoji — brand manuál §7 ikonovou sadu nezná a emoji pouští jen do newsletteru a na
       social. `font-size` tu zůstává, protože symbol se škáluje v `em`: velikost se dědí
       z textu a nemusí se psát dvakrát.
   EN: The icon is decoration. Since 14 Sep 2026 it is the brand's growth symbol
       (`ui/brand_symbol.html`), not an emoji — brand manual §7 knows no icon set and admits
       emoji only in the newsletter and on social. `font-size` stays here because the symbol
       scales in `em`: the size inherits from the text and need not be written twice. */

/* CZ: Růstový symbol — poměr stran 332:354 drží `aspect-ratio`, takže stačí zadat výšku
       (v `em`, aby se vázala na okolní text). `display:block` odstraní mezeru pod inline SVG.
   EN: The growth symbol — `aspect-ratio` holds the 332:354 proportions, so only the height is
       set (in `em`, tying it to the surrounding text). `display:block` removes the gap that
       sits under an inline SVG. */
.brand-symbol {
  display: block;
  height: 1em;
  width: auto;
  aspect-ratio: 332 / 354;
  margin-inline: auto;
}

.empty-state-title {
  margin: 0;
  font-family: var(--font-core);
  font-size: var(--text-lg);
  font-weight: var(--weight-subhead);
  color: var(--text-primary);
}

.empty-state-message {
  /* CZ: 13px a šedá, jak je má vzor. EN: 13px and grey, as the sample has it. */
  max-width: var(--max-reading-width);
  margin: 0;
  font-size: var(--app-text-secondary-size);
  line-height: var(--body-leading);
  color: var(--text-secondary);
}

/* CZ: Odsazení CTA od textu — `gap` sám dává jen základní mezeru, tady chceme víc.
   EN: Extra room above the CTA — `gap` alone gives only the base spacing. */
.empty-state-action { margin-top: var(--space-1); }

/* CZ: Kompaktní varianta do karet a užších sloupců (méně vertikálního prostoru).
   EN: Compact variant for cards and narrow columns (less vertical padding). */
.empty-state-sm { padding: var(--space-3) var(--space-2); }
.empty-state-sm .empty-state-icon { font-size: var(--text-2xl); }
.empty-state-sm .empty-state-title { font-size: var(--text-base); }

/* ---------------------------------------------------------------------------
   CZ: HTMX indikátor načítání. Skrývání řeší base.css (`.htmx-indicator { opacity: 0 }`
       a `.htmx-request .htmx-indicator { opacity: 1 }`) — htmx přidá `.htmx-request`
       na spouštěcí prvek po dobu requestu. Tady se to jen doplňuje: schovaný
       indikátor nesmí chytat kliky (byl by neviditelná past nad obsahem).
       Záměrně NEpoužíváme `display: none`, jinak by přechod opacity neproběhl a
       rozbily by se existující indikátory v šablonách.
   EN: HTMX loading indicator. Hiding lives in base.css (`.htmx-indicator { opacity: 0 }`
       and `.htmx-request .htmx-indicator { opacity: 1 }`) — htmx puts `.htmx-request` on
       the triggering element for the duration of the request. This only completes it: a
       hidden indicator must not capture clicks (it would be an invisible trap over the
       content). We deliberately avoid `display: none`, which would kill the opacity
       transition and break the indicators already used across templates.
   --------------------------------------------------------------------------- */
.htmx-indicator { pointer-events: none; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { pointer-events: auto; }

/* CZ: Vizuál sdíleného indikátoru — spinner + volitelný popisek na jednom řádku.
       Animaci nese `.spinner`, který má vlastní `prefers-reduced-motion` fallback výše.
   EN: Look of the shared indicator — spinner plus optional label on one line. The
       animation lives in `.spinner`, which has its own `prefers-reduced-motion` fallback. */
.loading-indicator {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  vertical-align: middle;
}
.loading-indicator-label { line-height: var(--leading-snug); }

/* CZ: Blokový režim — indikátor na vlastním řádku, vycentrovaný (typicky pod seznamem
       při „načíst další" nebo nad oblastí, kterou HTMX prohazuje).
   EN: Block mode — the indicator on its own centered line (typically under a list for
       "load more", or above the region HTMX swaps). */
.loading-indicator-block {
  display: flex;
  justify-content: center;
  width: 100%;
  padding: var(--space-2) 0;
}

/* ---------------------------------------------------------------------------
   CZ: Souhrn chyb nad formulářem (GOV.UK „error summary"). Kontrast: pozadí je světlý
       tint `--state-danger-soft`, text proto `--state-danger-text` (~6:1) — sytá
       `--state-danger` by na tintu dala jen ~3:1 a propadla by AA. Levý pruh navíc
       nese signál tvarem, ne jen barvou (barvoslepost).
   EN: Error summary above a form (GOV.UK style). Contrast: the background is the light
       `--state-danger-soft` tint, so the text uses `--state-danger-text` (~6:1) — the
       saturated `--state-danger` would be only ~3:1 on the tint and fail AA. The left
       rail carries the signal by shape, not by color alone (color blindness).
   --------------------------------------------------------------------------- */
.form-errors {
  padding: var(--space-2);
  margin-bottom: var(--space-2);
  background: var(--state-danger-soft);
  border: 1px solid var(--state-danger);
  border-left: 4px solid var(--state-danger);
  border-radius: var(--radius-chip);
  color: var(--state-danger-text);
}
/* CZ: Blok se fokusuje programově (tabindex="-1") — focus musí být vidět.
   EN: The block is focused programmatically (tabindex="-1") — the focus must be visible. */
.form-errors:focus-visible {
  outline: 2px solid var(--state-danger);
  outline-offset: 2px;
}

.form-errors-title {
  margin: 0 0 var(--space-1);
  font-family: var(--font-core);
  font-size: var(--text-base);
  font-weight: var(--weight-subhead);
  color: var(--state-danger-text);
}

.form-errors-list {
  margin: 0;
  padding-left: var(--space-3);
  font-size: var(--text-sm);
  line-height: var(--body-leading);
}
.form-errors-item + .form-errors-item { margin-top: var(--space-1); }

/* CZ: Odkazy na vadná pole — podtržení navíc k barvě, aby odkaz nebyl rozpoznatelný
       jen barvou (WCAG 1.4.1).
   EN: Links to the offending fields — underlined on top of the color, so the link is not
       identified by color alone (WCAG 1.4.1). */
.form-errors-link {
  color: var(--state-danger-text);
  font-weight: var(--weight-eyebrow);
  text-decoration: underline;
}
.form-errors-link:hover { text-decoration-thickness: 2px; }
.form-errors-link:focus-visible {
  outline: 2px solid var(--state-danger);
  outline-offset: 2px;
  border-radius: var(--radius-chip);
}

/* ===========================================================================
 * ATOMICKÉ UTILITY / ATOMIC UTILITIES
 * CZ: Doplněk k utilitám výše — pokrývá inline `style=""` deklarace, které se
 *     v šablonách opakovaly nejčastěji (margin/padding/gap/šířky/flex/text).
 *     Cíl: šablona nemá důvod sáhnout po inline stylu. Všechny hodnoty jdou
 *     z tokenů (`--spacing-*`, `--text-*`, `--color-*`, `--radius-*`), takže
 *     dark mode i změna škály se propíší automaticky. Nic výše se nemění —
 *     tato sekce jen přidává. Výjimky bez tokenu: 0, 100%, auto, 1px rám
 *     a rem hodnoty `max-w-*` (drží se stávající škály `.max-w-md/.max-w-lg`).
 * EN: Companion to the utilities above — covers the inline `style=""` declarations
 *     that repeated most often across templates (margin/padding/gap/width/flex/text).
 *     Goal: a template never needs an inline style. All values come from tokens
 *     (`--spacing-*`, `--text-*`, `--color-*`, `--radius-*`), so dark mode and any
 *     scale change propagate automatically. Nothing above is modified — this section
 *     only adds. Token-less exceptions: 0, 100%, auto, the 1px border and the rem
 *     `max-w-*` values (matching the existing `.max-w-md/.max-w-lg` scale).
 * =========================================================================== */

/* ---------------------------------------------------------------------------
   CZ: MARGIN — `.m-0` je zdaleka nejčastější inline styl (reset `<p>`/`<h*>`/`<ul>`
       uvnitř karet). `.mx-auto` centruje blok s `max-width`.
   EN: MARGIN — `.m-0` is by far the most common inline style (resetting `<p>`/`<h*>`/
       `<ul>` inside cards). `.mx-auto` centers a block that has a `max-width`.
   --------------------------------------------------------------------------- */
.m-0 { margin: 0; }
.mx-auto { margin-left: auto; margin-right: auto; }
.ml-auto { margin-left: auto; }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: var(--space-1); }
.mt-5 { margin-top: var(--space-3); }
.mt-12 { margin-top: var(--space-6); }
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: var(--space-1); }
.mb-5 { margin-bottom: var(--space-3); }
.mb-8 { margin-bottom: var(--space-4); }
.mb-12 { margin-bottom: var(--space-6); }
.ml-1 { margin-left: var(--space-1); }
.ml-2 { margin-left: var(--space-1); }
.mr-1 { margin-right: var(--space-1); }
.mr-2 { margin-right: var(--space-1); }

/* ---------------------------------------------------------------------------
   CZ: PADDING — `.p-*` je všesměrové, `.px-*`/`.py-*` osové, `.pt-*`/`.pb-*` jednostranné.
       Kombinace `padding: var(--space-1) var(--space-2)` = `.py-2 .px-3`.
   EN: PADDING — `.p-*` is all sides, `.px-*`/`.py-*` axial, `.pt-*`/`.pb-*` single side.
       The `padding: var(--space-1) var(--space-2)` combo = `.py-2 .px-3`.
   --------------------------------------------------------------------------- */
.p-0 { padding: 0; }
.p-1 { padding: var(--space-1); }
.p-2 { padding: var(--space-1); }
.p-3 { padding: var(--space-2); }
.p-4 { padding: var(--space-2); }
.p-5 { padding: var(--space-3); }
.p-6 { padding: var(--space-3); }
.px-0 { padding-left: 0; padding-right: 0; }
.px-1 { padding-left: var(--space-1); padding-right: var(--space-1); }
.px-2 { padding-left: var(--space-1); padding-right: var(--space-1); }
.px-3 { padding-left: var(--space-2); padding-right: var(--space-2); }
.px-4 { padding-left: var(--space-2); padding-right: var(--space-2); }
.px-6 { padding-left: var(--space-3); padding-right: var(--space-3); }
.py-0 { padding-top: 0; padding-bottom: 0; }
.py-1 { padding-top: var(--space-1); padding-bottom: var(--space-1); }
.py-3 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.py-8 { padding-top: var(--space-4); padding-bottom: var(--space-4); }
.pt-0 { padding-top: 0; }
.pt-1 { padding-top: var(--space-1); }
.pt-2 { padding-top: var(--space-1); }
.pt-3 { padding-top: var(--space-2); }
.pt-4 { padding-top: var(--space-2); }
.pb-0 { padding-bottom: 0; }
.pb-1 { padding-bottom: var(--space-1); }
.pb-2 { padding-bottom: var(--space-1); }
.pb-3 { padding-bottom: var(--space-2); }
.pb-4 { padding-bottom: var(--space-2); }

/* ---------------------------------------------------------------------------
   CZ: GAP — doplnění škály (`.gap-1`–`.gap-4`, `.gap-6` už existují výše).
       Pozor na mapování z inline hodnot: `.25rem` = `--space-1`, `.5rem` =
       `--space-1`, `.75rem` = `--space-2`, `1rem` = `--space-2`.
   EN: GAP — completes the scale (`.gap-1`–`.gap-4`, `.gap-6` already exist above).
       Mapping from raw inline values: `.25rem` = `--space-1`, `.5rem` =
       `--space-1`, `.75rem` = `--space-2`, `1rem` = `--space-2`.
   --------------------------------------------------------------------------- */
.gap-0 { gap: 0; }
.gap-5 { gap: var(--space-3); }
.gap-8 { gap: var(--space-4); }

/* ---------------------------------------------------------------------------
   CZ: DISPLAY — `.hidden` je tvrdé skrytí (nezaměňovat s `.sr-only`, které nechává
       obsah čitelný pro screen reader, ani s `[x-cloak]`, které řeší Alpine).
   EN: DISPLAY — `.hidden` hides outright (not to be confused with `.sr-only`, which
       keeps content readable for screen readers, nor with `[x-cloak]` for Alpine).
   --------------------------------------------------------------------------- */
.block { display: block; }
.inline-block { display: inline-block; }
.inline { display: inline; }
.inline-flex { display: inline-flex; }
.hidden { display: none; }

/* ---------------------------------------------------------------------------
   CZ: FLEX & GRID položky — `.min-w-0` je nutné u flex/grid potomků s dlouhým
       textem (implicitní `min-width: auto` jinak přeteče a stránka dostane
       vodorovný scroll). `.grid-full` roztáhne položku přes všechny sloupce.
   EN: FLEX & GRID items — `.min-w-0` is required on flex/grid children holding long
       text (the implicit `min-width: auto` otherwise overflows and gives the page a
       horizontal scrollbar). `.grid-full` spans an item across all columns.
   --------------------------------------------------------------------------- */
.flex-row { flex-direction: row; }
.flex-1 { flex: 1 1 0%; }
.flex-auto { flex: 1 1 auto; }
.flex-none { flex: 0 0 auto; }
.flex-shrink-0 { flex-shrink: 0; }
.min-w-0 { min-width: 0; }
.grid-full { grid-column: 1 / -1; }
.items-baseline { align-items: baseline; }
.items-end { align-items: flex-end; }
.items-stretch { align-items: stretch; }
.justify-start { justify-content: flex-start; }
.justify-end { justify-content: flex-end; }
.self-center { align-self: center; }
.self-start { align-self: flex-start; }
.self-end { align-self: flex-end; }

/* ---------------------------------------------------------------------------
   CZ: ROZMĚRY — `.max-w-*` navazuje na existující škálu `.max-w-md` (28rem) /
       `.max-w-lg` (32rem). `.max-w-reading` a `.max-w-content` jdou z layout tokenů.
   EN: SIZING — `.max-w-*` continues the existing `.max-w-md` (28rem) / `.max-w-lg`
       (32rem) scale. `.max-w-reading` and `.max-w-content` come from layout tokens.
   --------------------------------------------------------------------------- */
.w-auto { width: auto; }
.h-full { height: 100%; }
.h-auto { height: auto; }
.max-w-full { max-width: 100%; }
.max-w-sm { max-width: 24rem; }
.max-w-xl { max-width: 36rem; }
.max-w-2xl { max-width: 42rem; }
.max-w-3xl { max-width: 48rem; }
.max-w-4xl { max-width: 56rem; }
.max-w-5xl { max-width: 64rem; }
.max-w-reading { max-width: var(--max-reading-width); }
.max-w-content { max-width: var(--max-content-width); }

/* ---------------------------------------------------------------------------
   CZ: TYPOGRAFIE — dorovnání velikostní škály (`--text-xl` a výš) a zarovnání.
       Barvy textu jen přes tokeny; na světlém pozadí se pro accent používá
       `--text-link` (4.98:1), NIKDY `--orange-base` (2.84:1).
   EN: TYPOGRAPHY — completes the size scale (`--text-xl` and up) and alignment.
       Text colors via tokens only; on a light surface the accent is
       `--text-link` (4.98:1), NEVER `--orange-base` (2.84:1).
   --------------------------------------------------------------------------- */
.text-xl { font-size: var(--text-xl); }
.text-2xl { font-size: var(--text-2xl); }
.text-3xl { font-size: var(--text-3xl); }
.text-4xl { font-size: var(--text-4xl); }
.text-5xl { font-size: var(--text-5xl); }
.text-left { text-align: left; }
.text-primary { color: var(--text-primary); }
.text-inverse { color: var(--white); }
.text-accent { color: var(--text-link); }
.text-warning { color: var(--state-warning-text); }
.text-info { color: var(--state-info-text); }
.text-inherit { color: inherit; }
.font-normal { font-weight: var(--weight-body); }
.leading-tight { line-height: var(--leading-tight); }
.leading-snug { line-height: var(--leading-snug); }
.leading-normal { line-height: var(--body-leading); }
.uppercase { text-transform: uppercase; }
.no-underline { text-decoration: none; }
.underline { text-decoration: underline; }
/* CZ: Tabulární číslice — čísla ve sloupci se nerozjíždějí (finanční tabulky, KPI).
   EN: Tabular figures — numbers in a column stay aligned (financial tables, KPIs). */
.tabular-nums { font-variant-numeric: tabular-nums; }
.whitespace-nowrap { white-space: nowrap; }
.whitespace-pre-wrap { white-space: pre-wrap; }
.whitespace-pre-line { white-space: pre-line; }
/* CZ: Dlouhé e-maily/URL nesmí roztáhnout kartu — zalom i uprostřed slova.
   EN: Long emails/URLs must not stretch a card — allow mid-word breaking. */
.break-words { overflow-wrap: anywhere; word-break: break-word; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* CZ: Odkaz bez vizuálu odkazu — celá karta/řádek je klikatelná, ale text si drží
       barvu okolí. Používat JEN tam, kde je klikatelnost zřejmá z kontextu (celá
       karta), ne pro odkaz uvnitř odstavce — tam by chyběl netextový signál.
   EN: A link without link styling — the whole card/row is clickable while the text
       keeps the surrounding color. Use ONLY where clickability is obvious from the
       context (a whole card), never for an inline link inside a paragraph. */
.link-plain { color: inherit; text-decoration: none; }
.link-plain:hover { text-decoration: none; }

/* ---------------------------------------------------------------------------
   CZ: SEZNAMY — `.list-none` ruší jen odrážky; `.list-reset` navíc smaže margin a
       padding (nejčastější kombinace `list-style:none; margin:0; padding:0`).
   EN: LISTS — `.list-none` only drops the bullets; `.list-reset` also clears margin
       and padding (the common `list-style:none; margin:0; padding:0` trio).
   --------------------------------------------------------------------------- */
.list-none { list-style: none; }
.list-reset { list-style: none; margin: 0; padding: 0; }

/* ---------------------------------------------------------------------------
   CZ: RÁMY, POZADÍ, ZAOBLENÍ — vždy tokeny, aby dark mode přepnul barvu sám.
   EN: BORDERS, BACKGROUNDS, RADII — always tokens, so dark mode flips them for free.
   --------------------------------------------------------------------------- */
.border { border: 1px solid var(--line-card); }
.border-top { border-top: 1px solid var(--line-card); }
.border-bottom { border-bottom: 1px solid var(--line-card); }
.border-none { border: 0; }
.border-collapse { border-collapse: collapse; }
.rounded-sm { border-radius: var(--radius-chip); }
.rounded-md { border-radius: var(--radius-chip); }
.rounded-lg { border-radius: var(--radius-container); }
.rounded-xl { border-radius: var(--radius-card); }
.rounded-full { border-radius: var(--radius-pill); }
.bg-base { background: var(--surface-page); }
.bg-subtle { background: var(--gray-pale); }
.bg-muted { background: var(--gray-tint); }
.bg-transparent { background: transparent; }

/* ---------------------------------------------------------------------------
   CZ: OSTATNÍ — `.overflow-x-auto` je povinný obal pro široké tabulky (jinak
       roluje celá stránka vodorovně, viz docs/FE_RESPONSIVE.md).
   EN: MISC — `.overflow-x-auto` is the mandatory wrapper for wide tables (otherwise
       the whole page scrolls horizontally, see docs/FE_RESPONSIVE.md).
   --------------------------------------------------------------------------- */
.overflow-x-auto { overflow-x: auto; max-width: 100%; }
.overflow-hidden { overflow: hidden; }
.relative { position: relative; }
.absolute { position: absolute; }
.object-cover { object-fit: cover; }
.cursor-pointer { cursor: pointer; }

/* CZ: --- AI obsah (.ai-content) — vyrenderovaný Markdown z briefů, shrnutí a chatu. ---
       Modely vracejí Markdown; filtr `ai_text` (web/templatetags/ai_extras.py) a klientský
       `renderAiMarkdown` (static/js/chat.js) ho převedou na HTML. Dřív se vypisoval syrově,
       takže uživatel viděl `## Nadpis` a `**tučně**`. Tyhle styly dávají výsledku rytmus —
       bez nich by nadpisy a odrážky splynuly s okolním textem.
   EN: --- AI content (.ai-content) — rendered Markdown from briefs, summaries and chat. ---
       Models return Markdown; the `ai_text` filter (web/templatetags/ai_extras.py) and the
       client-side `renderAiMarkdown` (static/js/chat.js) turn it into HTML. It used to be
       printed raw, so users saw `## Heading` and `**bold**`. These styles give the result
       rhythm — without them headings and bullets would blend into the surrounding text. */
.ai-content > *:first-child { margin-top: 0; }
.ai-content > *:last-child { margin-bottom: 0; }
.ai-content p { margin: 0 0 var(--space-2); line-height: var(--body-leading); }
.ai-content h1,
.ai-content h2,
.ai-content h3,
.ai-content h4,
.ai-content h5,
.ai-content h6 {
  margin: var(--space-2) 0 var(--space-1);
  font-weight: var(--weight-subhead);
  color: var(--text-primary);
  line-height: var(--leading-snug);
}
.ai-content h1 { font-size: var(--text-lg); }
.ai-content h2 { font-size: var(--text-lg); }
.ai-content h3 { font-size: var(--text-base); }
.ai-content h4,
.ai-content h5,
.ai-content h6 { font-size: var(--text-sm); }
.ai-content ul,
.ai-content ol { margin: 0 0 var(--space-2); padding-inline-start: var(--space-3); }
.ai-content li { margin-bottom: var(--space-1); line-height: var(--body-leading); }
.ai-content strong { font-weight: var(--weight-subhead); color: var(--text-primary); }
.ai-content a { color: var(--text-link); }
.ai-content code {
  font-size: var(--text-sm);
  background: var(--gray-tint);
  padding: 0 var(--space-1);
  border-radius: var(--radius-chip);
}
.ai-content pre {
  background: var(--gray-tint);
  padding: var(--space-2);
  border-radius: var(--radius-chip);
  overflow-x: auto;
  max-width: 100%;
  margin: 0 0 var(--space-2);
}
.ai-content pre code { background: none; padding: 0; }
.ai-content blockquote {
  margin: 0 0 var(--space-2);
  padding-inline-start: var(--space-2);
  border-inline-start: 3px solid var(--line-card);
  color: var(--text-secondary);
}
.ai-content hr { border: 0; border-top: 1px solid var(--line-card); margin: var(--space-2) 0; }
/* CZ: Tabulky z Markdownu se na mobilu musí scrollovat, ne rozbít stránku.
   EN: Markdown tables must scroll on mobile rather than break the page. */
.ai-content table { width: 100%; border-collapse: collapse; margin: 0 0 var(--space-2); display: block; overflow-x: auto; }
.ai-content th,
.ai-content td { border: 1px solid var(--line-card); padding: var(--space-1); text-align: left; }

/* ==========================================================================
   CZ: Widgety dashboardu — kategorie SCL a stavy prázdna (2026-08-12)
       Nahradilo ECharts (1 030 900 B), který se na dashboard tahal kvůli dvěma
       widgetům. Vše přes tokeny, žádný hardcoded hex — jinak by to v dark mode
       zůstalo světlé (viz CLAUDE.md „Design tokeny").
   EN: Dashboard widgets — SCL categories and empty states (2026-08-12)
       Replaced ECharts (1,030,900 B), pulled in for just two widgets. Everything
       via tokens, no hardcoded hex — otherwise dark mode would stay light.
   ========================================================================== */

.scl-categories {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.scl-category { min-width: 0; }

.scl-bar-track {
  height: 8px;
  margin-top: var(--space-1);
  border-radius: var(--radius-pill);
  background-color: var(--line-card);
  overflow: hidden;
}

.scl-bar-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background-color: var(--orange-base);
}

/* CZ: Odznak „ve vývoji" (`.badge-wip`) byl odstraněn 2026-08-13. Zavedl se ve fázi 0,
       ale při widgetizaci zmizel ze všech šablon a zbyla jen tahle nepoužívaná definice —
       ověřeno, že se v `templates/` nevyskytuje ani jednou. Prázdné stavy dnes vysvětlují
       situaci textem (`empty_state.html`, parametr `note`), ne odznakem.
   EN: The "in development" badge (`.badge-wip`) was removed on 2026-08-13. Introduced in
       phase 0, it vanished from every template during widgetization, leaving only this unused
       definition — verified absent from `templates/`. Empty states now explain the situation
       in words (`empty_state.html`, the `note` parameter) rather than with a badge. */

/* CZ: Poznámka pod prázdným stavem — vysvětluje, PROČ je prázdno (stav vývoje).
   EN: Note under an empty state — explains WHY it is empty (development status). */
.empty-state-note {
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* CZ: PRÁZDNÁ SEKCE SCALING UP KONTEXTU — jeden řádek, ne velký panel (varianta B, 10. 9. 2026).
       Čtyři sekce na `/profile/` používaly plný `empty_state`: 4 × 214 px = 856 px prázdna
       v kartě vysoké 1684 px, tedy POLOVINA karty byla „zatím nic". Každý panel navíc sliboval
       AI, aniž by nabídl na co kliknout — tenhle slib teď plní jedno tlačítko v hlavičce karty.

       Prázdný stav tu smí být takhle drobný právě proto, že DALŠÍ KROK je hned pod ním: pole
       „Nová hodnota…" a tlačítko „Přidat". Velký panel s nadpisem by opakoval, co už říká
       popisek sekce nad ním.
   EN: AN EMPTY SCALING-UP CONTEXT SECTION — one line, not a large panel (option B, 10 Sep 2026).
       The four sections on `/profile/` used the full `empty_state`: 4 × 214 px = 856 px of
       emptiness inside a 1684 px card, i.e. HALF the card said "nothing yet". Each panel also
       promised AI without offering anything to click — that promise is now kept by a single
       button in the card header.

       The empty state may be this small precisely because the NEXT STEP sits right below it: the
       "New value…" field and the "Add" button. A large panel with a heading would repeat what
       the section label above it already says. */
.cc-hint {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  padding: var(--space-1) 0;
}

/* ==========================================================================
   CZ: HLAVIČKA PŘEHLEDU (A-01 + A-05, 7. 9. 2026)
       Vystřídala tmavý hero pás z 13. 8. 2026. Vzor žádný tmavý pás nemá. Přehled
       začíná prostým eyebrow, velkým nadpisem a podtitulkem na bílé. Vpravo, zarovnaná
       na spodní hranu nadpisu, sedí onboarding karta s oranžovou čepičkou.
       Pozn. k brand manuálu (BR-02, 14. 9. 2026): zákaz gradientního pozadí (§12) je tím
       splněný sám od sebe — pás, který gradient nesl, tu už není. `--gradient-hero`
       je proto zrušený token, ne vypnutý efekt.
   EN: THE OVERVIEW HEADER (A-01 + A-05, 7 Sep 2026)
       It replaced the dark hero band from 13 Aug 2026. The sample has no dark band.
       The overview opens with a plain eyebrow, a large heading and a subtitle on white.
       The orange-capped onboarding card sits to the right, aligned to the heading's
       bottom edge.
       Note on the brand manual (BR-02, 14 Sep 2026): the ban on gradient backgrounds
       (§12) is satisfied on its own — the band that carried the gradient is gone.
       `--gradient-hero` is therefore a removed token, not a disabled effect.
   ========================================================================== */

.dash-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
  flex-wrap: wrap;
}

.dash-head-main { min-width: 0; }

.dash-head-eyebrow { margin-bottom: var(--space-1); }

/* ==========================================================================
   CZ: NADPIS STRÁNKY — JEDINÉ MÍSTO (9. 9. 2026)
       Anatomie vzoru: eyebrow → nadpis → podtitulek. Aplikace pro ni měla TŘI kopie
       pravidla — `.page-header h1` (`layout.css`, 77 šablon), `.dash-head-title`
       a `.progress-head-title`. Dvě nastavovaly `--tracking-tight`, třetí ne a dědila
       `--h1-tracking` (−1.5 px) z `base.css`. Stejně velký nadpis se tedy sázel dvěma
       různými prostrky podle obrazovky. Sloučeno sem.

       ⚠️ VELIKOST JE ZÁMĚRNĚ 30 px, NE `--h1-size` (44–54 px) ze vzoru. Ta škála
       v `base.css` platí pro holé `<h1>` a je stavěná pro plochu s jedním velkým
       sdělením; hustá aplikační obrazovka s kartami by se pod 54px nadpisem propadla.
       Přepis je tím pádem rozhodnutí, ne nedopatření — a od 9. 9. je i napsané, takže
       se příště nepřepíše „na vzor" omylem. Otevřená otázka pro majitele: **O-12**
       v `docs/PLAN_PREHLED_VZOR_2026-09-07.md`.

       Nové ploše se NEPŘIDÁVÁ další alias — použije se `.page-head-title`.
   EN: THE PAGE TITLE — A SINGLE PLACE (9 Sep 2026)
       The sample's anatomy: eyebrow → title → subtitle. The app held THREE copies of the
       rule — `.page-header h1` (`layout.css`, 77 templates), `.dash-head-title` and
       `.progress-head-title`. Two set `--tracking-tight`, the third did not and inherited
       `--h1-tracking` (−1.5px) from `base.css`. An identically sized heading was therefore
       set with two different trackings depending on the screen. Merged here.

       ⚠️ THE SIZE IS DELIBERATELY 30px, NOT the sample's `--h1-size` (44–54px). That scale
       in `base.css` applies to a bare `<h1>` and is built for a surface carrying one large
       message; a dense app screen full of cards would collapse under a 54px title. The
       override is thus a decision, not an oversight — and since 9 Sep it is written down, so
       nobody "restores the sample" by mistake. Open question for the owner: **O-12** in
       `docs/PLAN_PREHLED_VZOR_2026-09-07.md`.

       A new surface gets NO further alias — it uses `.page-head-title`.
   ========================================================================== */
.page-head-title,
.dash-head-title,
.progress-head-title,
.page-header h1 {
  font-family: var(--font-core);
  font-size: var(--text-3xl);
  font-weight: var(--weight-heading);
  letter-spacing: var(--tracking-tight);
  margin: 0;
}

.page-head-subtitle,
.dash-head-subtitle,
.progress-head-subtitle,
.page-header .page-description {
  margin-top: var(--space-1);
  font-size: var(--text-base);
  color: var(--text-body);
}

/* CZ: Onboarding karta. `flex: 0 1 auto` + `min-width` — nemá se roztahovat přes půl
       hlavičky, ale ani se nesmí zmáčknout pod čitelnou šířku, než se zalomí pod nadpis.
   EN: The onboarding card. `flex: 0 1 auto` + `min-width` — it must not stretch across
       half the header, nor squeeze below a readable width before it wraps under the title. */
.dash-head-onboarding {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  flex: 0 1 auto;
  min-width: 18rem;
}

.dash-head-onboarding-body { min-width: 0; }

.dash-head-onboarding-title {
  font-size: var(--text-base);
  font-weight: var(--weight-subhead);
  margin: 0;
}

.dash-head-onboarding-steps {
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* CZ: Hotový krok ztmavne — odškrtnutí nese ✓, ne barva. Barva sama by pro barvoslepé
       nic neřekla a `--text-muted` × `--text-primary` drží kontrast v obou režimech.
   EN: A completed step darkens — the ✓ carries the state, not the colour. Colour alone
       would say nothing to a colour-blind reader, and `--text-muted` × `--text-primary`
       holds contrast in both themes. */
.dash-head-onboarding-step.is-done { color: var(--text-primary); }

.dash-head-onboarding-sep { margin: 0 var(--space-1); }

/* CZ: Mobil — karta pod nadpis přes celou šířku, ať se text kroků nemačká do sloupce.
   EN: Mobile — the card drops under the heading full width so the step text does not
       squash into a column. */
@media (max-width: 768px) {
  .dash-head { align-items: stretch; }
  .dash-head-onboarding { min-width: 0; flex: 1 1 100%; }

  /* CZ: Zmenšení nadpisu na mobilu platí pro VŠECHNY hlavičky, ne jen pro tuhle (9. 9. 2026).
         Do té doby ho měly jen Přehled a „Můj postup"; ostatních 77 šablon drželo 30 px i na
         telefonu, takže se dlouhý název firmy nebo dotazníku lámal přes tři řádky. Je to
         v téže media query jako zbytek mobilního chování hlavičky — dvě `@media` se stejným
         breakpointem se rozejdou.
     EN: The mobile heading step-down applies to ALL page headers, not just this one (9 Sep
         2026). Until then only the Overview and "My progress" had it; the other 77 templates
         kept 30px on a phone, so a long company or survey name broke across three lines. It
         sits in the same media query as the rest of the header's mobile behaviour — two
         `@media` blocks on one breakpoint drift apart. */
  .page-head-title,
  .dash-head-title,
  .progress-head-title,
  .page-header h1 { font-size: var(--text-2xl); }
}

/* CZ: AI PÁS (A-04) zanikl 21. 9. 2026 — nahradil ho plovoucí AI poradce (`.ai-fab`, konec souboru).
   EN: The AI BAND (A-04) went away on 21 Sep 2026 — replaced by the floating advisor (`.ai-fab`). */

/* ==========================================================================
   CZ: KARTA „PŘIPOMÍNKA PROGRESU" (A-08 / B-06, 7. 9. 2026) — čtyři čísla v mřížce 2×2,
       jak je má vzor. Vystřídala KPI pás přes celou šířku stránky.
   EN: THE "PROGRESS REMINDER" CARD (A-08 / B-06, 7 Sep 2026) — four numbers in the
       sample's 2×2 grid. It replaced the full-width KPI strip.
   ========================================================================== */

.firm-numbers {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3) var(--space-2);
}

/* CZ: `min-width: 0` — buňka gridu má implicitně `min-width: auto`, takže by ji dlouhé
       číslo („1 234 567") roztlačilo a mřížka by přetekla z karty ven.
   EN: `min-width: 0` — a grid item defaults to `min-width: auto`, so a long number
       ("1,234,567") would stretch it and push the grid out of the card. */
/* ==========================================================================
   CZ: KARTY ROZPADLÉ Z „KOMUNITY" (B-09, 7. 9. 2026) — novinky, akce, lidé, průvodce.
       Vzor záložky uvnitř karty nemá, takže ze tří panelů vznikly samostatné karty.
   EN: THE CARDS SPLIT OUT OF "COMMUNITY" (B-09, 7 Sep 2026) — news, events, people, guide.
       The sample has no tabs inside a card, so three panels became separate cards.
   ========================================================================== */

/* --- Co je nového: TAG │ text │ kdy --------------------------------------- */
.news-rows { list-style: none; padding: 0; }

/* CZ: `align-items: baseline` — verzálkový tag je menší než text řádku a na střed
       zarovnaný by „plaval". Účaří je drží na jedné lince.
   EN: `align-items: baseline` — the uppercase tag is smaller than the row text and would
       "float" if centred. The baseline keeps them on one line. */
.news-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  border-top: 1px solid var(--line-divider);
}

/* CZ: Pevná šířka tagu — sloupec textů začíná u všech řádků na stejné svislici. Bez toho
       by se levá hrana textu lámala podle délky slova („AKCE" × „PODCAST").
   EN: A fixed tag width — the text column starts on the same vertical for every row.
       Without it the text's left edge would jump with the word length. */
.news-row-tag { flex: 0 0 5.5rem; }

.news-row-text { flex: 1; font-size: var(--text-sm); min-width: 0; }

.news-row-when { font-size: var(--text-xs); color: var(--text-secondary); flex: 0 0 auto; }

/* CZ: `.news-placeholder` smazán 9. 9. 2026 s dokončením C-01b — všech pět kategorií feedu má
       reálný zdroj, takže pruh „připravujeme" v kartě „Co je nového" zanikl. Nezůstal tu jako
       „třeba se bude hodit": nepoužívané pravidlo se při dalším čtení souboru tváří jako
       živá komponenta.
   EN: `.news-placeholder` was deleted on 9 Sep 2026 when C-01b landed — all five feed categories
       have a real source, so the "coming soon" strip in the "What's new" card is gone. It was not
       kept "just in case": an unused rule reads as a live component next time someone opens this
       file. */

/* --- Akce a projekty ------------------------------------------------------ */
.event-rows { list-style: none; padding: 0; }

.event-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--line-divider);
}

.event-row-body { flex: 1; min-width: 0; }

.event-row-title { font-size: var(--text-sm); font-weight: var(--weight-subhead); }

.event-row-meta { font-size: var(--text-xs); color: var(--text-secondary); }

.event-row-until { flex: 0 0 auto; }

/* --- S kým se máš potkat -------------------------------------------------- */
.people-list { list-style: none; padding: 0; }

.people-row {
  display: flex;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--line-divider);
}

/* CZ: Kolečko s iniciálami 40 px podle vzoru. `flex: 0 0 40px` — nesmí se zmáčknout, když
       je jméno dlouhé; kolečko zdeformované do elipsy vypadá jako chyba.
   EN: The sample's 40 px initials circle. `flex: 0 0 40px` — it must not squash when the
       name is long; a circle deformed into an ellipse reads as a bug. */
.people-avatar {
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-pill);
  background: var(--gray-tint);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  font-weight: var(--weight-subhead);
}

.people-body { display: flex; flex-direction: column; min-width: 0; }

.people-name { font-size: var(--text-sm); font-weight: var(--weight-subhead); }

.people-reason { font-size: var(--text-xs); color: var(--text-secondary); }

/* --- Průvodce přístavem --------------------------------------------------- */
.guide-video { font-size: var(--text-sm); color: var(--text-secondary); }

.guide-links { list-style: none; padding: 0; margin-top: var(--space-2); }

/* CZ: CHYBOVÝ STAV KARTY (A-17) — vzor ho má u každé karty vedle běžného a prázdného.
       Skrytí přes `hidden` řeší reset `[hidden] { display: none !important }` v `base.css`;
       tady se řeší jen vzhled.

       ⚠️ Ten reset do 9. 9. 2026 NEEXISTOVAL, ačkoli se na něj tenhle komentář odvolával —
       a `display: flex` níž proto přebíjelo `[hidden]` z výchozího stylu prohlížeče. Chybový
       pruh „Kartu se teď nepodařilo načíst" tak svítil na KAŽDÉ kartě dashboardu, i když
       žádná neselhala. Kdo sem sáhne: nespoléhej na `hidden` bez toho resetu.
   EN: THE CARD ERROR STATE (A-17) — the sample gives every card one, alongside normal and
       empty. Hiding via `hidden` is handled by the `[hidden] { display: none !important }`
       reset in `base.css`; only the look lives here.

       ⚠️ That reset DID NOT EXIST until 9 Sep 2026, although this comment already claimed it —
       so the `display: flex` below defeated the browser's default `[hidden]`. The "this card
       failed to load" strip was therefore lit on EVERY dashboard card even though none had
       failed. If you edit this: do not rely on `hidden` without that reset. */
.widget-error {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
  padding: var(--space-2);
  border-radius: var(--radius-container);
  background: var(--state-danger-soft);
  color: var(--state-danger-text);
  font-size: var(--text-sm);
}

/* CZ: SKELETON (A-17) — šedý pulzující blok pro obsah, který se teprve načítá. Vzor ho má
       u celé přehledové plochy jako `scbPulse`. Pulz respektuje `prefers-reduced-motion`:
       bez pohybu zůstane statická plocha, což je pořád srozumitelnější než prázdno.
   EN: THE SKELETON (A-17) — a grey pulsing block for content still loading. The sample uses
       `scbPulse` across the whole overview. The pulse honours `prefers-reduced-motion`: with
       motion off it stays a static block, still clearer than emptiness. */
.skeleton {
  background: var(--gray-tint);
  border-radius: var(--radius-container);
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}

@keyframes skeleton-pulse {
  0%, 100% { opacity: 0.45; }
  50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; opacity: 0.6; }
}

/* CZ: KONTAKT NA KOUČE v pravém horním rohu karty „Naposledy s koučem" (A-06). Plave
       doprava, takže text karty obtéká zleva a nepotřebuje vlastní sloupec.
   EN: THE COACH CONTACT in the top-right corner of the "Last with your coach" card (A-06).
       It floats right so the card's text flows around it without needing its own column. */
.coach-contact {
  float: right;
  margin-left: var(--space-2);
  text-align: right;
  max-width: 50%;
}

.coach-contact-name { font-weight: var(--weight-subhead); font-size: var(--text-sm); }

.coach-contact-line { font-size: var(--text-xs); }

/* CZ: Prioritní kroky — řádky s termínem vpravo. Obrys, ne výplň: karta uvnitř karty by
       vrstvila povrchy a vzor to nedělá.
   EN: The priority steps — rows with the due date on the right. An outline, not a fill: a card
       inside a card would stack surfaces and the sample never does that. */
.coach-steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
  list-style: none;
  padding: 0;
  clear: both;
}

.coach-step {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--line-divider);
  border-radius: var(--radius-chip);
  color: var(--text-primary);
  text-decoration: none;
}

.coach-step:hover { border-color: var(--line-card); text-decoration: none; }

.coach-step-title { flex: 1; font-size: var(--text-sm); min-width: 0; }

.coach-step-due { font-size: var(--text-xs); color: var(--text-secondary); flex: 0 0 auto; }

/* CZ: Tečka a popisek stavu úkolu (vzor, 17. 9. 2026). Vzor staví řádek jako
       „tečka · název · popisek stavu" a barvu tečky bere ze `statusMeta`: čeká na tebe
       oranžová, pracuješ modrá, u kouče šedá, hotovo zelená. Popisek je nutný i vedle
       tečky — barva sama je pro čtečku i pro barvoslepého nesdělná.
   EN: The action's status dot and label (sample, 17 Sep 2026). The sample renders the row as
       "dot · title · status label" and takes the dot's colour from `statusMeta`: awaiting you
       orange, in progress blue, with the coach grey, done green. The label is needed alongside
       the dot — colour alone says nothing to a screen reader or a colour-blind reader. */
.coach-step-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--gray-base);
}
.coach-step-dot--awaiting_approval,
.coach-step-dot--pending { background: var(--accent-attention); }
.coach-step-dot--in_progress { background: var(--blue-base); }
.coach-step-dot--submitted,
.coach-step-dot--reviewed { background: var(--gray-base); }
.coach-step-dot--completed { background: var(--accent-positive); }
.coach-step-state {
  flex: 0 0 auto;
  font-size: var(--app-caption-size);
  font-weight: var(--app-caption-weight);
  letter-spacing: var(--app-caption-tracking);
  text-transform: uppercase;
  color: var(--text-secondary);
}

.firm-number { min-width: 0; }

.firm-number-value {
  font-family: var(--font-core);
  font-size: var(--text-2xl);
  font-weight: var(--weight-heading);
  letter-spacing: var(--tracking-tight);
}

.firm-number-label {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* CZ: Delta nese směr ŠIPKOU i barvou. Šipka je tam schválně: barva sama je pro barvoslepé
       čtenáře nečitelná a „zeleně dolů" by nikdo nepoznal.
   EN: The delta carries direction by ARROW as well as colour. The arrow is deliberate: colour
       alone is unreadable for a colour-blind reader and "green but down" would go unnoticed. */
.firm-number-delta {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--weight-subhead);
}

.firm-number-delta.is-up { color: var(--state-positive-text); }
.firm-number-delta.is-down { color: var(--state-danger-text); }

.firm-number-empty { color: var(--text-secondary); }

.firm-numbers-footer {
  margin-top: var(--space-3);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}


/* ==========================================================================
   CZ: POHYB (redesign, fáze 6) — jeden přechod (--transition-emphasized) na všechno
       nové, střídmě. Vstupní animace jede JEN při prvním vykreslení stránky: třídu
       `dash-entering` nasazuje dashboard_widgets.js na <body> při DOMContentLoaded a
       po doběhnutí ji sundá — HTMX swapy (resize/reorder/remove/polling) už třídu
       nemají, takže plocha při mutacích neposkakuje. `prefers-reduced-motion` řeší
       globální pravidlo v base.css (animation/transition zkrátí) — neduplikovat.
   EN: MOTION (redesign, phase 6) — one transition (--transition-emphasized) for all
       new movement, sparingly. The entrance animation runs ONLY on the first page
       render: dashboard_widgets.js puts the `dash-entering` class on <body> at
       DOMContentLoaded and removes it when done — HTMX swaps (resize/reorder/remove/
       polling) lack the class, so the desk never jumps on mutations. The global rule
       in base.css handles `prefers-reduced-motion` (it shortens animation/transition)
       — do not duplicate it.
   ========================================================================== */

/* CZ: Užší vycentrovaný sloupec dashboardu (mockup 3) — stránka dýchá, karty se
       neroztahují přes celý viewport.
   EN: The dashboard's narrower centred column (mockup 3) — the page breathes and the
       cards no longer span the whole viewport. */
.dash-shell {
  /* CZ: Obsahová studna vzoru — `--well-web` (1200 px), stejná jako `.container`.
         Dřív tu stálo 1320 px kvůli třem sloupcům ½+¼+¼; ty od A-03 nejsou a širší
         studna by dashboard rozdrbala proti zbytku aplikace, který jede na 1200 px.
     EN: The sample's content well — `--well-web` (1200 px), the same as `.container`.
         It used to be 1320 px for the ½+¼+¼ columns; those are gone since A-03, and the
         wider well would misalign the dashboard against the rest of the app at 1200 px. */
  max-width: var(--well-web);
  margin: 0 auto;
}

/* CZ: Řádek akce v Komunitě — datumový čtvereček, obsah, mono „za X" štítek.
   EN: A Community event row — the date square, the body, a mono "in X" tag. */
/* CZ: NR-15 — titulek události se lámal po JEDNOTLIVÝCH PÍSMENECH („We / bin / ář: / ca").
       Příčina: `.community-event-until` mělo `flex: none` + `white-space: nowrap`, takže dlouhý
       odpočet („za 1 měsíc, 1 týden") si vzal pevnou šířku a nikdy se nesmrštil. Tělo s
       `min-width: 0` se pak smrsklo na pár pixelů a `overflow-wrap: break-word` z `base.css`
       zbytek dokonal. V úzkém sloupci widgetu (~200 px) na titulek nezbylo nic.
       Bylo to i před redesignem; zvětšení písma na 17 px (F2) to jen vyhrotilo.
       Oprava: řádek se smí ZALOMIT a tělo má rozumné minimum — odpočet tak spadne na vlastní
       řádek místo toho, aby udusil titulek.
   EN: NR-15 — the event title broke ONE LETTER PER LINE ("We / bin / ář: / ca").
       Cause: `.community-event-until` had `flex: none` plus `white-space: nowrap`, so a long
       countdown ("in 1 month, 1 week") claimed a fixed width and never shrank. The body, with
       `min-width: 0`, then collapsed to a few pixels and `overflow-wrap: break-word` from
       `base.css` finished the job. In the widget's narrow column (~200 px) nothing was left for
       the title.
       It predates the redesign; raising the body size to 17 px (F2) merely made it acute.
       Fix: the row may WRAP and the body has a sensible minimum — the countdown drops to its own
       line instead of strangling the title. */
.community-event {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-1) var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--line-divider);
}

.community-event:last-child { border-bottom: 0; }

/* CZ: Datový čtvereček se barví podle typu akce (`.event-type-*` třída na stejném prvku);
       bez typu se `--event-type-*` nepřepíše a zůstane výchozí (šablona v takovém případě
       dosadí oranžovou rodinu, aby vzhled widgetu odpovídal stavu před 14. 8.).
   EN: The date square takes the event type's colour (an `.event-type-*` class on the same
       element); without a type `--event-type-*` stays at its default (the template then falls
       back to the orange family so the widget looks as it did before 14 Aug). */
.community-event-day {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-chip);
  background-color: var(--event-type-soft);
  color: var(--event-type-text);
  font-family: var(--font-core);
  font-weight: var(--weight-heading);
}

/* CZ: `min-width` v `ch` — titulek dostane vždy aspoň ~10 znaků na řádek, jinak nemá smysl ho
       zobrazovat. `flex-basis: 55%` mu dá většinu řádku, dokud je místo.
   EN: `min-width` in `ch` — the title always gets at least ~10 characters per line; below that
       showing it is pointless. `flex-basis: 55%` gives it most of the row while there is room. */
.community-event-body { flex: 1 1 55%; min-width: 10ch; }

.community-event-until {
  /* CZ: `flex: 0 0 auto` + `margin-left: auto` = drží se vpravo, ale díky `flex-wrap` na
         rodiči spadne na vlastní řádek, místo aby titulek stlačil na nulu.
     EN: `flex: 0 0 auto` + `margin-left: auto` = it hugs the right, but thanks to the parent's
         `flex-wrap` it drops to its own line instead of squeezing the title to nothing. */
  flex: 0 0 auto;
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--event-type-text);
  white-space: nowrap;
  padding-top: var(--space-1);
}

/* CZ: Plné outline tlačítko pod panelem (Všechny akce / skupiny / adresář, Napsat …).
   EN: The full-width outline button under a panel (All events / groups / directory, Write …). */
.community-more {
  display: block;
  width: 100%;
  text-align: center;
  margin-top: var(--space-2);
}

/* CZ: Tvůj tým — kulatý avatar s iniciálami (žádný obrázek).
   EN: Your team — a round initials avatar (no image). */
.team-member {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}

.team-member-body { min-width: 0; }

.team-avatar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-pill);
  background-color: var(--gray-pale);
  color: var(--text-primary);
  font-family: var(--font-core);
  font-weight: var(--weight-subhead);
  text-transform: uppercase;
}

@keyframes dash-widget-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes dash-bar-in {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

.dash-entering .widget { animation: dash-widget-in var(--transition-emphasized) both; }

/* CZ: Stagger ~70 ms — výchozí plocha má 7 karet, zbytek nastupuje s posledním krokem.
   EN: A ~70 ms stagger — the default desk has 7 cards; the rest enters with the last step. */
.dash-entering .widget:nth-child(2) { animation-delay: 70ms; }
.dash-entering .widget:nth-child(3) { animation-delay: 140ms; }
.dash-entering .widget:nth-child(4) { animation-delay: 210ms; }
.dash-entering .widget:nth-child(5) { animation-delay: 280ms; }
.dash-entering .widget:nth-child(6) { animation-delay: 350ms; }
.dash-entering .widget:nth-child(n+7) { animation-delay: 420ms; }

/* CZ: Progress bary narůstají od leva (TMF ve widgetu `bottlenecks`). Proužek nastavení
       z hera tu byl do 7. 9. 2026 — hero zaniklo a onboarding je nově karta s výčtem
       kroků, ne pruh, takže není co animovat.
   EN: Progress bars grow from the left (TMF in the `bottlenecks` widget). The hero's setup
       strip was listed here until 7 Sep 2026 — the hero is gone and onboarding is now a
       card listing steps rather than a bar, so there is nothing to animate. */
.dash-entering .scl-bar-fill {
  transform-origin: left;
  animation: dash-bar-in var(--transition-emphasized) both;
}

/* ==========================================================================
   CZ: Skládatelná plocha widgetů (2026-08-12)
       Mřížka o 12 sloupcích s DISKRÉTNÍMI velikostmi S/M/L — rozhodnutí majitele.
       Volný resize s přetokem sousedů by znamenal vendorovat GridStack; tohle
       jede celé na CSS gridu a pojmenovaných třídách, takže `test_css_classes.py`
       nemusí povolovat inline styly.
   EN: Composable widget desk (2026-08-12)
       A 12-column grid with DISCRETE S/M/L sizes — an owner decision. A free resize
       with neighbour reflow would mean vendoring GridStack; this rides entirely on
       CSS grid and named classes, so `test_css_classes.py` need not allow inline styles.
   ========================================================================== */

/* CZ: PLOCHA = DVA SLOUPCE 2fr : 1fr podle vzoru (A-03, 7. 9. 2026).
       Vzor staví Přehled na širokém levém a úzkém pravém sloupci (`grid-template-columns:
       2fr 1fr` v `BrAIen-Pristav-X.dc.html`). Tři sloupce ½+¼+¼ z 13. 8. 2026 tím zanikly:
       ve studni 1200 px by úzké sloupce vyšly na ~250 px a obsah by se v nich lámal.

       Sloupec karty nese pole `size` — `l` = levý, `s` I `m` = pravý. Žádná DB migrace,
       jen jiný výklad hodnoty (viz komentář v `_widgets.html`). Sloupec je flex fronta
       karet pod sebou, takže díry z šířek ani výšek NEMOHOU vzniknout.
   EN: THE DESK = TWO COLUMNS 2fr : 1fr per the sample (A-03, 7 Sep 2026).
       The sample builds the overview on a wide left and a narrow right column
       (`grid-template-columns: 2fr 1fr` in `BrAIen-Pristav-X.dc.html`). The ½+¼+¼ layout
       from 13 Aug 2026 is gone: inside the 1200 px well the narrow columns would be
       ~250 px and their content would wrap badly.

       A card's column rides the `size` field — `l` = left, both `s` and `m` = right. No DB
       migration, just a different reading (see the comment in `_widgets.html`). A column is
       a flex queue of stacked cards, so gaps from widths or heights CANNOT occur. */
.widget-desk {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--space-3);
  align-items: start;
}

.widget-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/* CZ: Zatím bez vlastních pravidel — třídy drží JS/testy a budoucí per-sloupec styly.
   EN: No own rules yet — the classes serve JS/tests and future per-column styling. */
.widget-col-wide { min-width: 0; }
.widget-col-narrow { min-width: 0; }

/* CZ: V režimu úprav je i PRÁZDNÝ sloupec viditelný cíl pro puštění karty.
   EN: In edit mode even an EMPTY column is a visible drop target. */
body.widgets-editing .widget-col {
  min-height: 6rem;
  outline: 1px dashed var(--line-card);
  outline-offset: 4px;
  border-radius: var(--radius-container);
}

/* CZ: Střední šířky (tablet na šířku) — od A-03 už NEPOTŘEBUJÍ vlastní pravidlo: dva
       sloupce 2fr : 1fr jsou výchozí stav, takže se sem není co přepínat. Blok, který tu
       stál (explicitní `grid-row` pro tři sloupce), zanikl spolu s druhým úzkým sloupcem.
   EN: Mid widths (landscape tablet) — since A-03 they need NO rule of their own: two
       columns at 2fr : 1fr are the default, so there is nothing to switch to. The block
       that used to live here (explicit `grid-row` for three columns) went away with the
       second narrow column. */

/* CZ: Mobil — sloupce pod sebou (nejdřív široký obsah, pak úzké) + hustší karty:
       menší mezery i vnitřní odsazení, ať se na obrazovku vejde víc obsahu.
   EN: Mobile — the columns stack (the wide content first, then the narrow ones) +
       denser cards: smaller gaps and padding so more content fits the screen. */
@media (max-width: 768px) {
  .widget-desk { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .widget-col { gap: var(--space-2); }
  .widget > .card-header { padding: var(--space-2) var(--space-2) 0; }
  .widget > .card-body { padding: var(--space-2) var(--space-2) var(--space-2); }
}

/* CZ: Anatomie karty widgetu (redesign 2026-08-13) — radius 20 px, bez tvrdého rámečku,
       7px tečka barevné rodiny u titulku a 3px akcentní hrana VLEVO, která se vysune na
       :hover / :focus-within (scaleY 0→1). Horní gradientový pruh je pryč. Zvednutí
       karty: translateY(-3px) + --shadow-none. Sytá barva je POUZE dekorativní; texty
       jedou přes „-text" tokeny.
   EN: Widget card anatomy (2026-08-13 redesign) — 20 px radius, no hard border, a 7px
       colour-family dot by the title and a 3px accent edge on the LEFT that slides in on
       :hover / :focus-within (scaleY 0→1). The top gradient bar is gone. Card lift:
       translateY(-3px) + --shadow-none. Saturated colour is DECORATIVE ONLY; text uses
       the "-text" tokens. */
/* CZ: Widget je karta ze vzoru (F4, 6. 9. 2026): OBRYS, radius 16 px, ŽÁDNÝ stín.
       Dřív měl `border: 0` a `--shadow-none`, protože vycházel z dlaždic členské sekce, které
       stály na stínu a gradientu. Vzor stojí na opaku — „Elevation is intentionally near-absent
       in this brand" — takže se karta vymezuje linkou, ne vrstvením.
       Rozhodnutí majitele (6. 9. 2026): widgety ZŮSTÁVAJÍ, jen dostávají vzhled vzoru.
   EN: A widget is the sample's card (F4, 6 Sep 2026): an OUTLINE, 16 px radius, NO shadow.
       It used to carry `border: 0` and `--shadow-none`, inheriting from the member-portal tiles
       that were built on shadow and gradient. The sample is the opposite — "Elevation is
       intentionally near-absent in this brand" — so the card is defined by a line, not by
       stacking. Owner's decision (6 Sep 2026): the widgets STAY, they just take the sample's look. */
.widget {
  margin: 0;
  min-width: 0;
  position: relative;
  border: var(--border-card-style);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-none);
  transition: border-color var(--transition-normal);
}

/* CZ: Akcentní hrana vlevo — odsazená shora i zdola, vysouvá se od středu.
   EN: The left accent edge — inset top and bottom, scales out from the centre. */
.widget::before {
  content: "";
  position: absolute;
  top: var(--space-2);
  bottom: var(--space-2);
  left: 0;
  width: 3px;
  border-radius: var(--radius-pill);
  transform: scaleY(0);
  transform-origin: center;
  transition: transform var(--transition-emphasized);
}

/* CZ: Zvednutí karty jen mimo režim úprav — v editaci má karta grab kurzor a přesun,
       poskakování pod kurzorem by se pralo se SortableJS.
   EN: The card lift only outside edit mode — while editing the card has the grab cursor
       and dragging; bouncing under the cursor would fight SortableJS. */
/* CZ: Hover ztmaví obrys místo zvednutí karty. Vzor pohyb omezuje na „short fades and colour
       shifts" a posun o −3 px se stínem je přesně to, co zakazuje. Karta se tak pořád hlásí,
       že je klikací, ale nepodskakuje.
   EN: Hover darkens the outline instead of lifting the card. The sample limits motion to "short
       fades and colour shifts", and a −3 px shift with a shadow is exactly what it forbids. The
       card still signals it is clickable without bouncing. */
body:not(.widgets-editing) .widget:hover,
body:not(.widgets-editing) .widget:focus-within {
  border-color: var(--gray-base);
}

.widget:hover::before,
.widget:focus-within::before { transform: scaleY(1); }

/* CZ: Tečka barevné rodiny před titulkem ZANIKLA (F4) — nahradil ji eyebrow nad nadpisem,
       což je anatomie karty ze vzoru. Dvě značky téže rodiny (tečka i eyebrow) by si braly
       pozornost navzájem.
   EN: The colour-family dot before the title is GONE (F4) — replaced by the eyebrow above the
       heading, which is the sample's card anatomy. Two markers of the same family (a dot and an
       eyebrow) would compete for attention. */

.widget-accent-lime::before { background: var(--green-base); }
.widget-accent-orange::before { background: var(--orange-base); }
.widget-accent-blue::before { background: var(--blue-base); }
.widget-accent-dark::before { background: var(--gray-base); }

/* CZ: Vzdušnější karta: hlavička bez dělicí čáry — obsah na ni plynule navazuje,
       karta nepůsobí jako orámovaná tabulka. Titulek --text-lg (globální .card-title),
       podnadpis --text-sm muted (globální .card-description).
   EN: An airier card: the header loses its divider — the body flows on from it, so the
       card stops reading as a framed table. Title --text-lg (global .card-title),
       subtitle --text-sm muted (global .card-description). */
.widget > .card-header {
  border-bottom: 0;
  padding: var(--space-3) var(--space-3) 0;
}

.widget > .card-body { padding: var(--space-2) var(--space-3) var(--space-3); }

/* CZ: Pill tlačítka a vstup AI promptu — tvarosloví členské sekce (plně kulaté rohy).
   EN: Pill buttons and the AI prompt input — the member portal's shapes (fully rounded). */
.widget .btn { border-radius: var(--radius-pill); }

.widget-ai-prompt {
  display: block;
  text-decoration: none;
  color: var(--text-secondary);
  border-radius: var(--radius-pill);
}

.widget-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}

.widget-header-main { min-width: 0; }

/* CZ: Úchyt a akce jsou skryté, dokud se nezapne režim úprav — v běžném zobrazení
       má být karta čistá a nerozptylovat.
   EN: The handle and actions stay hidden until edit mode is on — in normal viewing the
       card should be clean and undistracting. */
.widget-actions {
  display: none;
  flex: none;
  align-items: center;
  gap: var(--space-1);
}

/* CZ: Režim úprav se zapíná JEDNOU třídou na <body>. Ne na mřížce: tu každá mutace
       (velikost, odebrání, přesun) vymění za novou, takže by třída zmizela spolu se starým
       elementem a ovládání by po první změně zmizelo pod rukama. <body> swap nenahrazuje.
   EN: Edit mode is switched by ONE class on <body>. Not on the grid: every mutation (resize,
       remove, reorder) replaces it, so the class would vanish with the old element and the
       controls would disappear under the user's hands after the first change. <body> survives. */
body.widgets-editing .widget-actions { display: flex; }

/* CZ: V režimu úprav se táhne za CELOU kartu (úchyt ⠿ zmizel) — kurzor to říká předem.
       Interaktivní prvky uvnitř si nechávají vlastní kurzor, klikání dál funguje
       (SortableJS `filter` v dashboard_widgets.js).
   EN: In edit mode the WHOLE card drags (the ⠿ handle is gone) — the cursor says so up
       front. Interactive elements inside keep their own cursor and clicking keeps working
       (the SortableJS `filter` in dashboard_widgets.js). */
body.widgets-editing .widget {
  outline: 1px dashed var(--line-card);
  outline-offset: 2px;
  cursor: grab;
  /* CZ: Tah za celou kartu by jinak vybíral text uvnitř — výběr se vypíná jen
         v režimu úprav, při čtení zůstává text normálně kopírovatelný.
     EN: Dragging the whole card would otherwise select its text — selection is off
         only in edit mode; while reading, text stays copyable as usual. */
  user-select: none;
}

body.widgets-editing .widget:active { cursor: grabbing; }

/* CZ: SortableJS třídy — stejné názvy jako u kanbanu výzev, ať se to chová konzistentně.
       Zvednutá karta dostává výraznější stín, protože klidový stav už má shadow-md.
   EN: SortableJS classes — the same names as the challenge kanban, for consistent behaviour.
       The lifted card gets the stronger shadow because resting cards already carry shadow-md. */
.widget-desk .sortable-ghost { opacity: 0.4; }
.widget-desk .sortable-chosen { box-shadow: var(--shadow-overlay); }

/* CZ: Přepínač panelů ve sloučeném widgetu Komunita (role=tablist, šipky přes Alpine).
       Záložky nemizí — prázdný panel má vlastní prázdný stav, jinak by přepínač měnil šířku.
   EN: The panel switcher in the merged Community widget (role=tablist, arrow keys via
       Alpine). Tabs never vanish — an empty panel has its own empty state, otherwise the
       switcher would change width. */
.widget-tablist {
  display: flex;
  gap: var(--space-1);
  padding: var(--space-1);
  background-color: var(--gray-tint);
  border-radius: var(--radius-pill);
  margin-bottom: var(--space-2);
  width: fit-content;
}

.widget-tab {
  border: 0;
  background: none;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: var(--weight-eyebrow);
  color: var(--text-secondary);
  cursor: pointer;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.widget-tab.is-active {
  background-color: var(--surface-page);
  color: var(--text-primary);
  box-shadow: var(--shadow-none);
}

/* CZ: Sloučený widget „Kde tě firma brzdí" — dvousloupcový rozpad jen s TMF daty
       (`.has-tmf`); pod 769 px pod sebe. Bez TMF jednosloupcově (nikdy prázdný sloupec).
   EN: The merged "Where the company slows you down" widget — a two-column split only with
       TMF data (`.has-tmf`); stacked below 769 px. Without TMF single-column (never an
       empty column). */
.bottlenecks { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); }

@media (min-width: 769px) {
  .bottlenecks.has-tmf { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .bottlenecks.has-tmf .bottlenecks-steps {
    border-left: 1px solid var(--line-card);
    padding-left: var(--space-3);
  }
}

.bottlenecks-tmf { min-width: 0; }

/* CZ: `.score-bar` nahradil `.bottlenecks-metric` (B-11, 7. 9. 2026) — pruhy skóre se
       vykreslují ze sdíleného partialu `templates/ui/score_bars.html`, takže i třída musí být
       sdílená. Používá ji widget `bottlenecks` (TMF) i obrazovka „Můj postup" (TMF + SCL).
   EN: `.score-bar` replaced `.bottlenecks-metric` (B-11, 7 Sep 2026) — the score bars render from
       the shared `templates/ui/score_bars.html` partial, so the class must be shared too. Used by
       the `bottlenecks` widget (TMF) and the "My progress" screen (TMF + SCL). */
.score-bar { margin-bottom: var(--space-2); }
.score-bar:last-child { margin-bottom: 0; }

.bottlenecks-list { display: flex; flex-direction: column; gap: var(--space-2); }

.bottlenecks-step { display: flex; gap: var(--space-2); align-items: flex-start; }

.bottlenecks-step-num {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-pill);
  background-color: var(--blue-pale);
  color: var(--blue-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-subhead);
}

.bottlenecks-step-body { min-width: 0; }

.bottlenecks-tmf-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--line-card);
}

/* CZ: Vodorovný pás doporučených záznamů se scroll-snap — dlaždice s tintovaným coverem
       (nth-child střídá rodiny, žádná inline barva) a mono odznakem délky.
   EN: The horizontal recommended-recordings strip with scroll-snap — tiles with a tinted
       cover (nth-child rotates the families, no inline colours) and a mono duration badge. */
.video-strip {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--space-1);
  /* CZ: Pás zůstává tažitelný myší/dotykem i klávesnicí (tabindex="0" v šabloně), jen se
         schová viditelný scrollbar — stejný vzor jako `.plane-nav` v layout.css.
     EN: The strip stays draggable by mouse/touch and keyboard (tabindex="0" in the template),
         only the visible scrollbar is hidden — same pattern as `.plane-nav` in layout.css. */
  scrollbar-width: none;
}
.video-strip::-webkit-scrollbar { display: none; }

.video-strip-tile {
  flex: 0 0 220px;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  text-decoration: none;
  color: var(--text-primary);
  min-width: 0;
}

.video-strip-cover {
  display: flex;
  align-items: flex-end;
  height: 96px;
  border-radius: var(--radius-container);
  padding: var(--space-1);
}

.video-strip-tile:nth-child(4n+1) .video-strip-cover { background-color: var(--orange-pale); }
.video-strip-tile:nth-child(4n+2) .video-strip-cover { background-color: var(--green-pale); }
.video-strip-tile:nth-child(4n+3) .video-strip-cover { background-color: var(--blue-pale); }
.video-strip-tile:nth-child(4n+4) .video-strip-cover { background-color: var(--gray-pale); }

.video-strip-duration {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  background-color: var(--surface-page);
  border-radius: var(--radius-chip);
  padding: 0 var(--space-1);
}

.video-strip-title { font-weight: var(--weight-eyebrow); font-size: var(--text-sm); }
.video-strip-reason { font-size: var(--text-xs); color: var(--text-secondary); }

.widget-catalog {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.widget-catalog-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--line-card);
}

.widget-catalog-item:last-child { border-bottom: 0; padding-bottom: 0; }

/* CZ: Lišta úprav plochy — nad mřížkou, zarovnaná doprava, aby v běžném zobrazení
       nesoupeřila s obsahem karet o pozornost.
   EN: The desk edit bar — above the grid, right-aligned so it does not compete with the
       card content for attention in normal viewing. */
/* CZ: A-13 (7. 9. 2026) — lišta se přesunula NAD plochu → POD ni, proto `margin-top`.
   EN: A-13 (7 Sep 2026) — the bar moved from above the desk to BELOW it, hence `margin-top`. */
.widget-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-top: var(--space-3);
}

/* CZ: Nápověda k přetahování jde na začátek řádku (`order: -1`), aby se četla PŘED
       tlačítky, a na mobilu se zalomí na vlastní řádek.
   EN: The drag hint goes to the start of the row (`order: -1`) so it reads BEFORE the
       buttons, and wraps onto its own line on mobile. */
.widget-toolbar-hint {
  order: -1;
  margin: 0;
  margin-right: auto;
}

/* CZ: S/M/L tlačítka zanikla (varianta A — šířku určuje sloupec); zbývá jen ✕ odebrání.
   EN: The S/M/L buttons are gone (variant A — the column decides the width); only the
       ✕ removal remains. */
.widget-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.75rem;
  padding: 0 var(--space-1);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  color: var(--text-secondary);
  background-color: var(--surface-page);
  border: 1px solid var(--line-card);
  border-radius: var(--radius-chip);
  cursor: pointer;
}

.widget-action-btn:hover { background-color: var(--gray-tint); }

.modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.modal-header h3 {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
}

/* CZ: Paleta má 12 položek — na nízkém okně by přetekla mimo obrazovku, proto scroll uvnitř.
   EN: The palette holds 12 items — on a short window it would overflow off-screen, hence
       scrolling inside. */
#widget-catalog-body {
  max-height: 60vh;
  overflow-y: auto;
}

/* CZ: Vložený rezervační kalendář poskytovatele (Calendly). Pevná výška je nutná — rámec
       s cizím obsahem se sám nezmenší na obsah a bez ní by se sbalil na nulu.
       720 px je změřená výška rozcestníku typů schůzek na profilovém odkazu (6 položek, ověřeno
       v prohlížeči 17. 8. 2026). U odkazu na KONKRÉTNÍ typ události stačí ~520 px — až se odkazy
       zúží na jeden typ, dá se výška snížit.
   EN: The provider's embedded booking calendar (Calendly). A fixed height is required — a frame
       with foreign content does not shrink to its content and without it would collapse to zero.
       720px is the measured height of the event-type menu on a profile link (6 items, verified in
       a browser on 17 Aug 2026). A link to a SPECIFIC event type needs only ~520px — once the
       links narrow to one type, the height can come down. */
.booking-embed {
  display: grid;
  gap: var(--space-1);
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--line-card);
}
.booking-embed iframe {
  width: 100%;
  height: 720px;
  border: 0;
  border-radius: var(--radius-container);
  background: var(--gray-tint);
}
/* CZ: Na mobilu je Calendly stejně jednosloupcové a vyšší rámec jen nutí scrollovat uvnitř rámce.
   EN: On mobile Calendly is single-column anyway and a taller frame only forces scrolling inside
       the frame. */
@media (max-width: 640px) {
  .booking-embed iframe { height: 780px; }
}

/* CZ: Doporučená videa pod odpovědí v chatu. Dlaždice je NÁHLED S PROKLIKEM, ne přehrávač
       (rozhodnutí majitele 17. 8. 2026) — přehrávač v bublině natáhne stránku a na mobilu
       ukrojí půl obrazovky. Vodorovný pás se scroll-snapem, stejně jako pás na dashboardu.
   EN: Recommended videos under a chat answer. A tile is a THUMBNAIL WITH A LINK, not a player
       (owner's decision, 17 Aug 2026) — a player in the bubble stretches the page and eats half a
       phone screen. A horizontal scroll-snap strip, like the one on the dashboard. */
.chat-videos {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--line-card);
}
.chat-videos-title {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.chat-videos-strip {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--space-1);
  scrollbar-width: none;
}
.chat-videos-strip::-webkit-scrollbar { display: none; }
.chat-video-tile {
  flex: 0 0 12rem;
  scroll-snap-align: start;
  display: grid;
  gap: var(--space-1);
  text-decoration: none;
  color: var(--text-primary);
}
.chat-video-tile img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-chip);
  background: var(--gray-tint);
}
.chat-video-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-eyebrow);
  line-height: var(--leading-snug);
}
.chat-video-duration {
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.chat-video-tile:hover .chat-video-title { text-decoration: underline; }


/* ==========================================================================
   CZ: OBRAZOVKA „MŮJ POSTUP" (B-11, 7. 9. 2026)
       Vzor ji staví stejně jako Přehled: eyebrow → H1 → dva sloupce 2fr : 1fr. Mřížka se
       NEBERE z `.widget-desk` — ta nese pravidla režimu úprav (přerušovaný obrys drop zóny)
       a je svázaná s `dashboard_widgets.js`. Tahle stránka skládatelná není.
   EN: THE "MY PROGRESS" SCREEN (B-11, 7 Sep 2026)
       The sample builds it like the overview: eyebrow → H1 → two columns at 2fr : 1fr. The grid
       is NOT reused from `.widget-desk` — that one carries edit-mode rules (the dashed drop-zone
       outline) and is tied to `dashboard_widgets.js`. This page is not composable.
   ========================================================================== */

/* CZ: Typografie `.progress-head-title` / `-subtitle` je ve společném pravidle výš
       („NADPIS STRÁNKY"). Tady zůstává jen odsazení bloku.
   EN: The typography of `.progress-head-title` / `-subtitle` lives in the shared rule above
       ("THE PAGE TITLE"). Only the block's spacing stays here. */
.progress-head { margin-bottom: var(--space-3); }

/* CZ: DVA SLOUPCE 2fr : 1fr — JEDEN deklarační blok pro víc obrazovek (17. 9. 2026).
       `.progress-desk` (Můj postup) a `.desk-2-1` (profil klienta v rovině řízení) mají
       stejné rozvržení ze vzoru, takže tu nestojí dvě kopie pravidel: kdyby se rozvržení
       někdy měnilo, měnilo by se na jednom místě. Přidat třetí obrazovku = přidat selektor.
       `.widget-desk` výš zůstává samostatně ZÁMĚRNĚ — nese pravidla režimu úprav
       (přerušovaný obrys drop zóny) svázaná s `dashboard_widgets.js`.
   EN: TWO COLUMNS 2fr : 1fr — ONE declaration block for several screens (17 Sep 2026).
       `.progress-desk` (My progress) and `.desk-2-1` (the client profile in the management
       plane) share the sample's layout, so there are not two copies of the rules here: were the
       layout ever to change, it would change in one place. Adding a third screen = adding a
       selector. `.widget-desk` above stays separate ON PURPOSE — it carries edit-mode rules
       (the dashed drop-zone outline) tied to `dashboard_widgets.js`. */
.progress-desk,
.desk-2-1 {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--space-3);
  align-items: start;
}

.progress-col,
.desk-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/* CZ: Obrácený poměr 1fr : 2fr — úzký sloupec VLEVO (detail leadu: seznam případů vedle tabů).
       Sloupce sdílí `.desk-col` výš, mění se jen poměr; proto vlastní pravidlo na jeden řádek
       místo druhé kopie celé mřížky.
   EN: The reversed 1fr : 2fr ratio — the narrow column on the LEFT (the lead detail: the case
       list beside the tabs). The columns share `.desk-col` above, only the ratio differs; hence
       a one-line rule of its own rather than a second copy of the whole grid. */
.desk-1-2 {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: var(--space-3);
  align-items: start;
}

/* --- časová osa ----------------------------------------------------------- */
.progress-timeline { list-style: none; padding: 0; margin-top: var(--space-2); }

/* CZ: `align-items: baseline` — datum je menší než nadpis události a na střed zarovnané by
       „plavalo". Účaří je drží na jedné lince, stejně jako u řádků novinek na Přehledu.
   EN: `align-items: baseline` — the date is smaller than the event title and would "float" if
       centred. The baseline keeps them on one line, as with the overview's news rows. */
.progress-timeline-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--line-divider);
}

.progress-timeline-row:first-child { border-top: 0; }

/* CZ: Pevná šířka data — nadpisy začínají na jedné svislici bez ohledu na délku data.
   EN: A fixed date width — titles start on one vertical regardless of the date's length. */
.progress-timeline-date {
  flex: 0 0 6rem;
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* CZ: Tečka je DEKORACE (`aria-hidden` v šabloně) — kategorii nese text události, ne barva.
       Barevné odlišení podle kategorie tu ZÁMĚRNĚ není: třídu by musela skládat šablona
       (`…-dot--{{ event.category }}`), a `test_css_classes.py` atribut `class` s `{{ }}`
       přeskakuje — chybějící modifikátor by znamenal neviditelnou tečku a ŽÁDNÝ spadlý test.
       `--radius-pill`, ne `50%`: kulatost je tvarosloví a to se dědí z tokenů.
   EN: The dot is DECORATION (`aria-hidden` in the template) — the category is carried by the
       event's text, not by colour. Per-category colouring is DELIBERATELY absent: the class would
       have to be assembled by the template (`…-dot--{{ event.category }}`), and
       `test_css_classes.py` skips any `class` attribute containing `{{ }}` — a missing modifier
       would mean an invisible dot and NO failing test.
       `--radius-pill`, not `50%`: roundness is shape, and shape inherits from the tokens. */
.progress-timeline-dot {
  flex: 0 0 8px;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--gray-base);
}

.progress-timeline-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }

.progress-timeline-title { font-size: var(--text-sm); font-weight: var(--weight-subhead); }

.progress-timeline-detail { font-size: var(--text-xs); color: var(--text-secondary); }

/* --- skóre a zdroj -------------------------------------------------------- */
.progress-score {
  font-family: var(--font-core);
  font-size: var(--text-3xl);
  font-weight: var(--weight-heading);
  letter-spacing: var(--tracking-tight);
  margin-bottom: var(--space-2);
}

.progress-score-delta {
  font-family: var(--font-fallback);
  font-size: var(--text-xs);
  font-weight: var(--weight-body);
  color: var(--text-secondary);
}

.progress-source {
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* CZ: Mobil — sloupce pod sebe. Bez toho by se pravý sloupec na telefonu zmáčkl na pár znaků.
   EN: Mobile — the columns stack. Without it the right column squashes to a few characters. */
@media (max-width: 768px) {
  .progress-desk,
  .desk-2-1,
  .desk-1-2 { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .progress-col,
  .desk-col { gap: var(--space-2); }
  /* CZ: Zmenšení nadpisu je ve společném mobilním pravidle výš — tady by byla čtvrtá kopie.
     EN: The heading step-down lives in the shared mobile rule above — a copy here would be
         the fourth. */
}


/* ==========================================================================
   CZ: OBRAZOVKA „MOJE FIRMA" (B-12, 7. 9. 2026) — pilulkové taby a dvousloupcový obsah.
       Taby jsou ODKAZY, ne tlačítka: server ví, které smí uživatel vidět, a vykreslí jen ty.
   EN: THE "MY COMPANY" SCREEN (B-12, 7 Sep 2026) — pill tabs and two-column content.
       The tabs are LINKS, not buttons: the server knows which the user may see and renders
       only those.
   ========================================================================== */

.hub-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

/* CZ: Pilulka podle vzoru — neaktivní je obrys, aktivní plná modrá. Popředí přes
       `--text-on-primary`, NIKDY `--color-text-inverse`: ten se v tmavém režimu překlápí
       a popisek by na sytě modré zmizel.
   EN: The sample's pill — inactive is an outline, active a solid blue. Foreground via
       `--text-on-primary`, NEVER `--color-text-inverse`: it flips in dark mode and the label
       would vanish on the saturated blue. */
.hub-tab {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--line-card);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  color: var(--text-body);
  font-size: var(--text-sm);
  text-decoration: none;
}

.hub-tab:hover { color: var(--text-primary); text-decoration: none; }

.hub-tab.is-active {
  background: var(--action-primary);
  border-color: var(--action-primary);
  color: var(--text-on-primary);
  font-weight: var(--weight-subhead);
}

.hub-desk {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--space-3);
  align-items: start;
}

.hub-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

@media (max-width: 768px) {
  .hub-desk { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .hub-col { gap: var(--space-2); }
}

/* =====================
   CZ: VÝSLEDKY DOTAZNÍKU (10. 9. 2026) — skóre, pruhy a pásmové popisy.

       Obrazovka měla odpovědi pod sebou v jednom sloupci a skóre jako holá čísla. Pruh dává
       číslu měřítko na první pohled (7 z 10 je něco jiného než 7 z 15) a `.survey-hint` nese
       větu, co ta úroveň znamená — 75 takových vět v šabloně škálovatelnosti se dosud
       nezobrazovalo vůbec.

       ⚠️ Zelená/červená u změny NENÍ jediný nosič informace: vedle barvy stojí i šipka a
       znaménko, takže sdělení projde i barvoslepému oku a černobílému tisku.
   EN: SURVEY RESULTS (10 Sep 2026) — the score, bars and band descriptions.

       The screen used to stack answers in one column with bare numbers for scores. The bar gives
       a number its scale at a glance (7 out of 10 differs from 7 out of 15) and `.survey-hint`
       carries the sentence on what the level means — 75 such sentences in the scalability
       template were not shown at all.

       ⚠️ Green/red on a change is NOT the only carrier of meaning: an arrow and a sign stand next
       to the colour, so the message survives colour-blind eyes and black-and-white printing.
   ===================== */
.survey-top {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.4fr) minmax(0, 1fr);
  gap: var(--space-2);
  align-items: stretch;
}

.survey-score {
  font-family: var(--font-core);
  font-size: var(--text-4xl);
  font-weight: var(--weight-heading);
  line-height: 1.1;
  margin: var(--space-1) 0 0;
}
.survey-score-max { font-size: var(--text-lg); color: var(--text-secondary); font-weight: 400; }

.survey-delta { font-size: var(--text-sm); margin-top: var(--space-1); }
.survey-delta-inline { font-size: var(--text-xs); margin-left: 0.35em; }
/* CZ: `is-up` / `is-down` nesou SMĚR, ne hodnocení — u některých otázek je pokles v pořádku.
       Proto zelená/červená jen jako doprovod k šipce, ne jako verdikt.
   EN: `is-up` / `is-down` carry DIRECTION, not judgement — on some questions a drop is fine.
       Hence green/red accompany the arrow rather than pronouncing a verdict. */
.survey-delta.is-up, .survey-delta-inline.is-up { color: var(--state-positive-text); }
.survey-delta.is-down, .survey-delta-inline.is-down { color: var(--state-danger-text); }

.survey-bar {
  display: block;
  height: 8px;
  margin-top: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--gray-pale);
  overflow: hidden;
}
.survey-bar-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent-cap);
}

.survey-answer-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  flex-wrap: wrap;
  margin-top: var(--space-1);
}
.survey-answer-value {
  font-family: var(--font-core);
  font-size: var(--text-2xl);
  font-weight: var(--weight-heading);
  line-height: 1.1;
  margin: 0;
}

/* CZ: Pásmový popis — vlastní věta k číslu. Levý proužek ji odděluje od otázky nad ní, aby
       bylo poznat, že je to výklad hodnoty, ne další otázka.
   EN: The band description — a sentence belonging to the number. The left rule separates it from
       the question above so it reads as an interpretation of the value, not another question. */
.survey-hint {
  margin-top: var(--space-2);
  padding-left: var(--space-2);
  border-left: 3px solid var(--accent-cap);
  font-size: var(--text-sm);
  color: var(--text-primary);
}

.survey-answer-text {
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  white-space: pre-line;
}

@media (max-width: 900px) {
  .survey-top { grid-template-columns: minmax(0, 1fr); }
}

/* =====================
   CZ: ROZPIS CASHFLOW (`.cf-*`) — přesunuto 10. 9. 2026 z `<style>` uvnitř
       `portal/financials/cashflow.html`. Týž rozpis se nově vykresluje i v tabu „Finance"
       v „Moje firmě", a styl schovaný v `extra_head` jedné stránky by tam nedorazil: tabulka
       by se rozsypala na neformátovaná čísla.

       ⚠️ Zelená/červená není jediný nosič informace — znaménko čísla říká totéž.
   EN: THE CASHFLOW BREAKDOWN (`.cf-*`) — moved on 10 Sep 2026 out of a `<style>` inside
       `portal/financials/cashflow.html`. The same breakdown now renders in the "Financials" tab
       of "My company" too, and a style hidden in one page's `extra_head` would never reach it:
       the table would collapse into unformatted numbers.

       ⚠️ Green/red is not the only carrier of meaning — the number's sign says the same.
   ===================== */
.cf-table { width: 100%; border-collapse: collapse; }
.cf-table th, .cf-table td { padding: var(--space-1); }
.cf-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cf-pos { color: var(--state-positive); }
.cf-neg { color: var(--state-danger); }
.cf-zero { color: var(--text-body); }
.cf-label { text-align: left; }
.cf-section td { font-weight: 700; background: var(--gray-tint); }
.cf-detail .cf-label { padding-left: var(--space-3); }
.cf-detail td { color: var(--text-body); font-size: var(--text-sm); }
.cf-subtotal td { font-weight: 600; border-top: 2px solid var(--gray-base); }
.cf-grandtotal td { font-weight: 700; border-top: 3px double var(--gray-base); }
.cf-yearhead { text-align: right; font-weight: 600; }

/* =====================================================================================
 * KANBAN VÝZEV / THE CHALLENGES KANBAN
 * =====================================================================================
 * CZ: PŘESUNUTO ZE ŠABLONY (16. 9. 2026). Tenhle blok žil v `{% block extra_css %}`
 *     v `templates/portal/challenges/kanban.html` — jediný `<style>` v šabloně rovin.
 *     Komentář uvnitř si sám všiml, proč je to problém: „`test_margins_and_gaps_…` prochází
 *     jen CSS soubory, takže `<style>` v šabloně mu propadl." Platilo to i pro kontrolu
 *     tokenů a pro registr tříd — osm tříd `kanban-*` nebylo definovaných nikde, kam se
 *     kontroly dívají. Styl patří do CSS, šablona nese jen třídy (R10b/2).
 * EN: MOVED OUT OF THE TEMPLATE (16 Sep 2026). This block lived in `{% block extra_css %}` in
 *     `templates/portal/challenges/kanban.html` — the only `<style>` in any plane template.
 *     Its own comment spotted the problem: "`test_margins_and_gaps_…` scans CSS files only, so a
 *     template `<style>` slipped past." The same held for the token check and the class registry:
 *     eight `kanban-*` classes were defined nowhere the checks look. Style belongs in CSS; a
 *     template carries only classes (R10b/2).
 * ===================================================================================== */
/* CZ: Kanban podle dodaného vzoru (F4, 6. 9. 2026). Vzor má čtyři sloupce, aplikace pět —
       stavů má o jeden víc, a slučovat je kvůli vzoru by znamenalo ztratit informaci.
       Převzatý je vizuální jazyk, ne počet sloupců:
         · sloupec = šedá plocha, radius 12 px (kontejner), bez obrysu
         · záhlaví = eyebrow vlevo, prostý počet vpravo (ne pilulka — vzor tam pilulku nemá)
         · karta   = bílá, obrys 1.25 px, radius 12 px; hover ZTMAVÍ OBRYS, nepřidá stín
         · prázdný sloupec = přerušovaný rámeček s vysvětlením, ne prázdné místo
   EN: The kanban per the supplied sample (F4, 6 Sep 2026). The sample has four columns, the app
       five — it carries one more state, and merging states to match the sample would lose
       information. What is adopted is the visual language, not the column count:
         · column = a grey surface, 12 px radius (container), no outline
         · header = an eyebrow on the left, a plain count on the right (the sample has no pill)
         · card   = white, a 1.25 px outline, 12 px radius; hover DARKENS THE OUTLINE, no shadow
         · empty column = a dashed frame with an explanation, not blank space */
.kanban { display: grid; grid-template-columns: repeat(5, minmax(220px, 1fr)); gap: var(--space-2); overflow-x: auto; padding-bottom: var(--space-1); align-items: start; }
.kanban-col { background: var(--surface-panel); border-radius: var(--radius-container); padding: 12px; min-height: 240px; }
.kanban-col-header { display: flex; justify-content: space-between; align-items: center; padding: 4px 8px 12px; }
.kanban-col-title { font-size: var(--caption-size-sm); font-weight: var(--caption-weight); letter-spacing: var(--caption-tracking); text-transform: uppercase; color: var(--text-secondary); }
.kanban-col-count { font-size: var(--caption-size); color: var(--text-secondary); }
.kanban-cards { min-height: 120px; display: grid; gap: var(--space-1); align-content: start; }
.kanban-card { background: var(--surface-card); border: var(--border-card-style); border-radius: var(--radius-container); padding: 14px; text-decoration: none; color: inherit; display: block; cursor: grab; transition: border-color var(--transition-normal); }
.kanban-card:hover { border-color: var(--gray-base); text-decoration: none; }
/* CZ: `margin-bottom` na mřížce 8 px (`--space-1`). Dřív 6 px — mimo škálu bez důvodu; vzor
       říká „every margin and gap is a multiple of 8" a hlídá to `test_margins_and_gaps_…`,
       které ale prochází jen CSS soubory, takže `<style>` v šabloně mu propadl.
   EN: `margin-bottom` back on the 8 px grid (`--space-1`). It was 6 px — off the scale for no
       reason; the sample says "every margin and gap is a multiple of 8" and
       `test_margins_and_gaps_…` enforces it, but it scans CSS files only, so a template
       `<style>` slipped past. */

/* CZ: ⚠️ ŠEST PIXELŮ, KTERÉ SE NEPŘEBÍRAJÍ. Vzor dává mezeru nad popiskem karty `margin-top:6px`
       (`BrAIen-Pristav-X.dc.html:619`), jenže **týž vzor** ve svých pravidlech píše „every margin
       and gap is a multiple of 8" a spacing škála z něj začíná na 8. Vzor si tu odporuje sám se
       sebou a platí jeho PRAVIDLO, ne jeho jednorázová hodnota — proto `--space-1` (8px).
       Hlídá to `test_margins_and_gaps_are_on_the_8px_grid`.
   EN: ⚠️ SIX PIXELS THAT ARE NOT ADOPTED. The sample sets `margin-top:6px` above a card's meta
       line, yet the SAME sample states "every margin and gap is a multiple of 8" and its spacing
       scale starts at 8. The sample contradicts itself here, and its RULE wins over its one-off
       value — hence `--space-1` (8px). Guarded by `test_margins_and_gaps_are_on_the_8px_grid`. */
.kanban-card h4 { font-size: var(--text-sm); font-weight: var(--weight-subhead); line-height: 1.4; margin-bottom: var(--space-1); }
.kanban-card.sortable-ghost { opacity: 0.4; }
.kanban-card.sortable-chosen { cursor: grabbing; }
/* CZ: Prázdný sloupec — vzor ho nenechává prázdný, ale vysvětlí, proč nic nemá.
   EN: An empty column — the sample does not leave it blank but explains why it holds nothing. */
.kanban-empty { border: 1px dashed var(--line-card); border-radius: var(--radius-chip); padding: 20px 12px; text-align: center; font-size: var(--app-text-secondary-size); color: var(--text-secondary); }
@media (max-width: 1024px) { .kanban { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px)  { .kanban { grid-template-columns: 1fr; } }

/* =====================================================================================
 * LAYOUT UTILITY Z INLINE STYLŮ / LAYOUT UTILITIES LIFTED FROM INLINE STYLES
 * =====================================================================================
 * CZ: Tyhle třídy vznikly 16. 9. 2026 z inline stylů v šablonách rovin (R10b/2). Nejsou to
 *     nové nápady — je to týž kód přesunutý tam, kam patří, aby ho viděla kaskáda, tmavý
 *     režim i kontroly tokenů.
 * EN: These classes were lifted out of inline styles in plane templates on 16 Sep 2026
 *     (R10b/2). They are not new ideas — the same code moved where it belongs, so the cascade,
 *     dark mode and the token checks can see it.
 * ===================================================================================== */

/* CZ: Poměr 16:9 pro vložené video. Starý trik `padding-bottom: 56.25%` se opakoval ve třech
       šablonách, pokaždé se stejným `position:absolute; top:0; left:0; border:0` na iframu.
   EN: A 16:9 box for an embedded video. The old `padding-bottom: 56.25%` trick appeared in three
       templates, each with the same `position:absolute; top:0; left:0; border:0` on the iframe. */
.embed-16x9 { position: relative; width: 100%; aspect-ratio: 16 / 9; }
.embed-16x9 > iframe,
.embed-16x9 > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* CZ: Přehrávač z vlastního video serveru (Plyr, `templates/ui/video_player.html`). Barvu
       ovládání bere Plyr z `--plyr-color-main`, tak ji napojujeme na token primární akce.
   EN: Self-hosted video player (Plyr, `templates/ui/video_player.html`). Plyr takes its control
       colour from `--plyr-color-main`, so we wire it to the primary-action token. */
.video-player {
  --plyr-color-main: var(--action-primary);
  position: relative;
  margin-bottom: var(--space-6);
}
.video-player .plyr { border-radius: var(--radius-container); }
/* CZ: Pozice k pokračování je PŘEKRYV v rohu přehrávače, ne řádek nad ním — rozložení stránky
       zůstává stejné jako s Vimeo embedem (přání majitele 24. 9. 2026). Překrývá obsah, proto
       neprůhledné pozadí a stín překryvné vrstvy (pravidla 9 a 10 v CLAUDE.md).
   EN: The resume position is an OVERLAY in the player's corner, not a row above it — the page
       layout stays the same as with the Vimeo embed (owner's wish, 24 Sep 2026). It covers
       content, hence the opaque background and the overlay shadow (rules 9 and 10 in CLAUDE.md). */
.video-player__resume {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  right: var(--space-3);
  z-index: var(--z-popover);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  width: fit-content;
  max-width: calc(100% - 2 * var(--space-3));
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line-card);
  border-radius: var(--radius-container);
  background: var(--surface-overlay);
  box-shadow: var(--shadow-overlay);
  color: var(--text-primary);
  font-size: var(--caption-size);
}
.video-player__resume[hidden] { display: none; }

/* CZ: Součtový řádek tabulky — silnější linka nad mezisoučtem. Byl třikrát natvrdo v šabloně.
   EN: A table's subtotal row — a heavier rule above the sum. It was hardcoded in three places. */
.row-subtotal > td,
.row-subtotal > th { border-bottom: 2px solid var(--gray-base); }

/* CZ: Zvýrazněná (nepřečtená) položka v seznamu notifikací.
   EN: A highlighted (unread) row in the notification list. */
.row-unread { background-color: var(--orange-pale); }

/* CZ: Mřížka, která se sama zalomí podle dostupné šířky — `auto-fit` s minimem 220px.
   EN: A grid that wraps itself to the available width — `auto-fit` with a 220px minimum. */
.grid-autofit {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-2);
}

/* CZ: Posuvník hodnocení přebírá zelenou z palety, ne z výchozí modré prohlížeče.
   EN: The rating slider takes its green from the palette, not the browser's default blue. */
.range-positive { accent-color: var(--green-base); }

/* CZ: Úzká studna pro samostatnou hlášku (neplatný odkaz na schůzku).
   EN: A narrow well for a standalone message (an invalid meeting link). */
.well-message { max-width: 36rem; margin: var(--space-8) auto; }

/* CZ: Řádek oddělený spodní linkou — opakoval se inline ve wizardu konzultace.
   EN: A row separated by a bottom rule — repeated inline in the consultation wizard. */
.row-divided { border-bottom: 1px solid var(--line-card); }
/* CZ: Mezera odlišná ve svislém a vodorovném směru (chipy ve dvou řádcích).
   EN: A gap that differs vertically and horizontally (chips on two rows). */
.gap-1-2 { gap: var(--space-1) var(--space-2); }

/* CZ: Odsazení zleva — chybějící protějšek k `.px-*`; potřeboval ho odrážkový seznam
       v potvrzení financí, kde do teď stálo inline `padding-left: var(--space-3)`.
   EN: Left padding — the missing counterpart to `.px-*`; needed by the bullet list in the
       finance confirmation, which until now carried an inline `padding-left: var(--space-3)`. */
.pl-3 { padding-left: var(--space-3); }

/* CZ: Přihlašovací blok na KARTĚ akce (vzor, 17. 9. 2026). Je to `<form>`, protože přihlášení
       je mutace — a formulář na kartě potřebuje vlastní pravidlo, aby se odznak „Přihlášen"
       a tlačítko „Odhlásit" seřadily vedle sebe a nezalomily kartu na úzké obrazovce.
   EN: The sign-up block on an event CARD (sample, 17 Sep 2026). It is a `<form>` because
       signing up is a mutation — and a form on a card needs its own rule so the "Registered"
       badge and the "Unregister" button line up and do not break the card on a narrow screen. */
.event-signup {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: wrap;
  margin: 0;
}

/* CZ: Pobídka k výkazům a dotazníkům pod kroky onboardingu (17. 9. 2026). Vizuálně TIŠŠÍ než
       kroky — není to krok, jen poznámka, co kouči ještě pomůže. Proto drobné písmo
       a sekundární barva, ne odznak ani číslo.
   EN: The statements-and-surveys prompt below the onboarding steps (17 Sep 2026). Visually
       QUIETER than the steps — it is not a step, only a note on what else helps the coach.
       Hence small type and a secondary colour, not a badge or a number. */
.dash-head-onboarding-extra {
  margin-top: var(--space-1);
  font-size: var(--app-text-secondary-size);
  color: var(--text-secondary);
}

/* CZ: Rozsah období v nadpisu mezishrnutí (vzor, 17. 9. 2026). Drobnější a tišší než samotný
       nadpis — je to doplněk, který říká „jak dlouhý úsek", ne druhý nadpis.
   EN: The period range inside a summary's heading (sample, 17 Sep 2026). Smaller and quieter
       than the heading itself — an addendum saying "how long a stretch", not a second title. */
.card-title-range {
  font-size: var(--app-text-secondary-size);
  font-weight: var(--weight-body);
  color: var(--text-secondary);
}

/* =============================================
 * PLOVOUCÍ AI PORADCE (21. 9. 2026) — `templates/ui/ai_fab.html`, `static/js/ai_fab.js`
 * CZ: Vzor: modrá pilulka „Zeptat se AI" vpravo dole na všech obrazovkách přístavu kromě
 *     chatu; po rozkliku chatovací okno. Okno PŘEKRÝVÁ obsah → neprůhledné `--surface-overlay`
 *     a jediný povolený stín `--shadow-overlay` (CLAUDE.md, pravidla 9 a 10). Popředí na modré
 *     přes `--text-on-primary` (drží bílou i v tmavém režimu, pravidlo 11).
 *     Růstový symbol se NEPŘEBARVUJE (brand manuál §2 — vzor ho invertuje do bílé, to je
 *     porušení); stojí proto na malém bílém kruhu v původních barvách.
 * EN: The sample: a blue "Ask AI" pill bottom-right on every harbour screen except the chat;
 *     it opens a chat window. The window OVERLAYS content → opaque `--surface-overlay` and the
 *     single allowed shadow `--shadow-overlay`. Foreground on blue via `--text-on-primary`.
 *     The growth symbol is NOT recoloured (brand manual §2 — the sample inverts it to white,
 *     which breaks the rule); it sits on a small white disc in its own colours.
 * ============================================= */
.ai-fab {
  position: fixed;
  right: var(--space-4);
  bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
  z-index: var(--z-fab);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2);
  /* CZ: Kontejner kliknutí propouští (na telefonu je přes celou šířku) — chytají je jen děti.
     EN: The container lets clicks through (full width on a phone) — only its children catch them. */
  pointer-events: none;
}
.ai-fab > * { pointer-events: auto; }
.ai-fab-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--action-primary);
  color: var(--text-on-primary);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-subhead);
  box-shadow: var(--shadow-overlay);
  cursor: pointer;
  transition: background-color var(--duration-base) ease, transform var(--duration-base) ease;
}
.ai-fab-button:hover { background: var(--action-primary-hover); }
.ai-fab-button:active { background: var(--action-primary-press); transform: scale(0.98); }
.ai-fab-button:focus-visible { outline: 2px solid var(--action-primary); outline-offset: 3px; }
.ai-fab-button svg {
  box-sizing: content-box;
  padding: 4px;
  border-radius: var(--radius-pill);
  background: var(--text-on-primary);
}
.ai-fab-panel {
  display: flex;
  flex-direction: column;
  width: min(380px, calc(100vw - 2 * var(--space-2)));
  max-height: min(560px, 70vh);
  background: var(--surface-overlay);
  border: var(--border-card-style);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-overlay);
  overflow: hidden;
}
.ai-fab-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line-divider);
}
.ai-fab-title { margin: 0; font-size: var(--text-sm); font-weight: var(--weight-subhead); color: var(--text-primary); }
.ai-fab-tagline { margin: 0; font-size: var(--text-xs); color: var(--text-secondary); }
.ai-fab-messages {
  flex: 1;
  min-height: 120px;
  overflow-y: auto;
  padding: var(--space-2) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.ai-fab-msg {
  max-width: 85%;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
/* CZ: Tvar bublin jako plný chat (vzor): ostrý roh u mluvčího. EN: Bubble shapes as in the full chat. */
.ai-fab-msg-ai {
  align-self: flex-start;
  background: var(--surface-panel);
  color: var(--text-primary);
  border-radius: var(--radius-container) var(--radius-container) var(--radius-container) var(--radius-flag);
}
.ai-fab-msg-user {
  align-self: flex-end;
  background: var(--action-primary);
  color: var(--text-on-primary);
  border-radius: var(--radius-container) var(--radius-container) var(--radius-flag) var(--radius-container);
}
.ai-fab-msg-ai ul, .ai-fab-msg-ai ol { margin: var(--space-1) 0; padding-left: var(--space-2); }
.ai-fab-suggestions {
  list-style: none;
  margin: 0;
  padding: 0 var(--space-3) var(--space-1);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.ai-fab-suggestions .chip { font: inherit; font-size: var(--text-xs); cursor: pointer; }
.ai-fab-form {
  display: flex;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--line-divider);
}
.ai-fab-form .input { flex: 1; min-width: 0; border-radius: var(--radius-pill); }
.ai-fab-foot { padding: 0 var(--space-3) var(--space-2); display: flex; justify-content: flex-end; }
/* CZ: Telefon — okno přes celou šířku nad tlačítkem, tlačítko blíž k okraji.
   EN: Phone — the window spans the width above the button, the button sits closer to the edge. */
@media (max-width: 640px) {
  .ai-fab { right: var(--space-2); left: var(--space-2); bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px)); }
  .ai-fab-panel { width: 100%; max-height: 75vh; }
}
@media (prefers-reduced-motion: reduce) {
  .ai-fab-button { transition: none; }
  .ai-fab-button:active { transform: none; }
}

/* CZ: Pozvánka, na kterou vede upozornění „Otevřít pozvánku" (`#invite-row-<id>`), se zvýrazní
       a neschová se pod lepkavou hlavičkou (21. 9. 2026).
   EN: The invite the "Open invite" notification points at (`#invite-row-<id>`) is highlighted
       and does not hide under the sticky header (21 Sep 2026). */
.invite-anchor { scroll-margin-top: calc(var(--header-height) + var(--space-3)); }
.invite-anchor:target { background: var(--blue-pale); }
