/* mobile-first: это не «адаптация», а основная конструкция — ~95 % трафика
   мобильный (§4.2). Десктопная раскладка — в самом низу, медиазапросом. */

/* Псевдонимы, а не Comfortaa/Rubik: OFL резервирует имена шрифтов за
   правообладателем (Reserved Font Name), и раздавать файлы под ними мы не
   вправе. Псевдоним в CSS под запрет не подпадает. Файлы раздаются байт в байт
   из Google Fonts, своим субсеттером не режем.
   Лицензии лежат рядом: create/fonts/OFL-*.txt — удалять нельзя. */
@font-face {
  font-family: 'HB Head'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../fonts/comfortaa-cyrillic.woff2?v=7114564b') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'HB Head'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../fonts/comfortaa-latin.woff2?v=7114564b') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212;
}
@font-face {
  font-family: 'HB Body'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('../fonts/rubik-cyrillic.woff2?v=7114564b') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* ЛАТИНСКИЙ СУБСЕТ ОБЯЗАТЕЛЕН. В нём живут цифры и пунктуация: без него дата
   «15 марта, 14:00» падает в system-ui посреди строки, набранной HB Body, и
   буквы с цифрами выходят разной толщины. Знак ₽ в этот субсет не входит (его
   нет ни в одной раздаче Google Fonts) и всегда падает в system-ui — это не
   баг подключения, менять здесь нечего. Дефект с цифрами ловили в игре
   2026-07-29 — метод замера в шапке HB/farm/site/css/style.css. */
@font-face {
  font-family: 'HB Body'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('../fonts/rubik-latin.woff2?v=7114564b') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212;
}

:root {
  /* Слой 1 — каркас. Общий для всех игр, картриджем НЕ переопределяется. */
  --surface: #fdf7ee; --surface-raised: #fff; --surface-sunken: #f3e9da;
  --ink: #3b2a1e; --ink-muted: #6b5c4d; --line: #e0d3c0; --line-soft: #e6dccd;
  --focus: #3b82f6;
  --action: #f2c14e; --action-ink: #3b2a1e;
  --action-hover: #f7cd6b; --action-active: #e8b43a;
  /* Разрушительное действие («Убрать») — из палитры игры. Заведён здесь
     (находка ревью 2026-07-30): все четыре обращения ниже брали его через
     var() с фолбэком-литералом того же цвета — токена не существовало нигде
     ни в :root, ни в картридже, поэтому var() всегда падал на фолбэк.
     Выглядело как токен, было литералом с лишним слоем косвенности. */
  --red: #c0473a;

  /* Зелёный зова к действию («праздник уже совсем скоро»). Владелец просил
     ЯРКИЙ зелёный — и яркий здесь невозможен: замер на --surface #fdf7ee даёт
     у #22c55e 2.14:1 при норме AA 4.5:1, у #16a34a — 3.09:1. Оба нечитаемы.
     Взят самый светлый из проходящих: #177a3c даёт 5.07:1 — та же весовая
     категория, что у уже принятого --red (4.69:1). */
  --green: #177a3c;

  /* --muted понижен до роли ТОЛЬКО РАМОК. Замер: #8a7a6a на --surface даёт
     3.6:1 и не проходит AA 4.5:1. WCAG прощает это лишь для disabled — та
     лазейка, которой здесь пользоваться нельзя. Весь второстепенный текст
     живёт на --ink-muted #6b5c4d (6:1). */
  --muted: #8a7a6a;

  --accent: var(--action);

  /* Ширина КОЛОНКИ контента — одно число на всю страницу. Заведено 2026-07-30,
     когда полоса .scene стала во всю ширину окна: её текст держится в той же
     колонке, что карточки, и «та же» обязана быть буквально тем же значением.
     Первая попытка держала колонку двумя литералами (34rem у #create, 32rem у
     .scene-body) в двух местах файла — и десктопный override 38rem, объявленный
     в медиазапросе ВЫШЕ базового правила, молча проиграл ему по порядку: на
     1920px заголовок шапки уехал от края карточки на 48px (замер рендером).
     Токен убирает и дублирование, и порядковую ловушку сразу. */
  --column: 32rem;

  /* Лестница из ступеней — взамен случайного набора размеров. Каждая ступень
     существует под названную роль, и роль записана рядом с ней. */
  --step-scene: clamp(22px, 6.5vmin, 30px);
  --step-screen: clamp(20px, 5.5vmin, 26px);
  /* Ступень между заголовком экрана и телом текста. Существует ровно для одной
     роли — второго предложения-высказывания в полосе (.scene-pitch, разбор
     владельца 2026-07-30). Без неё пришлось бы либо набрать его телом текста
     (и тогда оно неотличимо от подводки — ступеней снова две вместо трёх),
     либо ступенью заголовка (и тогда в полосе два заголовка).
     Пределы clamp сдвинуты меньше, чем у соседей сверху: на 360px эта строка
     обязана остаться в двух строках, иначе воздух, ради которого ступень и
     вводилась, съедается её собственной высотой (замер рендером). */
  --step-lead: clamp(18px, 4.6vmin, 19px);
  --step-body: 16px;
  --step-small: 14px;
  --step-kicker: 11px;

  /* Слой 2 — КАРТРИДЖ ИГРЫ. Значения по умолчанию = бренд HB-games.
     Новая игра — это НЕ один блок и один webp (правка 2026-08-12, план 4B,
     задача 5: прежняя формулировка звучала как памятка «одна правка», а
     читают её первой, до того, как кто-то узнаёт обратное). Шагов больше
     одного: запись в config/games.json с полем `art` (имя файла в
     create/art/), сам файл той же пропорции, что рамка ниже, и блок
     [data-game="…"] со своим тоном — отличным и от бренда, и от тона любой
     другой игры реестра. Пропусти любой шаг — и получишь либо карточку
     брендового тона (владелец уже браковал такую 2026-07-30), либо арт,
     обрезанный cover по длинной стороне. Оба случая ловят сторожи
     (tests/visual-system.test.mjs, tests/create-assets.test.mjs), а не глаз.

     И это только шаг 3 из десяти. Весь порядок заведения игры —
     docs/new-game-checklist.md, требования к самой игре — docs/game-contract.md.

     Относительный путь в url() резолвится от таблицы стилей, в которой
     объявлено свойство, а не от того, кто его читает. Все объявления живут в
     одном create.css, поэтому поведение однородно. Если файл когда-нибудь
     разделят — пути надо перепроверить. */
  --scene-bg: #3b2a1e; --scene-ink: #fff7e8; --scene-kicker: #e6c88a;
  /* --scene-art: none — осознанное решение, не заглушка. Сцена бренда
     рисуется без картинки — тёмной полосой --scene-bg с градиентом. Файла
     art/brand-scene.webp не существует ни в одном репозитории (проверено
     2026-07-29), и подставлять в хаб арт «Фермы» нельзя: хаб перечисляет ВСЕ
     игры, и с третьей игрой такая шапка начнёт врать. Если однажды появится
     сцена бренда, эта строка меняется на путь к art/brand-scene.webp — и
     больше ничего. (Без url(), чтобы не попасть под сторож покрытия ассетов
     ниже: он матчит url() в тексте файла и не отличает комментарий от кода.) */
  --scene-art: none; --scene-focal: center 62%;
}

/* Картридж «Фермы». Пять переменных и одна картинка — весь объём темы.
   ОТКУДА ВЗЯТ --scene-bg (правка 2026-07-30, и так же берётся для следующей
   игры). До неё картридж повторял брендовый #3b2a1e буква в букву: страница
   каталога была набрана ОДНИМ цветом, и карточка-«окно» не отделялась от
   шапки-студии. Прозрачность вместо своего тона отвергнута сознательно —
   текст поверх картинки это ровно то место, где в этом проекте уже умирал
   контраст (1.58:1, ревью 2026-07-30).
   Приём: тон выводится из САМОГО арта, а не подбирается на глаз.
     1. По art/farm-scene.webp отбираются пиксели зелени (g ≥ r и g > b + 12) —
        26.4 % площади файла;
     2. берётся тёмный конец этой выборки: 2-й процентиль по сумме каналов дал
        #656935 = H65° S33% L31% (мох под деревьями, самое тёмное, что в игре
        вообще нарисовано);
     3. светлота гасится примерно вдвое, до фоновой: H70° S32% L16% = #32361c.
   Итог отличается от бренда не яркостью, а тоном: у кофейного r > g, у мха
   g > r, и глаз читает это как две разные вещи, а не как две заливки одной.
   Замер контраста на живой странице (снимок Chrome + чтение пикселя фона над
   и под строкой, не формула): --scene-ink 11.74:1, --scene-kicker 7.73:1,
   .window-note 8.78:1 — одинаково на 360, 768 и 1920, потому что под текстом
   теперь ровный --scene-bg, а не смесь с артом. Все выше AA 4.5:1 с запасом.
   Если у новой игры шаги 1–3 дадут тон, не проходящий AA, двигать надо ТОН, а
   не текст. */
/* ОТКУДА ВЗЯТ ФАЙЛ АРТА - записано здесь, потому что больше негде, а знать это
   обязательно. create/art/farm-scene.webp, 900x300, - кадр из сцены САМОЙ ИГРЫ
   (dist/farm/v1/assets/img/farm.webp, 1280x720). Кадр: x 55..1105, y 160..510
   (1050x350), уменьшено до 900x300 через LANCZOS, webp q82, PSNR 38.3 дБ к
   источнику. Промо показывает ровно ту сцену, которую увидит гость, а не
   похожую.

   ПОЧЕМУ ИМЕННО ЭТИ ГРАНИЦЫ - две правки владельца одного дня, и вторая
   объясняет первую:
     • 2026-07-30, первая: у прежнего кадра была срезана крыша сарая. Кадр
       поднят на 40px - крыша появилась, но ушла изгородь переднего плана,
       которая ровно в этих 40px и была.
     • 2026-07-30, вторая: «теперь не видно забор». Оба края влезают только в
       кадр ВЫШЕ прежнего (350px против 300px по вертикали), а при пропорции
       3:1 это значит взять и ШИРЕ - 1050px против 900px, - и уменьшить.
   Отсюда правило на будущее: подгонять кадр сдвигом нельзя, у сцены есть
   обязательный вертикальный минимум - от верха крыши до низа изгороди. Меняешь
   кадр - меняй высоту и ширину вместе, сохраняя 3:1.

   ⚠️ СТОРОЖ ДЕРЖИТ ПРОПОРЦИЮ, А НЕ РАЗМЕР (правка по широкому ревью ветки 4B,
   2026-08-12). Здесь было обещано «tests/create-assets: сторож на 900x300 и на
   совпадение с aspect-ratio в CSS» — первой половины больше нет. Задача 5
   заменила жёсткую сверку «ровно 900 на 300» сверкой одной пропорции по всем
   играм реестра, и это правильно: 900x300 — свойство кадра «Фермы», а не
   контракта, и второй игре навязывать её незачем. Но снято это было молча, а
   памятка рядом продолжала обещать снятое. Что стережётся сегодня: w/h каждого
   арта реестра равно единственной пропорции aspect-ratio в этом файле, и сама
   эта пропорция в CSS одна (двух разных быть не может). Точный размер не
   стережёт никто - вес и резкость файла на совести того, кто его кладёт. */
[data-game="farm"] {
  --scene-bg: #32361c;
  --scene-kicker: #e6c88a;
  --scene-art: url('../art/farm-scene.webp?v=7114564b');
  /* Декоративен, пока пропорции файла и рамки совпадают: cover не режет, и
     точке фокуса нечего смещать. Оставлен на случай арта другой пропорции у
     следующей игры. */
  --scene-focal: center 62%;
}

/* Картридж «Роболёта». Тон выведен из жёлто-оранжевой семьи арта витрины
   (тон 18–62°, S ≥ 35 % — 62 % площади файла, медиана тона 36°) по решению
   владельца 2026-08-26. Три тона, выведенные приёмом «Фермы» буква в букву, им
   отклонены после живых рендеров: тёплый #241409 (корабль на арте), холодный
   #193833 (неон роботов), индиго #16193b (титульный экран самой игры).
   ⚠️ ПРИЁМ «ФЕРМЫ» НА ЭТОЙ СЕМЬЕ НЕ РАБОТАЕТ, и это стоит знать третьей игре:
   её тёмный конец (2-й процентиль) — #7d5535 H27 S40 L35, гашение светлоты
   вдвое даёт #3d2a1a, то есть брендовый #3b2a1e буква в букву. Семья слишком
   светлая и слишком насыщенная, чтобы её «гасить»: держится насыщенность
   (S70), а гасится только светлота — до L26, потолка контраста при фермовском
   --scene-kicker. Выше L26 первым проваливается именно kicker, а не текст.
   Замер (та же формула, что дала записанные выше фермовские 11.74/7.73/8.78):
   --scene-ink 7.92:1, --scene-kicker 5.21:1, .window-note 6.13:1 — все выше
   AA 4.5:1. --scene-kicker оставлен фермовским: свой, более светлый, поднял бы
   потолок до L32, но развёл бы палитру студии на две.
   ⚠️ ЧЕСТНО ПРО ОТСТУПЛЕНИЕ ОТ ПРАВИЛА ВЫШЕ. Правило требует тона, отличного
   от бренда; H30 против брендового H25 — это 5°. Карточка отделяется от шапки
   светлотой (L26 против L17) и насыщенностью (S70 против S33), а не тоном.
   Отступление осознанное: семью выбрал владелец, увидев четыре живых рендера
   («оранжевого там не было» — про более яркий #935610). Сторож этого не
   различает — tests/visual-system.test.mjs сверяет hex побайтово, — поэтому
   образцом для следующей игры служит приём выше, а не этот блок. */
[data-game="hb-robots"] {
  --scene-bg: #714214;
  --scene-kicker: #e6c88a;
  --scene-art: url('../art/hb-robots-scene.webp?v=7114564b');
  --scene-focal: center 62%;
}

* { box-sizing: border-box; }
/* Страховка под полосу .scene: она вырывается из колонки на всю ширину окна
   через calc(50% - 50vw) (см. правило .scene ниже), а 100vw в части браузеров
   считается вместе с полосой прокрутки — тогда полоса шире вьюпорта на
   половину скроллбара с каждой стороны и страница получает горизонтальный
   скролл.
   ЧТО ИМЕННО ЗАМЕРЕНО, а что нет. В этом Chrome полосы прокрутки
   накладываемые: innerWidth == clientWidth == 1920, и переполнения нет ни с
   clip, ни без него — проверено снятием свойства на живой странице на 360,
   768, 1024 и 1920 (scrollWidth − clientWidth = 0 во всех восьми замерах).
   Классический скроллбар этой средой не воспроизводится, поэтому дефект здесь
   не наблюдался, а закрыт по расчёту: .scene замерена шириной ровно
   window.innerWidth, то есть ровно 100vw, и на машине, где innerWidth больше
   clientWidth, разница вылезет наружу целиком.
   clip, а НЕ hidden: hidden сделал бы html скролл-контейнером, и position:
   sticky у главной кнопки (@media ≥60rem ниже) начал бы липнуть к нему вместо
   окна — то есть молча перестал бы работать. clip скролл-контейнер не
   создаёт; кнопка проверена рендером после правки, sticky на месте. */
html { overflow-x: clip; }
body { margin: 0; background: var(--surface); color: var(--ink);
  font: 400 var(--step-body)/1.5 'HB Body', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
h1, h2, h3 { font-family: 'HB Head', 'HB Body', system-ui, sans-serif; font-weight: 700; }
/* max-width = колонка + два padding'а: ширина КОНТЕНТА тогда равна --column
   ровно, и полоса-шапка может встать по тому же краю (см. .scene-body). */
#create, #edit, #pay { max-width: calc(var(--column) + 2rem); margin: 0 auto; padding: 1rem 1rem 2rem; }

/* ≥16px обязательно: меньше — и iOS Safari зумит страницу при фокусе,
   вёрстка прыгает. Это не косметика, это ломает ввод (§4.2). */
input, textarea, button, select { font-size: var(--step-body); font-family: inherit; }
textarea, input[type="text"] {
  width: 100%; padding: 0.7em 0.8em; border: 1px solid var(--line);
  border-radius: 0.7em; background: var(--surface-raised); color: inherit; resize: none;
}
/* Полоса прокрутки — только когда поле упёрлось в потолок; её включает
   form.js:autosize. По умолчанию прокручивать нечего, а полоса со стрелками
   съедает половину однострочного поля (снимок 2026-07-28). */
textarea { line-height: 1.35; overflow-y: hidden; }
/* Подпись поля — ступень «подпись поля»: 16px весом 600. */
label { display: block; margin: 1.1em 0 0.35em; font-size: var(--step-body); font-weight: 600; }
.hint { color: var(--ink-muted); font-size: var(--step-small); margin-top: 0.3em; }
.counter { font-size: var(--step-small); color: var(--ink-muted); text-align: right; }
.counter.warn { color: #c2410c; }

/* Кнопка в потоке документа, НЕ position: fixed — на iOS фиксированные панели
   дерутся с экранной клавиатурой и уезжают (§4.2). */
.primary {
  display: block; width: 100%; margin-top: 1.4em;
  padding: 0.95em 1em; border: 0; border-radius: 999px;
  background: var(--action); color: var(--action-ink); font-weight: 700;
  /* БЕЗ env(safe-area-inset-bottom) в нижнем поле — оно тут стояло и дышало.
     Все точки входа /create объявляют viewport-fit=cover, а при нём iOS отдаёт
     вставку не константой: пока нижняя панель браузера развёрнута, страница до
     домашней полосы не достаёт и вставка равна нулю; в прокрутке панель
     схлопывается, страница дотягивается до края экрана — и вставка становится
     34 px. Панель схлопывается и разворачивается постоянно, поэтому кнопка
     постоянно и меняла высоту: ровно на эти 34 px и ровно снизу, а текст в ней
     не двигался. Так владелец дефект и описал 2026-07-30 — «кнопка сначала
     высокая, потом нижняя часть подтягивается вверх».
     Вставка была нужна не здесь: липкой кнопка бывает только на широком экране
     (@media ≥60rem ниже), а у настольных браузеров safe-area-вставок нет
     вовсе — env() не помогал никому и мешал каждому телефону.
     Сторож: tests/mobile-webkit.test.mjs. */
  /* Без него двойной тап ловит паразитную задержку ~300 мс, которую браузер
     держит на случай зума. */
  touch-action: manipulation;
}
/* Единственная кнопка воронки, у которой не было отклика на десктопе вовсе. */
.primary:hover { background: var(--action-hover); }
.primary:active { background: var(--action-active); }
.steps { margin-bottom: 1.2em; }
.steps-track { display: flex; gap: 0.35em; }
.steps-track span { flex: 1; height: 3px; background: var(--line-soft); border-radius: 2px; }
/* Тон игры, а не цвет действия: прогресс — состояние, а не кнопка (§6 спеки). */
.steps-track span.done { background: var(--scene-kicker); }
.steps-label { margin: 0.5em 0 0; font-size: var(--step-small); color: var(--ink-muted); }

/* Опорный блок для абсолютного позиционирования внутри экрана — сейчас
   ровно один потребитель, .confetti (doneScreen, «Движение» ниже в файле). */
.screen { position: relative; }

/* Заголовок экрана — вторая ступень лестницы. */
.screen-title { margin: 0 0 0.8em; font-size: var(--step-screen); line-height: 1.25; }
/* Кроме гостевого хаба: там на ступени --step-screen стоит не заголовок из
   двух слов, а целое продающее предложение (hub.js: HUB_LEAD, 62 знака). На
   узком телефоне оно занимало три строки крупным кеглем и вместе с полосой
   арта, вопросом про дату, двумя списками и кнопкой напоминания выталкивало
   главную кнопку страницы за нижний край: владелец на iPhone 6s Plus - «теряется
   кнопка „Собрать такую же игру“ - она не влезает на экран».
   --step-lead - ступень между заголовком и корпусом, заведённая ровно под
   такие фразы-подводки (см. .scene-pitch ниже). Правило адресное: заголовки
   шагов воронки набраны короткими существительными, им сбивать кегль не за что
   и вниз их выталкивать нечему. */
.hub .screen-title { font-size: var(--step-lead); }

@media (min-width: 60rem) {
  /* Здесь была двухколоночная сетка «форма + живая игра рядом» (§4.3). Колонки
     с игрой в DOM не существует: .stage-preview не создаёт ни один модуль. А
     сеткой становился сам #create — и его собственные дети разъезжались по
     колонкам по порядку: полоса шагов влево, экран формы вправо, «Далее»
     обратно влево (снимок 2026-07-28). Пока превью не построено, десктоп —
     одна колонка по центру, просто пошире мобильной. Вернуть сетку можно
     только вместе с настоящей второй колонкой и обёрткой вокруг первой.
     Расширяется ТОКЕН колонки, а не max-width каждого потребителя: за
     колонкой ходят ещё текст и арт полосы-шапки (.scene-body), и три
     независимых числа тут уже разъезжались. */
  :root { --column: 38rem; }
}

/* Видимый фокус обязателен: на десктопе клавиатура — основной способ
   заполнения формы, и «невидимый фокус» ломает именно её (§4.3). */
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* Паразитная задержка ~300 мс на двойном тапе — у всего, что нажимают пальцем. */
.nav-back, .choice, .answer, .playthrough-btn, .free-exit, .free-confirm,
.released-show, .forget-btn, .reset-ask, .reset-confirm, .reset-cancel,
.forget-ask, .forget-confirm, .forget-cancel { touch-action: manipulation; }

.answer-toggle { display: flex; gap: 0.5em; margin-top: 0.4em; }
/* color: inherit обязателен — это <button>, а форма-контролы НЕ наследуют
   color по умолчанию (UA-стиль даёт им свой «buttontext»). Замер рендером
   (Задача 6) поймал именно это: без явного inherit .answer.on красился в
   чёрный браузера, а не в --ink — сейчас числа совпали случайно (17.48:1,
   AA пройдена с запасом), но в теме с иным UA-цветом контролов совпадения
   могло и не быть. */
.answer { flex: 1; padding: 0.6em; border: 1px solid #e0d3c0; border-radius: 0.7em;
  background: #fff; color: inherit; }
/* Выбранный ответ читается рамкой и галочкой, а не заливкой: четыре жёлтых
   «Да» на экране вопросов занимали больше площади, чем кнопка «Далее», и
   акцент переставал значить «двигает вперёд». Форма к тому же читается на
   солнце, где заливка почти не видна. */
.answer.on { background: var(--surface-sunken); border-color: var(--ink);
  border-width: 2px; font-weight: 700; }
.answer.on::before { content: '✓'; margin-right: 0.35em; font-weight: 700; }
.proofread { display: grid; grid-template-columns: auto 1fr; gap: 0.3em 0.9em; margin: 1em 0; }
.proofread dt { color: var(--ink-muted); font-size: var(--step-small); }
.proofread dd { margin: 0; }
/* На 358px «auto 1fr» отдавало 132px серым подписям против 212px содержимому —
   то есть треть ширины уходила под служебное. Ниже 480px колонки складываются
   в одну: dt над dd. */
@media (max-width: 30rem) {
  .proofread { grid-template-columns: 1fr; gap: 0.15em 0; }
  .proofread dd { margin-bottom: 0.7em; }
}
.gate-note { color: var(--ink-muted); font-size: var(--step-small); font-style: italic; }
.playthrough-btn { width: 100%; margin-top: 1em; padding: 0.8em; background: none;
  border: 1px solid #e0d3c0; border-radius: 999px; }

/* Хаб, шаг импорта и экран «готово» — общая second-tier кнопка-ссылка. */
.offer { margin: 0.6em 0; }
/* touch-action: без него двойной тап по ссылке на мобильном ловит паразитную
   задержку ~300 мс, которую браузер держит на случай зума. */
.offer-link { display: inline-flex; align-items: center; gap: 0.5em; width: 100%;
  padding: 0.75em 1em; border: 1px solid #e0d3c0; border-radius: 0.9em;
  background: #fff; color: inherit; text-decoration: none; text-align: left;
  touch-action: manipulation; }
/* Фон при наведении и нажатии светлее исходного --surface, поэтому контраст
   текста (--ink) от них только растёт и пересчёта не требует. */
.offer-link:hover { background: #fdf6e8; border-color: #d9c6a8; }
.offer-link:active { background: #f7ecd6; }
/* Отклик на нажатие этих кнопок объявлен один раз — в блоке «Движение» в конце
   файла, вместе со всеми остальными. Прежнее объявление стояло здесь и задавало
   свою дозу приседания (0.98 против 0.972), из-за чего две соседние кнопки на
   «Готово!» приседали по-разному; вдобавок :active-трансформа лежала ВНЕ
   prefers-reduced-motion и играла у тех, кто просил движение не показывать. */
/* Дата праздника — два списка без года (hub.js: dayMonthFromParts). День уже
   месяца: «12» и «сентября» разной длины, и равные колонки смотрелись бы как
   ошибка вёрстки. */
.reminder-label { margin-bottom: 0.4em; font-size: var(--step-body); font-weight: 600; color: var(--ink); }
.reminder-date { display: flex; gap: 0.5em; margin-bottom: 0.5em; }
.reminder-date select { flex: 1; padding: 0.7em 0.8em; font-size: 16px;
  font-family: inherit; border: 1px solid #e0d3c0; border-radius: 0.7em;
  background: #fff; color: inherit; }
.reminder-date .reminder-day { flex: 0 0 5.5em; }
/* Вес кнопок хаба и каталога (правка 2026-07-29). Раньше .start-btn не имела
   своего правила вовсе и рисовалась обычной белой .offer-link — то есть
   единственное действие каталога выглядело как строка списка. Теперь сплошная
   заливка у неё: и в каталоге, и в хабе это то действие, ради которого
   страница существует.
   Напоминание осталось выше по порядку (§7: сначала то, что человек получает
   даром), но ниже по весу — контурная кнопка в том же акценте. Порядок
   предложений не изменился, изменилась только заметность. */
/* color: явно, а не inherit .offer-link. Кнопка всегда сидит на заливке
   --accent (золото) внутри тёмной .window-card — с 2026-08-11 на обеих
   страницах сразу, потому что плитки переехали и на гостевую, — а там
   .start-btn иначе наследует --scene-ink (почти белый) и сливается с золотом
   (1.58:1, ревью 2026-07-30). --action-ink заведена ровно как «чернила поверх
   акцентной заливки» и численно совпадает с --ink, поэтому на светлом фоне,
   где кнопка стояла в хабе прежде, она выглядела ровно так же. */
.start-btn { justify-content: center; background: var(--accent);
  border-color: var(--accent); color: var(--action-ink); font-weight: 700;
  text-align: center; }
.start-btn:hover { background: #f7cd6b; border-color: #f7cd6b; }
.start-btn:active { background: #e8b43a; border-color: #e8b43a; }
/* Витрина рядом со сборкой (2026-08-02). Контурная, а не залитая: акцент на
   карточке один, и носит его «Собрать» — ради неё страница и существует.

   Цвет текста задан ЯВНО, и это не украшение. .offer-link объявляет
   `color: inherit`, а .window-body тёмная — значит унаследованный текст здесь
   светлый. Первая редакция скопировала заливку у .reminder-btn (светлый
   --surface-raised) и получила светлый текст на светлом фоне: кнопка на проде
   читалась как пустая. .start-btn рядом не сломалась ровно потому, что задаёт
   свой --action-ink.

   Отступ сверху отделяет её от главной кнопки: две кнопки впритык читаются как
   одна разрезанная. */
.demo-btn { justify-content: center; margin-top: 8px;
  background: rgba(255, 255, 255, .10); color: #fff;
  border-color: rgba(255, 255, 255, .45); font-weight: 600; }
.demo-btn:hover { background: rgba(255, 255, 255, .18);
  border-color: rgba(255, 255, 255, .7); }
/* Белая контурная: акцент на гостевой носит не напоминание, а кнопка сборки —
   ради неё страница и существует. С 2026-08-11 это кнопка ПЛИТКИ игры
   (.start-btn внутри .window-card, своя у каждой игры), а не общая «Собрать
   свою игру»: та вела в сборку игры, которую гость только что прошёл, и убрана.
   Правило то же, что и было: заливка означает действие, двигающее вперёд, а
   напоминание — то, что человек получает даром. */
.reminder-btn { justify-content: center; background: var(--surface-raised);
  border-color: var(--line); font-weight: 600; }
.reminder-btn:hover { background: var(--surface-sunken); border-color: var(--muted); }
/* Совет про календарь — НЕ приглушённая мелочь (правка 2026-07-31). Без него
   кнопка у большинства владельцев айфонов молча не работает: iOS сохраняет
   событие, только если явно нажать строку выбора календаря, и так на двух
   разных аппаратах. Серые 14px для условия работы — неверный вес.
   Заливки и рамки нет — это по-прежнему пояснение к кнопке, а не тревога, и
   акцент на экране остаётся один.
   ОСНОВНЫЕ ЧЕРНИЛА ОСТАЮТСЯ, КЕГЛЬ УХОДИТ НА СТУПЕНЬ ВНИЗ (правка 2026-08-05).
   От серых 14px этот текст спасали два свойства сразу — цвет и размер, — и
   тянул он на две строки корпусом. Вес держит --ink: 12.84:1 против --ink-muted
   5.36:1, то есть отличие от «приглушённой мелочи» никуда не делось. А кегль
   здесь оплачивался чужой строкой: на 375px эти две строки стояли ровно между
   кнопкой напоминания и главной кнопкой страницы, которую владелец на 6s Plus
   и не увидел. Полужирное «обязательно» внутри (hub.js: REMINDER_ADVICE)
   работает на любом кегле. */
.reminder-advice { margin-top: 0.5em; color: var(--ink); font-size: var(--step-small); }
/* «Праздник уже совсем скоро» — не подтверждение, а разворот разговора:
   напоминание тут бесполезно, и человеку надо к соседней кнопке.
   РАЗМЕР — ОБЫЧНЫЙ. Сперва он был на ступень крупнее корпуса, и владелец
   2026-07-31: «слишком крупно получилось, достаточно обычного размера, но
   яркого зелёного цвета». Заметность теперь несут цвет и вес, а не кегль:
   крупный текст ломал ритм колонки и спорил с заголовком выше.
   Без заливки, рамки и красного: это не ошибка человека и не беда, пугать его
   нечем. Акцент на экране остаётся у кнопок сборки в плитках игр ниже
   (с 2026-08-11 их место заняли они, а не общая «Собрать свою игру»), к ним
   текст и ведёт.
   Кегль доведён до --step-small (правка 2026-08-05) - тем же решением владельца
   и в его же логике: раз заметность держат цвет и вес, размер тут ничего не
   несёт, а платит за него та самая кнопка. Текст длинный, три предложения, и
   на 375px корпусом он занимал четыре строки ровно над ней. Зелёный
   полужирный виден и на 14px. */
.reminder-note.urgent { margin: 0.7em 0 0; color: var(--green); font-weight: 700;
  font-size: var(--step-small); }
/* Карточка-«окно» — окно в игру. Скругление 17px и мягкая тень: карточка
   обязана выглядеть законченной вещью, а не первой из пустого ряда. */
.window-card { position: relative; isolation: isolate; overflow: hidden;
  margin: 1.2em 0; border-radius: 17px; background: var(--scene-bg);
  color: var(--scene-ink);
  /* rgba(59,42,30,…) — тот же цвет, что --ink/--scene-bg, но литералом.
     color-mix убирает дублирование значения (Minor, ревью 2026-07-30). */
  box-shadow: 0 6px 18px -8px color-mix(in srgb, var(--ink) 45%, transparent); }
/* Арт занимает свою строку в потоке, а не лежит подложкой под текстом
   (правка 2026-07-30 по замечанию «всё занято коричневым фоном и не видно
   картинку»). История здесь в два круга, и оба стоит помнить.
   Круг 1: стопы градиента стояли в ПРОЦЕНТАХ высоты .window-art (= высоте
   карточки), а та зависит от длины текста игры. У «Фермы» заголовок начинался
   на ~38 % — ДО первого непрозрачного стопа, и под текстом лежала смесь арта
   и фона: замер rgb(112,97,66), 5.67:1 против «бумажных» 12.84:1.
   Круг 2 (правка того же дня, раньше этой): проценты заменили на em (6em при
   padding-top текста 7.5em) — контраст починился, но арта осталось видно
   ~100px на карточке ~370px, то есть картинка работала текстурой. И «полоса
   6em» — это разные доли на карточке 328px и 608px: на широком окне от
   композиции оставалась нарезка без верха и низа.
   Фикс обоих кругов один: aspect-ratio 3/1 — собственная пропорция файла
   900×300. Арт получает 1/3 ширины карточки в высоту; замер рендером доли
   площади карточки: 109/364 = 30 % на 360, 171/390 = 44 % на 768,
   203/400 = 51 % на 1024 и 1920 (было ~100px, то есть ~27 % на 360 и ~25 %
   на 1920, при этом на 1920 — без верха и низа композиции). Cover при точном
   совпадении пропорций не режет ничего, а --scene-focal перестаёт быть
   компромиссом между «видно барн» и «видно поле».
   ГАРАНТИЯ, ЧТО ТЕКСТ ЛЕЖИТ НА НЕПРОЗРАЧНОМ --scene-bg, выведена заново и
   больше не числовая: .window-body — СЛЕДУЮЩИЙ блок в потоке за .window-art
   (screens.js: windowCard кладёт их в этом порядке), поэтому текст физически
   не может оказаться на арте — ни при какой длине заголовка, ни при какой
   ширине карточки, ни при какой яркости арта следующей игры. Прежняя
   гарантия была неравенством двух чисел в разных правилах и ломалась молча,
   стоило тронуть любое из них; эту сломать можно только перестановкой узлов
   в разметке, а это видно глазом сразу. */
.window-art { position: relative; aspect-ratio: 3 / 1;
  background-image: var(--scene-art); background-size: cover;
  background-position: var(--scene-focal); background-repeat: no-repeat; }
/* Нижний край картинки растворяется в теле карточки: арт кончается ровно там,
   где начинается текст, и резкая линия делила бы «окно» пополам. Проценты
   здесь снова законны — высота слоя задана aspect-ratio от ширины карточки и
   от длины текста не зависит вовсе, то есть та связь, на которой погорели оба
   круга выше, разорвана.

   ПЕРЕПИСАНО 2026-07-30: было `transparent 58%` - растворение съедало нижние
   42% арта, а в них лежит изгородь переднего плана. Владелец: «теперь не видно
   забор», и предложил на выбор либо опустить кадр (небо и верх крыши наружу),
   либо ослабить затемнение. Ослабление строго лучше: кадр только что подняли
   ради крыши, и возвращать её под обрез, чтобы показать низ, значит менять один
   срезанный край на другой. Показать можно оба - затемнение к композиции файла
   отношения не имеет.

   Ослаблять оказалось можно СМЕЛО, и это следствие той самой перестройки арта
   в блок. Пока арт был абсолютным слоем ПОД текстом, этот градиент был единственным,
   что держало контраст заголовка, - и трогать его было нельзя. Теперь .window-body
   идёт СЛЕДУЮЩИМ блоком в потоке и стоит на сплошном --scene-bg (замер: тело
   начинается ровно на нижней границе арта, зазор 0). Градиент остался чисто
   декоративным стыком, и контраст «Ферма» от него не зависит ни на процент -
   проверено замером до и после правки: 11.74:1 в обоих случаях.

   Полоса растворения - последние 18% высоты арта: этого хватает, чтобы не было
   резкой линии, и мало, чтобы задеть изгородь (она занимает нижнюю треть кадра).
   Первый стоп 82%, а не 88%: при 88% переход укладывался в 24px на десктопе и
   читался как линия, а не как растворение. */
.window-art::after { content: ''; position: absolute; inset: 0;
  background: linear-gradient(to bottom, transparent 82%,
    color-mix(in srgb, var(--scene-bg) 45%, transparent) 93%,
    var(--scene-bg) 100%); }
/* Верхнего отступа под арт больше не нужно: арт стоит выше в потоке и своё
   место занимает сам. */
.window-body { position: relative; padding: 1.1em 1.1em 1.2em; }
.window-body h2 { margin: 0 0 0.4em; font-size: var(--step-screen); color: var(--scene-ink); }
.window-note { margin: 0 0 1em; line-height: 1.4;
  color: color-mix(in srgb, var(--scene-ink) 84%, transparent); }
/* Контакты — три ГОЛЫХ значка, прижатых вправо (решение владельца 2026-08-08,
   вторая правка за день). Между двумя правками они успели побыть рядом из трёх
   карточек .offer-link, и это была ошибка: карточка рассчитана на подпись, а
   подписи мы сняли — получились три широкие белые плашки с крошечной картинкой
   посередине и пустотой вокруг. Разбор владельца: «просто иконки рядом справа,
   не заполняя собой всё пространство».
                                                                    ↓ здесь
   Поэтому это больше НЕ .offer-link ни в каком виде: ни рамки, ни заливки, ни
   ширины. Своё имя класса, а не переопределение чужого поверх — иначе следующий
   читатель увидит кнопку-карточку в разметке и карточку же в половине правил.

   До этого блок стоял столбцом, и записанная причина была верной: третья
   подпись — сам почтовый адрес (hub.js: CONTACTS), и в трети ширины
   360-пиксельного экрана он переносится и ломает ряд. Подписей больше нет, и
   возражение ушло вместе с ними; имя кнопки несёт aria-label (contacts.js).

   Отбивка сверху остаётся: без неё и без подписи полоска значков ни к чему не
   относится и читается как случайный мусор под карточкой игры.

   2.75em = 44px при --step-body 16px — ровно тач-цель iOS. Значок внутри 1.5em;
   остальное добирает невидимая площадь нажатия, и уменьшать её нельзя, сколько
   бы воздуха вокруг ни хотелось. */
.contacts { margin-top: 1.8em; padding-top: 1.1em; border-top: 1px solid #e6dccd;
  display: flex; justify-content: flex-end; gap: 0.25em; }
.contact-link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75em; height: 2.75em;
  border: 0; border-radius: 999px; background: none;
  color: inherit; text-decoration: none;
  touch-action: manipulation;
}
/* Отклик на палец — только подложка: круг под значком, а не рамка вокруг него.
   Рамка вернула бы ту самую карточку, от которой здесь избавились. */
.contact-link:hover { background: #f3e9da; }
.contact-link:active { background: #ecdfcb; }

/* Картинка — ::before, а не вставленный <svg>: разметка в screens.js собирается
   createElement + textContent (innerHTML запрещён), и ради трёх иконок городить
   createElementNS не стоит. Выбор иконки идёт по data-contact, который ставится
   из объявленного id контакта, — не по разбору href: адрес может смениться,
   id держится за смысл.

   Цветная только иконка, подложки под ней нет: фирменные синие не спорят с
   тёплой кремовой палитрой ровно потому, что заливки нет вовсе. */
.contact-link::before {
  content: '';
  flex: 0 0 1.5em;
  width: 1.5em; height: 1.5em;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}
/* ВК: ОФИЦИАЛЬНАЯ марка, а не наша перерисовка.
   Владелец спросил прямо, нельзя ли взять официальную (2026-07-30). Можно, и она
   здесь: контуры взяты 2026-07-30 из https://dev.vk.com/svg/main_dev_logo.svg -
   это логотип «VK for Developers», и первые два его контура и есть сам знак ВК:
   скруглённый квадрат #07F (= #0077FF) и белая монограмма «VK». Знак сдвинут по
   вертикали на -0.984 - в исходном файле он занимает y 0.984..24.984, а нам нужен
   квадрат 24x24.
   Использование по назначению: марка стоит на кнопке, ведущей в НАШЕ собственное
   сообщество (hub.js: VK_COMMUNITY_URL). Это тот случай, для которого иконки
   соцсетей и существуют.
   Что было до неё, чтобы не вернуться: сперва голая галочка M4 5L10 15L16 5
   (формой просто «V» - рядом с самолётиком телеграма читалась как галочка, а не
   как соцсеть, замечание владельца), потом НАША перерисовка квадрата с буквами
   по обводке. Перерисовка узнаваемее галочки, но у неё нет ни родной формы
   суперэллипса, ни родной монограммы, и подделывать чужой знак от руки незачем,
   когда официальный доступен.
   Форма подачи прежняя и обязательна: <path>, а НЕ <text>. Внутри data: URI шрифт
   подставляет сам браузер (никакой @font-face туда не доходит), и «VK» набралось
   бы чем попало от машины к машине - на одной Comfortaa, на другой Times.
   Контраст замерен снимком живой кнопки: заливка #0077FF на белом #fff - 4.13:1,
   буквы #fff на #0077FF - те же 4.13:1. Порог WCAG 1.4.11 для нетекстовой
   графики 3:1, запас есть у обоих. Правило одно на все страницы: блок контактов
   общий (screens.js: contactsBox), и картинка меняется сразу и в /create/, и в
   /create/farm/guest/ - проверено рендером на обеих. */
.contact-link[data-contact="vk"]::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg transform='translate(0 -0.984)'%3E%3Cpath d='M0 12.504c0-5.43 0-8.145 1.687-9.833C3.374.984 6.09.984 11.52.984h.96c5.43 0 8.146 0 9.833 1.687C24 4.36 24 7.074 24 12.504v.96c0 5.43 0 8.146-1.687 9.833-1.687 1.687-4.402 1.687-9.833 1.687h-.96c-5.43 0-8.146 0-9.833-1.687C0 21.61 0 18.895 0 13.464v-.96Z' fill='%2307F'/%3E%3Cpath d='M12.77 18.274c-5.47 0-8.59-3.75-8.72-9.99h2.74c.09 4.58 2.11 6.52 3.71 6.92v-6.92h2.58v3.95c1.58-.17 3.24-1.97 3.8-3.95h2.58c-.43 2.44-2.23 4.24-3.51 4.98 1.28.6 3.33 2.17 4.11 5.01h-2.84c-.61-1.9-2.13-3.37-4.14-3.57v3.57h-.31Z' fill='%23fff'/%3E%3C/g%3E%3C/svg%3E");
}
/* Телеграм: ОФИЦИАЛЬНАЯ марка, как и у ВК рядом (владелец 2026-07-30: «раз мы
   взяли ВК официальный, то логично и Телеграм взять официальный»).
   Взята 2026-07-30 из https://telegram.org/img/t_logo.svg - круг с фирменным
   градиентом #2AABEE -> #229ED9 и белый самолётик. Градиент сохранён как есть:
   он и есть узнаваемость, а плоская заливка превратила бы марку в похожую
   самоделку - ровно то, от чего мы ушли у ВК.
   Использование по назначению: марка стоит на кнопке, ведущей в наш собственный
   канал (hub.js: CONTACTS).
   ЗАМЕР И ОСОЗНАННЫЙ РАЗМЕН (пиксели живой кнопки, не формула по токенам):
     наш прежний плоский самолётик #229ED9 без круга - 3.02:1 на белом;
     официальный круг, низ градиента #229ED9 .............. 3.02:1;
     официальный круг, верх градиента #2AABEE ............. 2.57:1;
     в отрендеренной иконке 20x20 фактические пиксели ..... 2.70..2.94:1;
     белый самолётик против самой светлой части круга ..... 2.57:1.
   То есть верхняя часть официальной марки НЕ проходит порог 3:1 из WCAG 1.4.11,
   и прежний наш значок проходил. Разница - свойство фирменного градиента
   Телеграма; «поправить» его перекраской нельзя, получится не их марка, а
   похожая самоделка - ровно то, от чего мы ушли.
   Почему это допустимо: 1.4.11 требует контраста от графики, БЕЗ КОТОРОЙ
   содержимое не понять. Рядом с каждой иконкой стоит текстовая подпись
   («Телеграм»), и смысл несёт она; иконка помогает узнать канал, а не сообщает
   о нём. Тот, кто не различит круг на белом, прочтёт подпись.
   Если размен когда-нибудь сочтут неверным - правильный ход НЕ перекрасить
   марку, а добавить ей обводку или подложку, оставив цвета Телеграма в покое. */
.contact-link[data-contact="tg"]::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'%3E%3Cdefs%3E%3ClinearGradient id='tg' x1='50%25' x2='50%25' y1='0%25' y2='99.258%25'%3E%3Cstop offset='0%25' stop-color='%232AABEE'/%3E%3Cstop offset='100%25' stop-color='%23229ED9'/%3E%3C/linearGradient%3E%3C/defs%3E%3Ccircle cx='64' cy='64' r='64' fill='url(%23tg)'/%3E%3Cpath fill='%23FFF' d='M28.9700376,63.3244248 C47.6273373,55.1957357 60.0684594,49.8368063 66.2934036,47.2476366 C84.0668845,39.855031 87.7600616,38.5708563 90.1672227,38.528 C90.6966555,38.5191258 91.8804274,38.6503351 92.6472251,39.2725385 C93.294694,39.7979149 93.4728387,40.5076237 93.5580865,41.0057381 C93.6433345,41.5038525 93.7494885,42.63857 93.6651041,43.5252052 C92.7019529,53.6451182 88.5344133,78.2034783 86.4142057,89.5379542 C85.5170662,94.3339958 83.750571,95.9420841 82.0403991,96.0994568 C78.3237996,96.4414641 75.5015827,93.6432685 71.9018743,91.2836143 C66.2690414,87.5912212 63.0868492,85.2926952 57.6192095,81.6896017 C51.3004058,77.5256038 55.3966232,75.2369981 58.9976911,71.4967761 C59.9401076,70.5179421 76.3155302,55.6232293 76.6324771,54.2720454 C76.6721165,54.1030573 76.7089039,53.4731496 76.3346867,53.1405352 C75.9604695,52.8079208 75.4081573,52.921662 75.0095933,53.0121213 C74.444641,53.1403447 65.4461175,59.0880351 48.0140228,70.8551922 C45.4598218,72.6091037 43.1463059,73.4636682 41.0734751,73.4188859 C38.7883453,73.3695169 34.3926725,72.1268388 31.1249416,71.0646282 C27.1169366,69.7617838 23.931454,69.0729605 24.208838,66.8603276 C24.3533167,65.7078514 25.9403832,64.5292172 28.9700376,63.3244248 Z'/%3E%3C/svg%3E");
}
/* Почта — не бренд, поэтому не цветная: тот же приглушённый #6b5c4d, которым
   в этом файле набрано всё второстепенное. */
.contact-link[data-contact="mail"]::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='2' y='4' width='16' height='12' rx='2' fill='none' stroke='%236b5c4d' stroke-width='1.5'/%3E%3Cpath d='M3 5.5L10 11L17 5.5' fill='none' stroke='%236b5c4d' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.import-input { margin-bottom: 0.6em; }
/* Сцена-шапка. Во всю ширину ОКНА, а не внутри колонки #create: полоса,
   обрезанная по 34rem, читается как баннер внутри страницы, а не как её верх.
   Приём до 2026-07-30 — отрицательные поля на padding родителя (-1rem) — этот
   замысел выполнял только на телефоне, где 34rem шире экрана. Замер рендером
   ДО правки, окно 1920: полоса 640..1280 при карточке 656..1264, то есть она
   торчала ровно по 16px с каждой стороны — съехавшая вёрстка, а не шапка
   (замечание владельца).
   calc(50% - 50vw) растягивает полосу до края окна на ЛЮБОЙ ширине; про
   скроллбар в 100vw — см. `html { overflow-x: clip }` в шапке файла.
   padding-inline ровно 1rem (был 1.1rem): это padding самого #create, и
   только при точном совпадении левый край заголовка садится на левый край
   карточки пиксель в пиксель. Прежние 1.1rem давали расхождение и на телефоне
   тоже — 2px, замер ДО: h1 на 18px против карточки на 16px. Замер ПОСЛЕ на
   360/768/1024/1920 и на всех трёх страницах: 16/128/208/656 у обоих, ноль
   расхождения. */
.scene { position: relative; isolation: isolate;
  margin: -1rem calc(50% - 50vw) 1.4em; padding: 2.2em 1rem 1.6em;
  background: var(--scene-bg); color: var(--scene-ink); overflow: hidden; }
.scene.compact { padding-top: 1.5em; padding-bottom: 1.2em; margin-bottom: 1em; }

/* Содержимое полосы — обратно в колонку карточек. Заливка идёт во всю ширину
   окна, текст — нет: строка в 1900px не читается, а край, разошедшийся с
   карточкой, и был исходным замечанием. --column — то же самое число, из
   которого собрана max-width #create: см. :root. */
.scene-body, #create[data-game] .scene-art { max-width: var(--column); margin-inline: auto; }

/* Арт — НАСТОЯЩАЯ картинка в потоке, а не подложка под текстом (правка
   2026-07-30 по замечанию «картинка идёт обрезанной полосой»). Раньше слой
   лежал position: absolute на всю полосу, текст стоял поверх, и от фермы
   оставалась горизонтальная нарезка. Замер ДО правки: шаг 1 воронки — полоса
   640×103 (1920) и 360×124 (360), гостевая — 640×120, при картинке 900×300.
   То есть 6:1 против 3:1 у файла: половина композиции срезана сверху и снизу,
   на экране текстура, а не ферма.
   aspect-ratio 3/1 — СОБСТВЕННАЯ пропорция файла (900×300). При ней cover не
   режет ничего ни на 360, ни на 1920: видно ровно ту картинку, которую
   рисовали. Фиксированная высота в em этого дать не может — «полоса 6em» на
   1920px и на 360px это две разные картинки.
   Цена решения: на широком окне арт не идёт от края до края окна, он сидит в
   колонке (замер ПОСЛЕ: 328×109 на 360, 608×203 на 1920). Обратный размен —
   арт во всю ширину окна, но тогда на 1920 полоса становится 9.5:1 и картинку
   снова режет вдвое сильнее, чем до правки; выбрана целая композиция.
   ГАРАНТИЯ КОНТРАСТА выводится заново и теперь не числовая, а
   конструктивная: .scene-art — отдельный блок в потоке, .scene-body идёт
   ПОСЛЕ него (screens.js: sceneHeader кладёт их в этом порядке), значит текст
   физически не может лежать на арте ни при какой длине заголовка, ширине окна
   и яркости картинки следующей игры. Прежняя гарантия держалась на
   неравенстве «стоп градиента < padding текста» и ломалась молча, стоило
   тронуть любое из двух чисел. Замер контраста ПОСЛЕ (пиксель фона над и под
   строкой на живой странице): --scene-ink 11.74:1, --scene-kicker 7.73:1 —
   одинаково на 360, 768 и 1920 именно потому, что под текстом ровный
   --scene-bg. */
.scene-art { background-image: var(--scene-art); background-size: cover;
  background-position: var(--scene-focal); background-repeat: no-repeat; }
#create[data-game] .scene-art { position: relative; aspect-ratio: 3 / 1;
  margin-bottom: 1.2em; border-radius: 14px; overflow: hidden; }
/* Нижний край картинки растворяется в полосе — иначе арт кончается резкой
   линией посреди тёмного фона. Проценты здесь снова законны: высота слоя
   задана aspect-ratio от ширины колонки и от длины текста больше не зависит
   (ровно та связь, на которой погорела карточка-«окно», см. .window-art). */
#create[data-game] .scene-art::after { content: ''; position: absolute; inset: 0;
  background: linear-gradient(to bottom, transparent 62%,
    color-mix(in srgb, var(--scene-bg) 62%, transparent) 100%); }
.scene-body { position: relative; }
.scene-kicker { margin: 0 0 0.5em; font-family: 'HB Head', sans-serif; font-weight: 700;
  font-size: var(--step-kicker); letter-spacing: 0.11em; text-transform: uppercase;
  color: var(--scene-kicker); }
.scene h1 { margin: 0; font-size: var(--step-scene); line-height: 1.25; color: var(--scene-ink); }
/* .statement — заголовок-высказывание (каталог, гостевая), не название игры:
   на ступень ниже (§7 спеки). НЕ путать с .compact — тот про высоту самой
   полосы (шаг 1 воронки) и её не наследует: связав кегль текста с ростом
   полосы, следующая игра с другим сочетанием длины заголовка и высоты
   полосы не смогла бы выразить своё сочетание отдельно от этого. */
.scene.statement h1 { font-size: var(--step-screen); }
/* Средняя ступень полосы: второе предложение-высказывание (screens.js:
   sceneHeader — pitch). Появилась по разбору владельца 2026-07-30: два сильных
   предложения, набранные одним кеглем в один абзац, «слипаются в многобукв».

   РАЗВЕДЕНЫ СТУПЕНЬЮ И ВОЗДУХОМ, А НЕ ЦВЕТОМ, хотя цвет предлагался первым.
   Акцент на этих страницах означает ровно одно — действие, двигающее вперёд
   (92d2358, закреплено visual-system.test.mjs). В тёмной полосе золото уже
   занято двумя смыслами: подписью студии (.scene-kicker) и кнопкой; третий
   смысл «а это просто другая фраза» сломал бы первые два.

   Отсюда все три числа ниже, и ни одно не косметическое:
   • --step-lead (между --step-screen заголовка и --step-body подводки): без
     своего кегля эта строка неотличима от .scene-lead под ней, и ступеней
     снова две вместо трёх — именно это и было на первом собранном стенде.
   • 1.1em сверху против 0.6em у .scene-lead: воздух и есть вторая половина
     разделения. Отбивка больше, чем внутри пары «предложение — подводка»,
     говорит, где кончается мысль, а где продолжается.
   • Полная непрозрачность --scene-ink, в отличие от 86% у подводки: это
     по-прежнему высказывание, а не пояснение к нему. */
.scene-pitch { margin: 1.1em 0 0; font-size: var(--step-lead); line-height: 1.35;
  color: var(--scene-ink); }
.scene-lead { margin: 0.6em 0 0; font-size: var(--step-body); line-height: 1.45;
  color: color-mix(in srgb, var(--scene-ink) 86%, transparent); }

/* Каталог без картриджа: картинки нет вовсе (--scene-art: none), поэтому слой
   возвращается к своей второй роли — вуали на всю полосу. Пропорцию ему
   давать не за что, а пустая полоса 3:1 над заголовком читалась бы как
   недогрузившаяся картинка. Мягкий тёплый градиент вместо плоской заливки. */
#create:not([data-game]) .scene-art {
  position: absolute; inset: 0; z-index: -1;
  /* Центр блика 50%, а не 20% (правка вслед за полосой во всю ширину окна).
     Пока полоса была шириной колонки, 20% приходились на её левую четверть —
     рядом с текстом. Растянутая до 1920px, та же цифра увела блик на 384px,
     то есть в пустое поле слева от колонки: слева светлое пятно, справа
     плоская темнота, текст между ними (снимок 1920, до правки). Симметричное
     свечение сверху не зависит от ширины окна вовсе. */
  background-image: radial-gradient(120% 140% at 50% 0%,
    color-mix(in srgb, var(--wood-light, #e6c88a) 22%, var(--scene-bg)) 0%,
    var(--scene-bg) 62%);
}

/* Объяснение продукта на входе сборщика. Читает его человек с рекламы, который
   игру не видел, — поэтому размер обычный текстовый, а не мелкий «служебный»:
   это не примечание, а единственное место, где сказано, что он собирает. */
.intro { margin-bottom: 1.4em; }
.intro .lead { margin: 0.6em 0; line-height: 1.45; }
/* Подводка каталога - на светлой части страницы, под полосой (решение владельца
   2026-07-30: полоса продаёт, эта строка объясняет устройство, и делать обе
   работы одним тёмным блоком не надо).

   Кегль наследуется от body, то есть --step-body: владелец сказал «размер шрифта
   тот же», и ступень здесь ни при чём - строка сменила хозяина, а не вес.
   Цвет тоже обычный --ink, а не --ink-muted: это не примечание, а описание
   товара, и приглушать его нечем оправдать.

   Отбивка сверху 0, а не 0.6em как у .intro .lead: полоса уже принесла свои
   1.4em снизу (margin у .scene), и вторая отбивка сложилась бы с ней в провал
   между шапкой и текстом.

   Снизу МЕНЬШЕ, чем сверху (0.9em против 1.4em полосы), и это не подгонка
   пикселей. Строка описывает товар, то есть принадлежит карточке под собой, а не
   полосе над собой; равные зазоры сделали бы из неё ничью - висящую отдельно
   строку посреди пустого места. Замер рендером на 360px: 22px сверху, 14px снизу.

   Чего перенос НЕ дал, чтобы этого не искали в будущем: страница выше не стала.
   Полоса действительно осела заметно (на 360px её высота 226px против трёх
   текстовых блоков прежде), но карточка игры при этом сдвинулась ВНИЗ, а не
   вверх: строка снаружи стоит дороже той же строки внутри полосы, потому что
   снаружи у неё свои зазоры с обеих сторон. Точную разницу в высоте здесь не
   привожу намеренно - замерить её переносом узла в живом документе не удалось
   честно (два прогона дали 13px и 9px, потому что перенос между по-разному
   стилизованными родителями не воспроизводит прежнюю вёрстку в точности), а
   ради цифры в комментарии собирать прежнюю сборку не стоит. Смысл переноса - в
   разделении работ: полоса продаёт, строка объясняет устройство. Высота тут ни
   при чём. */
.catalog-lead { margin: 0 0 0.9em; line-height: 1.45; }

/* Тарифы в НИЗУ карточки игры (screens.js: gameTerms). Переехали сюда с корня
   hb-games.ru 2026-08-05 и в тот же день - внутрь карточки: у каждой игры будут
   свои тарифы, и общий блок на весь каталог смешал бы их, как только игр
   станет две.

   Ряд юрссылок «Оферта · Сроки · Возврат» отсюда убран 2026-08-08: им место на
   странице заказа (ст. 26.1) и в подвале site-requisites, а не в карточке
   впритык к цене. Разбор - в шапке gameTerms (screens.js).

   Живут на тёмном фоне карточки, поэтому цвета берутся от --scene-ink, а не от
   --ink: те же приёмы, что у .window-note строкой выше.

   Отбивка сверху и линия: блок отделён от кнопок волосяной чертой. Рамки и
   плашки вокруг него нет намеренно - коробка внутри коробки читается как
   «условия, которые надо изучить», а владелец ровно за это разбирал страницу
   заказа. Черта делает ту же работу одним пикселем.

   Тарифы - маркированный список с ценой в <b> и на обычной ступени: цену ищут
   глазами, и она обязана находиться без чтения. */
.game-terms {
  margin-top: 1.1em;
  padding-top: 0.9em;
  border-top: 1px solid color-mix(in srgb, var(--scene-ink) 22%, transparent);
}
.game-tiers {
  margin: 0 0 0.6em;
  padding-left: 1.2em;
  line-height: 1.45;
  color: color-mix(in srgb, var(--scene-ink) 88%, transparent);
}
.game-tiers li { margin-bottom: 0.3em; }
.game-tiers b { font-weight: 700; color: var(--scene-ink); }

.error { color: #c2410c; font-size: var(--step-small); }
/* Явное продолжение после сообщения о состоянии подписи вставленной ссылки
   (create/js/edit.js: contBtn, план 4C). Тихая пилюля как у .free-confirm, а
   не второй .primary — переход тут уже не первое действие на странице, а
   второе, после того как сообщение прочитано. */
.continue-btn { margin-top: 0.6em; padding: 0.6em 1.1em; border: 1px solid #e0d3c0;
  border-radius: 999px; background: #fff; }
/* Незаполненное поле после попытки уйти с шага. Рамка, а не заливка и не
   затемнение: поле остаётся рабочим и обычным на вид, помечено лишь то, куда
   надо вернуться. Пометка появляется ПОСЛЕ «Далее» (screens.js: state.fieldsGap),
   поэтому не ругается на поле, которое ещё пишут. */
.field-gap textarea, .field-gap input[type="text"] { border-color: #c2410c; }
.field-gap label { color: #c2410c; }
/* Причина — под полями шага, а не над ними: сверху она отодвигала бы поля вниз
   при каждом нажатии «Далее», и экран прыгал бы под пальцем. */
.fields-gap { margin-top: 1em; }

.nav-bar { display: flex; gap: 0.6em; margin-top: 1.2em; }
.nav-bar .nav-back { flex: 0 0 auto; padding: 0.8em 1.1em; border: 1px solid #e0d3c0;
  border-radius: 999px; background: none; }
.nav-bar .nav-next { flex: 1; margin-top: 0; }
/* Ряд без «Далее» - это вычитка: там движение вперёд несут собственные кнопки
   экрана, и общая «Далее» была бы вторым способом сделать то же (app.js:
   navBar). Оставшаяся одна «Назад» рисовалась узкой пилюлей у левого края -
   ряд читался недорисованным, будто вторая кнопка не загрузилась (разбор
   владельца 2026-08-05).
   Растягиваем на всю колонку: вся страница набрана в одну колонку кнопок во
   всю ширину, и одиночная «Назад» обязана попадать в тот же ритм. Заливки
   ей это не даёт - она остаётся контурной, то есть по-прежнему тише главного
   действия экрана, которое стоит выше. */
.nav-bar .nav-back:only-child { flex: 1; }

/* Сброс формы. Владелец: «маленькая кнопочка сброса без лишних указаний на неё»
   (2026-07-30). Отсюда всё: НЕ кнопка по форме, а строка мелким кеглем, без
   рамки и без заливки; по центру, чтобы не соперничать с «Назад» слева и
   «Далее» справа; отбивка 1.6em - больше, чем зазор внутри .nav-bar (0.6em), -
   потому что промах по этому контролу необратим, а стоит он в шести пикселях от
   «Назад» (app.js: resetZone, там же - почему одна на все шаги, а не по одной
   в каждом).

   Цвет --ink-muted, а не красный: красное здесь означало бы ошибку, а сброс -
   не ошибка, а решение. Красной осталась только «Убрать» на экране «Готово!»
   (.forget-btn.destructive) - там кнопка стоит среди нейтральных соседей и
   обязана от них отличаться. Контраст --ink-muted на --surface замерен
   рендером: 5.36:1, выше AA для мелкого текста.

   Подчёркивание при наведении, а не постоянное: постоянное сделало бы из неё
   ссылку, то есть «переход куда-то», - а это действие здесь и сейчас. */
.reset-zone { margin-top: 1.6em; text-align: center; }
.reset-ask { padding: 0.5em 0.9em; border: 0; background: none; cursor: pointer;
  font-size: var(--step-small); color: var(--ink-muted); }
.reset-ask:hover { text-decoration: underline; color: var(--ink); }
.reset-ask:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px;
  border-radius: 0.5em; }
/* Панель подтверждения - тот же вид, что у бесплатного выхода на вычитке
   (.free-note ниже): человек уже видел этот приём в этой же воронке, и второй
   язык для того же разговора ему не нужен. Текст слева, а не по центру
   родителя: читают его как обычный абзац.
   text-align: left задан здесь ещё и потому, что .reset-zone выравнивает по
   центру, и без сброса ряд кнопок внутри панели уехал бы к середине - это уже
   третье выравнивание на трёх соседних строках, ровно то, за что владелец и
   разобрал этот узел 2026-08-05. */
.reset-note { margin-top: 0.8em; padding: 0.9em 1em; background: #f3e9da;
  border: 1px solid #e0d3c0; border-radius: 0.9em; text-align: left; }
.reset-note p { margin: 0 0 0.7em; font-size: var(--step-small); }
/* Оба ответа - в одном ряду, под вопросом (app.js: resetZone). Отказ переехал
   сюда из триггера, где он был переименованной кнопкой «Очистить форму», то
   есть стоял ВЫШЕ вопроса, на который отвечает.
   flex-wrap обязателен: на 320px «Да, очистить» и «Не надо» в одну строку не
   помещаются, и без переноса вторая кнопка вылезала бы за скруглённый край
   панели. */
.reset-actions { display: flex; flex-wrap: wrap; gap: 0.6em; }
.reset-confirm { padding: 0.6em 1.1em; border: 1px solid #e0d3c0;
  border-radius: 999px; background: var(--surface-raised); font-weight: 600;
  cursor: pointer; }
.reset-confirm:hover { background: var(--surface-sunken); }
/* Отказ тише согласия, но остаётся кнопкой во всю высоту согласия: промах
   пальцем по этой паре стоит формы, и делать «Не надо» мелкой строкой значило
   бы отдать ей меньшую площадь, чем необратимому действию рядом. Без рамки и
   заливки - вес несёт «Да, очистить», а это его пара, а не соперник. */
.reset-cancel { padding: 0.6em 1.1em; border: 1px solid transparent;
  border-radius: 999px; background: none; color: var(--ink-muted);
  font-weight: 600; cursor: pointer; }
.reset-cancel:hover { background: var(--surface-sunken); color: var(--ink); }

/* ОЧИСТКА ПАМЯТИ на экране прошлой ссылки (screens.js: forgetZone). Тот же
   приём и тот же вид, что у «Очистить форму» выше, - буквально те же значения,
   а не «похожие»: это один и тот же разговор («точно?» - «да» / «не надо»), и
   двух его наречий в продукте быть не должно.

   Свои имена классов, а не переиспользованные .reset-*: те названы по сбросу
   ФОРМЫ, и повесить их на удаление ЗАПИСИ значило бы соврать следующему
   читателю. Значения при расхождении править парами.

   До 2026-08-08 на этом месте стояла красная плашка «Убрать с этого
   устройства», а подтверждение приходило после - отдельным экраном во весь
   рост. Теперь вопрос стоит до действия, а согласие уводит на витрину. */
.forget-zone { margin-top: 1.6em; text-align: center; }
.forget-ask { padding: 0.5em 0.9em; border: 0; background: none; cursor: pointer;
  font-size: var(--step-small); color: var(--ink-muted); }
.forget-ask:hover { text-decoration: underline; color: var(--ink); }
.forget-ask:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px;
  border-radius: 0.5em; }
.forget-note { margin-top: 0.8em; padding: 0.9em 1em; background: #f3e9da;
  border: 1px solid #e0d3c0; border-radius: 0.9em; text-align: left; }
.forget-note p { margin: 0 0 0.7em; font-size: var(--step-small); }
.forget-actions { display: flex; flex-wrap: wrap; gap: 0.6em; }
.forget-confirm { padding: 0.6em 1.1em; border: 1px solid #e0d3c0;
  border-radius: 999px; background: var(--surface-raised); font-weight: 600;
  cursor: pointer; }
.forget-confirm:hover { background: var(--surface-sunken); }
.forget-cancel { padding: 0.6em 1.1em; border: 1px solid transparent;
  border-radius: 999px; background: none; color: var(--ink-muted);
  font-weight: 600; cursor: pointer; }
.forget-cancel:hover { background: var(--surface-sunken); color: var(--ink); }

/* Ссылка — одна строка: 400+ символов payload абзацем (word-break: break-all)
   занимали пол-экрана и выдавливали кнопки вниз. Поле нужно только как запасной
   путь «выделить и скопировать руками», поэтому оно неброское и не переносится;
   text-overflow здесь не сработает (это input с прокруткой), и это правильно —
   конец ссылки достижим стрелкой, а Ctrl+A берёт её целиком. */
/* Не `.done .final-link`: ту же пару «поле со ссылкой + кнопка открыть»
   показывает экран входа создателя с прошлой ссылкой (.screen.released). */
/* 16px, не мельче. Это <input>, рассчитанный ровно на «выделить руками», и на
   0.8em (12.8px) iOS Safari зумил страницу при фокусе — прямое нарушение
   правила из шапки этого файла. Ссылка длинная, но она и не должна читаться
   глазами: её выделяют и копируют. */
/* cursor: pointer — единственный намёк на то, что поле кликабельно (правка
   2026-08-05: клик по нему копирует ссылку в буфер, screens.js). Поле выглядит
   как обычный input, то есть как место для ввода, а не как действие; без смены
   курсора возможность просто не обнаруживается. Отдельной подписи «нажмите,
   чтобы скопировать» не заводим: рядом стоит кнопка «Скопировать», и вторая
   инструкция про то же самое — лишняя строка на экране, где человек уже
   получил свой товар. */
.final-link { display: block; width: 100%; box-sizing: border-box;
  padding: 0.55em 0.7em; border: 1px solid var(--line-soft); border-radius: 0.7em;
  background: var(--surface-raised); font-size: 16px; color: var(--ink-muted);
  cursor: pointer; }
/* Обе кнопки экрана памяти — по центру (правка 2026-08-08, разбор владельца:
   «какой-то хаос из кнопок»). .offer-link по умолчанию прижимает текст влево, а
   .open-link центрировала его через display: block — и на экране прошлой ссылки
   две одинаковые на вид плашки стояли по-разному: верхняя по центру, нижняя
   слева. Разное выравнивание у одинаковых форм и читалось как хаос; появилось
   оно не решением, а порядком правил в этом файле.

   justify-content, а не только text-align: .offer-link — inline-flex, а
   text-align на флекс-контейнере его элементы не двигает. text-decoration тут
   больше не нужен — его и так задаёт .offer-link. */
.open-link, .start-fresh, .reuse-btn { justify-content: center; text-align: center; }
/* ЗОЛОТО ПЕРЕЕХАЛО НА ПОКУПКУ (решение владельца 2026-08-10, вечер).
   До этого заливку носила «Собрать новую игру» (правка 2026-08-08), и тогда это
   было верно: на экране прошлой ссылки она была единственным действием вперёд.
   Теперь рядом стоит «Добавить за <цена>» — платный выпуск с данными прошлого
   праздника, — и вперёд двигает она. Само правило «заливка означает действие,
   двигающее вперёд» не менялось: сменилось то, какое из двух действий им
   является. Чистый лист — возврат к началу, а не движение.

   Заливка та же, что у .start-btn в хабе (--accent). Правило стоит ПОСЛЕ
   .offer-link в каскаде, поэтому фон/рамку/цвет перекрывает при равной
   специфичности (та же механика каскада, что разобрана у .share-btn ниже,
   только здесь она в нашу пользу).

   Белый список носителей акцента держит tests/visual-system.test.mjs — при
   переезде заливки правится и он, иначе сторож молча разрешит лишнее. */
.reuse-btn {
  background: var(--accent); border-color: var(--accent);
  color: var(--action-ink); font-weight: 700;
}
.reuse-btn:hover, .reuse-btn:focus-visible {
  background: var(--action-hover); border-color: var(--action-hover);
}
/* ЗАЗОР МЕЖДУ КНОПКАМИ БЛОКА ПОВТОРНОЙ СБОРКИ (правка 2026-08-10).
   У .offer-link своих вертикальных полей нет: на всех прежних экранах между
   двумя такими плашками всегда что-то стояло — статус отправки, подпись,
   пустая строка. В блоке «Собрать ещё одну» после ПЛАТНОЙ записи объяснения
   про платную версию нет (купившему рассказывать нечего), и «Собрать платную
   на базе этой» встала вплотную к золотой «Собрать новую игру»: две плашки
   разного веса срослись в одну (снимок 390px, 2026-08-10).

   Соседский селектор, а не margin на самих кнопках: поле нужно ТОЛЬКО между
   ними, и первая кнопка не должна отодвигаться от подводки над собой. Значение
   то же, что у .offer (0.6em) — ритм второстепенных кнопок в продукте один. */
.reuse-zone .offer-link + .offer-link { margin-top: 0.6em; }
/* Главное действие экрана «готово»: человек только что заплатил и обязан унести
   ссылку. Раньше эта кнопка была неотличима от «Письмом себе» и «Скачать
   файлом» — три одинаковые белые пилюли подряд, и обязательное действие
   терялось среди запасных. Теперь она несёт класс .primary (screens.js:
   doneScreen) — ту же сплошную заливку --action, что и остальные кнопки
   движения вперёд по продукту, — а не отдельный, второй по силе стиль.
   .open-link («Открыть приглашение») понижен до второстепенной .offer-link.
   .share-btn здесь оставляет только то, что специфично для НЕЁ и не входит
   в .primary: flex-раскладку под иконку и саму иконку. Заливку/рамку/цвет
   текста .share-btn больше не задаёт — раньше они были объявлены здесь ЖЕ,
   позже .primary в исходнике, и по правилу равной специфичности побеждали:
   кнопка держала класс .primary, но фактический background оставался
   #fff8e8 (замер computed style в headless Chrome, ревью 2026-07-30) —
   «заливка» из комментария выше не переезжала никуда, тест-регулярка по
   тексту CSS (visual-system.test.mjs) этого не видит вовсе, она ищёт
   правило `background: var(--action)`, а не то, что реально побеждает в
   каскаде. */
.share-btn { display: flex; align-items: center; justify-content: center; gap: 0.6em; }
.share-btn::before {
  content: ''; flex: 0 0 1.3em; width: 1.3em; height: 1.3em;
  background-repeat: no-repeat; background-position: center; background-size: contain;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2V12' stroke='%233b2a1e' stroke-width='1.6' stroke-linecap='round'/%3E%3Cpath d='M6 6L10 2L14 6' fill='none' stroke='%233b2a1e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M4 9V15.5C4 16.3 4.7 17 5.5 17H14.5C15.3 17 16 16.3 16 15.5V9' fill='none' stroke='%233b2a1e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* Пустой статус не должен занимать место до первого нажатия. */
.share-status { margin: 0.6em 0 0; text-align: center; font-size: var(--step-small); color: var(--ink-muted); }
.share-status:empty { display: none; }
.qr-slot img { display: block; margin: 1em auto 0; max-width: 100%; height: auto; }

/* Приглушение — ФОНОМ И РАМКОЙ, не цветом текста и никогда не opacity.
   Замер по палитре: --muted #8a7a6a на --surface даёт 3.89:1 и уже не проходит
   AA 4.5:1, а WCAG прощает это только для disabled — ровно та лазейка,
   которой здесь пользоваться нельзя. opacity вдобавок множит контраст всего
   поддерева и незаметно уводит счётчики в 2:1 (§5). */
.showcase { background: #f3e9da; border: 1px solid #e0d3c0; border-radius: 0.9em;
  padding: 0.9em 1em; margin: 1.1em 0; }
.showcase-lead { margin: 0 0 0.6em; color: var(--ink-muted); font-size: var(--step-small); } /* 5.36:1 ✓ */
.showcase-q { margin: 0.45em 0; color: var(--ink); }

/* Карточка приглашения — с 2026-07-29 на экране вычитки, а не на шаге
   вопросов (перенесена решением владельца, screens.js: proofScreen). Показывает
   приглашение ЦЕЛИКОМ: к этому шагу логистика и тёплые слова уже введены, их
   требует заслон «Далее». Строки .mini-lead («Скоро праздник!») здесь больше
   нет — она была половиной титульной сцены игры и вместе с заголовком
   склеивалась в предложение, которого нет нигде (screens.js: inviteCard). */
.mini-card { background: #fff; border: 1px solid #e0d3c0; border-radius: 0.9em;
  padding: 1.1em 1em; text-align: center; }
/* overflow-wrap обязателен: 20-значное имя на 360 px иначе распирает панель. */
.mini-title { margin: 0; font-weight: 700; font-size: var(--step-screen);
  overflow-wrap: anywhere; }
.mini-text { margin: 0.7em 0 0; line-height: 1.4; overflow-wrap: anywhere; }
/* Логистика — сеткой «подпись: значение», как в вычитке: одинаковые вещи в
   продукте обязаны выглядеть одинаково. Слева по краю, а не по центру —
   центрированные пары «Когда/Где» не дают глазу колонки для сверки. */
.mini-rows { display: grid; grid-template-columns: auto 1fr; gap: 0.25em 0.7em;
  margin: 0.9em 0 0; text-align: left; }
.mini-rows dt { color: var(--ink-muted); font-size: var(--step-small); }
.mini-rows dd { margin: 0; overflow-wrap: anywhere; }

/* Прошлая выпущенная ссылка, свёрнутая нажатием «Собрать ещё одну»
   (screens.js: releasedBar). Тёплая плашка, а не красная: ничего не сломалось,
   человеку просто напоминают, что ссылка на месте. Над формой и до полосы
   шагов — тот, кто решил, что потерял оплаченное, до низа экрана не доскроллит. */
.released-bar { display: flex; gap: 0.6em; align-items: center; flex-wrap: wrap;
  margin-bottom: 1em; padding: 0.7em 0.9em; background: #fff8e8;
  border: 1px solid var(--accent); border-radius: 0.9em; font-size: var(--step-small); }
.released-bar span { flex: 1 1 12em; }
.released-show { padding: 0.4em 1em; border: 1px solid var(--accent);
  border-radius: 999px; background: #fff; color: var(--ink); font-weight: 600; }

/* Группа выбора вопросов. Шапки-легенды у блока больше нет: её роль исполняет
   h1 экрана, стоящий вплотную сверху (screens.js: TARIFF_LEGEND остался только
   как aria-label группы). Правило .choice-legend удалено вместе с ней — оно
   осиротело и красило бы пустоту. Блок выглядит законченным без неё за счёт
   того, что разделитель ушёл с ПЕРВОЙ строки на промежутки между строками:
   border-top на каждой .choice ложился вплотную под рамку контейнера и давал
   двойную линию. Замер по пикселям снимка (1920, x=960): было две строки
   #e0d3c0 подряд, y 119–120; стало одна, y 119. Двойная линия сверху читается
   как остаток срезанной шапки — блок выглядит недоделанным именно ею. */
.tariff-choice { margin: 1.2em 0; border: 1px solid #e0d3c0; border-radius: 0.9em;
  overflow: hidden; background: #fff; }
/* Сетка «точка | подпись», а не padding-left: точка обязана стоять в своей
   колонке, иначе вторая и третья строки подписи (цена, примечание) заезжают
   под неё. Колонка одна на любое число строк — .choice > * уводит в неё всех
   детей, сколько бы их ни завёл следующий тариф. */
.choice { display: grid; grid-template-columns: auto 1fr; column-gap: 0.7em;
  padding: 0.85em 1em; cursor: pointer; }
.choice + .choice { border-top: 1px solid #e0d3c0; }
.choice > * { grid-column: 2; }
/* Носитель выбора — ФОРМА, а не только цвет (WCAG 1.4.1 и просто понятность:
   владелец «кнопки надо сделать более очевидными»). Разметка радиогруппой была
   правильной с самого начала (role="radio" + aria-checked, screens.js), но
   глазу об этом не говорило ничто, кроме заливки строки. Пустое кольцо против
   кольца с точкой читается и в чёрно-белой печати, и на солнце, где заливка
   почти не видна, — тем же приёмом, что галочка у .answer.on выше.
   Рамка через --muted: это роль РАМКИ, ровно та, для которой токен и оставлен
   (см. :root). Замер по пикселям снимка живого шага 3: пустое кольцо #8a7a6a
   на белой строке — 4.14:1, кольцо с точкой #3b2a1e на --surface-sunken —
   11.38:1. Порог WCAG 1.4.11 для нетекстовой графики 3:1. */
.choice::before { content: ''; grid-column: 1; align-self: start;
  width: 1.15em; height: 1.15em; margin-top: 0.15em;
  border: 2px solid var(--muted); border-radius: 50%; }
/* Точка радиальным градиентом, а не вложенной тенью: inset-тень пришлось бы
   красить в фон строки, а он у выбранной строки другой (--surface-sunken), —
   и цвет точки поехал бы за состоянием. Здесь центр прозрачен, фон подставляет
   сама строка. */
.choice.on::before { border-color: var(--ink);
  background: radial-gradient(circle at 50% 50%, var(--ink) 0 45%, transparent 47%); }
.choice-title { display: block; font-weight: 600; }
.choice-note { display: block; color: var(--ink-muted); font-size: var(--step-small); margin-top: 0.15em; }
/* Цена в одну строку с названием тарифа по базовой линии, весом 600. */
.choice-price { display: block; margin-top: 0.15em;
  font-size: var(--step-body); font-weight: 600; color: var(--ink); }
/* Полоса слева остаётся — она читается и без цвета, на солнце в том числе.
   Заливка мягче: выбранный тариф это состояние, а не действие. */
.choice.on { background: var(--surface-sunken); box-shadow: inset 0.25em 0 0 var(--action); }
.choice:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }

/* Выход обязан быть читаемым: --muted даёт 3.89:1 и не проходит AA (§7.1). */
.free-exit { display: block; width: 100%; margin-top: 1em; padding: 0.6em;
  background: none; border: 0; color: var(--ink-muted); text-decoration: underline; }
.free-note { margin-top: 0.8em; padding: 0.9em 1em; background: #f3e9da;
  border-radius: 0.9em; }
.free-note p { margin: 0 0 0.7em; }
.free-confirm { padding: 0.6em 1.1em; border: 1px solid #e0d3c0;
  border-radius: 999px; background: #fff; }
.immutable-note { color: var(--ink-muted); font-size: var(--step-small); margin-top: 1em; }
/* Восстановление по номеру заказа на экране вычитки. Своя подложка, а не голое
   поле в потоке: без неё ввод номера встаёт вплотную к кнопке выпуска и
   читается как ещё одно поле приглашения — то есть как работа, которую от
   человека требуют, вместо выхода для того, кто уже заплатил. Форма тут
   невозможна (form-action 'none'), поэтому это поле с обычной кнопкой. */
.recover { margin-top: 1.2em; padding: 0.9em 1em; background: #f3e9da;
  border-radius: 0.9em; }
.recover .recover-label { margin-top: 0; }
.recover .secondary { width: 100%; margin-top: 0.6em; padding: 0.8em; background: #fff;
  border: 1px solid #e0d3c0; border-radius: 999px; touch-action: manipulation; }

/* Тихая строка под кнопкой оплаты (pay.js). Без своего правила она рисовалась
   бы обычным основным текстом и спорила бы с самой кнопкой за внимание —
   ровно то, чего пояснение под ней делать не должно. */
.muted-note { color: var(--ink-muted); font-size: var(--step-small); margin-top: 0.7em; }

@media (min-width: 60rem) {
  /* На десктопе sticky-панель с главной кнопкой РАЗРЕШЕНА — запрет из §4.2
     вызван дракой position: fixed с экранной клавиатурой iOS и сюда не тянется. */
  .screen .primary { position: sticky; bottom: 1.5rem; }

  /* …кроме вычитки. Sticky сдвигает коробку, оставляя её место в потоке, и со
     свойством bottom сдвиг идёт ВВЕРХ — то есть поверх всего, что стоит перед
     кнопкой в том же блоке. На шагах формы это безобидно: перед кнопкой поля,
     и человек их уже заполнил. На вычитке перед кнопкой стоит ровно то, ради
     чего экран существует, и в невысоком окне кнопка это накрывала: 2026-07-29
     дважды подряд — сначала строки карточки, потом «Пройти игру целиком»
     (замерено при высоте окна 577px: pay 786–839 против play 779–825).
     Перестановка блоков от этого не лечит, она лишь меняет, кого накроет
     следующим, — лечит только отказ от sticky здесь.
     И терять на вычитке нечего: кнопка и так последняя на экране, липнуть ей
     некуда. Смысл sticky — не заставлять листать назад к кнопке; здесь листать
     назад значит перечитывать то, что просили проверить. */
  .screen.proof .primary { position: static; }

  /* …и «Готово!» — тот же механизм, новый носитель (находка ревью 2026-07-30).
     `.share-btn` («Отправить / Скопировать») получила класс `.primary` в этой
     же задаче (акцент переехал на неё с «Открыть приглашение» — см. doneScreen)
     и тем самым унаследовала sticky от общего правила выше. На окне 1024×330
     кнопка прибивалась к низу в 24px и накрывала `.final-link` (саму ссылку)
     и половину «Открыть приглашение» — то есть ровно то, что человек должен
     сначала увидеть и прочитать, прежде чем нажать «Отправить». Порог тот же,
     что у вычитки: ширина ≥60rem и невысокое окно.
     У правила `.screen .primary { position: sticky }` после этой задачи
     остаётся всего два живых потребителя — `.screen.released .primary`
     («Собрать новую игру» в пустом состоянии, единственная кнопка на коротком
     экране, накрывать ей нечего) и эта самая `.share-btn`. Точечная static-
     оговорка по образцу `.screen.proof` выше — минимальная правка без риска
     тронуть `.nav-next`, который в это правило вообще не попадает (nav-bar —
     не потомок `.screen`, а сосед: root.appendChild кладёт их рядом, app.js:
     render) и sticky-поведения никогда не имел; менять его в рамках этой
     находки не в брифе и не проверено рендером.
     Дальше по файлу (блок «Движение») на этот же селектор ложится
     `position: relative` — опора для блика. Это НЕ отменяет находку: и static,
     и relative одинаково не sticky, а `.screen .primary` (0,2,0) проигрывает
     `.screen.done .primary` (0,3,0) по весу независимо от порядка, так что
     прибиться к низу кнопка здесь уже не может ни при каком раскладе.
     Проверено рендером при 1024px: ссылка 220–264px, кнопка 312–364px. */
  .screen.done .primary { position: static; }
}

.save-warning { margin: 0.6em 0 1.1em; padding: 0.85em 1em; background: #fff8e8;
  border: 1px solid var(--accent); border-radius: 0.9em; font-weight: 600; }
/* Номер заказа. Основной цвет текста, а не --ink-muted: на нём держится и
   возврат, и перевыпуск («назовите номер заказа» — оферта), то есть читать его
   будет человек, у которого что-то пошло не так. Приглушить его значит спрятать
   ровно тогда, когда он нужнее всего. */
.order-number { margin: 0 0 1.1em; font-size: var(--step-small); }
/* Моноширинный и выделяемый: номер диктуют голосом и переписывают руками, а
   в пропорциональном шрифте 0 и O, 1 и l различаются плохо. Алфавит номера
   двусмысленные знаки и так исключает (order.js), но начертание — вторая
   линия той же защиты, и стоит она одну строку. */
.order-number-value { font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
  font-weight: 700; letter-spacing: 0.04em; user-select: all; }
.local-copy { display: flex; gap: 0.6em; align-items: baseline; flex-wrap: wrap;
  margin-top: 1.4em; color: var(--ink-muted); font-size: var(--step-small); }
.forget-btn { padding: 0.35em 0.9em; border: 1px solid #e0d3c0; border-radius: 999px;
  background: #fff; color: var(--ink-muted); }
/* «Убрать» уничтожает единственную копию оплаченного, а выглядела как соседние
   нейтральные кнопки. Красный контур, не заливка: действие обратимым не станет,
   но и пугать до отказа им пользоваться незачем. */
.forget-btn.destructive { border-color: var(--red); color: var(--red); }
/* 8% давал 4.475:1 на белом .forget-btn (порог AA 4.5:1) — замер headless
   Chrome, ревью 2026-07-30 (:hover не мерили при исходной правке, только
   покой). Доля смеси уменьшена до 4%: 4.730:1, замерено тем же способом —
   getComputedStyle реального color-mix(), не формулой. */
.forget-btn.destructive:hover { background: color-mix(in srgb, var(--red) 4%, var(--surface-raised)); }

/* Каналы сохранения — второй ряд после «Отправить / Скопировать»: они нужны
   тем, кто не отправляет ссылку сразу, поэтому не соперничают с главной
   кнопкой ни размером, ни заливкой.

   Две колонки, а не столбец: кнопок осталось две (календарь с этого экрана
   убран, screens.js), и два коротких слова друг под другом во всю ширину
   пустовали бы горизонтально и читались бы как повтор главной кнопки. */
.save-channels { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6em; margin-top: 1.2em; }
.save-btn { display: flex; align-items: center; justify-content: center; gap: 0.5em;
  width: 100%; padding: 0.7em 0.6em; border: 1px solid #e0d3c0;
  border-radius: 999px; background: #fff; color: inherit;
  text-align: center; text-decoration: none; touch-action: manipulation; }
.save-btn::before {
  content: ''; flex: 0 0 1.2em; width: 1.2em; height: 1.2em;
  background-repeat: no-repeat; background-position: center; background-size: contain;
}
.save-btn:hover { background: #fdf6e8; border-color: #d9c6a8; }
.save-btn:active { background: #f7ecd6; }
/* «Письмом себе» — тот же конверт, что у почты в контактах: один визуальный
   язык для «это про e-mail» по всему продукту. */
.save-btn[href^="mailto:"]::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='2' y='4' width='16' height='12' rx='2' fill='none' stroke='%236b5c4d' stroke-width='1.5'/%3E%3Cpath d='M3 5.5L10 11L17 5.5' fill='none' stroke='%236b5c4d' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* «Скачать файлом» — единственный <button> в этом блоке (почта это <a
   href=mailto:>), поэтому селектор по тегу надёжнее ещё одного атрибута. */
.save-channels button.save-btn::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2V12' stroke='%236b5c4d' stroke-width='1.6' stroke-linecap='round'/%3E%3Cpath d='M6 8L10 12L14 8' fill='none' stroke='%236b5c4d' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M4 13V15.5C4 16.3 4.7 17 5.5 17H14.5C15.3 17 16 16.3 16 15.5V13' fill='none' stroke='%236b5c4d' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ───────────── Движение ─────────────
   Доза вторая: отклик и переходы между шагами. Третья — ровно один раз, на
   «Готово!». Всё анимируемое — только transform и opacity: остальные свойства
   заставляют браузер пересчитывать раскладку и дают дёрганую прокрутку на
   слабом телефоне. */

@media (prefers-reduced-motion: no-preference) {
  /* Отклик на нажатие — ОДНО объявление на все кнопки продукта, и одна доза
     приседания. Второе объявление наверху файла давало 0.98, и «Скачать
     файлом» приседала иначе, чем «Отправить» в сантиметре от неё.
     Только transform: background-color сюда не входит намеренно — §9 требует
     transform и opacity, а шапка блока это обещает. */
  /* .reset-cancel приседает наравне с .reset-confirm: это одна пара в одном
     ряду, и разный отклик на нажатие читался бы как «одна кнопка настоящая, а
     вторая нет». */
  .primary, .start-btn, .answer, .choice, .nav-back, .playthrough-btn,
  .share-btn, .save-btn, .offer-link, .released-show, .free-confirm,
  .reset-confirm, .reset-cancel, .forget-confirm, .forget-cancel,
  .contact-link {
    transition: transform 0.1s ease;
  }
  .primary:active, .start-btn:active, .answer:active, .choice:active,
  .nav-back:active, .playthrough-btn:active, .free-confirm:active,
  .reset-confirm:active, .reset-cancel:active,
  .forget-confirm:active, .forget-cancel:active, .contact-link:active,
  .share-btn:active, .save-btn:active, .offer-link:active { transform: scale(0.972); }

  /* Полоса шагов — заливка ростом по оси X, а не сменой цвета: scaleX не
     трогает раскладку. Играет РОВНО тот сегмент, который только что закрылся
     (класс ставит app.js: stepsBar), и только при шаге вперёд. Иначе заливка
     перезапускалась на каждой перерисовке: смена тарифа шаг не меняет, а все
     сегменты заливались заново, и «Назад» проигрывал движение слева направо,
     пока человек шёл справа налево. */
  .steps-track span { position: relative; overflow: hidden; }
  .steps-track span.just-done::after {
    content: ''; position: absolute; inset: 0; background: var(--scene-kicker);
    transform-origin: left center; animation: seg-fill 0.28s ease-out;
  }
  @keyframes seg-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }

  /* Смена шага — сдвиг с растворением. Направление приходит типом перехода
     (app.js: render), поэтому чистить классы после перехода не нужно. */
  @keyframes screen-in-right { from { opacity: 0; transform: translateX(14px); } }
  @keyframes screen-in-left  { from { opacity: 0; transform: translateX(-14px); } }
  @keyframes screen-out-left  { to { opacity: 0; transform: translateX(-14px); } }
  @keyframes screen-out-right { to { opacity: 0; transform: translateX(14px); } }

  :root::view-transition-old(root),
  :root::view-transition-new(root) { animation-duration: 0.26s; animation-timing-function: ease-out; }
  :root:active-view-transition-type(forward)::view-transition-old(root) { animation-name: screen-out-left; }
  :root:active-view-transition-type(forward)::view-transition-new(root) { animation-name: screen-in-right; }
  :root:active-view-transition-type(back)::view-transition-old(root) { animation-name: screen-out-right; }
  :root:active-view-transition-type(back)::view-transition-new(root) { animation-name: screen-in-left; }

  /* Появление блоков — progressive enhancement поверх всего остального.
     Проверка на animation-range обязательна: без неё сюда проходят браузеры с
     частичной поддержкой, и блоки застревают полупрозрачными. */
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes rise { from { opacity: 0; transform: translateY(12px); } }
    .window-card, .showcase, .mini-card {
      animation: rise auto linear backwards;
      animation-timeline: view();
      animation-range: entry 8% entry 55%;
    }
  }

  /* Конфетти — ~14 элементов, один проход, затем узел уходит из DOM
     (screens.js: doneScreen). Позиции и задержки заданы вручную: генерировать
     их из JS значило бы писать стили из скрипта ради декора. */
  .confetti { position: absolute; inset: 0 0 auto; height: 40vh;
    pointer-events: none; overflow: hidden; }
  .confetti-bit { position: absolute; top: -8px; width: 7px; height: 11px;
    border-radius: 2px; opacity: 0; animation: fall 1.5s ease-in forwards; }
  @keyframes fall {
    0%   { opacity: 0; transform: translateY(0) rotate(0deg); }
    12%  { opacity: 1; }
    100% { opacity: 0; transform: translateY(38vh) rotate(320deg); }
  }
  /* Не --action: тот означает ровно одно — «действие, двигающее вперёд»
     (visual-system.test.mjs, Задача 2/8), а «Готово!» уже позади этого
     действия. Тон сцены и декоративные литералы вместо семантических токенов.
     Зелёный и голубой — литералы, а не var(--green)/var(--sky): таких токенов
     в продукте нет, и ссылка на них была бы ловушкой — объявись они завтра для
     другой темы, конфетти незаметно перекрасилось бы. */
  .confetti-bit:nth-child(4n+1) { background: var(--scene-kicker); }
  .confetti-bit:nth-child(4n+2) { background: #6fb662; }
  .confetti-bit:nth-child(4n+3) { background: #9bd7e2; }
  .confetti-bit:nth-child(4n)   { background: #d98a63; }
  .confetti-bit:nth-child(1)  { left: 6%;  animation-delay: 0.02s; }
  .confetti-bit:nth-child(2)  { left: 14%; animation-delay: 0.16s; }
  .confetti-bit:nth-child(3)  { left: 22%; animation-delay: 0.07s; }
  .confetti-bit:nth-child(4)  { left: 30%; animation-delay: 0.24s; }
  .confetti-bit:nth-child(5)  { left: 38%; animation-delay: 0.11s; }
  .confetti-bit:nth-child(6)  { left: 46%; animation-delay: 0.3s;  }
  .confetti-bit:nth-child(7)  { left: 54%; animation-delay: 0.05s; }
  .confetti-bit:nth-child(8)  { left: 62%; animation-delay: 0.21s; }
  .confetti-bit:nth-child(9)  { left: 70%; animation-delay: 0.13s; }
  .confetti-bit:nth-child(10) { left: 78%; animation-delay: 0.27s; }
  .confetti-bit:nth-child(11) { left: 86%; animation-delay: 0.09s; }
  .confetti-bit:nth-child(12) { left: 92%; animation-delay: 0.19s; }
  .confetti-bit:nth-child(13) { left: 18%; animation-delay: 0.33s; }
  .confetti-bit:nth-child(14) { left: 66%; animation-delay: 0.37s; }

  /* Блик по главной кнопке — однократно, на том же экране. */
  .screen.done .primary { position: relative; overflow: hidden; }
  .screen.done .primary::after {
    content: ''; position: absolute; top: 0; bottom: 0; width: 40%;
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.55), transparent);
    transform: translateX(-160%); animation: sheen 0.9s ease-out 0.45s 1;
  }
  @keyframes sheen { to { transform: translateX(360%); } }
}

/* Просили не показывать — не показываем ничего, включая переходы между
   экранами: они идут в top layer и медиазапросом выше не отменяются. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* Реквизиты — служебная строка: она обязана быть читаемой, но не спорить с
   содержимым экрана. Контраст не ниже 4.5:1, то есть не --muted (§7.1). */
.site-requisites {
  margin: 3rem auto 1.5rem;
  max-width: 34rem;
  padding: 0 1rem;
  text-align: center;
  font-size: 0.8125rem;
  line-height: 1.5;
}
.site-requisites .requisites { margin: 0 0 0.35rem; }
.site-requisites .legal-link { margin: 0; }

/* Отступы на этой странице ужаты, и это не вкусовщина, а работа с одним
   числом: кнопка оплаты обязана целиком помещаться в первый экран самого
   старого поддерживаемого телефона (iPhone 6s Plus, видимая область 414×628 с
   адресной строкой). После редакции текстов владельцем 2026-08-05 шаги стали
   длиннее на три строки, и кнопка встала верхним краем ровно на сгибе -
   проверено скриншотом. Ужаты заголовки и зазор под плашкой заказа: это
   вернуло ~50px и кнопку в кадр целиком.
   Правила адресные (#pay), общая типографика воронки не трогается. */
#pay h1 { margin: 0.1em 0 0.7em; }
#pay h2 { margin: 0.9em 0 0.5em; }

/* ===== Страница оформления заказа =====
   До 2026-08-05 у неё не было НИ ОДНОГО собственного правила, кроме блока
   провайдера ниже: номер, сумма, условия и шаги рисовались браузерными
   умолчаниями. Половина ощущения «скучно и всё очень сложно», с которого начался
   разбор владельца, была именно отсюда - страница выглядела не строгой, а
   недоделанной, и это на самом дорогом экране воронки.

   Ничего нового не изобретаем: те же плашки, отступы и ступени, что у остальной
   воронки. Задача не украсить, а показать, что экран доделан. */

/* Номер и сумма - одной плашкой: это первое, что ищут глазами, и они читаются
   как пара, а не как два случайных абзаца. Сумма крупнее номера - платят за
   неё. */
/* #f3e9da - тот же песочный, что у .showcase и .reset-note: на странице
   заказа новых цветов не заводим, иначе воронка начнёт выглядеть собранной из
   разных мест. */
.pay-order, .pay-price {
  margin: 0;
  padding: 0.55em 0.8em;
  background: #f3e9da;
  line-height: 1.4;
}
.pay-order { border-radius: 10px 10px 0 0; border-bottom: 1px solid var(--line-soft); }
.pay-price { border-radius: 0 0 10px 10px; margin-bottom: 0.9em; }
.pay-price b { font-size: 1.15em; }

/* Условия - тихий текст. Их обязанность быть на экране (ст. 26.1 ЗоЗПП), а не
   спорить с кнопкой: человек, который читает, найдёт, а остальным они не
   мешают. */
.pay-terms { margin: 0 0 0.8em; }
.pay-terms p { margin: 0 0 0.4em; line-height: 1.45; }
.pay-terms-links { font-size: var(--step-small); color: var(--ink-muted); }
.pay-terms-links a { color: var(--ink-muted); }

/* Шаги - компактнее обычного списка: короткие предложения не должны занимать
   экран.
   Список РАЗОРВАН кнопкой (шаги 1-2 до неё, шаг 3 после), поэтому у второй
   половины свой отступ сверху: она стоит вплотную под кнопкой и без него
   слипается с ней. Нумерацию держит атрибут start="3" в разметке. */
.pay-steps { margin: 0 0 1.2em; padding-left: 1.3em; line-height: 1.45; }
.pay-steps li { margin-bottom: 0.35em; }
.pay-steps-after { margin-top: 1em; margin-bottom: 0; color: var(--ink-muted); }

/* Строка провайдера - п. 4.3.4 договора с Робокассой: логотип, ссылка на сайт
   и условия оплаты до момента оплаты.
   Логотип - квадратная марка размером со строку, поэтому inline-flex и
   вертикальное выравнивание по центру строки, а не блок над текстом: чёрный
   квадрат покрупнее спорил бы с жёлтой кнопкой оплаты прямо под ним.
   alt пустой намеренно: рядом стоит слово «Робокасса» текстом, и озвучивать
   его дважды скринридеру незачем. */
.pay-provider {
  display: flex; align-items: center; gap: 0.5em;
  margin: 0 0 1.2em;
  font-size: var(--step-small); color: var(--ink-muted); line-height: 1.45;
}
.pay-provider a { color: var(--ink-muted); }
.pay-provider-logo { flex: none; width: 24px; height: 24px; border-radius: 5px; }

/* Срок ответа - под кнопкой, тихо. Заголовка у него больше нет. */
.pay-answer { margin: 1.4em 0 1em; font-size: var(--step-small); color: var(--ink-muted); }
