/* Zeo Topup TMA — компоненты.
 *
 * Источник — дизайн-система как код: `_ds_bundle.js` (React.createElement,
 * инлайн-объекты `style`, ни одного css-класса) плюс сверка по
 * `tokens/*.css`. Задача этого файла — тот же визуальный результат
 * классами: каждый инлайн-объект стал `.zt-<component>` (+модификаторы),
 * литеральные значения и `var(--token)` перенесены без изменений.
 *
 * Конвенция именования: `zt-<component>` — корень, `zt-<component>--<mod>`
 * — вариант/размер/состояние-модификатор (ставит JS, когда состояние не
 * добывается из псевдокласса), `zt-<component>__<part>` — часть составного
 * компонента. Все имена классов ниже перечислены в ответе задачи (`files`/
 * `handoff`) — WP10b пишет `js/components/*.js` по этому списку и другого
 * источника не имеет.
 *
 * Три сознательных добавления сверх `_ds_bundle.js` (задание WP10a):
 *   1. `:focus-visible { box-shadow: var(--ring-focus) }` — у Button,
 *      IconButton, Checkbox, Switch, RadioGroup, Tabs, Tag, AmountOption,
 *      ProductTile его нет вовсе (components.md §6.1: "readme.md
 *      *specifies* a --ring-focus ring — adding it is on-spec, not a
 *      deviation"). У Input/Select фокус-кольцо уже было — оставлено как
 *      было.
 *   2. `:hover`/`:active` — в бандле это `React.useState`, здесь обычные
 *      псевдоклассы; клавиатура и тач получают то, что раньше получала
 *      только мышь (components.md §6.2).
 *   3. `:active` там, где источник вообще не определяет цвет нажатия
 *      (IconButton, Tag, Checkbox, Switch, RadioGroup, ProductTile, Card) —
 *      однородный `opacity:.85` / лёгкий `translateY`, без выдуманных
 *      токенов. Где источник определяет цвет нажатия явно (Button,
 *      AmountOption через `--ring-focus`), используется он.
 *
 * Паттерн отключённого состояния: где стилизуемый элемент — сам нативный
 * `<button disabled>`/`<input disabled>`, состояние берётся из `:disabled`
 * с гвардом `:not(:disabled)` на "живых" вариантах (без гонки
 * специфичности, порядок правил не важен). Где disabled лежит на потомке
 * (Input/Select — на native `<input>`/`<select>` внутри `<label>`, а
 * стилизуется обёртка) — модификатор `--disabled` ставит JS: `:has()` в
 * файле не используется нигде (Safari на старых iPhone у части аудитории,
 * та же причина, что и решение про import-карты в `docs/tma_plan.md`
 * поправка №1).
 *
 * Крестики-нолики "restated redundant branch" и т.п. заметки components.md
 * — про JS-логику самого бандла, не про CSS; здесь не воспроизводятся.
 */

/* ============================================================
   Keyframes — hoisted once (в бандле дублируются в каждом инстансе:
   Button/StatusPill/Toast/Dialog рендерят свой <style> с одним и тем же
   @keyframes zeo-spin через раз; тут — единственное определение).
   ============================================================ */

@keyframes zeo-spin { to { transform: rotate(360deg); } }
@keyframes zeo-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes zeo-rise { from { opacity: 0; transform: translateY(12px) scale(.985); } to { opacity: 1; transform: none; } }
@keyframes zeo-toast { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* Утилита на вращающуюся иконку — применить к `.zt-icon` внутри
   загрузочной кнопки / pending-тоста / spin-статуса. Длительность в
   бандле расходится (700ms Button/Toast, 900ms StatusPill,
   components.md §6.5) — `--slow` даёт вторую длительность на той же
   keyframes, не второй @keyframes. */
.zt-spin { animation: zeo-spin 700ms linear infinite; }
.zt-spin--slow { animation-duration: 900ms; }


/* ============================================================
   1. Core — components.md §1
   ============================================================ */

/* --- 1.1 Icon (§1.1) ---
   В реальной реализации (в отличие от бандла, который берёт SVG маской с
   jsDelivr) иконки — инлайновые SVG из vendor/lucide.js: WP9 обрезал
   width/height у тел иконок именно затем, чтобы их держал компонент.
   `.zt-icon` — класс на самом <svg> (не на обёртке): цвет наследуется
   обычным каскадом (stroke="currentColor" уже в разметке WP9), размер
   компонент проставляет инлайн per-instance (13–36px по реестру размеров
   — величина неоднородна, отдельного класса на каждый шаг нет смысла). */
.zt-icon {
  display: inline-block;
  flex: 0 0 auto;
}

/* --- 1.2 Button (§1.2) --- */
.zt-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  font-family: var(--font-ui);
  font-weight: var(--fw-semibold);
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transform: scale(1);
  transition: var(--transition-control);
}
.zt-button:focus-visible { box-shadow: var(--ring-focus); }
/* Нажатие — --shadow-primary-press у ЛЮБОГО варианта, включая danger/steam
   (components.md §6.7: сознательно оставленная особенность бандла). */
.zt-button:active:not(:disabled) { transform: scale(.99); box-shadow: var(--shadow-primary-press); }
.zt-button:disabled {
  background: var(--surface-sunken);
  color: var(--text-disabled);
  border-color: transparent;
  box-shadow: none;
  cursor: not-allowed;
}

.zt-button--sm { height: var(--control-height-sm); padding: 0 14px; gap: 6px; font-size: 13px; border-radius: var(--radius-sm); }
.zt-button--md { height: var(--control-height-md); padding: 0 20px; gap: 8px; font-size: 15px; border-radius: var(--radius-control); }
.zt-button--lg { height: var(--control-height-lg); padding: 0 28px; gap: 10px; font-size: 17px; border-radius: var(--radius-md); }
.zt-button--full { width: 100%; }

.zt-button--primary:not(:disabled) { background: var(--blue-500); color: var(--text-inverse); }
.zt-button--primary:hover:not(:disabled) { background: var(--blue-600); box-shadow: var(--shadow-primary); }
.zt-button--primary:active:not(:disabled) { background: var(--blue-700); }

.zt-button--secondary:not(:disabled) { background: var(--control-secondary-bg); color: var(--text-primary); border-color: var(--border-subtle); box-shadow: var(--shadow-xs); }
.zt-button--secondary:hover:not(:disabled) { background: var(--control-secondary-bg-hover); }
.zt-button--secondary:active:not(:disabled) { background: var(--surface-sunken); }

.zt-button--ghost:not(:disabled) { background: transparent; color: var(--text-brand); }
.zt-button--ghost:hover:not(:disabled) { background: var(--control-ghost-bg-hover); }
.zt-button--ghost:active:not(:disabled) { background: var(--alpha-blue-16); }

.zt-button--danger:not(:disabled) { background: var(--red-500); color: var(--text-inverse); }
.zt-button--danger:hover:not(:disabled),
.zt-button--danger:active:not(:disabled) { background: var(--red-700); }

.zt-button--telegram:not(:disabled) { background: var(--tg-500); color: var(--text-inverse); }
/* Литеральная тень источника — токена под неё нет (components.md §6.6). */
.zt-button--telegram:hover:not(:disabled) { background: var(--tg-700); box-shadow: 0 8px 20px rgba(42, 171, 238, .28); }
.zt-button--telegram:active:not(:disabled) { background: var(--tg-700); }

.zt-button--steam:not(:disabled) { background: var(--steam-900); color: var(--text-inverse); }
.zt-button--steam:hover:not(:disabled),
.zt-button--steam:active:not(:disabled) { background: var(--steam-700); }

/* --- 1.3 IconButton (§1.3) --- */
.zt-icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-control);
  border: 1px solid transparent;
  cursor: pointer;
  transition: var(--transition-control);
}
.zt-icon-button:focus-visible { box-shadow: var(--ring-focus); }
.zt-icon-button:disabled { background: var(--surface-sunken); color: var(--text-disabled); cursor: not-allowed; }

.zt-icon-button--sm { width: 32px; height: 32px; }
.zt-icon-button--md { width: 40px; height: 40px; }
.zt-icon-button--lg { width: 48px; height: 48px; }

.zt-icon-button--ghost:not(:disabled) { background: transparent; color: var(--text-secondary); }
.zt-icon-button--ghost:hover:not(:disabled) { background: var(--control-ghost-bg-hover); color: var(--text-brand); }
.zt-icon-button--ghost:active:not(:disabled) { opacity: .85; }

/* outline на hover теряет белую заливку — берёт tint ghost-hover
   (components.md §1.3: "loses its white fill on hover"). */
.zt-icon-button--outline:not(:disabled) { background: var(--control-secondary-bg); border-color: var(--border-subtle); color: var(--text-secondary); }
.zt-icon-button--outline:hover:not(:disabled) { background: var(--control-ghost-bg-hover); color: var(--text-brand); }
.zt-icon-button--outline:active:not(:disabled) { opacity: .85; }

.zt-icon-button--solid:not(:disabled) { background: var(--blue-500); color: var(--text-inverse); }
.zt-icon-button--solid:hover:not(:disabled) { background: var(--blue-600); }
.zt-icon-button--solid:active:not(:disabled) { background: var(--blue-700); }

/* --- 1.4 Input (§1.4) ---
   `<label class="zt-input [--sm|--lg] [--invalid] [--disabled]">`
     `<span class="zt-input__label">` (опционально)
     `<div class="zt-input__field">`
       `<svg class="zt-icon zt-input__icon">` (опционально)
       `<input class="zt-input__control">`
       `<span class="zt-input__suffix">` (опционально)
     `<span class="zt-input__hint">` (error || hint) */
.zt-input { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.zt-input__label { font-size: 13px; font-weight: var(--fw-semibold); color: var(--text-secondary); }

.zt-input__field {
  display: flex;
  align-items: center;
  gap: 10px;
  height: var(--control-height-md);
  padding: 0 14px;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-control);
  transition: var(--transition-control);
}
.zt-input--sm .zt-input__field { height: var(--control-height-sm); }
.zt-input--lg .zt-input__field { height: var(--control-height-lg); }
/* фокус живёт на нативном <input>, кольцо — на обёртке-FIELD */
.zt-input__field:focus-within { border-color: var(--border-brand); box-shadow: var(--ring-focus); }
.zt-input--invalid .zt-input__field { border-color: var(--border-danger); }
.zt-input--invalid .zt-input__field:focus-within { box-shadow: var(--ring-danger); }
.zt-input--disabled .zt-input__field { background: var(--surface-sunken); }

.zt-input__icon { color: var(--text-tertiary); }
.zt-input__field:focus-within .zt-input__icon { color: var(--text-brand); }

.zt-input__control {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font-family: var(--font-ui);
  font-size: 16px; /* см. примечание про зум iOS ниже по файлу */
  font-weight: var(--fw-medium);
  color: var(--text-primary);
  font-feature-settings: var(--num-tabular);
}
.zt-input--lg .zt-input__control { font-size: 17px; }
.zt-input__control::placeholder { color: var(--text-tertiary); }
.zt-input__control:disabled { cursor: not-allowed; }

.zt-input__suffix { font-size: 15px; font-weight: var(--fw-semibold); color: var(--text-tertiary); }
.zt-input__hint { font-size: 12px; color: var(--text-tertiary); }
.zt-input--invalid .zt-input__hint { color: var(--text-danger); }

/* --- 1.5 Select (§1.5) ---
   Нет size-варианта — высота всегда --control-height-md. Нет ветки
   --ring-danger на фокусе (в отличие от Input) — дословно по источнику. */
.zt-select { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.zt-select__label { font-size: 13px; font-weight: var(--fw-semibold); color: var(--text-secondary); }

.zt-select__wrap {
  position: relative;
  display: flex;
  align-items: center;
  height: var(--control-height-md);
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-control);
  transition: var(--transition-control);
}
.zt-select__wrap:focus-within { border-color: var(--border-brand); box-shadow: var(--ring-focus); }
.zt-select--invalid .zt-select__wrap { border-color: var(--border-danger); }
.zt-select--disabled .zt-select__wrap { background: var(--surface-sunken); }

.zt-select__control {
  appearance: none;
  -webkit-appearance: none;
  flex: 1;
  height: 100%;
  padding: 0 40px 0 14px;
  border: none;
  outline: none;
  background: transparent;
  font-family: var(--font-ui);
  font-size: 16px; /* см. примечание про зум iOS ниже по файлу */
  font-weight: var(--fw-medium);
  color: var(--text-tertiary); /* плейсхолдер-вид, пока не выбрано */
  cursor: pointer;
}
/* JS ставит этот модификатор, когда value непусто (== вид "выбрано") */
.zt-select__control--filled { color: var(--text-primary); }
.zt-select--disabled .zt-select__control { cursor: not-allowed; }

.zt-select__chevron { position: absolute; right: 14px; pointer-events: none; color: var(--text-tertiary); }
.zt-select__hint { font-size: 12px; color: var(--text-tertiary); }
.zt-select--invalid .zt-select__hint { color: var(--text-danger); }

/* --- 1.6 Checkbox (§1.6) ---
   `<label class="zt-checkbox [--with-description] [--disabled]">`
     `<input type="checkbox" class="zt-checkbox__input">`
     `<span class="zt-checkbox__box"><svg class="zt-icon zt-checkbox__check"></span>`
     `<span class="zt-checkbox__text">…</span>`
   Порядок DOM важен — `__box` идёт СРАЗУ за `__input` (общий соседний
   селектор `~`). */
.zt-checkbox { display: flex; gap: 12px; align-items: center; cursor: pointer; }
.zt-checkbox--with-description { align-items: flex-start; }
.zt-checkbox--disabled { cursor: not-allowed; opacity: .55; }
.zt-checkbox:active:not(.zt-checkbox--disabled) { opacity: .85; }

.zt-checkbox__input { position: absolute; opacity: 0; width: 0; height: 0; }

.zt-checkbox__box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  border-radius: 6px; /* литерал в источнике, НЕ var(--radius-xs) — components.md §6.6 */
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  transition: var(--transition-control);
}
.zt-checkbox__input:checked ~ .zt-checkbox__box { background: var(--blue-500); border-color: var(--blue-500); }
.zt-checkbox__input:focus-visible ~ .zt-checkbox__box { box-shadow: var(--ring-focus); }

.zt-checkbox__check { display: none; color: var(--text-inverse); }
.zt-checkbox__input:checked ~ .zt-checkbox__box .zt-checkbox__check { display: inline-block; }

.zt-checkbox__text { display: flex; flex-direction: column; gap: 2px; }
.zt-checkbox__label { font-size: 14px; font-weight: var(--fw-medium); color: var(--text-primary); }
.zt-checkbox__description { font-size: 12px; color: var(--text-tertiary); }

/* --- 1.7 Switch (§1.7) ---
   `<label class="zt-switch [--with-description] [--disabled]">`
     текст слева, `<input type="checkbox" role="switch" class="zt-switch__input">`,
     `<span class="zt-switch__track"><span class="zt-switch__thumb"></span></span>` */
.zt-switch { display: flex; align-items: center; justify-content: space-between; gap: 16px; cursor: pointer; }
.zt-switch--with-description { align-items: flex-start; }
.zt-switch--disabled { cursor: not-allowed; opacity: .55; }
.zt-switch:active:not(.zt-switch--disabled) { opacity: .85; }

.zt-switch__text { display: flex; flex-direction: column; gap: 2px; }
.zt-switch__label { font-size: 14px; font-weight: var(--fw-medium); color: var(--text-primary); }
.zt-switch__description { font-size: 12px; color: var(--text-tertiary); }

.zt-switch__input { position: absolute; opacity: 0; width: 0; height: 0; }

.zt-switch__track {
  display: block;
  width: 44px;
  height: 26px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  padding: 3px;
  background: var(--border-default);
  transition: background-color var(--dur-fast) var(--ease-standard);
}
.zt-switch__input:checked ~ .zt-switch__track { background: var(--blue-500); }
.zt-switch__input:focus-visible ~ .zt-switch__track { box-shadow: var(--ring-focus); }

.zt-switch__thumb {
  display: block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--white);
  box-shadow: var(--shadow-sm);
  transform: translateX(0);
  transition: transform var(--dur-fast) var(--ease-out);
}
/* 44 - 2*3 padding - 20 thumb = 18px хода (components.md §1.7 geometry check) */
.zt-switch__input:checked ~ .zt-switch__track .zt-switch__thumb { transform: translateX(18px); }

/* --- 1.8 Card (§1.8) ---
   padding — по спецификации `number|string` per-instance, остаётся
   инлайн-стилем у вызывающего (динамическое значение, не токен). */
.zt-card {
  position: relative;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  overflow: hidden; /* клипает -9px бейдж AmountOption и Tooltip-пузырь внутри — components.md §1.8/§6.4 */
  transition:
    box-shadow var(--dur-base) var(--ease-standard),
    transform var(--dur-base) var(--ease-standard),
    border-color var(--dur-fast) var(--ease-standard);
}
.zt-card--flat { box-shadow: none; }
.zt-card--sm { box-shadow: var(--shadow-sm); }
.zt-card--md { box-shadow: var(--shadow-md); }
.zt-card--lg { box-shadow: var(--shadow-lg); }

.zt-card--interactive { cursor: pointer; }
.zt-card--interactive:hover { border-color: var(--blue-200); box-shadow: var(--shadow-lg); transform: translateY(-2px); }
.zt-card--interactive:active { transform: translateY(0) scale(.99); }
.zt-card--interactive:focus-visible { box-shadow: var(--ring-focus); }

.zt-card__accent { position: absolute; inset-inline: 0; top: 0; height: 3px; }
.zt-card__accent--brand { background: var(--gradient-brand); }

/* --- 1.9 Badge (§1.9) --- */
.zt-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  letter-spacing: -0.005em;
  white-space: nowrap;
}
.zt-badge--neutral { background: var(--surface-sunken); color: var(--text-secondary); }
.zt-badge--brand { background: var(--surface-brand-soft); color: var(--text-brand); }
.zt-badge--success { background: var(--surface-success-soft); color: var(--text-success); }
.zt-badge--warning { background: var(--surface-warning-soft); color: var(--text-warning); }
.zt-badge--danger { background: var(--surface-danger-soft); color: var(--text-danger); }
/* Литеральный hex в источнике, без токена (components.md §1.9/§5) */
.zt-badge--stars { background: var(--star-100); color: #8A5E00; }
.zt-badge--solid { background: var(--blue-500); color: var(--white); }

.zt-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* --- 1.10 Tag → `.zt-chip` (файл компонента WP10b: `chip.js`) (§1.10) ---
   Hover-tint у источника появляется, только если задан onClick — CSS не
   знает про обработчики, поэтому это отдельный модификатор `--clickable`. */
.zt-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  color: var(--text-secondary);
  border: 1px solid var(--border-subtle);
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: var(--fw-semibold);
  cursor: default;
  transition: var(--transition-control);
}
.zt-chip:focus-visible { box-shadow: var(--ring-focus); }
.zt-chip--clickable { cursor: pointer; }
.zt-chip--clickable:hover:not(.zt-chip--selected) { background: var(--surface-brand-soft); }
.zt-chip--clickable:active:not(.zt-chip--selected) { opacity: .85; }

.zt-chip--selected {
  background: var(--blue-500);
  color: var(--text-inverse);
  border-color: var(--blue-500);
}

.zt-chip__remove { opacity: .6; cursor: pointer; }

/* --- 1.11 Tabs (§1.11) ---
   Используется, например, фильтром списка заказов (all/working/unpaid/done
   — CLAUDE.md «вкладки выведены из перевода»). Вариант по умолчанию —
   pill; `--underline` — вторая ветка источника. */
.zt-tabs { display: inline-flex; }
.zt-tabs--pill { gap: 4px; padding: 4px; background: var(--surface-sunken); border-radius: var(--radius-pill); }
.zt-tabs--underline { gap: 24px; padding: 0; border-bottom: 1px solid var(--border-subtle); }

.zt-tabs__tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: none;
  background: transparent;
  color: var(--text-tertiary);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: var(--transition-control);
}
.zt-tabs__tab:focus-visible { box-shadow: var(--ring-focus); }
.zt-tabs__tab:active { opacity: .85; }

.zt-tabs--pill .zt-tabs__tab { height: 36px; padding: 0 16px; border-radius: var(--radius-pill); }
.zt-tabs--pill .zt-tabs__tab--active { background: var(--surface-card); box-shadow: var(--shadow-sm); color: var(--text-primary); }

.zt-tabs--underline .zt-tabs__tab { height: 44px; padding: 0 2px; border-bottom: 2px solid transparent; }
.zt-tabs--underline .zt-tabs__tab--active { border-bottom-color: var(--blue-500); color: var(--text-primary); }

.zt-tabs__tab--active .zt-icon { color: var(--text-brand); }
.zt-tabs__count { font-size: 12px; color: var(--text-tertiary); font-feature-settings: var(--num-tabular); }

/* --- 1.12 RadioGroup (§1.12) ---
   `--active` на `.zt-radio-option` ставит JS вместе с `checked` на
   input'е (см. общее примечание про :has() в шапке файла). */
.zt-radio-group { display: flex; flex-direction: column; gap: 8px; }
.zt-radio-group__label { font-size: 13px; font-weight: var(--fw-semibold); color: var(--text-secondary); }
.zt-radio-group__options { display: flex; flex-direction: column; gap: 8px; }
.zt-radio-group__options--row { flex-direction: row; gap: 12px; }

.zt-radio-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  cursor: pointer;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-control);
  transition: var(--transition-control);
}
.zt-radio-group__options--row .zt-radio-option { flex: 1; }
.zt-radio-option:active { opacity: .9; }
.zt-radio-option--active { background: var(--surface-brand-soft); border-color: var(--border-brand); }

.zt-radio-option__input { position: absolute; opacity: 0; width: 0; height: 0; }

.zt-radio-option__ring {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 2px solid var(--border-default);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.zt-radio-option--active .zt-radio-option__ring,
.zt-radio-option__input:checked ~ .zt-radio-option__ring { border-color: var(--blue-500); }
.zt-radio-option__input:focus-visible ~ .zt-radio-option__ring { box-shadow: var(--ring-focus); }

.zt-radio-option__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--blue-500); display: none; }
.zt-radio-option--active .zt-radio-option__dot,
.zt-radio-option__input:checked ~ .zt-radio-option__ring .zt-radio-option__dot { display: block; }

.zt-radio-option__text { display: flex; flex-direction: column; gap: 1px; }
.zt-radio-option__title { font-size: 14px; font-weight: var(--fw-semibold); color: var(--text-primary); }
.zt-radio-option__description { font-size: 12px; color: var(--text-tertiary); }


/* ============================================================
   2. Commerce — components.md §2
   ============================================================ */

/* --- 2.1 AmountOption (§2.1) ---
   Выделение сигналится тем же токеном, что и фокус-кольцо (`--ring-focus`)
   — в источнике отдельного focus-стиля у этого компонента нет вовсе. */
.zt-amount-option {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 14px 16px;
  min-width: 128px;
  border-radius: var(--radius-control);
  cursor: pointer;
  text-align: left;
  transition: var(--transition-control);
}
.zt-amount-option:focus-visible { box-shadow: var(--ring-focus); }
.zt-amount-option:disabled { cursor: not-allowed; opacity: .5; }

.zt-amount-option:not(:disabled):not(.zt-amount-option--selected) {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  box-shadow: none;
}
.zt-amount-option:not(:disabled):not(.zt-amount-option--selected):hover { border-color: var(--blue-200); }
.zt-amount-option:not(:disabled):not(.zt-amount-option--selected):active { opacity: .85; }

.zt-amount-option--selected:not(:disabled) {
  background: var(--surface-brand-soft);
  border: 1px solid var(--border-brand);
  box-shadow: var(--ring-focus);
}

.zt-amount-option__amount {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: var(--fw-bold);
  letter-spacing: -0.02em;
  color: var(--text-primary);
  font-feature-settings: var(--num-tabular);
}
.zt-amount-option--selected .zt-amount-option__amount { color: var(--text-brand); }

.zt-amount-option__receives { font-size: 12px; color: var(--text-tertiary); font-feature-settings: var(--num-tabular); }

.zt-amount-option__badge {
  position: absolute;
  top: -9px;
  right: 10px;
  height: 18px;
  padding: 0 7px;
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-pill);
  background: var(--green-500);
  color: var(--white);
  font-size: 11px;
  font-weight: var(--fw-bold);
}

/* Компактный вариант — реальные числа звёздных/GRAM пресетов на экранах
   продукта (screens_products.md «tmaStarPresets», README «Пресеты: 6
   чипов в две колонки»), а не витринная форма AmountOption из бандла.
   Использовать там, где сетка пресетов должна совпасть с прототипом
   (два в ряд, meta-строка мельче). */
.zt-amount-option--compact {
  flex: 1 1 calc(50% - 4px);
  min-width: 0;
  min-height: 56px;
  padding: 8px 12px;
  gap: 2px;
  border-radius: 14px;
}
.zt-amount-option--compact:not(:disabled):not(.zt-amount-option--selected) {
  box-shadow: var(--shadow-sm);
}
.zt-amount-option--compact .zt-amount-option__amount {
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: var(--fw-bold);
  letter-spacing: normal;
}
.zt-amount-option--compact .zt-amount-option__receives { font-weight: var(--fw-semibold); }

/* Сетка пресетов Steam: два в ряд с фиксированными колонками, а не перенос
   по ширине содержимого. Номиналы там разной длины («500 ₽» против
   «5 000 ₽»), и на переносе вторая колонка гуляет от строки к строке. */
.zt-amount-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* --- 2.2 ProductTile (§2.2) --- */
.zt-product-tile {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 20px;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition:
    box-shadow var(--dur-base) var(--ease-standard),
    transform var(--dur-base) var(--ease-standard),
    border-color var(--dur-fast) var(--ease-standard);
}
.zt-product-tile:focus-visible { box-shadow: var(--ring-focus); }
.zt-product-tile:not(.zt-product-tile--selected):hover { border-color: var(--blue-200); box-shadow: var(--shadow-lg); transform: translateY(-2px); }
.zt-product-tile:not(.zt-product-tile--selected):active { transform: translateY(0) scale(.99); }
.zt-product-tile--selected { border-color: var(--border-brand); box-shadow: var(--ring-focus); }

.zt-product-tile__top { display: flex; align-items: center; justify-content: space-between; }
.zt-product-tile__icon { width: 48px; height: 48px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; }
.zt-product-tile--steam .zt-product-tile__icon { background: var(--product-steam-soft); color: var(--steam-900); }
.zt-product-tile--telegram .zt-product-tile__icon { background: var(--product-telegram-soft); color: var(--tg-700); }
.zt-product-tile--premium .zt-product-tile__icon { background: var(--surface-brand-soft); color: var(--blue-700); }
/* Литеральный hex в источнике, без токена — тот же, что у Badge stars */
.zt-product-tile--stars .zt-product-tile__icon { background: var(--product-stars-soft); color: #8A5E00; }

.zt-product-tile__body { display: flex; flex-direction: column; gap: 4px; }
.zt-product-tile__title { font-family: var(--font-display); font-size: 18px; font-weight: var(--fw-bold); letter-spacing: -0.014em; color: var(--text-primary); }
.zt-product-tile__subtitle { font-family: var(--font-body); font-size: 14px; line-height: 1.45; color: var(--text-secondary); }
.zt-product-tile__note { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: var(--fw-semibold); color: var(--text-brand); margin-top: auto; }

/* --- 2.3 StatusPill (§2.3) ---
   `--self-start` — фикс верности из docs/tma_plan.md §4: на карточке
   заказа пилюля выравнивается по левому краю, а не тянется строкой. */
.zt-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 11px;
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}
.zt-status-pill--paid,
.zt-status-pill--delivered { background: var(--surface-success-soft); color: var(--text-success); }
.zt-status-pill--processing { background: var(--surface-warning-soft); color: var(--text-warning); }
.zt-status-pill--awaiting { background: var(--surface-sunken); color: var(--text-secondary); }
.zt-status-pill--failed { background: var(--surface-danger-soft); color: var(--text-danger); }
.zt-status-pill--refunded { background: var(--surface-brand-soft); color: var(--text-brand); }
.zt-status-pill--self-start { align-self: flex-start; }

/* --- 2.4 StepIndicator → `.zt-step-track` (файл WP10b: `step-track.js`) (§2.4) --- */
.zt-step-track { display: flex; align-items: center; gap: 0; width: 100%; }
.zt-step-track__step { display: flex; align-items: center; gap: 9px; }

.zt-step-track__circle {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-sunken);
  border: 2px solid transparent;
  color: var(--text-tertiary);
  font-size: 12px;
  font-weight: var(--fw-bold);
  transition: var(--transition-control);
}
.zt-step-track__step--done .zt-step-track__circle { background: var(--blue-500); border-color: var(--blue-500); color: var(--white); }
.zt-step-track__step--active .zt-step-track__circle { background: var(--surface-card); border-color: var(--blue-500); color: var(--text-brand); }

.zt-step-track__label { font-size: 13px; font-weight: var(--fw-medium); color: var(--text-tertiary); white-space: nowrap; }
.zt-step-track__step--done .zt-step-track__label { color: var(--text-secondary); }
.zt-step-track__step--active .zt-step-track__label { font-weight: var(--fw-semibold); color: var(--text-primary); }

/* Цвет коннектора решает шаг ДО него — components.md §2.4 */
.zt-step-track__connector { flex: 1; height: 2px; margin: 0 12px; border-radius: 2px; background: var(--border-subtle); }
.zt-step-track__connector--done { background: var(--blue-300); }

/* Вертикальный трек — карточка заказа в макете из Claude Design. Не второй
   вид того же компонента ради разнообразия: у горизонтального нет места под
   вторую строку шага («ожидается», «обычно до 5 минут»), а она и есть
   половина ответа на вопрос «когда». Точка вместо кружка с номером по той
   же причине — номер шага рядом с его подписью ничего не добавляет. */
.zt-step-track--vertical { display: flex; flex-direction: column; align-items: stretch; width: 100%; }
.zt-step-track__row { display: flex; gap: 14px; align-items: stretch; }
.zt-step-track__rail { display: flex; flex-direction: column; align-items: center; }
.zt-step-track__dot {
  width: 12px;
  height: 12px;
  margin-top: 5px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background: transparent;
  border: 2px solid var(--border-strong);
}
.zt-step-track__row--done .zt-step-track__dot { background: var(--blue-500); border: none; }
.zt-step-track__line { flex: 1; width: 2px; margin-top: 3px; background: var(--border-subtle); }
.zt-step-track__line--done { background: var(--blue-500); }
.zt-step-track__text { display: flex; flex-direction: column; gap: 2px; padding-bottom: 18px; }
.zt-step-track__title { font-size: 14px; font-weight: var(--fw-semibold); color: var(--text-tertiary); }
.zt-step-track__row--done .zt-step-track__title { color: var(--text-primary); }
.zt-step-track__note { font-size: 12px; color: var(--text-tertiary); font-feature-settings: var(--num-tabular); }

/* --- 2.5 SummaryRow (§2.5) --- */
.zt-summary-row { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; padding: 7px 0; }
.zt-summary-row--emphasis { padding: 14px 0 0; border-top: 1px solid var(--border-subtle); }

.zt-summary-row__label { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; font-weight: var(--fw-regular); color: var(--text-secondary); }
.zt-summary-row--emphasis .zt-summary-row__label { font-size: 15px; font-weight: var(--fw-semibold); color: var(--text-primary); }

.zt-summary-row__value {
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: var(--fw-semibold);
  letter-spacing: -0.005em;
  color: var(--text-primary);
  font-feature-settings: var(--num-tabular);
  white-space: nowrap;
}
.zt-summary-row--emphasis .zt-summary-row__value { font-family: var(--font-display); font-size: 22px; font-weight: var(--fw-bold); letter-spacing: -0.02em; }
.zt-summary-row--success .zt-summary-row__value { color: var(--text-success); }
.zt-summary-row--danger .zt-summary-row__value { color: var(--text-danger); }


/* ============================================================
   3. Feedback — components.md §3
   ============================================================ */

/* --- 3.1 Dialog (§3.1) --- */
.zt-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-overlay);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  padding: 24px;
  animation: zeo-fade var(--dur-base) var(--ease-out);
}
.zt-dialog-panel {
  width: 100%;
  max-width: 460px; /* default width — конкретный экран может переопределить инлайн */
  background: var(--surface-card);
  border-radius: var(--radius-surface);
  box-shadow: var(--shadow-xl);
  padding: 28px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  animation: zeo-rise var(--dur-base) var(--ease-out);
}
.zt-dialog__head { display: flex; align-items: flex-start; gap: 14px; }
.zt-dialog__icon { width: 44px; height: 44px; flex: 0 0 auto; border-radius: var(--radius-md); background: var(--surface-brand-soft); display: flex; align-items: center; justify-content: center; }
.zt-dialog__body { flex: 1; display: flex; flex-direction: column; gap: 6px; }
.zt-dialog__title { font-family: var(--font-display); font-size: 22px; font-weight: var(--fw-bold); letter-spacing: -0.016em; margin: 0; }
.zt-dialog__description { font-family: var(--font-body); font-size: 15px; line-height: 1.55; color: var(--text-secondary); margin: 0; }
.zt-dialog__footer { display: flex; justify-content: flex-end; gap: 10px; }

/* --- Триггер «Где найти логин?» — узкая пилюля под полем логина Steam
   (владелец, 2026-08-29, макет: `tmaHelpTriggerStyle`). Та же пара токенов,
   что красит активную пилюлю шапки (`.zt-app-header__pill--active` выше в
   файле) — оба места читаются как «мягкая брендовая подсказка», просто эта
   не переключатель, а кнопка, открывающая лист снизу. */
.zt-help-sheet-trigger {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 36px;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: var(--fw-semibold);
  background: var(--surface-brand-soft);
  border: 1px solid var(--border-brand);
  color: var(--text-brand);
}
.zt-help-sheet-trigger:focus-visible { box-shadow: var(--ring-focus); }
.zt-help-sheet-trigger:active { opacity: .92; }

/* --- 3.1b Bottom sheet: «Где найти логин?» на экране Steam (владелец,
   2026-08-29, макет — `ZeoTopup.dc.html` строки 1002-1027). Портала для
   модалок у приложения нет (`js/app.js`, весь рендер — один узел `#app`), и
   лист живёт там, где его отрисовал экран, — внутри **прокручиваемого** тела.
   Отсюда `fixed`, а не `absolute`: у абсолютного листа область отсчёта —
   само тело, и лист уезжает вместе с его прокруткой, то есть открытый со
   скролла он оказывается выше видимой области. `fixed` считает от вьюпорта,
   а вьюпорт мини-приложения и есть каркас — лист всегда там, где смотрят.
   Побочный выигрыш: заодно перекрыты пилюли шапки, которые из-под подложки
   оставались нажимаемыми. Нативную кнопку Telegram перекрыть нельзя ничем —
   она вне webview. z-index — как в макете, с запасом над содержимым, но не
   совпадает со слоем диалога (100), которым лист не является. */
.zt-help-sheet {
  position: fixed;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.zt-help-sheet__backdrop {
  position: absolute;
  inset: 0;
  background: var(--surface-overlay);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: pointer;
}
.zt-help-sheet__panel {
  position: relative;
  background: var(--surface-card);
  border-radius: 28px 28px 0 0;
  padding: 18px 18px 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-shadow: var(--shadow-lg);
}
.zt-help-sheet__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.zt-help-sheet__title { font-size: 17px; letter-spacing: -0.02em; font-weight: var(--fw-bold); }
/* Строка мелким серым под кнопкой — адрес, на который она ведёт, назван
   текстом рядом: `ctx.openLink` открывает системный браузер, а не остаётся
   в приложении, и адрес на кнопке одной подписи мало кому виден. */
.zt-help-sheet__link { font-size: 11px; line-height: 1.5; color: var(--text-tertiary); text-align: center; }

/* «Скриншот» страницы аккаунта Steam внутри листа — тёмная карточка со
   своим градиентом (тот же приём, что у `.zt-region-card--selected` и
   `.zt-hero--steam` выше: чужой бренд, литеральные hex, комментарий вместо
   инлайна). Плашка с логином — жёлтая, под цвет предупреждения о вводе
   именно логина, не отображаемого имени: моноширинный шрифт `--font-mono`
   держит её похожей на настоящее поле ввода Steam. */
.zt-login-help-shot {
  position: relative;
  border-radius: 14px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: linear-gradient(135deg, #16202D 25%, #2A475E);
  box-shadow: var(--shadow-sm);
}
.zt-login-help-shot__crumb { font-size: 11px; color: rgba(255, 255, 255, .45); }
.zt-login-help-shot__row { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.zt-login-help-shot__label { font-size: 19px; font-weight: var(--fw-black); letter-spacing: -0.02em; color: #fff; }
.zt-login-help-shot__badge {
  font-family: var(--font-mono);
  font-size: 16px;
  font-weight: var(--fw-bold);
  color: #FFE9A8;
  padding: 2px 7px;
  border-radius: 8px;
  border: 1px solid #E9B949;
  background: rgba(233, 185, 73, .16);
  text-transform: uppercase;
}
.zt-login-help-shot__id { font-family: var(--font-mono); font-size: 10px; color: rgba(255, 255, 255, .4); }

/* --- 3.2 Toast (§3.2) --- */
.zt-toast {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-width: min(320px, 100%);
  max-width: 420px;
  padding: 14px 16px;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  animation: zeo-toast var(--dur-base) var(--ease-out);
}
.zt-toast__icon--success { color: var(--green-500); }
.zt-toast__icon--error { color: var(--red-500); }
.zt-toast__icon--info { color: var(--blue-500); }
.zt-toast__icon--pending { color: var(--amber-500); }
.zt-toast__body { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.zt-toast__title { font-size: 14px; font-weight: var(--fw-semibold); color: var(--text-primary); }
.zt-toast__description { font-size: 13px; color: var(--text-secondary); line-height: 1.45; }
.zt-toast__close { color: var(--text-tertiary); cursor: pointer; }

/* --- 3.3 Tooltip (§3.3) ---
   Абсолютное позиционирование внутри потока — родитель с overflow:hidden
   (Card) обрежет пузырь; z-index 60, ниже Dialog (100). Дословно по
   источнику. */
.zt-tooltip { position: relative; display: inline-flex; }
.zt-tooltip__bubble {
  position: absolute;
  z-index: 60;
  padding: 7px 10px;
  max-width: 240px;
  width: max-content;
  background: var(--n-900);
  color: var(--text-inverse);
  border-radius: var(--radius-xs);
  font-size: 12px;
  line-height: 1.4;
  font-weight: var(--fw-medium);
  box-shadow: var(--shadow-md);
  pointer-events: none;
}
.zt-tooltip__bubble--top { bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); }
.zt-tooltip__bubble--bottom { top: calc(100% + 8px); left: 50%; transform: translateX(-50%); }
.zt-tooltip__bubble--left { right: calc(100% + 8px); top: 50%; transform: translateY(-50%); }
.zt-tooltip__bubble--right { left: calc(100% + 8px); top: 50%; transform: translateY(-50%); }


/* ============================================================
   4. App shell — TMA-специфичная обвязка, НЕ из _ds_bundle.js.
   ============================================================
   Разметка самого прототипа Mini App (design/ZeoTopup.dc.html,
   TMA-раздел), пересказанная в README.md «Screens / Views» и в
   shell.md §5–6 / screens_orders.md §5.1 и §7.3 (числа проверены по
   обоим источникам — совпадают). Тексту задания WP10a эти файлы не
   названы, но без этих классов `js/components/{header,composer,
   soft-card,ticket-pill}.js` (WP10b) собирать не из чего — см. handoff
   в ответе задачи. */

/* --- Шапка экрана (shell.md §5, README «Шапка») --- */
/* В полноэкранном режиме (`requestFullscreen`, js/telegram.js) шапка иначе
   встаёт под системную строку статуса (часы, батарея) и под собственные
   кнопки Telegram («Закрыть», шеврон, «...») — они рисуются поверх окна
   приложения, а не сдвигают его. `--tg-safe-top`/`--tg-content-safe-top`
   пишет адаптер (`applyInsets`) из `safeAreaInset`/`contentSafeAreaInset`
   Bot API 8.0; `env(safe-area-inset-top)` — запасной путь на клиентах, где
   этих полей ещё нет (переменная тогда не выставлена вовсе). Верхний
   отступ — их сумма поверх обычных 12px, нижний и боковые остаются как
   были: снизу и по бокам собственных кнопок Telegram нет.
   */
.zt-app-header {
  flex: 0 0 auto;
  padding: 12px 14px;
  padding-top: calc(12px + var(--tg-safe-top, env(safe-area-inset-top, 0px)) + var(--tg-content-safe-top, 0px));
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface-card);
}
/* Глиф "←" — литеральный текстовый символ в источнике, не Lucide-иконка
   (shell.md §5.1). Показывается только на пяти вложенных экранах. */
.zt-app-header__back {
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-family: inherit;
  font-size: 15px;
  color: var(--text-secondary);
}
.zt-app-header__back:focus-visible { box-shadow: var(--ring-focus); }
.zt-app-header__back:active { opacity: .85; }

.zt-app-header__title {
  flex: 1;
  min-width: 0;
  font-size: 14px;
  font-weight: var(--fw-bold);
  letter-spacing: -0.012em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.zt-app-header__pills { display: flex; gap: 8px; flex: 0 0 auto; }
.zt-app-header__pill {
  height: 34px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px 0 10px;
  font-size: 12px;
  font-weight: var(--fw-semibold);
  white-space: nowrap;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-family: inherit;
  background: transparent;
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  transition: var(--transition-control);
}
.zt-app-header__pill:focus-visible { box-shadow: var(--ring-focus); }
.zt-app-header__pill:active { opacity: .85; }
.zt-app-header__pill--active { background: var(--surface-brand-soft); border-color: var(--border-brand); color: var(--text-brand); }
/* Настройки в шапке — третья кнопка того же ряда, но без подписи: у неё нет
   раздела, и слово рядом с двумя пилюлями не помещается (владелец,
   2026-08-29). Квадрат по высоте пилюли, чтобы ряд не поехал. */
.zt-app-header__pill--compact {
  width: 34px;
  padding: 0;
  justify-content: center;
  gap: 0;
}

/* --- Продуктовый переключатель (shell.md §6, README п.2) ---
   4 таба по ширине своих слов, свободное место делится поверх (было —
   поровну, см. `.zt-product-tabs__tab`); виден на stars/premium/steam/gram/
   orders/support, скрыт вместе с шапка-назад (back-button ⟺ нет таб-бара). */
.zt-product-tabs {
  flex: 0 0 auto;
  padding: 8px;
  margin: 10px 14px 0;
  display: flex;
  gap: 4px;
  background: var(--surface-sunken);
  border-radius: var(--radius-pill);
  overflow-x: auto;
  scrollbar-width: none;
}
.zt-product-tabs::-webkit-scrollbar { display: none; }

.zt-product-tabs__tab {
  /* Основа — **содержимое**, а не ноль (владелец, 2026-08-29: «для премиум
     слишком маленькое поле»). При `flex-basis: 0` четыре вкладки делят
     полосу поровну, и самое длинное слово — «Premium» — оказывалось в
     подложке впритык к своим краям, пока у «Stars» и «GRAM» оставался
     запас. С `auto` каждая начинает со своей ширины, а свободное место
     делится поверх неё: тесной не остаётся ни одна. Не влезли все —
     полоса прокручивается (`overflow-x: auto` выше), а не сжимает слова. */
  flex: 1 1 auto;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: 13px;
  font-weight: var(--fw-semibold);
  white-space: nowrap;
  /* Воздух по бокам слова, а не только между словом и знаком: 6 px читались
     как обрезка подложки по буквам. */
  padding: 0 12px;
  min-width: 0;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.zt-product-tabs__tab:focus-visible { box-shadow: var(--ring-focus); }
.zt-product-tabs__tab:active { opacity: .85; }
.zt-product-tabs__tab--active {
  background: var(--surface-card);
  border-color: var(--border-brand);
  box-shadow: var(--shadow-sm);
  color: var(--text-brand);
  font-weight: var(--fw-bold);
}
/* Во вкладке стоит **настоящая марка товара**, а не одноцветный глиф
   (владелец, 2026-08-29). Макет рисовал там глифы Lucide, покрашенные в цвет
   товара; знаки Premium, Steam и GRAM в проекте есть, и узнаются они лучше
   любой перерисовки. Размер тот же, что был у глифа, — 18 px, иначе полоса
   поедет. Звезда приехала последней, в тот же день, и правило покраски глифа
   звёзд ушло вместе с самим глифом: марка красит себя сама. */
.zt-product-tabs__mark {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  display: block;
  border-radius: 5px;
  object-fit: contain;
}

/* --- Мягкая карточка-кнопка → `.zt-soft-card` (файл WP10b: `soft-card.js`)
   Общая база строк заказов/тикетов/сроков Premium/способов оплаты
   (screens_orders.md §2/§5, screens_products.md §5 «softBtn»). --- */
.zt-soft-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px;
  width: 100%;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  background: var(--surface-card);
  color: var(--text-primary);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-sm);
  border-radius: 16px;
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.zt-soft-card:focus-visible { box-shadow: var(--ring-focus); }
.zt-soft-card:disabled { cursor: not-allowed; opacity: .6; }
.zt-soft-card:not(:disabled):active { opacity: .92; }
.zt-soft-card--selected { border-color: var(--border-brand); box-shadow: var(--ring-focus); }

/* --- Пилюля тикета — второй, независимый набор от StatusPill (CLAUDE.md:
   «TicketPill stays a second, independent pill system... do not merge
   with StatusPill»; screens_orders.md §5.1 «ticketPill»). Тона, не
   зашитые статусы — какой статус на какой тон ложится, решает WP10b/
   backend (тикет у нас — тумблер open/closed, не воронка из прототипа). --- */
.zt-ticket-pill {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: var(--fw-bold);
  white-space: nowrap;
}
.zt-ticket-pill--brand { background: var(--surface-brand-soft); color: var(--text-brand); }
.zt-ticket-pill--success { background: var(--surface-success-soft); color: var(--text-success); }
.zt-ticket-pill--warning { background: var(--surface-warning-soft); color: var(--text-warning); }

/* --- Композер треда поддержки (screens_orders.md §7.3) --- */
.zt-composer {
  flex: 0 0 auto;
  padding: 12px 14px;
  border-top: 1px solid var(--border-subtle);
  background: var(--surface-card);
  display: flex;
  gap: 10px;
  align-items: flex-end; /* кнопка отправки прижата к низу, пока textarea не растёт (minHeight фиксирован) */
}

/* Общая textarea — композер треда И форма нового тикета
   (screens_orders.md §6/§7.3, `tmaTextareaStyle`, используется в обоих
   местах). Фокус-кольцо — не в источнике, добавлено по той же логике,
   что и §1 (единообразие с Input/Select). */
.zt-textarea {
  min-height: 58px;
  resize: none;
  padding: 10px 12px;
  flex: 1;
  font-family: var(--font-body);
  font-size: 16px; /* см. примечание про зум iOS ниже по файлу */
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--surface-page);
  border: 1px solid var(--border-default);
  border-radius: 12px;
  outline: none;
  transition: var(--transition-control);
}
.zt-textarea:focus { border-color: var(--border-brand); box-shadow: var(--ring-focus); }
.zt-textarea::placeholder { color: var(--text-tertiary); }

/* --- Пузыри переписки (screens_orders.md §7.2) --- */
.zt-bubble { display: flex; flex-direction: column; gap: 5px; padding: 13px; border-radius: 16px; max-width: 90%; }
.zt-bubble--own { align-self: flex-end; background: var(--surface-sunken); border: 1px solid var(--border-subtle); }
.zt-bubble--support { align-self: flex-start; background: var(--surface-brand-soft); border: 1px solid transparent; }
.zt-bubble__meta { display: flex; gap: 10px; align-items: center; justify-content: space-between; }
.zt-bubble__author { font-size: 11px; font-weight: var(--fw-bold); color: var(--text-tertiary); }
.zt-bubble__time { font-size: 11px; color: var(--text-tertiary); font-feature-settings: var(--num-tabular); }
.zt-bubble__text { font-family: var(--font-body); font-size: 12px; line-height: 1.55; color: var(--text-primary); }

/* Каркас приложения (WP11). Держит четыре полосы во всю высоту окна: высота
   берётся из `--tg-viewport-height`, которую адаптер обновляет по событию
   `viewportChanged` — на 100dvh поверх открытой клавиатуры Telegram лежит не
   то число. `min-height: 0` у тела обязателен: без него flex-ребёнок не
   сжимается ниже своего содержимого и прокручивается страница целиком, вместе
   с шапкой. */
.zt-app {
  display: flex;
  flex-direction: column;
  height: var(--tg-viewport-height, 100dvh);
  overflow: hidden;
  background: var(--surface-page);
}
.zt-app__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px 14px 20px;
}

/* 16px у всех полей ввода — не вкус, а требование платформы: Safari и вебвью
   Telegram на iOS приближают страницу при фокусе на поле, у которого шрифт
   меньше шестнадцати, и обратно она сама не отъезжает. Дизайн называл 15px у
   строки и 14px у textarea (§1.4); разница в пиксель-другой стоит дешевле, чем
   прыгающий экран на каждом вводе имени получателя. Не менять обратно, не
   проверив на живом iPhone. Второй способ — `maximum-scale=1` в viewport —
   намеренно не использован: он выключает и обычный пинч-зум, то есть чинит
   ввод за счёт тех, кто увеличивает текст, чтобы его прочесть. */


/* ============================================================
   5. Макет из Claude Design — цветной слой поверх четвёртой секции.
   ============================================================
   Источник: проект `ZeoTopup.dc.html` (claude.ai/design, TMA-раздел),
   локальный снимок — `design_cloud/ZeoTopup.dc.html`. Прежний вид этих
   же экранов владелец отклонил 2026-08-28; контракт с API от этого не
   поменялся, поменялась только вёрстка, поэтому здесь новые классы, а
   не новые данные.

   Что принесла переделка:
     * шапка товара стала цветной плиткой (`.zt-hero`) вместо строки
       текста на фоне страницы;
     * у заказа и у канала оплаты появилась марка товара (`.zt-mark`);
     * суммы «к оплате» и заголовок выданного заказа красятся градиентом
       (`.zt-gradient-text`).

   Числа ниже — литералы из макета. Токенов под эти цвета в дизайн-системе
   нет вовсе: это фирменные цвета чужих продуктов (звёздное золото, навигация
   Steam, синева Telegram), и они одинаковы в обеих темах — как и сами
   логотипы. Единственное исключение — `--gradient-brand`: он наш, и он
   токен. */

/* --- Цветная плитка товара (макет: «К покупке» / «Зачислим» / «Подписка
   Premium»). Внутри три слоя: градиент на самой плитке, блик поверх него
   и марка товара в углу; текст идёт последним и всегда белый. --- */
.zt-hero {
  position: relative;
  overflow: hidden;
  padding: 16px;
  border-radius: 16px;
  box-shadow: var(--shadow-sm);
}
.zt-hero--stars { background: linear-gradient(135deg, #FFC94A, #F2B01E 55%, #DB9200); }
.zt-hero--premium { background: var(--gradient-brand); }
.zt-hero--steam { background: linear-gradient(135deg, #2A475E, #16202D 70%); }
.zt-hero--gram { background: linear-gradient(135deg, #5BC6F5, #2AABEE 55%, #1789C4); }

/* Блик из левого верхнего угла. Три силы, по одной на глубину подложки:
   светлое золото и синева Telegram держат .35, наш градиент — .28,
   почти чёрный Steam — .16, иначе блик на нём читается пятном. */
.zt-hero__glare {
  position: absolute;
  inset: 0;
  background: radial-gradient(80% 60% at 30% 0%, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 70%);
  pointer-events: none;
}
.zt-hero--premium .zt-hero__glare {
  background: radial-gradient(80% 60% at 30% 0%, rgba(255, 255, 255, .28), rgba(255, 255, 255, 0) 70%);
}
.zt-hero--steam .zt-hero__glare {
  background: radial-gradient(80% 60% at 30% 0%, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 70%);
}

/* Марка в углу — два разных приёма, а не один с вариантами. Глиф Lucide
   уезжает за край и обрезается плиткой (звёзды, Steam); настоящий логотип
   (Premium, GRAM) обрезать нельзя — он остаётся целым и стоит с отступом. */
/* Марка в углу — фон, а не содержание: цифры лежат поверх неё, и читаться
   должны они (владелец, 2026-08-29). Поэтому логотип полупрозрачный и без
   тени: сплошной знак спорил с суммой, а тень под просвечивающим знаком
   выглядит грязью. Узнаваемости хватает — форма и цвет остаются. */
.zt-hero__logo {
  position: absolute;
  right: 14px;
  bottom: 14px;
  transform: rotate(-8deg);
  pointer-events: none;
  display: block;
  width: 52px;
  height: 52px;
  border-radius: 14px;
  opacity: .38;
}
/* Логотип GRAM Wallet светлее плитки, на которой лежит, и без затемнения
   сливается с ней в белое пятно. Число — из макета. */
.zt-hero__logo--gram { filter: brightness(.72) saturate(1.05); }

/* Звезда — единственный знак без своей подложки: скруглять её нечем, а
   круглить прозрачный квадрат значит обрезать лучи. Отсюда прямоугольные
   углы и размер побольше — форма у неё узнаётся сама. */
.zt-hero__logo--stars {
  border-radius: 0;
  width: 72px;
  height: 72px;
  opacity: .32;
}

/* У знака Steam ровно обратная беда: тёмный конец его собственного круга
   (#051839) почти совпадает с тёмным концом плитки (#16202D), и марка
   читается пятном. Поднимаем яркость и добавляем светлый кант — синяя тень
   из общего правила на такой подложке не видна вовсе. */
.zt-hero__logo--steam {
  filter: brightness(1.35) saturate(1.1);
  /* Полупрозрачность общая, но на почти чёрной плитке .38 уводит знак в
     невидимость — здесь чуть плотнее. */
  opacity: .5;
}

.zt-hero__row {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
}
.zt-hero__col { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.zt-hero__col--end { align-items: flex-end; text-align: right; }

.zt-hero__overline {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: var(--fw-bold);
  color: rgba(255, 255, 255, .85);
}
.zt-hero--steam .zt-hero__overline { color: rgba(255, 255, 255, .7); }

.zt-hero__subject {
  font-size: 25px;
  font-weight: var(--fw-black);
  letter-spacing: -0.028em;
  line-height: 1.05;
  color: #fff;
  font-feature-settings: var(--num-tabular);
}
/* У звёзд крупная величина — количество, и оно короче суммы: макет даёт ему
   два лишних пикселя. */
.zt-hero--stars .zt-hero__subject { font-size: 27px; }

.zt-hero__value {
  font-size: 18px;
  font-weight: var(--fw-bold);
  letter-spacing: -0.02em;
  color: #fff;
  font-feature-settings: var(--num-tabular);
}
.zt-hero__note {
  font-size: 12px;
  color: rgba(255, 255, 255, .85);
  font-feature-settings: var(--num-tabular);
}
.zt-hero--steam .zt-hero__note { color: #66C0F4; }
.zt-hero--premium .zt-hero__note { color: #D6E4FF; }

/* --- Марка товара: скруглённый квадрат в строке заказа, в шапке карточки
   заказа и у канала оплаты. Радиус — 32 % стороны (макет: `Math.round(size *
   0.32)`), поэтому у каждого размера он свой. --- */
.zt-mark {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  box-shadow: var(--shadow-xs);
  color: #fff;
}
.zt-mark--sm { width: 38px; height: 38px; border-radius: 12px; }
.zt-mark--md { width: 40px; height: 40px; border-radius: 13px; }

/* У звёзд плитка своя, а знак поверх неё: файл владельца (2026-08-29) — это
   сама звезда на прозрачном фоне, без подложки, в отличие от `premium-logo`
   и `steam-logo`, у которых подложка нарисована внутри. Поэтому два слоя, а
   не один: сверху знак, снизу тот же градиент, что был здесь до него. */
.zt-mark--stars {
  background-image: url("../img/stars-logo.svg"),
    linear-gradient(135deg, #FFC94A, #F2B01E 55%, #DB9200);
  background-size: 58%, cover;
  background-position: center, center;
}
.zt-mark--steam { background-image: url("../img/steam-logo.svg"); }
/* Марка — сам логотип, а не глиф на подложке. У Premium и GRAM так и в
   макете (`background: url(...)`, глиф под ним прозрачный); Steam макет рисовал
   глифом `gamepad-2`, но знак Valve у нас есть, и владелец велел ставить
   настоящий (2026-08-29). Он самодостаточен — тёмный круг уже внутри файла,
   поэтому и подложки ему не нужно. */
.zt-mark--premium { background-image: url("../img/premium-logo.png"); }
.zt-mark--gram { background-image: url("../img/gram-logo.svg"); }

/* Каналы оплаты. `heleket` занимает слот, который в макете назывался
   CryptoBot: та же роль (долларовая платёжка) и тот же градиент. Монетный
   канал в макете отсутствует — он наш, и марка ему выдана телеграмная, та
   же, что у товара GRAM. */
.zt-mark--gateway-heleket { background-image: linear-gradient(135deg, #2E6BFF, #38D6E0); }
.zt-mark--gateway-xrocket { background-image: linear-gradient(135deg, #8B5CF6, #EC4899); }
.zt-mark--gateway-ton_gram { background-image: linear-gradient(135deg, #5BC6F5, #2AABEE 55%, #1789C4); }

/* --- Точка выбора канала оплаты. Не `.zt-radio-option__ring` из первой
   секции: там кольцо с точкой внутри, здесь — одна окружность, которая
   заполняется толщиной бордера (макет: 2px → 5px). --- */
.zt-radio-dot {
  width: 18px;
  height: 18px;
  border-radius: var(--radius-pill);
  flex: 0 0 auto;
  border: 2px solid var(--border-strong);
  transition: border-color var(--dur-fast) var(--ease-standard), border-width var(--dur-fast) var(--ease-standard);
}
.zt-radio-dot--on { border: 5px solid var(--blue-500); }

/* --- Строка выгод Premium. Отдельно от `.zt-chip`: тот кликабелен, у него
   свой размер и свои состояния, а это подпись. --- */
.zt-perk {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-brand-soft);
  color: var(--text-brand);
  font-size: 11px;
  font-weight: var(--fw-bold);
}
.zt-perks { display: flex; flex-wrap: wrap; gap: 6px; }

/* --- Плашка с иконкой: предупреждение на подтверждении и пояснение на
   экране поддержки. В макете это два одинаковых блока с разным фоном. --- */
.zt-callout {
  display: flex;
  gap: 10px;
  padding: 14px;
  border-radius: 16px;
  align-items: flex-start;
}
.zt-callout--warning { background: var(--surface-warning-soft); }
.zt-callout--brand { background: var(--surface-brand-soft); }
/* Третий тон — нейтральный, не предупреждающий и не брендовый: справка про
   регионы Steam не предостерегает и никуда не зовёт, поэтому вместо цветной
   мягкой заливки берёт тот же приглушённый серый, что и панель «О
   приложении» на экране настроек (`.zt-choice-row`'s соседи, `ABOUT` в
   `views/settings.js`) — `--surface-sunken`. */
.zt-callout--muted { background: var(--surface-sunken); }
.zt-callout__icon { flex: 0 0 auto; }
.zt-callout--warning .zt-callout__icon { color: var(--text-warning); }
.zt-callout--brand .zt-callout__icon { color: var(--text-brand); }
.zt-callout--muted .zt-callout__icon { color: var(--text-tertiary); }
.zt-callout__text { font-family: var(--font-body); font-size: 12px; line-height: 1.5; }
.zt-callout--warning .zt-callout__text { color: var(--text-warning); }
.zt-callout--brand .zt-callout__text { color: var(--text-secondary); }
.zt-callout--muted .zt-callout__text { color: var(--text-secondary); }

/* --- Кружок с буквой: чей это аккаунт (звёзды) и кому подписка (Premium).
   Заливка — наш фирменный градиент, буква белая. --- */
.zt-avatar {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background: var(--gradient-brand);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: var(--fw-bold);
  color: #fff;
}

/* --- Градиентный текст: сумма «к оплате» на подтверждении и заголовок
   зачисленного заказа. `-webkit-` парой, а не одним `background-clip`:
   вебвью Telegram на iOS до сих пор требует префикс, и без него текст
   становится прозрачным без заливки, то есть невидимым. --- */
.zt-gradient-text {
  background-image: linear-gradient(100deg, #2E6BFF, #38D6E0);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
.zt-gradient-text--done { background-image: linear-gradient(100deg, #34D399, #38D6E0, #2E6BFF); }


/* --- Экран настроек (владелец, 2026-08-29; макет из Claude Design) -------
   Две формы, которых нет среди общих компонентов: карточка выбора со
   свотчем-иконкой (тема) и строка выбора с круглой меткой (язык). Обе —
   «выбор одного из нескольких», но у первой знак сверху и карточки в ряд, у
   второй знак справа и строки в столбик; свести их в один компонент значило
   бы получить компонент с флагом «а нарисуй иначе». */
.zt-theme-card {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px;
  border-radius: 16px;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  color: var(--text-primary);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: var(--transition-control);
}
.zt-theme-card:focus-visible { box-shadow: var(--ring-focus); }
.zt-theme-card:active { opacity: .92; }
.zt-theme-card--selected {
  background: var(--surface-brand-soft);
  border-color: var(--border-brand);
  box-shadow: var(--ring-focus);
}
.zt-theme-card__swatch {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 12px;
  background: var(--surface-sunken);
  color: var(--text-secondary);
}
/* У выбранной свотч заливается брендом — так же, как марка канала оплаты. */
.zt-theme-card--selected .zt-theme-card__swatch {
  background: var(--gradient-brand);
  color: #fff;
}
.zt-theme-card__title { font-size: 14px; font-weight: var(--fw-semibold); }
.zt-theme-card__note { font-size: 12px; color: var(--text-tertiary); }

/* --- Регион Steam: свотч сверху, заголовок, приписка, сетка 2×2.
   Приведено к обновлённому макету владельца (2026-08-29, `ZeoTopup.dc.html`
   строки 2077-2115, `tmaCurChoices`) — прежняя версия красила выбранную
   плитку одним тоном (`--surface-brand-deep`, наш обычный бренд), макет
   красит **фирменным градиентом Steam**: тёмно-синяя сталь в светлый угол,
   почти чёрный в противоположный. Это чужой бренд, а не наш — литеральные
   hex здесь так же законны, как в `.zt-hero--steam` выше в этом файле, и
   держатся в CSS с комментарием, а не инлайном в разметке. */
.zt-region-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}
.zt-region-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 76px;
  padding: 10px 12px;
  border-radius: 16px;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-sm);
  color: var(--text-primary);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 140ms var(--ease-standard), transform 140ms var(--ease-standard);
}
.zt-region-card:focus-visible { box-shadow: var(--ring-focus); }
.zt-region-card:active { opacity: .92; }
/* Выбранный регион залит градиентом Steam (макет владельца, 2026-08-29):
   `#3C6584` в верхнем левом углу к `#16202D` на 75% диагонали — те же два
   тона, что у карточки товара Steam (`.zt-hero--steam` рядом использует
   близкий, но не тот же градиент — здесь плитка мельче и светлее у макета,
   свои числа). Тень и лёгкий сдвиг вверх — та же пара токенов, что у
   поднятой карточки где-либо ещё (`--shadow-md` вместо `--shadow-sm`,
   `translateY(-1px)`).

   **Обводка выражена, а не прозрачна** (владелец, 2026-08-30). Прежде рамка
   выбранной плитки была `transparent` — заливка считалась сама себе
   границей, — и в тёмной теме, где вся сетка и так тёмная, «выбрана» читалось
   только по галочке. Теперь по контуру идёт голубой Steam (`#66C0F4`, тот же
   тон, что у свотча и приписки внутри плитки) плюс кольцо в два пикселя
   вокруг: три пикселя видимого контура вместо нуля.

   Кольцо — вторым слоем `box-shadow`, а не толщиной рамки: рамка в 3 px
   отняла бы их у padding и сдвинула бы текст на выбранной плитке
   относительно соседних. Оно же остаётся снаружи скругления, поэтому
   `border-radius` пересчитывать не нужно. */
.zt-region-card--selected {
  background: linear-gradient(135deg, #3C6584, #16202D 75%);
  border-color: #66C0F4;
  box-shadow: var(--shadow-md), 0 0 0 2px rgba(102, 192, 244, .45);
  transform: translateY(-1px);
  color: var(--white);
}
/* Галочка выбранного региона (владелец, 2026-08-29). Заливки в макете
   оказалось мало: четыре плитки в две колонки, и тёмная среди светлых
   читается как «эта другая», а не как «эта выбрана» — особенно в тёмной теме,
   где вся сетка и так тёмная. Метка та же, что у выбранного заказа в списке
   обращений (`.zt-order-picker__mark--selected`), только в углу плитки:
   у колонки «символ → регион → валюта» правого края нет, и вставать метке
   больше некуда. Рисуется **только** у выбранной: четыре пустых кружка
   спрашивали бы «а тут что», хотя выбор ровно один. */
.zt-region-card__check {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--blue-500);
  color: var(--white);
}

.zt-region-card__swatch {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-bottom: 2px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: var(--fw-black);
  line-height: 1;
}
/* У выбранной свотч берёт голубой тон Steam-акцента — того же семейства,
   что и `#66C0F4` у курса в `.zt-hero--steam .zt-hero__note` выше в файле,
   но макет здесь называет свой оттенок (`#9BD8FA`) на 22%-ной подложке того
   же цвета: свотч сидит на тёмном градиенте плитки, а не на подложке во всю
   карточку. */
.zt-region-card--selected .zt-region-card__swatch {
  background: rgba(102, 192, 244, .22);
  color: #9BD8FA;
}
.zt-region-card__title {
  font-size: 13px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.02em;
  color: var(--text-primary);
}
.zt-region-card--selected .zt-region-card__title { color: var(--white); }
.zt-region-card__note {
  font-size: 12px;
  font-weight: var(--fw-semibold);
  font-feature-settings: var(--num-tabular);
  color: var(--text-tertiary);
}
/* На тёмной заливке приглушённый серый почти пропадает — приписка берёт
   тот же светлый фирменный тон, что и свотч выбранной плитки. */
.zt-region-card--selected .zt-region-card__note { color: #9BD8FA; }

.zt-choice-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: 56px;
  padding: 12px 14px;
  border-radius: 16px;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  color: var(--text-primary);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: var(--transition-control);
}
.zt-choice-row:focus-visible { box-shadow: var(--ring-focus); }
.zt-choice-row:active { opacity: .92; }
.zt-choice-row--selected {
  background: var(--surface-brand-soft);
  border-color: var(--border-brand);
}
.zt-choice-row__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.zt-choice-row__title { font-size: 15px; font-weight: var(--fw-semibold); }
.zt-choice-row__note { font-size: 12px; color: var(--text-tertiary); }
/* Метка выбора: пустой кружок с рамкой, у выбранной — залитый с галочкой.
   Не `checkbox` и не `radio`: строка целиком и есть кнопка, а нативный
   контрол внутри неё ловил бы второе нажатие на себя. */
.zt-choice-row__mark {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  color: #fff;
}
.zt-choice-row--selected .zt-choice-row__mark {
  background: var(--blue-500);
  border-color: var(--blue-500);
}

/* --- Блок «Гарантируем»: общая карточка на всех четырёх товарных экранах
   (`js/views/pay.Guarantee` — не своя копия на каждом экране, макет
   владельца держит её общей тоже). Шапка — кнопка целиком, раскрывающая
   список; список написан из словаря `common.guarantee_*`. */
.zt-guarantee {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: 20px;
  box-shadow: var(--shadow-sm);
}
.zt-guarantee__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0;
  min-height: 24px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  width: 100%;
}
.zt-guarantee__head-left { display: flex; align-items: center; gap: 8px; }
.zt-guarantee__title { font-size: 12px; font-weight: var(--fw-bold); letter-spacing: -0.01em; color: var(--text-secondary); }
.zt-guarantee__chevron {
  display: flex;
  color: var(--text-tertiary);
  transition: transform 200ms var(--ease-standard);
}
.zt-guarantee__chevron--open { transform: rotate(180deg); }
.zt-guarantee__list { display: flex; flex-direction: column; gap: 12px; }
.zt-guarantee__item { display: flex; gap: 12px; align-items: flex-start; }
.zt-guarantee__num {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  margin-top: 1px;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: var(--fw-bold);
  color: var(--text-tertiary);
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
}
.zt-guarantee__item-body { display: flex; flex-direction: column; gap: 3px; }
.zt-guarantee__item-title { font-size: 13px; font-weight: var(--fw-bold); letter-spacing: -0.01em; }
.zt-guarantee__item-text { font-family: var(--font-body); font-size: 11px; line-height: 1.5; color: var(--text-tertiary); }

/* --- Список заказа на экране нового обращения → `.zt-order-picker*`
   (`js/views/new-ticket.js`, макет владельца, 2026-08-29, строки 1002-1090
   `design_cloud/ZeoTopup.dc.html`). Свой раскрывающийся список карточек, а
   не компонент `Select` (§1.5 выше): у пункта два ряда текста и метка-
   галочка, которых у `<select>`/`<option>` не бывает. Триггер и пункт
   списка — одна и та же геометрия (макет их не различает), поэтому у обеих
   кнопок общие классы `__info`/`__code`/`__title`; раскрытость красит
   только триггер (`--open`), выбранность — только пункт (`--selected`). */
.zt-order-picker { position: relative; display: flex; flex-direction: column; gap: 8px; width: 100%; }

.zt-order-picker__trigger,
.zt-order-picker__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: 56px;
  padding: 10px 14px;
  border-radius: 16px;
  cursor: pointer;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.zt-order-picker__trigger:focus-visible,
.zt-order-picker__item:focus-visible { box-shadow: var(--ring-focus); }

/* Раскрытый триггер — та же рамка/тень, что у выбранного пункта ниже, но
   это два разных состояния (открыт vs выбран), поэтому классы не общие. */
.zt-order-picker__trigger--open { border-color: var(--border-brand); box-shadow: var(--ring-focus); }

.zt-order-picker__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 236px;
  overflow-y: auto;
  padding: 2px;
}

.zt-order-picker__item { border-color: var(--border-subtle); }
.zt-order-picker__item--selected {
  background: var(--surface-brand-soft);
  border-color: var(--border-brand);
  box-shadow: var(--ring-focus);
}
.zt-order-picker__item--selected .zt-order-picker__code { color: var(--text-brand); }

.zt-order-picker__info {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  text-align: left;
}
.zt-order-picker__code {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  color: var(--text-tertiary);
}
.zt-order-picker__title {
  font-size: 13px;
  font-weight: var(--fw-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.zt-order-picker__chevron {
  flex: 0 0 auto;
  display: flex;
  color: var(--text-tertiary);
  transition: transform 200ms var(--ease-standard);
}
.zt-order-picker__trigger--open .zt-order-picker__chevron { transform: rotate(180deg); }

/* Метка-галочка справа от пункта: невыбранная — кольцо без заливки,
   галочка спрятана через прозрачный `color` (SVG красит обводку
   `currentColor`, см. `js/components/icon.js`), а не через `display: none`
   — так рисуется ровно как в макете, а не другой разметкой пункта. */
.zt-order-picker__mark {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  color: transparent;
  background: transparent;
  border: 2px solid var(--border-strong);
}
.zt-order-picker__mark--selected {
  color: #fff;
  background: var(--blue-500);
  border-color: transparent;
}
