/*
 * cz-overrides.css — hand-authored cosmetic overrides for Combinezone v2
 * components that have no prop-level knob for the tweak we need. Linked by
 * base.html AFTER cz-v2-tokens.css + the vendored cz bundle so these rules win
 * the cascade. Keep this file tiny and each rule commented with the "why".
 */

/* ── 1. Drop the cz `Tag` accent diamond marker (◆) everywhere ──────────────
 * PO 2026-07-06: the little colored square/diamond that cz `Tag` prepends to
 * every label reads as noise on our catalog/label tables ("◆ в 1 шаг.",
 * "◆ COMMON", …). cz `Tag` renders it as a `.left_icon` slot whose 6×6 diamond
 * is a pure `::before` — so a Tag WITHOUT a real icon has an *empty* `.left_icon`
 * (no child element), while a Tag WITH a real `LeftIcon` puts an `<svg>` child
 * in that same slot. Hiding only the EMPTY slot removes the decorative marker
 * everywhere yet preserves every genuine leading icon. (The earlier per-surface
 * `shared/Chip` pill — added 2026-07-04 because cz exposes no prop to suppress
 * the marker — stays where it's used; this global rule just makes plain cz
 * `Tag`s diamond-free too, so no more markers survive anywhere.) */
.left_icon:empty {
  display: none !important;
}

/* ── 2. Narrow the catalog surface side panel default width ─────────────────
 * PO 2026-07-06: the shared "tree left + list right" catalog shell
 * (CatalogSurfaceLayout) rendered its cz `CollapsibleSidePanel` at the cz
 * `size="sm"` token = 40% of the row, leaving the (long-titled) data table
 * cramped. cz sizes are all percentages (xs 30% … xl 70%), none narrow enough,
 * and the component exposes no fixed-width prop. We pin a compact fixed default
 * by overriding the panel's own width custom property on its wrapper element
 * (the direct child that carries `data-is-opened` + `data-size`). Specificity
 * (0,3,1) beats cz's own `.OxuWG[data-is-opened][data-size="sm"]` (0,3,0), and
 * we deliberately use NO `!important` so the inline width cz writes while the
 * user DRAGS the resize handle still wins — this only sets the initial width. */
.catalog-surface-shell > div[data-is-opened][data-size] {
  --_collapsible-side-panel--width: 280px;
  --_collapsible-side-panel--min-width: 200px;
}

/* ── 3. Elevate cz's body-portal above app content everywhere ───────────────
 * soc-game tooltip defect (block soc-game-mobile-ux): `@combinezone/core`'s
 * `Portal` (used by `Tooltip`, and by `Overlay isFullscreen` — i.e. `Modal`/
 * `Drawer` — and anything else that floats via floating-ui) appends an
 * UNSTYLED `<div class="combinezone-portal">` straight to `document.body`
 * (`Portal.tsx`) with no `position`/`z-index` of its own. cz's OWN internal
 * z-index for that content is tiny by design — `var(--overlay-z-index)` = 1,
 * Tooltip's = `calc(var(--overlay-z-index) + var(--z-above))` = 2
 * (`@combinezone/theme` `basis/zIndexes.ts` + `components/{overlay,
 * tooltip}.ts`: base=0, above=1). Any page that gives a positioned ancestor
 * an EXPLICIT z-index (e.g. soc-game's command-center root `relative z-10`,
 * wrapping its own sticky bars at z-20/z-30) escapes to the SAME body-level
 * stacking context as this portal div and — being higher than the portal's
 * effective z-index — paints OVER it, regardless of DOM order. Measured live
 * on soc-game before this rule: a shop-price tap-tooltip rendered almost
 * entirely BEHIND its own product card, and the top-bar HUD tile's hover
 * tooltip lost its top ~10px under the sticky `CommandBar`.
 * `WizardShell.tsx`'s nav-footer comment documents the SAME class of bug
 * worked around LOCALLY (capping ITS OWN z-index at `z-[1]` so the DOM-order
 * tiebreak — portals always mount LAST — lets cz win); this rule fixes it at
 * the SOURCE for every island instead, so no host page has to reason about
 * it: give the portal wrapper itself an explicit z-index comfortably above
 * the highest z-index anywhere in the app today (the shell's own
 * `global-progress` bar, `tailwind-input.css`, = 100). `position: relative`
 * (no offsets) only ENABLES z-index on this otherwise-static div — it does
 * NOT change the containing block of a `position: fixed` descendant
 * (floating-ui's Tooltip/Overlay content still anchors to the viewport
 * exactly as before). Multiple simultaneously-open portals (e.g. a Tooltip
 * inside an open Modal) all get the SAME z-index here, so ties fall back to
 * DOM order — the later-opened (later-appended) one wins, which is always
 * the visually-innermost/topmost one; correct with no extra bookkeeping. */
.combinezone-portal {
  position: relative;
  z-index: 200;
}

/* ── 3a. Тост — выше оверлея модалки ────────────────────────────────────────
 * Известное ограничение §3, заведённое вместе с правилом в июле и закрытое
 * здесь: одно число выдаётся КАЖДОЙ портал-обёртке, а cz заводит отдельный
 * `div.combinezone-portal` на каждый портал. Одинаковый z-index у братьев даёт
 * каждому свой стековый контекст, и сквозная иерархия самой cz (её токены:
 * `--toast-z-index` выше `--overlay-z-index`) подменяется порядком в DOM.
 * Следствие: тост, показанный ДО открытия модалки, порталится РАНЬШЕ и уходит
 * под её оверлей — сообщение об ошибке студент не видит вовсе.
 *
 * Лечение — вернуть иерархию cz на уровне обёрток: обёртку с тостом поднять над
 * обёртками модалок/дроверов/тултипов. Обёртка ничем не помечена (`Portal.tsx`
 * ставит только класс), поэтому опознаём по содержимому — `.toast__container`
 * рисует cz `Toast`, а сам портал тостов существует, только пока есть хотя бы
 * один тост (`ToastProvider` монтирует `<Portal>` под `!!toasts.length`).
 *
 * Тост НЕ перехватывает клики за пределами своей плашки: поднимается обёртка,
 * а внутри неё cz-шный `ToastWrapper` остаётся `position: fixed` по своему углу.
 * Модальность оверлея сохраняется — гейт `tests/browser/
 * test_cz_portal_toast_layering.py` меряет обе стороны живым движком.
 *
 * Деградация на браузерах без `:has()` (Firefox < 121, Safari < 15.4) —
 * ровно сегодняшнее поведение, тост снова уйдёт под оверлей; хуже не станет. */
.combinezone-portal:has(.toast__container) {
  z-index: 300;
}

/* ── 4. cz `Modal` физически не помещается на узком экране ──────────────────
 * Дефект PO 2026-07-28 (публичная SOC-игра, «Правила» на телефоне): модалка
 * симметрично свисает за ОБА края — заголовок и кнопки срезаны, а крестик
 * закрытия оказывается за экраном и физически недоступен.
 *
 * Источник — сам cz: `ModalContainer` (node_modules/@combinezone/core/Modal/
 * Modal.tsx) задаёт `width: var(--_modal-width)` из size-токена и НЕ задаёт
 * `max-width` вовсе, плюс `margin: 40px`. Для `size="md"` это 720+80 = 800px
 * жёсткой ширины, которую оверлей центрирует, — то есть на КАЖДОМ экране уже
 * 800px контейнер обязан вылезти, и ни один размер cz-модалки от этого не
 * застрахован (lg/xl только шире). Пропа для потолка ширины у компонента нет.
 *
 * Правило намеренно БЕЗ медиазапроса: `100vw` уже делает его no-op везде, где
 * модалка и так помещается (на 1440px потолок 1360px, а модалка 720px — ничего
 * не меняется), поэтому оно чинит все size-токены разом и не требует угадывать
 * брейкпоинт. Медиазапрос ниже нужен только для полей: cz-шные 40px с каждой
 * стороны съедают половину экрана телефона, поэтому в мобильном диапазоне
 * (тот же порог 639px, что у `useIsMobile`) они ужимаются — и потолок ширины
 * считается от них же через общую переменную, чтобы эти два числа не разошлись
 * при будущей правке.
 *
 * Оба написания testid — потому что cz вешает на корень оверлея
 * `{testId}-overlaying-modal` через legacy-атрибут `data-test-id`, а часть
 * островов размечена `data-testid` (та же неконсистентность, что документирует
 * `tests/browser/soc_game_helpers._sel`).
 *
 * Регресс-гейт: `test_rules_modal_fits_viewport` (320/390/639) в
 * `orchestrator/tests/browser/test_soc_game_ux.py`. Обратите внимание, что
 * существующий `test_no_horizontal_scroll` этот класс дефектов НЕ ловит:
 * оверлей `position: fixed`, переполнение не увеличивает `scrollWidth`
 * документа — проверять надо прямоугольники, а не скролл. */
[data-testid$="-overlaying-modal"] .modal,
[data-test-id$="-overlaying-modal"] .modal {
  max-width: calc(100vw - 2 * var(--cz-modal-gutter, 40px));
}

@media (max-width: 639px) {
  [data-testid$="-overlaying-modal"] .modal,
  [data-test-id$="-overlaying-modal"] .modal {
    --cz-modal-gutter: 0.75rem;
    margin: var(--cz-modal-gutter);
  }
}

/* ── 5. Текст outline-бейджей cz не проходит по контрасту ───────────────────
 * Дефект PO 2026-07-28 («иконки разных цветов иногда не видно»). Замер на
 * проде (33 страницы, 190+ вхождений): cz `Badge` с `fill=outline` красит
 * подпись насыщенным примитивом ramp'ы поверх её же 96-го тинта —
 *   positive  #359c16 (green-35)  на #f4fded → 3.40:1
 *   warning   #e4740c (orange-47) на #fff3eb → 2.83:1
 *   attention #b38000 (yellow-35) на #fffaeb → 3.35:1
 * при кегле 12px, где WCAG AA требует 4.5:1. Это статусы «активен /
 * приостановлен / истекло» в каждой таблице портала — ровно тот текст,
 * который оператор читает бегло. `secondary` (#474885 на #eeeefc = 7.21:1)
 * и solid-заливки (белым по насыщенному фону) уже проходят — их не трогаем.
 *
 * Берём тёмные ступени той же ramp'ы, поэтому оттенок (зелёный/оранжевый/
 * жёлтый = смысл статуса) сохраняется, меняется только светлота:
 * 4.93 / 4.82 / 7.15:1.
 *
 * ── Почему переопределяем ПЕРЕМЕННУЮ cz, а не `color` ──────────────────────
 * Первая (задеплоенная) редакция правила задавала `color` самому бейджу и его
 * потомкам — и на проде НЕ сработала. Причина, снятая через CDP
 * `CSS.getMatchedStylesForNode` на живой странице: подпись лежит в отдельном
 * `<span>`, которому cz даёт СВОЁ правило `.dpaHBE .sc-Nxspf { color:
 * var(--_badge-color-text) }` — специфичность (0,2,0), ровно как у нашего
 * `[data-fill][data-accent] *`. При равной специфичности решает порядок, а
 * styled-components инжектит свой `<style>` в конец `<head>` уже после наших
 * `<link>` — то есть cz всегда последний и всегда выигрывает. Гонку за
 * `color` тут выиграть нечем, кроме `!important`.
 *
 * Поэтому правило меняет не цвет, а ИСТОЧНИК цвета: приватную переменную
 * `--_badge-color-text`, которую cz сам же и читает. На КОНТЕЙНЕРЕ бейджа
 * наша специфичность (0,2,0) выше cz-шной `.dpaHBE:where([data-accent=…])`
 * (0,1,0 — `:where()` обнуляет вложенное), поэтому порядок уже не важен.
 * Проверено на живом проде: переменная выигрывает даже когда наш `<style>`
 * вставлен ПЕРВЫМ элементом `<head>`.
 *
 * ── Почему свои имена токенов, а не `--text-positive` ──────────────────────
 * Вторая причина того же провала: `--text-positive` объявляем не только мы —
 * cz переопределяет его в рантайме. Замер на проде: в `cz-v2-tokens.css`
 * лежит `#2f7d23`, а `getComputedStyle(document.documentElement)` отдаёт
 * `#359c16`. Поэтому подпись красилась «в свой же исходный» цвет. Собственные
 * имена (`--text-*-strong`) с cz не пересекаются; объявлены в ОБЕИХ
 * генерациях темы (флаг LABA_CZ_V2_THEME_ENABLED), что гейтит
 * tests/unit/test_cz_overrides_tokens.py.
 *
 * Регресс-гейт: `test_badge_contrast_aa` в
 * `orchestrator/tests/browser/test_ux_guidelines_live.py` — он и поймал, что
 * первая редакция правила не работает на проде, хотя файл доехал. `--_badge-
 * color-text` — приватное имя cz и может смениться при bump библиотеки; этот
 * же live-гейт зафиксирует поломку (процедура bump — memory
 * `reference_combinezone_bump_procedure`). */
[data-fill="outline"][data-accent="positive"] {
  --_badge-color-text: var(--text-positive-strong);
}

[data-fill="outline"][data-accent="warning"] {
  --_badge-color-text: var(--text-warning-strong);
}

[data-fill="outline"][data-accent="attention"] {
  --_badge-color-text: var(--text-attention-strong);
}

/* `purple` вскрылся тем же live-гейтом уже ПОСЛЕ деплоя первых трёх: 4.06:1
   (#a144ee = purple-60 на #f4effa). Это бейджи ролей в таблице пользователей —
   на /labs и /assignments таких бейджей нет, поэтому в первый замер оттенок
   не попал. Дефект тот же, лечится так же. */
[data-fill="outline"][data-accent="purple"] {
  --_badge-color-text: var(--text-purple-strong);
}

/* ── 6. Страница с боковой колонкой: канон ширины, а не доли ────────────────
 * Дефект PO 2026-07-30 (скрин /my/assignments/<id>): на «узком» окне карточки
 * правой колонки (стенд / учётные данные / проверки) ломались по буквам
 * («Подкл ючени е к катало гу») и выпирали за края карточек.
 *
 * Корень — арифметика, а не конкретная карточка. Такие страницы включали
 * двухколоночный режим по брейкпоинту ширины ОКНА (`lg` = 1024px) и раздавали
 * боковой колонке ДОЛЮ (`lg:grid-cols-4` + `lg:col-span-1` = 1/4). Но окно —
 * не контент: шелл-навигация (`_shell_chrome.html`, `w-[280px]`) забирает
 * 280px ещё до страницы. Живой замер (прод, 2026-07-30):
 *
 *     окно 1024 → боковая колонка 140px   окно 1280 → 204px
 *     окно 1120 → 164px                   окно 1440 → 244px
 *
 * то есть 1/4 НИКОГДА не давала колонке нужных ~300px, а до 1280px было вдвое
 * меньше — отсюда внутренний overflow до 56px на карточках «Обучение»/«Стенд».
 *
 * Канон: боковая колонка получает ФИКСИРОВАННЫЕ 320px, а двухколоночный режим
 * включается только когда контента реально хватает на обе колонки — при 1280px
 * окна это 1280−280 (навигация) −48 (поля) = 952 = 608 (main) + 24 + 320.
 * Ниже — одна колонка, боковая идёт первой (контекст стенда выше инструкции).
 * `minmax(0,1fr)` у main обязателен: без него длинная строка (DN, URL, лог)
 * распирает колонку и снова выталкивает боковую за край.
 *
 * Класс, а не набор utility-классов, — сознательно: правило живёт в одном
 * месте и одинаково применимо и к Jinja-шаблонам, и к React-островам; менять
 * ширину навигации/колонки придётся ровно здесь. Гейты: browser
 * `test_aside_layout_narrow.py` (живой замер), vitest/pytest — контракт классов.
 */
.laba-aside-layout {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.laba-aside-layout > .laba-aside {
  order: -1;
}

/* Боковая колонка не всегда «контекст»: там, где в ней завершающие действия
 * (sandbox — «Сохранить как пресет» после захвата), поднимать её над основной
 * колонкой на узком экране неверно. Модификатор оставляет колонку на месте,
 * но она остаётся ЯВНО помеченной как боковая — иначе её попадание во вторую
 * колонку держится только на порядке следования в разметке. */
.laba-aside-layout > .laba-aside.laba-aside--trailing {
  order: 0;
}

@media (min-width: 1280px) {
  .laba-aside-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--laba-aside-width, 320px);
    align-items: start;
    gap: 1.5rem;
  }

  .laba-aside-layout > .laba-aside {
    order: 0;
  }

  /* The student assignment sidebar contains interactive checks, including
   * matching tables and ordered selects. 320px is enough for read-only meta,
   * but not for these forms. Let this page use more of a wide viewport while
   * keeping a useful main instruction column at the lower desktop boundary. */
  .laba-aside-layout.laba-assignment-layout {
    grid-template-columns: minmax(0, 1fr) clamp(435px, 34vw, 560px);
  }
}

/* ── 7. SOC-game outline-badge text on the DARK generation ──────────────────
 * Legibility audit regression (2026-08-06): rule №5 above fixes cz `Badge
 * fill="outline"` text for the light theme's portal tables by pointing
 * `--_badge-color-text` at `--text-positive-strong`/`--text-attention-strong`.
 * The SOC-game island (`#soc-game-root`, `CzProvider themeType="dark"`)
 * renders the SAME markup against a dark outline fill, where those SAME
 * tokens (tuned for the light tint) measure 3.20:1 / 1.88:1 — well under AA
 * (audit findings: `game-tactic-*` tiles, `ui.game.debrief.contained`
 * "Обнаружено"). `critical`/`secondary` already clear AA on dark (5.74:1 /
 * 11.44:1 measured live), so only these two need a twin.
 *
 * Deliberately NOT a competing `[data-accent=…]` selector (that was the
 * first draft, reverted): `test_cz_overrides_tokens.py`'s
 * `test_rule5_gives_every_low_contrast_accent_an_aa_colour` replays cascade
 * priority across every rule in this file whose selector carries a
 * `[data-accent="…"]` marker — a second, more-specific selector for the same
 * accent would win that replay and get checked against the LIGHT tint
 * (`--green-96` etc.), the wrong surface for a dark-tuned colour. Overriding
 * the TOKEN instead of adding a competing rule sidesteps that entirely: rule
 * №5 stays byte-identical (still reads `var(--text-positive-strong)`), and
 * CSS custom-property scoping does the rest — inside `#soc-game-root` that
 * name itself resolves to the dark-tuned value below; every other consumer
 * (portal tables) keeps the `:root` value from `cz-v2-tokens.css` untouched.
 * Fallback hex on the `var()` keeps `test_every_override_var_is_defined_in_
 * theme` satisfied without adding a legacy-generation (flag-OFF) twin for a
 * token only the always-v2 game island consumes. */
/* ── 7a. SOC-game secondary/critical text — legibility audit 2026-08-12 ──────
 * UX-стоп (severity HIGH, «слабая читаемость... недостаточный контраст»):
 * три конкретных находки — абзацы-пояснения правой панели (`Text
 * variant="secondary"`, `SecurityPosturePanel.tsx`'s `weakSpotsHint`/
 * `earlyGameNote`), подписи «средства нет» в плитках тактик (та же
 * `variant="secondary"`) и красные бейджи «Пока без прикрытия»
 * (`PrepDebriefPanel.tsx`, `Badge accent="critical"` без явного `fill` —
 * v2-дефолт `outline`). Замер (та же WCAG-формула, что rule №7 выше и
 * `test_cz_overrides_tokens.py`): `--text-neutral-medium`/`--text-critical`
 * УЖЕ проходят AA под дефолтной дарк-генерацией (7.5:1 и 5.74:1
 * соответственно — см. полный разбор в `cz-v2-tokens.css`, рядом с
 * `--text-neutral-medium-dark`), но с малым запасом для мелкого/несущего
 * тревогу текста — отсюда жалоба «тускло» при формальном проходе. Тот же
 * приём, что rule №7: переопределяем ТОКЕН, который cz читает сам
 * (`--text-neutral-medium`/`--text-neutral-soft`/`--text-critical`), а не
 * добавляем конкурирующий селектор — `#soc-game-root` (ID, специфичность
 * 1,0,0) побеждает cz-шный `:root`-блок (специфичность 0,1,0) НЕЗАВИСИМО от
 * порядка вставки стилей, так что здесь, в отличие от rule №5, обходной
 * `--*-strong`-слой не нужен: это прямые имена cz. */
#soc-game-root {
  --text-positive-strong: var(--text-positive-strong-dark, #3c9c2e);
  --text-attention-strong: var(--text-attention-strong-dark, #efc45d);
  --text-neutral-medium: var(--text-neutral-medium-dark, #c4c6d4);
  --text-neutral-soft: var(--text-neutral-soft-dark, #adafc2);
  --text-critical: var(--text-critical-dark, #f18e98);
}

/* U3/R-2 is gated separately from the already-shipped visual-refresh flag:
 * OFF must keep its previously invisible legacy glow byte-for-byte. */
#soc-game-root[data-visual-unification-enabled="1"] {
  isolation: isolate;
}

/* Codex gate-2 r1 MAJOR-1 fix (block soc-game-feedback-v18-visual-refresh,
 * MR-A): the v18 scene background used to be an unconditional rule on bare
 * `#soc-game-root`, so flag OFF still painted the NEW gradient underneath
 * the legacy `ProgressiveBackdrop` raster instead of restoring the OLD
 * gradient — AC-14 rollback broken. Both rules below now key off the same
 * `data-visual-refresh-v18-enabled` DOM attribute `game/main.tsx` already
 * writes onto `#soc-game-root` (`soc_game_public.html:145` emits it
 * server-side unconditionally, `"1"`/`"0"`, never absent) — so the attribute
 * selector is authoritative for BOTH states and there is no third,
 * attribute-absent case in production. Attribute selector (`[data-…]`,
 * specificity 0,1,0) is combined with the `#soc-game-root` ID selector
 * (1,0,0 total) so both rules keep outranking cz's own `:root` block
 * regardless of stylesheet order, same reasoning as rule №7/7a above. */
#soc-game-root[data-visual-refresh-v18-enabled="1"] {
  /* v18 visual refresh (spec §5, D1): растровые backdrop'ы сняты, сцена —
   * точный multi-stop фон Figma row09: black → blue focus → black.
   * Радиальные глоу-пятна рисует `SceneBackground` (fixed-слой) — им нужен
   * вьюпортный контекст, которого у background-attachment здесь нет. */
  background-color: var(--gray-0, #000000);
  background-image: linear-gradient(
    180deg,
    var(--gray-0, #000000) 0%,
    var(--gray-0, #000000) 20%,
    var(--soc-game-bg-gradient-mid) 32%,
    var(--gray-0, #000000) 56%,
    var(--gray-0, #000000) 81%,
    var(--gray-0, #000000) 100%
  );
  background-attachment: fixed;
}

/* Rollback R-1 twin: flag OFF restores the PRE-v18 rendering byte-for-byte —
 * the two radial glows + linear diagonal that `SceneBackground`/this rule
 * replaced (see `cb29aaf8aa`'s removed declaration). `App.tsx`'s
 * `!visualRefreshV18Enabled` branches bring the raster `ProgressiveBackdrop`
 * layers back on top of this same gradient, exactly as before v18 shipped. */
#soc-game-root[data-visual-refresh-v18-enabled="0"] {
  background-color: var(--blue-10, #0d1026);
  background-image:
    radial-gradient(ellipse at 86% 8%, color-mix(in srgb, var(--brand-blue, #163bff) 62%, transparent), transparent 54%),
    radial-gradient(ellipse at 4% 82%, color-mix(in srgb, var(--blue-50, #2658d9) 34%, transparent), transparent 46%),
    linear-gradient(118deg, color-mix(in srgb, var(--blue-10, #0d1026) 46%, black), var(--blue-10, #0d1026) 44%, color-mix(in srgb, var(--brand-blue, #163bff) 30%, var(--blue-10, #0d1026)));
  background-attachment: fixed;
}

/* ── 8. SOC-game surface level L2 (cz `Block`/`Card2`) ───────────────────────
 * spec `2026-08-06-soc-game-card-table-design.md` §2.6/§2а «серый везде».
 * `ui/surfaceLevels.ts` defines a 4-step DARK-BLUE elevation scale (L0 scene
 * `bg-cz-blue-10` … L3 raised `bg-cz-blue-18`), used everywhere the game
 * paints its OWN backgrounds (`CommandBar`, `MobileTabBar`, …) — everywhere
 * EXCEPT the L2 "card" step, which `GamePanel` (cz `Block`) and `GameCard`
 * (cz `Card2`) used to leave to `v2dark` itself. That was the bug: `Block`'s
 * `--block-background-color` and `Card2`'s `--card2-colors-background-
 * default` resolve (`@combinezone/theme` `default/ComponentsTokensGlobal
 * Style.ts`) to `--dark-glass-88` / a semi-transparent neutral glass tint —
 * a generic vendor grey with NO relationship to the game's blue scene
 * (`SURFACE_L0_SCENE`). Every panel/card in the game (briefing, shop shelf,
 * wave table) read as flat grey floating on blue — PO: «серый везде».
 * `Card2`'s DEFAULT border has the same problem one token over: its shape
 * wrapper hardcodes `--_border-color-default: var(--border-neutral-soft)`
 * (`Card/Card2.tsx`), the SAME shared "divider" token portal tables use for
 * the LIGHT theme.
 *
 * Fix: repaint the token, not the component — same technique as rule №7.
 * `--block-background-color`/`--block-border-color` are `Block`'s OWN public
 * tokens (only `Block`/`WidgetBlock` read them; `WidgetBlock` isn't used
 * anywhere in the game island, so this is Block-exclusive here).
 * `--card2-colors-background-default` is likewise `Card2`-exclusive (single
 * consumer: `Card/Card2.tsx`). `--border-neutral-soft` is shared more
 * widely, but audited: inside `#soc-game-root` its only OTHER readers are
 * `Button`'s DISABLED-state border and `Progressbar`'s track fill (neither
 * `WidgetBlock`/`Avatar`/`Drawer`/`ActionBar`/`CodeSnippet`/`FilesBox`/
 * `Select` ship in this island) — both already read as muted chrome, and
 * tinting them to match the game's blue family is a strict improvement, not
 * a regression. The round-4 colour audit deliberately leaves a larger
 * luminance/chroma interval between scene and content than the old adjacent
 * `blue-14/16` pairing: L2 is `--blue-20`, its frame is `--blue-40`, while
 * the scene remains near-black `--blue-10` with a local brand-blue glow.
 * This follows the BI.ZONE Bug Bounty reference's hierarchy (black base,
 * saturated blue light, clearly separated section) without copying its art.
 * Fallback hex (rule №7's convention): `#soc-game-root` is the
 * always-v2-dark game island, so there is no legacy-generation twin to keep
 * in sync — the fallback alone satisfies `test_every_override_var_is_
 * defined_in_theme` without inventing one. */
#soc-game-root {
  --block-background-color: var(--blue-20, #1c254a);
  --block-border-color: var(--blue-40, #2442a8);
  --card2-colors-background-default: var(--blue-20, #1c254a);
  --border-neutral-soft: var(--blue-40, #2442a8);
}

/* CzProvider declares its dark-generation component tokens on an inner theme
 * node, so inheritance from the island root alone is not sufficient: that
 * nearer declaration wins. The shared GamePanel/GameCard wrappers put this
 * stable class directly on cz's Block/Card2 shell; repeating the four tokens
 * there makes the intended L2 paint the actual computed paint. */
#soc-game-root .soc-game-l2-surface {
  --block-background-color: color-mix(in srgb, var(--blue-40, #2442a8) 28%, var(--blue-14, #121735));
  --block-border-color: var(--blue-24, #1f2b57);
  --block-content-padding-sm: 1rem 1.25rem;
  --block-content-padding-md: 1.25rem;
  --block-min-height: auto;
  --card2-colors-background-default: color-mix(in srgb, var(--blue-40, #2442a8) 28%, var(--blue-14, #121735));
  --border-neutral-soft: var(--blue-24, #1f2b57);
}

/* Figma row10 checkout plank: translucent #313363 at 30%, with its
 * #405a96 outline supplied by the positioning wrapper. Override the shared
 * opaque L2 Block only on this single visual-unification surface. */
#soc-game-root[data-visual-unification-enabled="1"]
  .soc-game-shop-checkout-surface
  .soc-game-l2-surface {
  --block-background-color: var(
    --soc-game-cta-plank-fill,
    rgb(49 51 99 / 30%)
  );
}

/* Visual-unification U2: cz Text emits its own styled-components colour
 * declaration after Tailwind has loaded.  A single utility class therefore
 * loses on equal specificity and the attack heading falls back to the modal's
 * neutral ink.  Modal content is portalled under <body>, outside
 * #soc-game-root, so an island-descendant selector cannot reach it.  Repeating
 * the explicit ON-only semantic class raises specificity to 0,2,0 and wins
 * over Text's generated 0,1,0 class without !important; flag OFF never renders
 * this class and stays byte-compatible. */
.soc-game-shop-attack-heading.soc-game-shop-attack-heading {
  color: var(--soc-game-status-defeat);
}

/* A shelf and a product card are different hierarchy levels. They used the
 * same L2 paint after round 4, so a card visually dissolved into its tier
 * panel even though its geometry was correct. Keep this contrast scoped to
 * product cards: wave outcomes continue to use their single semantic badge
 * without reintroducing coloured nested outlines. This base rule is
 * UNSCOPED (applies whether `visualRefreshV18Enabled` is ON or OFF — the
 * class itself is unconditional, `ShopProductCard.tsx`/`PrepDebriefPanel.tsx`
 * always render it) — kept at the ORIGINAL round-4 14% mix so OFF stays
 * byte-identical to pre-Phase-B. The v18-only bump lives in the
 * flag-scoped rule below. */
#soc-game-root .soc-game-product-card {
  --card2-colors-background-default: color-mix(in srgb, var(--blue-50, #2658d9) 14%, var(--blue-10, #0d1128));
  --border-neutral-soft: var(--blue-40, #2442a8);
}

/* Task B3 (v18 visual refresh, spec D4): round 4's fixed contrast target was
 * the tier PANEL (`.soc-game-l2-surface`, `--blue-40 28%`), not the raw
 * scene. Phase B (Task B2) removes that panel entirely for the per-tier
 * shelf ONLY WHEN THE FLAG IS ON — cards then sit directly on the scene
 * gradient (§7/D1). Under OFF the panel is still there (Task B2), so the
 * round-4 14%-vs-panel pairing above remains correct and must not shift.
 *
 * Codex gate-2 r2 (2026-08-17): the neighbour this rule was calibrated
 * against was the WRONG COLOUR. Both the browser oracle and its static twin
 * compared the card to `#soc-game-root`'s `background-color` — i.e. the
 * `--gray-0` FALLBACK under the gradient, a colour visible on screen only at
 * the very top edge of the viewport. The rule above (§7) paints the scene
 * with `background-image: linear-gradient(180deg, --gray-0 → --soc-game-bg-
 * gradient-mid)` at `background-attachment: fixed`, so the actual neighbour
 * of a card depends on where in the VIEWPORT it sits, and at the bottom edge
 * it is the full `--soc-game-bg-gradient-mid` (#010334) — measurably
 * lighter. The old 18% mix scored 1.289 against black but only ≈1.212
 * against that bottom stop: below the 1.25 round-4 threshold, i.e. a card
 * genuinely stopped separating in the lower part of the screen while both
 * oracles reported headroom (measured on the local scene stand, 2026-08-17:
 * a card scrolled to the bottom of the shop read ratio 1.240).
 *
 * 24% is calibrated against the WORST case — the gradient's end stop, not
 * its start: 1.285 against `--soc-game-bg-gradient-mid` and 1.367 against
 * `--gray-0`. U3 now deliberately makes `SceneBackground` contribute pixels
 * by isolating the root above; its branch browser oracle rechecks the actual
 * composite against the phase-B contrast gate before merge.
 *
 * Gated on `#soc-game-root[data-visual-refresh-v18-enabled="1"]` — the
 * server-rendered attribute (`soc_game_public.html:145`) that `main.tsx`
 * also reads to build the React prop, persists live in the DOM after
 * hydration (React only manages the root's CHILDREN, never touches
 * attributes on the mount node itself) — same gating mechanism MR-A's gate-2
 * round established for v18 CSS rules, not invented fresh here. */
#soc-game-root[data-visual-refresh-v18-enabled="1"] .soc-game-product-card {
  --card2-colors-background-default: color-mix(in srgb, var(--blue-50, #2658d9) 24%, var(--blue-10, #0d1128));
}

/* ── 9a. Premium-заливка BI.ZONE-карточки (Task B4, spec D5/AC-6) ──────────
 * Codex gate-2 r1 (F1): Task B4 вешал градиент Tailwind-классами на
 * `className` карточки, а cz `Card2` кладёт caller-`className` на ВНЕШНИЙ
 * `Card2ShapeContainer` (`node_modules/@combinezone/core/Card/Card2.tsx:258`).
 * Видимую грань красит его ВЛОЖЕННЫЙ `Card2Container` — `background:
 * var(--_card2-colors-background)` (там же, `:144`), непрозрачный, поверх
 * фона обёртки. Градиент существовал в DOM и не был виден ни на одном
 * пикселе: premium-карточки оставались на той же плоской заливке, что и
 * прочие вендоры.
 *
 * Красим САМ крашеный узел. Он адресуется своим `data-test-id` — cz ставит
 * его на `Card2Container` из обязательного пропа `testId` (`Card2.tsx:281`),
 * это единственный прямой потомок `.soc-game-product-card` с этим атрибутом
 * (второй возможный — `Card2Indicator` — атрибутов не несёт). Тот же якорь
 * уже используется рядом в `ShopProductCard.tsx`
 * (`[&>div[data-test-id]]:flex-col`), новой зависимости от вёрстки cz здесь
 * не заводится.
 *
 * Почему `background-image`, а не переопределение публичного токена
 * `--card2-colors-background-default` (как в правиле §9 выше): cz
 * ПЕРЕОПРЕДЕЛЯЕТ приватную `--_card2-colors-background` в каждом
 * интерактивном состоянии — hover/focus-visible/`[data-is-selected]`/active
 * (`Card2.tsx:113-127`). Через токен градиент жил бы ровно в состоянии
 * покоя и слетал на плоский тинт при наведении и на всё время, пока
 * карточка лежит в корзине (`isSelected`) — то есть именно тогда, когда
 * игрок на неё смотрит. Longhand `background-image` живёт в отдельном от
 * shorthand'а cz слое каскада (наша специфичность 1,3,1 против 0,1,0 у
 * styled-components — выигрыш не зависит от порядка вставки стилей), поэтому
 * градиент держится во ВСЕХ состояниях, а цвет состояния остаётся под ним в
 * `background-color`. Оба токена — v18-палитра (`cz-v2-tokens.css`), без
 * тематических hex.
 *
 * Гейт флага — тот же `data-visual-refresh-v18-enabled="1"`, что у §9 и
 * rule №7b: при OFF (rollback R-1) BI.ZONE-карточка обязана быть
 * неотличима от pre-v18. */
#soc-game-root[data-visual-refresh-v18-enabled="1"] .soc-game-product-card-premium > [data-test-id] {
  background-image: linear-gradient(
    180deg,
    var(--soc-game-premium-gradient-from) 0%,
    var(--soc-game-premium-gradient-to) 100%
  );
}

/* The shop audit clicks a primary "Buy" button and captures the resulting
 * secondary "Remove from cart" state immediately. Combinezone transitions
 * only the button background for 200ms while replacing the text colour in
 * the same frame, briefly producing gray-85 on blue-75 (1.57:1). This is a
 * real paint state, not an audit artefact: slow frames and reduced-motion
 * users can see it. The state change must be atomic, so this one toggle keeps
 * cz's border/box-shadow transitions but does not interpolate its background.
 * Hover/focus/pressed colours still come from the regular secondary tokens. */
#soc-game-root .soc-game-cart-remove {
  transition-property: border, box-shadow;
}

/* Intro and debrief cards carry content, not another section. A darker,
 * opaque face keeps them distinct from the blue L2 panel; semantic meaning
 * remains on the Card2 indicator instead of tinting the entire card. */
#soc-game-root .soc-game-intro-tile,
#soc-game-root .soc-game-metric-card {
  --card2-colors-background-default: var(--blue-12, #10152f);
  --border-neutral-soft: var(--blue-30, #203477);
}

/* ── 9. SOC-game text scale ─────────────────────────────────────────────────
 * Product feedback round 3: the island was the only portal surface without a
 * scoped type scale. Tailwind rem utilities inherited the browser's 16px root,
 * while cz text primitives use fixed pixel tokens, so changing either side
 * alone left half the screen unchanged. Keep the override on the island root:
 * rem-based copy grows by one small step and the cz Text/Label/Title families
 * receive the matching next size and line-height. Portal pages outside the
 * island retain the vendor defaults. */
#soc-game-root {
  font-size: 112.5%;

  --text-sizes-xs-font-size: 12px;
  --text-sizes-xs-line-height: 18px;
  --text-sizes-sm-font-size: 14px;
  --text-sizes-sm-line-height: 20px;
  --text-sizes-md-font-size: 16px;
  --text-sizes-md-line-height: 22px;
  --text-sizes-lg-font-size: 18px;
  --text-sizes-lg-line-height: 24px;

  --label-sizes-sm-font-size: 12px;
  --label-sizes-sm-line-height: 17px;
  --label-sizes-md-font-size: 14px;
  --label-sizes-md-line-height: 19px;
  --label-sizes-lg-font-size: 16px;
  --label-sizes-lg-line-height: 22px;

  --title-sizes-sm-font-size: 16px;
  --title-sizes-sm-line-height: 21px;
  --title-sizes-md-font-size: 20px;
  --title-sizes-md-line-height: 24px;
  --title-sizes-lg-font-size: 24px;
  --title-sizes-lg-line-height: 29px;
}

/* ── 10. SOC-game mobile cart/checkout density ─────────────────────────────
 * Live E2E after round 3 exposed the vendor Block padding as the dominant
 * mobile chrome: `sm` resolved to 32px 24px 72px and `md` to 32px 160px
 * 72px. The checkout alone became 197px tall; the empty cart's md content
 * also overflowed a 320px viewport by 12px. These two blocks are sticky
 * transaction chrome, not reading surfaces, so keep their vendor shape and
 * typography while scoping compact content padding to the game only. */
#soc-game-root [data-testid="game-shop-checkout"] {
  --block-content-padding-sm: 0.5rem 0.75rem;
}

#soc-game-root [data-testid="game-shop-cart-panel"] {
  --block-content-padding-md: 0.5rem 0.75rem;
}

#soc-game-root [data-testid="game-shop-cart-panel"],
#soc-game-root [data-testid="game-shop-checkout"] {
  --block-border-color: var(--blue-20, #1a2247);
}

/* The vendor `md` Block padding resolves to 32px 160px 72px. That is a
 * desktop reading-layout preset, not a viable mobile briefing inset: at a
 * 390px viewport it leaves a 46px content track and wraps every word by
 * letters. Keep the cz Block/shape while giving the briefing a game-scoped,
 * responsive-safe content inset. */
#soc-game-root [data-testid="game-briefing"] {
  --block-content-padding-md: 1rem 1.25rem;
}

@keyframes soc-game-cart-row-in {
  from {
    opacity: 0;
    transform: translateY(0.5rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

#soc-game-root .game-cart-row-in {
  animation: soc-game-cart-row-in 240ms ease-out both;
}

@media (prefers-reduced-motion: reduce) {
  #soc-game-root .game-cart-row-in {
    animation: none;
  }
}

/* ── 11. Pagination page-size Select clips "50" to "5" ──────────────────────
 * UX audit finding: the cz `Pagination` composite's page-size dropdown
 * (`@combinezone/core/Pagination/Pagination.tsx`) renders a plain cz `Select`
 * with NO width prop of its own — like the catalog side-panel in rule №2, the
 * component exposes nothing to size it, so its trigger just shrinks to
 * whatever the flex row around it happens to give it. At the default `sm`
 * size that collapsed narrower than a 2-3 digit value needs, and the trigger
 * text-overflow:ellipsis then clipped "50"/"100" down to "5"/"1" — unreadable
 * page-size, no visual cue that the count was wrong.
 *
 * Both PortalTable (`shared/table/PortalTable.tsx`) and the shared offset
 * pager (`features/shared/OffsetPagination.tsx`) render the SAME cz
 * `Pagination` composite under the hood, so a single selector fixed to its
 * generated testId pattern (`useTestId.ts`: the page-size Select's testId is
 * always `${paginationTestId}-limit`, and cz Select's own SelectInput.tsx
 * appends `-select-input` to whatever testId it's given) covers every portal
 * pager without touching either component — no per-table wiring to keep in
 * sync, and future pagers pick it up for free.
 *
 * min-width sized for "100" (the widest value in PortalTable's default
 * pageSizesList) at the `size="sm"` control height, with slack for the
 * dropdown-arrow icon that already eats into the trigger's right padding.
 */
[data-test-id$="-limit-select-input"] {
  min-width: 72px;
}

/* ── 12. SOC-game CTA typography (v18 visual refresh, spec D2, Task A6) ─────
 * Дизайнер: «в кнопках тоже одинаковый шрифт». Живая статическая разведка
 * (Task A2, `docs/superpowers/plans/_discovery/2026-08-16-visual-refresh-
 * phase-a.md`) показала, что семейство у всех CTA одно (`Golos Text` из
 * Tailwind preflight, `html { font-family }` + cz-компоненты повторяют его
 * явно через `--font-family-default`), а расходится ВЕС: `BrandCtaButton`
 * несёт `font-semibold` (600), share-ссылка `FinalScreen` — `font-medium`
 * (500), cz `Button` — 500 из темы. Выравниваем в одном месте, вместо того
 * чтобы заводить третий button-паттерн или править шесть вызовов по одному.
 * `#soc-game-root` (ID, специфичность 1,0,0) побеждает и cz-шный `:root`, и
 * Tailwind-утилиты классов, независимо от порядка вставки стилей.
 * `font-family: inherit` не меняет реальное значение (уже Golos Text
 * везде) — держит правило готовым на случай, если A2's вывод про семейство
 * когда-нибудь окажется неверным для нового CTA-паттерна.
 *
 * Codex gate-2 r1 MAJOR-1 fix: this rule used to fire unconditionally, so
 * flag OFF still unified every CTA to 600 instead of restoring the
 * pre-v18 mixed 500/600 rendering (AC-14 rollback broken, same class of bug
 * as rule №7b's background above). Gated behind the same
 * `data-visual-refresh-v18-enabled="1"` attribute selector — no OFF twin is
 * needed here (unlike the background): removing the override just lets
 * `BrandCtaButton`/cz `Button`/the share link fall back to their own
 * pre-existing weights, exactly as before this task shipped. */
#soc-game-root[data-visual-refresh-v18-enabled="1"] button,
#soc-game-root[data-visual-refresh-v18-enabled="1"] a[data-testid="game-final-share"] {
  font-family: inherit;
  font-weight: 600;
}
