:root {
    --panel-bg: #161d2e;
    --panel-bg-light: #1a2233;
    --panel-border: #0b0f1a;
    --gold: #f5c451;
    /* Stejné hex hodnoty jako RESOURCE_META[key].color v src/config.js - záměrná duplicita, ne
       přehlédnutí. `--res-color` (inline styl na .resStat/.roadmapTrack, viz render.js/roadmap.js)
       čte přímo z RESOURCE_META za běhu; tyhle proměnné existují jen pro statické CSS použití,
       kde JS hodnota není k dispozici (--tab-color na .tabBtn v index.html). Drž obojí v souladu
       při jakékoli změně palety suroviny. */
    --wood-color: #d9a441;
    --coal-color: #8fa0b8;
    --uranium-color: #4ade80;
    --crit-color: #ffe14d;
    --goldrush-color: #ff9d3d; /* Zlatá horečka - vědomě odlišná od --gold/--crit-color (jsou si podobné),
                                  aby event šel na první pohled odlišit od trvalého bonusu/jackpotu */
    --achievements-color: #a78bfa;
    --stats-color: #38bdf8;
    --system-color: #7c8aa8;
    /* Republika/prestige (T12, verze 5.3) - teplá karmínová. Musela se odlišit od VŠECH stávajících
       barev tabů: zlaté/okrové (wood, gold, crit, goldrush), fialové (achievements), modré (stats,
       uranium je zelená) i šedé (system). Karmínová (hue ~350°) je jediné volné místo v paletě, které
       navíc čte jako "vážná, trvalá věc" - odpovídá tomu, že je to jediná mechanika, jejíž postup
       přežije reset. Bonus (--bonus-color) je terakotová (hue ~14°), takže se nepřebíjejí. */
    --republic-color: #e0526f;
    /* Bonus (design rozhodnutí 2026-08): dřív sdílelo --gold s univerzálním accentem, což na první
       pohled splývalo s --wood-color (oba teplé okrové/zlaté). Terakotová/broskvová - teplý odstín,
       ale posunutý k červené (hue ~14°), ne ke žluté jako wood/gold/crit/goldrush - takže s nimi
       nekoliduje, a zároveň tvoří teplý/studený kontrastní pár s fialovými --achievements-color. */
    --bonus-color: #e09a85;
    /* Soustava (M1, plán 2.0) - tyrkysová. Je to JEDINÝ nový tab, který design 2.0 povoluje
       (risk R3), a je vidět jen v aktu 2. Volba barvy byla těsná: v paletě už jsou zlaté/okrové
       (wood, gold, crit, goldrush ~14-50°), zelená (uranium ~142°), modrá (stats ~199°), fialová
       (achievements ~258°), karmínová (republic ~350°) a terakotová (bonus ~14°). Tyrkysová
       (~172°) je uprostřed mezery mezi uranovou zelenou a modrou statistik - odstup je na obě
       strany jen ~30°, takže je to nejtěsnější dvojice v celé paletě, ale zbylá volná místa
       (purpurová ~310°) nečtou jako „elektrická síť". Ověřeno na 1920×1080: taby jsou vedle sebe
       s popiskem, takže se rozlišují i textem, ne jen odstínem. */
    --grid-color: #2dd4bf;

    /* Docházející palivo v aktu 2. Hodnota je BEZE ZMĚNY - do v6.41 stál týž literál `#e0685a`
       ve dvou pravidlech (`.gridRes--low` a pruh rezervy v liště) a BL-03 by přidalo třetí,
       takže se sjednotil (R4: barva má mít jediný zdroj pravdy). Vědomě NENÍ `--danger`:
       ta patří destruktivním AKCÍM (reset, smazat), kdežto tohle je stav, ne tlačítko. */
    --fuel-low-color: #e0685a;

    /* ---------- Sémantické tokeny (Fáze 1 UX/UI auditu) ----------
       Dřív literály opakované na mnoha místech. Hodnoty jsou ZÁMĚRNĚ beze změny (bit-identické) -
       jde o čistou centralizaci duplicit, ne o rebarvení. Škály (--space/--fs/--radius/--icon)
       přijdou ve Fázi 2 spolu s komponentami, které je konzumují. */
    --action: #14b8a6;      /* pozitivní / potvrzovací akce: koupit, uložit, odemknout (Fáze 1: teal místo blátivé zelené) */
    --danger: #c93540;      /* destruktivní akce: reset, smazat (Fáze 1: sytější červená místo tmavě hnědočervené;
                               doladěno o krok tmavší, aby bílý text .btn--danger splnil kontrast AA 4.5:1 - #e5484d
                               dávalo jen ~3.9:1) */
    /* Jemné světlé okraje (Fáze 1) - moderní jazyk „1px světlá linka + stín" místo tlustých tmavých rámů.
       Nahrazují dřívější 4px var(--panel-border) na horním/spodním panelu. */
    --border: rgba(255, 255, 255, 0.07);
    --border-strong: rgba(255, 255, 255, 0.14);
    /* Povrch karet (Fáze 2) - --surface je neprůhledná varianta (fallback), --surface-glass
       poloprůhledná pro glassmorphism (backdrop-filter blur). Karty tak „plavou" nad barevnou
       spodní lištou místo aby byly plochým obdélníkem. */
    --surface: #1a2233;
    --surface-glass: rgba(26, 34, 51, 0.6);
    /* Tři nejčastější textové odstíny. Jednorázové odstíny (label/locked/legend) jsou tokenizované
       níže v C.4 skupině. */
    --text-1: #e6eaf2;      /* primární text na tmavém panelu (Fáze 1: neutrální místo krémové) */
    --text-2: #c3cde0;      /* sekundární text: řádky karet, popisky, roadmap labely */
    /* #8b98b4 → #98a5c0 (vizuální QA audit v6.9, nález P2-12). Token byl laděný proti hlavnímu
       panelu #161d2e, ale konzumuje ho i text ležící na podkladech smíchaných s --res-color
       (color-mix), a proti tomu nejtmavšímu - uhelnému - propadal: .regionChipCost i
       .roadmapNextHint měly 4,05:1. Na dřevu (4,76) a uranu (5,29) přitom procházely, takže
       vada nebyla v tokenu, ale v tom, že se nikdy neověřil proti nejhorší ze tří variant.
5,3 - 6,4:1 podle podkladu. Cena revíru (.regionChipCost) navíc přešla na --text-2:
       je to nejdůležitější řádek čipu (P1-5) a na uranovém podkladu by ani zesvětlený token nestačil. */
    --text-muted: #aab6cf;  /* tlumený text: časová razítka, nápovědy */
    /* Rádiusová škála (Fáze 2) - první konzumenti: .btn (sm), sdílený povrch karet (md),
       #modalBox (lg). Hodnoty = přesně ty, které už v CSS byly (4/6/8px), jen pojmenované. */
    --radius-sm: calc(4px + 2px * var(--ui-k));
    --radius-md: calc(6px + 2px * var(--ui-k));
    --radius-lg: calc(12px + 4px * var(--ui-k));  /* Fáze 2: 8→12px, měkčí rohy karet/modalu. */
    /* Rozměry uzlu roadmapy - JEDINÝ zdroj pravdy (Fáze 6). Čte je i JS (roadmapNodeMetrics v
       roadmap.js) pro výpočet x-pozic diagonálních spojnic, aby geometrie v JS a v CSS nemohla
       rozejít. Dřív byly zapsané zvlášť v roadmap.js (ROADMAP_NODE_W/GAP) i tady. */
    --roadmap-node-w: calc(40px + 12px * var(--ui-k));
    /* Rozestup 10px na spodním konci škály: 12 uzlů se vejde do jednoho sloupce (klik/pasiv vedle
       sebe, v1.9.0) i na úzkém okně bez vodorovného posuvníku. */
    --roadmap-node-gap: calc(10px + 4px * var(--ui-k));
    /* Typografická škála (audit C.3) - pokrývá všech 8 dřív roztroušených velikostí písma; hodnoty
       jsou beze změny, jen pojmenované na jednom místě. */
    /* Škála zvednutá (v1.9.0) - panely se od 1.9.0 už NEscalují dolů se scénou (scaluje se jen mapa),
       text se renderuje ve skutečných px. Podlaha je 12px (dřív 10), aby drobné popisky/nápovědy
       byly čitelné i na ~14" monitoru. */
    --fs-2xs: calc(12px + 2px * var(--ui-k)); --fs-xs: calc(12px + 2px * var(--ui-k));
    --fs-sm: calc(13px + 3px * var(--ui-k)); --fs-md: calc(14px + 3px * var(--ui-k));
    --fs-base: calc(15px + 4px * var(--ui-k)); --fs-lg: calc(16px + 4px * var(--ui-k));
    --fs-xl: calc(19px + 5px * var(--ui-k)); --fs-2xl: calc(22px + 6px * var(--ui-k));
    /* Spacing škála (audit C.3) - 6 nejčastějších hodnot mezer (gap). Vzácné jednorázové mezery
       (5/20/28px) a složené paddingy/marginy zůstávají literálem (netvoří škálu, tokenizace 1:1 by
       byla jen přejmenování). */
    --space-1: calc(4px + 2px * var(--ui-k)); --space-2: calc(6px + 2px * var(--ui-k));
    --space-3: calc(8px + 2px * var(--ui-k)); --space-4: calc(10px + 3px * var(--ui-k));
    --space-5: calc(12px + 4px * var(--ui-k)); --space-6: calc(16px + 5px * var(--ui-k));
    /* Dřív roztroušené jednorázové / málo-použité literály (audit C.4) - pojmenované na jednom místě.
       --abundance-rare je reálný dedup (2×: ikona + legenda), --crit-color (výše) taky; ostatní jsou
       jednorázové, tady spíš pro úplnost a čitelnost než kvůli duplicitě. Hodnoty beze změny. */
    --crit-float: #ffd23c;      /* plovoucí číslo kritického zásahu (.floatText.crit) */
    --abundance-rare: #f5c451;  /* okraj vzácného ložiska - ikona i legenda */
    --text-label: #8fa0c0;      /* verzálkový popisek suroviny v horním panelu (.resName) */
    --text-locked: #aeb8cc;     /* název zamčeného úspěchu */
    --text-legend: #dbe3f0;     /* text legendy hojnosti */
    --radius-xs: calc(1px + 1px * var(--ui-k));  /* mikro-rádius (obdélníčky úrovní bonusu) */
    /* Jednotný focus ring na všem interaktivním (Fáze 2 sjednocuje dřívější gold 3px / bílá / gold 2px). */
    --focus-ring: var(--border-w-3) solid var(--gold);
    /* Výšky horního/spodního panelu a lišty tabů (2.3). Pozor: tyhle tři jsou jediné tokeny, které
       ubírají místo MAPĚ - viz podlaha mapy v docs/visual-baseline.md. */
    --panel-top-h: calc(80px + 24px * var(--ui-k));
    --panel-tabs-h: calc(36px + 8px * var(--ui-k));
    --panel-bottom-h: calc(250px + 60px * var(--ui-k));
    /* Minimální šířka sloupce v gridu úspěchů (viz .achievementsGrid). */
    --achievements-col-min: calc(160px + 45px * var(--ui-k));
    /* Max. šířka #goalBox (2.6) - na spodním konci škály je 560px těsně na hraně nejdelšího textu
       („Jaderná elektrárna spuštěna! 120 000 uranu nashromážděno" + ikona). Změřeno v prohlížeči
       (label.scrollWidth při white-space:nowrap): potřeba ~560/609/676px přes škálu, tady s rezervou. */
    --goal-box-max-w: calc(560px + 160px * var(--ui-k));
    /* ---------- PLYNULÁ ŠKÁLA UI (v6.21) ----------
       Do 6.20 tu byly tři pevné sady tokenů pod `:root[data-ui-tier="…"]` a hráč přepínal MEZI NIMI.
       Chování na hraně prahu bylo skokové: stačilo dvacet pixelů výšky okna a celé UI se přeplo
       o stupeň (nález R1 auditu v6.20 - „dvacet pixelů rozhodovalo o velikosti písma").
       Nově se všechno odvozuje z JEDNOHO čísla, které JS počítá z okna (`fluidUiScale`,
       render.core.js) a přepínač v Systému k němu jen přičítá odchylku (UI_TIERS.offset).

       `--ui-scale` je to číslo (1,00 = spodní konec, 1,30 = horní), `--ui-k` je totéž
       znormované na 0-1, aby se tokeny daly psát jako `calc(A + (B-A) * var(--ui-k))`.
       A i B jsou PŘESNĚ hodnoty, které do 6.20 nesly úrovně „Malé" a „Velké", takže na obou
       koncích je vzhled beze změny a uprostřed se plynule prolíná.

       Výchozí 1,15 platí jen do prvního doběhu JS (a pro `file://` bez skriptů) - je to hodnota,
       kterou měla úroveň „Střední", tedy referenční sestava. */
    --ui-scale: 1.15;
    --ui-k: calc((var(--ui-scale) - 1) / 0.3);
    /* ŠÍŘKY OKRAJŮ SE PLYNULE NEŠKÁLUJÍ - schválně. Zlomkový hairline se v prohlížeči rozostří
       (proto do 6.20 stálo u úrovně „Střední" výslovně „rozdíl 1→1,15px by jen rozostřil čáry").
       Přepínají se proto skokem přes `data-ui-crisp`, který nastavuje applyUiTierAttribute(). */
    --border-w-1: 1px;
    --border-w-2: 2px;
    --border-w-3: 3px;
}
/* Silnější okraje na horním konci škály (dřív sada úrovně „Velké"). Práh 1,22 leží mezi
   „Výchozí" a „Větší" na referenčním okně, takže se chová stejně jako dřívější přepnutí tieru. */
:root[data-ui-crisp="2"] {
    --border-w-1: 2px;
    --border-w-2: 3px;
    --border-w-3: 4px;
}

/* ---------- Velikost UI: PLYNULÁ ŠKÁLA (v6.21, dřív tři pevné sady) ----------
   Do 6.20 tu stály dva bloky `:root[data-ui-tier="fullhd21"]` a `[…="qhd27"]`, každý s vlastní
   sadou 29 tokenů, a přepínač v Systému mezi nimi skákal. Obě sady jsou teď rozpuštěné do
   `calc()` výrazů v :root výše: jejich krajní hodnoty se staly konci škály a hodnoty prostřední
   úrovně z nich vycházejí jako mezikrok (odchylka do 1 px u tří tokenů, změřeno).

   Co zůstalo: atribut `data-ui-tier` na <html> se pořád nastavuje (applyUiTierAttribute) a pořád
   nese id z UI_TIERS - jen už neřídí tokeny, ale slouží jako značka zvolené ODCHYLKY (a používá
   ho jedno pravidlo pro odznak úspěchu níž). Skutečnou velikost nese `--ui-scale`, kterou JS
   spočítá z okna a odchylky dohromady.

   Samotná mapa (#mapStage, včetně .resourceIcon na ní a legendy hojnosti uvnitř #mapStage) se
   tady neškáluje vůbec - tu do zbylého prostoru škáluje fitMap() (main.js), který se po každé
   změně škály i velikosti okna volá znovu. */

* { box-sizing: border-box; }

body {
    margin: 0;
    background: #000;
    /* Fáze 4: Inter na začátku stacku (moderní UI písmo); pokud není v systému, spadne na
       Segoe UI Variable / Segoe UI (Windows 11) - žádný web font se nestahuje (file:// only). */
    font-family: 'Inter', 'Segoe UI Variable', 'Segoe UI', system-ui, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    color: var(--text-1);
    overflow: hidden;
}

#stageWrapper {
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    /* L0 — app frame (Fáze 1): nejtmavší vrstva „mimo scénu", jemná radiální hloubka místo ploché černé. */
    background: radial-gradient(120% 100% at 50% -10%, #0d1220, #05070d);
}

/* v1.9.0: scéna už se NEscaluje jako celek. Vyplní viewport, panely se renderují ve skutečných px
   (čitelný text na malých monitorech) a scaluje se jen mapa (#mapStage, viz fitMap v main.js). */
#stage {
    width: 100%;
    height: 100%;
    position: relative;
    display: flex;
    flex-direction: column;
    /* L1 — stage (Fáze 1): svislý gradient dává scéně hloubku shora dolů místo jedné ploché barvy. */
    background: linear-gradient(180deg, #111725 0%, #0b0f1a 100%);
    overflow: hidden;
}

/* ---------- Horní panel ---------- */
#topPanel {
    height: var(--panel-top-h);
    flex: 0 0 var(--panel-top-h);
    background: var(--panel-bg);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
    border-bottom: var(--border-w-1) solid var(--border);
    display: flex;
    align-items: center;
    padding: 0 24px;
    gap: 20px;
    /* #notice (níže) se pozicuje relativně k tomuhle prvku, ne k #stage - viz komentář u #notice. */
    position: relative;
}

#resStats {
    display: flex;
    align-items: center;
    gap: 28px;
    /* F1 (plán 6.0): bez tohohle by se skupina pilířů chovala jako nestlačitelný blok a tlak by
       se přenesl na sousedy v liště (nakonec na pauzu a ozubené kolo, které by utekly ven). */
    min-width: 0;
}

/* Jemný nádech barvou suroviny na pozadí + 3px podtržení (ne 2px) - stejná intenzita/logika jako
   klidový stav .tabBtn ve spodní liště (viz style.css níže), aby oba panely četly jako jeden
   vizuální jazyk, ne dvě generace UI vedle sebe. */
.resStat {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-4);
    /* F1 (plán 6.0): 190 px je PREFEROVANÁ šířka, ne podlaha. Do 5.35 to bylo `min-width: 190px`,
       což ve spojení s výchozím `min-width: auto` na flex položkách znamenalo, že se pilíře nedaly
       zmenšit VŮBEC - horní lišta pak při nedostatku místa nepřetekla dovnitř, ale ven, a vytlačila
       pauzu i ozubené kolo mimo obrazovku (viz F1 v docs/implementacni-plan-6.0.md).
       `min-width: 0` je nutná druhá polovina: bez ní flex-shrink nesmí jít pod min-content obsahu.
       POZOR na tvar zápisu: `width: 190px` (ne `flex-basis: 190px`) je jediná varianta, která drží
       původních 190 px i tam, kde je místa dost. S flex-basis se totiž `#resStats` (shrink-to-fit
       kontejner) vysází podle max-content OBSAHU pilířů, ne podle basis, a pilíře spadnou na 158 px
       i na 1920 - tedy tichá vizuální regrese na referenční sestavě. Změřeno, ne odvozeno.

       ŠÍŘKA SE ŠKÁLUJE S TIEREM (vizuální QA audit v6.9). Do 6.9 tu bylo pevných 190 px, kdežto
       `.resAmount` má `font-size: var(--fs-2xl)`, které s tierem roste (22 → 24 → 28 px). Číslo tedy
       rostlo, pilíř ne, a `text-overflow: ellipsis` rozdíl tiše ukrojil: na „Velkých" se stav
       suroviny ořezal už od 1 000 000 („1 999 946" → „1 999 …"), na „Střední" od 100 000 000.
       Změřeno: 190/219/247 px pokryje i devítimístné číslo na všech třech tierech.
       Na 1366×768 zůstává 190 px jen PREFEROVANÁ hodnota - flex-shrink výše pilíře dál zúží
       (naměřeno 158-170 px) a horní lišta se vejde i s odznakem Zlaté horečky (ozubené kolo
       na 1342/1366), takže se F1 tímhle neruší.

       VĚDOMÝ ÚSTUPEK: širší pilíře berou místo ukazateli cíle. V JEDINÉ kombinaci - 1920×1080,
       úroveň „Velké", AKT 2 a zrovna běžící Zlatá horečka - se kvůli tomu popisek `#goalLabel`
       zkrátí elipsou o 26 px („Zátěž 4 · Liberec: 800 / 520 MW"). Je to výměna, ne přehlédnutí:
       proti tomu stojí ořez STAVU SUROVINY, který nastával na „Velkých" od 1 000 000 pořád, ve
       všech stavech a bez ohledu na horečku. Zkrácený popisek cíle má navíc tooltip s přesnými
       čísly (goalTooltipHtml) a horečka je vidět jen 12,7 % herního času.
       Menší násobky se zkoušely a nefungují: `calc(150px + 40px * var(--ui-scale))` (190/196/202)
       neuhlídá ani `.resAmount`, ani čísla odemčení. Pokud tenhle ústupek jednou vadit bude,
       správné místo na opravu je zkrácení popisku zátěže v aktu 2 (nález P2-13 auditu), ne návrat
       k pevné šířce pilíře. */
    width: calc(190px * var(--ui-scale));
    flex: 0 1 auto;
    min-width: 0;
    padding: 4px 8px 6px;
    background: color-mix(in srgb, var(--res-color, transparent) 10%, transparent);
    border-bottom: var(--border-w-3) solid var(--res-color, transparent);
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: background 0.15s ease;
}
.resStat:hover { background: color-mix(in srgb, var(--res-color, transparent) 20%, rgba(255, 255, 255, 0.05)); }

/* Šipka mezi pilíři = páteř "Cesta republiky" (Dřevo › Uhlí › Uran). Sekvence odemykání se čte
   jako jedna cesta, ne tři nesouvisející dlaždice. Dekorativní - nechytá kliky. */
.resStat:not(:first-child)::before {
    content: '';
    position: absolute;
    left: -19px;
    top: 50%;
    width: 7px;
    height: 7px;
    border-top: 2px solid var(--text-muted);
    border-right: 2px solid var(--text-muted);
    transform: translateY(-50%) rotate(45deg);
    pointer-events: none;
}

/* Zamčený pilíř: místo množství/rychlosti ukazuje podmínku odemčení + progress bar (páteř). */
.resStat:not(.locked) .resUnlock { display: none; }
.resStat.locked .resAmount, .resStat.locked .resRate { display: none; }
.resStat.locked { opacity: 0.9; }
.resUnlock { display: flex; flex-direction: column; gap: 2px; margin-top: 1px; }
.resUnlockCond { font-size: var(--fs-xs); color: var(--text-muted); white-space: nowrap; }
.resUnlockCond .lockMini { display: inline-flex; width: calc(11px * var(--ui-scale)); height: calc(11px * var(--ui-scale)); vertical-align: -1px; }
.resUnlockCond .lockMini .gicon { width: 100%; height: 100%; }
/* Pilíř horní lišty používá od B2 (plán 5.19) tentýž komponent řádků jako karta odemčení dole
   (unlockProgressBar) - jen v úspornější velikosti, protože panel má pevnou výšku a uran má
   řádky dva. Vlastní .resUnlockBar/.resUnlockFill tím odpadly. */
.resUnlock .unlockRows { gap: 2px; margin: 0; }
/* TŘI STOPY, ne čtyři (vizuální QA audit v6.9). Do 6.9 tu bylo
   `auto auto auto minmax(28px, 1fr)`, tedy stopa navíc pro pruh - jenže ten se v pilíři od 5.26
   NEKRESLÍ (`unlockProgressBar(spec, false)`, render.panels.js; rozhodnutí zadavatele po hraní:
   „z pruhu zbyl pár pixelů, ze kterých se nedal odečíst žádný postup"). Řádek má tedy 3 buňky,
   ale mřížka držela 4 stopy a ta čtvrtá si přes `1fr` brala VŠECHNO volné místo. Na čísla zbylo
   63,8 px, „3 120 / 42 300" se zalomilo na dva řádky a řádek narostl z 15 na 30 px - u uranu jsou
   řádky dva, takže pilíř vyskočil na 133 px v panelu vysokém 90 px a popisek „URAN" utekl nad
   horní hranu obrazovky. Klasické „CSS nedostalo zprávu o změně v JS".
   `nowrap` na číslech je druhá polovina: zalomení tady NIKDY nesmí nastat, protože panel má pevnou
   výšku a přetečení z něj se nemá kam schovat. Přebytek radši ořízne elipsa - přesná čísla nese
   tooltip pilíře (resourceTooltipHtml) i aria-label. Se škálovanou šířkou pilíře (viz .resStat)
   na elipsu nedojde ani u devítimístných hodnot; je to pojistka, ne běžný stav. */
.resUnlock .unlockRow { grid-template-columns: auto 1fr auto; font-size: var(--fs-2xs); column-gap: 4px; line-height: 1.15; }
.resUnlock .unlockRowNums { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.resUnlock .unlockRowIcon { width: calc(11px * var(--ui-scale)); height: calc(11px * var(--ui-scale)); }

/* USTUPOVÁNÍ ŘÁDKU ODEMČENÍ (nález N4, re-běh checklistu nad v6.16). Spouští to odznak Zlaté
   horečky: naskočí na 8 s, vezme si z lišty 166 px a pilíře mu je dorovnají. Na 1366×768
   s úrovní „Velké" spadnou z 245 na 186 px a čísla odemčení se elipsou uřízla o 6 a 13 px
   („12 450 / 59 100"). Týž vzor jako P0-1, P1-4 i N1 - pevný rám, elastický obsah a `hidden`,
   který deficit tiše spolkne. Pořadí ústupu je proto stejné jako u #goalBox: nejdřív dekorace
   a odsazení, ČÍSLA NIKDY.
     1. --tightRows    fajfka přestane rezervovat místo + mezera 4 → 2 px
     2. --tighterRows  ikona řádku 11 → 9 px
   Naměřeno na jediné kombinaci, kde k tomu vůbec dochází: krok 1 sám stačí (ok/ok), krok 2 je
   rezerva. Krok 1 nebere hráči nic viditelného - fajfka je do splnění `visibility: hidden`
   (viz .unlockRowCheck níž), takže se ruší jen REZERVACE místa. Ta má bránit poskočení čísel při
   dotažení měny; v zúženém stavu se to poskočení vrací, ale jen jednou za surovinu a v okamžiku,
   kdy se pilíř překresluje tak jako tak.
   Ikona řádku se NESMÍ schovat úplně, jen zmenšit: uranový pilíř má dva řádky (dřevo a uhlí)
   a rozliší je jedině ona - `aria-label` pilíře je pouhé „Uran — přejít na panel". Vyzkoušeno,
   zamítnuto. Zamítnuta i podlaha `min-width` na .resStat: pilíře se sice roztáhnou, ale
   `#resStats` zůstane na své šířce a přebytek 47-105 px vyleze rovnou přes #bonusChanceBox -
   tedy přesně regrese F1, před kterou varuje komentář u .resStat výš. */
.resStat--tightRows .resUnlock .unlockRow { column-gap: 2px; }
.resStat--tightRows .resUnlock .unlockRowCheck { display: none; }
.resStat--tighterRows .resUnlock .unlockRowIcon { width: calc(9px * var(--ui-scale)); height: calc(9px * var(--ui-scale)); }

.resIcon { width: calc(32px * var(--ui-scale)); height: calc(32px * var(--ui-scale)); flex: 0 0 calc(32px * var(--ui-scale)); }
.resIcon .gicon { width: 100%; height: 100%; }

/* F1 (plán 6.0): aby šel pilíř zúžit (viz .resStat výš), musí jít zúžit i jeho textový sloupec -
   jinak by ho min-content textu držel na původní šířce. Zkracuje se elipsou; plná hodnota je
   v tooltipu pilíře (resourceTooltipHtml) i v aria-label, takže se žádná informace neztrácí. */
/* `flex: 1` přibylo v6.42 kvůli BL-03. Do 6.41 se sloupec smršťoval na obsah, takže mu vpravo
   v pilíři zbývalo 64 px nevyužitého místa (naměřeno na „Výchozí"). Bilance vedle pruhu rezervy
   si je bere - bez tohohle řádku by ji pruh musel zaplatit ze své šířky a spadl by z 96 na 33 px.
   Na akt 1 to nemá vliv kromě jednoho zlepšení: širší sloupec znamená, že se stav suroviny
   začne krátit elipsou později, ne dřív. */
.resTexts { min-width: 0; flex: 1 1 auto; }
.resName, .resAmount, .resRate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.resName { font-size: var(--fs-sm); color: var(--text-label); text-transform: uppercase; letter-spacing: 1px; }
.resAmount { font-size: var(--fs-2xl); font-weight: bold; transition: color 0.15s ease; }
.resAmountLock.inlineIcon { width: calc(24px * var(--ui-scale)); height: calc(24px * var(--ui-scale)); vertical-align: -6px; filter: drop-shadow(0 0 3px rgba(245, 196, 81, 0.5)); }
.resRate { font-size: var(--fs-sm); color: #cfcfcf; }

.resAmount.gainFlash { animation: gainFlash 0.22s ease; }
@keyframes gainFlash {
    0% { color: var(--res-color, #fff); text-shadow: 0 0 8px var(--res-color, #fff); }
    100% { color: inherit; text-shadow: none; }
}

.resStat.milestonePop { animation: milestonePop 0.7s ease; }
@keyframes milestonePop {
    0% { transform: scale(1); }
    30% { transform: scale(1.12); }
    100% { transform: scale(1); }
}

/* margin-left:auto posouvá box (a vše za ním, tj. i #goalBox) doprava - vytváří větší mezeru od
   posledního zdroje (Uran) a lépe využívá prostor mezi ním a cílem. Klikací (viz listener v
   main.js init()) - vede na Bonus tab, stejný vzor jako .resStat výše. */
#bonusChanceBox {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: 8px 16px;
    /* Fáze 4: jemně tónovaný glass + horní světlá linka - „ukazatel štěstí" čte jako prémiový
       panel a jako sourozenec #goalBox (stejný recept). Tón od 2026-08 --bonus-color (terakotová),
       ne univerzální --gold - ať widget slibuje přesně tu barvu, kterou pak vidí po rozkliknutí tabu. */
    background: linear-gradient(180deg, color-mix(in srgb, var(--bonus-color) 9%, var(--surface)), var(--surface));
    border: var(--border-w-1) solid rgba(224, 154, 133, 0.28);
    border-radius: var(--radius-md);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
    font-size: var(--fs-base);
    color: var(--bonus-color);
    margin-left: auto;
    cursor: pointer;
    transition: background 0.15s ease, box-shadow 0.15s ease;
}
#bonusChanceBox:hover { background: color-mix(in srgb, var(--bonus-color) 16%, var(--surface)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 0 14px rgba(224, 154, 133, 0.15); }
.bonusIcon .gicon { width: calc(26px * var(--ui-scale)); height: calc(26px * var(--ui-scale)); display: block; }

/* align-items:stretch (výchozí u flex sloupce) roztáhne .bonusLevelDots na stejnou šířku jako
   .bonusChanceText nad ním (ověřeno napříč všemi stavy 0-5 odemčených úrovní - šířka řádku
   obdélníků vždy přesně odpovídá šířce textu, i když se text mění). */
.bonusTextWrap { display: flex; flex-direction: column; gap: 5px; }

/* Jeden obdélník za úroveň v CRITICAL_CLICK_UPGRADES - prázdný/orámovaný do odemčení, vyplněný
   zlatě po něm (viz renderBonusChanceBox v render.js). Vyšší (8px místo 6px) a s větší mezerou
   (4px místo 3px), aby korespondovaly se zvětšeným textem nahoře. */
.bonusLevelDots { display: flex; gap: var(--space-1); }
.bonusLevelDot {
    width: 10px;
    height: 8px;
    border-radius: var(--radius-xs);
    border: 1px solid rgba(224, 154, 133, 0.4);
    background: transparent;
}
.bonusLevelDot.filled { background: var(--bonus-color); border-color: var(--bonus-color); }

/* ---------- Nabíjecí pruh kritického zásahu (T8, verze 4.3) ----------
   Nahradil statický text "1 % šance ×5". Pruh se přestavuje s každým klikem (renderBonusChanceBox
   přepisuje innerHTML boxu), takže tu ZÁMĚRNĚ není `transition: width` - u nového elementu by se
   nikdy nespustila a jen by mátla příštího čtenáře. Skoková změna navíc u počítadla kliků působí
   správně (okamžitá odezva na klik). */
.critChargeTrack {
    height: 5px;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.35);
    overflow: hidden;
    border: 1px solid rgba(224, 154, 133, 0.25);
}
.critChargeFill {
    height: 100%;
    background: linear-gradient(90deg, color-mix(in srgb, var(--bonus-color) 70%, #fff), var(--bonus-color));
}

/* Nabito - pulz je na SAMOTNÉM #bonusChanceBox, ne na vnitřním pruhu: box přežívá přepis innerHTML
   (viz komentář u renderBonusChanceBox v render.js), takže animace běží plynule místo restartu
   při každém kliku. */
#bonusChanceBox.critCharged {
    border-color: var(--crit-color);
    animation: critChargedPulse 1.1s ease-in-out infinite;
}
#bonusChanceBox.critCharged .bonusChanceText { color: var(--crit-color); font-weight: 700; }
#bonusChanceBox.critCharged .critChargeFill { background: linear-gradient(90deg, #fff, var(--crit-color)); }
@keyframes critChargedPulse {
    0%, 100% { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 0 6px rgba(255, 225, 77, 0.35); }
    50% { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 0 16px rgba(255, 225, 77, 0.75); }
}

/* Nabitý krit zvýrazní i políčka na mapě - "teď zamiř na nejbohatší ložisko". Jedna třída na
   KONTEJNERU #mapGrid (jedno přepnutí, viz renderCritChargedHint), zvýraznění dělá CSS potomkům -
   ne průchod přes všech 159 ikon na každý klik. Jen jemný zlatý dech, ne poskakování: mapa se při
   klikání nesmí "hýbat" pod kurzorem. */
#mapGrid.critCharged .resourceIcon:not(.locked) {
    animation: critReadyGlow 1.1s ease-in-out infinite;
}
@keyframes critReadyGlow {
    0%, 100% { filter: none; }
    50% { filter: drop-shadow(0 0 5px rgba(255, 225, 77, 0.85)); }
}

/* Zlatá horečka - odznak s odpočtem (renderGoldRushBadge, render.js). Prázdný/skrytý mimo aktivní
   event (display:none přes chybějící .show), aby v běžném stavu nezabíral místo v horním panelu
   ani nerušil rozestupy vedle #bonusChanceBox/#goalBox. Pulzující záře (na rozdíl od statického
   #bonusChanceBox) - má jasně číst jako dočasný, na první pohled odlišný stav od trvalých panelů. */
#goldRushBadge {
    display: none;
    align-items: center;
    gap: var(--space-2);
    padding: 8px 16px;
    background: linear-gradient(180deg, color-mix(in srgb, var(--goldrush-color) 16%, var(--surface)), var(--surface));
    border: var(--border-w-1) solid var(--goldrush-color);
    border-radius: var(--radius-md);
    color: var(--goldrush-color);
    font-size: var(--fs-base);
    font-weight: 600;
    white-space: nowrap;
    animation: goldRushPulse 1.1s ease-in-out infinite;
}
#goldRushBadge.show { display: flex; }
.goldRushIcon { font-size: var(--fs-lg); flex: 0 0 auto; }
/* F1 (plán 6.0): odznak je nejširší prvek lišty (254-282 px podle úrovně UI) a naskakuje jen na 8 s
   každých ~63 s - právě on přetlačoval ozubené kolo mimo obrazovku na referenčním 1920 s úrovní
   „Velké". Zkracuje se od slov, NIKDY od odpočtu: „⚡ Zlatá horečka ×2 — 0:08" → „⚡ ×2 — 0:08".
   Násobič i zbývající čas jsou to jediné, co hráč v těch osmi sekundách opravdu potřebuje;
   že jde o Zlatou horečku, mu paralelně říká banner nad mapou i zbarvení mapy.

   PRIORITA ZKRACOVÁNÍ (nález N4). Samotné `min-width: 0` ten slib výš nedrží. Flexbox dělí
   nedostatek místa POMĚRNĚ podle šířek, takže si odznak i pilíře ukrojily každý svůj díl:
   odznak zůstal na 213 px (slova jen zčásti zkrácená) a pilíře spadly z 245 na 170 px, kde už
   se čísla odemčení nevešla. Slova odznaku jsou přitom to nejméně důležité v celé liště -
   `flex-shrink: 6` je tedy pošle pryč DŘÍV, než začnou ustupovat data. Naměřeno na 1366×768:
   odznak 213 → 166 px, pilíř 170 → 186 px.

   Podlaha není opatrnost, ale nutnost: bez ní se odznak na „Velkých" srazí na 88 px a UŘÍZNE
   ODPOČET (změřeno), tedy přesně to, co má být chráněné. 128 px × ui-scale sedí těsně nad
   chráněný obsah (ikona + „×2 — 0:08" + odsazení = 139/153/169 px podle úrovně) a zároveň je
   pod šířkou, na které odznak dnes stojí, takže mu nikde nepřidává místo na úkor pilířů. */
#goldRushBadge { min-width: calc(128px * var(--ui-scale)); flex-shrink: 6; }
.goldRushWords { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.goldRushValue { flex: 0 0 auto; }
@keyframes goldRushPulse {
    0%, 100% { box-shadow: 0 0 0 rgba(255, 157, 61, 0); }
    50% { box-shadow: 0 0 14px rgba(255, 157, 61, 0.55); }
}

/* --goal-pct (0-100, nastavuje renderTopPanel()) pohání sytost okolní záře - na 1 % skoro
   neznatelná, na 99 % zřetelně viditelná. Cíl hry tak postupně nabírá vizuální váhu místo
   stejného vzhledu od začátku do konce. Glow je na #goalBox (ne na #goalBarTrack), aby ho
   neořízlo track's vlastní overflow:hidden potřebné pro zaoblené rohy pruhu.
   Pozadí/okraj/border-radius sladěné s #bonusChanceBox výše (stejné panel-bg-light + zlatě
   tónovaný okraj) - obě jsou "ukazatele postupu/štěstí" v horním panelu a měly by číst jako
   sourozenecké komponenty, ne dvě nesouvisející věci vedle sebe. */
#goalBox {
    flex: 1;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--space-5);
    max-width: var(--goal-box-max-w);
    /* F1 (plán 6.0): cíl je hlavní tlumič tlaku v liště - `#goalLabel` už od B4 zkracuje čísla přes
       formatBigInt a plnou hodnotu nese tooltip, takže se smí zúžit bez ztráty informace.
       ALE JEN K PODLAZE: `flex: 1` znamená `flex-basis: 0`, takže cíl dostane jen to, co po ostatních
       zbyde - a při aktivní Zlaté horečce na 1366 px z něj zbylo 32-60 px, tedy holá ikona bez
       popisku. Aktuální cíl je přitom to hlavní, co hráč v liště sleduje. Podlaha 200 px ho udrží
       čitelný a zbytek tlaku přenese na pilíře surovin (ty klesnou na 147-170 px, ale částky
       v nich zůstávají celé - změřeno na všech třech úrovních UI). */
    min-width: 200px;
    margin-left: auto;
    padding: 6px 14px;
    /* Fáze 4: zlatě tónovaný glass panel (sourozenec #bonusChanceBox) + horní světlá linka. Dynamická
       záře (--goal-pct) sílí s postupem - cíl vizuálně „nabírá váhu" od začátku ke konci hry. */
    background: linear-gradient(180deg, color-mix(in srgb, var(--gold) 9%, var(--surface)), var(--surface));
    border: var(--border-w-1) solid rgba(245, 196, 81, 0.28);
    border-radius: var(--radius-md);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
    filter: drop-shadow(0 0 calc(var(--goal-pct, 0) * 0.14px) rgba(245, 196, 81, calc(var(--goal-pct, 0) / 130)));
}

/* Ikona vlevo od CELÉHO bloku (text + pruh), ne jen vedle textu - zarovnaná na výšku obou řádků
   dohromady, proto může být výrazně větší a lépe rozpoznatelná. */
#goalIcon { width: calc(48px * var(--ui-scale)); height: calc(48px * var(--ui-scale)); flex: 0 0 calc(48px * var(--ui-scale)); object-fit: contain; }

#goalTextWrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }

/* Zvětšeno a ztučněno oproti původním 13px/normal - má odpovídat vizuální váze zvětšené ikony a
   silnějšího pruhu níž, ne působit jako drobný text nad výrazným dlouhým pruhem. */
/* nowrap + ellipsis (T11, verze 5.0): horní panel má PEVNOU výšku, takže druhý řádek popisku tlačí
   na pruh postupu pod ním. Do T11 to nehrozilo (jediný cíl = jeden známý text), ale nekonečná řada
   generuje i dlouhá jména a devítimístná čísla, takže délka popisku přestala být předvídatelná.
   Zkracování čísel (formatBigInt v render.js) samo nestačí - naměřeno v prohlížeči, že na úrovni
   "Velké" má popisek jen ~514 px (MÍŇ než na "Střední", protože zbytek panelu roste s písmem a
   #goalBox na svůj --goal-box-max-w vůbec nedosáhne), takže tam zalomil i běžný druhý cíl.
   Ellipsis je vědomý kompromis: radši ukroušený konec ("uranu") než rozbitý panel. U ručně psaných
   cílů 1-4 se neprojeví, jen u pozdních procedurálních na největší úrovni. */
/* TŘI ČÁSTI, NE JEDEN TEXT (P2-13, v6.14). Do 6.13 tu byl jeden textový uzel s elipsou, takže se
   při nedostatku místa uřízl KONEC - tedy čísla, jediné, co hráč z popisku doopravdy potřebuje
   („Jaderná elektrárna: 0 / 150 0…"). Flexbox s `flex: 0 0 auto` na číslech obrací pořadí ústupu:
   zkracuje se JMÉNO, čísla zůstávají celá. Zbylé dva stupně (skrytí ikony a jednotky) řeší
   refreshGoalBoxFit v render.panels.js. */
#goalLabel {
    display: flex;
    align-items: baseline;
    gap: 0.35em;
    font-size: var(--fs-lg);
    font-weight: 600;
    color: var(--gold);
    white-space: nowrap;
    overflow: hidden;
}
.goalName { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.goalNums { flex: 0 0 auto; }
/* Stupeň 2: ikona je dekorace, ne data - ustupuje dřív než jakýkoli text. Uvolní 62-69 px. */
#goalBox.goalBox--tight #goalIcon { display: none; }
/* A s ikonou padá i důvod pro podlahu 200 px (nález N2 z průchodu checklistem nad v6.15). Ta byla
   nastavená tak, aby se vedle ikony vešel i popisek (viz `min-width` u #goalBox výš) - jenže
   v tomhle režimu žádná ikona není. Uvolněných ~50 px se vrátí pilířům surovin, které jsou na
   1366×768 se Zlatou horečkou nejvíc pod tlakem: naměřeno 156 → 168 px, čímž se stav suroviny
   („5,0 mil.") přestane ořezávat. Popisek cíle si na to místo nedělá nárok - má vlastní ladder
   a v případě potřeby ustoupí o stupeň dál (na `--tighter`), což je přesně to, k čemu je. */
#goalBox.goalBox--tight { min-width: 150px; }
/* Stupeň 3: jednotka („ uranu" / „ MW") jde poslední. Uvolní dalších ~45 px a nastává jen na
   1366×768 s úrovní „Velké" v aktu 2 - jedinou kombinací, kde ani bezikonový popisek nestačí. */
#goalBox.goalBox--tighter .goalUnit { display: none; }
/* Jméno zkrácené pod ~3 znaky se schová celé - viz refreshGoalBoxFit (render.panels.js). Samotná
   elipsa vypadá jako artefakt („. 0 / 150 000 uranu"), ne jako zkrácený text. */
#goalBox.goalBox--noname .goalName { display: none; }

#goalBarTrack {
    width: 100%;
    height: 16px;
    background: #14201a;
    border: var(--border-w-2) solid #2b3654;
    border-radius: var(--radius-sm);
    overflow: hidden;
    position: relative;
    background-image: repeating-linear-gradient(90deg, rgba(0,0,0,0) 0, rgba(0,0,0,0) calc(25% - 1px), rgba(0,0,0,0.4) 25%, rgba(0,0,0,0) calc(25% + 1px));
}

#goalBarFill {
    height: 100%;
    width: 0%;
    background: linear-gradient(90deg, #6fbf3f, var(--gold));
    transition: width 0.3s ease;
}

/* Trvalý stav po dosažení cíle (state.victoryShown) - ne jen jednorázová oslava v checkVictory().
   Bez tohohle vypadá #goalBox po zavření vítězného modalu úplně stejně jako předtím, přestože jde
   o největší úspěch celé hry. */
#goalBox.victory {
    /* Pevná (ne --goal-pct odvozená) záře - postup je natrvalo na 100 %, glow proto nemá důvod
       dál kolísat s výpočtem. */
    filter: drop-shadow(0 0 8px rgba(245, 196, 81, 0.65));
}
#goalBox.victory #goalLabel { color: var(--crit-color); font-weight: bold; }
#goalBox.victory #goalBarFill { background: var(--gold); }
#goalBox.victory #goalBarTrack { border-color: var(--gold); }

/* top:100% + margin-top (ne natvrdo top:88px) - pozicuje se vůči skutečné výšce #topPanel
   (position:relative výše), ne vůči pevné 80px hodnotě zapsané dvakrát na dvou místech. Kdyby se
   výška horního panelu někdy změnila, #notice se posune automaticky s ním. margin-top je 12px
   (ne 8px) - top:100% se počítá vůči padding-boxu #topPanel (box-sizing:border-box vyjme jeho
   4px border-bottom), takže +4px navíc dorovnává na skutečnou vizuální hranu panelu. */
#notice {
    position: absolute;
    top: 100%;
    margin-top: 12px;
    left: 50%;
    background: #1a2233;
    border: var(--border-w-2) solid #2b3654;
    padding: 8px 18px;
    font-size: var(--fs-base);
    border-radius: var(--radius-sm);
    z-index: 50;
    transform: translate(-50%, -14px);
    opacity: 0;
    pointer-events: none;
    transition: transform 0.25s ease, opacity 0.25s ease;
}
#notice.visible { transform: translate(-50%, 0); opacity: 1; }
#notice.notice-positive { border-color: var(--action); background: #1f3418; color: #d8f0c8; }
#notice.notice-warning { border-color: var(--danger); background: #402020; color: #f0d8d8; }
#notice.notice-neutral { border-color: #2b3654; background: #1a2233; color: var(--text-1); }
/* Kontextová nápověda (T15, implementační plán 4.0) - delší text než ostatní typy hlášek (viz
   NOTICE_DURATION_MS.onboarding v render.js), proto max-width + zalomení na střed, ať se nenatáhne
   přes půl obrazovky na jeden řádek. */
#notice.notice-onboarding { border-color: var(--action); background: #123331; color: #cdeee8; max-width: 340px; text-align: center; white-space: normal; }

/* ---------- Mapa ---------- */
/* Mapa bere zbylou výšku (flex:1) mezi horním a spodním panelem a scaluje #mapStage tak, aby se
   vešla (fitMap v main.js nastaví --map-scale). overflow:hidden ořízne přesah scalované mapy. */
#mapContainer {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
    /* L3 — mapa (Fáze 2): radiální světlo shora dělá z mapy „nasvícený stůl" = jednoznačné ohnisko
       obrazovky. Panely nad/pod ustupují, oko jde na mapu. */
    background: radial-gradient(130% 120% at 50% 0%, #0f1c2b, #0a1420 70%);
}

#mapStage {
    width: 1760px;
    height: 800px;
    flex: 0 0 auto;
    position: relative;
    transform-origin: center center;
    transform: scale(var(--map-scale, 1));
}

/* Podkladová vrstva - terén, sousední státy, hory, řeky, města (čistě vizuální, vykresleno přes canvas) */
#mapCanvas {
    position: absolute;
    inset: 0;
    width: 1760px;
    height: 800px;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

/* Popisky geografie (T13) - jména pohoří/řek/měst, vždy-zapnutá vrstva (žádný hover, viz render.js
   komentář u buildMapLabels). Mezi canvasem a #mapGrid, ať ikony surovin (kreslené později v DOM)
   překryjí text tam, kde by se náhodou potkaly - čitelnost ikon má přednost. */
#mapLabels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
#mapLabels.hidden { display: none; }
/* PROTIŠKÁLOVÁNÍ velikosti (vizuální QA audit v6.9, nález P2-8). Popisky leží uvnitř #mapStage,
   který fitMap() zmenšuje tím víc, čím vyšší jsou panely - a panely rostou s úrovní zobrazení.
   Pevných 11 px se tak renderovalo jako 10,31 px na „Malých", ale jen 9,16 px na „Velkých":
   nastavení, které má text ZVĚTŠIT, ho na mapě o 11 % zmenšovalo. Dělení `--map-scale` (nastavuje
   ho fitMap na #mapStage) ten poměr ruší - popisek má stejnou fyzickou velikost na všech úrovních.

   OBRYS místo pouhého stínu. Popisky neleží na panelu, ale na pixel-artu, a proti nejsvětlejším
   dlaždicím měly změřený kontrast 1,09-1,55:1 (vzorkováno přímo z canvasu). Měkký stín na to
   nestačí; osm směrů plné černé vytvoří skutečný obrys, který text oddělí od podkladu bez ohledu
   na to, co je pod ním. Vyšší krytí textu (0,55 → 0,78) zvedá naměřený poměr, obrys řeší zbytek. */
.mapLabel {
    position: absolute;
    transform: translate(-50%, -50%);
    font-size: calc(11px / var(--map-scale, 1));
    font-weight: 600;
    letter-spacing: 0.3px;
    white-space: nowrap;
    color: rgba(255, 255, 255, 0.78);
    text-shadow:
        -1px -1px 0 rgba(0, 0, 0, 0.85), 1px -1px 0 rgba(0, 0, 0, 0.85),
        -1px  1px 0 rgba(0, 0, 0, 0.85), 1px  1px 0 rgba(0, 0, 0, 0.85),
         0 1px 3px rgba(0, 0, 0, 0.9);
}
.mapLabel-river { color: rgba(178, 219, 255, 0.92); font-style: italic; font-weight: 500; }
.mapLabel-city { color: rgba(255, 236, 205, 0.95); }
/* Popisky revírů (C1, plán 5.19) - menší verzálky s prostrkáním v barvě suroviny (--zone-color
   nastavuje buildMapLabels z RESOURCE_META). Verzálky jsou tu nositelem KATEGORIE: hráč na první
   pohled odliší "revír, ve kterém se těží" od pohoří/města, aniž by se kreslil rámeček nebo ikona -
   sedm dalších cedulek by mapu zahltilo (riziko pojmenované v zadání C1). Menší a prostrkanější než
   pohoří ze stejného důvodu.
   Silnější stín než u ostatních popisků: text leží na pestrém terénu (uhelná sloj, uranové ložisko)
   a barva suroviny je světlejší než bílá 55 % ostatních popisků, takže by se bez něj na světlých
   dlaždicích ztratil. */
/* Popisky revírů dostávají obrys i plnou sytost přednostně (vizuální QA audit v6.9, P2-8): na rozdíl
   od pohoří/řek/měst NEJSOU dekorace. Hra na jméno revíru odkazuje v úspěších („Černé srdce",
   „Uranová republika"), v hlášce JACKPOT i v aria-label políčka, takže je to informace a má
   splňovat kontrast. Nejhorší naměřená hodnota před opravou byla 1,00:1 („Ostravsko-Karvinsko" -
   text měl v tom bodě přesně barvu terénu pod sebou, tedy byl doslova neviditelný). */
/* POCTIVÁ POZNÁMKA K ČÍSLU: na 4,5:1 se tyhle popisky dostat NEDÁ, a to ani čistě bílé. Změřeno
   vzorkováním pixelů pod textem: čistá --zone-color dává v nejhorším bodě 1,00:1, mix s bílou 1,62:1
   a plná bílá 2,36:1 - nad libovolným pixel-artem se vždycky najde dlaždice podobného jasu.
   Jediné, co by práh splnilo, je podkladová plocha za textem, a tu design vědomě odmítá („sedm
   dalších cedulek by mapu zahltilo", zadání C1). Skutečným nositelem čitelnosti je proto OBRYS
   (viz text-shadow níž), který se do vzorce pro kontrastní poměr nepromítá vůbec. Mix s bílou je
   tu kvůli tomu, co změřit jde: zvedá nejhorší bod z 1,00 na 1,62 a přitom drží barevné kódování
   suroviny, podle kterého hráč revír odliší od pohoří a měst. */
.mapLabel-zone {
    color: color-mix(in srgb, var(--zone-color, #fff) 55%, #fff);
    font-size: calc(10px / var(--map-scale, 1));
    letter-spacing: 1.2px;
    text-transform: uppercase;
    text-shadow:
        -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000,
         0 0 5px rgba(0, 0, 0, 0.95);
}

/* Rostoucí elektrárna (T14) - mezi #mapLabels a #mapGrid, ať ikony surovin (kreslené později v DOM)
   překryjí stavbu tam, kde by se náhodou potkaly (viz stejná úvaha u #mapLabels výš). Sloty jsou
   skryté přes .hidden, dokud jejich cíl nezačne (viz refreshPlantOverlay v render.js). */
#plantOverlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.plantSlot {
    position: absolute;
    transform: translate(-50%, -50%);
}
.plantSlot.hidden { display: none; }
.plantSlot--main { width: 72px; height: 65px; }
.plantSlot--satellite { width: 46px; height: 41px; }
.plantSlot .gicon { width: 100%; height: 100%; display: block; }
/* Vedení (síť/export, T14) - jedna sdílená SVG přes celou mapu, stejné absolutní rozměry jako
   #mapCanvas (viz komentář tam "jediný zdroj pravdy... duplicitně i v CSS" - stejná konvence). */
.plantLines {
    position: absolute;
    inset: 0;
    width: 1760px;
    height: 800px;
    pointer-events: none;
}
.plantLineSlot.hidden { display: none; }
/* Záře nekonečných cílů (C4, plán 5.19) - do 5.18 tu byla cedule s číslem („#5"), kterou horní
   lišta duplikovala. Poloměr i sytost rostou s `--glow-step` (1 = pátý cíl, strop
   PLANT_GLOW_MAX_STEP nastavuje refreshPlantOverlay), takže desátý cíl vypadá jinak než pátý,
   aniž by se kreslila nová grafika - přesně v duchu "stačí opakovat poslední vizuál" z T14.
   Radiální gradient, ne box-shadow: záře má mít měkký okraj bez zřetelného obrysu, aby se nepletla
   s prstencem zásoby na uzlu (.resourceIcon). */
.plantGlowSlot {
    position: absolute;
    transform: translate(-50%, -50%);
    --glow-step: 1;
    width: calc(96px + var(--glow-step) * 28px);
    height: calc(96px + var(--glow-step) * 28px);
    border-radius: 50%;
    /* Alfa je stejná jako v konceptu schváleném zadavatelem (2026-08-28) - slabší hodnoty vypadaly
       v testu na mapě jako nic. Roste s krokem, ale mnohem pomaleji než poloměr: hlavní nositel
       "kolikátý je to cíl" má být VELIKOST záře, ne to, jak moc přesvítí terén pod sebou. */
    background: radial-gradient(circle,
        rgba(255, 242, 180, calc(0.38 + var(--glow-step) * 0.05)) 0%,
        rgba(255, 214, 90, calc(0.22 + var(--glow-step) * 0.03)) 40%,
        rgba(255, 200, 60, 0) 70%);
    animation: plantGlowPulse 4.5s ease-in-out infinite;
}
.plantGlowSlot.hidden { display: none; }
/* Dýchání, ne blikání - 4,5 s a rozsah 0,85-1,0 je pod prahem, kde by to rušilo klikání po mapě
   (viz stejná úvaha u pulzu indikátoru tabu, B1). V omezeném režimu animace mizí úplně (viz
   reduced-motion bloky dole) a záře zůstane statická, tedy pořád čitelná. */
@keyframes plantGlowPulse {
    0%, 100% { opacity: 0.85; }
    50% { opacity: 1; }
}
@keyframes plantGlowPulse { 0%, 100% { opacity: 0.7; } 50% { opacity: 1; } }
.plantGlow { animation: plantGlowPulse 2s ease-in-out infinite; transform-origin: center; }
@keyframes plantSteamDrift { 0% { transform: translateY(0); opacity: 0.5; } 100% { transform: translateY(-6px); opacity: 0; } }
.plantSteam { animation: plantSteamDrift 2.4s ease-in-out infinite; transform-origin: center; }
.plantSteamR { animation-delay: 1.1s; }
body.reduced-motion .plantGlow, body.reduced-motion .plantSteam { animation: none; }

/* Klikací vrstva - ikony surovin, absolutně pozicované nad podkladem */
#mapGrid {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.resourceIcon {
    position: absolute;
    width: 34px;
    height: 34px;
    /* Druhý, NE-barevný kanál hojnosti (T4, design review 2026-08-17) - samotná barva lemu byla na
       hutné pixelové mapě (159 stejně tmavých destiček) nedostatečná a pro barvoslepé hráče
       nefunkční. --tier-scale přepisují .abundance-* pravidla níže (0.85× až 1.2×); výchozí 1 tady
       je jen záchranná síť pro stav bez přiřazené hojnosti. */
    --tier-scale: 1;
    transform: translate(-50%, -50%) scale(var(--tier-scale));
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    pointer-events: auto;
    /* Puk skoro neprůhledný (dřív rgba(0,0,0,.32)) - mapa je sytá pixel-art malba (lesy/hory/řeky/
       střechy), při .32 skrz ni prosvítala natolik, že ikona (hlavně dřevo - zelený piktogram na
       zeleném terénu) splývala s podkladem. Změřeno na běžícím serveru (Canvas getImageData pod
       reálnými uzly dřeva): kontrast piktogramu vůči puku byl ~1.8:1, po zneprůhlednění ~3.7:1
       (WCAG 3:1 pro grafické objekty). Jemný radiální gradient = drobný bevel/"žeton", ne plochý kruh. */
    background: radial-gradient(circle at 32% 28%, rgba(38, 46, 62, 0.97), rgba(9, 12, 19, 0.95));
    border: 2px solid rgba(255, 255, 255, 0.4);
    user-select: none;
    transition: transform 0.1s ease, opacity 0.2s ease;
}
/* Trvalý tmavý kontrastní prstenec + stín na samostatné pseudo-vrstvě (ne na .resourceIcon samotném),
   aby nekolidoval s box-shadow, který abundance-high/veryHigh/rare pravidla níže dávají PŘÍMO na
   .resourceIcon (vyšší specificita by jinak náš univerzální stín úplně přepsala). Drží ikonu čitelnou
   nad JAKOUKOLI barvou terénu pod ní - světlou (pláň, sousední země) i tmavou (hustý les). */
.resourceIcon::after {
    content: '';
    position: absolute;
    inset: -1px;
    border-radius: 50%;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 2px 5px rgba(0, 0, 0, 0.5);
    pointer-events: none;
}
.resourceIcon .gicon { width: 20px; height: 20px; }
.resourceIcon .lockIcon { display: none; position: absolute; width: 19px; height: 19px; filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.7)); }
.resourceIcon .lockIcon .gicon { width: 100%; height: 100%; }

/* calc() na --tier-scale (ne natvrdo 1.15) - ať hover zvětší KAŽDOU hojnost o stejný POMĚR, místo
   aby přebilo rozdíl velikostí mezi tiery (co bylo 0.85× v klidu, musí zůstat nejmenší i při hoveru). */
.resourceIcon:hover { transform: translate(-50%, -50%) scale(calc(var(--tier-scale) * 1.15)); }
/* :focus-visible řeší sjednocené pravidlo v sekci Tlačítka (--focus-ring). */

/* Barva okraje + velikost (--tier-scale) = hojnost políčka (ne typ suroviny - ten už ukazuje
   piktogram). Selektory MUSÍ být vázané na .resourceIcon - bez toho stejná bare .abundance-*
   třída sedí i na .legendDot.abundance-* v legendě (viz níže), a protože legendDot nemá
   position:relative, `::before` s position:absolute by se přichytilo k nejbližšímu POZICOVANÉMU
   předkovi - celé krabici #abundanceLegend - a vykreslilo obří rotující ovál přes celou tabulku
   místo malé tečky.
   T4 (design review 2026-08-17) sem krátce přidal i tečky (background-image vrstvy podle počtu
   ABUNDANCE_TIERS[tier].marks) jako TŘETÍ signál hojnosti navrch k barvě a velikosti - zadavatel
   je ale po vyzkoušení v prohlížeči zamítl (odebráno, viz CHANGELOG). Velikost (--tier-scale)
   zůstává jediným ne-barevným kanálem. */
/* VELIKOSTNÍ KANÁL (M0.5, plán 2.0). Do v5.37 byly stupně 0,85 / 1 / 1,05 / 1,12 / 1,2, tedy
   rozestup mezi „běžnou" a „vysokou" pouhých 5 % (naměřeno v prohlížeči: 30,2 px vs. 31,7 px,
   rozdíl 1,5 px). Při 160 ikonách na jedné obrazovce to není rozlišitelné a druhý - ne-barevný -
   kanál hojnosti tím neplnil účel, kvůli kterému vznikl (T4, plán 4.0).
   Nové stupně mají rozestup ≥ 12,8 % mezi KAŽDOU dvojicí sousedních tierů.

   PROČ SE ZVĚTŠUJE SMĚREM DOLŮ a ne nahoru: strop 1,2 u „vzácné" zůstává beze změny záměrně.
   Vzácná ikona nese pulzující auru s ~24px přesahem, na který jsou dimenzované LEGEND_EXCLUSION_*
   v config.js, a uranové uzly mají medián vzdálenosti souseda 36 px při ikoně 36 px - zvětšení
   nahoru by vyrobilo překryv. Rozestup se proto kupuje zmenšením spodních tierů.

   DŮSLEDEK, KTERÝ JE POTŘEBA ZNÁT: „běžná" (nejčastější tier, 52 ze 101 dřevěných uzlů) se zmenší
   z 1,0 na 0,83, tedy z ~34 px na ~28 px. Zůstává pohodlným klikacím cílem (>24 px), ale je to
   změna, kterou hráč pozná - je to cena za to, aby bohatá ložiska vůbec šla poznat. */
.resourceIcon.abundance-low { --tier-scale: 0.73; border-color: #8a8a8a; }
.resourceIcon.abundance-normal { --tier-scale: 0.83; border-color: rgba(255, 255, 255, 0.45); }
.resourceIcon.abundance-high { --tier-scale: 0.94; border-color: #38bdf8; box-shadow: 0 0 4px rgba(56, 189, 248, 0.6); }
.resourceIcon.abundance-veryHigh { --tier-scale: 1.06; border-color: #a78bfa; box-shadow: 0 0 6px rgba(167, 139, 250, 0.7); }
.resourceIcon.abundance-rare {
    --tier-scale: 1.2;
    border-color: var(--abundance-rare);
    /* Silnější lem (2px → 3px) - druhý, čistě grafický rozdíl navrch k velikosti (tečky byly
       třetí, ale zamítnuté - viz komentář výš). box-sizing:border-box (globální reset) drží
       celkový rozměr puku beze změny. */
    border-width: 3px;
    animation: rarePulse 1.4s ease-in-out infinite;
}
.resourceIcon.abundance-rare::before {
    content: '';
    position: absolute;
    inset: -7px;
    border-radius: 50%;
    border: 1px dashed rgba(245, 196, 81, 0.65);
    animation: rareAuraSpin 6s linear infinite;
}

@keyframes rarePulse {
    0%, 100% { box-shadow: 0 0 8px rgba(245, 196, 81, 0.9); }
    50% { box-shadow: 0 0 18px rgba(245, 196, 81, 1), 0 0 6px #fff; }
}
@keyframes rareAuraSpin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

.resourceIcon.locked {
    cursor: not-allowed;
    filter: grayscale(0.8) brightness(0.55);
    transform: translate(-50%, -50%) scale(0.62);
    opacity: 0.75;
}
.resourceIcon.locked:hover { transform: translate(-50%, -50%) scale(0.62); }
.resourceIcon.locked .gicon:not(.lockIcon .gicon) { opacity: 0.35; }
.resourceIcon.locked .lockIcon { display: block; }

/* ---------- Zásoba ložiska (T7 implementačního plánu 4.0 - UI k vytěžitelnosti z T6) ----------
   Přebírá skoro 1:1 dřívější (zrušený) prstenec cooldownu: .cooldownRing → .stockRing, `--pct` →
   `--stock`. Zásadní rozdíl oproti cooldownu: vyčerpané ložisko NEMÁ pointer-events:none - dál se
   klikat dá (jen za DEPLETED_MULTIPLIER zlomek, viz actions.js), takže `.depleted` je jen odbarvení,
   ne blokace. Prstenec je viditelný jen pro NEplné uzly (`.stockPartial`, nastaví JS) - ne trvale
   na všech 159 ikonách, jinak by mapa byla plná prstenců i tam, kde není co ukazovat. */
.resourceIcon.depleted {
    filter: saturate(0.35) brightness(0.7);
}

.stockRing {
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    pointer-events: none;
    background: conic-gradient(rgba(255,255,255,0.75) calc(var(--stock, 100) * 1%), transparent 0);
    mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
    opacity: 0;
    transition: opacity 0.2s ease;
}
.resourceIcon.stockPartial .stockRing { opacity: 1; }

/* Jednorázový záblesk PŘI PŘECHODU do vyčerpání (flashClass, effects.js) - .depleted výš je trvalý
   stav, tohle jen upozorní na moment, kdy k němu došlo. */
.resourceIcon.stockDrained { animation: stockDrained 0.4s ease; }
@keyframes stockDrained {
    0%, 100% { filter: none; }
    50% { filter: brightness(0.45) saturate(0.15); }
}

.resourceIcon.justUnlocked { animation: unlockFlash 0.5s ease; }
@keyframes unlockFlash {
    0% { box-shadow: 0 0 0 rgba(255, 255, 255, 0); }
    40% { box-shadow: 0 0 16px 6px rgba(255, 255, 255, 0.9); }
    100% { box-shadow: 0 0 0 rgba(255, 255, 255, 0); }
}

#mapGrid.regionFocus .resourceIcon { transition: opacity 0.15s ease, transform 0.1s ease; }
.resourceIcon.regionDimmed { opacity: 0.18; }
/* Trvalý fokus (M0.4, plán 2.0) ztlumuje SLABĚJI než hover. Hover je krátký záblesk, u kterého
   ostré ztlumení pomáhá; trvalý stav je pozadí celé hry a při 0,18 by se z pixel-artové mapy
   republiky stal seznam dvaceti svítících ikon na prázdné ploše. 0,42 stačí na to, aby těžená
   surovina jasně vystoupila, a přitom je pořád vidět, kde leží zbytek ložisek. */
#mapGrid.regionFocus--persistent .resourceIcon.regionDimmed { opacity: 0.42; }

/* v1.10.0: legenda je škálovatelná jedním knoflíkem --legend-scale. Font-size z něj vychází a všechny
   ostatní rozměry jsou v em (padding, rádius, velikost teček), takže stačí změnit jedno číslo.
   POZOR (pro příští velký update): legenda je uvnitř #mapStage (scaluje se s mapou) a nesmí přerůst
   vyloučenou zónu, kam se negenerují uzly. Při dalším zvětšování je nutné úměrně zvětšit
   LEGEND_EXCLUSION_X_MAX / LEGEND_EXCLUSION_Y_MIN (config.js) i vyloučení #abundanceLegend ve
   worldgen.js, jinak by legenda překryla surovinové uzly v levém dolním rohu.

   PROTIŠKÁLOVÁNÍ (v6.17) - stejná oprava, jakou dostaly popisky geografie v P2-8, ale z opačné
   strany. Legenda písmo z `--fs-xs` brala vždycky, jenže leží uvnitř #mapStage, který fitMap()
   zmenšuje tím víc, čím vyšší jsou panely - a panely rostou právě s úrovní zobrazení. Ty dva
   pohyby se skoro vyrušily. Změřeno na 1920×1080 PŘED opravou (velikost písma legendy na
   obrazovce, tedy po vynásobení `--map-scale`):

       Malé   12,38 px      Střední 12,69 px      Velké 12,82 px      (+3,6 % přes celou škálu)

   Zbytek UI přitom přes tutéž škálu roste o 16,7 % (--fs-xs 12 → 14 px). Nastavení, které má text
   zvětšit, tedy na legendě prakticky nic nedělalo. Dělení `--map-scale` ten součin ruší a legenda
   se škáluje přesně jako prvky mimo mapu - po opravě 13,2 / 14,3 / 15,4 px, tedy `--fs-xs` × 1,1.

   PROČ `max(..., 0.83)` A NE HOLÉ DĚLENÍ: protiškálování zvětšuje legendu v souřadnicích mapy
   úměrně 1/`--map-scale`, a to roste bez omezení, jak se okno zmenšuje. Na 1366×768 padá
   `--map-scale` k 0,45 a legenda by v mapě zabrala přes 400×250 px, tedy skoro čtvrtinu její
   šířky - přerostla by vyloučenou zónu a lezla přes ložiska. 0,83 je nejmenší `--map-scale`
   REFERENČNÍ SESTAVY (1920×1080, úroveň „Velké" - viz `docs/visual-baseline.md`): na ní a na všem
   větším se legenda škáluje plně, na menším okně se přestane zvětšovat místo toho, aby spolkla
   mapu (a tam už hra stejně sama hlásí, že se zvolená úroveň do okna nevejde - uiTierFitWarning).
   Strop stopy v souřadnicích mapy je tím 282×175 px a od něj jsou odvozené LEGEND_EXCLUSION_*. */
#abundanceLegend {
    --legend-scale: 1.1;
    /* Podlaha dělitele - viz komentář výš. Drž ji v souladu s LEGEND_EXCLUSION_* v config.js:
       zmenšení téhle hodnoty zvětší stopu legendy v mapě a vyloučená zóna se musí zvětšit s ní. */
    --legend-counter-floor: 0.83;
    position: absolute;
    left: 10px;
    bottom: 10px;
    background: rgba(10, 16, 26, 0.72);
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 0.5em;
    padding: 0.55em 0.85em;
    font-size: calc(var(--fs-xs) * var(--legend-scale) / max(var(--map-scale, 1), var(--legend-counter-floor)));
    color: var(--text-legend);
    line-height: 1.65;
    pointer-events: none;
    z-index: 20;
}
/* Rozměry v em (v1.10.0) - škálují se s --legend-scale přes font-size #abundanceLegend. Velikost
   tečky teď (T4) taky nese hojnost, stejné poměry (0.85×-1.2×) jako .resourceIcon na mapě - přímo
   jako width/height v em, ne transform:scale (legendDot je inline-block v textovém toku, scale by
   mohl vizuálně kolidovat se sousedním textem řádku; přepočtená pevná šířka v em nic takového
   nedělá a drží stejnou --legend-scale škálovatelnost jako zbytek legendy). */
.legendDot {
    display: inline-block;
    width: 0.78em;
    height: 0.78em;
    border-radius: 50%;
    margin-right: 0.5em;
    border: 0.17em solid;
    background: rgba(255,255,255,0.08);
    vertical-align: -0.05em;
}
.legendDot.abundance-low { width: 0.66em; height: 0.66em; border-color: #8a8a8a; }
.legendDot.abundance-normal { border-color: rgba(255,255,255,0.6); }
.legendDot.abundance-high { width: 0.82em; height: 0.82em; border-color: #38bdf8; }
.legendDot.abundance-veryHigh { width: 0.87em; height: 0.87em; border-color: #a78bfa; }
.legendDot.abundance-rare { width: 0.94em; height: 0.94em; border-color: var(--abundance-rare); }

/* Nápověda na jeden klik (v6.28) - protipól legendy hojnosti v druhém dolním rohu mapy.
   ZÁMĚRNĚ NENÍ `.btn`: ta má vlastní výplň, padding i hover-lift navržený pro obdélníková
   tlačítka v panelech, a kulatý poloprůhledný knoflík na mapě by se s ní přetahoval o každou
   vlastnost. Podklad, lem i barva textu se naopak berou ze STEJNÝCH hodnot jako legenda -
   je to její protějšek, ne nový vizuální jazyk.

   PROTIŠKÁLOVÁNÍ je tu ze stejného důvodu jako u legendy výš (a počítá se stejným vzorcem):
   tlačítko leží uvnitř #mapStage, který fitMap() zmenšuje tím víc, čím vyšší jsou panely -
   a panely rostou právě s úrovní zobrazení. Bez dělení `--map-scale` by nastavení, které má
   text zvětšit, otazník na obrazovce prakticky nezvětšilo.

   PODLAHA DĚLITELE JE ALE JINÁ NEŽ U LEGENDY (0,45 proti 0,83) - a je to oprava nálezu z první
   verze, ne nedůslednost. S převzatou podlahou 0,83 se otazník na 1800×880 choval přesně tak,
   jak se před v6.17 chovala legenda: naměřeno 30 / 30 / 29 px pro „Menší / Výchozí / Větší",
   tedy ani o pixel nahoru, protože `--map-scale` je na téhle sestavě 0,688-0,583 a podlaha
   protiškálování vypnula. Přitom 1800×880 je podle `docs/visual-baseline.md` realistický pohled
   na cílovou sestavu, ne okrajový případ.

   Legenda tu podlahu potřebuje, protože má stopu 282×175 px a bez omezení by na malém okně
   spolkla čtvrtinu mapy. Otazník má stopu kolem 45 px a volného místa kolem sebe 350×300 px, tedy
   není co chránit. 0,45 je proto až `--map-scale` okna 1366×768 (nejmenší rozměr, se kterým
   projekt počítá - viz komentář u legendy) a slouží jen jako strop stopy: nad ním nemá vliv,
   pod ním drží tlačítko do ~100 px v souřadnicích mapy, tedy pod prahem 140 px, kterým roh hlídá
   test ve `worldgen.test.js`. Kdyby se měnilo `--maphelp-scale`, přepočítej i ten práh.

   BEZ TOOLTIPU (rozhodnutí zadavatele) - a tedy ani `title`, ani `attachTooltip`. Poloprůhlednost
   je klidový stav, na hover i na focus z klávesnice se knoflík dobarví do plné sytosti. */
#mapHelpBtn {
    --maphelp-scale: 1.35;
    --maphelp-counter-floor: 0.45;
    position: absolute;
    right: 10px;
    bottom: 10px;
    width: 2.2em;
    height: 2.2em;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: rgba(10, 16, 26, 0.72);
    border: var(--border-w-1) solid rgba(255, 255, 255, 0.15);
    color: var(--gold);
    font-family: inherit;
    font-size: calc(var(--fs-xs) * var(--maphelp-scale) / max(var(--map-scale, 1), var(--maphelp-counter-floor)));
    line-height: 1;
    cursor: pointer;
    opacity: 0.72;
    z-index: 20;
    transition: opacity 0.15s ease, border-color 0.15s ease;
}
#mapHelpBtn:hover,
#mapHelpBtn:focus-visible {
    opacity: 1;
    border-color: var(--gold);
}

/* ---------- Particles, shockwave, camera shake, vignette, confetti, jackpot banner ---------- */
.clickParticle {
    position: absolute;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    pointer-events: none;
    animation: particleFly 0.5s ease-out forwards;
}
@keyframes particleFly {
    0% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
    100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.3); opacity: 0; }
}

.shockwaveRing {
    position: absolute;
    width: 12px;
    height: 12px;
    margin-left: -6px;
    margin-top: -6px;
    border-radius: 50%;
    border: 2px solid;
    pointer-events: none;
    animation: shockwaveExpand 0.45s ease-out forwards;
}
@keyframes shockwaveExpand {
    0% { width: 12px; height: 12px; margin-left: -6px; margin-top: -6px; opacity: 0.9; }
    100% { width: 90px; height: 90px; margin-left: -45px; margin-top: -45px; opacity: 0; }
}

#vignetteOverlay {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 90;
    opacity: 0;
    box-shadow: inset 0 0 0 0 rgba(255, 225, 77, 0);
}
#vignetteOverlay.flash { animation: vignetteFlash 0.5s ease; }
@keyframes vignetteFlash {
    0% { opacity: 0; box-shadow: inset 0 0 120px 40px rgba(255, 225, 77, 0); }
    30% { opacity: 1; box-shadow: inset 0 0 160px 60px rgba(255, 225, 77, 0.85); }
    100% { opacity: 0; box-shadow: inset 0 0 120px 40px rgba(255, 225, 77, 0); }
}

#confettiLayer {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 95;
    overflow: hidden;
}
.confettiPiece {
    position: absolute;
    width: 7px;
    height: 10px;
    opacity: 0;
    animation: confettiFall 1.1s ease-out forwards;
}
@keyframes confettiFall {
    0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
    100% { transform: translate(var(--dx), calc(var(--dy) * -1 + 220px)) rotate(var(--rot)); opacity: 0; }
}

#jackpotBanner {
    position: fixed;
    top: 26%;
    left: 50%;
    transform: translate(-50%, -20px) scale(0.85);
    background: rgba(20, 16, 4, 0.9);
    border: 2px solid var(--crit-color);
    color: var(--crit-color);
    font-size: var(--fs-2xl);
    font-weight: bold;
    text-shadow: 0 0 10px rgba(255, 225, 77, 0.8);
    padding: 14px 28px;
    border-radius: 8px;
    z-index: 96;
    opacity: 0;
    pointer-events: none;
    transition: none;
}
#jackpotBanner.show { animation: jackpotBanner 1.6s ease; }
/* Zlatá horečka sdílí #jackpotBanner element/animaci (stejný "velký oznamovací banner uprostřed
   obrazovky" vzor), jen přebarvený na --goldrush-color - ať je na první pohled jasné, že nejde o
   kritický zásah (viz showGoldRushBanner, effects.js). */
#jackpotBanner.show--goldrush { border-color: var(--goldrush-color); color: var(--goldrush-color); text-shadow: 0 0 10px rgba(255, 157, 61, 0.8); }
@keyframes jackpotBanner {
    0% { opacity: 0; transform: translate(-50%, -20px) scale(0.7); }
    15% { opacity: 1; transform: translate(-50%, 0) scale(1.08); }
    25% { transform: translate(-50%, 0) scale(1); }
    80% { opacity: 1; }
    100% { opacity: 0; transform: translate(-50%, -10px) scale(1); }
}

/* ---------- Tooltip ---------- */
#gameTooltip {
    position: absolute;
    max-width: 260px;
    /* Glass (Fáze 3): dost neprůhledné (.92) pro čitelnost nad rušnou mapou, ale s rozostřením
       pozadí a neutrálním okrajem místo dřívějšího zlatého - tooltip je neutrální info, ne odměna. */
    background: rgba(16, 22, 36, 0.92);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: var(--border-w-1) solid var(--border-strong);
    border-radius: var(--radius-md);
    padding: 8px 12px;
    font-size: var(--fs-sm);
    line-height: 1.6;
    color: var(--text-1);
    /* T18: úvodní obrazovka (#titleScreen, z-index 90) přibyla NAD tooltip (dřív 80) - "verze hry"
       na ní je taky interaktivní (attachTooltip), takže se tooltip po najetí myší schovával za
       overlay. 97 je nad titleScreen i nad efekty běžící hry (vignette 90/confetti 95/jackpot 96 -
       ty se stejně nikdy nepřekrývají s úvodní obrazovkou, hra tou dobou ještě neběží), ale pod
       modalem (100) - modal má vždy poslední slovo.
       v6.59 (BL-16): 99, tedy i nad nápovědou (98) - verze v jejím rohu má tooltip taky. Nápověda
       dřív ležela nad tooltipem, aby přes ni nepřeblikla bublina z prvku pod ní; tu teď při
       otevření schová openHelp() (help.js). */
    z-index: 99;
    pointer-events: none;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
}
#gameTooltip.hidden { display: none; }
.inlineIcon { display: inline-flex; width: calc(16px * var(--ui-scale)); height: calc(16px * var(--ui-scale)); vertical-align: -3px; }
.inlineIcon .gicon { width: 100%; height: 100%; }

.floatText {
    position: absolute;
    font-weight: bold;
    font-size: var(--fs-xl);
    color: #fff8d0;
    text-shadow: 1px 1px 2px #000;
    pointer-events: none;
    animation: floatUp 0.9s ease-out forwards;
    white-space: nowrap;
}

.floatText.crit {
    color: var(--crit-float);
    font-size: var(--fs-2xl);
    text-shadow: 0 0 6px #ff9900, 1px 1px 2px #000;
    animation: floatUpCrit 1.8s ease-out forwards;
}

@keyframes floatUp {
    0% { opacity: 1; transform: translate(-50%, 0); }
    100% { opacity: 0; transform: translate(-50%, -50px); }
}

@keyframes floatUpCrit {
    0% { opacity: 1; transform: translate(-50%, 0) scale(1.3); }
    15% { transform: translate(-50%, -14px) scale(1.5); }
    65% { opacity: 1; transform: translate(-50%, -40px) scale(1.3); }
    100% { opacity: 0; transform: translate(-50%, -70px) scale(1.15); }
}

/* ---------- Spodní panel ---------- */
/* --active-tab-color nastavuje switchTab() (src/actions.js) přímo tady - hlavní, nejvíc viditelný
   barevný signál patří ploše celé spodní lišty, ne jednotlivým kartám/prvkům na ní (viz níže). */
#bottomPanel {
    height: var(--panel-bottom-h);
    flex: 0 0 var(--panel-bottom-h);
    background: color-mix(in srgb, var(--active-tab-color, transparent) 20%, var(--panel-bg));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
    border-top: var(--border-w-1) solid var(--border);
    display: flex;
    flex-direction: column;
}
#tabs {
    display: flex;
    height: var(--panel-tabs-h);
    flex: 0 0 var(--panel-tabs-h);
    background: #0d121e;
}

/* Barva každého tabu vychází z --tab-color nastaveného inline v index.html (stejný vzor jako
   --res-color u .resStat) - Dřevo/Uhlí/Uran sdílí paletu se svou surovinou, zbylé taby mají
   vlastní odstín podle povahy (Bonus = zlatá jako #bonusChanceBox, Úspěchy = fialová, Statistiky
   = modrá, Systém = neutrální modrošedá). Míchá se do NEUTRÁLNÍ tmavé báze (#0b0f1a), ne do
   hnědé - jinak by i namíchaná barva vycházela zakaleně do hněda místo čistého odstínu tabu.
   Barva je vidět v plné síle (podtržení) i mimo výběr, ne jen po kliknutí. */
.tabBtn {
    position: relative;
    flex: 1;
    min-width: 0;
    background: color-mix(in srgb, var(--tab-color, #2b3450) 18%, #0b0f1a);
    border: none;
    border-right: var(--border-w-1) solid #2b3450;
    border-bottom: var(--border-w-3) solid var(--tab-color, #2b3450);
    color: var(--text-1);
    font-size: var(--fs-base);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
    /* Fáze 3: plynulý přechod pozadí i glow při hover/aktivaci. */
    transition: background 0.15s ease, box-shadow 0.15s ease;
}
/* Ikona zámku uvnitř zamčeného tabu (Uhlí/Uran) - okamžitě rozpoznatelný zamčený prvek. */
.tabLockIcon { display: inline-flex; width: calc(15px * var(--ui-scale)); height: calc(15px * var(--ui-scale)); vertical-align: -3px; margin-right: 5px; }
.tabLockIcon .gicon { width: 100%; height: 100%; }
/* Progress k odemčení přímo na tabu (--unlock-pct nastavuje renderTopPanel každý tik) - hráč vidí,
   jak daleko je od odemčení, bez kliknutí do tabu. Leží nad spodním okrajem tabu jako plnící se linka. */
.tabBtn.locked::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    height: var(--border-w-3);
    width: calc(var(--unlock-pct, 0) * 1%);
    background: var(--tab-color, var(--gold));
    transition: width 0.3s ease;
}
/* Indikátor „na tomhle tabu je dostupné vylepšení" (B1, plán 5.19). Hvězdička je stejný symbol,
   jaký nese odznak doporučeného uzlu roadmapy (.roadmapBadge, B3) - hráč se učí jeden znak.
   Kreslí se přes ::after, protože tlačítkům uhlí/uran přepisuje innerHTML renderTabContent
   (zámek) a vložený element by mizel; třída na tlačítku to přežije. S .tabBtn.locked::after
   (pruh postupu k odemčení) kolidovat nemůže - zamčený tab nemá co koupit, takže se ty dvě
   třídy na jednom tlačítku nikdy nesejdou (clickNodeState vrací u zamčené suroviny 'locked'). */
.tabBtn.hasAvailable::after {
    content: '★';
    position: absolute;
    top: 1px;
    right: 4px;
    font-size: var(--fs-2xs);
    line-height: 1;
    color: var(--gold);
    text-shadow: 0 0 4px rgba(245, 196, 81, 0.75);
}
/* Pulz JEN do prvního otevření tabu (rozhodnutí zadavatele 2026-08-26) - trvale blikající spodní
   lišta by rušila při klikání na mapě. Třídu sundává refreshTabAvailability (render.js). */
.tabBtn.hasAvailable.availPulse::after { animation: tabAvailPulse 1.6s ease-in-out infinite; }
@keyframes tabAvailPulse {
    0%, 100% { opacity: 0.6; transform: scale(1); }
    50% { opacity: 1; transform: scale(1.3); }
}
/* Odkrytí záložky „Republika" (D3, plán 5.19) - jednorázově, jen při přechodu skrytá → viditelná
   (třídu nasazuje a po REPUBLIC_TAB_REVEAL_MS sundává refreshRepublicTabVisibility v render.js).
   Záměrně výrazněji než hvězdička dostupného vylepšení (B1): ta říká „tady se dá něco koupit",
   tohle říká „mezi záložkami přibyla nová", což hráč vidí jednou za průchod.
   Zlatá obruba je STATICKÁ část efektu, pulz jen jeden - v omezeném režimu (reduced-motion) tak
   z efektu zbude klidné zvýraznění na stejnou dobu, ne prázdno. */
.tabBtn.tabRevealed {
    box-shadow: 0 0 0 var(--border-w-2) rgba(245, 196, 81, 0.9), 0 0 20px 5px rgba(245, 196, 81, 0.4);
    background: color-mix(in srgb, var(--gold) 20%, #0b0f1a);
    z-index: 2;
    animation: tabRevealPulse 1.2s ease-in-out 1;
}
@keyframes tabRevealPulse {
    0%, 100% { transform: scale(1); }
    45% { transform: scale(1.06); }
}
.tabBtn:hover { background: color-mix(in srgb, var(--tab-color, #2b3450) 26%, #0b0f1a); }
.tabBtn.active {
    /* U Dřeva/Uhlí/Uranu je --tab-color schválně stejná barva jako --res-color použitá na
       odemčených uzlech roadmapy (viz .roadmapNode.state-purchased) - moc silné namíchání tady
       by splynulo s uzly a snížilo kontrast mezi pozadím a "odemykatelnými prvky" (vylepšeními).
       Držíme to zřetelně slabší než barva samotných uzlů. */
    /* Aktivní tab „září" ze své spodní hrany (barva tabu) směrem nahoru - jako by ho nasvěcovalo
       barevné podtržení. Gradient shora + inset glow zdola, obojí v barvě daného tabu (teal Dřevo,
       zlatá Bonus, fialová Úspěchy…), takže identita každého tabu zůstává. */
    background:
        linear-gradient(180deg, color-mix(in srgb, var(--tab-color, #2b3450) 20%, transparent), transparent 60%),
        color-mix(in srgb, var(--tab-color, #2b3450) 30%, #0b0f1a);
    color: #fff;
    font-weight: bold;
    box-shadow: inset 0 -12px 18px -10px color-mix(in srgb, var(--tab-color, #2b3450) 75%, transparent);
}
.tabBtn.locked {
    background: #12182a;
    color: #7f8ba3;
    border-bottom-color: #2b3654;
}
/* Aktivní + zamčený zároveň (otevřená záložka suroviny, která ještě není odemčená) - bez tohohle
   by .locked přebilo pozadí/barvu .active (obě mají stejnou specificitu, .locked je v souboru až
   za .active) a nešlo by poznat, který tab je zrovna otevřený. */
.tabBtn.locked.active { background: #222c40; color: var(--text-2); }

/* Žádné vlastní pozadí - barvu nese #bottomPanel (viz výš), tady jen prosvítá skrz. Jedna vrstva
   barvení místo dvou překrývajících se je jednodušší a nemátoucí. */
#tabContent {
    flex: 1;
    min-height: 0;
    padding: 12px 20px;
    display: flex;
    gap: var(--space-6);
    /* overflow-y jako pojistka pro sekundární taby (Statistiky/Systém) s vyšším obsahem; surovinové
       taby se sloupci (viz .roadmapScroll) se vejdou bez posuvníku. */
    overflow-y: auto;
    overflow-x: hidden;
}

/* Pozadí karet zůstává neutrální (beze změny barvou) - jsou to zřetelně odlišitelná "okna" na
   barevné ploše #bottomPanel, ne samy o sobě obarvené plochy (to už řeší lišta). Jen okraj mírně
   sladí kartu s aktivním tabem. */
/* Sdílený povrch všech karet (dřív 4× duplikovaný na .upgradeCard/.unlockCard/.slotCard/
   .achievementBadge) - pozadí, okraj laděný k aktivnímu tabu, rádius, horní světlá linka.
   Jednotlivé karty pak už řeší jen svůj layout (flex/padding). Hodnoty jsou beze změny. */
.upgradeCard, .unlockCard, .slotCard, .achievementBadge {
    /* Glass povrch (Fáze 2): poloprůhledné pozadí + rozostření pozadí za kartou = frosted efekt,
       1px okraj jemně tónovaný k aktivnímu tabu, elevační stín (karta „plave") + horní světlá
       linka. Nahrazuje dřívější plochý neprůhledný obdélník s 2px tmavým rámem. */
    background: var(--surface-glass);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: var(--border-w-1) solid color-mix(in srgb, var(--active-tab-color, #ffffff) 22%, var(--border-strong));
    border-radius: var(--radius-lg);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

.upgradeCard {
    flex: 1;
    padding: 10px 14px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

/* Jednotný vzhled "nic tu není" hlášek (audit C.2) - tlumeně a kurzívou, ať čtou jako prázdný
   stav, ne jako běžný obsah. Sparkline placeholder ("zatím málo dat…") i "prázdné" u autosave už
   používají stejnou tlumenou barvu (--text-muted). */
.emptyState { font-size: var(--fs-sm); color: var(--text-muted); font-style: italic; }

.upgradeCard h4 { margin: 0 0 4px 0; font-size: var(--fs-base); color: var(--gold); }
.upgradeCard .line { font-size: var(--fs-md); margin: 2px 0; color: var(--text-2); }
.upgradeCard .maxed { color: #6fbf3f; font-weight: bold; }

/* v1.9.1: kompaktnější (nižší sparkline, těsnější řádky), aby se karta grafu vešla do zkráceného
   spodního panelu bez svislého posuvníku - konzistentně s ostatními taby. */
.statsGraphRow { margin: 3px 0; }

/* Čtvrtá řada grafu v aktu 2 (M5-5, „dodaný výkon"). `#tabContent` má pevnou výšku, takže se
   čtvrtý řádek musí vejít DOVNITŘ karty, ne ji roztáhnout - jinak by ze Statistik byla jediná
   rolující záložka ve hře (týž limit, kvůli kterému jsou v tabu dvě karty vedle sebe místo jedné
   vysoké). Zkrátí se proto křivka i rozestupy, popisky zůstávají. Změřeno v prohlížeči na
   referenční sestavě 1920×1080 / „Střední", ne odhadnuto. */
.statsGraphChart--act2 .statsGraphRow { margin: 1px 0; }
.statsGraphChart--act2 .statsSparkline { height: 16px; }

/* Přehled ér (M5-2). Sloupec s doktrínou je jediný textový a jediný, který může přetéct -
   ostatní tři jsou čísla. Dostane proto VŠECHNU zbylou šířku (ostatní tři jsou `width: 1%`, což
   je v `table-layout: auto` idiom pro „jen tak široký, jak je obsah") a ořezává se elipsou až
   jako poslední pojistka; plný název je v tooltipu řádku. Zalomení schválně ne - delší řádek by
   tabulku prodloužil a karta má pevnou výšku jako celý panel. */
.eraLogTable td.eraLogDoctrine { width: auto; }
.eraLogTable th[scope="row"],
.eraLogTable td:not(.eraLogDoctrine) { width: 1%; white-space: nowrap; padding-left: 8px; }
.eraLogTable tbody tr { cursor: help; }
/* ---------- ZAROVNÁNÍ SLOUPCŮ (BL-08) ----------
   `.statsTable` zarovnává doprava VŠECHNY `td` i všechny hlavičky a popisky doleva jako
   `th[scope="row"]`. Na dvousloupcovou statistiku („Kliknutí | 1 240 | 8 900") to sedí, protože
   tam je popisek první sloupec a zbytek jsou čísla. Tahle tabulka má ale pořadí opačné: čísluje
   se `th[scope="row"]`, takže pořadové číslo éry dědilo zarovnání POPISKU (doleva) pod hlavičkou
   zarovnanou doprava, a jediný textový sloupec (doktrína) dědil zarovnání ČÍSLA (doprava) a
   třepil se zleva. Obojí se tu vrací na svou stranu — text doleva, číslo doprava.
   Tabulku staví DVĚ místa (karta „Éry" v render.tabs.js a finále ligy v persistence.js), proto
   je oprava v CSS a ne v jednom z nich. */
.eraLogTable th[scope="row"] { text-align: right; font-variant-numeric: tabular-nums; }
.eraLogTable th.eraLogDoctrine,
.eraLogTable td.eraLogDoctrine { text-align: left; padding-left: 8px; }
/* HLAVIČKY MAJÍ TOTÉŽ ODSAZENÍ JAKO SVÉ BUŇKY (v6.48, nedodělek z BL-08). v6.39 ho dala jen
   hlavičce doktríny. Číselné sloupce se sázejí `width: 1%`, tedy na šíři nejdelšího obsahu -
   a když je tím obsahem HLAVIČKA (ve finále ligy „skóre" nad dvoucifernými hodnotami), stojí její
   text bez mezery hned vedle sousední. Naměřeno 0 px mezi „špička" a „skóre". */
.eraLogTable thead th { padding-left: 8px; }
/* ---------- Karta „Éry" je od v6.35 LIGOVÁ TABULKA a musí se vejít (M5-4) ----------
   `ERA_LOG_MAX` vyrostl z 5 na 10 (celá liga), čímž tabulka natáhla kartu z 222 na 263 px
   a záložka Statistiky začala rolovat na všech třech úrovních zobrazení (naměřeno 367/246 na
   „Výchozí"). Roluje proto jen tabulka uvnitř karty — „žádný tab neroluje" je nepsaný invariant
   téhle hry a jedinou vědomou výjimkou zůstávají Úspěchy (M6-7).
   Karta je flex sloupec, aby rolovaná oblast dostala PŘESNĚ zbytek místa po nadpisu, řádku ligy
   a řádku rekordu — bez pevné výšky v px, která by se rozešla s úrovněmi zobrazení. `min-height: 0`
   je to, co flex položce vůbec dovolí zmenšit se pod svůj obsah (táž past jako u `.regionChipLevel`). */
.eraLogCard { display: flex; flex-direction: column; min-height: 0; }
.eraLogScroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.statsGraphLabel { font-size: var(--fs-xs); color: var(--text-2); margin-bottom: 1px; }
.statsSparkline { display: block; width: 100%; height: 22px; }

/* Sdílený crosshair (viz bindStatsTabEvents v render.js) - jedna svislá čára přes celou kartu,
   ne jedna na řádek, aby šlo porovnat všechny 3 suroviny ve stejném časovém okamžiku najednou. */
.statsGraphChart { position: relative; cursor: crosshair; }
.statsCrosshairLine {
    position: absolute;
    top: 0;
    bottom: 0;
    width: var(--border-w-1);
    background: var(--border-strong);
    pointer-events: none;
    transform: translateX(-50%);
}
.statsCrosshairLine.hidden { display: none; }

/* ---------- Tlačítka (sdílená báze) ----------
   Dřív měl vzhled tlačítka každý kontext zvlášť (.buyBtn / .namedSaveNew button / .slotBtns button /
   #modalBox button / .resetBtn) a zelená #4a7a2f se opakovala 4×. Teď: .btn nese vzhled, .btn--*
   barvu (primary = akce, danger = destrukce, ghost = neutrální/zrušit), .btn--sm/--lg velikost.
   Kontext (karta/modal/řádek) doplňuje už jen rozestup. */
.btn {
    padding: 6px 10px;
    font-size: var(--fs-md);
    border: none;
    border-radius: var(--radius-sm);
    color: #fff;
    background: #3a4256;
    cursor: pointer;
    white-space: nowrap;
    /* Fáze 3: plynulý přechod pro hover-lift + stisk (micro-interaction). */
    transition: filter 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;
}
/* Hover = jemné projasnění + nadzvednutí o 1px (tlačítko „ožije"); :active = stisk zpět dolů. */
.btn:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }
.btn:active:not(:disabled) { transform: translateY(0); filter: brightness(0.95); }
/* Zakázané tlačítko (vizuální QA audit v6.9, nález P1-3). Do 6.9 tu byla jen `background` + `opacity`,
   a to byla tichá past: `.btn--primary` níž má `color: #04201c` (tmavá zeleň navržená pro SVĚTLOU
   tyrkysovou výplň, na které má 6,9-9,2:1). Jakmile `:disabled` výplň nahradilo tmavou #3a4256,
   zůstal tmavý text na tmavém pozadí a `opacity: 0.5` rozdíl domazala - naměřeno **1,34 : 1**.
   Postihovalo to „Odemknout uhlí"/„Odemknout uran", tedy hlavní tlačítko první hodiny hry v jeho
   VÝCHOZÍM (nedostupném) stavu, a odpočet ve vítězném modalu („Pokračovat ve hře (5)"), kde je to
   číslo v závorce jediná informace, na kterou hráč čeká.
   Explicitní `color` dědění tmavého textu ruší; 0,5 → 0,65 zvedne i ghost varianty nad 4,5:1.
   Zakázanost tak nese pozadí, kurzor a tlumenější text - ne nečitelnost. */
.btn:disabled { background: #3a4256; color: var(--text-2); opacity: 0.8; cursor: not-allowed; }
/* Teal výplň nese TMAVÝ text (kontrast ~9:1) - bílá na teal by byla nečitelná (~2.3:1). Jemný
   svislý gradient dává tlačítku fyzickou „vypouklost" moderního UI; barevný stín pod primárním/
   destruktivním tlačítkem dělá hover-lift čitelný (tlačítko se zvedá nad vlastní stín). */
.btn--primary { background: linear-gradient(180deg, #2dd4bf, var(--action)); color: #04201c; box-shadow: 0 4px 12px rgba(20, 184, 166, 0.30); }
.btn--danger { background: var(--danger); box-shadow: 0 4px 12px rgba(201, 53, 64, 0.28); }
/* Ghost = obrys místo výplně (moderní sekundární akce): průhledné + 1px okraj, na hoveru jemná plocha. */
.btn--ghost { background: transparent; border: var(--border-w-1) solid var(--border-strong); color: var(--text-1); }
.btn--ghost:hover:not(:disabled) { background: rgba(255, 255, 255, 0.06); }
.btn--sm { padding: 5px 8px; font-size: var(--fs-sm); }
.btn--lg { padding: 8px 20px; }

/* ---------- Tlačítka nákupu v aktu 2 (v6.29) ----------
   Rozvoj revíru, četa a stavba bloku. Do v6.28 nesly stav VÝHRADNĚ jasem textu: pozadí bylo
   v obou stavech #3a4256, tedy naměřených **1,12 : 1** mezi „jde koupit" a „nejde" - a jediný
   rozdíl byl text 10,03 : 1 → 4,80 : 1. Nedalo se to přečíst na první pohled a u čety to bylo
   dokonce obrácené: `.btn--ghost:disabled` dostával výplň, kterou má aktivní `.btn`, takže
   nedostupná Četa vypadala jako dostupný Rozvoj.

   Stav proto nese TVAR, ne jas, a používá k tomu značky, které hra už učí jinde:
     available  zlatý okraj a záře  = totéž, co `.roadmapNode.state-available` v aktu 1
     short      pruh naspořeného    = vodorovná obdoba prstence `--pct` u uzlů roadmapy
     blocked    holý obrys          = na tohle se spořit nedá (práh MW, kapacita, maximum)

   Barva pruhu se bere ze ZÁLOŽKY: `--res-color` na `.regionTrack` u Dřeva/Uhlí/Uranu,
   `--active-tab-color` (nastavuje `switchTab`) na Soustavě, tedy tyrkysová. Nákup tak vypadá
   jako součást té záložky, ve které stojí. */
.btnBuy {
    --btn-buy-color: var(--res-color, var(--active-tab-color, var(--gold)));
    background: transparent;
    border: var(--border-w-1) solid var(--border-strong);
    color: var(--text-1);
}
.btnBuy--available {
    border-color: var(--gold);
    background: color-mix(in srgb, var(--gold) 14%, transparent);
    box-shadow: 0 0 5px rgba(245, 196, 81, 0.45);
    color: #fff;
}
.btnBuy--available:hover { background: color-mix(in srgb, var(--gold) 22%, transparent); }
/* Pruh je GRADIENT s ostrým zlomem, ne ::before - text tlačítka je holý textový uzel, kterému
   se nedá dát z-index, takže by ho pseudoprvek musel podlézat záporným z-indexem a propadl by
   pod pozadí čipu. Gradient tenhle problém nemá vůbec. */
.btnBuy--short {
    color: var(--text-2);
    border-color: color-mix(in srgb, var(--btn-buy-color) 45%, transparent);
    background: linear-gradient(to right,
        color-mix(in srgb, var(--btn-buy-color) 30%, transparent) calc(var(--pct, 0) * 1%),
        transparent calc(var(--pct, 0) * 1%));
}
.btnBuy--blocked {
    color: var(--text-2);
    border-color: rgba(255, 255, 255, 0.10);
}
/* Nedostupné tlačítko je pořád KLIKATELNÉ - odpoví hláškou, co chybí (viz buyButtonAttrs
   v render.core.js) - takže si nechává kurzor i reakci na hover. `not-allowed` by lhal. */
.btnBuy--short:hover, .btnBuy--blocked:hover { border-color: var(--border-strong); }

/* Rozestup tlačítka odemčení uvnitř .unlockCard (dřív margin-top přímo na .buyBtn). */
.unlockCard .btn { margin-top: 6px; }

/* ---------- Jednotný focus ring na všem interaktivním ----------
   Dřív nejednotné: .resourceIcon gold 3px, .roadmapNode bílá, input gold 2px, tlačítka a taby nic.
   Sjednoceno do jednoho tokenu --focus-ring (3px solid var(--gold)). Výjimka (Fáze 3): formulářový
   vstup má vlastní teal focus (viz .namedSaveNew input:focus-visible výše) - teal = barva akce/psaní.

   PRAVIDLO MÍSTO VÝČTU (vizuální QA audit v6.9, nález P2-10). Do 6.9 tu stál seznam šesti tříd
   a všechno, co do žádné z nich nespadlo, dostalo tenký výchozí prstenec prohlížeče (1px auto).
   Naměřeno na devíti prvcích: #goalBox (zastávka Tabu s tooltipem cíle), #pauseBtn, #menuBtn,
   #tabContent a všech pět přepínačů v Nastavení. Nejlíp to bylo vidět v modalu Nastavení, kde
   přepínače měly tenký oranžový prstenec a tlačítka hned pod nimi tlustý zlatý - dva různé focus
   indikátory na jedné obrazovce.
   `:where()` drží specificitu na nule, takže konkrétnější výjimky níž i výš vyhrávají bez
   `!important` (typicky #titleScreenButtons .btn--primary, které focus ring vědomě ruší). */
:where(button, input, select, textarea, [tabindex], [role="button"]):focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
}

.unlockCard {
    flex: 1;
    padding: 10px 14px;
}
.unlockCard h4 { margin: 0 0 6px 0; color: var(--gold); }
.unlockCard.done { border-color: var(--action); }

/* Postup k odemčení - vizuální parita s progress ringem roadmap uzlů (audit D3). Track laděný
   podle pruhu cíle hry (#goalBarTrack), výplň v barvě akce; při 100 % je plná a tlačítko aktivní. */
.unlockProgress { height: 6px; background: #14201a; border-radius: var(--radius-sm); overflow: hidden; margin: 6px 0 2px; }
.unlockProgressFill { height: 100%; background: var(--action); transition: width 0.2s ease; }

/* Řádky odemčení - jeden na měnu (B2, plán 5.19). Do 5.18 tu byl JEDEN souhrnný pruh počítaný
   z minima přes měny, takže u uranu (33 800 uhlí + 47 300 dřeva) nešlo poznat, která z měn hráče
   brzdí. Grid místo flexu, aby čísla i pruhy byly u obou řádků svisle zarovnané. */
.unlockRows { display: flex; flex-direction: column; gap: 4px; margin: 6px 0 2px; }
.unlockRow {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    column-gap: var(--space-2);
    font-size: var(--fs-xs);
    color: var(--text-2);
}
.unlockRowIcon { display: inline-flex; width: calc(14px * var(--ui-scale)); height: calc(14px * var(--ui-scale)); }
.unlockRowIcon .gicon { width: 100%; height: 100%; }
.unlockRowNums { font-variant-numeric: tabular-nums; }
/* Fajfka drží místo i nesplněná (visibility, ne display) - jinak by řádkům při dotažení měny
   poskočila čísla o pár pixelů. */
.unlockRowCheck { color: var(--action); font-weight: bold; visibility: hidden; }
.unlockRow.done .unlockRowCheck { visibility: visible; }
.unlockRow.done .unlockRowNums { color: var(--action); }
/* Pruh se táhne přes celou šířku řádku pod textem - proto vlastní grid řádek. */
.unlockRow .unlockProgress { grid-column: 1 / -1; margin: 2px 0 0; }
.unlockRow .unlockProgressFill { background: var(--res-color, var(--action)); }

/* 4 karty roztažené přes celou dostupnou šířku - Manuální/Automatické uložení dostávají dvakrát
   větší podíl (mají na řádek label + časové razítko + 2 tlačítka, potřebují víc místa), zbylé
   dvě karty jsou užší. overflow-y zůstává jen jako krajní pojistka (běžně se neuplatní - žádná
   z karet dnes nemá proměnnou výšku, která by přesáhla dostupný prostor). */
/* v1.9.1: bez vlastního scrollu (jednotná politika = jediná pojistka na #tabContent); kompaktnější
   vertikální rozestupy, aby se nejvyšší sloupec vešel do zkráceného spodního panelu. */
.systemBox {
    flex: 1;
    min-height: 0;
    display: grid;
    grid-template-columns: 2fr 2fr 1fr 1fr;
    align-items: start;
    gap: var(--space-4);
}

/* Poslední sloupec gridu (1fr) - nová karta "Velikost UI" (2.3) je vrstvená nad kartou Reset
   místo vlastního pátého sloupce, aby ostatní 3 karty zůstaly beze změny šířky/rozvržení. Stejný
   trik používá i 3. sloupec (2.14) - karta "Verze hry" je vrstvená pod kartou Přístupnost a zvuk
   (dřív žila uvnitř karty Reset, kam logicky nepatřila). */
.systemBoxStack {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.slotCard {
    min-width: 0;
    padding: 7px 10px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}
/* ---------- Tab „Republika": prestige (T12, verze 5.3) ---------- */
/* Dvě části: souhrn (body / statistiky / spouštěč resetu) a mřížka trvalých bonusů. Mřížka je
   auto-fill, ne pevný počet sloupců - bonusů je dnes 8, ale tabulka PRESTIGE_UPGRADES je datová a
   přidání dalšího nemá vynutit sahání do CSS. */
.republicBox {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);   /* space-4 -> space-2 (P2-7, v6.14) - viz komentar u .republicHelpInline p */
}

/* Čtvrtý sloupec (D2) nese stručné vysvětlení - je `1fr`, tedy zabírá právě to volné místo, které
   mezi čísly a tlačítkem stejně zůstávalo prázdné. Sloupec s čísly proto přešel z `1fr` na `auto`. */
.republicSummary {
    display: grid;
    grid-template-columns: auto auto 1fr auto;
    align-items: center;
    gap: var(--space-5);
}

.republicPoints { text-align: center; }
.republicPointsValue {
    font-size: calc(28px * var(--ui-scale));
    font-weight: bold;
    color: var(--republic-color);
    line-height: 1.1;
}
.republicStats .line { font-size: var(--fs-sm); color: var(--text-2); margin: 2px 0; }
/* Rozpad „(z uranu X + za cíle Y)" (D2) je INLINE součást řádku, ke kterému patří - ne vlastní
   řádek. Jako samostatná řádka přidal `.republicStats` čtvrtý řádek (59 → 76 px) a tab tím na
   „Malém" přetekl o 5 px; opraveno v 5.24. Tlumeně, protože je to rozklad čísla před ním. */
.republicStats .line .ts { display: inline; margin: 0; }
.republicAction { text-align: right; display: flex; flex-direction: column; gap: 3px; align-items: flex-end; }

/* Stručné vysvětlení prestige v souhrnu (D2, plán 5.19) - sedí ve VOLNÉM vodorovném místě mezi
   čísly a tlačítkem, takže nepřidává řádek navíc: spodní panel má pevných 213 px (hd14) a souhrn
   s mřížkou bonusů z nich zaberou 208. Samostatná karta by mřížku shodila pod okraj a Republika by
   byla jediný rolující tab ve hře (viz komentář u republicHowItWorksHtml v render.js).
   Zbytek textu je v modalu za tlačítkem „Jak to funguje". */
.republicHelpInline {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    min-width: 0;
}
/* `line-height` 1,5 → 1,4 (P2-7, v6.14). Tenhle odstavec je nejvyšší položkou souhrnu a tím pádem
   URČUJE jeho výšku - na „Velkých" se v úzkém sloupci lámal do 101 px, kdežto na ostatních úrovních
   měl 69-73. Právě těch ~30 px navíc shazovalo mřížku bonusů pod okraj na 1366×768 (o 13 px), což
   jde proti akceptačnímu kritériu D2 „mřížka bonusů zůstává celá vidět".
   Deficit se dorovnal třemi drobnostmi místo jedné velké: gap `.republicBox` (space-4 → space-2),
   tohle řádkování a vnitřní mezera `.prestigeCard` (4 → 2 px). Žádná z nich sama nestačila a
   žádná neubírá obsah - naměřeno 0 px rolování na všech třech úrovních. */
.republicHelpInline p {
    margin: 0;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    line-height: 1.4;
}
/* Odstavce v modalu s plným vysvětlením - zarovnané doleva (modal má jinak text na střed, což se
   u čtyř souvětí čte špatně). */
.republicHelpModal { text-align: left; }
.republicHelpModal p {
    margin: 0 0 10px;
    font-size: var(--fs-sm);
    color: var(--text-2);
    line-height: 1.6;
}
.republicHelpModal p:last-child { margin-bottom: 0; }

.prestigeGrid {
    display: grid;
    /* F5 (plán 6.0): minimum sloupce se ZÁMĚRNĚ neškáluje s `--ui-scale`. Karta je vysoká 120-135 px,
       takže se do panelu (213-264 px) vejde právě JEDEN řádek - a osm bonusů se do jednoho řádku
       musí vejít na nejužší podporované šířce (1366 px). S násobením přes `--ui-scale` vycházelo
       minimum 178 px („Střední") a 208 px („Velké"), tedy jen 7 resp. 6 sloupců, druhý řádek
       a rolující tab Republika - jediný rolující tab ve hře (viz docs/visual-baseline.md).
       Kolik sloupců se vejde, má záviset na dostupné ŠÍŘCE, ne na velikosti písma; karty jsou `1fr`,
       takže se na širších obrazovkách roztáhnou samy a vzhled na 1920 se nemění.

       150 → 118 px (M4): uzlů je nově DESET, ne osm — tři procentní, dva strukturální a pět
       mechanických odemčení. Změřeno na 1366×768 (nejužší podporovaná šířka): při 150 px se jich
       vejde osm, tedy druhý řádek a rolující tab Éra — jediný rolující tab ve hře. Při 122 px
       devět, teprve při 118 px deset a jeden řádek. Druhá polovina té opravy je v renderRepublicTab:
       řádek doktríny nese jen JMÉNO, ne popis; s popisem souhrn na 1366 zabral o 55 px víc
       a mřížka se nevešla ani na deset sloupců. Obojí se musí držet spolu — když se sem sáhne,
       přeměř tab na 1366×768 ve všech třech úrovních zobrazení.

       `auto-fit`, NE `auto-fill` (nález ze hraní, v6.24). S `auto-fill` si mřížka vyrobí tolik
       118px stop, kolik se jich do šířky vejde, a ty prázdné SI MÍSTO NECHAJÍ. Na 1920×1080 to
       dělalo 14 stop na 10 karet, takže karta zůstala 123 px široká, přestože bylo k dispozici
       1 880 px — nadpisy se lámaly do tří řádků a tečky úrovní se z hlavičky vytlačily až 48 px
       do sousední karty. `auto-fit` prázdné stopy sbalí, karty se roztáhnou na 177 px a karta je
       o 35 px NIŽŠÍ než předtím. Je to přesně tatáž vada i táž oprava jako u `.gridBlocks` v v6.6.
       Na 1366×768 se nemění nic: tam je stop deset a všechny obsazené. */
    grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
    gap: var(--space-4);
    align-content: start;
}

.prestigeCard {
    min-width: 0;
    /* Svisle 7 → 4 px (vizuální QA audit v6.9, nález P2-7). Karta nemá vlastní povrch (leží uvnitř
       .republicBox, který ho nese), takže užší odsazení nic vizuálně neodděluje - jen šetří výšku.
       Na 1366×768 tab Republika rolovala, což jde přímo proti akceptačnímu kritériu D2 („mřížka
       bonusů zůstává celá vidět"); po změně je na „Malých" i „Střední" čistá.
       Kombinace 1366×768 + „Velké" zůstává přetečená o ~13 px - do 768px obrazovky se panel
       vysoký 310 px s osmi kartami nevejde a stlačovat kvůli tomu i referenční sestavu by byla
       horší volba. Viz VISUAL_QA_AUDIT.md, P2-7. */
    padding: 4px 10px;
    display: flex;
    flex-direction: column;
    gap: 2px;   /* 4 -> 2 px (P2-7, v6.14) */
}
/* Karmínová PROSVĚTLENÁ bílou (vizuální QA audit v6.9, nález P2-12) - stejný případ jako
   .regionChipLevel: nadpis v barvě --republic-color ležel na podkladu namíchaném z téže barvy,
   takže si konkuroval sám se sebou (naměřeno 3,57:1 u 15px bold, kde je práh 4,5). */
.prestigeCard h4 { margin: 0; font-size: var(--fs-md); color: color-mix(in srgb, var(--republic-color) 72%, #fff); }
/* HLAVIČKA NENÍ FLEX ŘÁDEK, ALE OBTÉKÁNÍ (nález ze hraní, v6.24).
   Do v6.23 to byl `display: flex` s nadpisem a tečkami vedle sebe — a měl dvě vady naráz:
     1. Nadpis jako flex položka se nesmrskne pod svůj obsah (`min-width: auto`), takže dlouhé
        jméno hlavičku roztáhlo a tečky vystrčilo VEN z karty. Naměřeno +48 px do sousední karty
        na 1920×1080 u šesti z deseti karet.
     2. Když se nadpis smrsknout SMÍ, tečky mu z prvního řádku ukrojí ~68 px, takže se „Průmyslová
        modernizace" zalomila do ČTYŘ řádků a karta narostla na 173 px — tab Éra pak na 1800×880
        / „Větší" začal rolovat o 6 px, tedy jediný rolující tab ve hře.
   `float: right` řeší obojí: tečky sedí vpravo nahoře jako předtím, ale nadpis kolem nich obtéká
   a od druhého řádku má PLNOU šířku karty. Naměřeno 148 px místo 173 na nejtěsnější kombinaci.
   `flow-root` je tu proto, aby hlavička float obsáhla (jinak by mohl vytéct z karty).
   POZOR: tečky musí být v DOM PŘED nadpisem, jinak se float pod blokový `<h4>` propadne —
   viz komentář u `pipsHtml` v render.grid.js. */
.prestigeCardHead { display: flow-root; }
.prestigeCardHead h4 { overflow-wrap: break-word; }
/* Koupené úrovně jako tečky, ne text "2/5" - malé číslo se z vizuálního stavu přečte rychleji. */
.prestigePips { display: flex; gap: 3px; float: right; margin-left: 5px; }
.prestigePip {
    width: 7px; height: 7px;
    border-radius: 50%;
    border: var(--border-w-1) solid var(--republic-color);
    opacity: 0.45;
}
.prestigePip.filled { background: var(--republic-color); opacity: 1; }
/* Vykoupený bonus se vizuálně "utiší" - hráč hledá, co ještě může koupit, ne co už má. */
.prestigeCard.maxed { opacity: 0.62; }
.prestigeCard .btn { margin-top: auto; }

/* ---------- M4-3 / M4-4: odemčení a legacy uzly ---------- */
/* Štítek v hlavičce karty. Mechanické odemčení má jednu úroveň a odemyká AKCI, takže se od
   procentních uzlů musí lišit na první pohled - jinak hráč nepozná, že za 25 bodů kupuje sloveso,
   ne číslo. Legacy uzel nese štítek ze zrcadlového důvodu: vysvětluje, proč ho vidí jen on. */
.prestigeTag {
    margin-left: 5px;
    padding: 0 5px;
    border-radius: 999px;
    font-size: var(--fs-xs);
    font-weight: 400;
    vertical-align: middle;
    background: color-mix(in srgb, var(--republic-color) 26%, transparent);
    color: color-mix(in srgb, var(--republic-color) 55%, #fff);
}
.prestigeTag--legacy {
    background: color-mix(in srgb, var(--text-muted) 22%, transparent);
    color: var(--text-muted);
}
.prestigeCard--legacy { opacity: 0.78; }

/* ---------- M4-2 / M4-5: doktríny a mutace v potvrzení nové éry ---------- */
/* Leží v MODALU, ne v tabu - `#tabContent` má na „Malém" 213 px a žádný tab ve hře nesmí rolovat
   (viz odchylka od zadání D2 u republicHowItWorksHtml). Modal vlastní výšku má a rolovat smí. */
.eraSectionLabel { margin-top: var(--space-3); }
.eraDoctrines {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}
.eraDoctrine {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 9px;
    text-align: left;
    cursor: pointer;
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--panel-bg) 60%, transparent);
    color: inherit;
    font: inherit;
}
.eraDoctrine:hover { border-color: color-mix(in srgb, var(--republic-color) 55%, var(--panel-border)); }
/* Vybraná doktrína se pozná OBRUBOU I PODKLADEM, ne jen barvou rámečku: je to jediné místo
   dialogu, kde se dá něco přepnout, a rozhodnutí musí být čitelné i pro toho, kdo barvy nerozliší. */
.eraDoctrine.is-selected {
    border-color: var(--republic-color);
    background: color-mix(in srgb, var(--republic-color) 16%, transparent);
}
.eraDoctrine:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.eraDoctrineName { font-size: var(--fs-md); color: color-mix(in srgb, var(--republic-color) 72%, #fff); }
.eraDoctrineDesc { font-size: var(--fs-xs); color: var(--text); }
.eraDoctrineFlavour { font-size: var(--fs-xs); color: var(--text-muted); font-style: italic; }

.eraMutations { margin-bottom: var(--space-3); }
.eraMutation { font-size: var(--fs-sm); color: var(--text); }
/* Čistý výsledek dvou mutací, které táhnou proti sobě (nález F13). Je to shrnutí nad výčtem,
   ne další mutace - proto tlumené jako `.ts` a s odsazením shora, aby se s výčtem nesplynulo. */
.eraMutationSum { margin-top: var(--space-1); }

.slotCard h4 { margin: 0 0 2px 0; font-size: var(--fs-md); color: var(--gold); }
/* Drobný podpopisek (dřív omylem zúžený jen na .slotCard - všude jinde, kde se .ts použil (Republika,
   úvodní obrazovka, karty Systému), tiše padal na nestylovaný výchozí text prohlížeče: 16px, plná
   barva místo tlumené. To mj. dělalo karty bonusů na Malém/Středním zobrazení zbytečně vysoké -
   text se zalamoval na víc řádků, než by musel. */
.ts { font-size: var(--fs-xs); color: var(--text-muted); margin-bottom: 3px; }

/* „verze hry" je interaktivní (tooltip s novinkami verze, viz versionTooltipHtml/attachTooltip) -
   naznač to zlatou barvou, tečkovaným podtržením a nápovědným kurzorem. */
.versionInfo {
    color: var(--gold);
    font-weight: 600;
    cursor: help;
    border-bottom: 1px dotted color-mix(in srgb, var(--gold) 55%, transparent);
}
.versionInfo:hover { border-bottom-color: var(--gold); }
.versionInfo:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
/* Seznam novinek uvnitř tooltipu verze - kompaktní odrážky. */
.versionChanges { margin: 6px 0 0; padding-left: 16px; }
.versionChanges li { margin: 2px 0; }
/* Kód pro přenos (PUB-035). Hráč ho OPISUJE na jiný počítač, proto neproporcionální písmo (0 a O,
   1 a I se nepletou i bez Crockfordovy abecedy) a prostrkání mezi znaky. */
.cloudCode {
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    font-size: var(--fs-lg);
    letter-spacing: 0.12em;
    color: var(--text-1);
    margin: 2px 0 4px;
}
/* Hláška v dialogu „Obnovit ze zálohy" (v6.57) - chyba u tlačítka, které ji vyvolalo, protože toast
   `#notice` leží pod dialogem. Světlejší červená než `--fuel-low-color`: ta má na panelu jen
   4,29 : 1 (nález PUB-063/PUB-065), text hlášky potřebuje 4,5 : 1. */
.restoreMsg { margin-top: var(--space-1); font-size: var(--fs-sm); color: #f3a39a; }
/* Pole s chybou (špatný nebo neexistující kód) - rámeček v téže barvě jako hláška pod ním. */
.namedSaveNew input.inputError { border-color: #f3a39a; }
.slotBtns { display: flex; gap: var(--space-2); }
/* Vzhled/velikost tlačítek nese .btn/.btn--sm (v markupu); tady jen layout - roztažení na řádku. */
.slotBtns button { flex: 1; }

/* ---------- Pojmenovaná uložení ---------- */
/* Šířku řídí grid sloupec (.systemBox), ne pevná hodnota tady - viz .namedSaveLabel níže pro
   ořez dlouhého názvu elipsou, který funguje na jakoukoli šířku. */
.namedSaveNew { display: flex; gap: var(--space-2); margin: 3px 0 4px; }
.namedSaveNew input {
    flex: 1;
    min-width: 0;
    /* Fáze 3: tmavší „zapuštěné" pozadí (pole je níž než okolí) + jemný přechod na focus. */
    background: #0e1626;
    border: var(--border-w-1) solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-1);
    font-size: var(--fs-sm);
    padding: 6px 9px;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.namedSaveNew input::placeholder { color: var(--text-muted); }
/* Vlastní teal focus (mimo jednotný zlatý focus-ring níže): teal okraj + jemná teal záře.
   Box-shadow ring (3px) plní roli viditelného focus indikátoru pro klávesnici (přístupnost). */
.namedSaveNew input:focus-visible {
    outline: none;
    border-color: var(--action);
    box-shadow: 0 0 0 var(--border-w-3) rgba(20, 184, 166, 0.22);
}
/* Tlačítko „Uložit" nese vzhled přes .btn .btn--primary .btn--sm v markupu (viz render.js). */

/* Žádný scroll - NAMED_SAVES_MAX/AUTOSAVE_SLOTS.length je natvrdo 3 (truncateNamedSavesToMax()
   v persistence.js ořezává starší uložení nad limit), takže tenhle seznam nikdy nemá víc než 3
   řádky a vždycky se vejde na výšku, kterou mu grid sloupec (.systemBox) dá. */
.namedSavesList { display: flex; flex-direction: column; gap: var(--space-1); }
.namedSaveRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 2px 7px;
    background: #161d2e;
    border-radius: var(--radius-sm);
}
.namedSaveInfo { min-width: 0; }
.namedSaveLabel { font-size: var(--fs-sm); color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.namedSaveRow .slotBtns { flex: 0 0 auto; }
.namedSaveRow .slotBtns button { flex: 0 0 auto; }

/* ---------- Úspěchy ---------- */
.achievementsWrap {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    gap: var(--space-3);
}
.achievementsProgress { font-size: var(--fs-md); color: var(--gold); flex: 0 0 auto; }
.achievementsProgress b { font-size: var(--fs-base); }

/* 2.2: úzčí sloupce (minmax 160px) + jednořádkový popis (viz .achievementDesc) - na ~14" (1366px)
   8 sloupců → 16 odznaků ve 2 řadách, které se vejdou do stejné výšky panelu jako ostatní taby.
   #bottomPanel má teď natvrdo jednu výšku pro všechny taby, takže se mapa nikdy nepřepočítává
   (fitMap) jen kvůli přepnutí na Úspěchy - dřív širší sloupce + 2řádkový popis potřebovaly vyšší
   panel (#bottomPanel.tall), což při každém překliku na/z Úspěchů viditelně zmenšovalo/zvětšovalo
   mapu. Bez vlastního scrollu (jednotná politika = jediná pojistka na #tabContent). */
.achievementsGrid {
    flex: 1;
    min-height: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--achievements-col-min), 1fr));
    gap: var(--space-3);
    align-content: start;
    /* F5 (plán 6.0): řádek nesmí být nižší než obsah odznaku. Bez tohohle grid řádky POMĚRNĚ
       stlačil, aby se vešly do pevné výšky panelu (naměřeno na 1366×768 / „Střední": řádek 29,6 px
       proti 36 px textu), a `overflow: hidden` na odznaku pak u VŠECH 27 úspěchů uřízl spodní
       3-4 px popisu. Ořez byl tichý - hráč nepozná, že mu chybí kus textu.
       `min-content` je správnější než `align-items: start`: to sice taky vrátí odznaku přirozenou
       výšku, ale řádky nechá nízké, takže se odznaky navzájem PŘEKRYJÍ (vyzkoušeno a zamítnuto). */
    grid-auto-rows: min-content;
}

.achievementBadge {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    /* Svislé odsazení jede se škálou, vodorovné ne (F5 z plánu 6.0, plynule od v6.21).
       Původ: po zavedení `grid-auto-rows: min-content` (viz .achievementsGrid) mají odznaky
       přirozenou výšku a přestaly ořezávat popis - jenže 27 úspěchů ve 4 řádcích pak na malém
       okně přesáhlo panel a tab začal rolovat. Do 6.20 to řešilo pravidlo
       `:root[data-ui-tier="hd14"] { padding-block: 4px }`, tedy skok navázaný na jednu ze tří
       pevných úrovní. Se spojitou škálou je z toho obyčejný token: 4px na spodním konci,
       8px na horním, mezi tím plynule. Na obou koncích i uprostřed to dává přesně hodnoty,
       které tam byly předtím. */
    padding: calc(4px + 4px * var(--ui-k)) 10px;
}
.achievementBadge.unlocked { border-color: var(--gold); }
/* 0,55 → 0,75 (vizuální QA audit v6.9, nález P2-12). Při 0,55 propadaly kontrastem VŠECHNY tři
   texty zamčeného odznaku: název 3,60:1 (a to je 14px BOLD, hlavní text odznaku), popis 4,26:1
   a číslo postupu 4,17:1. Naměřeno po zvednutí: 5,67 / 6,90 / 6,76:1.
   Zamčenost tím nezmizí - nese ji dál ikona zámku místo poháru, barva názvu (--text-locked místo
   --gold) a chybějící zlatý okraj. Průhlednost byla čtvrtý, redundantní kanál; 0,75 pořád viditelně
   tlumí, jen už nemaže text. */
.achievementBadge.locked { opacity: 0.75; }
.achievementBadge.locked .achievementIcon .gicon { opacity: 0.9; }

.achievementIcon { flex: 0 0 calc(26px * var(--ui-scale)); width: calc(26px * var(--ui-scale)); height: calc(26px * var(--ui-scale)); }
.achievementIcon .gicon { width: 100%; height: 100%; }

.achievementText { min-width: 0; }
/* Název i popis na jeden řádek s elipsou (kompaktní odznak v1.9.1) - plný popis + datum je v tooltipu
   (bindAchievementsTabEvents) a v aria-label. */
.achievementName { font-size: var(--fs-sm); font-weight: bold; color: var(--gold); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.achievementBadge.locked .achievementName { color: var(--text-locked); }
/* Popis na jeden řádek s elipsou (2.2, viz .achievementsGrid) - plný text nese tooltip + aria-label.
   Jen na 14"/HD (výchozí, žádný vlastní blok) - tam je sloupec nejužší (--achievements-col-min)
   a zkrácení + tooltip je jediný způsob, jak se 16 odznaků vejde do panelu beze scrollu. Na 21"/27"
   (2.5) je sloupec i panel dost široký/vysoký na plný, nezkrácený popis, viz override níže. */
.achievementDesc { font-size: var(--fs-xs); color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 2.5 zavedlo na 21"/27" víceřádkový popis („širší sloupec i vyšší panel dávají dost místa").
   ZRUŠENO v 5.22: po D4 už to neplatí. Změřeno na 1920×1080 / „Střední": popisy narostly
   („napříč republikami") a úspěchů je 27 místo 16, takže grid dává řádku 55 px, kdežto
   dvouřádkový popis potřebuje 70-88 px. Text se tím tiše OŘEZÁVAL uvnitř `overflow: hidden`
   (které přibylo v D4 kvůli pruhu postupu) - a bez toho ořezu by zase začal rolovat celý tab.
   Jednořádkový popis s elipsou na VŠECH tierech je jediná varianta, která nedělá ani jedno;
   plný text nese tooltip i `aria-label`, takže se žádná informace neztrácí. */

/* ---------- Varování o zastaralém kódu (T19, implementační plán 4.0) ---------- */
/* z-index nad modalem (100) i vignetou (90-96) - musí být vidět za všech okolností, protože jde
   o krajní případ, kdy nejde věřit ničemu, co hra vykreslila. */
#staleVersionBanner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 12px;
    padding: 10px 16px;
    background: var(--danger);
    color: #fff;
    font-size: var(--fs-base);
    font-weight: bold;
    text-align: center;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
}
#staleVersionBanner.hidden { display: none; }

/* ---------- Modal ---------- */
#modalOverlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
    transition: background 0.18s ease;
}
#modalOverlay.hidden { display: none; }
/* Overlay přijímá kliky PRÁVĚ A JEN tehdy, když je vidět (nález F16 z auditu hraní v6.24).
   Zavírací animace trvá 180 ms a `.hidden` (display:none) se přidává až po ní, takže mezi
   kliknutím a skutečným zmizením overlay dál pokrýval celou obrazovku a jeho tlačítka byla dál
   klikatelná - jen průhledná. Dvojklik na „Ano, ..." tak provedl nevratnou akci DVAKRÁT a klik
   do mapy hned po zavření modalu se ztratil v ničem. `:not(.show)` pokrývá obojí konec okna
   (před `.show` je overlay stejně `display:none`). Druhá obrana je zámek v runAction, render.core.js. */
#modalOverlay:not(.show) { pointer-events: none; }
/* Fáze 3: overlay lehce rozostří scénu za dialogem (frosted zaostření na modal). */
#modalOverlay.show { background: rgba(0,0,0,0.62); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }

#modalBox {
    /* Glass dialog (Fáze 3): poloprůhledné pozadí + silné rozostření = dialog „plave" nad rozmazanou
       scénou. 1px neutrální okraj místo dřívějšího 3px teal rámu (barvu nese tlačítko uvnitř), silný
       vržený stín + jemná teal záře po obvodu pro prémiový dojem. */
    background: rgba(20, 27, 43, 0.88);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: var(--border-w-1) solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(45, 212, 191, 0.12);
    padding: 24px 32px;
    max-width: 480px;
    text-align: center;
    transform: scale(0.9);
    opacity: 0;
    transition: transform 0.18s ease, opacity 0.18s ease;
}
#modalOverlay.show #modalBox { transform: scale(1); opacity: 1; }
/* Destruktivní dialog (reset / mazání) - neutrální rám místo zeleného. Zelená (var(--action))
   značí "bezpečně pokračuj"; u nevratné akce je to matoucí signál. Varování nese červené
   tlačítko uvnitř (.btn--danger), rám je jen tlumeně neutrální. */
/* Bez teal záře po obvodu (base ji přidává) - teal = "bezpečně pokračuj", u nevratné akce matoucí. */
#modalBox.tone-danger { border-color: #2b3654; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6); }
#modalBox h2 { color: var(--gold); margin-top: 0; }
/* display:block (ne inline-block) - používá se jen ve vítězném modalu (checkVictory), kde
   následuje tlačítko "Pokračovat ve hře". Jako inline-block by se tlačítko za ní (taky inline-
   level prvek) mohlo vejít vedle ní na stejný řádek místo pod ni, pokud by v #modalBox
   (max-width 480px) zbylo dost místa. */
.statsSummary {
    display: block;
    text-align: left;
    margin-top: 12px;
    padding: 10px 16px;
    background: rgba(0, 0, 0, 0.25);
    border-radius: var(--radius-md);
}
.statsSummary .line { font-size: var(--fs-md); margin: 3px 0; color: var(--text-2); }

/* Řádek "Další cíl: ..." v modalu splněného cíle (T11, verze 5.0). Nekonečná řada cílů znamená, že
   oslava není konec - hráč má odcházet od okna s vědomím, na co teď míří. Zlatě, ale menším písmem
   než nadpis: je to pozvánka dál, ne druhý titulek. */
.goalNext { margin-top: 14px; font-size: var(--fs-md); color: var(--gold); }

/* Blok „Co teď?" ve vítězném modalu (D1, plán 5.19) - pojmenovává volbu, kterou hra do 5.19 měla,
   ale nikdy neřekla nahlas: hrát dál vs. založit republiku. Odlišený vodorovnou linkou nahoře, ne
   dalším rámečkem: modal už jednu odsazenou kartu má (.statsSummary) a druhá by z okna udělala
   seznam boxů. Zarovnání doleva, protože jde o dvě souvětí ke čtení, ne o čísla k prolétnutí. */
.goalChoice {
    margin-top: 16px;
    padding-top: 12px;
    border-top: var(--border-w-1) solid var(--border);
    text-align: left;
}
.goalChoice h3 {
    margin: 0 0 8px;
    font-size: var(--fs-md);
    color: var(--gold);
}
.goalChoiceOption {
    font-size: var(--fs-sm);
    color: var(--text-2);
    line-height: 1.55;
}
.goalChoiceOption + .goalChoiceOption { margin-top: 8px; }
/* Doporučená volba (M0.5, plán 2.0). Akt 1 má končit na 1. cíli - poslední nákup ve hře padne
   v 17m43s, takže dál je hra z 46-68 % dotěžování k prahu. Modal proto od v6.1 RADÍ reset;
   pokračování zůstává dostupné, jen přestává být rovnocennou nabídkou.
   Odlišení je vizuální (rámeček + odznak), ne jen textové - kritérium M0.5 zní „je to vidět
   bez čtení". */
.goalChoiceOption--recommended {
    border: var(--border-w-1) solid color-mix(in srgb, var(--gold) 55%, transparent);
    background: color-mix(in srgb, var(--gold) 12%, transparent);
    border-radius: 6px;
    padding: 8px 10px;
    color: var(--text-1);
}
.goalChoiceBadge {
    display: inline-block;
    margin-right: 6px;
    padding: 1px 6px;
    border-radius: 4px;
    background: var(--gold);
    color: #1a1408;
    font-size: var(--fs-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    vertical-align: 1px;
}
/* Důvod doporučení - stejný vzor jako „vrátí se za ~N kliknutí" u Dalšího kroku: rada bez důvodu
   je příkaz, rada s důvodem je informace. */
.goalChoiceWhy {
    display: block;
    margin-top: 4px;
    color: var(--text-2);
    font-size: var(--fs-xs);
}
/* Jednořádková varianta pro druhý a další cíl (u nich se celý blok nezobrazuje - viz goalChoiceHtml). */
.goalChoiceLine {
    margin-top: 14px;
    font-size: var(--fs-md);
    color: var(--text-2);
}
/* Modální tlačítka nesou vzhled/velikost přes .btn .btn--lg (+ barevná varianta) v markupu; tady
   jen jejich rozestup: odsazení od obsahu nad nimi a mezera mezi dvěma sousedními (potvrdit/zrušit)
   - dřív inline `margin-left:8px` na tlačítku Zrušit. */
#modalBox .btn { margin-top: 16px; }
#modalBox .btn + .btn { margin-left: 8px; }

/* Modal "Historie verzí" (showVersionHistoryModal, render.js) - sdílí #modalBox shell (jednotný
   design s vítězným/uvítacím oknem), jen o něco širší (56 verzí potřebuje víc místa na řádek
   "Verze X — datum", než kolik dovolí výchozích 480px) a se scrollovatelným seznamem, aby modal
   sám nerostl přes výšku obrazovky. */
#modalBox.modalWide { max-width: 640px; }
.versionHistoryList {
    max-height: 65vh;
    overflow-y: auto;
    text-align: left;
    margin-top: 12px;
    padding-right: 6px;
}

/* Vlastní posuvník (design 2026-08) - výchozí prohlížečový (plochý šedý obdélník) nezapadal do
   tmavé/zlaté vizuální identity hry, nejvíc vadil právě v dlouhém scrollovatelném seznamu historie
   verzí. Firefox (scrollbar-width/-color) + WebKit/Blink (::-webkit-scrollbar*) - obě větve dají
   stejný výsledek: tenký, "plovoucí" zlatý pruh bez track pozadí (track je průhledný, splývá
   s glass pozadím modalu/panelu), jen o kousek světlejší při hoveru. Sdíleno s #tabContent (viz
   "krajní pojistka" komentář výš) - ne globálně na celou stránku, mapa/canvas scroll nikdy nemá.
   TŘETÍ MÍSTO PŘIBYLO V6.27 (F17): `.roadmapScroll--stacked`, tedy záložka suroviny v aktu 2. Od
   v6.12 tam je skutečný svislý posuvník, ale tenhle blok o něm nevěděl, takže jediné místo ve hře,
   kde se roluje při běžném hraní, vykreslovalo systémový 15px posuvník uprostřed zlaté identity.
   Vedle vzhledu je to i 5 px šířky zpátky obsahu.
   Border+background-clip trik zúží viditelný pruh na ~6px uvnitř 10px žlabu ("plovoucí pilulka"
   místo pruhu na celou šířku) - stejný vizuální jazyk jako zaoblené glass karty/tlačítka jinde. */
.versionHistoryList,
.roadmapScroll--stacked,
#tabContent {
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--gold) 55%, transparent) transparent;
}
.versionHistoryList::-webkit-scrollbar,
.roadmapScroll--stacked::-webkit-scrollbar,
#tabContent::-webkit-scrollbar {
    width: 10px;
}
.versionHistoryList::-webkit-scrollbar-track,
.roadmapScroll--stacked::-webkit-scrollbar-track,
#tabContent::-webkit-scrollbar-track {
    background: transparent;
}
.versionHistoryList::-webkit-scrollbar-thumb,
.roadmapScroll--stacked::-webkit-scrollbar-thumb,
#tabContent::-webkit-scrollbar-thumb {
    background-color: color-mix(in srgb, var(--gold) 55%, transparent);
    background-clip: padding-box;
    border: 2px solid transparent;
    border-radius: var(--radius-sm);
}
.versionHistoryList::-webkit-scrollbar-thumb:hover,
.roadmapScroll--stacked::-webkit-scrollbar-thumb:hover,
#tabContent::-webkit-scrollbar-thumb:hover {
    background-color: var(--gold);
    background-clip: padding-box;
}

/* Jemná oddělovací linka mezi záznamy (ne nad prvním/pod posledním - proto na sourozeneckém
   selektoru, ne na .versionHistoryEntry samotné). */
.versionHistoryEntry + .versionHistoryEntry { border-top: var(--border-w-1) solid var(--border); margin-top: 12px; padding-top: 12px; }
.versionHistoryHead { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4); }
.versionHistoryVer { font-weight: 600; color: var(--gold); }
.versionHistoryDate { font-size: var(--fs-sm); color: var(--text-muted); white-space: nowrap; }

/* ---------- Úvodní obrazovka (T18, implementační plán 4.0) ----------
   Vlastní overlay (ne #modalOverlay/#modalBox) - modal je pro krátkodobá dialogová okna nad
   BĚŽÍCÍ hrou, kdežto tahle obrazovka je startovní rámec, který hru drží NEZAPOČATOU (main.js
   initShell/startGame). Přesto sdílí vizuální jazyk (glass panel, --gold nadpis) a `zone-index`
   NIŽŠÍ než modal (100), protože „Nová hra"/„Načíst hru" otevírají modal NAD touhle obrazovkou
   (potvrzovací dialog, seznam uložení) - kdyby byl titleScreen výš, modal by zůstal schovaný pod
   ním. #staleVersionBanner (200) zůstává nad vším, i nad úvodní obrazovkou. */
#titleScreen,
#systemMenu {
    position: fixed;
    inset: 0;
    z-index: 90;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Pozadí je rozostřená #stage POD overlayem - viz index.html komentář. Tmavý gradient je
       fallback pro prohlížeče bez backdrop-filter (jinak by rozmazaná, ale nezatmavená mapa
       hlavičku/tlačítka pod sebou prosvítala a čitelnost by trpěla). */
    background: linear-gradient(180deg, rgba(10, 14, 22, 0.88), rgba(10, 14, 22, 0.94));
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: opacity 0.2s ease;
}
#titleScreen.hidden,
#systemMenu.hidden { display: none; }

#titleScreenPanel,
#systemMenuPanel {
    position: relative;
    width: min(90vw, 420px);
    padding: 32px 28px;
    text-align: center;
    background: var(--surface-glass);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: var(--border-w-1) solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(45, 212, 191, 0.12);
}
#titleScreenHeading,
#systemMenuHeading { margin: 0 0 8px; font-size: 2em; color: var(--gold); }
#titleScreenSubtitle,
#systemMenuSubtitle { margin: 0 0 20px; color: var(--text-2); font-size: var(--fs-md); }
/* Řádek prestige postupu (T12 -> T18) - ukazuje postup ULOŽENÉ hry, kterou by načetlo „Pokračovat",
   ne aktuální (ještě nezvolený) `state` - viz renderTitleScreen v render.js. */
#titleScreenPrestige { margin: 0 0 18px; color: var(--gold); font-size: var(--fs-md); }
#titleScreenPrestige[hidden] { display: none; }
#titleScreenButtons,
#systemMenuButtons { display: flex; flex-direction: column; gap: 10px; }
#titleScreenButtons .btn,
#systemMenuButtons .btn { width: 100%; }
#titleScreenButtons .btn[hidden],
#systemMenuButtons .btn[hidden] { display: none; }
/* renderTitleScreen() (render.js) volá .focus() na primární tlačítko HNED po načtení stránky, aby
   šlo okamžitě potvrdit klávesou Enter - ale prohlížeče (Chrome i Firefox) považují i programové
   focus() bez předchozí interakce za ":focus-visible", takže by se hráči hned po otevření hry
   ukázal žlutý rám, který nikdo nevyvolal Tabem (zadavatel to nahlásil jako nechtěné, 2026-08-18).
   Potlačeno JEN u primárního tlačítka - to je díky teal výplni (.btn--primary) opticky odlišené i
   bez rámu; ostatní tlačítka (Nová hra/Načíst hru/Konec, viz .btn--ghost) rám při skutečné
   klávesové navigaci (Tab) dál dostávají, tam ho hráč potřebuje. */
#titleScreenButtons .btn--primary:focus-visible { outline: none; }
#titleScreenQuitHint { margin-top: 14px; }
#titleScreenQuitHint[hidden] { display: none; }
#titleScreenVersion,
#systemMenuVersion,
#helpVersion {
    position: absolute;
    right: 16px;
    bottom: 12px;
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

/* ---------- Roadmapa - vizuální technologický strom ---------- */
.maxed { color: #6fbf3f; font-weight: bold; }

.roadmapWrap {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    gap: var(--space-4);
    /* Kotva pro „obsah pokračuje" níž - `.roadmapScroll` je poslední dítě, takže spodní hrana
       wrapu je i spodní hranou rolované oblasti. */
    position: relative;
}

/* ---------- „Obsah pokračuje" na spodní hraně rolované záložky (F17, v6.27) ----------
   Posuvník sám o sobě je slabý signál: je vpravo, tenký a při `scrollTop = 0` snadno unikne.
   Naměřeno na v6.26: v aktu 2 nebyl z druhé sekce vidět ani jeden z 24 uzlů, takže záložka
   vypadala hotová, přestože jí chybělo 145 px obsahu. Přechod na hraně je oproti tomu vidět bez
   hledání a mizí, jakmile je doopravdy dole.
   Třídu přepíná JS podle SKUTEČNÉHO měření (refreshRoadmapScrollHint, roadmap.js) - stejný vzor
   jako refreshRoadmapLayout a fitMap: měří JS, kreslí CSS. Pevný práh v px by se rozešel
   s velikostí uzlu i s počtem revírů.
   `::after` visí na wrapu, ne na rolované oblasti: pseudo-element uvnitř scrollu se roluje spolu
   s obsahem, takže by se po prvním posunutí odjel z hrany pryč. */
.roadmapWrap::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--space-6);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease;
    background: linear-gradient(to top,
        color-mix(in srgb, var(--active-tab-color, transparent) 20%, var(--panel-bg)),
        transparent);
}
.roadmapWrap--more::after { opacity: 1; }

.roadmapHeader { flex: 0 0 auto; display: flex; flex-direction: column; gap: var(--space-2); }
.roadmapProgress { font-size: var(--fs-md); color: var(--gold); }
.roadmapProgress b { font-size: var(--fs-base); }

/* Trvalý "Další krok" ukazatel (QW #1+#2) - odpovídá na "co koupit dál" i "jak daleko jsem"
   bez skrolování. Vizuálně laděný jako lehký pruh s barvou aktivního tabu (--active-tab-color),
   ať patří k obsahu suroviny pod ním. */
.roadmapRecommend {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-3);
    font-size: var(--fs-sm);
    padding: 5px 10px;
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--active-tab-color, var(--gold)) 12%, transparent);
    border-left: var(--border-w-3) solid var(--active-tab-color, var(--gold));
    color: var(--text-2);
}
.roadmapRecommend .recLabel {
    font-size: var(--fs-2xs);
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--gold);
    font-weight: 500;
}
.roadmapRecommend .recName { color: var(--text-1); font-weight: 500; }
.roadmapRecommend .recCost { color: var(--text-2); }
.roadmapRecommend .recHave { color: var(--text-muted); font-size: var(--fs-xs); }
.roadmapRecommend .recMaxed { color: var(--text-muted); font-style: italic; }
/* PROČ zrovna tenhle uzel (B3, plán 5.19) - návratnost ze stejné funkce, podle které se doporučení
   vybírá. Odsazená doprava, ať doplňuje řádek a nepřebíjí název vylepšení. */
.roadmapRecommend .recWhy { margin-left: auto; color: var(--gold); font-size: var(--fs-xs); opacity: 0.85; }

/* v1.9.0: klikací a pasivní řada VEDLE SEBE (dva sloupce), ne pod sebou - využívá širokého/nízkého
   spodního panelu, takže se surovinový tab vejde bez svislého posuvníku i po zvětšení písma.
   Tematické pozadí (letokruhy/vrstvy/prstence) zůstává na tomto prvku. */
.roadmapScroll {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: row;
    gap: var(--space-6);
}

/* Upozornění, že zvolená velikost zobrazení na tohle okno nesedí (P1-19/P2-7/P2-13, v6.13).
   Tón je INFORMATIVNÍ, ne varovný: hráčova volba je legitimní (typicky volba přístupnosti) a hra ji
   nemíní zpochybňovat, jen k ní dodává, co se na tomhle okně nevejde. Proto --text-2 a jemný okraj
   v barvě zlaté, ne červená jako u chyby. */
.uiTierWarn {
    margin-top: var(--space-2);
    padding: var(--space-2) var(--space-3);
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-2);
    background: color-mix(in srgb, var(--gold) 10%, transparent);
    border-left: var(--border-w-3) solid color-mix(in srgb, var(--gold) 60%, transparent);
    border-radius: var(--radius-sm);
}

/* ---------- Skládané rozvržení záložky suroviny v AKTU 2 (P0-2, v6.12) ----------
   V aktu 1 se nic z tohohle neuplatní - `--stacked` přidává renderResourceRoadmap jen tehdy, když
   existuje větev revírů, tedy výhradně v aktu 2.

   Tady se VĚDOMĚ ruší nepsaný invariant „žádný tab ve hře neroluje" (docs/visual-baseline.md).
   Důvod: tři větve vedle sebe se do šířky nevejdou a dosavadní řešení - nechat je stlačit - platilo
   skrytým obsahem. Změřeno na 1920×1080: klikací i pasivní stopa schovávala 2 uzly na „Střední"
   a 3 na „Velkých", čip revíru měl 88 px a jeho tlačítka 10 px z potřebných 26. Svislý posuvník
   je proti tomu poctivý: říká, že obsah pokračuje, kdežto uříznuté tlačítko ne.
   Naměřeno po změně: roluje se o 141-156 px, ale ŽÁDNÝ uzel není oříznutý ani skrytý, čip má
   ~300 px a tlačítka se vejdou vedle sebe i s plnými popisky.

   `flex: 0 0 auto` na obou řádcích je tu to podstatné. S `1 1 auto` se druhý řádek stlačil na 22 px
   a uzly v něm se oříznuly o 66-83 px - tedy přesně ta vada, kterou tahle změna opravuje, jen
   přestěhovaná o patro níž. Nic v týhle oblasti se už nesmí smět zmenšit pod svůj obsah. */
.roadmapScroll--stacked {
    flex-direction: column;
    overflow-y: auto;
    /* --space-3, ne --space-5 (F17, v6.27): mezera mezi sekcemi byla přesně to místo, kam padal
       spodní okraj panelu, takže záložka vypadala jako hotová - z 24 uzlů druhé sekce nebyl při
       `scrollTop = 0` vidět ANI JEDEN a ani pixel jejího popisku. Zúžení posune hranu do karty
       druhé sekce, takže je vidět, že něco začíná. Signál nese `.roadmapWrap--more` níž; tohle mu
       jen dává šanci ukázat kus obsahu, ne jen prázdnou mezeru. */
    gap: var(--space-3);
}
.roadmapScroll--stacked > .roadmapBranch { flex: 0 0 auto; }
.roadmapRow {
    display: flex;
    flex-direction: row;
    gap: var(--space-6);
    flex: 0 0 auto;
    min-width: 0;
}
.roadmapRow > .roadmapBranch { flex: 1 1 0; min-width: 0; }
/* Když se ani půlka šířky nestačí (typicky 1366×768 s většími úrovněmi zobrazení), složí se
   i druhý řádek a záložka má tři řádky pod sebou. Rozhoduje o tom měření v refreshRoadmapLayout
   (roadmap.js), ne práh v px - velikost uzlu je per-tier a počet uzlů daný tabulkou, takže by se
   jakékoli pevné číslo dřív nebo později rozešlo se skutečností. */
.roadmapRow--stacked { flex-direction: column; }
.roadmapRow--stacked > .roadmapBranch { flex: 0 0 auto; }

.roadmapBranch {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: 10px 12px;
    background: rgba(0, 0, 0, 0.16);
    border: var(--border-w-1) solid rgba(255, 255, 255, 0.06);
    border-radius: var(--radius-lg);
    overflow-x: auto;
}
.roadmapRowLabel { font-size: var(--fs-md); line-height: 1.35; color: var(--text-2); }
.roadmapNextHint { color: var(--text-muted); font-size: var(--fs-sm); margin-left: 10px; }

.roadmapTrack {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--roadmap-node-gap);
    padding: 6px 0;
}

.roadmapNode {
    position: relative;
    flex: 0 0 var(--roadmap-node-w);
    width: var(--roadmap-node-w);
    /* Kruh (border-radius:50%) - výška MUSÍ jít se šířkou (--roadmap-node-w, per-tier), jinak by
       uzel na jiné velikosti UI než 14"/HD vyšel jako ovál, ne kruh. */
    height: var(--roadmap-node-w);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #222c40;
    border: var(--border-w-2) solid #2b3654;
    cursor: default;
    transition: transform 0.15s ease;
}
/* :focus-visible řeší sjednocené pravidlo v sekci Tlačítka (--focus-ring) - dřív tu byla bílá 3px. */
.roadmapIcon { width: calc(22px * var(--ui-scale)); height: calc(22px * var(--ui-scale)); }
.roadmapIcon .gicon { width: 100%; height: 100%; }

.roadmapNode:not(:first-child)::before {
    content: '';
    position: absolute;
    left: calc(-1 * var(--roadmap-node-gap));
    top: 50%;
    width: var(--roadmap-node-gap);
    height: var(--border-w-2);
    transform: translateY(-50%);
    background: #2b3654;
}

.roadmapNode.state-purchased { background: color-mix(in srgb, var(--res-color, #6fbf3f) 30%, #1a2233); border-color: var(--res-color, #6fbf3f); box-shadow: inset 0 0 6px rgba(255,255,255,0.15); }

/* Tier eskalace (Fáze 3): odemčené uzly "sílí" po tierech (1-3, 4-6, 7-9, 10-12) - zachovává barvu
   suroviny (identitu), stupňuje jen sytost + záři, takže dráha vizuálně "roste". Definováno PŘED
   .milestoneNode níže, aby zlatý max uzl (tier 4 + milestone) vyhrál nad tier-4 pravidlem. */
.roadmapNode.tier-2.state-purchased { background: color-mix(in srgb, var(--res-color, #6fbf3f) 40%, #1a2233); }
.roadmapNode.tier-3.state-purchased { background: color-mix(in srgb, var(--res-color, #6fbf3f) 52%, #1a2233); box-shadow: inset 0 0 6px rgba(255,255,255,0.18), 0 0 5px color-mix(in srgb, var(--res-color, #6fbf3f) 55%, transparent); }
.roadmapNode.tier-4.state-purchased { background: color-mix(in srgb, var(--res-color, #6fbf3f) 64%, #1a2233); box-shadow: inset 0 0 7px rgba(255,255,255,0.22), 0 0 9px color-mix(in srgb, var(--res-color, #6fbf3f) 70%, transparent); }
.roadmapNode.state-purchased:not(:first-child)::before { background: var(--res-color, #6fbf3f); }

/* B3 (plán 5.19): „dostupné" je od 5.19 TICHÝ stav - zlatý okraj a jemná záře, žádná animace ani
   odznak. Do 5.18 tu pulzoval každý dostupný uzel (klikací i pasivní současně) a k tomu měl zlatý
   odznak „!", takže „můžu koupit" vypadalo úplně stejně jako „vyplatí se koupit" (.recommended,
   pár pixelů vedle) - a hráč se ptal, co je tedy doporučené. Animaci i odznak má dnes výhradně
   doporučený uzel. */
.roadmapNode.state-available { border-color: var(--gold); box-shadow: 0 0 5px rgba(245, 196, 81, 0.45); cursor: pointer; }
.roadmapNode.state-available:hover { transform: scale(1.1); }
.roadmapNode.state-available:not(:first-child)::before { background: var(--gold); }

.roadmapNode.state-affordable-locked { opacity: 0.6; }
.roadmapNode.state-affordable-locked .roadmapIcon { opacity: 0.8; }

.roadmapNode.state-locked { background: #161d2e; border-color: #2f3a55; }
.roadmapNode.state-locked .roadmapIcon { opacity: 0.25; }
.roadmapNode.state-locked:not(:first-child)::before { background: repeating-linear-gradient(90deg, #46516b 0 4px, transparent 4px 8px); }

/* Od B3 (plán 5.19) nese odznak výhradně DOPORUČENÝ uzel (markRecommendedAndScroll), a to
   hvězdičkou - stejným symbolem, jaký má indikátor na tabu (.tabBtn.hasAvailable::after, B1).
   Do 5.18 to byl vykřičník na každém dostupném uzlu, tedy na klikacím i pasivním současně. */
.roadmapBadge {
    position: absolute;
    top: -5px;
    right: -5px;
    width: calc(15px * var(--ui-scale));
    height: calc(15px * var(--ui-scale));
    border-radius: 50%;
    background: var(--gold);
    color: #3a2c00;
    font-size: var(--fs-xs);
    font-weight: bold;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 0 4px rgba(0,0,0,0.5);
}

.roadmapLock {
    position: absolute;
    bottom: -5px;
    right: -5px;
    width: calc(17px * var(--ui-scale));
    height: calc(17px * var(--ui-scale));
    filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.7));
}
.roadmapLock .gicon { width: 100%; height: 100%; }

.roadmapNode::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    pointer-events: none;
    background: conic-gradient(var(--gold) calc(var(--pct, 0) * 1%), transparent 0);
    mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
    opacity: 0;
}
.roadmapNode.state-affordable-locked::after { opacity: 0.8; }

/* +12px pevně nad běžným uzlem (52px na 14"/HD, kde je --roadmap-node-w 40px) - ne poměrem, aby
   milníkový uzel zůstal viditelně větší i po přepnutí velikosti UI, ne stejně velký jako běžný
   uzel jiné úrovně (--roadmap-node-w se liší per-tier, viz :root[data-ui-tier] výše). */
.roadmapNode.milestoneNode {
    width: calc(var(--roadmap-node-w) + 12px);
    height: calc(var(--roadmap-node-w) + 12px);
    flex-basis: calc(var(--roadmap-node-w) + 12px);
}
.roadmapNode.milestoneNode .roadmapIcon { width: calc(22px * var(--ui-scale) + 6px); height: calc(22px * var(--ui-scale) + 6px); }
.roadmapNode.milestoneNode.state-purchased { box-shadow: 0 0 10px rgba(245, 196, 81, 0.7), inset 0 0 6px rgba(255,255,255,0.2); border-color: var(--gold); }

/* Korunka nad vrcholovým (max) uzlem každé dráhy (Fáze 3) - "legendární" konec větve. Zamčená/
   nezakoupená je tlumená (aspirace), po odemčení plná zlatá. */
.roadmapCrown { position: absolute; top: -12px; left: 50%; transform: translateX(-50%); width: calc(16px * var(--ui-scale)); height: calc(16px * var(--ui-scale)); pointer-events: none; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.5)); }
.roadmapCrown .gicon { width: 100%; height: 100%; }
.roadmapNode:not(.state-purchased) .roadmapCrown { opacity: 0.35; filter: grayscale(0.4); }

/* "Právě odemčeno" burst (Fáze 3) - jednorázová oslavná animace nově zakoupeného uzlu
   (flashClass 'nodeUnlockBurst' z burstUnlockedNode). Barva = barva suroviny/kritického kliku
   zděděná z --res-color na .roadmapTrack. */
.roadmapNode.nodeUnlockBurst { animation: nodeUnlockBurst 0.65s ease; z-index: 2; }
@keyframes nodeUnlockBurst {
    0% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--res-color, #f5c451) 90%, transparent); }
    30% { transform: scale(1.35); box-shadow: 0 0 16px 5px color-mix(in srgb, var(--res-color, #f5c451) 75%, transparent); }
    100% { transform: scale(1); box-shadow: 0 0 0 0 transparent; }
}

/* Tematická pozadí dráhy podle suroviny (Fáze 3) - jemný tint dává každé surovině vlastní identitu,
   ale zůstává dost slabý, aby uzly zůstaly čitelné. Dřevo = teplý les, uhlí = antracit + žár,
   uran = radioaktivní zeleň. */
/* Dřevo — letokruhy z kmene (v1.9.0): soustředné elipsy s dření mimo střed (řez kládou), teplé
   hnědé tóny. Vizuálně odlišné od centrovaných reaktorových prstenců uranu i vrstev uhlí. */
.roadmap-wood .roadmapScroll {
    background:
        repeating-radial-gradient(ellipse 130% 95% at 30% 45%, rgba(120,84,44,0.12) 0 2px, transparent 2px 12px),
        radial-gradient(140% 100% at 30% 45%, rgba(201,164,104,0.10), transparent 65%);
}
/* Uhlí — "sestup do šachty" (Fáze 4, atmosféra): vodorovné vrstvy horniny + tmavnutí shora dolů
   (= čím níž na dráze, tím hlouběji) + žhavý přísvit u dna. Statické (uhlí = tíha/hloubka). */
.roadmap-coal .roadmapScroll {
    background:
        linear-gradient(180deg, rgba(255,255,255,0.02), rgba(0,0,0,0.20)),
        repeating-linear-gradient(180deg, rgba(255,255,255,0.028) 0 2px, transparent 2px 26px),
        radial-gradient(70% 60% at 92% 100%, rgba(217,122,52,0.07), transparent 60%);
}
/* Uran — "reaktorové jádro" (Fáze 4): soustředné prstence z prostředka evokují radiál, aniž se
   mění vodorovný layout uzlů. Pulzující Čerenkovská záře je na .roadmap-uranium::after níže. */
.roadmap-uranium .roadmapScroll {
    background:
        repeating-radial-gradient(circle at 50% 52%, rgba(74,222,128,0.05) 0 1px, transparent 1px 22px),
        radial-gradient(120% 90% at 50% 40%, rgba(74,222,128,0.10), transparent 62%);
}

/* v1.10.0: strukturované pozadí je nově na KARTÁCH sloupců (.roadmapBranch), ne na .roadmapScroll -
   ten karty od 1.9.0 překrývaly, takže textura nebyla vidět. Tóny jsou světlejší/kontrastnější, aby
   četly na tmavém panelu (dřív tmavá-na-tmavé splývala). Uzly a text zůstávají čitelné. Přepisuje
   generické .roadmapBranch pozadí; Bonus tab má od 2026-08 vlastní motiv níže (.roadmap-bonus). */
/* Dřevo (varianta N4, v1.10.1): "čelo klády v rohu" - soustředné prstence px-based (fixní velikost,
   jen střed ukotvený u levého dolního rohu) + jemná svislá žilka. Nahrazuje dřívější %-elipsy
   (repeating-radial ellipse 165%…), které se s šířkou karty roztahovaly a na jiném rozlišení
   vypadaly úplně jinak. px stopy → stejný vzhled na jakémkoli rozlišení. */
.roadmap-wood .roadmapBranch {
    background:
        repeating-radial-gradient(circle at 6% 108%, transparent 0 8px, rgba(214,176,118,0.30) 8px 10px, transparent 10px 20px),
        repeating-linear-gradient(90deg, rgba(200,160,100,0.10) 0 1.5px, transparent 1.5px 12px),
        linear-gradient(150deg, #3a2a15, #281c0e);
}
.roadmap-coal .roadmapBranch {
    background:
        repeating-linear-gradient(180deg, rgba(186,194,204,0.14) 0 2px, transparent 2px 4px, transparent 4px 15px),
        radial-gradient(78% 55% at 88% 102%, rgba(217,122,52,0.14), transparent 60%),
        linear-gradient(180deg, #343842, #22262c);
}
.roadmap-uranium .roadmapBranch {
    background:
        repeating-radial-gradient(circle at 50% 40%, rgba(120,224,150,0.17) 0 2px, transparent 2px 4px, transparent 4px 14px),
        linear-gradient(150deg, #16291d, #0e1a12);
}

/* Čerenkovská záře (Fáze 4) - jemná pulzující radioaktivní aura přes celou uranovou roadmapu.
   pointer-events:none → kliky procházejí na uzly; screen blend přidává zelený svit. Respektuje
   reduced-motion (animace se vypne, statická slabá záře zůstane). */
.roadmap-uranium { position: relative; }
.roadmap-uranium::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 3;
    background: radial-gradient(circle at 50% 55%, rgba(74,222,128,0.10), transparent 58%);
    mix-blend-mode: screen;
    animation: cerenkovPulse 3.6s ease-in-out infinite;
}
@keyframes cerenkovPulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }

/* Bonus - "šťastný záblesk" (design 2026-08): na rozdíl od materiálových motivů (letokruhy/vrstvy/
   reaktor) jde o mechaniku štěstí/kritického zásahu, ne surovinu - diagonální paprsky evokující
   záblesk/jiskru místo geologické textury. Stejný pulzující princip jako Čerenkovská záře uranu
   (vlastní @keyframes, ať jde nezávisle vypnout přes prefers-reduced-motion). */
.roadmap-bonus .roadmapBranch {
    background:
        repeating-linear-gradient(115deg, rgba(224,154,133,0.14) 0 2px, transparent 2px 3px, transparent 3px 22px),
        radial-gradient(85% 65% at 50% 15%, rgba(224,154,133,0.16), transparent 62%),
        linear-gradient(150deg, #33241f, #211714);
}
.roadmap-bonus { position: relative; }
.roadmap-bonus::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 3;
    background: radial-gradient(circle at 50% 30%, rgba(224,154,133,0.12), transparent 55%);
    mix-blend-mode: screen;
    animation: bonusSparkPulse 2.8s ease-in-out infinite;
}
@keyframes bonusSparkPulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }

.roadmapNode.recommended { box-shadow: 0 0 0 3px rgba(245, 196, 81, 0.5); animation: recommendedGlow 1.6s ease-in-out infinite; }
@keyframes recommendedGlow {
    0%, 100% { box-shadow: 0 0 0 3px rgba(245, 196, 81, 0.35); }
    50% { box-shadow: 0 0 0 6px rgba(245, 196, 81, 0.15); }
}

/* Závislost klik→pasiv nese textově pasivní řádkový popisek ("vyžaduje úroveň <nástroj> N") -
   viz renderResourceRoadmap. Vizuální brány i jejich dělící pruh byly odstraněny (v1.8.2). */
.roadmapInfoLine { font-size: var(--fs-xs); color: var(--text-muted); margin-top: 4px; }

/* ---------- prefers-reduced-motion ---------- */
/* Dva různé způsoby "vypnutí" pohybu podle toho, co animaci zastavuje:
   - `flashClass()` (efekty vázané na JS setTimeout, ne na konec animace) -> animation:none je
     bezpečné, nic na jejím doběhnutí nezávisí.
   - `spawnParticles/spawnShockwave/spawnConfetti/spawnFloatText` (effects.js, render.js) se
     odstraňují až v listeneru na `animationend` - kdyby animace přestala běžet úplně, event by
     nikdy nepřišel a element by v DOM zůstal navždy. Tam se proto jen zkracuje doba trvání na
     prakticky nulovou, ne úplně ruší. */
@media (prefers-reduced-motion: reduce) {
    .resourceIcon.justUnlocked,
    .resourceIcon.stockDrained,
    #bonusChanceBox.critCharged,
    #mapGrid.critCharged .resourceIcon:not(.locked),
    .resourceIcon.abundance-rare,
    .resourceIcon.abundance-rare::before,
    .resAmount.gainFlash,
    .resStat.milestonePop,
    .tabBtn.hasAvailable.availPulse::after,
    .roadmapNode.recommended,
    .roadmapNode.nodeUnlockBurst,
    .roadmap-uranium::after,
    .roadmap-bonus::after,
    .plantGlowSlot,
    #goldRushBadge,
    .tabBtn.tabRevealed,
    #vignetteOverlay.flash,
    #jackpotBanner.show {
        animation: none !important;
    }

    .clickParticle,
    .shockwaveRing,
    .confettiPiece,
    .floatText,
    .floatText.crit {
        animation-duration: 0.001s !important;
    }

    #notice,
    #modalOverlay,
    #modalBox,
    #titleScreen,
    #systemMenu,
    #helpOverlay,
    .resourceIcon,
    .roadmapNode,
    .unlockProgressFill,
    .btn,
    .tabBtn,
    #mapHelpBtn {
        transition-duration: 0.001s !important;
    }
}

/* In-app přepínač (Systém tab, toggleReducedMotion) - nezávislý na OS nastavení výše, stejná
   pravidla jen navázaná na třídu místo mediálního dotazu, protože CSS neumí "OR" mezi @media
   a třídou bez duplicitního zápisu selektorů. */
body.reduced-motion .resourceIcon.justUnlocked,
body.reduced-motion .resourceIcon.stockDrained,
body.reduced-motion #bonusChanceBox.critCharged,
body.reduced-motion #mapGrid.critCharged .resourceIcon:not(.locked),
body.reduced-motion .resourceIcon.abundance-rare,
body.reduced-motion .resourceIcon.abundance-rare::before,
body.reduced-motion .resAmount.gainFlash,
body.reduced-motion .resStat.milestonePop,
body.reduced-motion .tabBtn.hasAvailable.availPulse::after,
body.reduced-motion .roadmapNode.recommended,
body.reduced-motion .roadmapNode.nodeUnlockBurst,
body.reduced-motion .roadmap-uranium::after,
body.reduced-motion .roadmap-bonus::after,
body.reduced-motion .plantGlowSlot,
body.reduced-motion #goldRushBadge,
body.reduced-motion .tabBtn.tabRevealed,
body.reduced-motion #vignetteOverlay.flash,
body.reduced-motion #jackpotBanner.show {
    animation: none !important;
}

body.reduced-motion .clickParticle,
body.reduced-motion .shockwaveRing,
body.reduced-motion .confettiPiece,
body.reduced-motion .floatText,
body.reduced-motion .floatText.crit {
    animation-duration: 0.001s !important;
}

body.reduced-motion #notice,
body.reduced-motion #modalOverlay,
body.reduced-motion #modalBox,
body.reduced-motion #titleScreen,
body.reduced-motion #systemMenu,
body.reduced-motion #helpOverlay,
body.reduced-motion .resourceIcon,
body.reduced-motion .roadmapNode,
body.reduced-motion .unlockProgressFill,
body.reduced-motion .btn,
body.reduced-motion .tabBtn,
body.reduced-motion #mapHelpBtn,
/* Odsycení mapy v pauze (E1) - v omezeném režimu se přepne skokem, ne přechodem. Samotné
   zšednutí zůstává: nese informaci o stavu, není to dekorace. */
body.reduced-motion.paused #mapCanvas,
body.reduced-motion.paused #mapGrid,
body.reduced-motion.paused #mapLabels,
body.reduced-motion.paused #plantOverlay,
body.reduced-motion.paused #abundanceLegend {
    transition-duration: 0.001s !important;
}

.toggleRow {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    font-size: var(--fs-sm);
    color: var(--text-2);
    cursor: pointer;
    /* Sníženo z 4px (2.15) - po odebrání popisku "Výchozí jsou..." pod přepínači vycházela karta
       Přístupnost a zvuk na Velkém zobrazení přesně na hranici výšky #tabContent (0px rezervy,
       viz změřeno v prohlížeči) - o chlup těsnější řádky dávají skutečnou rezervu, ne jen
       nahodilou shodu na pixel. */
    margin: 2px 0;
}
.toggleRow input { accent-color: var(--action); width: calc(15px * var(--ui-scale)); height: calc(15px * var(--ui-scale)); cursor: pointer; }

/* Dvousloupcová tabulka statistik „tento průchod / všechny republiky" (E4, plán 5.19). Tabulka,
   ne dva sloupce divů: jsou to skutečná řádková data se záhlavím, takže `<table>` je zároveň to,
   co čtečka přečte správně (řádek „Kliknutí: tento průchod 1 240, všechny republiky 8 900").
   Čísla doprava, popisky doleva - porovnávají se svisle. */
.statsTable {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-sm);
    margin-bottom: 6px;
}
.statsTable th,
.statsTable td { padding: 2px 0; }
.statsTable thead th {
    font-size: var(--fs-xs);
    font-weight: normal;
    color: var(--text-muted);
    text-align: right;
    border-bottom: var(--border-w-1) solid var(--border);
    padding-bottom: 3px;
}
.statsTable tbody th {
    font-weight: normal;
    color: var(--text-2);
    text-align: left;
    white-space: nowrap;
}
.statsTable td {
    text-align: right;
    font-variant-numeric: tabular-nums;
    padding-left: var(--space-4);
}
/* Kumulativní sloupec je ten zajímavější („kam se moje hraní počítá"), ale nesmí přebít aktuální
   průchod - proto jen o odstín světlejší, ne tučný. */
.statsTable td:last-child { color: var(--text-1); }

/* ---------- Míra naplnění a spoilery úspěchů (D4, plán 5.19) ---------- */
/* Pruh postupu při spodní hraně odznaku - `::after` řízený proměnnou `--ach-pct`, stejný vzor jako
   postup k odemčení na tabu (.tabBtn.locked::after). Absolutně pozicovaný ZÁMĚRNĚ: grid úspěchů se
   do spodního panelu vejde jen tak tak (viz komentář u .achievementsGrid), takže míra naplnění
   nesmí odznaku přidat ani pixel výšky. Barvu nese --gold jako ostatní ukazatele postupu. */
.achievementBadge { position: relative; overflow: hidden; }
.achievementBadge.hasProgress::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    height: var(--border-w-2);
    width: calc(var(--ach-pct, 0) * 1%);
    background: var(--gold);
    opacity: 0.7;
    transition: width 0.3s ease;
}
/* Číslo („3 240 / 5 000") stojí na ZAČÁTKU řádku s popisem, ne na konci. Důvod je layout, ne vkus:
   na 14"/HD se popis krátí elipsou (`white-space: nowrap`), kdežto na 21"/27" se naopak zalamuje
   přes víc řádků (viz override u .achievementDesc). Flexbox s číslem vpravo by to druhé rozbil
   a na tom prvním by číslo vytlačil ven ze zkráceného řádku (změřeno: popis 111 px, číslo 60 px).
   Jako obyčejný inline prvek na začátku funguje v obou režimech a nikdy se neořízne - popis
   ustoupí, což je v pořádku, protože ten je celý v tooltipu i v aria-label. */
.achievementProgressNum {
    color: var(--gold);
    font-variant-numeric: tabular-nums;
}
.achievementProgressNum::after {
    content: ' · ';
    color: var(--text-muted);
}
/* Zamčený spoiler (D4) - „???" místo názvu. Vizuálně se od běžného zamčeného odznaku liší jen tím,
   že nemá co prozradit; žádné zvláštní zvýraznění, ať nepřitahuje pozornost víc než ostatní. */
.achievementBadge.spoiler .achievementName { letter-spacing: 2px; }

/* ---------- Pauza (E1, plán 5.19) ---------- */
/* Tlačítko vpravo v horní liště. Klidově tlumené jako ostatní ovládací prvky panelu, v pauze
   zlaté - stav se musí poznat i bez pohledu na mapu. */
/* Klidový stav je ZÁMĚRNĚ dost kontrastní (v5.26): první verze měla tlumené šedé na tmavém a
   zadavatel z hraní nahlásil, že tlačítko „prakticky vůbec není vidět". Horní lišta je hustá a
   tlačítko v ní musí obstát vedle barevných pilířů surovin i zlatého boxu bonusu - proto zlatý
   okraj a světlá ikona i v klidu, ne až po najetí myší. */
#pauseBtn,
#menuBtn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: calc(38px * var(--ui-scale));
    height: calc(38px * var(--ui-scale));
    padding: 0;
    background: color-mix(in srgb, var(--gold) 16%, #0b0f1a);
    border: var(--border-w-2) solid color-mix(in srgb, var(--gold) 55%, transparent);
    border-radius: var(--radius-sm);
    color: var(--gold);
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
#pauseBtn .gicon,
#menuBtn .gicon { width: 58%; height: 58%; display: block; }
#pauseBtn:hover,
#menuBtn:hover {
    background: color-mix(in srgb, var(--gold) 30%, #0b0f1a);
    border-color: var(--gold);
    color: #fff;
}
#pauseBtn.isPaused,
#menuBtn.isOpen {
    background: color-mix(in srgb, var(--gold) 26%, #0b0f1a);
    border-color: var(--gold);
    color: var(--gold);
}

/* „Zamrzlá mapa" (koncept B, vybraný zadavatelem ze tří nabídnutých 2026-08-28): mapa zšedne
   a ztmavne, přes ni jen nápis bez rámečku. Odsycená mapa sama nese informaci „tady se teď
   neklika", takže je stav nepřehlédnutelný, aniž by se četl jako modal čekající na potvrzení
   (to byla vada zamítnuté varianty A). Filtr jde na jednotlivé vrstvy mapy, ne na #mapStage jako
   celek - ten by odsytil i samotný nápis. */
body.paused #mapCanvas,
body.paused #mapGrid,
body.paused #mapLabels,
body.paused #plantOverlay,
body.paused #abundanceLegend {
    filter: grayscale(0.85) brightness(0.65);
    transition: filter 0.2s ease;
}
/* #mapHelpBtn v tomhle seznamu ZÁMĚRNĚ chybí (v6.28). Odsycení říká „tady se teď neklika" -
   jenže na otazník se klikat dá i v pauze (a hráč, který si hru pozastavil, aby si něco našel,
   ho potřebuje právě teď). Zůstat barevný nad zamrzlou mapou je proto pravdivé, ne nedůslednost:
   je to jediná věc na mapě, která v pauze pořád funguje. `#pauseOverlay` má `pointer-events: none`,
   takže mu klik nesebere, i když nad ním leží (z-index 30 vs. 20). */
/* Překryv leží nad #mapGrid (viz pořadí v index.html). `pointer-events: none` je záměr: klik
   stejně zahodí `onMapClick`/`onMapCanvasClick` (E1), takže overlay nemusí nic odchytávat sám -
   a hráč se v pauze pořád dostane na tooltipy legendy. */
#pauseOverlay {
    position: absolute;
    inset: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(8, 12, 20, 0.15);
    pointer-events: none;
}
#pauseOverlay[hidden] { display: none; }
.pauseOverlayBox { text-align: center; }
.pauseOverlayTitle {
    font-size: calc(30px * var(--ui-scale));
    font-weight: bold;
    color: var(--gold);
    letter-spacing: 3px;
    text-transform: uppercase;
    /* Dvojitý stín místo podkladu - nápis musí být čitelný nad jakoukoli částí mapy (světlá
       cizina i tmavé hory), ale bez plochy, která by z něj udělala panel. */
    text-shadow: 0 2px 12px #000, 0 0 30px #000;
}
.pauseOverlayHint {
    margin-top: 6px;
    font-size: var(--fs-sm);
    color: var(--text-1);
    text-shadow: 0 1px 8px #000, 0 0 20px #000;
}

/* ---------- Nápověda: znalostní báze (F1, implementační plán 5.19) ----------
   Overlay sdílí backdrop s úvodní obrazovkou a nabídkou Systém (stejný vizuální jazyk), ale panel
   je ZÁMĚRNĚ jiný: ty dva jsou úzké sloupce tlačítek, tohle je čtecí plocha dvou sloupců. Proto
   vlastní pravidla, ne rozšíření selektorů jako u #systemMenu.
   z-index 98: nad nabídkou (90), ze které se otevírá, a nad tooltipem (97), aby přes text
   nepřebliklo bublinou z prvku pod overlayem; pod modalem (100). */
#helpOverlay {
    position: fixed;
    inset: 0;
    z-index: 98;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(180deg, rgba(10, 14, 22, 0.9), rgba(10, 14, 22, 0.95));
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
#helpOverlay.hidden { display: none; }

/* Výška je stropovaná na viewport, ne na obsah - sekce mají velmi různou délku a bez stropu by
   nejdelší z nich přetáhla obrazovku (roluje se pak jen #helpContent, viz níž). */
#helpPanel {
    display: flex;
    flex-direction: column;
    width: min(94vw, calc(940px * var(--ui-scale)));
    height: min(86vh, calc(620px * var(--ui-scale)));
    background: var(--surface-glass);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: var(--border-w-1) solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(45, 212, 191, 0.12);
    overflow: hidden;
}

#helpHeader {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-5) var(--space-6);
    border-bottom: var(--border-w-1) solid var(--border);
    flex: 0 0 auto;
}
#helpHeading { margin: 0; font-size: var(--fs-xl); color: var(--gold); }

#helpBody {
    display: grid;
    grid-template-columns: calc(220px * var(--ui-scale)) minmax(0, 1fr);
    flex: 1 1 auto;
    min-height: 0; /* bez tohohle grid neumí zmenšit potomka pod jeho obsah a scroll nevznikne */
}

/* Seznam sekcí roluje sám za sebe - na Malém zobrazení se dvanáct položek do výšky panelu nevejde. */
#helpNav {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-4);
    overflow-y: auto;
    border-right: var(--border-w-1) solid var(--border);
}
.helpNavBtn {
    text-align: left;
    padding: var(--space-2) var(--space-4);
    background: transparent;
    border: var(--border-w-1) solid transparent;
    border-radius: var(--radius-md);
    color: var(--text-2);
    font-size: var(--fs-sm);
    font-family: inherit;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}
.helpNavBtn:hover { background: rgba(255, 255, 255, 0.06); color: var(--text-1); }
.helpNavBtn.active {
    background: color-mix(in srgb, var(--gold) 16%, transparent);
    border-color: color-mix(in srgb, var(--gold) 45%, transparent);
    color: var(--gold);
}

#helpContent {
    padding: var(--space-5) var(--space-6);
    overflow-y: auto;
    color: var(--text-2);
    font-size: var(--fs-md);
    line-height: 1.55;
}
#helpContent h3 { margin: 0 0 var(--space-5); font-size: var(--fs-lg); color: var(--text-1); }
#helpContent p { margin: 0 0 var(--space-5); }
#helpContent p:last-child { margin-bottom: 0; }
#helpContent b { color: var(--text-1); }

.helpTable {
    width: 100%;
    margin: 0 0 var(--space-5);
    border-collapse: collapse;
    font-size: var(--fs-sm);
}
.helpTable th, .helpTable td {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    vertical-align: top;
    border-bottom: var(--border-w-1) solid var(--border);
}
.helpTable th { color: var(--text-1); font-weight: normal; white-space: nowrap; width: 1%; }
.helpTable tr:last-child th, .helpTable tr:last-child td { border-bottom: none; }

/* Odkaz do jiné sekce - vypadá jako odkaz, je to ale <button> (nevede nikam ven, jen přepíná
   obsah). Používá se i MIMO nápovědu (tab Bonus, karta odemčení, tab Republika). */
.helpLink {
    display: inline;
    padding: 0;
    background: none;
    border: none;
    color: var(--gold);
    font: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}
.helpLink:hover { color: var(--text-1); }

/* ---------- Záložka „Soustava" (M1, plán 2.0) ----------
   Vzhled ZÁMĚRNĚ kopíruje Republiku: souhrn nahoře (grid s `auto`/`1fr` sloupci) a mřížka karet
   pod ním. Není to lenost - spodní panel má PEVNOU výšku (213 px na „Malém", 264 na „Velkém")
   a Republika je jediné rozložení v celé hře, o kterém je ověřeno, že se do ní vejde souhrn
   i mřížka najednou. Nový tab je poslední místo, kde experimentovat s výškou (dohoda č. 5). */
.gridBox {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.gridSummary {
    display: grid;
    grid-template-columns: auto auto 1fr;
    align-items: center;
    gap: var(--space-5);
}

.gridPower { text-align: center; }
.gridPowerValue {
    /* 24px, ne 28 jako u bodů odkazu na Republice: souhrn Soustavy má o sloupec víc a spodní panel
       má pevnou výšku. Naměřeno - s 28px přetekl tab o 13 px na 1920×1080 („Střední") a s 24px
       ještě o 3 px na 1366×768 („Malé"). Při 22px je čisto na všech třech úrovních. */
    font-size: calc(22px * var(--ui-scale));
    font-weight: bold;
    color: var(--grid-color);
    line-height: 1.1;
}
/* JEN PŘÍMÍ POTOMCI. Bez ">" udělal tenhle řádek blok i z popisku „140 MW" uvnitř
   .gridLoadName, ten se tím zalomil na druhou řádku a panel přetekl - přesně ten druh tichého
   přetečení, které v overflow: hidden okem nepoznáš (dohoda č. 5). */
.gridPower > .ts, .gridReserves > .ts, .gridNextLoad > .ts { display: block; }
.gridReserves .line, .gridNextLoad .line { font-size: var(--fs-sm); color: var(--text-2); margin: 2px 0; }
.gridNextLoad { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }
/* Jméno zátěže i řádky souhrnu se nelámou - ze stejného důvodu jako řádky karet bloků níž:
   „České Budějovice 45 MW" se do sloupce zalomí na dva řádky a panel s pevnou výškou přeteče. */
.gridLoadName {
    font-size: var(--fs-md);
    font-weight: bold;
    color: var(--text-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.gridReserves .line, .gridNextLoad .line { white-space: nowrap; }

/* Posledních pět připojených zátěží. `flex-wrap` a ne seznam pod sebou: řada je nekonečná, takže
   svislý seznam by panel s pevnou výškou rozjel - a přetečení v `overflow: hidden` není okem vidět. */
.gridLoadList { display: flex; flex-wrap: wrap; gap: 4px var(--space-3); margin-top: 2px; }
.gridLoadDone { font-size: var(--fs-xs); color: var(--text-muted); }
.gridLoadDone::before { content: '● '; color: var(--grid-color); }

/* Stav palivové rezervy. Prahy počítá reserveState() (render.js) z GRID_MIN_RESERVE_SECONDS, ne
   z těchhle tříd - barva se tak nemůže rozejít s pojistkou, která stavbu bloku skutečně zamítá. */
.gridRes--ok { color: var(--uranium-color); }
.gridRes--watch { color: var(--goldrush-color); }
.gridRes--low { color: var(--fuel-low-color); }
.gridRes--idle { color: var(--text-muted); }
.gridWarn { color: var(--goldrush-color); }
/* Hlášení podpalivé soustavy je od v6.29 v DOM VŽDYCKY a jen se přepíná `hidden` - jinak by se
   nedalo aktualizovat živě (přidání a odebrání řádku je změna struktury, ne hodnoty).
   `[hidden]` se proto musí vynutit autorským pravidlem: `.gridNextLoad .line` o pár řádků výš
   nastavuje `font-size`/`margin` a `.line` samo `display: block`, což PŘEBÍJÍ výchozí
   `[hidden] { display: none }` z prohlížeče - týž trap, jaký je popsaný u `.resReserve[hidden]`.
   Naměřeno: bez tohohle řádku držel skrytý řádek 20 px a záložka Soustava přetekla o 13 px,
   tedy tiše (overflow: hidden), přesně jak varuje docs/visual-baseline.md. */
.gridBrownout[hidden] { display: none; }
.gridLocked { color: var(--text-muted); font-style: italic; }

.gridBlocks {
    display: grid;
    /* Tři typy bloků, tedy nejvýš tři sloupce - `auto-FIT`, ne `auto-fill` (M3/v6.5, oprava
       naměřeného přetečení). Rozdíl je jen v prázdných stopách: `auto-fill` je na širokém okně
       vyrobí i tam, kde není co do nich dát, čímž se VŠECHNY sloupce stlačí na minimum 230 px -
       a v tak úzkém sloupci se nadpis karty („Biomasová kotelna 6 MW") zalomí na dva řádky.
       Karta tím povyroste o 26 px a tab s pevnou výškou přeteče.
       Naměřeno na „Velkém": při šířce okna 1920 px vyšlo 7 stop, nadpis se vešel na řádek a tab
       byl přesně na hraně (264 = 264); při 2047 px vyšlo 8 stop, nadpis se zalomil a tab přetekl
       na 278 > 264. Tedy čím širší monitor, tím hůř - přesně naopak, než by člověk čekal.
       `auto-fit` prázdné stopy sbalí, takže si tři karty rozdělí celou šířku a nadpis se vejde
       vždycky. Chování na úzkých obrazovkách zůstává stejné (pod 3× 230 px se karty zalomí). */
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: var(--space-4);
    align-content: start;
}

.gridActions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }

/* Užší svislé odsazení než u ostatních karet. Naměřeno: se sdíleným .upgradeCard paddingem
   (10px svisle) přetekl tab na „Malém" (1366×768) o 13 px. Vodorovné odsazení zůstává - to
   se do výšky nepromítá a užší text by kartu jen zhoršil. */
.gridBlockCard { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.gridBlockCard h4 { margin-bottom: var(--space-1); }

/* ---------- Pruh palivové rezervy v horní liště (M1-9, akt 2) ----------
   Sedí PŘESNĚ na místě `.resRate`, který v aktu 2 mizí - proto stejná výška řádku (--fs-sm × 1.4),
   ne vlastní rozměr. Horní lišta má pevnou výšku a už jednou přetekla (F1/v5.36), takže nový prvek
   se musí vejít do stopy toho starého, ne si vzít pixely navíc.
   Skrývání řeší `hidden` z JS (refreshResourceReserve), ne `display: none` v CSS - v aktu 1 tenhle
   prvek nikdy nedostane obsah ani rozměr.

   POZOR na pořadí kaskády (vizuální QA audit v6.9): `display: flex` níž PŘEBÍJÍ `[hidden]`, protože
   pravidlo `[hidden] { display: none }` pochází z výchozího stylu prohlížeče a autorský styl nad ním
   vyhrává vždycky. Do 6.9 tu proto věta o „nikdy nedostane rozměr" NEPLATILA: v aktu 1 zabíral
   skrytý pruh 22,4 px v každém pilíři a - protože `.resReserveFill` nemá v aktu 1 nastavenou šířku
   a `width` bloku je tedy 100 % - vykresloval se navíc jako PLNÝ pruh v uranové zelené. Hráč aktu 1
   tak pod každou surovinou viděl stoprocentní ukazatel palivové rezervy, kterou žádný blok
   nespotřebovává. Explicitní `[hidden]` pravidlo níž je to, co ten záměr teprve vynucuje. */
.resReserve {
    height: calc(var(--fs-sm) * 1.4);
    display: flex;
    align-items: center;
    gap: 6px;
}
.resReserve[hidden] { display: none; }
.resReserveTrack {
    /* `flex`, ne `width: 100%` (BL-03, v6.42): vedle pruhu teď stojí ještě číslo bilance, takže
       si pruh bere ZBYTEK řádku, ne celý. `min-width` je podlaha - kdyby bilance jednou vyrostla
       přes celý řádek (statisíce za sekundu), ustoupí pruh na 24 px a zkrátí se číslo, ne naopak.
       Pruh bez šířky je totiž k ničemu, kdežto zkrácené číslo je pořád čitelné jako řádová
       informace a přesné je v tooltipu pilíře. */
    flex: 1 1 auto;
    min-width: 24px;
    height: 5px;
    background: rgba(255, 255, 255, 0.10);
    border-radius: 3px;
    overflow: hidden;
}
.resReserveFill {
    height: 100%;
    border-radius: 3px;
    background: var(--uranium-color);
    transition: width 0.25s linear, background-color 0.25s linear;
}
/* Tři stavy, tytéž prahy jako u karet bloků - počítá je reserveState() (render.js) z
   GRID_MIN_RESERVE_SECONDS, takže se pruh nemůže rozejít s pojistkou, která stavbu zamítá. */
.resReserve[data-state="watch"] .resReserveFill { background: var(--goldrush-color); }
.resReserve[data-state="low"] .resReserveFill { background: var(--fuel-low-color); }

/* ---------- Bilance suroviny za sekundu (BL-03, v6.42) ----------
   Sedí na řádku pruhu rezervy, takže lišta nepřibrala ani pixel výšky. Zabírá místo, které
   v pilíři dosud leželo ladem vpravo od textového sloupce - naměřeno 64 px, a nejširší reálné
   číslo („+12 345,6/s") měří 78 px, takže se sloupec o kus roztáhne a pruh zůstane na dnešní
   šířce. `flex: 0 0 auto` je k tomu nutná polovina: číslo se smí zkrátit až tehdy, když by pruh
   spadl pod svou podlahu (viz `.resReserveTrack`).
   `tabular-nums`, protože se hodnota přepisuje každou sekundu - bez něj by číslo pod měnícími
   se číslicemi poskakovalo do stran. */
.resBalance {
    flex: 0 0 auto;
    font-size: var(--fs-sm);
    color: var(--uranium-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-variant-numeric: tabular-nums;
}
/* Ztráta je červená bez ohledu na to, jak plný je pruh vedle: rezerva „ještě čtyři hodiny" a
   „ubývá 57 za sekundu" jsou dvě různé zprávy a ta druhá nesmí zezelenat jen proto, že první
   je zatím v pohodě. */
.resBalance--loss { color: var(--fuel-low-color); }

/* ---------- Rozsvícená města a vedení soustavy (M1-8, akt 2) ----------
   Koncept F (výběr zadavatele 2026-09-03): zář + rozsvícená okna + přerušované vedení od
   Jesenické elektrárny. Vrstva leží POD #mapGrid, takže nesnižuje čitelnost 160 ikon surovin.
   V aktu 1 je celý #gridOverlay prázdný (buildGridOverlay ho naplní vždy, ale refreshGridOverlay
   nechá všechno `hidden`), takže dnešní hra vypadá přesně jako dřív. */
#gridOverlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.gridLines {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}
.gridLine {
    stroke: #ffe9b0;
    stroke-width: 2.5;
    stroke-dasharray: 7 5;
    opacity: 0.7;
    /* Linka se „nakreslí" od elektrárny k městu při připojení. Délka 2400 je nadhodnocená úhlopříčka
       mapy (1760×800 → 1933), takže pokryje i ten nejdelší tah bez počítání per město. */
    stroke-dashoffset: 0;
    animation: gridLineDraw 1.6s ease-out;
}
@keyframes gridLineDraw {
    from { stroke-dasharray: 0 2400; }
    to { stroke-dasharray: 7 5; }
}
.gridLine.hidden { display: none; }

/* Zář města. Poloměr nese `--glow-r` z `edgeRadius` (geoData.js), takže Praha září víc než Liberec -
   velikost je informace, ne dekorace. Dvě vrstvy (širší tlumená + užší sytější) dělají měkký spád
   bez `filter: blur()`, který na 160 ikonách stojí výkon. */
.cityGlow {
    position: absolute;
    width: calc(var(--glow-r) * 2);
    height: calc(var(--glow-r) * 2);
    transform: translate(-50%, -50%);
    border-radius: 50%;
    /* Sytost je ZMĚŘENÁ, ne odhadnutá: při 0,42 uprostřed byla zář na 1920×1080 („Střední")
       prakticky neviditelná - mapa je pestrá a 160 ikon přes ni. Zvednuto na 0,72 s tvrdším
       spádem, ověřeno v prohlížeči. Víc už ne: zář leží POD ikonami surovin a nesmí je přebít
       (mapa má 160 ikon, to je strop čitelnosti - §12). */
    background: radial-gradient(circle,
        rgba(255, 226, 150, 0.72) 0%,
        rgba(255, 221, 138, 0.46) 35%,
        rgba(255, 216, 120, 0.16) 62%,
        rgba(255, 216, 120, 0) 80%);
    animation: cityGlowIn 1.2s ease-out;
}
@keyframes cityGlowIn {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
    to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.cityGlow.hidden { display: none; }

/* Brownout je JEDINÝ vizuální projev podpalivé soustavy na mapě a je záměrně jemný: světla
   ztlumí, ale nezhasnou. Brownout nic neodebírá (§8.4), takže nesmí vypadat jako výpadek. */
.gridOverlay--brownout .cityGlow { opacity: 0.45; }
.gridOverlay--brownout .gridLine { opacity: 0.25; }

/* Omezený režim (reduced-motion): světla i vedení zůstanou, jen naskočí bez animace - je to
   informace o stavu hry, ne efekt, takže se vypnout nesmí. Stejný přístup jako u ostatních
   herních stavů ve hře. */
@media (prefers-reduced-motion: reduce) {
    .gridLine, .cityGlow { animation: none; }
}
body.reduced-motion .gridLine, body.reduced-motion .cityGlow { animation: none; }

/* Mini tlačítka „−" / „+" pro odstavení a spuštění bloku. Text je jediný znak, takže plná šířka
   tlačítka by kartu zbytečně roztáhla - a karta se musí vejít do panelu s pevnou výškou. */
.gridBtnMini {
    min-width: 30px;
    padding-left: var(--space-2);
    padding-right: var(--space-2);
}
/* Karty bloků nesou tooltip (gridBlockTooltipHtml), takže musí dát najevo, že se dá najet. */
.gridBlockCard { cursor: help; }

/* Řádky karty bloku se NIKDY nelámou. Je to tvrdé pravidlo, ne estetika: panel má pevnou výšku
   a cena reaktoru je víceměnová („76 050 uranu + 53 235 uhlí + 68 445 dřeva"), takže se na užší
   kartě zalomí na dva řádky a tab tiše přeteče. Useknutý text má plnou verzi v tooltipu karty
   (gridBlockTooltipHtml), takže se žádná informace neztrácí - jen se přesouvá o jeden hover dál. */
.gridBlockCard .line {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Tři palivové rezervy na jednom řádku, oddělené tečkou. `gap` místo textového oddělovače, aby
   se odstup dal ladit stejně jako ve zbytku UI. */
.gridReserveRow { display: flex; gap: var(--space-3); flex-wrap: nowrap; }
.gridReserveRow span + span::before { content: '· '; color: var(--text-muted); }

/* Teaser Ostravy ve vítězném modalu posledního průchodu aktu 1 (M1-0b, risk R12).
   Tyrkysový rám, tedy barva Soustavy - je to jediná věc v aktu 1, která na akt 2 ukazuje,
   a hráč ji má poznat, až tu barvu za pár minut uvidí na nové záložce. */
.goalChoiceOption--teaser {
    border: var(--border-w-1) solid color-mix(in srgb, var(--grid-color) 55%, transparent);
    background: color-mix(in srgb, var(--grid-color) 12%, transparent);
    border-radius: 6px;
    padding: 8px 10px;
    color: var(--text-1);
}

/* Ladicí přepínač fáze (M1-0c) - odlišený od herních voleb nad ním, ať je jasné, že to není
   nastavení hry, ale nástroj. Vidí ho jen hráč za branou aktu 2 (viz eraDebugToggleHtml). */
.toggleRow--debug {
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: var(--border-w-1) solid color-mix(in srgb, var(--text-muted) 30%, transparent);
    color: var(--text-muted);
}

/* ---------- Úroveň rozvoje revíru na mapě (M2-4, akt 2) ----------
   „Velmi tichý signál — sytost podkladu revíru, ne odznak" (§12 designu). Vrstva leží NEJNÍŽ ze
   všech (hned nad terénem), takže popisky, ikony ložisek i overlay soustavy jsou nad ní.
   `pointer-events: none` je tu podstatný: dohoda č. 2 zakazuje na mapě cokoli, co vyžaduje hover,
   a tahle vrstva by jinak brala kliky ložiskům pod kurzorem. */
#regionOverlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.regionTint {
    position: absolute;
    width: calc(var(--region-r) * 2);
    height: calc(var(--region-r) * 2);
    transform: translate(-50%, -50%);
    border-radius: 50%;
    /* Měkký spád bez `filter: blur()` — stejná technika jako u záře měst (M1-8). Blur přes
       třináct velkých kruhů by na mapě se 160 ikonami stál zbytečný výkon při každém překreslení. */
    background: radial-gradient(circle,
        color-mix(in srgb, var(--region-color) 100%, transparent) 0%,
        color-mix(in srgb, var(--region-color) 55%, transparent) 45%,
        transparent 78%);
    /* Sytost NASTAVUJE JS přímo do `style.opacity` (viz refreshRegionOverlay). Do v6.6 tu
       bylo `opacity: var(--region-alpha)` a přechod se po první změně nikdy nespustil znovu -
       neregistrovaná vlastní property přechod nespouští. Výchozí 0 tu zůstává pro prvek, kterému
       JS ještě hodnotu nedal. */
    opacity: 0;
    transition: opacity 0.4s ease;
}
.regionTint.hidden { display: none; }

/* ---------- Rozvoj revírů jako třetí větev v tabu suroviny (M2-2, akt 2) ----------
   Sedí uvnitř `.roadmapScroll`, tedy v jediné části panelu, která smí rolovat - proto se tady
   nemusí bojovat o pixely jako v tabu Soustava. Čipy jsou v jedné řadě a nelámou se: revírů je
   na surovinu nejvýš šest a vodorovné rolování roadmapy hráč už zná z obou větví nad nimi. */
.regionTrack {
    display: flex;
    gap: var(--space-3);
    align-items: stretch;
}
/* ---------- ČIP NA JEDEN ŘÁDEK (F17, v6.27) ----------
   Do 6.26 stály jméno, úroveň, cena a řádek tlačítek POD SEBOU, takže čip měřil 97/106/120 px
   podle úrovně zobrazení a sekce revírů 144/156/175 px. Do rolované oblasti se přitom vejde
   126/145/162 px - hlavní nákupní plocha aktu 2 tedy nebyla celá vidět ani na začátku rolování
   a byla useknutá o 18/11/13 px.
   Vodorovně měří čip 66/73/83 px (referenční okno 1800×880, tři tlačítka, rozvinuté revíry
   s tříměnovou cenou) a sekce 113/123/138 px, tedy se VEJDE CELÁ na všech třech úrovních.
   Přetečení celé záložky spadlo ze 145 na 105 px a zbytek nese posuvník tak, jak o něm rozhodla
   v6.12 - všechno najednou se do panelu vejít nedá (obsah 290 px, místo 145 px, a i nejtvrdší
   zhuštění končí na 232 px; zvětšit panel nejde, mapě zbývá nad její podlahou 430 px jen 80 px).

   ROZDĚLENÍ JE PEVNÉ, NE „JAK VYJDE": text na první řádek, tlačítka na druhý (`flex-basis: 100%`
   níž). První pokus nechal tlačítka zalomit se samy, když se nevejdou - a naměřeno se ukázalo, že
   o tom rozhoduje DÉLKA JMÉNA REVÍRU: „Šumava" a „Jeseníky" měly tlačítka vpravo, „Krkonoše"
   a „Vysočina" pod textem, tedy čtyři čipy vedle sebe ve dvou různých rozvrženích. Nabídka
   k porovnání musí vypadat stejně, jinak se porovnává rozvržení, ne revíry.
   Kdo se krátí první, když je i tak těsno: jméno nikdy (je krátké a je to identita čipu), pak
   cena, nejdřív úroveň - cena je jediné číslo, podle kterého se o nákupu rozhoduje. Plné znění
   nese tooltip čipu (v6.9) a aria-label tlačítek. */
.regionChip {
    display: flex;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    /* 96 px, ne 132: šest lesních revírů se dělí o třetinu šířky panelu (větev „Revíry" stojí
       vedle větví nástroje a pasivu). Naměřeno v prohlížeči - při 132 px utíkaly DVA čipy mimo
       obrazovku, a standardní kontrolní skript z docs/visual-baseline.md to NECHYTÍ, protože
       potomky roadmapScroll z kontroly vyjímá (kvůli jeho vlastnímu vodorovnému rolování).
       Cena se proto přesunula z tlačítek na vlastní řádek a tlačítka nesou jen sloveso. */
    /* Pružná šířka místo pevného minima znamená „vejdi se VŽDYCKY, ať je vás kolik chce".
       Pevné minimum to neumělo: na „Velkém" (větší písmo) utíkal poslední ze šesti lesních čipů
       mimo obrazovku, a kontrolní skript z visual-baseline.md to NECHYTÍ, protože potomky
       roadmapScroll z kontroly vyjímá (kvůli jeho vlastnímu vodorovnému rolování).
       Text se v úzkém čipu ořízne třemi tečkami; plné znění nese aria-label tlačítek. */
    flex: 1 1 0;
    min-width: 0;
    padding: var(--space-2) var(--space-3);
    text-align: left;
    border: var(--border-w-1) solid color-mix(in srgb, var(--res-color) 45%, transparent);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--res-color) 10%, transparent);
    color: var(--text-1);
    font: inherit;
    cursor: pointer;
    white-space: nowrap;
}
.regionChip:hover:not(:disabled) { background: color-mix(in srgb, var(--res-color) 20%, transparent); }
.regionChip:disabled { cursor: default; opacity: 0.55; }
.regionChipName,
.regionChipLevel,
.regionChipCost { overflow: hidden; text-overflow: ellipsis; }
.regionChipName { font-size: var(--fs-sm); font-weight: bold; flex: 0 0 auto; }
/* min-width: 0 je to, co dovolí elipsu misto zalomeni - bez nej se flex polozka nesmi zmensit
   pod svuj obsah a radek by se rozpadl (F17, v6.27). */
.regionChipLevel { flex: 1 2 auto; min-width: 0; }
.regionChipCost { flex: 0 1 auto; min-width: 0; }
/* Barva suroviny PROSVĚTLENÁ bílou (vizuální QA audit v6.9, nález P2-12). Čistá --res-color tu
   ležela na pozadí namíchaném z TÉŽE barvy (`.regionChip` má `color-mix(… 10%, transparent)`),
   takže si text konkuroval sám se sebou: naměřeno 4,31:1 na dřevu a 3,80:1 na uhlí. Příměs 30 %
   bílé drží barevné kódování revíru (pořád je na první pohled poznat, o kterou surovinu jde)
   a zvedne kontrast na 5,57 / 5,28 / 5,83:1. */
.regionChipLevel { font-size: var(--fs-xs); color: color-mix(in srgb, var(--res-color) 70%, #fff); }
.regionChipCost { font-size: var(--fs-xs); color: var(--text-2); }
/* Revír, na který hráč právě má - jediný vizuální rozdíl, žádná hvězdička ani odscrollování.
   Doporučovač v aktu 2 diagnostikuje, nepřikazuje (§11). */
.regionChip--available { border-color: color-mix(in srgb, var(--res-color) 85%, transparent); }
.regionChip--maxed .regionChipCost { color: var(--res-color); }

/* Dvě tlačítka v čipu revíru (M2-2 rozvoj, M2-3 četa) - vedle sebe, protože je to táž volba
   „kam investovat" a hráč je má porovnávat, ne hledat na dvou místech v UI. */
/* ZPÁTKY VEDLE SEBE (P0-2, v6.12). Pod sebe se tlačítka dostala proto, že větev „Revíry" dostávala
   třetinu šířky panelu (615 px), o kterou se dělilo až šest lesních revírů - na čip zbývalo ~88 px
   a popisky vedle sebe se ořízly na „R…"/„Č…". Jenže na výšku místo nebylo taky: čip potřeboval
   127 px a měl 104, takže se obě tlačítka smrskla na 10 px z potřebných 26 a text se přeříznul
   v půli písmen.
   Skládané rozvržení aktu 2 (viz .roadmapScroll--stacked) dalo větvi celý řádek, takže čip má nově
   ~300 px místo 88 - a v té šířce se tlačítka vedle sebe vejdou i s plnými popisky (změřeno na
   všech třech úrovních zobrazení). Tím se vrací i původní záměr: obojí je táž volba „kam
   investovat" a hráč je má porovnávat vedle sebe, ne pod sebou.
   `flex-wrap: wrap` je pojistka pro případ, že by revírů jedné suroviny někdy přibylo a čip se
   zúžil - radši se tlačítka zalomí pod sebe, než aby se zase ořízla. */
/* `margin-top` odešel s jednořádkovým čipem (F17, v6.27) - tlačítka stojí VEDLE textu, ne pod ním,
   a odsazení nahoře by na jednom řádku jen posunulo jejich těžiště proti popiskům. */
.regionChipButtons { display: flex; flex-direction: row; flex-wrap: wrap; gap: var(--space-1); flex: 0 0 100%; }
/* `flex: 1 1 auto`, ne `1 1 0` (P0-2, v6.12). Do 6.11 tu byla nula jako flex-basis - ve SLOUPCOVÉM
   kontejneru to znamenalo basis 0 na VÝŠCE, a protože `overflow: hidden` zároveň ruší automatické
   minimum, směla se tlačítka smrsknout na libovolnou výšku. Přesně tak vznikl nález P0-2: z 26 px
   jim zbylo 10 a popisek se přeříznul v půli písmen.
   V řádkovém uspořádání je basis `auto`, takže tlačítko vychází ze svého obsahu a dělí se jen
   o PŘEBYTEK místa - zmenšit pod text se nedá. Elipsa níž zůstává jako pojistka, ne jako běžný stav. */
.regionChipButtons .btn { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-left: var(--space-1); padding-right: var(--space-1); }

/* ---------- Dispečink (M2-7, akt 2) ----------
   Nahrazuje pruh „Další krok" v hlavičce roadmapy, nepřidává se k němu - je to táž pozice
   a týž komponent, jen jinak naplněný. Položky jsou v řádku a lámou se, protože jejich počet
   se mění podle stavu soustavy (rezerva · zátěž · dvě cesty). */
.dispatch { flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.dispatchItem { font-size: var(--fs-sm); color: var(--text-2); }
.dispatchItem b { color: var(--text-1); }
.dispatchReady { color: var(--uranium-color); }
/* „NEBO" mezi dvěma cestami je celý smysl dispečinku: dvě možnosti, obě vyčíslené, žádná
   označená hvězdičkou. Odlišuje se velikostí a tlumeností, ne barvou - není to varování. */
.dispatchOr { font-size: var(--fs-xs); color: var(--text-muted); letter-spacing: 0.06em; margin: 0 2px; }
