/* ai4you — оформление. Лаконичная современная тема (тёмная + светлая). */

/* Шрифт Inter (self-hosted, переменный). Отличная кириллица — как у Т-Банка/VK/Ozon. */
/* `optional`, а не `swap` (A_О_520): swap подменял запасной шрифт уже
   на экране, шапка телефона из двух строк сжималась в одну, и страница
   прыгала на 41 px (CLS 0,24 на 390). Файлы предзагружаются в base.html
   и почти всегда успевают к первой отрисовке; не успели — эта загрузка
   остаётся на запасном шрифте без прыжка, следующая берёт из кэша. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-display: optional;
  font-weight: 100 900;
  src: url("../fonts/inter-cyrillic-wght-normal.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-display: optional;
  font-weight: 100 900;
  src: url("../fonts/inter-latin-wght-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Тёмная тема (по умолчанию) */
  /* Тёмная палитра сведена к макетам Stitch (слово владельца 27.08,
     A_О_269): нейтральный графит вместо синевы, светло-голубой акцент.
     Токены из DESIGN.md образца (raspak3): фон surface #11131a,
     карточки surface-container, бордер outline-variant, акцент primary.
     Кнопки (--fill) НЕ образцовые намеренно: у образца текст на кнопке
     тёмный, у нас белый, и белый на #528dff даёт 3,2 при норме 4,5
     (правило A_О_124 — контраст мерить, не копировать). */
  --bg: #11131a;
  --bg-soft: #151b2b;
  /* Карточки образца — «dark-surface» #141b2d из его же code.html:
     синие карточки на графитовом фоне (слово владельца 27.08). */
  --surface: #141b2d;
  --surface-2: #1a2338;
  --fg: #e1e2ec;
  --fg-strong: #f2f3f9;
  --muted: #c2c6d6;
  --accent: #afc6ff;
  --accent-2: #7c5cff;
  /* ЗАЛИВКА ПОД БЕЛЫМ ТЕКСТОМ — ОТДЕЛЬНО ОТ АКЦЕНТА (замер A_О_124,
     решение владельца 15.08). Один цвет служил двум ролям, и роли тянут
     в разные стороны: как фон кнопки он должен быть темнее (белый текст
     на #4f8cff даёт 3,22 при норме 4,5), как цвет ссылки на тёмном фоне —
     светлее (затемнение уронило бы ссылки с 5,85 до 3,76). Поэтому
     заливка живёт своей переменной: белый на #4173d1 — 4,57. */
  /* Кнопки тёмной темы — по файлу образца (primary-container #528dff,
     on-primary-container #00275f): тёмный текст на светлой заливке,
     контраст 4,54 — проходит AA (мерено, не скопировано вслепую). */
  --fill: #528dff;
  --fill-2: #6b8bff;
  --btn-fg: #00275f;
  --border: #23304d;
  --border-hover: #35456d;
  /* РАМКА КНОПКИ ШАПКИ — СВОЯ, А НЕ ОБЩАЯ (слово владельца 10.09:
     «Регистрация» в тёмной теме не читается как кнопка).

     Замерено браузером на статье 8: общая `--border` давала рамке
     против фона шапки 1,46 в тёмной и 1,25 в светлой при норме 3,0
     для границ элементов управления (WCAG 1.4.11). Текст при этом
     проходил с запасом (12,14 и 13,26) — беда была не в тексте,
     а в том, что кнопку не видно КАК кнопку.

     Почему отдельная переменная, а не подъём `--border`: общая рамка
     рисует карточки, поля и таблицы, где 1,2 к фону — намеренная
     мягкость. Подняв её, я бы починила одно место и огрубила
     двадцать. Правится роль, а не цвет. */
  --cta-border: #5a6a8f;
  /* Карточка очереди: темнее фона страницы, а не светлее. Светлые блоки
     на тёмном фоне читались как промежутки — владелец так и сказал:
     «нужно, чтобы темнее были блоки, а не промежутки между ними». */
  --queue-cell: #070b13;
  --radius: 14px;
  /* Шкала размеров текста. Заведена 2026-08-07: в файле накопилось два
     десятка размеров, включая пиксели вперемешку с rem и по четыре почти
     одинаковых значения подряд (.88 / .9 / .92 / .95). Глазом такая разница
     не читается как замысел — она читается как небрежность, и страницы
     выглядят собранными из разных кусков.

     Ступеней намеренно мало: если новому тексту не подходит ни одна,
     это повод пересмотреть блок, а не завести .93rem.

     В пикселях не задаём ничего: rem уважает размер шрифта, выбранный
     человеком в браузере, а px его игнорирует — для тех, кому мелко,
     это разница между «читаемо» и «нет». */
  --fs-xs: .75rem;    /* метки, плашки поверх картинок */
  --fs-sm: .82rem;    /* подписи под элементами, мелкие кнопки */
  --fs-md: .9rem;     /* вторичный текст: подсказки, пояснения */
  --fs-base: 1rem;    /* основной текст */
  --fs-lg: 1.15rem;   /* заметная строка: цена, итог */
  --fs-xl: 1.5rem;    /* заголовок внутри страницы */
  --shadow: 0 10px 30px -12px rgba(0, 0, 0, .55);
  --card-shadow: none;
  --ring: 0 0 0 3px rgba(79, 140, 255, .35);
  /* Ширина содержимого. Расширяется на мониторах шире FHD — см. media-запросы
     ниже по файлу: на 4K при 1080 px по краям остаётся по четверти экрана. */
  --maxw: 1080px;
  /* Цвета, зависящие от темы (переопределяются в светлой) */
  --hero-grad: linear-gradient(180deg, #fff, #b9c6e6);
  --topbar-bg: rgba(11, 15, 26, .72);
  --body-bg:
    radial-gradient(900px 500px at 80% -10%, rgba(124, 92, 255, .16), transparent 60%),
    radial-gradient(1000px 600px at 0% 0%, rgba(79, 140, 255, .14), transparent 55%),
    var(--bg);
  --error-bg: #3a1f28;
  --error-fg: #ffb3c1;
  --error-border: #6b2a3a;
  /* Цвет выполненного: галочка у пройденного шага обработки */
  --ok-fg: #4ec27a;
  /* ФОН ЗНАЧКА «ГОТОВО» — СВОЙ У КАЖДОЙ ТЕМЫ (замер A_Д_90/A_О_124).
     Переменной `--ok-bg` не было вовсе: цвет брался из запасного
     значения в правиле, а оно светлое. В тёмной теме выходил
     светло-зелёный текст на светло-зелёном фоне — контраст 2,02
     при норме 4,5, то есть значок почти не читался. Пара цветов
     должна задаваться вместе: половина, оставленная на запасное
     значение, разъезжается с той, что переопределили. */
  --ok-bg: #12301f;
}

/* Светлая тема: авто по системной настройке + ручное [data-theme="light"] */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) { color-scheme: light; }
  :root:not([data-theme="dark"]) {
    --bg: #e7ecf4;
    --bg-soft: #dde5f0;
    --surface: #ffffff;
    --surface-2: #eef2f9;
    --fg: #1b2740;
    --fg-strong: #0c1424;
    --muted: #55627a;
    /* АКЦЕНТ СВЕТЛОЙ ТЕМЫ ТЕМНЕЕ ЗАЛИВКИ (замер A_О_124). Прежний #3b6fe0
       годился только на белом (4,63): на мягких фонах — плашке cookies,
       подвале, карточках — он давал 4,12 и 3,65 при норме 4,5. Беда была
       не в подвале, а в самом цвете, поэтому правится он, а не место:
       #3361c3 проходит на всех трёх фонах (5,77 / 5,14 / 4,55). */
    --accent: #3361c3;
    --accent-2: #6a48e6;
    /* В светлой теме заливка совпадает с акцентом: белый на #3b6fe0 даёт
       4,63, менять нечего. Переменная всё равно своя — иначе следующая
       правка акцента молча уронит контраст кнопок. */
    --fill: #3b6fe0;
    --fill-2: #6a48e6;
    --btn-fg: #ffffff;
    --border: #c9d5e8;
    --border-hover: #b0c2dd;
    /* Рамка кнопки шапки светлой темы — см. разбор в тёмной палитре.
       В светлой было ХУЖЕ (1,25): фон кнопки почти совпадал с фоном
       шапки, 1,06. */
    --cta-border: #6c7c99;
    --queue-cell: #d7dfec;
    --shadow: 0 14px 34px -18px rgba(20, 40, 80, .30);
    --card-shadow: 0 6px 22px -12px rgba(23, 43, 77, .22);
    --ring: 0 0 0 3px rgba(59, 111, 224, .30);
    --hero-grad: linear-gradient(180deg, #10213f, #3b6fe0);
    --topbar-bg: rgba(231, 236, 244, .78);
    --body-bg:
      radial-gradient(900px 500px at 80% -10%, rgba(124, 92, 255, .12), transparent 60%),
      radial-gradient(1000px 600px at 0% 0%, rgba(59, 111, 224, .12), transparent 55%),
      var(--bg);
    --error-bg: #ffe3e9;
    --error-fg: #a3203a;
    --error-border: #f3b6c3;
    /* Текст темнее: #1f8a4c давал 3,92 при норме 4,5. */
    --ok-fg: #187540;
    --ok-bg: #e7f6ec;
  }
}
:root[data-theme="light"] {
  color-scheme: light;
  --queue-cell: #d7dfec;
  --bg: #e7ecf4;
  --bg-soft: #dde5f0;
  --surface: #ffffff;
  --surface-2: #eef2f9;
  --fg: #1b2740;
  --fg-strong: #0c1424;
  --muted: #55627a;
  --accent: #3361c3;
  --accent-2: #6a48e6;
  --fill: #3b6fe0;
  --fill-2: #6a48e6;
  /* БЕЛЫЙ ТЕКСТ НА КНОПКЕ — И ЗДЕСЬ (A_Д_451, находка D_Д_483). Копия
     потеряла эту строку: при тёмной системе и светлом сайте кнопки
     брали тёмный текст тёмной палитры на светлой заливке. Полноту
     копии стережёт `core.test_svetlaya_tema_kopiya_polnaya`. */
  --btn-fg: #ffffff;
  --border: #c9d5e8;
  --border-hover: #b0c2dd;
  --cta-border: #6c7c99;
  --shadow: 0 14px 34px -18px rgba(20, 40, 80, .30);
  --card-shadow: 0 6px 22px -12px rgba(23, 43, 77, .22);
  --ring: 0 0 0 3px rgba(59, 111, 224, .30);
  --hero-grad: linear-gradient(180deg, #10213f, #3b6fe0);
  --topbar-bg: rgba(231, 236, 244, .78);
  --body-bg:
    radial-gradient(900px 500px at 80% -10%, rgba(124, 92, 255, .12), transparent 60%),
    radial-gradient(1000px 600px at 0% 0%, rgba(59, 111, 224, .12), transparent 55%),
    var(--bg);
  --error-bg: #ffe3e9;
  --error-fg: #a3203a;
  --error-border: #f3b6c3;
  --ok-fg: #187540;
  --ok-bg: #e7f6ec;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 16px;
  background: var(--body-bg);
  color: var(--fg);
  line-height: 1.65;
  letter-spacing: -.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* Прижимаем подвал к низу окна: на коротких страницах (пустой кабинет,
     список из пары плиток) он иначе повисает посреди экрана, а под ним
     остаётся полоса фона. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  /* Плашка cookies висит поверх низа окна, и всё, что оказалось под ней,
     клиент не видит и не нажмёт. Пока плашка показана, страница кончается
     на её высоту выше; величину меряет сам баннер (`--cookie-bar-space`),
     потому что она зависит от языка и ширины окна. Приёмка D 15.08. */
  padding-bottom: var(--cookie-bar-space, 0px);
}

a { color: var(--accent); text-decoration: none; transition: color .15s ease; }
a:hover { color: #7ea9ff; }
/* «Изменить ссылку» на экране подтверждения — КНОПКА формы (правка везёт
   выбор подписанным пакетом, A_О_304), но выглядеть обязана ссылкой, как
   «изменить» под превью. Базового правила у `.btn-link` не было — только
   размер под палец в @media, — и на широком экране кнопка выходила белым
   прямоугольником (владелец 03.09 глазами на бою, A_Д_286_В). */
.btn-link {
  background: none; border: 0; padding: 0; margin: 0; font: inherit;
  color: var(--accent); cursor: pointer; text-decoration: none;
  transition: color .15s ease;
}
.btn-link:hover { color: #7ea9ff; }

h1, h2, h3 { line-height: 1.15; letter-spacing: -.025em; color: var(--fg-strong); }

:focus-visible { outline: none; box-shadow: var(--ring); border-radius: 8px; }

/* --- Шапка --- */
/* Высота шапки нужна не только ей: под неё подстраивается всё липкое
   ниже по странице (кадр редактора). Держим числом в одном месте —
   иначе следующая правка отступов шапки молча заведёт чужие блоки
   ей под низ. */
:root { --topbar-h: 72px; }
/* Наименьшая цель под палец. Число из руководств Apple и Android —
   у обоих оно одно, 44. Живёт здесь одно на весь лист: см. блок
   про площадь касания ниже. */
:root { --palec: 44px; }
.topbar {
  position: sticky; top: 0; z-index: 50;
  /* body — колонка flex, и без запрета на сжатие шапка с подвалом
     сплющивались бы на длинных страницах */
  flex-shrink: 0;
  background: var(--topbar-bg);
  backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid var(--border);
}
/* В РЕДАКТОРЕ НА ТЕЛЕФОНЕ ШАПКА НЕ ЛИПНЕТ (A_Д_153_В).

   Замер браузером 17.08 на 375x667 (iPhone SE, наш дешёвый покупатель):
   шапка занимает не заявленные в макете 72 px, а 150 — ряды переносятся
   (`flex-wrap`), и на узком экране их три: марка; имя, «Кабинет»,
   баланс, «Новая нарезка»; «Выйти», тема, язык. Это 22% окна ВСЁ ВРЕМЯ,
   а клиенту под саму правку остаётся 517 px.

   Ни одним из этих органов правку не делают: путь к «Сохранить» здесь
   3,7 экрана (A_Д_154_В), и человек проходит его многократно. Отдаём
   ему эти 150 px: шапка уезжает вверх и возвращается прокруткой наверх.

   Только редактор и только телефон. На широком экране шапка ничего
   не съедает (72 px из 900), а на других страницах липкость помогает
   уйти из длинной ленты — там её трогать нечем. */
@media (max-width: 560px) {
  .body-editor .topbar { position: static; }
}
.topbar-inner {
  max-width: var(--maxw); margin: 0 auto;
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 24px; gap: 12px; flex-wrap: wrap;
  /* ВЫСОТА ШАПКИ ОДНА НА ВСЕ СТРАНИЦЫ (A_О_291_В, слово владельца
     29.08: «при переходе по левому меню разная высота шапки»).

     Причина найдена снимками: кнопка «Новая нарезка» (`.nav-cta`)
     прячется на «Моих заказах» и на самой форме заказа, а на прочих
     страницах стоит — и она выше строки ссылок, потому что у неё
     свои поля и рамка. Шапка росла и падала на 11 px при каждом
     переходе по левому меню.

     Держим МИНИМУМ, а не жёсткую высоту: на узком экране содержимое
     переносится на второй ряд, и `height` обрезал бы его.
     Число уже живёт переменной — под неё подстроено всё липкое ниже
     (кадр редактора), и второго числа здесь заводить нельзя. */
  min-height: var(--topbar-h);
  box-sizing: border-box;
}
.brand { font-size: 1.25rem; font-weight: 800; color: var(--fg); letter-spacing: -.02em; }
.brand span { color: var(--accent); }
.topbar nav { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.topbar nav a { color: var(--muted); font-weight: 500; }
.topbar nav a:hover { color: var(--fg); }
.nav-cta {
  color: var(--fg) !important; background: var(--surface-2);
  border: 1px solid var(--cta-border); padding: 8px 14px; border-radius: 10px;
}
.nav-cta:hover { border-color: var(--accent); }
/* Две кнопки заказа в шапке — одной группой (A_Д_290_В): на телефоне
   строка шапки переносится, и порознь они разъезжались по разным рядам
   (снято на 390 px: дубляж справа в первом ряду, нарезка слева во втором). */
.nav-order { display: inline-flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.inline { display: inline; }
.linkbtn { background: none; border: none; color: var(--muted); cursor: pointer; font: inherit; padding: 0; }
.linkbtn:hover { color: var(--fg); }
.lang select {
  background: var(--surface); color: var(--fg); border: 1px solid var(--border);
  border-radius: 10px; padding: 7px 10px; cursor: pointer;
}
/* Баланс минут в шапке: ссылка на пакеты, но не кричит как кнопка */
.balance { white-space: nowrap; }
.balance b { color: var(--accent); }

/* Пометка о неизрасходованном Пробном Заказе — рядом с балансом */
.trial-badge {
  white-space: nowrap; font-size: var(--fs-sm); color: var(--accent);
  border: 1px solid var(--accent); border-radius: 999px; padding: 2px 8px;
}

/* Кто вошёл: в шапке и на первой строке кабинета */
.whoami { color: var(--muted); font-size: var(--fs-md); }
.topbar .whoami { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.theme-toggle {
  background: var(--surface); border: 1px solid var(--border); cursor: pointer;
  border-radius: 10px; padding: 6px 10px; font-size: var(--fs-base); line-height: 1;
  transition: border-color .15s ease, transform .15s ease;
}
.theme-toggle:hover { border-color: var(--accent); transform: translateY(-1px); }

/* --- Контейнер --- */
/* flex: 1 — основной блок забирает всю свободную высоту, чтобы подвал
   оказался внизу окна. width: 100% нужен потому, что body теперь flex-контейнер
   в колонку: без него max-width сработал бы, а вот на узком экране блок
   сжался бы по содержимому. */
.container { max-width: var(--maxw); width: 100%; margin: 0 auto; padding: 32px 24px 56px; flex: 1 0 auto; }

/* Расширение контейнера на больших мониторах. Порог начинается ВЫШЕ 1920,
   чтобы на FHD ничего не поменялось. Расширяются шапка, содержимое и подвал
   разом — они все считают ширину от --maxw, и разъехаться по выравниванию
   им нельзя. Юридических страниц это не касается: у .legal свой предел
   760 px, и растягивать сплошной текст было бы только хуже для чтения. */
@media (min-width: 2000px) { :root { --maxw: 1440px; } }
@media (min-width: 2800px) { :root { --maxw: 1800px; } }

/* ЛОВУШКА, НА КОТОРОЙ МЫ ОБОЖГЛИСЬ ТРИЖДЫ.
   Браузерное правило `[hidden] { display: none }` — обычное правило
   таблицы стилей по умолчанию, и любое наше `display: flex/block/grid`
   его перебивает: элемент помечен скрытым, а продолжает показываться.
   Так на странице висело второе изображение клипа, так же вернулась
   лента сцен, убранная десять раз.
   Правило ниже закрывает класс целиком: `hidden` в разметке и в скрипте
   теперь означает «не показывать», что бы ни было написано рядом.
   `!important` здесь по делу: он должен побеждать именно те объявления,
   которые его и ломали. */
[hidden] { display: none !important; }

/* --- Кнопки --- */
.btn {
  display: inline-block; background: linear-gradient(135deg, var(--fill), var(--fill-2));
  color: var(--btn-fg, #fff); padding: 13px 26px; border: none; border-radius: 12px;
  font-size: var(--fs-base); font-weight: 600; cursor: pointer;
  box-shadow: 0 8px 20px -8px rgba(79, 140, 255, .6);
  transition: transform .15s ease, box-shadow .15s ease, opacity .15s ease;
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 14px 28px -10px rgba(79, 140, 255, .7); color: var(--btn-fg, #fff); }
.btn:active { transform: translateY(0); }
/* КОЛЬЦО ФОКУСА КНОПКАМ — ОТДЕЛЬНЫМ ПРАВИЛОМ (замер A_О_124 обходом Tab).
   Общее `:focus-visible` рисует его тенью, но объявлено ВЫШЕ, а `.btn`
   задаёт свою `box-shadow` — и при равной силе правил побеждает последнее.
   Фокус пропадал ровно на главных кнопках сайта: человек, идущий
   с клавиатуры, не видел, где стоит. Тень кнопки сохраняем — иначе
   она подпрыгивает в момент фокуса. */
.btn:focus-visible {
  outline: none;
  box-shadow: var(--ring), 0 8px 20px -8px rgba(65, 115, 209, .6);
}

/* --- Hero --- */
.hero { text-align: center; padding: 76px 0 60px; }
.hero h1 {
  font-size: clamp(2.1rem, 4.6vw, 3rem); margin: 0 0 20px; font-weight: 800;
  letter-spacing: -.035em; line-height: 1.08; color: var(--fg-strong);
}
.hero p {
  color: var(--muted); font-size: var(--fs-lg); line-height: 1.6;
  max-width: 560px; margin: 0 auto 32px;
}
.hero .hint { margin-top: 16px; }

/* --- Секции --- */
.section { padding: 60px 0; border-top: 1px solid var(--border); }
.section h2 {
  text-align: center; font-size: clamp(1.55rem, 3vw, 2.1rem); font-weight: 800;
  letter-spacing: -.03em; margin: 0 0 40px;
}

/* Шаги */
.steps {
  list-style: none; padding: 0; margin: 0; display: grid; gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.step {
  background: linear-gradient(180deg, var(--surface), var(--bg-soft));
  border: 1px solid var(--border); border-radius: var(--radius); padding: 32px 28px;
  text-align: center; box-shadow: var(--card-shadow);
  transition: transform .18s ease, border-color .18s ease;
}
.step:hover { transform: translateY(-4px); border-color: var(--border-hover); }
.step h3 { margin: 18px 0 8px; font-size: var(--fs-lg); }
.step p { margin: 0; color: var(--muted); }

/* Услуги (витрина на главной) */
.services { display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.service-card {
  position: relative; display: flex; flex-direction: column;
  background: linear-gradient(180deg, var(--surface), var(--bg-soft));
  border: 1px solid var(--border); border-radius: var(--radius); padding: 28px;
  box-shadow: var(--card-shadow); color: inherit;
  transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
a.service-card:hover {
  transform: translateY(-4px); border-color: var(--accent); text-decoration: none;
  box-shadow: 0 18px 42px -16px rgba(79, 140, 255, .4);
}
.service-icon { font-size: 2rem; line-height: 1; margin-bottom: 14px; }
.service-card h3 { margin: 0 0 8px; font-size: var(--fs-lg); }
.service-card p { margin: 0 0 16px; color: var(--muted); }
.service-link { margin-top: auto; color: var(--accent); font-weight: 600; }
.service-card.is-soon { opacity: .72; }
.soon-badge {
  position: absolute; top: 16px; right: 16px;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--border);
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 3px 10px; border-radius: 20px;
}

/* Преимущества */

/* Цены */
/* ШЕСТЬ пакетов (линейка от 12.08), поэтому 230px заменены на 300px:
   при 1080px ширины это ровно три колонки, то есть 3 + 3, а не 4 + 2.
   Ряд из двух карточек под рядом из четырёх выглядит недосчитанным,
   и глаз ищет, что случилось с остальными. На узких экранах сетка
   переносит их сама. Число карточек нигде не задано: пакеты приходят
   из базы, и вёрстка не должна знать, сколько их. */
/* MIN(300px, 100%), А НЕ 300px (A_Д_90, замер на 320 px). `minmax`
   с жёстким минимумом не даёт колонке сузиться меньше 300, и на самом
   узком экране (старый iPhone SE, 320 px) карточка вылезала за окно
   на 4 точки — страница получала горизонтальную прокрутку. `min()`
   разрешает колонке стать шириной родителя, когда места меньше. */
.pricing {
  display: grid; gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  max-width: 1080px; margin: 0 auto; align-items: stretch;
}
.price-card {
  position: relative; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 30px 26px; text-align: center;
  display: flex; flex-direction: column; align-items: center;
  box-shadow: var(--card-shadow);
  transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.price-card:hover {
  transform: translateY(-6px); border-color: var(--accent);
  box-shadow: 0 18px 42px -16px rgba(79, 140, 255, .45);
}
.price-card.featured {
  border-color: var(--accent);
  background: linear-gradient(180deg, rgba(79, 140, 255, .10), var(--surface));
  box-shadow: 0 0 0 1px rgba(79, 140, 255, .25), var(--shadow);
}
/* Название пакета набирается ТЕМ ЖЕ кеглем, что и цена (владелец, 13.08):
   имя и цена — две главные вещи карточки, и мелкое имя над крупной ценой
   читалось как подпись к числу. Размер задан ОДНИМ правилом на оба:
   разъехаться они могут только вместе. */
.price-card h3,
.price-card .price { font-size: 2rem; font-weight: 800; }
.price-card h3 { margin: 0 0 12px; }
.price-card .price { margin: 0 0 6px; }
.price-card .price span { font-size: var(--fs-base); font-weight: 400; color: var(--muted); }
.price-card .price-rub { margin: 0 0 12px; color: var(--muted); font-size: var(--fs-md); }
.price-card .muted { min-height: 44px; width: 100%; }
.price-card .pack-minutes { margin: 0 0 4px; font-weight: 600; }
/* Цена за клип — главный аргумент против фриланса, поэтому выделена акцентом */
.price-card .per-clip {
  margin: 0 0 16px; color: var(--accent); font-size: var(--fs-md); font-weight: 600;
}
.price-card .btn { margin-top: auto; }
.pricing-note { text-align: center; max-width: 640px; margin: 22px auto 0; }

/* Таблица выхода клипов */
.badge {
  position: absolute; top: -12px; left: 50%; transform: translateX(-50%);
  background: linear-gradient(135deg, var(--fill), var(--fill-2)); color: #fff;
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 4px 14px; border-radius: 20px;
}

/* CTA */
.cta {
  text-align: center; margin-top: 56px; padding: 56px 24px;
  border: 1px solid var(--border); border-radius: 20px;
  background:
    radial-gradient(600px 200px at 50% 0%, rgba(124, 92, 255, .18), transparent 70%),
    var(--surface);
}
.cta h2 { font-size: clamp(1.5rem, 3vw, 2rem); margin: 0 0 12px; }
.cta p { color: var(--muted); margin: 0 0 26px; }

/* --- Карточки форм / кабинет --- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 26px; max-width: 440px; box-shadow: var(--shadow);
}
.card p { margin: 0 0 14px; }
/* Широкая карточка для страниц-форм: регистрация, оформление покупки, оплата.
   Обычные 440 px там читаются как узкая полоса у левого края — на широком
   экране страница выглядит недоделанной. Кабинет и заказы уже во всю ширину,
   и формы должны быть с ними одного вида. */
.card-wide { max-width: 720px; margin-inline: auto; }
/* В КАБИНЕТЕ КАРТОЧКА-ФОРМА ИДЁТ ВО ВСЮ ШИРИНУ КОЛОНКИ (владелец 27.08:
   «чтобы как на оплате не было зазора между левым меню и основным
   текстом»). Ограничение в 720 px верно для страниц БЕЗ сайдбара —
   регистрации и входа, где карточка стоит одна посреди пустой
   страницы, — но рядом с меню оно оставляло слева пустую полосу.
   Поэтому сужаем правило по месту, а не отменяем его всем. */
.cab-content .card-wide { max-width: none; margin-inline: 0; }
/* Заголовок и вводный абзац страницы-формы. Карточка под ними центрирована,
   и заголовок у левого края смотрелся как чужой: глаз ищет одну ось. */
.page-center { text-align: center; }
.page-center + p, p.page-center { max-width: 720px; margin-left: auto; margin-right: auto; }
/* Сводка покупки: название пакета слева, цена справа, обе крупно и в одной
   строке. Раньше цена уезжала на строку ниже отдельным абзацем и выглядела
   оторванной от того, за что она. На узком экране всё равно переносится. */
.buy-summary {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 8px 20px; margin: 0 0 6px;
}
.buy-summary .buy-name { font-size: var(--fs-lg); font-weight: 600; margin: 0; }
.buy-summary .buy-name span { color: var(--muted); font-weight: 400; }
.buy-summary .buy-price { font-size: 1.9rem; font-weight: 800; margin: 0; white-space: nowrap; }
.card input, .card select {
  width: 100%; padding: 11px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg-soft); color: var(--fg);
}
.card input:focus, .card select:focus { border-color: var(--accent); }
/* Длинная ссылка на видео не имеет пробелов и не переносится сама — без этого
   правила URL вылезает за пределы карточки на странице подтверждения заказа. */
.break { overflow-wrap: anywhere; word-break: break-word; }

/* Сводка заказа: подпись и значение в столбик, значение не растягивает карточку */
.order-summary { margin: 0 0 18px; }
/* Названия полей были еле видны серым: это подписи к тому, что клиент
   пришёл проверить перед списанием минут, — они должны читаться. */
.order-summary dt {
  color: var(--fg-strong); font-size: var(--fs-sm); margin-top: 14px;
  font-weight: 600; letter-spacing: .02em; text-transform: uppercase;
}
.order-summary dd { margin: 2px 0 0; min-width: 0; }

/* Было .85rem — подсказки читались с трудом, а в них важное: по телефону
   выставляется удалённый счёт Kaspi, и ошибка в номере стоит клиенту оплаты. */
.helptext { display: block; margin-top: 5px; color: var(--muted); font-size: var(--fs-md); line-height: 1.45; }
.errorlist { color: var(--error-fg); padding-left: 18px; margin: 6px 0; }

/* --- Согласие на обработку ПДн --- */
.consent { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; margin: 0 0 14px; }
/* переопределяем .card input { width: 100% } — иначе чекбокс растянется на всю строку */
.consent input[type="checkbox"] { width: auto; flex: none; margin-top: 3px; accent-color: var(--accent); }
.consent label { flex: 1; font-size: var(--fs-md); color: var(--muted); line-height: 1.45; }
.consent .errorlist { flex-basis: 100%; margin: 0; }
.form-legal { color: var(--muted); font-size: var(--fs-sm); margin: 14px 0 0; }

/* --- Таблицы / кабинет --- */
table.orders { width: 100%; border-collapse: collapse; }
table.orders th, table.orders td { text-align: left; padding: 12px; border-bottom: 1px solid var(--border); }
table.orders th { color: var(--muted); font-weight: 600; font-size: var(--fs-md); }
.url { word-break: break-all; color: var(--muted); }
/* В списке заказов адрес видео — ссылка на заказ, поэтому выглядит как ссылка */
table.orders a.url { color: var(--accent); text-decoration: none; }
table.orders a.url:hover { text-decoration: underline; }
table.orders tbody tr:hover { background: var(--bg-soft); }
/* ФРЕЙМ ИСТОРИИ ПЛАТЕЖЕЙ (образец владельца 27.08): таблица и пояснения
   под ней — в одной карточке, шапка таблицы на подложке, последняя
   строка без разделителя. Отдельным классом, а не правкой `table.orders`:
   ту же таблицу рисуют список заказов и админские страницы, и панель
   там не нужна. */
.table-panel {
  border: 1px solid var(--border); border-radius: 14px;
  background: var(--surface); overflow: hidden;
}
.table-panel .hint { margin: 0; padding: 0 16px; }
.table-panel .hint:first-of-type { padding-top: 14px; }
.table-panel .hint:last-child { padding-bottom: 16px; }
.table-panel table.orders thead th { background: var(--bg-soft); border-bottom: 1px solid var(--border); }
.table-panel table.orders tbody tr:last-child td { border-bottom: 0; }
.table-panel table.orders th,
.table-panel table.orders td { padding: 12px 16px; }
/* Числа — по правому краю: так столбец читается в один взгляд,
   а разряды выстраиваются друг под другом. */
.cell-num { text-align: right !important; white-space: nowrap; }
.delta-plus { color: var(--ok-fg, #1a7f3c); }
.delta-minus { color: var(--error-fg); }
.muted { color: var(--muted); }
.hint { color: var(--muted); font-size: var(--fs-md); }
/* ПРЕДУПРЕЖДЕНИЕ РЕДАКТОРА ОФОРМЛЕНИЯ (A_О_347_В): строка не помещается
   в блок субтитров. Не ошибка — клиент вправе так оставить, — поэтому
   цвет предупреждения, а не отказа, и та же полоса слева, что у других
   предупреждений страницы. */
/* ПОЛЗУНОК ПРИГЛУШЕНИЯ (A_О_348_В): подпись рядом со шкалой, а не под
   ней — глаз читает их вместе, как одно число. */
.field-slider .duck-slider { width: 100%; max-width: 320px; vertical-align: middle; }
.field-slider .duck-value { margin-left: 10px; color: var(--muted); white-space: nowrap; }


/* ===== Настройки обработки и монтажа (A_О_353_В, макет владельца) =====
   Два вопроса о ЗАПИСИ — быстрая смена планов и чистка звука — стоят
   карточками с тумблером справа: заголовок, объяснение, а решение
   одним движением. Прежде это были две галки в ряд, и человек читал
   их как мелкий список настроек, не как выбор.

   БЕЗ СКРИПТА ВСЁ РАБОТАЕТ: тумблер — это обычный `checkbox`, которому
   мы поменяли вид; радио-карточки — обычные `radio` в подписях. Скрипт
   только гасит вложенную галку музыки, как и раньше. */
.setup-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 20px; margin: 0 0 16px;
  box-shadow: var(--card-shadow);
}
.setup-card-head { display: flex; align-items: flex-start; gap: 16px; }
.setup-icon {
  flex: none; width: 44px; height: 44px; border-radius: 12px;
  display: grid; place-items: center; margin-top: 2px;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--accent); font-size: 20px; line-height: 1;
}
.setup-body { flex: 1 1 auto; min-width: 0; }
.setup-title {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: var(--fs-lg); font-weight: 600; color: var(--text); margin: 0;
}
.setup-title label { cursor: pointer; }
.setup-sub { margin: 4px 0 0; color: var(--muted); font-size: var(--fs-md); }
.setup-badge {
  display: inline-flex; align-items: center; padding: 2px 10px;
  border-radius: 999px; font-size: var(--fs-sm); font-weight: 500;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
}
/* Мини-схема «что произойдёт с картинкой»: слова, а не картинка, —
   рисунок пришлось бы держать в двух местах и переводить отдельно. */
.setup-flow {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 12px; font-size: var(--fs-sm); color: var(--muted);
}
.setup-flow-pill {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; padding: 6px 12px; color: var(--text);
}
.setup-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.setup-dot-on { background: var(--accent); }

/* ТУМБЛЕР. Своим классом, а не всем галкам подряд: остальные галки
   формы остаются обычными, и правка этого блока их не тронет. */
/* СЕЛЕКТОР С ТИПОМ ОБЯЗАТЕЛЕН: выше по файлу действует
   `.panel input { width: 100% }` (0,1,1), объявленное НИЖЕ по файлу.
   Тумблер растягивался на всю строку, а текст рядом сжимался в колонку
   по одному слову. Поймано СНИМКОМ, а не чтением кода: в исходнике
   правило выглядело верным, и только браузер показал, кто побеждает
   (06.09; ровно как у `.voice-item input[type="radio"]` до этого).
   Отсюда и вес (0,2,1): он выигрывает независимо от порядка. */
.setup-card input.switch-input {
  appearance: none; -webkit-appearance: none; flex: none;
  width: 46px; min-width: 46px; height: 26px; border-radius: 999px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border);
  position: relative; transition: background .15s, border-color .15s;
}
.setup-card input.switch-input::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--muted); transition: transform .15s, background .15s;
}
.setup-card input.switch-input:checked { background: var(--accent); border-color: var(--accent); }
.setup-card input.switch-input:checked::after { transform: translateX(20px); background: #fff; }
.setup-card input.switch-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.setup-card input.switch-input:disabled { opacity: .5; cursor: not-allowed; }

/* Выбор уровня: две карточки-радио. На узком экране — одна под другой. */
.setup-levels-title {
  margin: 20px 0 10px; font-size: var(--fs-sm); font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
}
.level-cards { display: grid; gap: 14px; grid-template-columns: 1fr 1fr; }
/* Узкий экран разбирается В ОБЩЕМ медиазапросе ниже по файлу, а не своим
   вторым с тем же условием: два блока «max-width: 720px» в одном файле
   разводят узкоэкранные правила по разным местам, и первый же читатель
   (в том числе сторож `test_the_phone_sees_the_decision_first`) находит
   не тот. Поймано красным 06.09. */
/* КАРТОЧКА — ЭТО `label` ВНУТРИ `.panel`, а там живёт
   `.panel label { display: block }` весом (0,1,1): одиночный `.level-card`
   (0,1,0) ему проигрывает и раскладка молча становится не той.
   Поэтому селектор с типом — как у соседей по файлу, и сторож
   специфичности требует ровно этого. */
label.level-card {
  display: block; position: relative; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 12px; padding: 16px;
}
label.level-card:hover { border-color: var(--border-strong, var(--accent)); }
.level-card-head { display: flex; align-items: center; gap: 10px; }
.level-card-name { font-weight: 600; color: var(--text); }
.level-card-tag {
  font-size: var(--fs-sm); font-weight: 600; letter-spacing: .04em;
  color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: 6px; padding: 1px 6px;
}
/* `display: block` у обоих обязателен: это `span`-ы внутри `label`,
   и без него подпись и объяснение слипались в одну строку
   («Базовый фильтр Ровный гул и шипение…»). Поймано снимком 06.09. */
.level-card-head { display: flex; align-items: center; gap: 10px; }
.level-card-sub { display: block; margin: 2px 0 0; font-size: var(--fs-sm); color: var(--muted); }
.level-card-text { display: block; margin: 12px 0 0; font-size: var(--fs-md); color: var(--text); }
.level-card-foot {
  display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  margin-top: 14px; font-size: var(--fs-sm); color: var(--muted);
}
.level-card-foot b { color: var(--ok, #3fb27f); font-weight: 600; }
.level-card input[type="radio"] {
  position: absolute; top: 16px; right: 16px; margin: 0;
  width: auto; flex: none; accent-color: var(--accent);
}
label.level-card:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface-2));
}
/* ПОКА ЧИСТКА ВЫКЛЮЧЕНА, УРОВЕНЬ НИЧЕГО НЕ ОБЕЩАЕТ (A_О_380_В, слово
   владельца 10.09: «пока не включена галка убрать шум, не должен быть
   выделен пункт Убрать шум — иначе кажется, что будет сделано
   шумоподавление»).

   ПОЧЕМУ ВЫДЕЛЕНИЕ ВРАЛО. `denoise_level` — радио с умолчанием `basic`,
   и одно из двух в нём отмечено ВСЕГДА, даже когда чистка не заказана.
   Отмеченная карточка читается как «это будет сделано», а без галки
   уровень в обработку не едет вовсе.

   ПОЧЕМУ CSS, А НЕ СКРИПТ. Уровень остаётся ВИДЕН и нажимаем — так
   решено 06.09, чтобы форма работала без скрипта. Здесь снимается
   только ОБЕЩАНИЕ: рамка и заливка выбранной карточки возвращаются
   к обычным, а блок гаснет. Скрипт для этого не нужен: `:has` уже
   держит само выделение строкой выше, и обе половины правила живут
   рядом — разъехаться им нечем.

   ОДИН БЛОК НА ДВА ЭКРАНА: разметка общая у нарезки и дубляжа
   (`templates/orders/_nastroyki_zvuka.html`), поэтому правило одно
   и лечит оба. */
.setup-card:has([name="denoise"]:not(:checked)) .setup-levels-title,
.setup-card:has([name="denoise"]:not(:checked)) .level-cards { opacity: .55; }
.setup-card:has([name="denoise"]:not(:checked)) label.level-card:has(input:checked) {
  border-color: var(--border);
  background: var(--surface-2);
}

/* Честная оговорка: она отговаривает от переплаты, и потому заметна,
   но не тревожна — это подсказка, а не ошибка. */
.honest-note {
  display: flex; gap: 10px; margin-top: 16px; padding: 12px 14px;
  border-radius: 10px; background: var(--surface-2);
  border-left: 3px solid #d8a13a; font-size: var(--fs-md);
}
.honest-note b { color: #d8a13a; }
.setup-nested {
  margin-top: 16px; padding: 12px 14px; border-radius: 10px;
  background: var(--surface-2); border: 1px solid var(--border);
}
.setup-nested.is-off { opacity: .55; }
.setup-nested label { display: flex; align-items: center; gap: 10px; font-weight: 600; }
.setup-nested input[type="checkbox"] { width: auto; flex: none; margin: 0; }
.setup-nested .helptext { display: block; margin-top: 6px; }

.hint-warn {
  color: var(--text); border-left: 3px solid #d8a13a;
  padding: 6px 0 6px 10px; margin: 0 0 8px;
}
/* Подзаголовок страницы: объясняет, что человек видит. Крупнее подсказки
   (слово владельца 27.08 о «Стиле клипов»), но приглушённее заголовка. */
.page-lead { color: var(--muted); font-size: var(--fs-lg); line-height: 1.5; max-width: 62ch; }
.page-lead.page-center { margin-left: auto; margin-right: auto; }
/* Подсказка, которую надо прочитать, а не пролистать: правила рамки
   кадрирования иначе узнаются методом тыка. Тот же размер, но контраст
   и вес обычного текста. */
.hint-strong { color: var(--text); font-weight: 500; }
/* Кнопка действия и подсказка к ней. Класс стоял в разметке страницы
   заказа с самого начала, а правила за ним не было вовсе: подсказка текла
   за кнопкой обычным текстом, и её хвост уезжал под кнопку к левому краю —
   у заказа с частичной выдачей это выглядело сломанной вёрсткой. Подсказка
   тянется своей колонкой и переносится целиком, а не обтекает кнопку. */
.inline-form { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 12px 0; }
.inline-form .hint { flex: 1 1 260px; margin: 0; }
.msg { padding: 12px 16px; border-radius: 10px; margin-bottom: 16px; border: 1px solid var(--border); background: var(--surface); }
.msg.error { background: var(--error-bg); color: var(--error-fg); border-color: var(--error-border); }
/* Заголовок страницы и кнопка действия в одной строке. На узком экране
   кнопка переносится под заголовок и растягивается на всю ширину. */
.page-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 8px;
  /* ОДНА ВЫСОТА ШАПКИ РАЗДЕЛА (слово владельца 29.08, найдено снимками:
     «Мои заказы» ниже, чем «Настройки», и верхушки блоков не совпадают).

     Причина: справа от заголовка иногда стоит кнопка («Новая нарезка»
     в «Моих заказах»), и она выше строки заголовка. Полоса растягивалась
     под кнопку, заголовок съезжал вниз на её половину, а всё содержимое
     раздела — вместе с ним. Разделы без кнопки начинались выше.

     Держим минимум по высоте кнопки: тогда полоса одинакова с кнопкой
     и без неё, заголовки стоят на одной линии во всех разделах, и первые
     карточки справа начинаются на одном уровне. Число — общее (--palec),
     им же меряются все нажимаемые цели. */
  min-height: var(--palec);
}
/* Кнопка в шапке раздела — РОВНО В ПАЛЕЦ, как и сама полоса. Иначе она
   выше полосы (у неё свои поля), полоса тянется под неё, и «Мои заказы»
   с кнопкой стоят ниже «Настроек» без кнопки — ровно то, что владелец
   увидел глазами. Поля по бокам остаются, вертикальные снимаем:
   высоту держит `min-height`, текст центрируется сам. */
.page-head .btn,
.page-head .btn-small,
.page-head-actions .btn,
.page-head-actions .btn-small {
  min-height: var(--palec);
  padding-top: 0; padding-bottom: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.page-head h1 { margin: 0; }
.page-head-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
/* ДВЕ КНОПКИ ЗАКАЗА ДЕРЖАТСЯ ВМЕСТЕ (A_Д_280, слово владельца 02.09).
   Рядом с ними в шапке живут счётчик выбора и кнопка удаления, и общий
   `flex-wrap` разводил «Дубляж видео» и «Новую нарезку» по разным
   строкам: на 390 px одна вставала на 483, другая на 533. Свой ряд
   переносится целиком, а внутри не рвётся. */
.head-order-actions {
  display: inline-flex; align-items: center; gap: 12px; flex-wrap: nowrap;
}
.selection-count { color: var(--muted); font-size: var(--fs-md); }

/* Кнопка опасного действия. Цвета берём из тех же переменных, что и сообщения
   об ошибках, — иначе в светлой теме она осталась бы тёмной */
.btn-danger {
  padding: 8px 16px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--error-border); background: var(--error-bg);
  color: var(--error-fg); font: inherit; font-size: var(--fs-md);
}
.btn-danger:hover { filter: brightness(1.08); }

/* Окно подтверждения. <dialog> вместо confirm(): нужен свой текст и две кнопки */
.confirm-dialog {
  max-width: 420px; padding: 24px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--surface); color: var(--fg);
}
.confirm-dialog::backdrop { background: rgba(0, 0, 0, .55); }
.confirm-dialog h2 { margin: 0 0 12px; font-size: var(--fs-lg); }
.confirm-dialog p { margin: 0 0 16px; }
.confirm-actions { display: flex; gap: 10px; justify-content: flex-end; margin: 0; }

@media (max-width: 480px) {
  .page-head .btn { width: 100%; text-align: center; }
}

/* --- Список заказов плитками ---
   Карточка ровно на ширину обложки плюс внутренние отступы: 192 + 12·2 = 216. */
.order-cards {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: 16px;
  /* ШИРИНА КАРТОЧКИ = ОБЛОЖКА + ДВА ОТСТУПА, ОДНОЙ ПЕРЕМЕННОЙ (A_Д_298_В,
     владелец 03.09: «превью смещено к правой границе карточки»). Число
     216 было вписано отдельно от отступа, а общее правило карточек
     витрины ниже по файлу давало отступ 22 px — обложка в 192 px
     переставала помещаться и вылезала за правый край. */
  --card-pad: 12px;
  grid-template-columns: repeat(auto-fill, calc(192px + 2 * var(--card-pad)));
  justify-content: start;
}
.order-card {
  position: relative;  /* якорь для галочки выбора */
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
}
/* Галочка поверх обложки. Своя, а не системная: системная рисуется толстой
   и непрозрачной и перетягивает внимание с кадра. Здесь тонкая рамка
   и полупрозрачная заливка — читается и на светлом кадре, и на тёмном. */
/* Правый верхний угол превью. Отступы 18px — это padding карточки (12) плюс
   6 от края обложки. Плашке длительности внизу справа не мешает. */
/* ГАЛКА — ТОЧНО КАК У ВЫБОРА КЛИПОВ (`.clip-select`, владелец 03.09: «посмотри
   как у клипов, и они не прозрачные»): те же 16 px от угла обложки (галка
   лежит ВНУТРИ обложки, как у клипа — внутри плитки), та же подложка,
   тот же системный чекбокс без своего размера и цвета. Правила совпадают
   с `.clip-select` строка в строку — расхождение одного из двух мест
   владелец видит глазами. */
.order-select {
  position: absolute; top: 16px; right: 16px; z-index: 2;
  display: flex; padding: 4px; border-radius: 6px;
  background: rgba(0, 0, 0, .55);
}
.order-select input { margin: 0; cursor: pointer; }
.order-select input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.order-card:hover { border-color: var(--accent); }
.order-card {
  display: flex; flex-direction: column; gap: 10px; padding: 12px;
}
/* Ссылка обёрнута вокруг заголовка, но кликабельна вся карточка: псевдоэлемент
   растянут по ней. Так галочка и кнопка правки могут лежать поверх ссылки,
   не будучи внутри неё (кнопка внутри ссылки — невалидная разметка). */
.order-card-link { text-decoration: none; color: inherit; }
.order-card-link::after { content: ""; position: absolute; inset: 0; border-radius: 12px; }
/* Всё интерактивное поверх растянутой ссылки. ГАЛКИ ВЫБОРА ЗДЕСЬ НЕТ
   (A_Д_297_В, владелец 03.09: «не видно чекбокса», «превью криво встали»):
   она позиционирована абсолютно в углу обложки выше по файлу, а это
   правило перебивало её `position: relative` — галка выпадала в поток
   карточки невидимой белой полосой в 18 px и сдвигала обложку вниз.
   Абсолютное положение с z-index уже держит её поверх ссылки. */
.order-edit,
.order-download { position: relative; z-index: 2; }
/* Обложка 16:9 фиксированной ширины: плитки должны быть одинаковыми
   независимо от того, скачалась обложка или нет. Файл при этом прежний —
   растягиваем только показ. */
.order-thumb {
  position: relative; width: 192px; aspect-ratio: 16 / 9;
  border-radius: 8px; overflow: hidden; background: var(--bg-soft); display: block;
}
.order-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.order-thumb-empty {
  display: flex; width: 100%; height: 100%;
  align-items: center; justify-content: center; padding: 8px;
}
/* Подпись вместо пустого места (A_О_367): человек должен понимать,
   ждать кадр или нет. Мелкая и приглушённая — это объяснение, а не
   содержание карточки. */
.order-thumb-note {
  font-size: var(--fs-sm); line-height: 1.3; text-align: center;
  color: var(--muted);
}
.order-duration {
  position: absolute; right: 6px; bottom: 6px;
  padding: 2px 6px; border-radius: 4px;
  background: rgba(0, 0, 0, .75); color: #fff; font-size: var(--fs-sm); line-height: 1.4;
}
/* Всё под обложкой в столбик. align-items: flex-start, иначе пилюля статуса
   растянулась бы на всю ширину карточки. */
.order-card-body {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  min-width: 0; width: 100%;
}
/* Название переносится по словам. Ограничение в три строки — чтобы длинный
   заголовок с YouTube не растягивал весь ряд плиток. */
.order-title {
  font-weight: 600; line-height: 1.35; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Кнопка скачивания внизу плитки: во всю её ширину, с отбивкой от карточки,
   чтобы визуально не сливаться с текстом заказа */
.order-download {
  display: block; margin: 0; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: 8px;
  text-align: center; text-decoration: none; color: inherit;
  font-size: var(--fs-md); background: var(--bg-soft);
}
.order-download:hover { border-color: var(--accent); }
/* Та же кнопка на странице заказа: там ширина контейнера до 1800 px,
   и растянутая во всю строку кнопка выглядела бы нелепо */

/* Статус слева, число клипов прижато к правому краю. width: 100% обязателен:
   родитель выровнен по flex-start, и без него строка сжалась бы по содержимому,
   а «Клипов» встало бы вплотную к статусу. Правый край при этом совпадает
   с краем обложки и кнопки — у всех троих отступ от карточки 12 px. */
.order-meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; width: 100%;
  /* СТРОКА ПЕРЕНОСИТСЯ, А НЕ ВЫЛЕЗАЕТ ЗА КРАЙ КАРТОЧКИ (владелец, 27.08:
     «надпись „не скачано“ уезжает за границу карточки заказа»). В сетке
     готовых заказов карточка узкая — около 200 px, — и три приписки
     подряд (статус, число клипов, «не скачано») в неё не помещались.
     Без переноса flex не сжимает `nowrap`-приписки и выносит последнюю
     наружу: текст оказывается поверх соседней карточки и обрезается. */
  flex-wrap: wrap;
}
.order-clips { color: var(--muted); font-size: var(--fs-md); }
/* Перевод в карточке списка (решение владельца 28.08). Тем же приглушённым
   цветом и размером, что «Клипов: 40»: это приписка к статусу, а не второй
   заголовок — карточка и так живёт под правилом «один шрифт» (27.08). */
/* ВИД ЗАКАЗА В СПИСКЕ (A_О_307): у дубляжа колонка клипов пуста
   по устройству, и пустое место человек читает как «ничего
   не собралось». Оформление то же, что у строки перевода, — это
   приписки одного рода, и разный вид сказал бы, что одна важнее. */
.order-kind { color: var(--muted); font-size: var(--fs-md); }
.order-translation { color: var(--muted); font-size: var(--fs-md); }
/* Приписки справа — столбиком, по образцу владельца (27.08): «Клипов: 40»
   и под ним «не скачано». Выравнивание по левому краю блока, а не по
   правому: два разной длины текста, прижатых вправо, читаются как
   лесенка. */
.order-meta-right {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 2px; min-width: 0;
}
/* ЕДИНЫЙ ШРИФТ КАРТОЧКИ (слово владельца 27.08: «все шрифты разные,
   не красиво»). Было четыре размера в одной карточке: заголовок 1rem,
   число клипов и дата .9, статус .82, а срок хранения не задан вовсе
   и потому шёл базовым — крупнее числа клипов над ним. Теперь ДВА
   размера: заголовок и всё остальное. Метка длительности на обложке
   остаётся мелкой намеренно — она лежит НА кадре, а не в тексте. */
.order-card-body .status,
.order-card-body .order-rebuilding,
.order-untaken,
.order-expiry,
.order-download { font-size: var(--fs-md); }
/* Срок хранения — того же приглушённого цвета, что дата над ним:
   это справочная строка, а не предупреждение. */
.order-expiry { color: var(--muted); }
/* Приписки не рвём внутри слова: «не ска-» на одной строке и «чано»
   на другой хуже переноса целиком. */
.order-clips, .order-untaken, .order-rebuilding { white-space: nowrap; }
/* «клип пересобирается» — тем же цветом, что «в обработке» у статуса:
   это ИДУЩАЯ РАБОТА, и читаться она должна как работа, а не как
   ошибка и не как обычная приписка вроде числа клипов. */
.order-rebuilding {
  padding: 2px 8px; border-radius: 999px; font-size: var(--fs-sm);
  background: #eef2ff; color: #3949ab;
}
/* Статус подсвечен цветом, чтобы в длинном списке взгляд цеплялся за неудачи */
.status { padding: 2px 8px; border-radius: 999px; font-size: var(--fs-sm); background: var(--bg-soft); }
.status-done { background: var(--ok-bg, #e7f6ec); color: var(--ok-fg, #1a7f3c); }
.status-processing { background: #eef2ff; color: #3949ab; }
.status-failed,
.status-rejected { background: var(--error-bg); color: var(--error-fg); }
.order-date { color: var(--muted); font-size: var(--fs-md); }
/* Карандаш правки справа от названия. align-items: flex-end прижимает его
   к последней строке — у названия их может быть до трёх. */
.order-title-row { display: flex; align-items: flex-end; gap: 6px; }
.order-title-row .order-title { flex: 1 1 auto; min-width: 0; }
.order-title-row .order-edit { flex: 0 0 auto; }
.order-edit {
  display: inline-flex; padding: 3px; border: 0; border-radius: 5px;
  background: none; color: var(--muted); cursor: pointer; line-height: 0;
}
.order-edit:hover { background: var(--bg-soft); color: var(--fg); }
.rename-input {
  width: 100%; padding: 9px 12px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg-soft);
  color: inherit; font: inherit; font-size: var(--fs-md);
}

/* На узком экране плитка занимает всю ширину, обложка тянется за ней */
@media (max-width: 420px) {
  .order-cards { grid-template-columns: 1fr; }
  .order-thumb { width: 100%; }
}

.rename { margin: 12px 0 20px; }
.rename summary { cursor: pointer; color: var(--muted); font-size: var(--fs-md); }

/* Клипы плитками — так же, как заказы в кабинете: видео сверху, под ним
   название, длительность и действия. Ширина плитки на обложку плюс отступы. */
.clips {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, 216px);
  justify-content: start;
}
.clips li.clip {
  display: flex; flex-direction: column; gap: 10px; padding: 12px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
  position: relative;
}
.clips li.clip:hover { border-color: var(--accent); }
/* Плитка, не попавшая под поиск. Правило обязательно: у .clip задан
   display: flex, и он сильнее браузерного [hidden] { display: none } —
   без этой строки скрытые плитки остались бы на экране. */
.clips li.clip[hidden] { display: none; }

/* Поиск по заголовку среди клипов. Показывается скриптом и только на
   больших заказах: на пяти плитках он ничего не ускоряет. */
.clips-search {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 14px;
}
.clips-search label { color: var(--muted); font-size: var(--fs-md); }
.clips-search input[type="search"] {
  padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface);
  color: inherit; font: inherit; font-size: var(--fs-md); min-width: 220px;
}
.clips-found { color: var(--muted); font-size: var(--fs-md); }

/* Галочка выбора клипа — поверх угла превью, а не строкой над ним: в потоке
   она отодвигала картинку вниз и в сетке из восьмидесяти плиток бросалась
   в глаза сильнее самих клипов. Угол правый верхний: слева вверху стоит
   оценка потенциала, справа внизу — длительность.
   Подложка нужна, потому что кадр под галочкой любой: на светлом рамка
   чекбокса пропадает. */
.clip-select {
  position: absolute; top: 16px; right: 16px; z-index: 1;
  display: flex; padding: 4px; border-radius: 6px;
  background: rgba(0, 0, 0, .55);
}
.clip-select input[type="checkbox"] { margin: 0; cursor: pointer; }

/* Действия над выбранными клипами. Кнопки появляются скриптом по мере
   выбора, поэтому пустой блок не должен занимать высоту: без содержимого
   отступа снизу тоже нет. */
.clips-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.clips-actions:has(:not([hidden])) { margin: 0 0 14px; }
/* Вертикальное видео 9:16 во всю ширину плитки */
.clip-thumb { position: relative; display: block; width: 192px; }
.clip-player {
  width: 100%; aspect-ratio: 9 / 16;
  background: #000; border-radius: 8px; display: block;
}
/* Превью до клика: картинка вместо проигрывателя.
   Размер тот же, что у .clip-player, — иначе плитка дёрнется в момент
   подмены, а мы это как раз и чиним. */
.clip-preview {
  width: 100%; aspect-ratio: 9 / 16;
  padding: 0; border: 0; border-radius: 8px; background: #000;
  display: block; position: relative; cursor: pointer; overflow: hidden;
}
.clip-preview img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* Треугольник «смотреть». Рисуем рамкой, а не картинкой: лишний файл
   на 372 плитки — это лишний запрос там, где мы их только что убрали. */
.clip-play {
  position: absolute; left: 50%; top: 50%;
  width: 0; height: 0; pointer-events: none;
  transform: translate(-40%, -50%);
  border-style: solid; border-width: 13px 0 13px 22px;
  border-color: transparent transparent transparent rgba(255, 255, 255, .92);
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .6));
}
.clip-preview:hover .clip-play { border-left-color: #fff; }
.clip-preview:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* `display: block` у класса перебивает атрибут `hidden` — он слабее по
   специфичности. Без этой строки скрытый плеер остаётся на месте пустым
   чёрным прямоугольником под картинкой, и плитка выходит вдвое длиннее.
   Ровно это и увидел владелец на стенде: «картинки очень длинные». */
.clip-player[hidden], .clip-preview[hidden] { display: none; }
/* Плитка без проигрывателя: пересборка, потерянный файл, истёкший срок.
   Место превью занимаем всегда — иначе плитка в сетке становится вдвое
   ниже соседних, и ряд разъезжается. Надпись говорит, что именно случилось:
   пустой прямоугольник клиент читает как поломку страницы. */
.clip-thumb-state {
  width: 192px; aspect-ratio: 9 / 16; border-radius: 8px;
  display: flex; align-items: center; justify-content: center; padding: 10px;
  text-align: center; background-size: cover; background-position: center;
}
/* ПРЕВЬЮ КЛИПА — В ФОРМАТЕ ЗАКАЗА (A_Д_169_В).

   Три рамки — проигрыватель, картинка до клика и заглушка «файла нет» —
   стояли жёстко 9:16, и квадратный клип показывался в вертикальной
   рамке: `object-fit: cover` обрезал его молча, а пустые поля рамки
   читались как чёрные полосы в самом клипе.

   Соотношение берётся у списка (`data-aspect`), а не у каждой плитки:
   формат один на весь заказ. Атрибутный селектор (0,2,0) тяжелее
   голого класса (0,1,0) — правило побеждает весом, а не порядком строк.

   ШИРИНА У ГОРИЗОНТАЛИ БОЛЬШЕ: при 192 px горизонтальная плитка выходит
   108 px высотой, и разглядеть на ней субтитры нельзя — а смотрят
   в превью именно на них. Ряд при этом не длиннее: плитки ниже. */
[data-aspect="16_9"] .clip-thumb,
[data-aspect="16_9"] .clip-thumb-state { width: 288px; }
/* И КОЛОНКА ПОД НЕЁ (A_О_478): сетка держала 216 точек, и плитка
   в 288 вылезала из карточки на соседнюю. 288 + поля карточки 2×12
   + рамка 2×1. */
.clips[data-aspect="16_9"] { grid-template-columns: repeat(auto-fill, 314px); }
[data-aspect="16_9"] .clip-player,
[data-aspect="16_9"] .clip-preview,
[data-aspect="16_9"] .clip-thumb-state,
[data-aspect="16_9"] .clip-thumb-pending { aspect-ratio: 16 / 9; }
[data-aspect="1_1"] .clip-player,
[data-aspect="1_1"] .clip-preview,
[data-aspect="1_1"] .clip-thumb-state,
[data-aspect="1_1"] .clip-thumb-pending { aspect-ratio: 1 / 1; }
/* Заглушка «в очереди» — в том же списке (A_О_478): ширину 288 она
   брала у горизонтали, а пропорцию 9:16 — и стояла столбом 288×512
   среди горизонтальных клипов. */

.clip-thumb-label {
  font-size: var(--fs-md); line-height: 1.35; font-weight: 600;
  padding: 6px 10px; border-radius: 8px;
}
/* Пересборка — единственное состояние, где файл ещё есть: под надписью
   показываем прежнюю заставку приглушённой, чтобы клип оставался узнаваем.
   Бегущая полоса внизу отличает «делается прямо сейчас» от «стоит
   в очереди»: без неё оба состояния — одинаковые серые прямоугольники,
   и клиент не понимает, идёт ли дело вообще. Полоса не показывает долю
   сделанного — её мы не знаем, и рисовать её значило бы врать. */
.clip-thumb-state.is-working {
  background-color: #000; position: relative; overflow: hidden;
}
.clip-thumb-state.is-working .clip-thumb-label {
  background: rgba(0, 0, 0, .62); color: #fff;
}
.clip-thumb-state.is-working::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 3px; width: 40%;
  background: var(--accent); border-radius: 3px;
  animation: clip-working 1.6s ease-in-out infinite;
}
@keyframes clip-working {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
/* У кого движение вызывает дурноту, полоса просто стоит на месте:
   состояние читается по надписи, анимация здесь — украшение. */
@media (prefers-reduced-motion: reduce) {
  .clip-thumb-state.is-working::after { animation: none; width: 100%; }
}
/* Потеря — наша вина, и цвет тревожный. Истёкший срок — порядок вещей,
   и кричать о нём нечем: спокойный серый. */
.clip-thumb-state.is-lost {
  background-color: var(--surface-2);
  border: 1px dashed var(--danger, #c0392b);
}
.clip-thumb-state.is-lost .clip-thumb-label { color: var(--danger, #c0392b); }
.clip-thumb-state.is-gone {
  background-color: var(--surface-2); border: 1px dashed var(--border);
}
.clip-thumb-state.is-gone .clip-thumb-label { color: var(--fg-muted); }

/* Плашка длительности поверх превью. Скрипт прячет её на время проигрывания */
.clip-duration {
  position: absolute; right: 6px; bottom: 6px; pointer-events: none;
  padding: 2px 6px; border-radius: 4px;
  background: rgba(0, 0, 0, .75); color: #fff; font-size: var(--fs-sm); line-height: 1.4;
}
/* Ход обработки заказа: список шагов с отметками пройденного */
.progress {
  margin: 16px 0; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: var(--radius);
}
.progress-steps { list-style: none; margin: 0 0 12px; padding: 0; }

/* Три состояния шага должны различаться СРАЗУ, не приглядываясь: клиент
   смотрит на этот список несколько минут и хочет понимать, где он сейчас.
   Различаем тремя признаками сразу — цветом, начертанием и значком слева. */
.progress-step {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0; color: var(--muted); font-size: var(--fs-md);
}

/* Пройденное — приглушённое, но не серое: это сделанная работа, а не мусор */
.progress-step.is-done { color: var(--fg); }
.progress-step.is-done .progress-label { text-decoration: none; }

/* Текущее — самое заметное на странице: крупнее, ярче и жирным */
.progress-step.is-current {
  color: var(--fg-strong); font-weight: 700; font-size: var(--fs-base);
}

/* ЖДУЩЕЕ — НЕ ПРЕДСТОЯЩЕЕ И НЕ ТЕКУЩЕЕ. Заказ до шага дошёл, но пул
   занят: работа не идёт, и пульсирующая точка соврала бы. Показываем
   его как начатый (обычная яркость и полужирный), но со спокойным
   значком и припиской «ждёт очереди».

   Без этого класса ждущий шаг выглядел ровно как будущий — бледным
   и пустым, — и человек читал страницу как «предыдущее сделано,
   дальше ничего не происходит». Владелец так и прочитал на заказе
   #243 (нашла B, 15.08). */
.progress-step.is-waiting { color: var(--fg); font-weight: 600; opacity: 1; }
.progress-step.is-waiting .progress-mark {
  border-color: var(--fg); opacity: .85;
}
.progress-note { color: var(--muted); font-size: var(--fs-sm); white-space: nowrap; }

/* Предстоящее оставляем бледным и обычным начертанием */
.progress-step:not(.is-done):not(.is-current):not(.is-waiting) { opacity: .6; }

/* Значок слева: галочка у сделанного, точка с пульсацией у текущего,
   пустой кружок у будущего. Значок дублирует цвет — на случай, если
   человек различает цвета плохо. */
.progress-mark {
  width: 18px; height: 18px; flex: none; border-radius: 50%;
  border: 2px solid currentColor; position: relative; opacity: .6;
}
.progress-step.is-done .progress-mark {
  border-color: var(--ok-fg); background: var(--ok-fg); opacity: 1;
}
.progress-step.is-done .progress-mark::after {
  content: ""; position: absolute; left: 4px; top: 1px;
  width: 4px; height: 8px; border: solid #fff;
  border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.progress-step.is-current .progress-mark {
  background: var(--accent); border-color: var(--accent); opacity: 1;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 25%, transparent);
  /* Пульсация показывает, что работа идёт, а страница не замерла */
  animation: progress-pulse 1.4s ease-in-out infinite;
}
@keyframes progress-pulse { 50% { box-shadow: 0 0 0 8px transparent; } }
/* Уважаем настройку системы: анимация может мешать и укачивать */
@media (prefers-reduced-motion: reduce) {
  .progress-step.is-current .progress-mark { animation: none; }
}
.progress .hint { margin: 4px 0 0; }

/* Оценка потенциала — в противоположном углу от длительности, чтобы плашки
   не сталкивались. Прятать при проигрывании не нужно: контролы плеера внизу */
.clip-score {
  position: absolute; left: 6px; top: 6px; pointer-events: none;
  padding: 2px 7px; border-radius: 4px;
  background: rgba(79, 140, 255, .92); color: #fff;
  font-size: var(--fs-sm); line-height: 1.4; font-weight: 600;
}
.clip-info { display: flex; flex-direction: column; gap: 6px; width: 100%; min-width: 0; }
/* ТАЙМИНГ КЛИПА В ИСХОДНИКЕ (задача владельца 16.08). Моноширинные
   цифры: подписи стоят столбиком под названиями, и «12:30» рядом
   с «1:05:00» прыгали бы по ширине знаков. Спокойный цвет — это
   справка, а не заголовок: человек находит её глазом, когда ищет,
   и не спотыкается, когда просто смотрит клипы. */
.clip-range {
  margin: 0; font-size: var(--fs-sm); color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Название в две строки: заголовки от подрядчика бывают длинными, а плитки
   в ряду должны оставаться одной высоты */
.clip-title {
  margin: 0; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Кнопки действий в плитке: «Скачать» и «Править» делят верхнюю строку,
   «Во весь экран» идёт под ними во всю ширину. Три кнопки в ряд в 192 px
   не помещаются никак, а уменьшать их дальше значит делать непопадаемыми
   пальцем.

   `flex: 1 1 auto` у верхних, а не фиксированная сетка в две колонки:
   у неудалённого клипа «Править» может отсутствовать (пересборка, истёкший
   срок), и тогда «Скачать» растягивается на строку сам, без пустой ячейки
   рядом. */
.clip-actions { display: flex; gap: 6px; flex-wrap: wrap; row-gap: 6px; margin: 0; }
.clip-actions .btn-small {
  flex: 1 1 auto; text-align: center;
  padding: 6px 9px; font-size: var(--fs-sm); white-space: nowrap;
}
/* Отдельная строка целиком. `flex-basis: 100%` заставляет перенос сам,
   без разрывов вроде `::before` */
.clip-actions .clip-fullscreen { flex: 1 0 100%; }
/* Ссылка-кнопка «Описание и текст»: выглядит ссылкой, но это button —
   иначе без JS она вела бы в никуда */
.clip-textlink {
  align-self: center; padding: 0; border: 0; background: none;
  color: var(--accent); font: inherit; font-size: var(--fs-md); cursor: pointer;
  text-decoration: underline;
}

/* Окно с описанием, хэштегами и расшифровкой. Расшифровка бывает длинной,
   поэтому окно прокручивается внутри себя, а не растёт за пределы экрана. */
.clip-dialog { max-width: 620px; max-height: 80vh; overflow-y: auto; }
.clip-dialog h3 {
  margin: 18px 0 6px; font-size: var(--fs-md); color: var(--muted);
  display: flex; align-items: center; gap: 8px;
}
/* Иконка копирования у заголовка блока. Обе картинки лежат в разметке,
   переключаются классом: подменить <use> средствами CSS нельзя. */
.copy-icon {
  display: inline-flex; padding: 3px; border: 0; border-radius: 5px;
  background: none; color: var(--muted); cursor: pointer; line-height: 0;
}
.copy-icon:hover { background: var(--bg-soft); color: var(--fg); }
.copy-icon .i-done { display: none; }
.copy-icon.copied { color: var(--ok-fg, #1a7f3c); }
.copy-icon.copied .i-copy { display: none; }
.copy-icon.copied .i-done { display: inline; }
.clip-dialog h3:first-of-type { margin-top: 0; }
.clip-tags { color: var(--accent); overflow-wrap: anywhere; }
.clip-transcript-text { white-space: pre-wrap; font-size: var(--fs-md); }
/* Используется и на <a>, и на <button>: font/color сбрасываем, иначе кнопка
   унаследует системный шрифт и будет отличаться от соседней ссылки */
.btn-small {
  display: inline-block; padding: 7px 14px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface);
  font: inherit; font-size: var(--fs-md); color: inherit;
  text-decoration: none; cursor: pointer;
}
.btn-small:hover { background: var(--bg-soft); }

/* Кнопка, которой пока нельзя пользоваться (A_О_301): «скачать всё»
   на неполном заказе. Гашение ВИДНО, но рядом всегда стоят слова,
   чего ждём, — серое пятно без объяснения читается как поломка. */
.btn-small.is-disabled,
.btn-small:disabled { opacity: .45; cursor: default; }
.btn-small.is-disabled:hover,
.btn-small:disabled:hover { background: var(--surface); }

/* На узком экране плитка клипа во всю ширину, видео тянется за ней */
@media (max-width: 420px) {
  .clips, .clips[data-aspect="16_9"] { grid-template-columns: 1fr; }
  .clip-player { width: 100%; }
}

/* --- Подвал --- */
.site-footer {
  border-top: 1px solid var(--border); background: var(--bg-soft);
  margin-top: 40px; flex-shrink: 0;
}
.footer-inner {
  max-width: var(--maxw); margin: 0 auto; padding: 36px 24px 24px;
  display: flex; justify-content: space-between; gap: 28px; flex-wrap: wrap;
}
.footer-brand { max-width: 320px; }
.footer-brand .muted { font-size: var(--fs-md); margin: 10px 0 0; }
.footer-links { display: flex; flex-direction: column; gap: 10px; }
.footer-links a { color: var(--muted); font-size: var(--fs-md); }
.footer-links a:hover { color: var(--fg); }
.footer-bottom {
  max-width: var(--maxw); margin: 0 auto; padding: 16px 24px 30px;
  border-top: 1px solid var(--border);
  display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap;
}
.footer-bottom .muted { font-size: var(--fs-md); }

/* --- Юридические страницы --- */
.legal { max-width: 760px; margin: 0 auto; }
.legal h1 { font-size: clamp(1.6rem, 3vw, 2.1rem); margin: 8px 0 18px; }
.legal h2 { font-size: var(--fs-lg); margin: 30px 0 10px; }
.legal p, .legal li { line-height: 1.7; }
.legal ul { padding-left: 20px; }
.legal li { margin: 6px 0; }
.legal-updated { color: var(--muted); font-size: var(--fs-md); margin: -6px 0 24px; }
.legal-req { border-collapse: collapse; margin: 18px 0; }
.legal-req td { padding: 9px 18px 9px 0; border-bottom: 1px solid var(--border); vertical-align: top; }
.legal-req td:first-child { color: var(--muted); white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; scroll-behavior: auto !important; }
}

/* Счёт на оплату — печатная форма */
.invoice { max-width: 760px; margin: 0 auto; }
.invoice h1 { font-size: 1.35rem; margin-bottom: 20px; }
.invoice-req, .invoice-items {
  width: 100%; border-collapse: collapse; margin-bottom: 20px;
}
.invoice-req th, .invoice-req td,
.invoice-items th, .invoice-items td {
  border: 1px solid var(--border); padding: 8px 12px; text-align: left; vertical-align: top;
}
.invoice-req th { width: 30%; font-weight: 600; color: var(--muted); }
.invoice-items td:last-child, .invoice-items th:last-child { text-align: right; }
.invoice-sign { margin-top: 36px; }

/* Широкая таблица едет внутри себя, а не тащит за собой всю страницу:
   на телефоне иначе уходит вправо последняя колонка, а в счёте это сумма. */
.table-scroll { overflow-x: auto; }

/* На телефоне пять колонок счёта не влезают в ширину из-за отступов.
   Прокрутка контейнера спасает страницу, но итог оказывается за краем,
   а это главное число счёта: ужимаем отступы, чтобы он был виден сразу. */
@media (max-width: 480px) {
  .invoice-items th, .invoice-items td { padding: 6px 4px; }
}

@media print {
  /* На бумаге нужен только сам счёт */
  header, footer, .no-print { display: none !important; }

  /* ФОН ТЕМЫ НА БУМАГЕ НЕ ПЕЧАТАЕМ. Найдено 18.08 печатью счёта
     в PDF: браузер с включённой печатью фона заливал лист цветом
     темы, и клиент нёс в банк сиреневую страницу. Экрана это
     не касается — только листа. */
  html, body, .invoice { background: #fff !important; }

  .invoice { max-width: none; color: #000; }

  /* ССЫЛКА НА ОФЕРТУ ПЕЧАТАЕТСЯ ЧЁРНЫМ. Класс `hint` приглушён
     намеренно — на экране это подсказка. Но на счёте это
     единственная строка, называющая юридическое основание сделки,
     и печаталась она бледно-серым: на бумаге почти не читается. */
  .invoice .hint { color: #000; }
  .invoice-req th, .invoice-req td,
  .invoice-items th, .invoice-items td { border-color: #000; }
  /* На бумаге прокручивать нечем: контейнер не должен обрезать таблицу */
  .table-scroll { overflow: visible; }
  /* ЗНАК НА ПЕЧАТИ РИСУЕТСЯ ЯВНЫМИ ЦВЕТАМИ. Он берёт их у темы
     (`currentColor` и `var(--accent)`), а на бумаге темы нет: ножницы
     печатались бы невидимыми, и в шапке документа остался бы
     полупустой квадрат. Предупреждение D, проверяется печатью,
     а не видом на экране. */
  /* ЗНАК БЕРЁТ ЦВЕТ У ОБЁРТКИ, второго правила ему не нужно (A_Д_223):
     новый знак нарисован одним `currentColor` целиком, поэтому чёрного
     на самой обёртке достаточно. Прежнее `.invoice-mark .mark` осталось
     от старого знака, у которого часть фигур красилась своим цветом. */
  .invoice-mark { color: #000; }

  /* СЧЁТ ОБЯЗАН ЛЕЧЬ НА ОДИН ЛИСТ (A_О_143, замер печатью).
     Мерено в режиме печати при ширине листа A4 с полями (718 px):
     русский счёт занимал 1210 px при 1047 полезных, казахский
     с длинным названием — 1366. То есть ВСЕГДА второй лист, а на нём
     ровно две строки: «Вопросы по счёту» и подпись Исполнителя.
     Клиент несёт в банк документ, у которого подпись на отдельной
     странице, — так теряется и лист, и доверие к бумаге.

     Ужимаем ТОЛЬКО на печати: на экране просторная вёрстка верна,
     и трогать её незачем. Само содержимое не убрано ни строкой —
     сжаты отступы, а они на бумаге и не нужны. */
  .container { padding: 0; }
  .invoice h1 { font-size: 1.15rem; margin-bottom: 10px; }
  .invoice-mark { margin-bottom: 6px; }
  .invoice-req, .invoice-items { margin-bottom: 10px; }
  .invoice-req th, .invoice-req td,
  .invoice-items th, .invoice-items td { padding: 4px 8px; }
  .invoice p { margin: 6px 0; }
  .invoice-total-words { margin: 8px 0; }
  .invoice-sign { margin-top: 18px; }
  /* Подпись и сумма прописью не разрываются между листами: если счёт
     когда-нибудь всё же перерастёт лист, рваться должно не по ним. */
  .invoice-sign, .invoice-total-words { break-inside: avoid; }
}

/* Шапка печатного счёта: знак и имя рядом, как в письме и на сайте. */
.invoice-mark { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; }
.invoice-mark .mark { width: 28px; height: 28px; }
/* Сумма прописью — крупнее прочего текста: её ищут глазами, а не читают
   подряд. Это первое, на что смотрит чужой бухгалтер. */
.invoice-total-words { font-size: var(--fs-base); font-weight: 600; margin: 12px 0; }

/* Выбор типа аккаунта при регистрации */
/* Разметка та же, что у .radio-group: Django 5 рендерит вложенные <div> */
/* Группа переключателей (способ оплаты): кружок и подпись строго в одну строку.
   Каждый вариант — отдельная строка-плашка, чтобы попадать по ней было легко. */
.radio-group { margin: 0 0 18px; }
.radio-group > label { display: block; margin-bottom: 8px; color: var(--muted); font-size: var(--fs-md); }
/* Django 5 рендерит RadioSelect как <div><div><label><input> …, а не списком */
.radio-group > div > div { margin-bottom: 8px; }
.radio-group > div > div:last-child { margin-bottom: 0; }
.radio-group > div > div > label {
  display: flex; align-items: center; gap: 10px; margin: 0; cursor: pointer;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px;
  background: transparent; transition: border-color .15s ease, box-shadow .15s ease;
}
.radio-group > div > div > label:hover { border-color: var(--border-hover); }
.radio-group input[type="radio"] { width: auto; margin: 0; flex: none; accent-color: var(--accent); }
/* Выделяем именно выбранный вариант: невыбранные остаются обычными,
   без серой заливки — она читалась как «недоступно». */
.radio-group > div > div:has(input:checked) > label {
  border-color: var(--accent); box-shadow: var(--ring); color: var(--fg-strong);
  font-weight: 600;
}
.radio-group .helptext { display: block; margin-top: 6px; }

/* Главная кнопка формы — по центру карточки */
.form-actions { display: flex; justify-content: center; margin: 4px 0 0; }

/* Реквизиты для перевода — их копируют, поэтому моноширинный и заметный блок */

/* Кнопка переключения языка — видна только без JS (скрипт прячет её) */
.lang-go {
  background: var(--surface); color: var(--fg); border: 1px solid var(--border);
  border-radius: 10px; padding: 7px 10px; cursor: pointer; margin-left: 6px;
}
/* Прятать СТИЛЕМ, а не только скриптом внизу страницы (A_О_520): скрипт
   успевал после первой отрисовки, и на телефоне шапка с кнопкой стояла
   на строку выше нужного, а потом прыгала на 41 px (CLS 0,24 в 4 из 8
   загрузок). Класс `no-js` снимается в <head>, до отрисовки. */
html:not(.no-js) .lang-go { display: none; }

/* --- Оформление субтитров -------------------------------------------------
   Плитки той же ширины, что и карточки заказов (216 px), но пропорции 9:16:
   это кадр клипа, и показывать его нужно так, как он будет выглядеть. */
/* `justify-content: center` — плитки образцов идут по центру окна, а не
   липнут к левому краю: на широком экране ряд из трёх карточек в 216 px
   оставлял справа пустоту в пол-экрана, и страница выглядела съехавшей. */
.template-grid {
  display: grid; gap: 20px; grid-template-columns: repeat(auto-fill, 216px);
  justify-content: center;
  list-style: none; padding: 0; margin: 0 0 28px;
}
.template-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; padding: 12px; text-align: center;
}
/* ОБРАЗЕЦ ВО ВСЮ ШИРИНУ КАРТОЧКИ, А ВЫСОТА — ПО ФОРМАТУ (A_О_382_В,
   слово владельца 10.09). Стояло жёсткое 192×341 (9:16) с `object-fit:
   cover`: горизонтальное оформление обрезалось по краям, и человек видел
   не то, что получит. Теперь размеры приходят разметкой из
   `clip_formats.preview_box`, а правило только не даёт им вылезти
   из карточки; `object-fit: contain` — предохранитель на случай, если
   картинка придёт с другими пропорциями: лучше поля по краям, чем
   срезанный текст. */
.template-card img {
  width: 100%; height: auto; object-fit: contain;
  border-radius: 8px; background: var(--surface-2);
}

/* ГРУППА ОДНОГО ФОРМАТА в списке стилей (A_О_382_В). Заголовок группы
   отделяет вертикальные стили от горизонтальных: одним списком они
   читались как один набор, и выбирать приходилось, гадая по картинке. */
.template-format-group { margin-bottom: 34px; }
.template-format-group h2 { margin-bottom: 6px; }
/* Формат оформления в редакторе — словом, без рычага: менять его
   нечем намеренно (см. разбор в `template_form.html`). */
.template-aspect-name { margin: 4px 0 0; font-weight: 600; }
.template-card h3 { font-size: var(--fs-base); margin: 10px 0 8px; }
.template-card form { display: inline; }

/* Поля выбора цвета: браузер рисует их крохотными, а .card input { width:100% }
   растягивает на всю карточку — и то и другое выглядит сломанным. */
.card input[type="color"] {
  width: 64px; height: 36px; padding: 2px; cursor: pointer;
}

/* Выбор оформления картинками: образец важнее названия — по слову «Хайлайт»
   понять, что получится, невозможно. Сам кружок переключателя прячем:
   выбранное показывает рамка вокруг образца. */
/* СВЁРНУТЫЙ ВЫБОР ОФОРМЛЕНИЯ НА ТЕЛЕФОНЕ (A_Д_157).

   На широком экране заголовок сводки не нужен: галерея и так видна
   целиком, а лишняя строка «Оформление: Классика» повторяла бы то,
   что показано рамкой на плитке. Поэтому сводка живёт только там,
   где список сворачивается, — до 560 px. */
.template-fold-head { display: none; }
@media (max-width: 560px) {
  .template-fold-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px; cursor: pointer; list-style: none;
    padding: 12px 14px; margin: 10px 0 0;
    border: 1px solid var(--border); border-radius: 12px;
    background: var(--surface); min-height: var(--palec);
  }
  /* Стрелку-треугольник убираем: у нас есть своя подпись действия,
     а два указателя на одно действие читаются как два действия. */
  .template-fold-head::-webkit-details-marker { display: none; }
  .template-fold-act { color: var(--accent); font-weight: 600; white-space: nowrap; }
  .template-fold[open] .template-fold-act { color: var(--muted); }
}
.template-pick { border: 0; padding: 0; margin: 18px 0; }
.template-pick legend { font-weight: 600; padding: 0; }
.template-pick-list {
  display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  margin-top: 10px;
}
.template-pick-item {
  display: block; text-align: center; cursor: pointer;
  border: 2px solid var(--border); border-radius: 12px; padding: 8px;
  background: var(--surface);
}
.template-pick-item:hover { border-color: var(--border-hover); }
.template-pick-item input[type="radio"] {
  position: absolute; opacity: 0; width: 0; height: 0; margin: 0;
}
.template-pick-item img {
  width: 100%; height: auto; aspect-ratio: 9 / 16; object-fit: cover;
  border-radius: 8px; background: var(--surface-2); display: block;
}
.template-pick-item span { display: block; margin-top: 8px; font-size: var(--fs-md); }
/* Выбранная плитка: рамка вокруг образца и жирная подпись. Через соседний
   селектор — по той же причине, что и «Выбрано» выше. */
.template-pick-item input:checked ~ img {
  outline: 3px solid var(--accent); outline-offset: -1px;
}
.template-pick-item input:checked ~ .template-pick-name {
  font-weight: 700; color: var(--fg-strong);
}
.template-pick-item:has(input:checked) {
  border-color: var(--accent); box-shadow: var(--ring);
}
/* Соотношения, для которых годится оформление (A_Д_170_В). Мельче
   названия и приглушённо: это справка, а не предмет выбора. Селектор
   с типом элемента обязателен — общее `.template-pick-item span`
   (0,1,1) выше задаёт им размер названия. */
.template-pick-item span.template-pick-formats {
  margin-top: 2px; font-size: var(--fs-sm); color: var(--muted);
  letter-spacing: 0.02em;
}

/* Клавиатура: без видимого кружка фокус должен быть заметен на самой плитке */
.template-pick-item input:focus-visible ~ img { outline-color: var(--fg-strong); }

/* ОБРАЗЕЦ В ФОРМАТЕ ЗАКАЗА (A_О_231).

   Рамка образца — вертикальная 9:16 по умолчанию, и это верно, пока
   заказывают вертикаль. Но кадр теперь бывает трёх видов, а `object-fit:
   cover` в чужой рамке ОБРЕЗАЕТ картинку молча: горизонтальный образец
   в вертикальной рамке показал бы узкую полосу из середины кадра, и человек
   выбирал бы оформление по тому, чего в клипе не увидит.

   Соотношение берётся у секции (`data-aspect`), а не пишется каждой плитке:
   формат один на весь экран, и менять его при переключении должно одно
   место. Атрибутный селектор (0,2,1) тяжелее голого `.template-pick-item img`
   (0,1,1) — правило ниже не «побеждает порядком», оно побеждает весом,
   и потому переживёт перестановку блоков. */
[data-aspect="16_9"] .template-pick-item img { aspect-ratio: 16 / 9; }
[data-aspect="1_1"] .template-pick-item img { aspect-ratio: 1 / 1; }

/* Плитка «своё оформление» едет вместе с остальными. Забыть её нельзя:
   она стоит в том же ряду сетки, и ряд тянется по САМОЙ ВЫСОКОЙ плитке —
   вертикальный пунктир рядом с горизонтальными образцами вытягивал ряд
   вдвое и оставлял под соседями пустое поле. Замечено снимком на 1440,
   из кода это не видно никак. */
[data-aspect="16_9"] .template-pick-new svg { aspect-ratio: 16 / 9; }
[data-aspect="1_1"] .template-pick-new svg { aspect-ratio: 1 / 1; }

/* В горизонтали колонка шире: образец при 150 px становится 84 px высотой,
   и разглядеть на нём субтитры — то, ради чего в образец и смотрят, —
   нельзя. Ряд при этом короче: плитки ниже, места в высоту уходит меньше,
   чем у вертикали. */
[data-aspect="16_9"] .template-pick-list {
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
/* Карточка пересборки у 16:9 шире (A_О_481): в общей `.card` (440 точек)
   образцы от 240 вставали по одному — семь плиток столбом. 600 дают два
   в ряд; на телефоне карточка и так во всю ширину. */
.card[data-aspect="16_9"] { max-width: 600px; }

/* Плитка, спрятанная скриптом при смене формата. Через список — чтобы
   вес (0,2,1) перебивал `display: block` у самой плитки (0,1,0):
   голый `[hidden] { display: none }` браузера легче и молча проигрывает. */
.template-pick-list .template-pick-item[hidden] { display: none; }
/* Пустая группа своих оформлений уходит вместе с заголовками. Вес (0,2,0)
   перебивает и `display: grid` у списка (0,1,0), и заголовок группы. */
.panel .pick-group[hidden],
.panel .template-pick-list[hidden] { display: none; }

/* Объяснение, что случилось при смене формата: сказано ВСЛУХ, значит
   и выглядеть должно как речь, а не как ошибка. */
.template-format-note { margin-top: 6px; }

/* Плитка «своё оформление» — тот же размер и рамка, что у образцов, но это
   ссылка, а не выбор: настоящего образца у несозданного шаблона нет. */
.template-pick-new {
  display: flex; flex-direction: column; justify-content: center;
  text-decoration: none; color: var(--fg);
}
.template-pick-new svg {
  width: 100%; height: auto; aspect-ratio: 9 / 16; display: block;
  border-radius: 8px; background: var(--surface-2); color: var(--fg-muted, currentColor);
}
.template-pick-new:hover { border-color: var(--accent); }
.template-pick-new:hover svg { color: var(--accent); }

/* --- Выбор клипов для смены оформления ------------------------------------
   Та же сетка плиток, что у выбора оформления, но плитки мельче: здесь
   их бывает тридцать, и выбирают из них по заголовку, а не по картинке.
   Галочку показываем настоящую: снятая галочка — осмысленное действие
   («оставить как есть»), и она должна читаться с одного взгляда. */
.clip-pick { border: 0; padding: 0; margin: 18px 0; }
.clip-pick legend { font-weight: 600; padding: 0; }
.clip-pick-list {
  display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  margin-top: 10px;
}
.clip-pick-item {
  display: block; text-align: center; cursor: pointer; position: relative;
  border: 2px solid var(--border); border-radius: 12px; padding: 8px;
  background: var(--surface);
}
.clip-pick-item:hover { border-color: var(--border-hover); }
.clip-pick-item input[type="checkbox"] {
  position: absolute; top: 10px; left: 10px; margin: 0; z-index: 1;
  width: 18px; height: 18px; accent-color: var(--accent);
}
.clip-pick-item img {
  width: 100%; height: auto; aspect-ratio: 9 / 16; object-fit: cover;
  border-radius: 8px; background: var(--surface-2); display: block;
}
.clip-pick-title {
  display: block; margin-top: 8px; font-size: var(--fs-md);
  /* Заголовки бывают длинными, а плитка узкая: две строки и многоточие */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.clip-pick-style { display: block; font-size: var(--fs-sm); color: var(--fg-muted); }
.clip-pick-item:has(input:checked) {
  border-color: var(--accent); box-shadow: var(--ring);
}
/* Кадр клипа — в пропорции заказа (A_О_476_В): заставка снята с самого
   клипа, и в вертикальной рамке горизонтальный клип терял бока. */
[data-aspect="16_9"] .clip-pick-item img { aspect-ratio: 16 / 9; }
[data-aspect="1_1"] .clip-pick-item img { aspect-ratio: 1 / 1; }
/* Невыбранный клип гасим: видно, что он останется как есть */
.clip-pick-item:not(:has(input:checked)) img { opacity: 0.45; }
.clip-pick-item input:focus-visible { outline: 2px solid var(--fg-strong); }

/* --- Редактор оформления --------------------------------------------------
   Слева кадр с блоками, справа поля. На узком экране кадр уезжает наверх. */
/* Пара «кадр + поля» стоит ПО ЦЕНТРУ ЭКРАНА (владелец, 13.08), а не
   у левого края: на широком мониторе редактор занимал левую треть,
   и справа оставалось пустое поле шириной с сам редактор.
   `max-width` держит колонки рядом друг с другом — без него центрирование
   развело бы их по краям на большом экране. */
.template-editor {
  /* Высота кадра — ОДНО выражение на всю пару: от него считается и сам
     кадр, и ширина колонки под ним. Пока ширину колонки определяло
     содержимое, длинная подпись под кадром растягивала её до 941 px
     при кадре в 270, и карточка полей ложилась на эту подпись. */
  --frame-room: calc(100vh - var(--topbar-h) - 110px);
  --frame-h: min(480px, var(--frame-room));
  /* ПРОПОРЦИЯ КАДРА — ПО ФОРМАТУ ОФОРМЛЕНИЯ (A_О_475). До 29.09 здесь
     стояло 9:16 числом, и оформление 16:9 правилось в вертикальном
     кадре: блоки и кегль врали ровно на разницу пропорций. Формат
     кладёт вид в `data-aspect` формы. Потолок высоты у горизонтали
     ниже: 480 точек высоты дали бы кадр 853 в ширину, и полям рядом
     места бы не осталось. */
  --frame-k: 9 / 16;
  display: flex; gap: 28px; align-items: flex-start; flex-wrap: wrap;
  /* Ширина как у верхнего меню (владелец, 13.08): на большом экране
     панель полей занимала треть, а справа оставалась пустота шириной
     с саму панель. Узкой она остаётся там, где иначе не помещается, —
     колонки складываются в столбик сами (flex-wrap). */
  justify-content: center; max-width: 1240px; margin-inline: auto;
}
.template-editor[data-aspect="1_1"] {
  --frame-h: min(400px, var(--frame-room)); --frame-k: 1;
}
.template-editor[data-aspect="16_9"] {
  --frame-h: min(360px, var(--frame-room)); --frame-k: 16 / 9;
}
/* Кадр ЛИПКИЙ. Форма длиннее экрана вчетверо, и до 13.08 человек,
   правивший нижние поля (обводку, эмодзи), делал это вслепую: кадр
   к этому времени уезжал за верхний край. Смысл предпросмотра в том,
   чтобы видеть правку в тот момент, когда её делаешь.
   `align-items: flex-start` у родителя обязателен — растянутый по высоте
   элемент липнуть не может. */
/* Колонка ровно по кадру: пропорция формата от его высоты. Подписи под
   кадром переносятся внутри этой ширины, а не раздвигают её. */
.template-stage {
  flex: 0 0 calc(var(--frame-h) * var(--frame-k));
  max-width: 100%; align-self: stretch;
}
/* Липнет эта коробка, а не колонка: колонка растянута на высоту формы,
   и внутри неё кадру с подписью есть где ехать. Пробовала наоборот —
   у колонки по высоте содержимого липкость не работает вовсе, кадр
   уезжал за экран во всех окнах (замер 13.08: 600, 700, 900 px). */
.template-sticky {
  position: sticky;
  /* НИЖЕ ШАПКИ, а не под ней: шапка тоже липкая (`.topbar`, top: 0,
     z-index 50), и кадр, прилипавший к 16 px, заезжал под неё верхней
     полосой — владелец увидел это первым же прокручиванием.
     Высота шапки ~72 px: 14 + 44 (лого/кнопки) + 14 отступов. */
  top: calc(var(--topbar-h) + 12px);
}
/* Панель полей тянется во всю оставшуюся ширину до края верхнего меню
   (владелец, 13.08). Общий `.card` держит 440 px — для страниц-форм это
   верно, а здесь рядом стоит кадр, и панель обязана занять остальное,
   иначе справа висит пустота шириной с саму панель. */
.template-fields { flex: 1 1 320px; max-width: none; }

/* На узком экране кадр стоит НАД формой во всю ширину: липким он съел бы
   половину экрана, и поля пришлось бы искать под ним. Там он обычный. */
@media (max-width: 700px) {
  .template-sticky { position: static; }
}

/* Кадр в пропорции формата — той же, что у клипа, иначе блоки врали бы */
.template-frame {
  /* ВСЕГДА ВЛЕЗАЕТ ЦЕЛИКОМ (владелец, 13.08). Липкий кадр высотой 480 px
     помещался не в каждое окно: у невысокого экрана низ уезжал за край,
     и нижние блоки правились вслепую — ровно то, ради чего липкость
     и делалась. Теперь высота берётся от окна (`--frame-h`: вычитаем
     шапку, подписи под кадром и поля), ширина — от неё по формату. */
  /* ВЫСОТУ СЧИТАЕТ ПРОПОРЦИЯ, А НЕ ЧИСЛО. Заданные разом высота
     и ширина `aspect-ratio` отменяют: где колонка уже кадра (телефон),
     кадр сжимался бы в ширину при прежней высоте — не тот формат. */
  position: relative; height: auto;
  width: calc(var(--frame-h) * var(--frame-k)); max-width: 100%;
  aspect-ratio: var(--frame-k);
  border-radius: 12px; overflow: hidden; border: 1px solid var(--border);
  background: var(--surface-2); touch-action: none; user-select: none;
}
/* Ролик появления слов занимает то же место, что и картинка (A_Д_308):
   они подменяют друг друга, и разъехавшийся размер читался бы как
   «предпросмотр прыгает». Правило одно на оба, а не два похожих. */
.template-frame img,
.template-frame video { width: 100%; height: 100%; object-fit: cover; display: block; }
.template-frame img[hidden],
.template-frame video[hidden] { display: none; }

/* ЗОНЫ ИНТЕРФЕЙСА ПЛОЩАДОК. Пунктир, а не заливка: это подсказка,
   а не запрет — поставить туда можно, просто зритель не увидит.
   Лежат ПОД рамками блоков (`z-index`), иначе перекрывали бы нажатие. */
.safe-zone {
  position: absolute; pointer-events: none; z-index: 0;
  /* Заметнее, чем кажется нужным: первая попытка была тоньше и бледнее,
     и на кадре 270 px её попросту не видели — подсказка, которую надо
     искать, не работает. */
  border: 2px dashed rgba(255, 255, 255, .75);
  background: repeating-linear-gradient(
    -45deg, rgba(0, 0, 0, .34) 0 5px, transparent 5px 11px);
}
.safe-zone-top { left: 0; right: 0; top: 0; border-top: 0; }
.safe-zone-bottom { left: 0; right: 0; bottom: 0; border-bottom: 0; }
.safe-zone-right { top: 0; bottom: 0; right: 0; border-right: 0; }
.safe-zone-name {
  position: absolute; left: 6px; bottom: 6px;
  font-size: 10px; line-height: 1.2; color: #fff;
  background: rgba(0, 0, 0, .55); padding: 1px 5px; border-radius: 4px;
}
/* Рамки блоков — поверх зон: их тащат, зоны только показывают. */
.drag-box { z-index: 1; }

/* Блок поверх кадра: перетаскивается целиком, растягивается за углы.

   ТРИ РАМКИ НА ОДНОМ КАДРЕ 270 px ШИРИНОЙ — и до 13.08 они были
   одинаковыми: один цвет, сплошная синяя заливка поверх картинки,
   подписи всегда яркие и налезающие друг на друга. Кадр читался
   как чертёж, а не как будущий клип, и главное — под заливкой не было
   видно того самого текста, ради которого её двигают.

   Что изменено (владелец, 13.08 «рамки попробуй сделать лучше»):
   свой цвет каждому блоку, заливка ТОЛЬКО под курсором и при
   перетаскивании, подписи приглушены до наведения. */
.drag-box {
  --box: var(--accent);
  position: absolute; border: 1.5px dashed var(--box); border-radius: 6px;
  background: transparent; cursor: move;
  transition: background .12s ease, box-shadow .12s ease;
}
/* Свой цвет: три рамки рядом различаются с одного взгляда, и подпись
   больше не единственное, чем они отличаются друг от друга. */
.drag-box[data-block="title"] { --box: #4f8cff; }
.drag-box[data-block="sub"]   { --box: #22c55e; }
.drag-box[data-block="emoji"] { --box: #f59e0b; }

/* Под курсором — лёгкая заливка и подсветка: видно, ЧТО схватишь,
   до того как нажал. `color-mix` берёт цвет самой рамки, поэтому
   добавление четвёртого блока не потребует новых правил. */
.drag-box:hover, .drag-box:focus-within {
  background: color-mix(in srgb, var(--box) 14%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--box) 45%, transparent);
}
.template-frame.is-dragging .drag-box {
  background: color-mix(in srgb, var(--box) 12%, transparent);
}

.drag-box-name {
  position: absolute; top: -9px; left: 6px; padding: 0 5px;
  font-size: var(--fs-xs); line-height: 16px; border-radius: 4px;
  background: var(--box); color: #fff; white-space: nowrap;
  /* Приглушены, пока рамку не трогают: три ярких ярлыка на маленьком
     кадре спорят с самим клипом, а нужны они в момент выбора. */
  opacity: .62; transition: opacity .12s ease;
}
.drag-box:hover .drag-box-name,
.template-frame.is-dragging .drag-box-name { opacity: 1; }

/* Рамка эмодзи мала по своей природе: её сторона — доля высоты кадра,
   а не свободно тянущийся блок. Подпись внутри закрывала бы сам символ,
   поэтому у этой рамки она стоит НАД ней. */
.drag-box-emoji .drag-box-name { top: -20px; }
.drag-box-emoji .drag-box-text {
  display: grid; place-items: center; width: 100%; height: 100%;
}

/* Поле, выключенное вместе с тем, от чего оно зависит (B_О_259, B_О_258):
   приёмы над субтитрами при снятой галке «показывать субтитры». Гасим
   всю строку вместе с подписью — иначе видно только серый ввод,
   а подпись остаётся яркой и обещает работу. */
.field-off { opacity: .5; }
.field-off label, .field-off .helptext { cursor: not-allowed; }

/* Строка «почему выбран этот момент» под заголовком клипа (B_О_261):
   мельче заголовка и в одну строку — это пояснение, а не второй
   заголовок. Длинную строку модель не пришлёт (обрезаем до 60 знаков),
   но узкий экран всё равно надо стеречь. */
.clip-reason {
  margin: .15rem 0 0; font-size: .82rem; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* Предпросмотр обложки. КАРТИНКА ВПИСЫВАЕТСЯ В КАДР 9:16, а не показывается
   в своём размере: собранную обложку рисует сервер уже в кадре, а свою
   картинку — браузер, и без этих правил она вылезала «как есть» (владелец,
   03.09: «превью загрузилось, но не смасштабировалось»). Поля видны тем же
   цветом, каким их зальёт рендер. */
.cover-preview { margin: .6rem 0 0; max-width: 220px; }
.cover-preview img {
  display: block; width: 100%; aspect-ratio: 9 / 16;
  object-fit: contain; background: #000; border-radius: 6px;
}
/* Кадр обложки — по формату оформления (A_О_478): `data-aspect` кладёт
   на форму редактора вид (A_О_475). */
[data-aspect="16_9"] .cover-preview img { aspect-ratio: 16 / 9; }
[data-aspect="1_1"] .cover-preview img { aspect-ratio: 1 / 1; }
.cover-preview figcaption { margin-top: .25rem; }

/* Полоса времени: рамка в её настоящую толщину — 14 точек из 1920,
   то есть меньше процента высоты. Подпись поэтому НАД рамкой, как
   у эмодзи, а курсор говорит, что двигать её можно только вверх-вниз. */
.drag-box-line { cursor: ns-resize; }
.drag-box-line .drag-box-name { top: -20px; }

.drag-handle {
  position: absolute; width: 11px; height: 11px; border-radius: 50%;
  background: #fff; border: 2px solid var(--box); box-sizing: border-box;
}
/* ПАЛЕЦ ТОЛЩЕ УГОЛКА. Сама точка остаётся маленькой — крупные квадраты
   закрывали бы кадр, — но ловит нажатие невидимая площадка вокруг неё.
   То же правило, что и на телефонных целях в кабинете. */
.drag-handle::after {
  content: ""; position: absolute; inset: -13px;
}
.drag-handle[data-edge="nw"] { left: -6px; top: -6px; cursor: nwse-resize; }
.drag-handle[data-edge="ne"] { right: -6px; top: -6px; cursor: nesw-resize; }
.drag-handle[data-edge="sw"] { left: -6px; bottom: -6px; cursor: nesw-resize; }
.drag-handle[data-edge="se"] { right: -6px; bottom: -6px; cursor: nwse-resize; }

/* Разделы «Заголовок клипа» и «Субтитры»: рамка показывает, что к чему */
.block-fields {
  border: 1px solid var(--border); border-radius: 10px;
  padding: 4px 14px 10px; margin: 16px 0;
}
.block-fields legend { padding: 0 6px; }
.block-fields legend label {
  display: flex; align-items: center; gap: 8px; font-weight: 600; cursor: pointer;
}
.block-fields legend input[type="checkbox"] { width: auto; margin: 0; }

/* ГДЕ ГОДИТСЯ ОФОРМЛЕНИЕ: галочка перед подписью (A_О_231).

   Без правила для квадратика он растягивается на всю колонку общим
   `.card input { width: 100% }`, и подпись уезжает под него, а сам он —
   к правому краю. Замечено снимком редактора на 1440: разметка и форма
   при этом верны, из кода не видно ничего.

   СЕЛЕКТОРЫ НАПИСАНЫ ПО ПРЕДМЕТУ, а не по памяти о Django: `forms.
   CheckboxSelectMultiple` в Django 5 рисует не `ul > li`, а вложенные
   `div > label > input`. Правила для списка, написанные по памяти, здесь
   не совпали НИ С ЧЕМ и молча не делали ничего — а страница выглядела
   исправленной, потому что чинил её один селектор из трёх.

   Вес (0,3,1) тяжелее общего `.card input` (0,1,1): правило побеждает
   весом, а не порядком строк, и переживёт перестановку блоков. */
.block-fields[data-block="aspects"] > div > div {
  margin: 2px 0;
}
.block-fields[data-block="aspects"] label {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
}
.block-fields[data-block="aspects"] input[type="checkbox"] {
  width: auto; margin: 0;
}

/* Галочка внутри блока: квадратик перед подписью, всё одной строкой —
   так же, как в легенде блока. Без width:auto поле растянулось бы
   на всю колонку по общему правилу для input. */
.field-check label {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
}
.field-check input[type="checkbox"] { width: auto; margin: 0; }

/* Надпись внутри блока — та же, что на картинке, но нарисованная браузером.
   Видна только пока блок тащат: иначе она двоилась бы с картинкой. */
.drag-box-text {
  /* Ровно по блоку. КУДА ПРИЖАТ ТЕКСТ, РЕШАЕТ СКРИПТ, а не этот файл:
     у заголовка он стоит у верхней границы, у субтитров — на две строки
     выше нижней (A_О_349_В), и оба числа живут в одном месте с рендером.
     До 06.09 здесь стояло жёсткое `flex-end` «как в самом клипе» — оно
     и соврало владельцу, когда клип рисовать стали иначе.
     Всё, что не поместилось, обрезается рамкой, а не вылезает наружу. */
  position: absolute; inset: 0;
  display: none; align-items: flex-end; justify-content: center;
  text-align: center; line-height: 1.15; font-weight: 700;
  pointer-events: none; overflow: hidden;
}
.template-frame.is-dragging .drag-box-text { display: flex; }
/* Картинку гасим: на ней текст ещё на прежнем месте */
.template-frame.is-dragging img { opacity: .25; }

/* Сообщение о том, что с заказом прямо сейчас что-то происходит:
   заметнее подсказки, но не тревожное, как ошибка. */
.notice {
  background: var(--surface-2); border: 1px solid var(--border);
  border-left: 3px solid var(--accent); border-radius: 10px;
  padding: 12px 16px; margin: 0 0 16px;
}

/* --- Подтверждение заказа -------------------------------------------------
   Две колонки: слева кадр, справа что заказано. Прежде страница была одной
   карточкой шириной 440 px и на большом экране выглядела узкой полосой. */
.split-layout {
  display: grid; gap: 24px; align-items: start;
  grid-template-columns: 270px minmax(0, 1fr);
  max-width: 900px;
}
@media (max-width: 720px) {
  /* Предпросмотр остаётся сверху: он и есть главное на странице */
  .split-layout { grid-template-columns: 1fr; }
  /* НО НЕ ТАМ, ГДЕ ЧЕЛОВЕК РЕШАЕТ (A_О_195.1). На экране подтверждения
     картинка занимала весь первый экран телефона — 667 px, — и блок
     «Спишется» оказывался на отметке 1872: две прокрутки до денег.
     Здесь он пришёл решать, а не любоваться, и предпросмотр уходит
     под главное. На широком окне порядок прежний: там колонки рядом
     и спорить не о чем. */
  .split-decide .split-media { order: 2; }
  /* Карточки уровней чистки — в столбик (A_О_353_В). */
  .level-cards { grid-template-columns: 1fr; }
  /* Пара «оригинал — перевод» тоже в столбик: переехало сюда из своего
     блока с тем же условием (A_О_354_В). */
  .translation-pair { grid-template-columns: 1fr; }
}
/* БЕЗ ПРЕВЬЮ — ОДНА КОЛОНКА (A_Д_286_В): у дубляжа врезки нет, и 270 px
   под неё стояли пустыми между меню кабинета и «Проверьте заказ». */
.split-layout.split-single { grid-template-columns: minmax(0, 1fr); }
/* ПЛЕЕР ДУБЛЯЖА — ПО ШИРИНЕ ПАНЕЛИ (A_Д_294_В, владелец 03.09: «видео уезжает
   за правую границу экрана»). Без правила <video> рисуется в своём размере,
   1920 px, и страница уезжала вправо. */
.dubbed-player { display: block; width: 100%; max-width: 100%; height: auto;
                 background: #000; border-radius: 10px; }
/* Широкое превью 16:9 в основном блоке (A_Д_291_В, владелец 03.09): кадр
   записи на всю ширину панели, а не четверть узкой колонки. */
.split-main img.confirm-shot-wide {
  display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: 10px; margin-bottom: 8px;
}
.split-main .split-media-caption { margin: 0; }
.split-media { position: sticky; top: 16px; }
/* Предпросмотр — такая же карточка, как блоки справа. Голая картинка рядом
   с панелями в рамках выглядела выпавшей из страницы: одна колонка живёт
   по своим правилам, вторая по своим. Рамка и фон у карточки, у самой
   картинки их нет — иначе рамка в рамке. */
.split-media .panel { padding: 14px; }
.split-media img {
  width: 100%; height: auto; aspect-ratio: 9 / 16; object-fit: cover;
  border-radius: 10px; background: var(--surface-2); display: block;
}
.split-media-caption {
  color: var(--muted); font-size: var(--fs-sm); margin: 10px 0 0; text-align: center;
}

/* КАРТИНКА «КАК БУДУТ ВЫГЛЯДЕТЬ КЛИПЫ» — В ФОРМАТЕ ЗАКАЗА (A_Д_167_В).

   ШЕСТОЙ случай той же болезни: `.split-media img` выше задаёт
   `aspect-ratio: 9 / 16` и `object-fit: cover`, и квадратный кадр
   приезжал ОБРЕЗАННЫМ в вертикальную рамку — заголовок клипа терял
   первую и последнюю букву. Нашёл владелец глазами 21.08, атрибуты
   width/height при этом уже были верные: их перебивает CSS.

   Поэтому селектор начинается с `.split-media` — как у `.confirm-shot`
   на подтверждении, и по той же причине: вес, а не порядок. */
.split-media img.order-shot { aspect-ratio: 9 / 16; }
.split-media img.order-shot-16_9 { width: 100%; aspect-ratio: 16 / 9; }
.split-media img.order-shot-1_1 { aspect-ratio: 1 / 1; }
.split-media .panel .split-media-caption { margin-bottom: 0; }
.split-main { display: grid; gap: 16px; }

/* Блок сведений: рамка отделяет «Видео» от «Что получится» и от расхода */
.panel {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px 22px; box-shadow: var(--shadow);
}
.panel p:last-child { margin-bottom: 0; }
.panel-title {
  font-size: var(--fs-sm); letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 14px; font-weight: 700;
}
/* Расход — то, ради чего страницу и открыли: выделяем рамкой поярче */
.panel-cost { border-color: var(--border-hover); }
.cost-figure { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap;
  font-size: var(--fs-lg); margin: 0 0 12px; }
.cost-figure .muted { font-size: var(--fs-md); }

/* ЗАГРУЗКА ФАЙЛА: состояние словами.

   У этого блока не было НИ ОДНОГО правила — ни у обычной надписи,
   ни у `.upload-error`, которую скрипт исправно вешал с первого дня.
   То есть сообщения были, а выглядели как мелкая серая строка сбоку
   от полосы; ошибка не отличалась от хода загрузки ничем.

   Просьба владельца 16.08 была про проверку файла («не понятно, почему
   пауза»), но причина оказалась общей: состояние не показывалось
   никак. */
.upload-state { display: grid; gap: 8px; margin-top: 12px; }
.upload-state progress { width: 100%; height: 10px; }

/* ПРОВЕРКА ФАЙЛА — КРУПНО И ЖИРНО (прямая просьба владельца). Байты
   доехали, полоса стоит на сотне, а мы смотрим содержимое: без слова
   человек видит замерший экран и решает, что мы сломались. Это самая
   дорогая пауза во всём пути клиента — она идёт после того, как он уже
   потратил минуты канала. */
.upload-checking [data-upload-text] {
  /* ПО ЦЕНТРУ И КРУПНЕЕ (владелец, 16.08). Слева, обычным кеглем,
     надпись читается как подпись к полосе — то есть как деталь.
     А она здесь главная: это единственное, что объясняет паузу,
     и человек должен наткнуться на неё взглядом, а не найти. */
  display: block; text-align: center;
  font-size: clamp(1.25rem, 3.2vw, 1.75rem); font-weight: 700;
  color: var(--fg-strong); line-height: 1.3; margin-top: 4px;
}
.upload-error [data-upload-text] { color: var(--danger, #c0392b); font-weight: 600; }

/* ФАЙЛ ПРИНЯТ — ЭТО КОНЕЦ ОЖИДАНИЯ, И ВЫГЛЯДЕТЬ ОН ОБЯЗАН КОНЦОМ.
   Владелец 16.08 смотрел на принятый файл и написал «похоже опять
   висит»: серая строчка «Файл готов» ничем не отличалась от строчки
   «12 / 31 МБ», а кнопка отправки лежит экраном ниже. Отличие от
   «проверяем» здесь ровно обратное: там надо было объяснить паузу,
   здесь — сказать, что пауза кончилась. */
.upload-done [data-upload-text] { color: var(--ok-fg); font-weight: 600; }

/* ВЫБОР ОБРАБАТЫВАЕМОГО КУСКА ЗАПИСИ (A_О_184).

   Два ползунка стоят ОДИН ПОД ДРУГИМ, а не наложены друг на друга
   общей шкалой. Наложение выглядит наряднее и стоит того, чего мы
   не готовы платить: две ручки на одной дорожке слипаются в крайних
   положениях, и на телефоне человек уже не может взять нижнюю.
   Тот же приём, что в редакторе клипов, — там он проверен руками. */
/* ДВА КЛАССА В СЕЛЕКТОРЕ, а не один: ниже по файлу живёт
   `.panel label { display: block }`, и оно тяжелее одиночного класса.
   Первая редакция писала `.fragment-scope-item` — правило было верным
   и не работало: кружок переключателя вставал НАД подписью, а поля
   времени разъезжались столбиком. Из кода этого не видно вовсе,
   нашлось снимком; у нас это уже третий случай, когда специфичность
   отменяет правило молча. */
.fragment-scope { display: flex; gap: 20px; flex-wrap: wrap; margin: 0 0 14px; }
.fragment-scope .fragment-scope-item {
  display: flex; align-items: center; gap: 8px;
  /* Подпись не переносится по словам: «Всё видео» в две строки рядом
     с кружком читается как два разных варианта. `flex: none` одного
     не хватило — перенос делает сам текст внутри подписи, поэтому
     здесь же `white-space: nowrap`. */
  flex: none; white-space: nowrap; margin-bottom: 0; font-weight: 400;
}
/* СТОЛБЦОМ ВСЕГДА (A_О_456, слово владельца 23.09): три варианта
   разрешения стояли «два в строке, третий под ними» и читались как
   «два главных и лишний». Столбец не зависит от длины подписей и языка.
   Перенос по словам здесь разрешён: подписи нарезки длинные («…клипы
   будут мутными»), а в столбце кружок один на строку — спутать нечего. */
.fragment-scope.fragment-scope--stolbec { flex-direction: column; gap: 10px; }
.fragment-scope--stolbec .fragment-scope-item { white-space: normal; }
/* КРУЖОК — СВОЕЙ ШИРИНЫ. `.panel input { width: 100% }` ниже по файлу
   растягивал его на всю строку, едва подписи разрешили перенос: кружок
   вставал посередине, подпись уезжала к правому краю. Координаты строк
   при этом были верны — нашлось только снимком (ШЕСТОЙ случай,
   см. `.fragment-scope-item` выше). */
.fragment-scope--stolbec .fragment-scope-item input { width: auto; flex: none; }
.fragment-fields { display: grid; gap: 12px; }
.fragment-rail { display: grid; gap: 6px; }
.fragment-rail input[type="range"] { width: 100%; }
.fragment-times { display: flex; gap: 16px; align-items: flex-end; flex-wrap: wrap; }
.fragment-times .fragment-time { display: grid; gap: 4px; font-size: var(--fs-md); }
/* Поле времени узкое намеренно: в него влезает «1:05:00» и не влезает
   ничего лишнего — ширина сама подсказывает, чего от неё ждут. */
.fragment-times input[type="text"] { width: 8em; font-variant-numeric: tabular-nums; }
.fragment-total { color: var(--muted); font-size: var(--fs-md); padding-bottom: 6px; }

/* СВОДКА ЗАКАЗА ТРЕМЯ ГРУППАМИ (A_Д_180_В): «что получится», «из чего»,
   «когда». Семь пар подряд глаз перебирает по одной; тремя кучками
   он находит нужное сразу. Сетка, а не колонки: на телефоне группы
   ложатся друг под друга сами, и второй разметки для узкого экрана
   не нужно. */
.order-facts {
  display: grid; gap: 22px 28px; margin: 14px 0 0;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.order-facts-group { min-width: 0; }
/* Заголовок группы РЕЗКО ОТЛИЧАЕТСЯ ОТ ПОДПИСИ ПОЛЯ, и это не вкусовщина.
   Первая редакция сделала его мелким прописным — ровно как `dt` рядом, —
   и на снимке (телефон, 390 px) группы перестали читаться группами:
   «ЧТО ПОЛУЧИТСЯ» и «СТАТУС» шли одинаковой лентой. Поймано глазами,
   числами такое не ловится.

   Обычный регистр, крупнее подписей, с линией под ним: три признака
   сразу, потому что одного (размера) на мелком экране не хватило. */
.order-facts-title {
  margin: 0 0 10px; padding-bottom: 6px; font-size: var(--fs-md);
  font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--fg-strong); border-bottom: 1px solid var(--border);
}
/* Первая подпись поля не отбивается от заголовка группы дважды */
.order-facts .order-summary-inline dt { margin-top: 0; }
/* Внутри группы пары идут столбиком: в строку они разъезжались бы
   по ширине группы и снова превращались в ленту. */
.order-facts .order-summary-inline { display: block; margin: 0; }
.order-facts .order-summary-inline > div + div { margin-top: 10px; }

/* Пары «название — значение» в строку, а не столбиком: их немного */
.order-summary-inline {
  display: flex; flex-wrap: wrap; gap: 8px 32px; margin: 14px 0 0;
}
.order-summary-inline > div { min-width: 0; }
.order-summary-inline dt { margin-top: 0; }

/* Врезки: предупреждение о формате видео и нехватка минут */
.callout {
  border-radius: 10px; padding: 11px 14px; margin: 14px 0 0;
  font-size: var(--fs-md); border: 1px solid var(--border); background: var(--surface-2);
}
.callout-warning { border-left: 3px solid #d8a13a; }
/* Предупреждение про время и деньги — не про качество результата.
   Стоят такие плашки рядом, и три одинаковых читаются одним куском:
   клиент пробегает их глазом и пропускает ту единственную, которая
   влияет на его решение платить. Синяя полоса и чуть более плотный текст
   отделяют «сделаем, но дольше и дороже» от «сделаем, но за качество
   кадрирования не ручаемся». */
.callout-cost { border-left: 3px solid var(--accent); }
.callout-cost b { font-weight: 600; }
/* Пробный заказ: зелёная полоса — это предложение, а не предупреждение */
.callout-trial { border-left: 3px solid #4a8f5b; }
.callout-error { border-left: 3px solid var(--error-fg); color: var(--error-fg); }
/* НЕОТМЕЧЕННАЯ ОБЯЗАТЕЛЬНАЯ ГАЛКА — КРАСНЫМ (слово владельца 02.09).
   Экран подтверждения отказывался отправлять заказ и говорил почему,
   но подпись появлялась ПОД галкой: на телефоне человек, стоящий
   у кнопки, видел лишь то, что ничего не произошло. Красим строку
   целиком и саму галку: `accent-color` перекрашивает квадратик
   без картинок и работает во всех наших браузерах. */
.field-check-invalid {
  border-left: 3px solid var(--error-fg);
  padding-left: 10px;
  color: var(--error-fg);
}
.field-check-invalid input[type=checkbox] { accent-color: var(--error-fg); }
/* Сообщение отказа — СВОЕЙ строкой. Оно вставляется скриптом как
   <span> и лежало inline: на 390 px наезжало на подсказку под галкой
   (снято браузером 02.09: подсказка 187..396, сообщение 378..467). */
.field-check [data-confirm-error] { display: block; margin-top: 8px; }
/* Предложение следующего шага, когда всё скачано и очередь пуста
   (первый экран кабинета, замысел D). Полоса та же, что у пробного:
   это предложение, а не предупреждение. */
.callout-next { border-left: 3px solid #4a8f5b; }

/* Служебный подвал кабинета: почта, удаление учётной записи, покупки
   и стиль клипов. Отделён чертой и приглушён — это не работа клиента,
   а обслуживание, и наверху оно съедало первый экран телефона. */
/* СЛУЖЕБНОЕ УХОДИТ ЗА СГИБ У ПУСТОГО КАБИНЕТА (A_Д_152_В).

   Замер 18.08 на 390x844 и 375x667: у клиента без заказов «Вы вошли
   как…» и «Удалить учётную запись» стоят на 559 px — то есть на первом
   же экране, в двух сантиметрах под приглашением «Начнём с первого
   видео». Первое, что мы предлагаем новому человеку рядом с кнопкой
   заказа, — удалиться.

   У клиента с заказами беды нет: список отодвигает служебное вниз.
   Поэтому и не видели — кабинет с заказами открывали все, пустой
   никто.

   Отступ считаем от ОКНА, а не числом в пикселях: 32 px хватает
   на ноутбуке и не хватает на телефоне, а «сколько осталось до низа
   экрана» — это ровно то, что мы хотим сказать. */
.cabinet-service { margin-top: 32px; padding-top: 16px;
  border-top: 1px solid var(--line); color: var(--muted); }
.dashboard-empty .cabinet-service { margin-top: 45vh; }
.cabinet-service-links { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 8px 0 0; }

/* Кнопка, которая пока не сработает: путь виден, но не зовёт.
   Не `disabled`: нажатие остаётся возможным, иначе новичок решит,
   что сервис не работает вовсе, — условие сказано словами рядом. */
/* НЕАКТИВНАЯ КНОПКА ВЫГЛЯДИТ НЕАКТИВНОЙ (находка D 30.08). Одной
   бледности мало: градиент оставался тот же, и на телефоне кнопка
   читалась как рабочая — человек нажимал и не понимал, почему тихо.
   Это единственное действие на первом экране новичка.

   Снимаем то, чем кнопка обещает действие: заливку, тень и объём.
   Остаётся обведённая рамкой форма — вид «пока нельзя», а не поломки.
   Нажатие НЕ запрещаем (правило A_О_270: путь к заказу должен быть
   виден, иначе новичок решит, что сервис не работает) — на той стороне
   стоит объяснение, почему нельзя. */
.btn-muted {
  opacity: 1;
  background: none; box-shadow: none;
  color: var(--muted); border: 1px dashed var(--border);
  font-weight: 500;
}
.btn-muted:hover { color: var(--fg); border-color: var(--border-hover); }
.hint-inline { display: inline-block; margin-left: 8px; }
/* Подпись «после подтверждения почты» — ДЕЙСТВУЮЩАЯ (A_Д_255_В, слово
   владельца 29.08): те же слова, но по нажатию письмо уходит заново.
   Видом остаётся подсказкой, поведением — кнопкой: подчёркивание
   появляется при наведении, чтобы читалось как нажимаемое. */
.hint-verify {
  color: var(--muted); font-size: var(--fs-md);
  text-decoration: underline dotted; text-underline-offset: 3px;
}
.hint-verify:hover { color: var(--fg); text-decoration-style: solid; }
/* Форма-обёртка не должна добавлять своих отступов: подпись стоит
   в одной строке с приглушённой кнопкой «Новая нарезка». */
form.inline-form.hint-inline { display: inline-flex; margin: 0; gap: 0; }

/* Обещание письма стоит у строк очереди, а не под списком */
.queue-hint { margin: 0 0 12px; }

/* Полоса готовности клипов. Стоит вплотную под плашкой «готово N из M»
   и повторяет её числа шириной — своего знания у неё нет.
   Высота маленькая намеренно: это спутник строки, а не отдельный
   элемент управления, и на телефоне он не должен спорить с текстом. */
.clips-bar {
  height: 6px; margin: 8px 0 0; border-radius: 3px;
  background: var(--surface-2); border: 1px solid var(--border);
  overflow: hidden;
}
.clips-bar span {
  display: block; height: 100%; background: var(--accent);
  /* Плавность — про приход НОВЫХ клипов: страница перечитывается сама,
     и без перехода полоса прыгает рывком при каждой перезагрузке. */
  transition: width .4s ease;
}

/* Полоса «остались несохранённые правки» в редакторе клипа.
   Кнопки в строку с текстом: предложение, а не тревога — потери ещё
   не случилось, и пугать человека нечем. */
.draft-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  border-left: 3px solid var(--accent);
}
.draft-bar span { flex: 1 1 240px; }

/* Главная кнопка страницы — во всю ширину блока: промахнуться невозможно */
.btn-wide { width: 100%; text-align: center; }

/* --- Новая нарезка --------------------------------------------------------
   Блоки в столбик, но во всю доступную ширину: узкая карточка на большом
   экране оставляла половину страницы пустой, а плитки оформления жались. */
/* КОЛОНКА НЕ РАЗДУВАЕТСЯ ПОД MIN-CONTENT (A_Д_276, замер 02.09). У grid
   с колонкой `auto` минимум трека — min-content содержимого, и одна
   несжимаемая строка голоса (радио + имя + подпись nowrap + кнопка)
   растягивала колонку до 427 px при форме 342: страница на телефоне
   скроллилась вбок на любом языке, на казахском — сильнее всего.
   `minmax(0, 1fr)` даёт треку минимум ноль, и nowrap-текст честно
   обрезается многоточием, как и задумано. */
.order-form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; max-width: 900px; }
.panel-submit { border-color: var(--border-hover); }
.panel-submit .form-actions { margin-bottom: 10px; }
.panel-submit .btn-wide { max-width: 380px; }

/* Поля внутри блока — те же правила, что и в карточке */
.panel input, .panel select, .panel textarea {
  width: 100%; padding: 11px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg-soft); color: var(--fg);
}
.panel input:focus, .panel select:focus { border-color: var(--accent); }
.panel label { display: block; margin-bottom: 6px; font-weight: 500; }
.field { margin: 0 0 16px; }
.field:last-child { margin-bottom: 0; }
/* Два поля в ряд: язык и длина клипов коротки, столбиком они разрежены */
.field-row { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.helptext-lead { display: block; margin: -6px 0 14px; }

/* Кнопка в ожидании: кружок слева от текста и приглушённый вид.
   Нажать второй раз нельзя — сама кнопка отключена скриптом. */
.btn.is-busy {
  opacity: .75; cursor: progress;
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
}
.btn.is-busy::before {
  content: ""; width: 15px; height: 15px; flex: none;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Уважаем системную настройку «меньше движения»: кружок замирает,
   но приглушённый вид кнопки остаётся — ожидание всё равно видно. */
@media (prefers-reduced-motion: reduce) {
  .btn.is-busy::before { animation: none; }
}

/* Колонка страницы: заголовок и блоки одной ширины и по центру экрана.
   Без неё заголовок растягивался во всю страницу, а содержимое жалось слева. */
.page-narrow { max-width: 900px; margin: 0 auto; }
/* В КАБИНЕТЕ УЗКАЯ СТРАНИЦА ПРИЖАТА К МЕНЮ, А НЕ К ЦЕНТРУ КОЛОНКИ (владелец
   03.09 на 4K-мониторе при 125 %: «большой промежуток между левым меню
   и блоком оформления заказа»). С 2000 px контейнер растёт до 1440,
   с 2800 — до 1800; колонка кабинета становится 1140–1500 px, и форма
   в 900 px, центрируясь в ней, отъезжала от меню на 148–328 px (снято
   браузером на 2560 и 3072). Меню и форма — одно рабочее место, пустота
   пусть остаётся справа. */
.cab-content .page-narrow { margin-left: 0; }

/* Заголовок страниц заказа — по центру колонки: он относится ко всей
   странице, а не к левому блоку, и по краю смотрелся сбитым. */
.page-narrow > h1 { text-align: center; margin-bottom: 22px; }

/* --- Ход обработки --------------------------------------------------------
   Заголовок слева, оценка остатка справа: раньше весь текст жался к левому
   краю широкого блока, а правая половина пустовала. */
.panel-progress { border-color: var(--border-hover); }
.progress-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px;
}
.progress-head .panel-title { margin: 0; }
.progress-eta {
  margin: 0; font-weight: 700; color: var(--accent); font-size: var(--fs-lg);
  white-space: nowrap;
}
/* Когда срока у нас нет, на этом месте стоит «дольше обычного». Тем же
   бодрым синим оно читалось как хорошая новость — глазами это видно
   сразу, а никакой тест такого не покажет. Извинение говорим тише
   обещания: цвет обычного текста и вес полегче. */
.progress-eta-quiet {
  color: var(--muted); font-weight: 600; font-size: var(--fs-md);
  white-space: normal;
}
/* Шаги в две колонки: их семь, и одним столбцом список тянется вниз пустой
   полосой. На узком экране колонка снова одна. */
.progress-steps { columns: 2; column-gap: 28px; }
@media (max-width: 560px) { .progress-steps { columns: 1; } }
.progress-step { break-inside: avoid; }
.panel-progress .hint { margin: 12px 0 0; }

/* --- Заглушка «обработка задерживается» -----------------------------------
   Не красная: это не ошибка клиента и не его вина, пугать незачем. Тёплая
   рамка отличает «мы чиним» от «видео не подошло». */
.panel-stalled { border-color: #d99a2b; border-left-width: 4px; }
.stalled-text { margin: 0; font-size: var(--fs-lg); line-height: 1.55; }
.panel-stalled .hint { margin: 12px 0 0; }
/* Ссылка на исходник — мелко и приглушённо: это справка, а не действие */
.split-source { font-size: var(--fs-md); margin-top: 14px; }

/* Подпись группы плиток: «Ваши» и «Готовые» */
.pick-group {
  font-size: var(--fs-sm); letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin: 4px 0 10px; font-weight: 700;
}
.pick-group + .template-pick-list { margin-bottom: 18px; }

/* Отметка выбранного: рамки мало — её не замечают с первого взгляда.

   `span.` в селекторе не для красоты. Выше, в правилах плитки, лежит
   `.template-pick-item span { display: block }` — вес (0,1,1). Голое
   `.template-pick-badge` весит (0,1,0) и проигрывает ему, сколько бы
   строк ниже ни стояло: порядок решает только при равном весе. Из-за
   этого `display: none` не срабатывал никогда, и надпись «ВЫБРАНО»
   висела на КАЖДОЙ плитке — то есть отметка выбранного не отмечала
   ничего. Замечено глазами на стенде 2026-08-11, жило и на бою.

   Комментарий, стоявший здесь раньше, боялся ровно этого исхода, но
   винил `:has()` в старых браузерах. Причина оказалась своя, соседняя
   и куда более частая. */
.template-pick-item span.template-pick-badge {
  display: none; margin-top: 6px; font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--accent);
}
/* Соседний селектор, а не :has(): тот поддерживают не все браузеры. Вес
   здесь (0,3,1) — выше и общего правила на span, и правила выше. */
.template-pick-item input:checked ~ span.template-pick-badge { display: block; }

/* --- Редактор готовых клипов ----------------------------------------------
   Устроен как редактор оформления: слева кадр предпросмотра, справа поля.
   Кадр здесь только показывает — двигать по нему нечего, поэтому ни блоков,
   ни рамок перетаскивания. */
/* Редактор занимает всю ширину страницы — ту же, что текст над ним.
   Полей много, и втискивать их в половину колонки значит превращать
   каждую строку в две. */
.clip-editor {
  /* ПРОПОРЦИЯ КЛИПА — ПО ФОРМАТУ ЗАКАЗА (A_О_478): от неё считаются
     и сцена ролика, и колонка под ней. До 29.09 обе стояли 9:16
     числом, и горизонтальный клип вписывался в вертикальную рамку. */
  --clip-k: 9 / 16;
  display: grid; gap: 28px; align-items: start; width: 100%;
  grid-template-columns: minmax(300px, 420px) minmax(0, 1fr);
}
.clip-editor[data-aspect="16_9"] { --clip-k: 16 / 9; }
.clip-editor[data-aspect="1_1"] { --clip-k: 1; }
/* Страница с проигрывателем. Ширину колонки задаёт САМ ролик: клип
   вертикальный (9:16), и колонка считается от его высоты, а не от доли
   экрана. Через долю (46vw) на широких экранах колонка раздувалась,
   ролик упирался в предел высоты, и по бокам оставались пустые поля,
   а вёрстка разъезжалась — это было видно на всём, что шире FHD.

   Высота ролика — clamp: не ниже 420 точек (иначе на ноутбуке текст
   субтитров не разобрать) и не выше 860 (иначе на большом экране
   к нижним кнопкам пришлось бы прокручивать). */
.clip-editor.has-player {
  /* ЧИСЛА ВЗЯТЫ С МАКЕТА ОТЛАДКИ, а не подобраны заново. Владелец
     настраивал правку субтитров на нём и просил «как было там» шесть раз;
     каждый раз я подбирал похожее и промахивался. Замерено с макета:

       колонка ролика   minmax(420px, 46vw)
       высота ролика    max-height: 78vh, пропорция своя
       обёртка          max-width: 1500px

     Ролику НЕ задаётся пропорция: он вписывается сам, ограниченный только
     высотой. Прежняя рамка 9:16 делала колонку узкой (484 точки на FHD) —
     это и был «размер превью такой же, как у фрейма справа». */
  /* Две колонки: ролик слева, правки справа. Одну колонку пробовали
     по просьбе владельца и вернулись обратно — решение его. */
  /* ШИРИНА КОЛОНКИ СЧИТАЕТСЯ ТОЙ ЖЕ ФОРМУЛОЙ, ЧТО И САМ РОЛИК
     (`.clip-player-stage`: min(100%, 78vh * 9/16)). Обещание «колонку
     задаёт сам ролик» стояло абзацем выше с 11.08 и НЕ выполнялось:
     колонка бралась долей экрана (46vw), а ролик — долей ВЫСОТЫ, и на
     широком экране числа расходились. Замерено 14.08 на 1920x1080:
     колонка 883, ролик 474, между ними 409 точек пустоты, а правой
     панели оставалось 541 — подписи переносились в две строки, поля
     выпирали из карточки. Владелец увидел это на бою как «превью
     по центру, а не слева» и «панель узкая, вылезает за фрейм».
     Теперь колонка равна ролику при любой высоте окна, а всё, что
     раньше уходило в пустоту, достаётся панели правок. */
  grid-template-columns: min(46vw, calc(78vh * var(--clip-k))) minmax(0, 1fr);
}
/* Панели правок нужен свой предел: карточка вокруг ограничивает её
   440 точками, и без явного значения блок остаётся узкой колонкой,
   сколько ни расширяй сетку. */
.clip-editor.has-player .clip-edit-fields { width: 100%; max-width: 900px; }
/* Ширину держит СТРАНИЦА, а не сетка внутри неё, — это была причина
   «всё разъехалось на большом экране». Арифметика: общий контейнер
   сайта 1080 точек, а колонка ролика по макету 46vw, то есть 883 точки
   на экране 1920. Правой панели оставалось около сотни, и она выпирала
   за край. В макете обёртка была 1500, поэтому там всё вставало.
   Общий --maxw не трогаем: он про весь сайт, от него же считаются
   шапка и подвал. */
.container.page-editor { max-width: 1500px; }
.clip-editor.has-player .clip-edit-stage { flex: 1 1 auto; width: 100%; }
@media (max-width: 900px) {
  /* Обе записи, и это не перестраховка. Раскладку с роликом задаёт
     `.clip-editor.has-player` — селектор СИЛЬНЕЕ, чем `.clip-editor`,
     и он побеждал здесь независимо от порядка правил. Из-за этого
     редактор на телефоне оставался двухколоночным: колонка ролика
     держала свои 420 точек (нижний предел с макета), правая панель
     сжималась в полоску по одному слову в строке, а страница ехала
     вбок на 827 точек при экране 390. Замерено и увидено глазами
     2026-08-11. Число 420 не трогаем: оно про широкий экран, где
     колонки две, и настроено владельцем. */
  .clip-editor,
  .clip-editor.has-player { grid-template-columns: minmax(0, 1fr); }
}
.clip-edit-stage { flex: 0 0 380px; max-width: 100%; }
/* min-width: 0 — не косметика, а условие прокрутки. У flex-элемента
   минимальная ширина по умолчанию равна ширине содержимого: колонка
   растягивалась под ленту планов, лента получала всю нужную ей ширину,
   и прокручиваться ей было незачем — куски просто уезжали за край страницы.
   Владелец увидел это дважды подряд, и оба раза причина была здесь. */
/* max-width: none — иначе сюда приезжает общий предел карточки (440 px),
   и блок правок остаётся узкой колонкой, сколько ни расширяй сетку вокруг.
   Именно это и было видно: сетка растянута, а карточка внутри — нет. */
.clip-edit-fields { min-width: 0; max-width: none; }
/* Заглушка на месте ещё не собранного клипа: та же плитка, но пустая
   и приглушённая. Клиент видит, сколько будет всего, не считая в уме. */
.clip-pending { opacity: .5; }
.clip-thumb-pending {
  display: flex; align-items: center; justify-content: center;
  width: 100%; aspect-ratio: 9 / 16; border-radius: 12px;
  border: 1px dashed var(--border); background: var(--surface-2);
}
.clip-thumb-pending .clip-thumb-label {
  color: var(--fg-muted); font-weight: 500; font-size: var(--fs-sm);
  text-transform: lowercase;
}
/* «собирается…» — ПО ЦЕНТРУ РАМКИ (владелец, 16.08). У готового клипа
   под рамкой стоит заголовок, и он прижат влево по делу: заголовки
   читают строкой, а длинный переносится. Здесь же под рамкой одно
   слово о состоянии — прижатое влево, оно выглядит обрезанным
   заголовком, а не подписью к пустой плитке. */
.clip-pending .clip-title { text-align: center; }

/* Метка возврата в списке покупок. Строчная и спокойная: это не тревога
   и не успех, а состояние денег — «заявлен», «одобрен», «возвращено».
   Цвет берём тот же, что у прочих «всё в порядке» состояний, чтобы
   возврат не выглядел поломкой: клиент сам его и попросил. */
.tag-refund {
  display: inline-block; margin-left: 6px; padding: 2px 8px;
  border-radius: 999px; font-size: var(--fs-xs); font-weight: 600;
  color: var(--ok-fg); background: var(--ok-bg); white-space: nowrap;
}

.clip-edit-stage { position: relative; }
.clip-edit-stage::after {
  content: attr(data-busy-text);
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  padding: 6px 12px; border-radius: 999px; font-size: var(--fs-sm);
  background: rgba(0, 0, 0, .62); color: #fff; opacity: 0;
  transition: opacity .15s; pointer-events: none;
}
.clip-edit-stage:has(.is-busy)::after { opacity: 1; }

/* min-width: 0 обязателен И ЗДЕСЬ. `fieldset` — особый случай: у него своя
   минимальная ширина по содержимому, и она не снимается ни `min-width`
   у родителя, ни `flex`. Без этой строки лента внутри группы раздвигала
   колонку, и прокручиваться ей было незачем: куски уезжали за край.
   Владелец сообщал об этом трижды; первые два раза я чинил не то место. */
.clip-edit-group { border: 0; padding: 0; margin: 0 0 22px; min-width: 0; }
.clip-edit-group legend { padding: 0; margin: 0 0 8px; font-weight: 600; }

/* Лента планов: из чего склеен клип. Ширина куска — его длительность,
   иначе полусекундный план выглядел бы наравне с тридцатисекундным
   и клиент правил бы не то, что видит. */
/* Webkit прячет полосу иначе, чем Firefox, — рисуем свою и там. */
/* Стоп-кадр точки разреза: маленький, потому что отвечает на один вопрос
   («что в том месте»), а не заменяет предпросмотр. Пока картинка едет,
   держим её место рамкой — иначе кнопка разреза прыгает под курсором
   в момент, когда на неё целятся. */
.clip-split-peek {
  display: flex; align-items: center; gap: 12px;
  margin: 12px 0 4px; color: var(--muted); font-size: var(--fs-md);
}
.clip-split-peek img {
  width: 120px; height: 68px; object-fit: cover; flex: none;
  border-radius: 6px; background: var(--bg-soft);
  border: 1px solid var(--border);
}
.clip-split-peek b { color: var(--text); font-weight: 600; margin-left: 6px; }
/* Кадр не приехал даже после повторов (сервер занят сборкой других).
   Пустой кусок молчит, и клиент решает, что сломалась сборка, — поэтому
   на нём написано, что картинки нет, а сам кусок остаётся рабочим. */
/* Убранный план остаётся на ленте: вернуть его клиент должен уметь,
   а исчезнувший кусок выглядел бы как потеря, а не как правка. */

/* Ручка склейки. Тянут её, а не сам план: двигается граница между двумя
   соседями, и обе стороны меняются разом. */

.clip-plan-panel {
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2);
}
.clip-plan-title { margin: 0 0 6px; font-weight: 600; }
/* Панель правит ВЫБРАННЫЙ план, а ниже на странице идут настройки всего
   клипа — их легко перепутать, там снова встречается слово «кадр».
   Поэтому у панели своя шапка, а раздел клипа отделён чертой и назван. */
.clip-plan-head {
  margin: 0 0 8px; font-size: var(--fs-sm); text-transform: uppercase;
  letter-spacing: .04em; color: var(--fg-muted);
}
.clip-plan-span { display: flex; justify-content: space-between; gap: 10px; margin: 12px 0 4px; }
.clip-plan-modes {
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin: 0 0 12px;
}
.clip-plan-modes label { display: flex; align-items: center; gap: 6px; }
/* Панель — простой столбец. Раскладка по колонкам с ручным `grid-column`
   разбросала элементы: у панели разное число блоков в разных режимах
   (у двух этажей рамок две, у убранного плана рамки нет вовсе), и любое
   жёсткое расписание мест разъезжается на первом же таком случае. */
.clip-plan-panel { display: block; }

/* Рамка кадра: показываем целый кадр исходника, поверх — окно 9:16.
   Именно оно и станет кадром клипа, поэтому рамка не «подсказка», а сам
   орган управления: её видно всегда, а не по наведению. */
/* overflow: hidden обязателен. Затемнение вокруг рамки сделано тенью
   с разлётом в 9999 пикселей — без обрезки она красит серым всё вокруг,
   включая соседние поля и предпросмотр. Владелец увидел это как «область
   экрана становится серой при выборе сцены». */
/* Кадр лежит ПОВЕРХ ролика: правка видна там же, где идёт клип, и глазу
/* Кадр результата поверх ролика: «как будет выглядеть клип» на том же
   месте и в том же размере, где только что шло видео. Вертикальный,
   поэтому в рамку ложится ровно — в отличие от кадра исходника, который
   горизонтальный и здесь смотрелся обрезанным. */
.clip-result-big {
  position: absolute; inset: 0; z-index: 2;
  width: 100%; height: 100%; object-fit: contain;
  background: #000; transition: opacity .15s;
}
.clip-result-big.is-waiting { opacity: .5; }

/* Надпись на время сборки ролика: он собирается по требованию, и молчащий
   постер выглядит замершим. Поверх кадра, по центру. */
.clip-player-wait {
  position: absolute; inset: 0; margin: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .55); color: #fff; font-size: var(--fs-base);
  pointer-events: none;
}

/* Кадр исходника с рамкой кадрирования — в колонке правок, в СВОЁМ
   формате. Поверх ролика он лежать не может: ролик вертикальный (9:16),
   исходник горизонтальный, и рамка на нём оказывалась то поверх чёрных
   полей, то обрезанной. */
.clip-frame-pick {
  position: relative; margin: 0 0 8px; touch-action: none;
  overflow: hidden; border-radius: 8px;
  /* Кадр исходника горизонтальный: во всю ширину широкой колонки он занял
     бы пол-экрана, а рамку по нему всё равно ставят мышью. */
  max-width: 560px;
}
.clip-frame-pick img {
  width: 100%; display: block; border-radius: 8px; border: 1px solid var(--border);
}
.clip-frame-box {
  position: absolute; box-sizing: border-box; cursor: move;
  border: 2px solid var(--accent); border-radius: 4px;
  z-index: 1;
}
/* Выбранная рамка лежит ПОВЕРХ. Рамки перекрываются, и соседка забирала
   себе потяг под собой: у первой не менялась правая граница, потому что
   там, где надо было хватать, лежала вторая. */
.clip-frame-box.is-active { z-index: 3; }
/* Затемнение вокруг — только у одиночной рамки. У двух этажей рамок две,
   и две тени накрыли бы друг друга: под верхней нижняя стала бы тёмной,
   хотя обе одинаково в кадре. */
.clip-frame-pick:not(.has-two) .clip-frame-box {
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .38);
}
/* Потяги за края: владелец жал на границу и ничего не происходило — рамку
   можно было только поставить, но не изменить. Полоски широкие (10 px),
   мышью попасть в двухпиксельную границу нельзя. */
.clip-frame-grip { position: absolute; }
.clip-frame-grip.is-n { left: 0; right: 0; top: -5px; height: 10px; cursor: ns-resize; }
.clip-frame-grip.is-s { left: 0; right: 0; bottom: -5px; height: 10px; cursor: ns-resize; }
.clip-frame-grip.is-w { top: 0; bottom: 0; left: -5px; width: 10px; cursor: ew-resize; }
.clip-frame-grip.is-e { top: 0; bottom: 0; right: -5px; width: 10px; cursor: ew-resize; }
/* Номер этажа: две одинаковые рамки на одном кадре иначе не различить,
   а верхняя станет верхним этажом клипа. */
.clip-frame-mark {
  position: absolute; left: 3px; top: 3px; padding: 0 6px;
  border-radius: 999px; background: var(--fill); color: #fff;
  font-size: var(--fs-xs); line-height: 18px;
}
/* Какая рамка сейчас правится. Зум и кнопки действуют только на неё,
   значит клиент обязан видеть, на какую именно, — иначе он жмёт «+»
   и не понимает, почему меняется не тот этаж. Невыбранная приглушена,
   но не спрятана: она тоже часть кадра. */
.clip-frame-pick.has-two .clip-frame-box { border-color: var(--fg-muted); opacity: .65; }
.clip-frame-pick.has-two .clip-frame-box.is-active {
  border-color: var(--accent); opacity: 1;
  box-shadow: 0 0 0 2px rgba(79, 140, 255, .35);
}
.clip-frame-pick.has-two .clip-frame-box .clip-frame-mark { background: var(--fg-muted); }
.clip-frame-pick.has-two .clip-frame-box.is-active .clip-frame-mark {
  background: var(--accent);
}
.clip-frame-zoom { display: flex; gap: 8px; margin: 0 0 6px; }
.clip-frame-zoom button {
  /* Тоже цель под палец, и мельче всех прочих: 34 px (A_Д_155_В).
     Замером редактора не ловилась — кнопки масштаба живут вне
     `form.clip-editor`, и проверка их просто не видела. Нашлась
     пробой на само число. */
  min-width: var(--palec); min-height: var(--palec);
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); font-size: var(--fs-md); line-height: 26px;
}

/* Вырезание фраз по тексту. Кнопка у слова видна ВСЕГДА, и прячется
   только там, где есть наведение мышью.
   
   Прежде было наоборот — спрятана до наведения, — и на телефоне вырезание
   становилось недоступно вовсе: наведения там нет. Причём это самый
   «телефонный» способ правки из всех: читаешь расшифровку и тыкаешь
   в лишнее.
   
   Различаем по СПОСОБУ ВВОДА (`hover`/`pointer`), а не по ширине экрана:
   планшет с мышью и ноутбук с сенсором ломают привязку к ширине, а вопрос
   здесь ровно один — может ли этот человек навести курсор. */
.clip-word { position: relative; }
.clip-word-cut {
  position: absolute; top: -7px; right: -5px;
  width: 16px; height: 16px; padding: 0; line-height: 14px;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--surface); color: var(--fg-muted);
  font-size: 12px; cursor: pointer; opacity: 1; transition: opacity .12s;
}
@media (hover: hover) and (pointer: fine) {
  /* Мышь: кнопка у каждого слова превратила бы расшифровку в частокол,
     поэтому показываем её по наведению — как и было. */
  .clip-word-cut { opacity: 0; }
  .clip-word:hover .clip-word-cut,
  .clip-word-cut:focus-visible { opacity: 1; }
}
@media (hover: none) {
  /* Палец — это 44 точки, а не 16. Кнопку не раздуваем, а расширяем
     область попадания невидимой рамкой вокруг неё. */
  .clip-word-cut { width: 22px; height: 22px; line-height: 20px; font-size: 15px; }
  .clip-frame-grip.is-n, .clip-frame-grip.is-s { height: 26px; }
  .clip-frame-grip.is-n { top: -13px; }
  .clip-frame-grip.is-s { bottom: -13px; }
  .clip-frame-grip.is-w, .clip-frame-grip.is-e { width: 26px; }
  .clip-frame-grip.is-w { left: -13px; }
  .clip-frame-grip.is-e { right: -13px; }
  .clip-frame-zoom button { min-width: 46px; line-height: 40px; }
  .clip-plan-seam { flex-basis: 18px; }
}
/* Вырезанное слово остаётся на месте зачёркнутым: вернуть его клиент должен
   уметь, а исчезнувший текст выглядел бы как потеря расшифровки. */
.clip-word.is-cut input {
  text-decoration: line-through; opacity: .45;
}
.clip-word.is-cut .clip-word-cut { opacity: 1; color: var(--danger, #c0392b); }

/* Подпись слева, ползунок во всю оставшуюся ширину: так две границы
   подрезки стоят друг под другом и их видно как одну шкалу. */
/* Подпись, сам орган управления и его значение — в одну строку и по
   колонкам: подписи выровнены между собой, ползунки начинаются с одного
   места. Вразнобой это читалось как список несвязанных вещей. */
.clip-edit-slider {
  /* Подпись по своей ширине, ползунок — на весь остаток. Раньше под
     подпись отводилась колонка до 200 точек, и короткое слово («Сдвиг»)
     оставляло между текстом и ползунком пустую половину строки.
     `max-content` убирает эту дыру: ползунок начинается сразу за текстом. */
  display: grid; align-items: center; gap: 12px; margin: 0 0 10px;
  grid-template-columns: max-content minmax(0, 1fr) auto;
}
.clip-edit-slider span {
  color: var(--fg-muted); font-size: var(--fs-md); white-space: nowrap;
}
.clip-edit-slider input[type="range"] { width: 100%; }
.clip-edit-slider input[type="range"] { flex: 1 1 auto; min-width: 0; }
/* ПОЛЗУНОК — ТОЖЕ ЦЕЛЬ ПОД ПАЛЕЦ (A_Д_155_В). Он выходил 40 px в высоту:
   на 4 меньше нормы, и промах по нему хуже промаха по кнопке — палец
   попадает в полосу, ползунок прыгает, а клиент этого не заказывал.
   Растёт только область касания: сама дорожка и бегунок те же, вид
   не меняется. */
.clip-edit-slider input[type="range"] { min-height: var(--palec); }

/* Галка монтажа: цель под палец целиком — клиент попадает по подписи,
   а не по квадратику в 13 точек (то же правило, что у ползунка). */
.clip-edit-check {
  display: flex; align-items: center; gap: 10px; margin: 0 0 10px;
  min-height: var(--palec); cursor: pointer;
}
.clip-edit-check span { color: var(--fg); font-size: var(--fs-md); }
.clip-edit-check input[type="checkbox"] { width: 20px; height: 20px; }

/* Выбор режима кадрирования: кружок и подпись рядом, все варианты
   в одну строку. Django печатает RadioSelect блоками `<div>` — по варианту
   на строку, — и четыре режима занимали четыре строки, выглядя длиннее
   всего остального в колонке. Селектор по `id` поля, а не по классу:
   разметку рисует сам виджет, класса на обёртке у него нет. */
#id_edit_mode {
  display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 0 0 12px;
}
#id_edit_mode > div { margin: 0; }
#id_edit_mode label {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
}

/* Длительность после подрезки. Цифры моноширинные: иначе строка дёргается
   при каждом движении ползунка. */
.clip-edit-range {
  margin: 0 0 10px; font-variant-numeric: tabular-nums; font-size: var(--fs-base);
}

/* Слова субтитров: каждое — своё поле, ширина по содержимому. Строкой,
   как в тексте, чтобы фразу можно было читать глазами. */
.clip-words { display: flex; flex-wrap: wrap; gap: 6px; }
.clip-word input {
  /* Явные цвета и рамка: поля стоят на подложке списка слов, и без них
     поле неотличимо от фона — владелец решил, что оно потерялось. */
  width: auto; min-width: 60px; padding: 4px 8px; font-size: var(--fs-md);
  field-sizing: content;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
}
.clip-word input:focus { border-color: var(--accent); outline: none; }
/* Уже исправленное слово подсвечено: в длинной расшифровке иначе
   не найти, что ты успел поправить. */
.clip-word.is-changed input { border-color: var(--accent); }

/* --- Проигрыватель клипа в редакторе ---
   ПРАВИЛА СКОПИРОВАНЫ С МАКЕТА ОТЛАДКИ (`scratchpad/demo/index.html`),
   на котором владелец настраивал правку субтитров. Числа и цвета взяты
   оттуда как есть, а не подобраны похожими: подбирал я четыре раза
   и четыре раза промахивался.

   Второе, что здесь важно, — ИЗОЛЯЦИЯ ОТ СТРАНИЦЫ. Макет был отдельным
   файлом без наших общих стилей, а здесь блок стоит внутри карточки,
   и она навязывает ему своё: выравнивание по центру (отсюда кнопки,
   уехавшие в середину), свои цвета кнопок и полей. Поэтому каждое
   правило ниже задаёт значение явно, даже там, где оно кажется
   умолчанием. */
/* Обёртка ролика в редакторе. Имя своё: `clip-player` в витрине носит сам
   <video>, и пропорция 9:16 с чёрным фоном там нужна. Здесь под тем же
   именем стоял <div> вокруг кадра, полос и подсказок — он брал ту же
   рамку и оставлял под роликом чёрное поле высотой около 180 точек.
   Владелец это поле уже показывал; тогда ужали внутреннюю сцену
   (.clip-player-stage), а внешняя коробка сохранила пропорцию, и поле
   вернулось. Одно имя на две разные вещи — оно и держало ошибку. */
.clip-editor-player { margin-top: 0; width: 100%; text-align: left; }

.clip-player-stage {
  position: relative; background: #000; border-radius: 8px; overflow: hidden;
  /* Пропорция ДО загрузки видео. Ролик тяжёлый и тянется только по первому
     щелчку, а пустой `<video>` имеет нулевую высоту — рамка схлопывалась,
     и всё, что в ней абсолютным слоем, оказывалось прижатым к верху окна:
     кнопки «съехали вверх», субтитр показывался почти под шапкой.
     Это же лечит и скачок вёрстки при первом кадре.

     Ширину ограничиваем так, чтобы рамка УМЕЩАЛАСЬ по высоте.
     Иначе она считается от ширины колонки, выходит выше своего предела,
     и под роликом остаётся чёрное поле — то самое, которое владелец видел
     ниже подписи «Остановите там, где…». */
  aspect-ratio: var(--clip-k); width: min(100%, calc(78vh * var(--clip-k)));
  margin-inline: auto;
}
/* РАМКУ ДЕРЖИТ ПРОПОРЦИЯ, А НЕ РАЗМЕР РОЛИКА. Пробовал `fit-content` —
   рамка облегала ролик, но при незагруженном видео её высота считалась
   от натурального размера файла (1920 точек), и фрейм растягивался вниз.
   Владелец видел это как «фрейм уехал вниз».
   Пропорция задана и до, и после загрузки — формата заказа (`--clip-k`,
   A_О_478), рендер приводит к нему любой исходник. Ролик внутри
   вписывается целиком. */
.clip-player-stage video {
  /* `object-fit: contain` и для постера тоже: стоп-кадр клипа уже
     в формате заказа, вписывается ровно. */
  display: block; width: 100%; height: 100%; object-fit: contain;
}

/* Текущее слово поверх кадра. Клип собран БЕЗ вжжённых субтитров:
   иначе поверх готового текста лёг бы второй, и клиент не понял бы,
   какой из них правит. */
.clip-player-line {
  position: absolute; left: 0; right: 0; bottom: 7%; text-align: center;
  pointer-events: none; padding: 0 6%;
}
.clip-player-line span {
  display: inline-block; color: #fff;
  font: 800 clamp(18px, 2.6vw, 38px)/1.2 system-ui, sans-serif;
  text-shadow: 0 0 6px #000, 0 3px 0 #000, 0 0 20px rgba(0, 0, 0, .95);
}

/* Какая сцена идёт сейчас — подписью у полосы сцен. На ролике она
   закрывала кадр сверху и читалась как замена картинке. */
.clip-scene-now {
  /* Та же беда, что у подписи выше: #c6ccd8 на светлом фоне 1,36.
     var(--muted) — 7,42 и 5,18. */
  font-weight: 400; color: var(--muted); font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

/* Полоса проигрывания: щелчок — переход, риски это слова. Своя тёмная
   подложка, а не фон страницы: на светлой теме бледные риски на бледном
   фоне сливались — «полоски не читаются». */
.clip-player-bar {
  position: relative; height: 34px; margin-top: 10px; cursor: pointer;
  background: #0f1116; border: 1px solid #2a2f3a; border-radius: 8px;
  overflow: hidden;
}
.clip-player-done {
  position: absolute; left: 0; top: 0; bottom: 0; background: #243040;
}
.clip-player-tick {
  position: absolute; top: 6px; height: 10px; background: #3f4a5a;
  border-radius: 2px;
}
.clip-player-tick.is-said { background: #4d5769; }
.clip-player-tick.is-playing { background: #ffd23f; }
.clip-player-tick.is-shifted { background: #4ade80; }
.clip-player-tick.is-changed { background: #7dd3fc; }
.clip-player-head {
  position: absolute; top: 0; bottom: 0; width: 2px; background: #fff;
  box-shadow: 0 0 6px rgba(255, 255, 255, .8);
}
/* Шкала секунд вдоль полосы: без неё «остановить на нужном месте»
   приходится делать наугад. */
.clip-player-scale {
  position: absolute; left: 0; right: 0; bottom: 1px; height: 12px;
  color: #8b93a3; font-size: 10px; pointer-events: none;
}
.clip-player-scale i {
  position: absolute; font-style: normal; transform: translateX(-50%);
}

/* Подпись над полосой: без неё две полосы под видео читаются как одна
   мешанина, и непонятно, где сцены, а где слова. */
.clip-bar-label {
  /* ТОКЕНАМИ ТЕМЫ, А НЕ БЕЛЫМ (владелец 25.08: «это почти не читается
     в редакторе, нужно поменять цвет текста»).

     Белое стояло здесь с 2026-08-10 по верной тогда причине: подписи
     лежали на тёмной подложке под роликом. Подложка с тех пор осталась
     у самой полосы (`.clip-player-bar`), а подписи выехали на фон
     страницы — и в светлой теме получилось белое по светлому.
     Замерено: #fff на #e7ecf4 даёт 1,19 при норме 4,5, подсказка
     #c6ccd8 — 1,36, число сцен #ffd23f — 1,22. То есть не «бледно»,
     а нечитаемо совсем.

     Токены дают 15,51 в светлой теме и 17,98 в тёмной, и — главное —
     больше не расходятся с фоном при следующей правке раскладки. */
  margin: 10px 0 4px; font-size: var(--fs-sm); font-weight: 600;
  display: flex; align-items: baseline; gap: 8px; text-align: left;
  color: var(--fg-strong);
}
/* Подсказка: var(--muted) — 7,42 в тёмной теме, 5,18 в светлой. */
.clip-bar-label .hint { font-weight: 400; color: var(--muted); }
/* Сколько сцен нашлось. Не украшение: по этому числу сразу видно,
   пришли ли данные от ленты планов, — а «полоса пустая» и «данных нет»
   выглядят одинаково. */
.clip-bar-count {
  /* Число должно выделяться, но не ценой читаемости: жёлтое #ffd23f
     на светлом фоне давало 1,22. Акцент темы — 5,95 и 4,87. */
  font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums;
}

/* Полоса сцен — над полосой слов: сцена крупнее слова, и читать удобнее
   сверху вниз, от кусков к словам. */
.clip-scenes { height: 30px; }
.clip-scene-tick {
  /* Сцены идут встык, поэтому между ними нужна видимая граница: без неё
     полоса читается как одна сплошная заливка, и «сцен не видно». */
  position: absolute; top: 3px; bottom: 3px; border-radius: 3px;
  background: #4a586e; cursor: pointer;
  border: 1px solid #0f1116; box-shadow: inset 0 0 0 1px #5d6b82;
}
.clip-scene-tick:hover { background: #5a677c; }
.clip-scene-tick.is-edited { background: #4ade80; }
.clip-scene-tick.is-chosen { border-color: #7dd3fc; }
.clip-scene-tick.is-dropped { background: repeating-linear-gradient(
  45deg, #3f4a5a, #3f4a5a 3px, transparent 3px, transparent 6px); }

/* Кнопки перемотки. `justify-content: flex-start` и `text-align: left`
   не украшение: карточка вокруг центрирует содержимое, и без них ряд
   кнопок уезжал в середину ролика — владелец видел это дважды. */
/* Кнопки по центру, время справа. Раньше всё жалось влево одной кучей:
   кнопки и цифры читались как один блок, и глазу не за что было
   зацепиться (замечание владельца 2026-08-10). */
.clip-player-controls {
  display: flex; align-items: center; justify-content: center;
  gap: 6px; margin-top: 10px; flex-wrap: wrap; text-align: left;
}
.clip-player-clock {
  /* `auto` слева отталкивает время к правому краю, а кнопки остаются
     по центру всей полосы. */
  margin-left: auto;
  color: var(--text-dim); font-size: 13px; font-variant-numeric: tabular-nums;
}
/* Подпись режима перемотки — слева, чтобы центр оставался за кнопками. */
#clipMode { margin-right: auto; order: -1; }
.clip-player-clock b { color: var(--text); font-weight: 600; }
.btn-step {
  background: #252a34; color: #e7eaf0; border: 1px solid #2a2f3a;
  border-radius: 8px; padding: 9px 12px; font-size: 16px; min-width: 56px;
  cursor: pointer; user-select: none; -webkit-user-select: none;
  touch-action: manipulation;
}
.btn-step:hover { border-color: #3d4658; }
.btn-step:active, .btn-step.is-held { background: #3a4454; }
.btn-step:disabled { opacity: .4; cursor: default; }
/* Кнопка «Играть» выделена цветом, как на макете: она главная в ряду.
   Ширина как у соседних: со значком вместо слова ей больше не нужно
   места под самую длинную подпись. */
.btn-play { background: #2b3a2f; border-color: #3c5a44; }
/* Значки перемотки и воспроизведения одного размера: иначе ряд выглядит
   собранным из разных наборов. */
.btn-step { font-size: 16px; line-height: 1.2; }

/* Слово, которое звучит сейчас, — и оно же показано в кадре. Приглушённое
   означает «уже отзвучало, речь в паузе»: без этого подсветка гасла бы
   в каждой паузе, а их в клипе больше трети времени.
   Цвета — с макета: жёлтый для звучащего, зелёная рамка для сдвинутого,
   голубая для выбранного. */
.clip-word.is-playing input { background: #ffd23f; color: #14161a; font-weight: 700; }
.clip-word.is-playing.is-dim input { background: rgba(255, 210, 63, .45); }
.clip-word.is-picked input { border-color: #7dd3fc; outline: 2px solid #7dd3fc; }
.clip-word.is-shifted input { border-color: #4ade80; }

/* Кнопки «начало здесь» и «конец здесь» — рядом со словами, а не в общем
   ряду: они относятся к выбранному слову, а не ко всему клипу. */
/* Пока кадр собирается ffmpeg-ом, на его месте стоит надпись, а не пустота:
   сборка идёт до пары секунд, и пустое место читается как поломка. */
.clip-frame-pick { position: relative; min-height: 120px; }
.clip-frame-wait {
  position: absolute; inset: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-soft); border: 1px dashed var(--border);
  border-radius: var(--radius-sm); color: var(--text-dim);
  font-size: var(--fs-sm);
}
#clipFrameSource.is-waiting { opacity: .25; }

/* Закладки колонки правок: «Настройки клипа» и «Правка субтитров».
   Разные занятия — первое делается один раз перед сборкой, второе
   по проигрывателю, слово за словом, — и рядом они мешали друг другу. */
.clip-tabs {
  display: flex; gap: 4px; margin: 0 0 18px;
  border-bottom: 1px solid var(--border);
}
.clip-tab {
  padding: 10px 16px; border: 0; background: none; cursor: pointer;
  color: var(--text-dim); font-size: var(--fs-base); font-weight: 600;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
/* Вырезанное слово в строке предпросмотра: зачёркнуто и приглушено.
   Клиент нажал крестик — строка обязана показать, что этого куска
   в клипе не будет, иначе вырез выглядит несработавшим. */
#clipLine.is-cut { text-decoration: line-through; opacity: .45; }

.clip-tab:hover { color: var(--text); }
/* На узком экране три вкладки в строку не помещаются: на 320 точках
   они вылезали за край на 59 точек, и до третьей («Правка субтитров»)
   было не дотянуться. Переносим на вторую строку и ужимаем отступы —
   так все три видны сразу, без прокрутки вбок. Прокрутку вкладок
   не берём намеренно: спрятанная вкладка выглядит как отсутствующая,
   а тут за ней половина возможностей редактора. */
@media (max-width: 380px) {
  .clip-tabs { flex-wrap: wrap; gap: 0; }
  .clip-tab { padding: 10px 10px; }
}
.clip-tab.is-current { color: var(--text); border-bottom-color: var(--accent); }

/* Кнопки формы: значок рядом с подписью, а не вместо неё. Дискета
   узнаётся не всеми, а слово без значка теряется среди прочих кнопок
   страницы. */
/* wrap: на 320 точках «Сохранить и пересобрать» и «Отменить» в строку
   не влезают, и «Отменить» уезжало за край экрана — уходит на вторую
   строку. На широком экране обе по-прежнему рядом. */
.clip-form-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* «СОХРАНИТЬ» ПОД РУКОЙ, ПОКА ИДЁТ ПРАВКА (A_Д_154_В).

   Замер 18.08 на 375x667: до кнопки 2427 px — 3,6 экрана при потолке 3,0.
   Дорога длинная не по недосмотру: над кнопкой лежат сам ролик (767 px)
   и панель полей (1358 px), и резать там нечего — это и есть предмет
   правки. А проходят её многократно: клиент правит, смотрит, правит
   снова.

   Поэтому не укорачиваем страницу, а ДАЁМ КНОПКЕ ДОГНАТЬ ЧЕЛОВЕКА:
   пока он внутри панели полей, строка кнопок прилипает к низу окна.
   Липким здесь оно заслуженно — в отличие от шапки, которой при правке
   не пользуются вовсе (A_Д_153_В).

   `sticky`, а не `fixed`, и это не вкус: `fixed` висел бы и над роликом,
   где кнопка не нужна, и налезал бы на баннер cookies (`.cookie-bar`,
   тоже прижат к низу). Липкость внутри панели включается ровно тогда,
   когда панель на экране.

   Полоса непрозрачная и во всю ширину карточки (отрицательные поля
   гасят её отступы): полупрозрачная поверх текста читается как сбой
   отрисовки. Отступ снизу — на панель, иначе последнее поле прячется
   под кнопкой ровно в тот момент, когда его правят. */
@media (max-width: 560px) {
  .body-editor .clip-form-actions {
    position: sticky; bottom: 0; z-index: 40;
    margin: 0 -26px -26px; padding: 10px 16px;
    background: var(--surface);
    border-top: 1px solid var(--border);
    /* В ОДИН РЯД — иначе липкая полоса съедает 143 px (21% окна), то есть
       ровно столько же, сколько шапка, которую мы отсюда убрали. Перенос
       ставил «Отменить» вторым рядом. В один ряд полоса 79 px (12%),
       и обе кнопки остаются крупнее пальца: 52 px при норме 44. */
    flex-wrap: nowrap; gap: 8px;
  }
  /* Подпись кнопки в две строки — это нормально: высоту держит полоса,
     а не текст. Обрезать текст многоточием нельзя, «Сохранить и…»
     не говорит, что произойдёт. */
  .body-editor .clip-form-actions .btn { flex: 1 1 auto; }
  .body-editor .clip-form-actions .btn-cancel { flex: 0 0 auto; }
  .body-editor .clip-edit-fields { padding-bottom: 12px; }
}
.clip-form-actions .btn { display: inline-flex; align-items: center; gap: 8px; }
.btn-icon { font-size: 1.1em; line-height: 1; }
.btn-cancel {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 13px 20px; border-radius: 12px; font-weight: 600;
  border: 1px solid var(--border); color: var(--text-dim);
  background: var(--surface); text-decoration: none;
}
.btn-cancel:hover { color: var(--text); border-color: var(--text-dim); }

/* Блок правки субтитров под роликом — такая же карточка, как колонка
   настроек справа: это самостоятельный инструмент, а не хвост
   проигрывателя. Без оформления он читался как продолжение кнопок
   перемотки и терялся (слово владельца 2026-08-09). */
/* Слова в рамке с прокруткой: у боевого клипа их под сотню, и без
   предела высоты страница уезжает вниз на целый экран. */
.clip-tab-panel .clip-words {
  max-height: 40vh; overflow: auto; padding: 8px;
  background: var(--bg-soft); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.clip-subtitle-time {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 10px 0; padding: 8px 10px; background: var(--bg-soft);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.clip-picked { font-size: var(--fs-sm); color: var(--text-dim); }
.clip-track-shift { margin-top: 12px; }
.clip-track-shift label { display: block; font-size: var(--fs-sm); }

/* Шкала над ползунком сдвига субтитров.
   Без неё ползунок сообщает только «сдвинуто примерно вправо»: на глаз
   секунды не отмерить, а сдвиг — это про доли секунды (владелец, 14.08).
   Засечки ставит скрипт, числа берёт из min/max самого поля. */
.clip-shift-line { position: relative; padding-top: 22px; }
.clip-shift-scale {
  position: absolute; left: 0; right: 0; top: 0; height: 20px;
  pointer-events: none;
}
.clip-shift-tick {
  position: absolute; bottom: 0; width: 1px; height: 5px;
  background: var(--border-hover);
}
/* Крупная засечка выше и с числом: секунды должны читаться, а половинки
   между ними — только чувствоваться, иначе шкала превращается в частокол. */
.clip-shift-tick.is-major { height: 9px; background: var(--muted); }
.clip-shift-tick i {
  position: absolute; bottom: 9px; left: 50%; transform: translateX(-50%);
  font-style: normal; font-size: 11px; line-height: 1;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
/* Текущее значение — рядом с ползунком и всегда на месте: подпись,
   появляющаяся только при сдвиге, прыгала бы вместе с версткой. */
.clip-shift-value {
  display: block; margin-top: 4px; font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums; color: var(--text);
}
.clip-shift-value.is-zero { color: var(--muted); }

/* --- Баннер согласия на cookies ---
   Прижат к низу, но не модальный: перекрывать содержимое ради согласия
   на счётчик посещаемости — перебор, читать оферту он мешать не должен. */
.cookie-bar {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 60;
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
  max-width: 860px; margin: 0 auto; padding: 10px 14px;
  background: var(--bg-soft); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.cookie-bar[hidden] { display: none; }
/* Одна строка вместо абзаца: полоса стала узкой (решение владельца
   14.08 по замеру D — было 346 точек на телефоне, 41% экрана, и обе
   кнопки первого экрана оказывались закрыты). Текст переносится,
   а не обрезается: казахская строка длиннее русской. */
.cookie-bar-text {
  flex: 1 1 340px; min-width: 0; margin: 0;
  color: var(--muted); font-size: var(--fs-md); line-height: 1.4;
}
.cookie-bar-text a { font-size: inherit; white-space: nowrap; }
/* Кнопки в строку, но на узком экране растягиваются на всю ширину:
   иначе «Только необходимые» не влезает и обрезается. */
.cookie-bar-actions { display: flex; flex-wrap: wrap; gap: 10px; flex: 0 0 auto; }
.cookie-bar-actions .btn, .cookie-bar-actions .btn-small { white-space: nowrap; }
@media (max-width: 560px) {
  /* Кнопки СТРОКОЙ, а не каждая на всю ширину: три этажа (текст + две
     кнопки) давали 142 точки, то есть 17% экрана вместо обещанных 12%.
     Замерено на 390 px, а не прикинуто. */
  .cookie-bar { left: 8px; right: 8px; bottom: 8px; padding: 10px 12px; gap: 8px 10px; }
  .cookie-bar-text { flex: 1 1 100%; font-size: var(--fs-sm); line-height: 1.35; }
  .cookie-bar-actions { width: 100%; flex-wrap: nowrap; }
  .cookie-bar-actions .btn, .cookie-bar-actions .btn-small {
    flex: 1 1 0; text-align: center; padding: 8px 10px;
    font-size: var(--fs-sm); white-space: normal;
  }
}

/* --- Своя очередь клиента -------------------------------------------------
   Заказ — карточка-ячейка: клиент выбирает её и переставляет мышью или
   стрелками. Прежний вид был строкой со стрелками у каждой, и владелец
   назвал его примитивным — по делу: список из шести строк с шестью парами
   стрелок читается как таблица настроек, а не как очередь. */
.queue { margin: 0 0 26px; }
/* Заголовок и обещание письма — ПО ОБЩЕЙ БАЗОВОЙ ЛИНИИ (владелец 27.08:
   «текст после „В работе“ стоит выше»). При выравнивании по центру
   строка с собственным нижним отступом поднималась над заголовком:
   центрируется коробка, а видно текст. */
.queue-top { display: flex; align-items: baseline; gap: 12px; margin: 0 0 12px; flex-wrap: wrap; }
.queue-top .queue-hint { margin: 0; }
.queue-head { margin: 0; font-size: var(--fs-lg); }
.queue-tools { display: flex; gap: 6px; margin-left: auto; }
.queue-btn {
  width: 34px; height: 34px; padding: 0; line-height: 32px;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--fg); cursor: pointer;
  transition: border-color .12s, background .12s;
}
.queue-btn:hover { border-color: var(--accent); background: var(--surface-2); }
@media (hover: none) { .queue-btn { width: 44px; height: 44px; line-height: 42px; } }

.queue-grid { margin: 0; padding: 0; list-style: none; display: grid; gap: 8px; }
.queue-cell {
  /* Flex, а не grid с фиксированными колонками: у строки переменный набор
     элементов — галочка и ручка перетаскивания есть только у ждущих,
     обложка только там, где она скачана. Фиксированная сетка ставила
     такие строки в разные колонки, и столбцы «плыли» от строки к строке. */
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 14px; border-radius: 12px;
  border: 1px solid var(--border);
  /* Блок ТЕМНЕЕ промежутка между блоками: светлые полосы на тёмном фоне
     читались наоборот — как будто залиты промежутки. Цвет живёт токеном
     `--queue-cell`, потому что «темнее фона» в тёмной и светлой теме —
     это два разных цвета, а не одна прозрачность. */
  background: var(--queue-cell);
  transition: border-color .12s, box-shadow .12s, transform .12s;
}
/* Идущий заказ — тем же блоком, но чуть светлее: он не в очереди, а в работе. */
.queue-cell.is-running { background: var(--surface); border-style: dashed; }
/* Галочка выбора: щелчок по самой карточке ничего не открывает — открыть
   заказ можно стрелкой справа. Прежде названием была ссылка во всю ширину,
   и любое нажатие уводило со страницы: ни выбрать, ни потянуть. */
.queue-pick { display: flex; align-items: center; }
.queue-check { width: 18px; height: 18px; cursor: pointer; }
/* ЗНАЧКИ СТРОКИ — ОДНОГО ВИДА И РАЗМЕРА (образец владельца 27.08,
   ~/Desktop/skriny/«В работе.png»): карандаш, отмена и переход стояли
   тремя разными вещами — svg-кнопкой, текстовым крестиком и стрелкой
   в рамке. Ряд читался как случайный набор. */
.queue-open {
  width: 28px; height: 28px; display: inline-flex;
  align-items: center; justify-content: center;
  border-radius: 8px; color: var(--muted); text-decoration: none;
}
.queue-open:hover { background: var(--bg-soft); }
.queue-cell .order-edit,
.queue-cell .queue-drop-btn,
.queue-open {
  width: 32px; height: 32px; padding: 0; border: 0; background: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 8px; color: var(--muted);
}
.queue-cell .order-edit:hover,
.queue-cell .queue-drop-btn:hover,
.queue-open:hover { background: var(--bg-soft); color: var(--fg); }
.queue-cell .queue-drop-btn:hover { color: #e5484d; }
@media (max-width: 600px) {
  /* На касании цель обязана быть в палец — правило доступности старше
     этой правки вида (var(--palec)). На мыши хватает 32 px. */
  .queue-cell .order-edit,
  .queue-cell .queue-drop-btn,
  .queue-open { width: var(--palec); height: var(--palec); }
}
.queue-open:hover { border-color: var(--accent); color: var(--fg); }
.queue-cell.is-waiting { cursor: grab; }
.queue-cell.is-waiting:hover { border-color: var(--accent); }
/* Выбранная — та, которую двигают кнопки. Без явной отметки клиент жмёт
   стрелку и не понимает, что именно поехало. */
.queue-cell.is-picked {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(79, 140, 255, .18);
}
.queue-cell.is-dragging { cursor: grabbing; opacity: .9; transform: scale(1.01); }
.queue-cell.is-running { border-style: dashed; }

/* Номер места — синим, как и «сейчас»: бледный кружок на тёмной карточке
   терялся, а место в очереди это первое, что человек ищет глазами.
   Цифра крупная и жирная по той же причине. */
.queue-place {
  min-width: 36px; height: 36px; line-height: 36px; text-align: center;
  padding: 0 10px; box-sizing: border-box;
  border-radius: 10px; background: var(--fill); color: #fff;
  font-size: var(--fs-lg); font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* У идущего заказа здесь слово, а не цифра. Оно чуть мельче номера —
   слово длиннее, и в один размер с цифрой распирало бы блок, — но именно
   ЧУТЬ: разница в два шага шкалы читалась как разные по важности вещи,
   хотя это одно и то же место карточки. */
.queue-cell.is-running .queue-place {
  font-size: var(--fs-base); font-weight: 700; white-space: nowrap;
}
.queue-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.queue-state { color: var(--fg-muted); font-size: var(--fs-sm); white-space: nowrap; }
.queue-grip { color: var(--fg-muted); opacity: .5; }
.queue-cell.is-running .queue-grip { visibility: hidden; }

@media (max-width: 560px) {
  /* На телефоне состояние уезжает под название: три колонки в 360 точек
     не помещаются, и название сжималось до многоточия.

     ПРЕЖДЕ ЗДЕСЬ СТОЯЛО `grid-column: 2; grid-row: 2`, а карточка —
     flex (см. выше, набор элементов у строк разный). В flex-контейнере
     свойства сетки не значат НИЧЕГО: правило было написано, подключено
     и мертво, а перенос элементов оставался на откуп flex-wrap. У ждущего
     заказа элементов на два больше (галочка и ручка), и на 551 px крестик
     с ручкой сваливались третьей строкой поодиночке (снято браузером,
     scripts/shot.py --width 551, A_Д_300).

     Чиним тем же flex, а не сеткой: название добирает остаток первой
     строки и выталкивает вниз всё прочее, слова о состоянии идут вторым
     рядом, кнопки — группой у правого края. */
  .queue-cell { gap: 8px; row-gap: 6px; }
  .queue-name { flex: 1 1 55%; min-width: 0; }
  .queue-status, .queue-state { order: 3; }
  /* У ЖДУЩЕГО заказа элементов на два больше — галочка и ручка, — и восемь
     штук в 551 px не сходятся ни при каком порядке: крестик вываливался
     третьей строкой один. Пустой ::after шириной во всю строку — это ЯВНЫЙ
     разрыв ряда: flex-wrap сам ломает строку там, где кончилось место,
     а нам нужно, чтобы кнопки шли группой и всегда вместе. У бегущего
     и готовящегося заказа кнопок меньше, им разрыв не нужен. */
  .queue-cell.is-waiting::after {
    content: ""; flex-basis: 100%; height: 0; order: 4;
  }
  .queue-grip { order: 5; margin-left: auto; }
  .queue-cell .order-edit,
  .queue-cell .queue-drop-btn,
  .queue-open { order: 6; }
  /* Полоса этапов и «осталось ~» — своей строкой: они про один заказ
     целиком, а не про соседнюю кнопку. */
  .queue-progress, .queue-eta { order: 7; flex-basis: 100%; }
}



/* Строка «в работе»: обложка, название, статус и этап. Название тянется,
   остальное по содержимому — иначе длинное имя выталкивает этап за край. */
.queue-name { flex: 1 1 12ch; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.queue-thumb { flex: 0 0 auto; width: 56px; height: 32px; overflow: hidden;
  border-radius: 6px; background: var(--surface-2, transparent); }
.queue-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.queue-status { flex: 0 0 auto; }
.queue-cell .order-edit { flex: 0 0 auto; }

/* ---- Страница оплаты -----------------------------------------------------
   Владелец 12.08: «первая страница с хорошими стилями, а страница оплаты
   нет. Сделай её красивее и более читаемые шрифты, чётче и крупнее».

   Здесь человек расстаётся с деньгами, и мелкий серый текст читается как
   небрежность в самом неподходящем месте. Поэтому весь текст на шаг
   крупнее обычного, подписи не серые, а сумма — самое заметное на экране.
   Размеры берутся из общей шкалы (--fs-*), новых значений не заводим. */
/* ШИРИНА — КАК У ОСТАЛЬНЫХ КАБИНЕТНЫХ СТРАНИЦ (владелец 27.08: «убери
   пустое место между меню слева и блоком, пусть как в Настройках»).
   Прежние 720 px по центру оставляли слева пустую полосу рядом
   с сайдбаром, и страница выглядела съехавшей. */
.pay-card { font-size: var(--fs-base); }

/* Чек. Сумма крупно, всё остальное — строками под ней. */
.pay-check {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow);
  padding: 24px 26px; margin-bottom: 26px;
}
.pay-check-label {
  margin: 0; color: var(--muted); font-size: var(--fs-sm);
  text-transform: uppercase; letter-spacing: .08em; font-weight: 600;
}
/* Крупнее всей шкалы намеренно: это единственное число, ради которого
   человек открыл страницу. Ступени --fs-xl (1.5rem) для него мало. */
.pay-amount-big {
  margin: 4px 0 18px; font-size: 2.4rem; line-height: 1.1;
  font-weight: 800; color: var(--fg-strong); letter-spacing: -.02em;
}
.pay-check-rows {
  /* РЕКВИЗИТЫ В ЧЕТЫРЕ КОЛОНКИ (образец владельца 27.08,
     ~/Desktop/skriny/Покупка2.png): подпись сверху, значение под ней.
     Прежние строки «слева подпись — справа значение» на широком экране
     растягивали взгляд через всю карточку, а пояснение про юридическое
     имя уезжало в правый край и читалось хуже.
     На узком — две колонки, на телефоне одна: `auto-fit` решает сам. */
  display: grid; gap: 18px 24px; margin: 0;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  border-top: 1px solid var(--border); padding-top: 16px;
}
.pay-check-rows > div { display: block; }
.pay-check-rows dt { color: var(--muted); font-size: var(--fs-md); margin-bottom: 4px; }
.pay-check-rows dd { margin: 0; color: var(--fg-strong); }
.pay-check-rows .hint { display: block; margin-top: 4px; }
/* Заголовок и дорога назад — над карточкой, во всю ширину колонки;
   сам заголовок по центру (слово владельца 27.08). */
.pay-head-wrap h1 { margin-top: 0; text-align: center; }
.pay-back { margin: 0 0 6px; }
/* Поле и кнопка одного действия — в строку; на телефоне переносится.
   ВЫРАВНИВАЕМ ПО ВЫСОТЕ, А НЕ ПО ЦЕНТРУ (владелец 27.08: «кнопка
   немного ниже поля»): у поля и кнопки разные внутренние отступы,
   и центрирование давало сдвиг в несколько точек. `stretch` делает
   их одной высоты, а текст кнопки центрируется внутри неё. */
.pay-line { display: flex; gap: 10px; flex-wrap: wrap; align-items: stretch; }
.pay-line .pay-input { flex: 1 1 220px; margin-bottom: 0; }
.pay-line .btn { display: inline-flex; align-items: center; justify-content: center; }

.pay-head { font-size: var(--fs-xl); margin: 0 0 16px; }

/* Способы. Каждый — отдельная карточка: видно, что это выбор, а не список. */
.pay-ways { display: flex; flex-direction: column; gap: 14px; margin-bottom: 22px; }
.pay-option {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow); overflow: hidden;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.pay-option:hover { border-color: var(--border-hover); }
/* Выбранный способ обведён и подсвечен. `:has` поддерживают все живые
   браузеры; там, где нет, останется обычная рамка — выбор от этого
   не ломается, только виден слабее. */
.pay-option:has(.pay-way-radio:checked) {
  border-color: var(--accent); box-shadow: var(--ring);
}
.pay-way {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 18px 20px; cursor: pointer;
}
/* Селектор с типом обязателен: `.card input { width: 100% }` выше по файлу
   бьёт простой класс по весу, и переключатель растягивался на 346 px —
   кружок уезжал к левому краю, а текст сжимался в узкую колонку. Та же
   ловушка уже дважды отмечена в этом файле (.consent, .radio-group). */
.pay-way input[type="radio"].pay-way-radio {
  width: 20px; height: 20px; flex: none; margin: 3px 0 0;
  accent-color: var(--accent);
}
/* Номера способов оплаты (1, 2, 3) убраны по решению владельца 13.08:
   выбор делается кружком радио, а вторая круглая метка рядом с ним
   читалась как второй переключатель. */
.pay-check-rows dt, .pay-check-rows dd { line-height: 1.4; }
.pay-way-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.pay-way-title { font-size: var(--fs-lg); font-weight: 700; color: var(--fg-strong); }
/* Не серым: подпись отвечает, каким банком можно платить этим способом,
   и это единственное, что удержит человека от неудачной попытки. */
.pay-way-note { font-size: var(--fs-base); color: var(--muted); }

/* Содержимое выбранного способа идёт от того же края, что и заголовок
   карточки: ступенька под кружок читалась как сбитая вёрстка, а не
   как связь с названием. */
.pay-way-body { padding: 18px 20px 20px; border-top: 1px solid var(--border); }
.pay-way-body p { margin: 0 0 14px; }
.pay-label {
  display: block; margin: 0 0 6px; font-weight: 600; color: var(--fg-strong);
}
.pay-way-body input.pay-input {
  width: 100%; max-width: 360px; margin-bottom: 16px;
  padding: 12px 14px; font-size: var(--fs-base);
  border-radius: 10px; border: 1px solid var(--border);
  background: var(--bg-soft); color: var(--fg);
}
.pay-input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.pay-way-body .btn { max-width: 360px; }
/* Номер покупки в комментарии — единственное, без чего платёж не найдётся.
   Это не подсказка мелким шрифтом, а условие. */
.pay-note-strong {
  padding: 12px 14px; border-radius: 10px;
  background: var(--bg-soft); border-left: 3px solid var(--accent);
  font-size: var(--fs-base);
}
.pay-enter-amount { font-size: var(--fs-lg); color: var(--fg-strong); }
.pay-card .hint { font-size: var(--fs-md); }

/* Карточка Kaspi QR. Картинка вытянутая (899×1599): ограничиваем ширину,
   высоту считает браузер — сплющенный код камера не прочитает. Белая
   подложка нужна тёмной теме: код на тёмном фоне не сканируется. */
.kaspi-qr {
  display: block; width: 100%; max-width: 280px; height: auto;
  margin: 0 0 16px; border-radius: 12px; background: #fff;
}

@media (max-width: 560px) {
  /* На телефоне отступ под номером съедает половину ширины. */
  .pay-way-body { padding-left: 20px; }
  .pay-amount-big { font-size: 2rem; }
  .pay-check-rows > div { flex-direction: column; gap: 2px; }
  .pay-check-rows dd { text-align: left; }
}

/* ---- Пальцем, а не мышью --------------------------------------------
   Замер на 390 px: карандаш переименования выходил 6×19 пикселей,
   стрелка «открыть» 28×28, ссылки шапки и подвала 24–26 в высоту.
   Мышью в такое попадают, пальцем нет — а клиент приходит с телефона.

   Правило простое: у всего, по чему нажимают, площадь касания
   не меньше `--palec`. Размер САМОГО значка не трогаем — растёт только
   область вокруг него, поэтому вид на глаз не меняется.

   ЧИСЛО СТАЛО ОДНИМ НА ВСЁ (A_Д_155_В, 18.08). Здесь стояло 40, а
   руководства Apple и Android дают 44 — и дают ОДИНАКОВОЕ, спорить
   не с кем. Сорок появилось не из источника, а из осторожности: «хоть
   так». Замер редактора на 375x667 показал цену осторожности — вкладки
   40 px, кнопки перемотки 39, и мимо них промахиваются.

   Держим переменной, а не числом в трёх местах: следующая правка
   отступов иначе разъедет цели по размерам, и каждая будет выглядеть
   правильной. */
@media (max-width: 767px) {
  .order-edit,
  .queue-open,
  .theme-toggle,
  .clip-fullscreen,
  /* Редактор клипа: вкладки и перемотка. Ими пользуются не глядя —
     человек смотрит в ролик, а не в кнопку. */
  .clip-tab,
  .btn-step {
    min-width: var(--palec);
    min-height: var(--palec);
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* Ссылки шапки и подвала: добавляем поле сверху и снизу, а не высоту
     строки, — иначе межстрочный интервал разъедет меню на три ряда. */
  .topbar nav > a,
  .topbar .balance,
  .topbar .linkbtn,
  .footer-links a,
  .btn-link {
    display: inline-flex;
    align-items: center;
    min-height: var(--palec);
  }
  /* Во flex-строке пробелы между словами пропадают: без зазора
     выходило «Баланс:0» (A_Д_452, снимок D_Д_483 на 390). */
  .topbar .balance { gap: .3em; }
}

/* Строка «в работе» на телефоне. Шесть элементов в 390 px не помещаются
   в один ряд, и перенос по умолчанию рвал их как попало: место, обложка
   и название оказывались в разных строках. Даём название целой строкой,
   остальное — рядом под ним. */
@media (max-width: 560px) {
  .queue-cell { row-gap: 6px; }
  .queue-name { flex: 1 1 100%; order: -1; font-weight: 600; }
  .queue-thumb { width: 44px; height: 26px; }
  .queue-status, .queue-state { font-size: var(--fs-sm); }
}

/* ГЛАВНЫЙ ДОВОД — ВЫШЕ СГИБА НА УЗКОМ ЭКРАНЕ (A_Д_138, замер D).

   «Первое видео до 20 минут — бесплатно, без карты» лежало на 844 px
   при окне 667 (iPhone SE и любой недорогой телефон). Из четырёх мест,
   где мы говорим о бесплатной пробе, три работают ПОСЛЕ регистрации —
   значит для того, кто ещё решает, эта строка единственная, и он
   уходил, не встретив её.

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

   Довод встаёт ПЕРЕД кнопками: «бесплатно, без карты» — это ответ
   на возражение, а кнопка «Начать бесплатно» сразу под ним читается
   как продолжение фразы, а не как отдельное требование.

   Срок («час записи обычно за 15 минут») уезжает вниз намеренно:
   он важен, но человеку, который ещё не решил пробовать, он отвечает
   на вопрос, которого тот пока не задал. */
@media (max-width: 560px) {
  .lp-hero-text { display: flex; flex-direction: column; }
  .lp-hero-text .lp-eyebrow { order: 1; }
  .lp-hero-text h1 { order: 2; }
  .lp-hero-text .lp-lead { order: 3; }
  .lp-hero-text .lp-free-note { order: 4; }
  .lp-hero-text .lp-hero-actions { order: 5; }
  /* ЧИСЛА НА ТЕЛЕФОНЕ — ПОСЛЕ КНОПОК (замер 26.08). На мониторе строка
     стоит под lead, как просила D, а на 390 она вытолкнула из первого
     экрана сами кнопки: с плашкой согласия туда влезает шесть строк,
     а не семь. Действие важнее довода: числа убеждают того, кто уже
     видит, чем нажать. Порядок тот же приём, что у строки срока
     выше — она уехала вниз по той же причине. */
  .lp-hero-text .lp-numbers { order: 6; }
  .lp-hero-text .lp-term { order: 7; }
  /* На телефоне порядок тот же, что на мониторе: обещание, потом
     числа, потом срок. Держим одним правилом, чтобы вторая вёрстка
     не разошлась с первой. */
}

/* ГАЛЕРЕЯ ОФОРМЛЕНИЯ НА ТЕЛЕФОНЕ (A_О_204, замер D).

   Кнопка «Отправить в обработку» лежала на 4564 px — 5,4 экрана
   прокрутки, при 1,7 экрана на ноутбуке. Всю разницу давала эта
   галерея: 3380 px из 4603.

   ПРИЧИНА БЫЛА В ВОСЕМНАДЦАТИ ПИКСЕЛЯХ. Сетка просит
   `minmax(150px, 1fr)`, на 390 px ширины ей достаётся 296, а две
   колонки требуют 314 (150 + 14 + 150). Не хватило — и `auto-fill`
   честно выложил ОДНУ колонку шириной во всю панель, растянув каждый
   образец 9:16 до 491 px высотой. Шесть образцов, каждый почти
   в полный экран, — ровно то, что D увидела браузером.

   Отсюда обе правки, и обе про телефон:

   * колонка уже (128 px) — двум хватает места с запасом;
   * образец кадрируется до 9:12 снизу. Предмет выбора — оформление
     СУБТИТРОВ, а они в нижней трети кадра: при `object-position: bottom`
     видно ровно то, что выбирают, а треть высоты уходит. Полный кадр
     9:16 здесь показывал бы в основном картинку, к выбору не
     относящуюся. */
@media (max-width: 560px) {
  .template-pick-list { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
  /* 9:10, а не 9:12 (A_Д_141). Седьмое оформление «Классика со значком»
     добавило галерее целый ряд, и кнопка «Отправить» уехала с 2,2
     до 2,6 экрана — за порог, поставленный в A_О_204. Подрезаем образец
     сильнее: субтитры в нижней части кадра, и при кадрировании снизу
     они остаются видны — это и есть предмет выбора. */
  /* Подрезка касается ТОЛЬКО вертикали (A_О_231). У горизонтали
     и квадрата кадр и так низкий: подрезав его, мы отняли бы у клиента
     как раз субтитры, ради которых он в образец и смотрит. */
  [data-aspect="9_16"] .template-pick-item img {
    aspect-ratio: 9 / 10; object-position: bottom;
  }
  [data-aspect="16_9"] .template-pick-item img {
    aspect-ratio: 16 / 9; object-position: center;
  }
  /* На телефоне колонок и так одна-две: широкая колонка тут не нужна,
     а вот пунктирная плитка обязана следовать формату и здесь. */
  [data-aspect="16_9"] .template-pick-list {
    grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
  }
  [data-aspect="16_9"] .template-pick-new svg { aspect-ratio: 16 / 9; }
  [data-aspect="1_1"] .template-pick-new svg { aspect-ratio: 1 / 1; }
  [data-aspect="1_1"] .template-pick-item img {
    aspect-ratio: 1 / 1; object-position: center;
  }
  .template-pick-new svg { aspect-ratio: 9 / 10; }
  .template-pick-item span { font-size: var(--fs-sm); margin-top: 6px; }
}

/* ======================================================================
   ВИТРИНА: переменные обеих тем.

   Объявлены ТРЕМЯ блоками, как и вся палитра этого файла: тёмная
   в `:root`, светлая — и по системной настройке, и по явному выбору
   темы. Пропусти любой из трёх — и переключатель темы даст белый
   текст на белом.
   ====================================================================== */
:root {
  /* Сплошной фон карточек, как у блоков «Возможностей» (слово
     владельца 27.08): белёсый градиент читался старым цветом. */
  --lp-surface: var(--surface);
  --lp-surface-2: var(--surface);
  --lp-line: rgba(255, 255, 255, .09);
  --lp-glow-1: rgba(79, 140, 255, .22);
  --lp-glow-2: rgba(124, 92, 255, .18);
  /* Тайм-код: мятный. В тёмной теме даёт контраст 9,48:1 — считано D. */
  --lp-tc: #22d3a5;
  --lp-card-shadow:
    0 20px 42px -26px rgba(3, 7, 18, .85),
    0 3px 8px -4px rgba(3, 7, 18, .55),
    inset 0 1px 0 rgba(255, 255, 255, .07);
  /* КАДР ТЕЛЕФОНА ЖИВЁТ СВОИМИ ПЕРЕМЕННЫМИ и остаётся тёмным в обеих
     темах: силуэт нарисован светлым, и на белой подложке он исчезает
     вовсе. Грабля D, проверена ею на себе. */
  --lp-phone-bg: #0b1120;
  /* Плашка кадра: на тёмном кадре хватает .45 — текст даёт 8,9. */
  --lp-tag-plate: rgba(0, 0, 0, .45);
  --lp-tag-fg: #e9eefb;
  --lp-phone-line: rgba(233, 238, 251, .62);
  --lp-phone-line-soft: rgba(233, 238, 251, .38);
  --lp-sub-fg: #fff;
  --lp-sub-shadow: 0 2px 8px rgba(0, 0, 0, .8);
  --lp-sub-plate: rgba(6, 10, 20, .45);
  --lp-phone-line: rgba(255, 255, 255, .10);
  /* Подсветка слова ВНУТРИ кадра. Кадр тёмный в обеих темах, поэтому
     цвет здесь не следует за темой страницы: `--lp-tc` в светлой теме
     темнеет ради контраста на светлом фоне, и подсветка становилась
     тёмно-зелёной на тёмном. Видно только глазами. */
  --lp-phone-accent: #22d3a5;
  /* Градиент второй строки заголовка главной (A_О_399, макет 17.09). */
  --lp-title-grad: linear-gradient(90deg, #38bdf8, #60a5fa 50%, #818cf8);
  --lp-title-glow: 0 0 10px rgba(56, 189, 248, .65), 0 0 25px rgba(59, 130, 246, .4);
  --lp-cyan: #38bdf8;
  --lp-cyan-line: rgba(56, 189, 248, .2);
  --lp-cyan-soft: rgba(56, 189, 248, .1);
  --lp-top-glow: radial-gradient(ellipse closest-side at 50% 30%, rgba(56, 189, 248, .12) 0%, rgba(59, 130, 246, .08) 40%, transparent 100%);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --lp-surface: rgba(255, 255, 255, .72);
    --lp-surface-2: #ffffff;
    --lp-line: #c9d5e8;
    --lp-glow-1: rgba(59, 111, 224, .14);
    --lp-glow-2: rgba(106, 72, 230, .12);
    /* На светлой мятный давал 1,71:1 при норме 4,5 — тот же смысл
       цвета, но тёмный тон. */
    --lp-tc: #0a6b55;
    --lp-card-shadow: 0 6px 22px -12px rgba(23, 43, 77, .22);
    /* Светлый hero-телефон в светлой теме (слово владельца 27.08). */
    --lp-phone-bg: #dfe7f3;
    /* На светлом кадре .45 давало 3,47 при норме 4,5 — плашка гуще. */
    --lp-tag-plate: rgba(0, 0, 0, .62);
    --lp-tag-fg: #e9eefb;
    --lp-phone-line: rgba(23, 32, 54, .58);
    --lp-phone-line-soft: rgba(23, 32, 54, .32);
    --lp-sub-fg: #17203a;
    --lp-sub-shadow: none;
    --lp-sub-dim: rgba(23, 32, 54, .55);
  --lp-sub-on: #a16207;
  --lp-sub-plate: rgba(255, 255, 255, .72);
    --lp-sub-on: #a16207;
    --lp-phone-accent: #0a6b55;
    /* На светлом светлая середина градиента пропадала — тон темнее. */
    --lp-title-grad: linear-gradient(90deg, #0369a1, #1d4ed8 50%, #4338ca);
    --lp-title-glow: 0 0 18px rgba(56, 189, 248, .25);
    --lp-cyan: #0369a1;
    --lp-cyan-line: rgba(3, 105, 161, .25);
    --lp-cyan-soft: rgba(3, 105, 161, .08);
    --lp-top-glow: radial-gradient(ellipse closest-side at 50% 30%, rgba(56, 189, 248, .14) 0%, rgba(59, 130, 246, .06) 40%, transparent 100%);
  }
}

:root[data-theme="light"] {
  --lp-surface: rgba(255, 255, 255, .72);
  --lp-surface-2: #ffffff;
  --lp-line: #c9d5e8;
  --lp-glow-1: rgba(59, 111, 224, .14);
  --lp-glow-2: rgba(106, 72, 230, .12);
  --lp-tc: #0a6b55;
  --lp-card-shadow: 0 6px 22px -12px rgba(23, 43, 77, .22);
  /* Светлый hero-телефон в светлой теме (слово владельца 27.08). */
  --lp-phone-bg: #dfe7f3;
  /* На светлом кадре .45 давало 3,47 при норме 4,5 — плашка гуще. */
  --lp-tag-plate: rgba(0, 0, 0, .62);
  --lp-tag-fg: #e9eefb;
  --lp-phone-line: rgba(23, 32, 54, .58);
  --lp-phone-line-soft: rgba(23, 32, 54, .32);
  --lp-sub-fg: #17203a;
  --lp-sub-shadow: none;
  --lp-title-grad: linear-gradient(90deg, #0369a1, #1d4ed8 50%, #4338ca);
  --lp-title-glow: 0 0 18px rgba(56, 189, 248, .25);
  --lp-cyan: #0369a1;
  --lp-cyan-line: rgba(3, 105, 161, .25);
  --lp-cyan-soft: rgba(3, 105, 161, .08);
  --lp-top-glow: radial-gradient(ellipse closest-side at 50% 30%, rgba(56, 189, 248, .14) 0%, rgba(59, 130, 246, .06) 40%, transparent 100%);
  --lp-sub-dim: rgba(23, 32, 54, .55);
  --lp-sub-on: #a16207;
  --lp-sub-plate: rgba(255, 255, 255, .72);
  --lp-phone-accent: #0a6b55;
}
/* ======================================================================
   ВИТРИНА (landing page). Перенос утверждённого владельцем прототипа
   (13.08 03:27; разбор — docs/dizain-predlozhenie.md).

   ВСЁ ПОД `.lp`, И ЭТО НЕ УКРАШЕНИЕ, А ЗАЩИТА. Прототип пользуется
   именами `.card`, `.btn`, `.num` — они в этом файле УЖЕ ЗАНЯТЫ
   кабинетом, тарифами и админскими списками (`.card` встречается
   семь раз, `.btn` девятнадцать). Правило без обёртки переписало бы
   вид половины сайта молча. Специфичность `.lp .card` выше `.card`,
   поэтому витрина получает своё, а остальные страницы не трогаются
   вовсе.

   ТЕМЫ. Прототип нарисован только тёмным. Здесь у каждой переменной
   есть светлое значение, объявленное в тех же трёх местах, что и вся
   палитра файла (`:root`, `prefers-color-scheme: light`,
   `[data-theme="light"]`) — иначе переключатель темы даст белый текст
   на белом.

   ТЕЛЕФОННЫЙ БЛОК СТИЛЕЙ — ПОСЛЕДНИМ (грабля D, проверена ею на себе):
   правила одинаковой силы решаются порядком, и media-блок, стоящий
   выше обычного правила, проигрывает ему на телефоне.
   ====================================================================== */

/* РАЗМЕРЫ ЖИВУТ В :root, А НЕ ВНУТРИ `.lp`, и это починка настоящей
   беды (нашёл владелец 13.08: «кнопки с квадратными границами»).
   Радиусы я объявила внутри витрины — значит ВНЕ её переменной
   не существует вовсе, а `border-radius: var(--lp-r-sm)` без значения
   даёт НОЛЬ. Кнопки на страницах покупок, пакетов и оформления стали
   прямоугольными разом, и виновата была одна строка: место объявления,
   а не сами правила. */
:root {
  --lp-gap: 18px;
  --lp-pad: clamp(20px, 4vw, 40px);
  --lp-max: 1180px;
  --lp-r-lg: 28px;
  --lp-r-md: 20px;
  --lp-r-sm: 14px;
}

.lp {
  /* `clip`, а не `hidden`: hidden делает элемент прокручиваемым
     контейнером и ломает `position: sticky` у всего, что внутри. */
  position: relative; overflow-x: clip;
}

/* Свет ЗА содержимым, а не заливка поверх: два мягких пятна вверху.

   ПО ГОРИЗОНТАЛИ НЕ ВЫХОДИТ ЗА КРАЙ. В прототипе он вылезал на 10%
   в каждую сторону, а страницу спасал `overflow-x: hidden` на body.
   У нас его нет — и свечение давало прокрутку вбок на ВСЕХ ширинах
   (320 → 352, 1440 → 1584). Поймал сторож ширины; глазами это выглядит
   всего лишь как лишняя полоса внизу экрана, и на неё не смотрят.
   Лечим причину: пятна остаются внутри. Обрезка у `.lp` — второй рубеж
   на случай, если внутрь once добавят что-то широкое. */
.lp-glow {
  position: absolute; inset: -20% 0 auto 0; height: 70vh;
  z-index: 0; pointer-events: none; filter: blur(20px);
  background:
    radial-gradient(46% 60% at 22% 0%, var(--lp-glow-1), transparent 70%),
    radial-gradient(38% 55% at 78% 8%, var(--lp-glow-2), transparent 70%);
}

.lp-wrap {
  width: 100%; max-width: var(--lp-max); margin: 0 auto;
  padding: 0 var(--lp-pad); position: relative; z-index: 1;
}

/* Воздух между блоками в полтора-два раза больше, чем внутри блока:
   до этого блоки шли одинаковыми промежутками и сливались в поток. */
.lp-section { padding: clamp(48px, 7vw, 96px) 0; position: relative; }
/* ПЕРВЫЙ ЭКРАН СЖИМАЕТСЯ НА ВЫСОТУ ПЛАШКИ (приёмка D 15.08).
   Отступ считался только от ШИРИНЫ (7vw), поэтому на низком окне
   (1280x800 — обычный ноутбук) первый экран кончался кнопкой на 795-й
   точке из 800, впритык, а с плашкой cookies кнопка пропадала вовсе.
   Теперь верхний отступ первой секции уступает плашке, пока она висит,
   и не опускается ниже 32 точек: без отступа заголовок липнет к шапке. */
.lp-section#top {
  padding-top: max(32px, calc(clamp(48px, 7vw, 96px) - var(--cookie-bar-space, 0px)));
}
/* НИЗКОЕ ОКНО — отдельная мерка. Все отступы первого экрана считались
   от ширины (vw, ch), и на широком-но-низком окне ноутбука 1280x800
   первый экран не влезал: кнопка кончалась на 795-й точке из 800.
   Одного вычета плашки не хватило (795 → 738 при плашке с 710), поэтому
   на низком окне поджимаются и внутренние отступы. Порог 860 взят
   по замеру: на 900 кнопка укладывается сама, поджимать там нечего. */
@media (max-height: 860px) {
  .lp-section#top {
    padding-top: max(14px, calc(clamp(48px, 7vw, 96px) - var(--cookie-bar-space, 0px)));
  }
  .lp-section#top .lp-eyebrow { margin-bottom: 8px; padding: 7px 14px; }
  .lp-section#top .lp-lead { margin-top: 10px; }
  .lp-section#top .lp-hero-actions { margin-top: 10px; }
  .lp-section#top .lp-term { margin-top: 12px; }
  .lp-section#top .lp-free-note { margin-top: 10px; }
}
/* ТЕ ЖЕ ПОДЖАТИЯ, ПОКА ВИСИТ ПЛАШКА СОГЛАСИЯ (A_Д_201, замер 26.08).
   Порог по высоте окна мерили ПО КНОПКЕ: на 900 она укладывалась,
   и поджатие выключалось. А обещание «первое видео до 20 минут —
   бесплатно» стоит строкой НИЖЕ кнопки и уходило под плашку: 90 точек
   из 900 на мониторе, 125 из 844 на телефоне, на всех трёх языках.
   Признак ставит сам баннер (`data-cookie-bar` на <html>), потому что
   высота плашки зависит от языка и ширины окна. Вид при этом — тот,
   что D уже приняла для низких окон 15.08. */
html[data-cookie-bar] .lp-section#top {
  padding-top: max(14px, calc(clamp(48px, 7vw, 96px) - var(--cookie-bar-space, 0px)));
}
html[data-cookie-bar] .lp-section#top .lp-eyebrow { margin-bottom: 8px; padding: 7px 14px; }
html[data-cookie-bar] .lp-section#top .lp-lead { margin-top: 10px; }
html[data-cookie-bar] .lp-section#top .lp-numbers { margin-top: 10px; }
html[data-cookie-bar] .lp-section#top .lp-hero-actions { margin-top: 10px; }
html[data-cookie-bar] .lp-section#top .lp-term { margin-top: 12px; }
html[data-cookie-bar] .lp-section#top .lp-free-note { margin-top: 10px; }

/* УЗКИЙ ТЕЛЕФОН, ПОКА ПЛАШКА ВИСИТ (A_О_383, замер D 10.09).
   ПОЧЕМУ ПРЕЖНЕГО ПОДЖАТИЯ НЕ ХВАТИЛО. Вычет плашки уходил в один
   `padding-top`, а он упирается в пол 14 px: на 360x800 выигрыш выходил
   34 px против 127 занятых плашкой, и кнопки первого экрана перехватывал
   `elementFromPoint` в пользу плашки — обе на ru, по одной на kk и en.
   Мерка — предмет, а не координаты: у перекрытой кнопки рамка честно
   лежит в окне.

   ЧТО ЗДЕСЬ ПОДЖАТО. Только то, что стоит ВЫШЕ кнопок: ниже их правка
   ничего не двигает. Отступы режутся вдвое, а не на глаз, и `lp-hero-pill`
   на это время уходит совсем: её текст «ИИ-нарезка длинных видео в клипы»
   повторяет заголовок, и на узком экране она стоит 76 px из 800. Так же
   на 380 уже уходит тайм-код у `.lp-eyebrow` — первым уходит украшение,
   а не смысл.

   ВОЗВРАЩАЕТСЯ САМО: признак `data-cookie-bar` снимает сам баннер после
   выбора согласия, и первый экран становится тем, что приняла D. */
@media (max-width: 430px) {
  html[data-cookie-bar] .lp-section#top .lp-hero-pill { display: none; }
  html[data-cookie-bar] .lp-section#top .lp-eyebrow { margin-bottom: 4px; }
  html[data-cookie-bar] .lp-section#top h1 { margin-top: 8px; margin-bottom: 8px; }
  html[data-cookie-bar] .lp-section#top .lp-lead { margin-top: 6px; margin-bottom: 8px; }
  html[data-cookie-bar] .lp-section#top .lp-free-note { margin-top: 6px; }
  html[data-cookie-bar] .lp-section#top .lp-hero-actions { margin-top: 6px; gap: 8px; }

  /* КНОПКИ В ОДИН РЯД, ПОКА ВИСИТ ПЛАШКА (слово владельца 10.09, формой).
     На узком экране кнопки штатно во всю ширину и стоят друг под другом:
     две по 56 px и есть те 112 px, из-за которых на 360×640 нижняя
     уходила под плашку. В один ряд они стоят 56 px вместо 112.

     `flex: 1 1 0` и `min-width: 0` — чтобы длинная казахская подпись
     («Бұл қалай жұмыс істейді») переносилась ВНУТРИ кнопки, а не
     выпихивала соседнюю за край: без `min-width: 0` flex не даёт
     кнопке стать уже своего текста. Кегль и поля уменьшены ровно
     настолько, чтобы палец по-прежнему попадал: высота остаётся
     не меньше 44 px (`min-height`), а это норма попадания. */
  html[data-cookie-bar] .lp-section#top .lp-hero-actions { flex-wrap: nowrap; }
  html[data-cookie-bar] .lp-section#top .lp-hero-actions .btn {
    width: auto;
    flex: 1 1 0;
    min-width: 0;
    min-height: 44px;
    padding: 10px 8px;
    font-size: var(--fs-sm);
    line-height: 1.2;
    text-align: center;
  }
}

/* «Отправлено» вместо кнопки: тот же вес, что у кнопки, чтобы глаз
   нашёл ответ там же, где искал действие (A_О_264_В). */
.pay-done { margin: 14px 0 0; line-height: 1.5; }
.pay-done b { color: var(--ok, #1a7f37); }

/* Строка с числами: тот же вес, что у lead, но не крупнее заголовка. */
.lp-numbers { margin-top: 14px; line-height: 1.5; }

.lp h1, .lp h2, .lp h3 {
  letter-spacing: -.03em; line-height: 1.06; font-weight: 650;
}
/* Верх шкалы ниже прототипного (4,4rem): там заголовок стоял во всю
   ширину страницы, а здесь делит первый экран с кадром телефона —
   на 1180 px он ломался на четыре строки. Заголовок в две строки —
   требование D, и оно про обе ширины, не только про телефон. */
.lp h1 { font-size: clamp(2.2rem, 4.6vw, 3.5rem); }
.lp h2 { font-size: clamp(1.75rem, 3.9vw, 2.9rem); margin-bottom: .5em; }
.lp h3 { font-size: clamp(1.2rem, 1.6vw, 1.45rem); line-height: 1.28; letter-spacing: -.02em; }

/* Ширина текста — не больше 70 знаков: длинные строки на широком
   мониторе читать тяжело, и это самая частая беда «простых» страниц. */
.lp-lead {
  font-size: clamp(1.05rem, 1.7vw, 1.3rem); color: var(--muted);
  max-width: 56ch; margin-top: 20px;
}

/* Надпись над заголовком. Крупная намеренно (владелец, 12.08): их
   четыре на странице, и все читались как служебная мелочь. */
.lp-eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 17px; border-radius: 999px;
  background: var(--lp-surface); border: 1px solid var(--lp-line);
  font-size: 1rem; color: var(--fg); letter-spacing: .01em; margin-bottom: 22px;
}
/* ТАЙМ-КОД. Мятный #22d3a5 на светлой теме давал контраст 1,71:1 при
   норме 4,5 — посчитано D, а не прикинуто; в тёмной он же даёт 9,48:1.
   Поэтому цвет живёт переменной и меняется только в светлой теме. */
.lp-eyebrow b {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: .86rem; font-weight: 600; color: var(--lp-tc);
  letter-spacing: .06em; padding: 3px 9px; margin-right: 9px;
  border: 1px solid var(--lp-line); border-radius: 8px;
  background: var(--lp-surface-2);
}

/* Стеклянная карточка: единый радиус, единая линия, единый шаг.
   Объём даётся тремя тенями — одной выходит «наклейка». */
.lp .card {
  /* MAX-WIDTH СНИМАЕМ ЯВНО. Старое правило `.card` из кабинета ставит
     440 px, и специфичность тут ни при чём: я переопределила фон,
     рамку и отступы, но не ширину — и карточки в сетке упирались
     в 440 px, оставляя дыру справа. На витрине это выглядело как
     «блоки разъехались»: график и языки стояли с провалом в 200 px,
     хотя сетка была верной. Нашёл владелец глазами, подтвердил замер
     координат в браузере. */
  max-width: none;
  background: linear-gradient(180deg, var(--lp-surface-2), var(--lp-surface));
  border: 1px solid var(--lp-line); border-radius: var(--lp-r-md);
  box-shadow: var(--lp-card-shadow);
  padding: clamp(20px, 2.4vw, 30px); backdrop-filter: blur(14px);
  display: flex; flex-direction: column; gap: 10px; min-height: 100%;
  transition: border-color .2s ease, transform .2s ease;
}
.lp .card:hover { border-color: var(--border-hover); transform: translateY(-4px); }
.lp .card p { font-size: 1.06rem; line-height: 1.5; }

/* Bento: блоки разной ширины в ОДНОЙ сетке из шести колонок.
   Человек видит шесть-девять смыслов за пятнадцать секунд, а не два-три. */
.lp-bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--lp-gap); }
.lp-b2 { grid-column: span 2; }
.lp-b3 { grid-column: span 3; }
.lp-b4 { grid-column: span 4; }
.lp-b6 { grid-column: span 6; }

/* ПРАВИЛО ОДНОГО АКЦЕНТА НА ЭКРАН: залита ровно одна кнопка — та,
   которую мы хотим, чтобы нажали. Остальные контурные. До этого
   в тарифах шли четыре синие кнопки подряд, и глаз не знал, куда идти. */
.lp .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  height: 52px; padding: 0 26px; border-radius: var(--lp-r-sm);
  font-weight: 600; font-size: 1rem; letter-spacing: -.01em;
  border: 1px solid transparent; text-decoration: none;
  /* ЦВЕТ ТЕКСТА ИЗ ПАЛИТРЫ, А НЕ ЗАШИТЫЙ (A_О_378_В по находке A_О_377_В).
     Здесь стоял `color: #fff` — копия решения, принятого до тёмной темы.
     15.08 ради тёмной темы завели `--btn-fg: #00275f`: заливка там светлее
     (`--fill-2: #6b8bff`), и белый на ней давал 3,11 при норме 4,5.
     Общая кнопка `.btn` переменную слушалась, витринная копия — нет,
     и беда жила на главной, /kk/, /en/ и /ru/pricing/ сразу. */
  color: var(--btn-fg, #fff);
  background: linear-gradient(135deg, var(--fill), var(--fill-2));
  box-shadow: 0 12px 30px -12px rgba(79, 140, 255, .75);
  transition: transform .15s ease, box-shadow .2s ease;
}
.lp .btn:hover { transform: translateY(-2px); }
/* Кольцо фокуса и здесь: у витринной кнопки своя тень, объявленная ниже
   общего правила, и без этой строки фокус на главной кнопке сайта
   не виден вовсе (замер обходом Tab, A_О_124). */
.lp .btn:focus-visible {
  outline: none;
  box-shadow: var(--ring), 0 12px 30px -12px rgba(65, 115, 209, .75);
}
.lp .btn-ghost {
  background: var(--lp-surface); color: var(--fg);
  border-color: var(--lp-line); box-shadow: none;
}

.lp-hero-actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 26px; }

/* Срок обеими половинами (оферта, п. 5.5). Спокойнее ведущего текста,
   но не мельче подписи: это условие договора, а не сноска. */
.lp-term { margin-top: 18px; font-size: 1rem; color: var(--muted); max-width: 56ch; }

/* Условие сделки и снятие возражений — не сноска (владелец, 12.08).
   Были 0,9rem цветом подписи, стали крупнее и ярче: это лучшее,
   что есть на первом экране. */
.lp-free-note { margin-top: 16px; font-size: 1.06rem; color: var(--fg); }
.lp-free-note b { font-weight: 650; }

/* Полоса доверия: три карточки, отвечающие на три главных возражения. */
.lp-trust {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--lp-gap); margin-top: clamp(28px, 4vw, 48px);
}
.lp-trust-card {
  display: flex; align-items: center; gap: 14px;
  padding: 16px 18px; border-radius: var(--lp-r-sm);
  background: var(--lp-surface); border: 1px solid var(--lp-line);
}
.lp-trust-card svg { width: 24px; height: 24px; color: var(--accent); flex: none; }
.lp-trust-card b { display: block; font-size: 1.05rem; color: var(--fg); }
.lp-trust-card i { font-style: normal; font-size: .95rem; color: var(--muted); }

/* Экран телефона. СВОИ ПЕРЕМЕННЫЕ (грабля D): рамка и подложка кадра
   не берутся из палитры страницы — на светлой теме кадр обязан
   остаться тёмным, иначе белый силуэт на белом исчезает. */
.lp-phone {
  position: relative; aspect-ratio: 9 / 16; width: min(300px, 72vw);
  margin-inline: auto; border-radius: 28px; overflow: hidden;
  background: var(--lp-phone-bg); border: 1px solid var(--lp-phone-line);
  box-shadow: 0 30px 70px -30px rgba(3, 7, 18, .9);
}
.lp-phone .lp-scene { position: absolute; inset: 0; width: 100%; height: 100%; }
.lp-phone .lp-wave { position: absolute; left: 0; right: 0; bottom: 0; height: 22%; width: 100%; }
.lp-phone .lp-tag {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  font-size: .8rem; padding: 5px 10px; border-radius: 999px;
  /* ПЛАШКА СЛЕДУЕТ ЗА КАДРОМ, А НЕ ЗА ПАМЯТЬЮ (A_О_378_В). Здесь были
     зашиты `rgba(0, 0, 0, .45)` и `#e9eefb` — годные, пока кадр был
     тёмным в обеих темах. С 27.08 в светлой теме кадр светлый
     (`--lp-phone-bg: #dfe7f3`), и та же плашка выцветала до серой:
     3,47 при норме 4,5. Плотность теперь живёт переменной и своя
     у каждой темы. */
  background: var(--lp-tag-plate); color: var(--lp-tag-fg);
}
/* Субтитры макета: подсветка слова — общий знаменатель всех
   конкурентов и то, что клиент увидит в готовом клипе. */
.lp-phone .lp-sub {
  position: absolute; left: 12px; right: 12px; bottom: 8px; z-index: 2;
  text-align: center; padding: 8px 14px;
  /* Подложка под буквами (слово владельца 27.08): текст лежит на
     пёстрой фигуре, без плашки он тонул в обеих темах. */
  background: var(--lp-sub-plate); border-radius: 12px;
  backdrop-filter: blur(3px);
  font-weight: 800; font-size: clamp(1.05rem, 4.6vw, 1.35rem);
  line-height: 1.25; color: var(--lp-sub-fg); text-shadow: var(--lp-sub-shadow);
}
.lp-phone .lp-sub em { font-style: normal; color: var(--lp-phone-accent); }

/* ЦЕНЫ. Карточка по центру: имя, сумма, объём, цена за минуту и за клип
   столбиком — так их и утвердил владелец. Кнопка во всю ширину и прибита
   к низу, поэтому карточки в ряду одной высоты при разной длине текста. */
.lp-price-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--lp-gap); }
.lp-price { position: relative; align-items: center; text-align: center; gap: 2px; }
/* `margin-top: auto` у кнопки НЕТ намеренно: свободное место забирает
   `.lp-perclip` ниже, а два `auto` в одной колонке делят его пополам
   и разводят разделительную черту в ряду карточек (поймано снимком
   15.08). */
.lp-price .btn { width: 100%; }

/* Имя пакета — фирменным градиентом. `-webkit-text-fill-color` обязателен
   рядом с `background-clip`: без него в части браузеров текст остаётся
   залитым обычным цветом и градиент не виден вовсе. */
.lp-tier {
  font-size: 2rem; font-weight: 700; letter-spacing: -.025em; margin-bottom: 4px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
.lp-sum {
  font-size: 2.15rem; font-weight: 700; letter-spacing: -.035em;
  color: var(--fg); font-variant-numeric: tabular-nums;
}
.lp-mins { font-size: 1.02rem; color: var(--fg); font-weight: 600; margin-top: 6px; }
/* «Это 10 часов записи» — минуты в привычную покупателю меру */
.lp-hrs { font-size: .95rem; color: var(--muted); }
/* «≈ 30–40 клипов» — то, за чем человек пришёл, поэтому цветом текста,
   а не приглушённым: он покупает клипы, а минуты и тенге переводит в них
   в уме. Вес как у `.lp-mins`, но БЕЗ `margin-top`: два отступа подряд
   развели бы строки сильнее, чем остальные в карточке. */
.lp-clips { font-size: 1.02rem; color: var(--fg); font-weight: 600; }
.lp-per { font-size: .9rem; color: var(--muted); }
/* ПОДВАЛ КАРТОЧКИ ПРИБИТ К НИЗУ ЦЕЛИКОМ — черта, цена клипа и кнопка,
   а не одна кнопка. Свободное место забирает `margin-top: auto` ЗДЕСЬ,
   и второго `auto` в карточке быть не должно: два делят место пополам,
   и выравнивание исчезает.

   Куплено снимком 15.08: у «Старта» пропала строка про часы (она там
   повторяла минуты), кнопки остались на месте, а РАЗДЕЛИТЕЛЬНАЯ ЧЕРТА
   поднялась выше соседних — ряд карточек стал выглядеть свёрстанным
   по-разному. Прогон этого не видит вовсе: разметка верна, высота
   карточек прежняя. */
.lp-perclip {
  width: 100%; font-size: .95rem; color: var(--fg); padding-top: 10px;
  margin-top: auto; border-top: 1px solid var(--lp-line);
}
/* СТРОКА АПСЕЙЛА (D, 09.09) — ПОД ценой клипа и приглушена: это
   справка, а не обещание. Своего `margin-top: auto` у неё НЕТ и быть
   не должно: второй `auto` в колонке делит свободное место пополам
   и разводит разделительную черту в ряду (беда, купленная снимком
   15.08 и описанная выше). */
.lp-upsell {
  width: 100%; font-size: .85rem; color: var(--muted); padding-top: 6px;
}
/* КАРТОЧКА БЕЗ СТРОКИ АПСЕЙЛА (верхний пакет, A_О_407_В, слово владельца
   17.09). Свободное место, которое у соседей занимает апсейл, уходило НАД
   чертой — цена клипа висела ниже соседних с пустотой сверху. Здесь его
   забирает кнопка: черта встаёт на место соседей по ряду (строки над ней
   у них те же), кнопка — вровень с их кнопками. Один `auto` в колонке. */
.lp-price-bez-apsella .lp-perclip { margin-top: 0; }
.lp-price-bez-apsella .btn { margin-top: auto; }
/* Выбранный тариф приподнят и светится: глазу нужно за что зацепиться,
   а список из шести одинаковых карточек такого места не даёт. */
.lp-price-pick {
  transform: translateY(-6px); z-index: 1;
  border-color: rgba(79, 140, 255, .45);
  background: linear-gradient(180deg, rgba(79, 140, 255, .14), var(--lp-surface));
  box-shadow: 0 34px 64px -28px rgba(79, 140, 255, .55),
              0 4px 10px -4px rgba(3, 7, 18, .5),
              inset 0 1px 0 rgba(255, 255, 255, .14);
}
.lp-pill {
  position: absolute; top: -11px; right: 20px; padding: 4px 12px;
  border-radius: 999px; font-size: .76rem; font-weight: 600;
  /* Та же заливка — тот же цвет текста из палитры, см. `.lp .btn`. */
  color: var(--btn-fg, #fff);
  background: linear-gradient(135deg, var(--fill), var(--fill-2));
}

/* ПОЯВЛЕНИЕ ПРИ ПРОКРУТКЕ (владелец, 13.08: «покажи с анимацией, для
   телефонов её можно отключать»). Класс вешает СКРИПТ, и это правило,
   а не мелочь: пряча содержимое стилями, мы отдали бы видимость страницы
   на волю javascript — не отработал, и человек видит пустоту.
   На телефоне и при «меньше движения» появление выключено ниже. */
.lp-rv {
  opacity: 0; transform: translateY(16px);
  transition: opacity .65s cubic-bezier(.2, .7, .2, 1),
              transform .65s cubic-bezier(.2, .7, .2, 1);
}
.lp-rv.lp-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .lp-rv, .lp-rv.lp-in { opacity: 1; transform: none; transition: none; }
}
/* ЗДЕСЬ БЫЛИ 61 СТРОКА ДОСЛОВНОГО ДУБЛЯ и сняты 10.09 (A_О_379_В,
   слово владельца). Копия жила с 13.08 (90ab9b11) и повторяла
   `.lp-term`, `.lp-free-note`, `.lp-trust*` и весь блок `.lp-phone*`
   дословно — второй экземпляр стоял ниже и переписывал первый теми же
   значениями.

   ЧЕМ ЭТО БЫЛО ОПАСНО: дубль выглядит рабочим. Правишь первую копию —
   вид не меняется, потому что побеждает вторая; правишь вторую —
   работает, и никто не знает, что есть первая. 10.09 при починке
   плашки кадра пришлось править ОБЕ, чтобы не гадать.

   ЧЕМ ДОКАЗАНО, ЧТО СНОС НИЧЕГО НЕ МЕНЯЕТ: между копиями не было
   ни одного правила на те же селекторы (проверено разбором файла,
   не памятью); все свойства четырнадцати правил сняты браузером
   до и после сноса. Полный разбор — 538 элементов страницы, включая
   градиенты, тени, псевдоэлементы `::before`/`::after` и сетки: ОДНО
   расхождение, и оно про `.lp-phone.lp-rv.lp-in` — появление при
   прокрутке, застигнутое в разной фазе (opacity 0,982718 против
   0,982713). Из-за него же расходились полные снимки; снятые
   с `reduced_motion="reduce"` они совпали ПОБАЙТОВО с дублем и без.
   Сторож на повторы — `core/test_css_bez_doslovnyh_dublej.py`. */
/* ЗДЕСЬ БЫЛ МЁРТВЫЙ БЛОК СТИЛЕЙ ЦЕН и снят 16.08 (A_Д_131).
   Четыре правила под классы `.lp-price-name`, `.lp-price-sum`,
   `.lp-price-line`, `.lp-price-clip` — таких классов не было
   ни в одном шаблоне, ни в одном скрипте, ни в питоне: проверено
   поиском по всему дереву, не по памяти. Рядом лежал второй,
   дословно совпадающий `.lp-price-grid`; живой стоит выше.

   След прежней разметки карточек: её переписали, стили остались.
   Опасны они не весом, а тем, что читаются как действующие —
   и следующий, кто станет править вид карточек, поправит их,
   а на экране ничего не изменится. Живые правила цен — выше,
   в блоке `.lp-price-grid` / `.lp-price`.

   Запись оставлена намеренно: без неё через месяц это выглядит
   как «кто-то потерял стили», и блок вернут обратно. */


/* Первый экран: текст слева, кадр справа. На телефоне — одной колонкой
   (правило ниже, в последнем блоке раздела). */
.lp-hero {
  display: grid; grid-template-columns: 1.06fr .94fr;
  gap: clamp(24px, 4vw, 56px); align-items: center;
}

/* Шапка секции. Ширина 62 знака — предел, за которым строка на широком
   мониторе читается тяжело; `wide` снимает его там, где текст должен
   идти строкой, а не колонкой (владелец, 12.08). */
/* Заголовки секций — по центру и по возможности одной строкой
   (слово владельца 27.08, «как у Сравнения пакетов»). */
.lp-sec-head { max-width: none; margin-bottom: clamp(28px, 4vw, 52px); text-align: center; }
/* Капсулы-таймкоды остаются СЛЕВА (слово владельца 27.08: в центр
   уехали зря), по центру — только сами заголовки. Инлайн слушает
   text-align родителя, поэтому — флекс шириной по содержимому. */
.lp-sec-head .lp-eyebrow { display: flex; width: max-content; margin: 0 auto 0 0; }
.lp-sec-head h2 { font-size: clamp(1.6rem, 3.2vw, 2.4rem); white-space: nowrap; }
@media (max-width: 900px) { .lp-sec-head h2 { white-space: normal; } }
.lp-sec-head p, .lp-sec-head .lp-lead { margin-left: auto; margin-right: auto; max-width: 72ch; }
.lp-sec-head.wide, .lp-sec-head.wide p { max-width: none; }

/* Пояснения под ценами: их четыре подряд, и каждое — оговорка договора.
   Набраны спокойно, но не мелко: это условия сделки, а не сноски. */
.lp-note { margin-top: 12px; max-width: 70ch; font-size: .98rem; color: var(--muted); }

/* Выбранный тариф: рамка акцентом и метка. Заливки нет намеренно —
   акцент на экране один, и он у кнопки этой же карточки. */
.lp-price-pick { border-color: var(--accent); }


/* Макеты готового клипа: четыре вертикальных кадра 9:16 в ряд,
   на телефоне — по два (правило ниже, в телефонном блоке). */
.lp-samples { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--lp-gap); }
.lp-frame { margin: 0; }
.lp-frame img {
  width: 100%; height: auto; aspect-ratio: 9 / 16; object-fit: cover;
  border-radius: var(--lp-r-md); border: 1px solid var(--lp-line);
  background: var(--lp-phone-bg); display: block;
}
.lp-frame figcaption { margin-top: 8px; font-size: .95rem; color: var(--muted); text-align: center; }
/* Подпись о происхождении кадров — ближе к ним, чем прочие оговорки */
.lp-origin { margin-bottom: 18px; }

/* Надпись-рубрика внутри карточки */
.lp-num {
  font-size: .98rem; color: var(--muted); font-weight: 500;
  letter-spacing: .06em; text-transform: uppercase;
}

/* ГРАФИК ЖИВЁТ НА СВОЕЙ ТЁМНОЙ ПОДЛОЖКЕ В ОБЕИХ ТЕМАХ (грабля D):
   он нарисован под тёмное — подписи светло-серые, сетка белая
   с прозрачностью, — и на белой карточке светлой темы исчезал бы
   вместе с осями. Цвет здесь не следует за темой страницы. */


/* Номер шага крупно: цифра работает как акцент, а не как подпись.
   Была 0,78rem серым — читалась служебной мелочью (владелец, 12.08). */
.lp-step {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: clamp(2.4rem, 4vw, 3.4rem); line-height: 1; font-weight: 700;
  letter-spacing: -.02em; margin-bottom: 2px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* Таблица выхода клипов внутри карточки */
.lp-yield { width: 100%; border-collapse: collapse; margin-top: 6px; }
.lp-yield th, .lp-yield td { padding: 7px 0; border-bottom: 1px solid var(--lp-line); }
.lp-yield th { text-align: left; font-weight: 500; color: var(--muted); }
.lp-yield td { text-align: right; font-variant-numeric: tabular-nums; color: var(--fg); }
.lp-yield tr:last-child th, .lp-yield tr:last-child td { border-bottom: 0; }


/* КАРАОКЕ-ПОДСВЕТКА в макете кадра: слово за словом, как в готовом
   клипе. Спокойное состояние приглушено, подсвеченное — мятное,
   тем же цветом, что и подсветка нашего рендера. */
.lp-sub em span { color: var(--lp-sub-dim, rgba(255, 255, 255, .55)); transition: color .18s ease; }
.lp-sub em span.on {
  /* Жёлтая, как в утверждённом прототипе: на кадре с человеком она
     заметнее мятной и не спорит с синими наушниками. */
  color: var(--lp-sub-on, #ffd166); text-shadow: 0 2px 18px rgba(255, 209, 102, .5);
}

/* Шкала времени слева: линия с бегунком, едущим за чтением. Только там,
   где для неё есть поле, — на узком экране полезла бы на текст.
   Скрипт снимает `hidden`; без скрипта её просто нет. */
.lp-rail { display: none; }
@media (min-width: 1320px) {
  .lp-rail:not([hidden]) {
    display: block; position: fixed; top: 96px; bottom: 40px; z-index: 5;
    left: calc((100vw - var(--lp-max)) / 2 - 34px); width: 9px;
    pointer-events: none;
  }
  .lp-rail::before {
    content: ""; position: absolute; left: 4px; top: 0; bottom: 0; width: 1px;
    background: linear-gradient(180deg, transparent, var(--lp-line) 8%,
                                var(--lp-line) 92%, transparent);
  }
  .lp-rail b {
    position: absolute; left: 0; top: 0; width: 9px; height: 9px;
    border-radius: 50%; background: var(--accent);
    box-shadow: 0 0 0 3px rgba(79, 140, 255, .18), 0 0 14px rgba(79, 140, 255, .7);
    transition: top .08s linear;
  }
}


/* Якоря витрины в шапке */
.lp-anchor { color: var(--muted); text-decoration: none; }
.lp-anchor:hover { color: var(--fg); }
/* ПРАВИЛО БЫЛО НАПИСАНО И НЕ РАБОТАЛО (найдено снимком 14.08, A_О_100).
   Скрытие задавалось селектором `.lp-anchor` (0,1,0), а ниже по файлу
   ссылкам шапки объявлен `display: inline-flex` селектором
   `.topbar nav > a` (0,2,1) — он сильнее, и якоря оставались видимыми
   на всех телефонах. Комментарий рядом честно обещал «на узком экране
   прячутся», и поверить ему можно было только не открыв страницу.
   Поэтому здесь специфичность не ниже перебивающей.

   С 02.10 (A_О_514) якоря стоят в обёртке `.lp-anchors` — панели кнопки-
   меню на телефоне. На ПК обёртка `display: contents`, и якоря остаются
   в ряду шапки; прячется на узком экране вся обёртка, а раскрывает её
   флажок `razdelyToggle` (правило ниже, у `topbar-menu`). */
.lp-anchors { display: contents; }
@media (max-width: 1100px) {
  .topbar .lp-anchors { display: none; }
}

/* ШАПКА ВИТРИНЫ: тайм-код записи и полоса воспроизведения.
   Живут вне `.lp` — шапка общая, — поэтому у них свой префикс. */
.lp-hud {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 10px; border-radius: 9px; letter-spacing: .06em;
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .82rem;
  color: var(--fg); background: var(--lp-surface); border: 1px solid var(--lp-line);
}
/* Точка «идёт запись»: тем же цветом, что тайм-коды секций */
.lp-hud::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--lp-tc); box-shadow: 0 0 8px var(--lp-tc);
}
.lp-playhead {
  position: absolute; left: 0; bottom: -1px; height: 2px; width: 0;
  background: linear-gradient(90deg, var(--accent), var(--lp-tc));
  box-shadow: 0 0 14px rgba(79, 140, 255, .55); border-radius: 0 2px 2px 0;
}
/* На телефоне тайм-код прячем: места в шапке нет, а полоса остаётся —
   она ничего не занимает. */
@media (max-width: 900px) { .lp-hud { display: none; } }


/* Фирменный знак рядом с названием. `currentColor` в контуре — поэтому
   он темнеет и светлеет вместе с текстом, и отдельных правил на тему
   ему не нужно. */
.brand .mark { width: 1.5em; height: 1.5em; vertical-align: -.34em; margin-right: .34em; color: var(--accent); }
.brand { display: inline-flex; align-items: center; }
/* Подвал по утверждённому прототипу: ссылки строкой сверху, знак
   с описанием под ними. */
/* ПОДВАЛ ПО ШАБЛОНУ D (замечание владельца 13.08): всё по центру,
   ссылки на документы КРУПНЕЕ прочего — это единственная дорога
   к оферте и политике, — знак с описанием одной строкой под ними.

   ПЕРЕОПРЕДЕЛЯЯ ЧУЖОЙ БЛОК, СНИМАЮ ВСЕ ЕГО СВОЙСТВА, а не одно.
   Дважды за смену на этом стою: у `.card` осталась чужая
   `max-width: 440px` и блоки витрины разъехались, у ссылок подвала —
   `flex-direction: column`, и он шёл столбиком. Специфичность моего
   правила выше, но только для НАЗВАННЫХ свойств. */
.site-footer .footer-inner { display: block; text-align: center; }
.site-footer .footer-links {
  display: flex; flex-direction: row; flex-wrap: wrap;
  justify-content: center; gap: 10px 28px; margin-bottom: 4px;
}
/* Ссылки на документы — САМАЯ КРУПНАЯ строка подвала и самая яркая
   (владелец, 13.08: «крупнее остальных строк»). Это единственная дорога
   к оферте и политике, а всё прочее здесь — сноска. Разница в 1 px,
   как было, на глаз не читается вовсе: рядом стоят описание 1rem
   и реквизиты 0,9rem, и ссылки терялись между ними. */
.site-footer .footer-links a { font-size: 1.16rem; color: var(--fg); }
.site-footer .footer-links a:hover { color: var(--accent); }
.site-footer .footer-brand {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; margin-top: 28px; max-width: none; white-space: nowrap;
}
.site-footer .footer-brand p { margin: 0; font-size: .98rem; }
/* Реквизиты (© ИП, ИИН, почта, телефон) — их в прототипе нет вовсе,
   но владелец решил оставить: сведения о продавце видны с любой
   страницы. Самый мелкий кегль подвала — это подпись, а не довод. */
.site-footer .footer-bottom { justify-content: center; text-align: center; }
.site-footer .footer-bottom .muted { font-size: .88rem; }
@media (max-width: 820px) {
  .site-footer .footer-brand { flex-wrap: wrap; white-space: normal; }
  .site-footer .footer-brand p { font-size: .95rem; }
}

/* --- ТЕЛЕФОН: этот блок обязан оставаться ПОСЛЕДНИМ в разделе ------- */
@media (max-width: 900px) {
  /* Появление блоков на телефоне ВЫКЛЮЧЕНО (слово владельца): на слабой
     машине оно дёргается, а на узком экране блоки и так идут по одному. */
  .lp-rv, .lp-rv.lp-in { opacity: 1; transform: none; transition: none; }
  .lp-hero { grid-template-columns: 1fr; }
  .lp-bento, .lp-price-grid, .lp-trust { grid-template-columns: 1fr; }
  /* ПРОЛЁТЫ СБРАСЫВАЮТСЯ ВМЕСТЕ С КОЛОНКАМИ (A_О_378_В, слово владельца
     10.09: «карточки возможностей накладываются друг на друга»).

     Одной строки выше НЕ ХВАТАЛО: колонок остаётся одна, а у карточек
     живёт `grid-column: span 2|3|6`, и grid дорисовывает НЕЯВНЫЕ треки
     под запрошенный пролёт. Замерено на 390 px: сетка «Возможностей»
     выходила `6px 0px 0px 68.66px 68.66px 68.66px`, карточки вставали
     по две в ряд, и первая была раздавлена до 42 px из 302 — текст в ней
     наезжал сам на себя.

     ПОЧЕМУ ЭТО НЕ ЗАМЕТИЛИ РАНЬШЕ. В остальных сетках витрины пролёт
     не больше 3, и неявные треки складывались так, что карточка всё
     равно занимала всю ширину — случайно правильно. Рассыпается ровно
     та сетка, где есть `lp-b6`: только она просит шесть треков. Значит
     починка колонок была неполной с самого начала, а её зелёность
     держалась на составе карточек. */
  .lp-bento > * { grid-column: 1 / -1; }
  /* Кадры — по ДВА в ряд, а не по одному: вертикальный кадр во всю
     ширину телефона занял бы весь экран, и соседних было бы не видно. */
  .lp-samples { grid-template-columns: repeat(2, 1fr); }
  .lp-hero-actions .btn { width: 100%; }
}

/* ======================================================================
   ЕДИНЫЙ ВИД ОСТАЛЬНЫХ СТРАНИЦ (владелец, 13.08: «делай остальные
   страницы»).

   Витрина утверждена и живёт под `.lp`. Здесь тот же язык переносится
   на кабинет, заказы, покупки и формы — но НЕ КОПИЕЙ правил, а правкой
   базовых элементов: карточка, кнопка, поле, заголовок. Копия
   разошлась бы с витриной при первой же правке, а это ровно тот класс
   ошибок, на котором мы сегодня стояли трижды.

   ЧТО ИМЕННО ПЕРЕНОСИТСЯ: стеклянная поверхность вместо плоской
   заливки, единый радиус, объём тремя тенями, кнопка ростом 52 px
   с тем же радиусом, спокойные поля ввода. Цвета берутся из тех же
   переменных темы — отдельной палитры для «внутренних» страниц нет
   и заводить её нельзя.
   ====================================================================== */

/* Карточка — как на витрине. `max-width` НЕ ТРОГАЕМ: на страницах-формах
   она держит колонку в 440 px, и это осознанная ширина чтения. */
.card {
  background: linear-gradient(180deg, var(--lp-surface-2), var(--lp-surface));
  border-color: var(--lp-line);
  border-radius: var(--lp-r-md);
  box-shadow: var(--lp-card-shadow);
  backdrop-filter: blur(14px);
}

/* Кнопка: тот же рост и радиус, что на витрине. Раньше внутренние
   кнопки были ниже и с другим скруглением — рядом с витринными они
   читались как элементы другого сайта. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 52px; padding: 0 26px; border-radius: var(--lp-r-sm);
  box-shadow: 0 12px 30px -12px rgba(79, 140, 255, .75);
}
/* Контурная кнопка — второе действие на экране. Правило одного акцента
   действует и здесь: залита та кнопка, которую мы хотим, чтобы нажали. */
.btn-ghost {
  background: var(--lp-surface); color: var(--fg);
  border: 1px solid var(--lp-line); box-shadow: none;
}
.btn-ghost:hover { color: var(--fg); border-color: var(--border-hover); }

/* Заголовки страниц — тот же плотный трекинг, что на витрине */
h1, h2, h3 { letter-spacing: -.03em; }

/* Поля ввода: радиус и поверхность заодно с карточками */
.card input, .card select, .card textarea,
input[type="text"], input[type="email"], input[type="password"],
input[type="url"], input[type="number"], input[type="date"], select, textarea {
  border-radius: 12px; background: var(--lp-surface); border-color: var(--lp-line);
}

/* Панель — тот же материал, что карточка: на странице заказа они стоят
   рядом, и разница между ними читалась как недоделка. */
.panel {
  background: linear-gradient(180deg, var(--lp-surface-2), var(--lp-surface));
  border-color: var(--lp-line); border-radius: var(--lp-r-md);
  box-shadow: var(--lp-card-shadow);
}

/* Карточки и плашки внутренних страниц — тем же материалом, что витрина.
   Имена у них свои (`.order-card`, `.callout`, `.price-card`), поэтому
   правило общего вида приходится называть поимённо: единственная
   альтернатива — переименовать классы во всех шаблонах, а это правка
   ради правки. Список короткий и держится рядом с самим правилом. */
.order-card,
.price-card,
.callout,
.confirm-dialog,
.empty-state {
  background: linear-gradient(180deg, var(--lp-surface-2), var(--lp-surface));
  border-color: var(--lp-line);
  border-radius: var(--lp-r-md);
  /* ОТСТУП ВНУТРИ, а не только фон (A_Д_203). Фон рисовался, а padding
     был нулевой — замерено на 390: содержимое лежало вплотную к краю
     блока и читалось как обрезанное. На широком экране это почти
     не видно, и потому прожило долго. */
  padding: 22px;
  border-width: 1px;
  border-style: solid;
}
.order-card, .price-card { box-shadow: var(--lp-card-shadow); }
/* Карточке заказа — СВОЙ отступ (A_Д_298_В): общее правило выше даёт 22 px,
   а её сетка считает ширину от 12; при 22 обложка не помещалась. */
.order-card { padding: var(--card-pad, 12px); }
/* Плашке тень не даём: она лежит В карточке, и вторая тень внутри
   первой читается как ошибка вёрстки, а не как объём. */
.callout { border-radius: var(--lp-r-sm); }

/* ШАПКА НЕ ЛОМАЕТСЯ У ВОШЕДШЕГО (нашёл владелец 13.08: «при
   возвращении на главное лого съезжает на строку выше»).

   ЭТО НЕ РЕГРЕСС ДИЗАЙНА — проверено замером на БОЕВОМ коде, там
   ровно та же высота 117 px вместо 72. Просто у вошедшего в шапке
   девять элементов (имя, кабинет, стиль клипов, баланс, пробное
   видео, «новая нарезка», выход, тема, язык), и на главной к ним
   добавляется знак: 975 px содержимого плюс девять промежутков
   не помещаются в 1080, и форма языка уезжает на вторую строку.

   Лечим двумя мерами, обе — от тесноты, а не от переноса:
   промежуток между пунктами меньше, и ИМЯ КЛИЕНТА прячется там,
   где места мало. Имя не теряется: оно есть в кабинете, а в шапке
   было подсказкой «под кем вошёл», а не средством работы. */
/* БЕЗ ПРИВЯЗКИ К ШИРИНЕ ОКНА, и это поправка к моей же первой попытке:
   я спрятала имя ниже 1500 px — и на 1680 шапка сломалась снова.
   Контейнер шапки ограничен `--maxw` (1080 px), поэтому на широком
   экране места НЕ ПРИБАВЛЯЕТСЯ ВОВСЕ: разъезд одинаков и на 1280,
   и на 1920. Замер показал это сразу, а рассуждение — нет. */
/* ИМЯ УЧЁТКИ ОСТАЁТСЯ (владелец заметил пропажу 13.08): человек должен
   видеть, под кем вошёл, — особенно когда учёток несколько. Тесноту
   снимаем промежутком, а не выбрасыванием содержимого: 975 px
   содержимого при девяти промежутках по 10 px дают 1065 из 1080,
   и шапка держится в один ряд. Считано, а не подобрано. */
.topbar nav { gap: 10px; }
/* ШАПКЕ ШИРЕ, ЧЕМ ТЕКСТУ. Колонка чтения ограничена намеренно (70 знаков
   в строке), но шапка — не текст: в ней девять элементов, и при общей
   ширине 1080 они не помещались, даже когда я сжала промежутки.
   Считать надо было по ДОСТУПНОМУ месту: у контейнера ещё отступы
   по 24 px с каждой стороны, то есть на содержимое остаётся 1032,
   а нужно 1077. Первый мой расчёт этого не учёл — поймал замер. */
.topbar-inner { max-width: 1240px; }
/* Длинное имя не должно распирать шапку: сокращаем многоточием.
   Полное видно во всплывающей подсказке (title) и в кабинете. */
.topbar .whoami {
  max-width: 14ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ОДИН СТИЛЬ КНОПОК НА ВЕСЬ САЙТ (владелец, 13.08: «в стилях кнопки
   квадратные, нужно везде один стиль — с закруглением»).

   Радиусы разъехались по файлу: 8 px у мелких и опасных, 12 у отмены,
   14 у главной. А `.btn-secondary` не был описан ВООБЩЕ — на странице
   «Стиль клипов» браузер рисовал его своей квадратной кнопкой, и это
   и был самый заметный случай.

   Радиус берётся из той же переменной, что у витрины: одно число
   на весь сайт, а не пять похожих. */
.btn,
.btn-danger,
.btn-small,
.btn-secondary,
.btn-cancel,
.btn-step,
.btn-wide,
button.linkbtn:not(.lang-go) { border-radius: var(--lp-r-sm); }

/* Второстепенное действие рядом с главным: контур вместо заливки.
   Раньше правила не было вовсе — кнопка выглядела системной. */
.btn-secondary {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 52px; padding: 0 26px; font-size: var(--fs-base); font-weight: 600;
  background: var(--lp-surface); color: var(--fg); cursor: pointer;
  border: 1px solid var(--lp-line); box-shadow: none;
  transition: transform .15s ease, border-color .2s ease;
}
.btn-secondary:hover { color: var(--fg); border-color: var(--border-hover); transform: translateY(-2px); }

/* Ссылка удаления учётки — рядом с почтой и мелко (владелец, 13.08).
   Действие редкое и необратимое: ему не место среди рабочих кнопок,
   но и прятать его нельзя — право на удаление даёт закон. */
.erase-link { margin-left: 12px; font-size: .88rem; color: var(--muted); }
.erase-link:hover { color: var(--fg); }

/* Убрать заказ из очереди — крестик в строке рядом со стрелкой «открыть».
   Спокойный по цвету: действие обратимо по деньгам (минуты возвращаются),
   но заказ пропадает, и кричащая красная кнопка в ряду с перетаскиванием
   провоцировала бы случайные нажатия. Подтверждение спрашивается всегда. */
.queue-drop { display: inline-flex; margin: 0; }
.queue-drop-btn {
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  border-radius: var(--lp-r-sm); cursor: pointer; font: inherit; line-height: 1;
  min-width: var(--palec); min-height: var(--palec);
}
.queue-drop-btn:hover { color: #e5484d; border-color: #e5484d; }

/* Строка о точной сумме — крупнее и ярче обычной подсказки: человек
   вводит сумму руками в эту самую минуту, и это единственное, что стоит
   между ним и возвратом всего платежа (владелец, 13.08). */
.pay-exact { color: var(--fg); font-size: var(--fs-base); }

/* --- Что показывать без JavaScript ---------------------------------------
   Обещания вроде «перетащите блок» без скриптов неправдивы: сами блоки
   рисует JS. Пока на <html> стоит `no-js` (снимается первым же скриптом),
   такие подписи скрыты, а вместо них показывается честная. */
.no-js .js-only { display: none; }
.only-no-js { display: none; }
.no-js .only-no-js { display: inline; }


/* --- ТЕЛЕФОН: 430 / 390 / 360 / 320 ---------------------------------------
   БЛОК ДЕРЖИМ ПОСЛЕДНИМ В ФАЙЛЕ (предупреждение D, проверено ею). Правила
   здесь узкие и обязаны перебивать всё, что объявлено выше; вставленный
   в середину, он молча проигрывает более поздним селекторам той же силы,
   и телефон «чинится» лишь местами.

   Что чинится, увидено снимками окна на 390 и 320, а не чтением кода:
   на 320 шапка занимала ТРИ строки, заголовок — ЧЕТЫРЕ, и до кнопок
   человек не доходил вовсе: первый экран целиком уходил на меню и
   заголовок. На 390 заголовок шёл в три строки при правиле D
   «не более двух на телефоне».

   Кегль трогаем только у первого экрана: шкала размеров заведена не зря,
   и менять её целиком ради телефона значило бы переверстать весь сайт
   (правило D «новые размеры не заводить»). */
@media (max-width: 430px) {
  /* Заголовок первого экрана: своя формула, а не общая. Верхняя граница
     ниже настольной, нижняя — чтобы на 430 он всё ещё читался крупно. */
  .lp h1 { font-size: clamp(1.75rem, 7.4vw, 2.3rem); }

  /* Шапка: сжимаем промежутки и делаем меню плотнее. Три строки меню
     на телефоне — это 200 с лишним точек до первого слова о деле. */
  .topbar-inner { gap: 8px; padding: 10px 12px; }
  .topbar nav { gap: 8px 10px; }
  .topbar nav a, .topbar .linkbtn { font-size: var(--fs-sm); }

  /* Плашка над заголовком: на узком экране её текст ломался на три
     строки и спорил с самим заголовком за внимание. */
  .lp-hud { font-size: var(--fs-sm); }

  /* Значок «+1 пробное видео» — у ВОШЕДШЕГО шапка и без него длинная
     (почта, кабинет, баланс, «новая нарезка», выход, тема, язык),
     и на 390 она занимала ТРИ строки: больше двухсот точек до первого
     слова о деле. Значок при этом ничего не теряет — в кабинете про
     пробное видео стоит отдельный блок с полным объяснением. */
  .trial-badge { display: none; }

  /* Выбор языка — самый широкий элемент шапки, и из-за него меню
     переносится на лишнюю строку: на 390 он висел на ней один.
     Сужаем, а не прячем: без выбора языка казахоязычный клиент
     остаётся на русской странице. */
  .lang select { max-width: 108px; }
}

@media (max-width: 360px) {
  .lp h1 { font-size: clamp(1.55rem, 8vw, 1.95rem); }

  /* Плашка над заголовком: на 320 её текст ломался на ЧЕТЫРЕ строки
     и занимал больше места, чем сам заголовок. Тайм-код внутри —
     украшение (он же продублирован в шапке), и на самом узком экране
     первым уходит именно он, а не смысл: «Казахская речь — проверено
     тестами» это наше единственное отличие от конкурентов. */
  .lp-eyebrow b { display: none; }
  .lp-eyebrow { padding: 8px 14px; }
}

@media (max-width: 320px) {
  .lp h1 { font-size: 1.45rem; }
  .topbar-inner { padding: 8px 10px; }
  .topbar nav { gap: 6px 8px; }
}

/* РАЗДЕЛ ПОМОЩИ (A_О_131).

   Описание статьи — с новой строки. В одну строку со ссылкой оно
   читалось слитно: «Сколько клипов получится из моего видео Откуда
   берётся число клипов…» — глаз не отделяет заголовок от пояснения.
   Поймано снимком, а не чтением разметки: в коде это две разные
   сущности, а на экране одна строка. */
.help-list { list-style: none; padding: 0; display: grid; gap: 16px; }
.help-list li { padding-left: 0; }
.help-list a { font-weight: 600; }
.help-list .hint { display: block; margin-top: 2px; }

/* Таблицы в статьях прокручиваются ВНУТРИ себя, а не тянут страницу
   вбок: на телефоне уехавший правый край уносит как раз цену. */
.help-article { overflow-wrap: break-word; }
.help-table { width: 100%; border-collapse: collapse; margin: 14px 0; }
.help-table td { padding: 7px 10px; border-bottom: 1px solid var(--border); }
.help-table td:not(:first-child) { text-align: right; white-space: nowrap; }
.help-tail { margin-top: 18px; }

/* ОКНО ЧАТА В ПОМОЩИ (A_О_131). Не всплывающий пузырь в углу, а блок
   внизу страницы помощи: пузырь закрывает содержимое на телефоне
   и требует места, которого там нет. Человек всё равно доходит сюда
   после статей — значит и спрашивать будет здесь. */
.help-chat { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); }
.help-chat-form { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.help-chat-form input { flex: 1 1 220px; }
.help-chat-answer { margin-top: 12px; padding: 12px 14px; border-radius: 10px;
  background: var(--surface-2); white-space: pre-line; }

/* ВЫБОР ФОРМАТА КЛИПА НА ФОРМЕ ЗАКАЗА (A_О_230, вид выбран владельцем
   по снимкам 21.08). Три плитки в ряд, БЕЗ КАРТИНОК: форму кадра
   показывает пустая рамка нужного соотношения. Прежняя редакция несла
   в плитках живые кадры, и владелец, посмотрев экран, сказал «три
   картинки рядом… странно выглядит» — крупный кадр остался один,
   на подтверждении.

   ЛИШНИЙ КЛАСС В СЕЛЕКТОРЕ НЕ ЛИШНИЙ: ниже живёт
   `.panel label { display: block }`, и оно тяжелее одиночного класса.
   Первая редакция писала `.format-pick-tile` — правило выглядело верным
   и не работало вовсе: рамка, название и подсказка вставали друг на
   друга, подписи наезжали на соседнюю плитку. Из кода этого не видно,
   нашлось снимком; у нас это ЧЕТВЁРТЫЙ такой случай (первые три —
   `.fragment-scope-item` и соседи выше по файлу). */
.format-pick { margin-top: 4px; }
.format-pick-radio { position: absolute; left: -9999px; opacity: 0;
  pointer-events: none; }
.format-pick-tiles { display: flex; gap: 8px; }
.format-pick .format-pick-tile { flex: 1 1 0; min-width: 0; display: flex;
  flex-direction: column; align-items: center; gap: 5px; padding: 10px 4px;
  margin-bottom: 0; cursor: pointer; text-align: center; font-weight: 400;
  border: 1px solid var(--border); border-radius: 10px; }
/* Рамка повторяет соотношение сторон: подпись словами человек читает,
   а форму кадра — узнаёт, не читая. */
.format-pick-frame { display: block; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 4px; }
.format-pick-9_16 { width: 22px; height: 39px; }
.format-pick-16_9 { width: 39px; height: 22px; margin: 8px 0 9px; }
.format-pick-1_1  { width: 31px; height: 31px; margin: 4px 0; }
/* Полное русское название втроём в 375 px не помещается — измерено
   снимком: «Горизонтальный» переносится, и это нормально, а вот
   наезжать на соседа ему нельзя. Отсюда `min-width: 0` выше
   и перенос по буквам здесь. */
.format-pick-name { font-size: 11px; line-height: 1.2; font-weight: 600;
  overflow-wrap: break-word; }
.format-pick-hint { font-size: 10px; line-height: 1.15; color: var(--muted);
  overflow-wrap: break-word; }
.format-pick-radio[value="9_16"]:checked ~ .format-pick-tiles .format-pick-tile[for="format-9_16"],
.format-pick-radio[value="16_9"]:checked ~ .format-pick-tiles .format-pick-tile[for="format-16_9"],
.format-pick-radio[value="1_1"]:checked  ~ .format-pick-tiles .format-pick-tile[for="format-1_1"] {
  border-color: var(--accent); background: var(--surface-2); }
/* Клавиатура: обводка ведёт себя как у остальных полей формы. */
.format-pick-radio[value="9_16"]:focus-visible ~ .format-pick-tiles .format-pick-tile[for="format-9_16"],
.format-pick-radio[value="16_9"]:focus-visible ~ .format-pick-tiles .format-pick-tile[for="format-16_9"],
.format-pick-radio[value="1_1"]:focus-visible  ~ .format-pick-tiles .format-pick-tile[for="format-1_1"] {
  outline: 2px solid var(--accent); outline-offset: 2px; }

/* ОДНО ПРЕВЬЮ НА ПОДТВЕРЖДЕНИИ, под выбранный формат (A_О_230).
   Жёстких `width` и `height` у картинки больше нет: при 16:9 они врали
   бы вдвое и держали под кадром пустое место в полэкрана.

   СЕЛЕКТОР НАЧИНАЕТСЯ С `.split-media`, и это не украшение. Выше в этом
   же файле стоит `.split-media img { aspect-ratio: 9 / 16;
   object-fit: cover }` — оно тяжелее одиночного класса. Первая редакция
   писала просто `.confirm-shot-16_9`, и горизонтальный кадр приезжал
   ОБРЕЗАННЫМ в вертикальную рамку: измерено снимком, 297×528 вместо
   297×167. Хуже пустого места: клиент видел бы не тот кадр, который
   закажет. Это ПЯТЫЙ такой случай у нас — см. `.fragment-scope-item`
   и `.format-pick-tile` выше. */
.split-media img.confirm-shot { display: block; width: 270px;
  max-width: 100%; height: auto; aspect-ratio: 9 / 16;
  object-fit: cover; border-radius: 10px; }
/* Горизонталь — во всю ширину колонки: 270 px было бы вдвое мельче
   вертикали по высоте кадра, и оформление на ней не разглядеть. */
.split-media img.confirm-shot-16_9 { width: 100%; aspect-ratio: 16 / 9;
  object-fit: contain; }
.split-media img.confirm-shot-1_1 { width: 270px; aspect-ratio: 1 / 1;
  object-fit: contain; }

/* ==== A_О_269: перенос визуала с макетов Stitch (слово владельца 27.08) ==== */

/* (10) Строка «Профи» в таблице перекликается с бейджем карточки.
   Класс is-featured стоял в разметке с A_Д_190, а правила не было —
   наш класс «написано и не подключено». */
table.orders tbody tr.is-featured { background: var(--bg-soft); }
table.orders tbody tr.is-featured td { font-weight: 600; }

/* (11) Таблица по макету: шапка на своём фоне, числа по правому краю.
   Данные не меняются, только вид. */
.pricing-table thead th { background: var(--bg-soft); }
.pricing-table th:nth-child(2), .pricing-table td:nth-child(2),
.pricing-table th:nth-child(3), .pricing-table td:nth-child(3),
.pricing-table th:nth-child(5), .pricing-table td:nth-child(5),
.pricing-table th:nth-child(6), .pricing-table td:nth-child(6),
.pricing-table th:nth-child(7), .pricing-table td:nth-child(7) { text-align: right; }
/* Число и знак валюты не рвутся («100 000 / ₸» в узкой колонке кабинета,
   A_Д_453): тесной таблице место даёт прокрутка `.table-scroll`.
   Только на широком экране: на телефоне таблица без прокрутки, и там
   жёсткие числа сжимали «Это примерно» до слова в строку. */
@media (min-width: 701px) {
  .pricing-table td:nth-child(n+2):not(:nth-child(4)) { white-space: nowrap; }
}

/* (9) «Сколько стоит один клип» двумя карточками-репликами:
   у монтажёра / у нас. Тексты прежние (D), меняется только вид. */
.compare-replies { display: grid; gap: 12px; max-width: 760px; margin: 12px auto 0; text-align: left; }
.compare-reply {
  display: flex; gap: 14px; align-items: flex-start;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 16px 18px;
}
.compare-reply .compare-icon {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
  background: var(--bg-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
}
.compare-reply p { margin: 0 0 8px; }
.compare-reply p:last-child { margin-bottom: 0; }

/* (3) Подвал в одну строку: 1rem — по-прежнему крупнее сносок (0.9rem,
   слово владельца 13.08 сохранено), но семь ссылок помещаются в строку. */
.site-footer .footer-links { gap: 8px 20px; }
.site-footer .footer-links a { font-size: 1rem; }

/* (4) Активная страница в ссылках подвала подчёркнута: меню-строки
   в шапке у нас нет, подвал — единственная навигация по разделам. */
.site-footer .footer-links a.active { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }

/* (8) Капсулы языков в подвале телефона: селект в шапке на телефоне
   мелкий. Видны только на узком экране, на десктопе их нет. */
.footer-langs { display: none; }
@media (max-width: 640px) {
  .footer-langs { display: flex; justify-content: center; gap: 8px; margin: 10px 0 2px; }
  .footer-langs button {
    border: 1px solid var(--border); background: var(--bg-soft); color: var(--fg);
    border-radius: 999px; padding: 6px 14px; font-size: 0.95rem; cursor: pointer;
  }
  .footer-langs button[aria-current="true"] { border-color: var(--accent); color: var(--accent); }
}

/* (7) Свойства пакета — списком с галочками (A_О_269, макеты Stitch). */
.pack-props { list-style: none; padding: 0; margin: 6px 0 12px; display: grid; gap: 8px; text-align: left; }
.pack-props li { display: flex; gap: 9px; align-items: flex-start; }
.pack-props .tick { flex: 0 0 auto; color: var(--accent); margin-top: 2px; }

/* Верхнее меню (A_О_269): якоря главной + страница цен. На телефоне
   спрятано — разделы доступны из подвала. */
.topbar .topbar-menu { display: flex; gap: 22px; margin-left: 28px; margin-right: auto; }
.topbar .topbar-menu a { color: var(--muted); font-size: var(--fs-md); }
.topbar .topbar-menu a:hover { color: var(--fg); }
.topbar .topbar-menu a.active { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }
@media (max-width: 980px) { .topbar .topbar-menu { display: none; } }
/* КНОПКА-МЕНЮ РАЗДЕЛОВ НА ТЕЛЕФОНЕ (A_О_514, слово владельца 02.10).
   Флажок раскрывает тот же `topbar-menu` панелью под шапкой — ссылки
   не копируются (на главной — якоря витрины `.lp-anchors`). Панель вынута из потока (`absolute` от липкой шапки),
   поэтому открытие не двигает страницу, а закрытое состояние стиль
   знает до отрисовки: первый экран не прыгает (A_О_520). На ПК флажок
   и значок не рисуются вовсе — шапка там прежняя. */
.topbar-burger-check,
.topbar-burger { display: none; }
/* ВИД КНОПКИ — ВНЕ ШИРИНЫ, ВКЛЮЧЕНИЕ — ПО ШИРИНЕ (A_О_522). Пока
   `display: none`, вид ни на что не влияет; а включают кнопку два
   порога: везде с 980, на главной — уже с 1100, где прячутся её якоря.
   Одно описание вида на оба порога: две копии разошлись бы. */
/* Флажок невидим, но фокусируем: им управляют с клавиатуры. */
.topbar-burger-check {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.topbar-burger {
  align-items: center; justify-content: center;
  margin-left: auto; min-width: var(--palec); min-height: var(--palec);
  box-sizing: border-box; cursor: pointer; color: var(--fg);
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
}
.topbar-burger:hover,
.topbar-burger-check:checked + .topbar-burger { border-color: var(--accent); }
.topbar-burger-check:focus-visible + .topbar-burger { outline: 2px solid var(--accent); outline-offset: 2px; }
/* ГЛАВНАЯ НА 981–1100 (A_О_522, находка A_О_514). Якоря витрины прячутся
   с 1100 (ряд шапки не влезает), а кнопка везде включалась только с 980:
   посетитель с ноутбука 1024 оставался без навигации. Порог кнопки
   на главной — порог её якорей; узнаётся главная по самим якорям
   в шапке, а не по имени страницы. Панель якорей — здесь же: на 980
   этот блок тоже действует. */
@media (max-width: 1100px) {
  .topbar:has(.lp-anchors) .topbar-burger-check { display: block; }
  .topbar:has(.lp-anchors) .topbar-burger { display: inline-flex; }
  .topbar .topbar-burger-check:checked ~ nav .lp-anchors {
    display: flex; flex-direction: column; align-items: stretch; gap: 2px;
    position: absolute; top: calc(100% + 6px); right: 12px; z-index: 60;
    min-width: 220px; margin: 0; padding: 8px;
    background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, .28);
  }
  .topbar .topbar-burger-check:checked ~ nav .lp-anchors a {
    display: flex; align-items: center; min-height: var(--palec);
    padding: 0 12px; border-radius: 10px; color: var(--fg); font-size: var(--fs-base);
  }
  .topbar .topbar-burger-check:checked ~ nav .lp-anchors a:hover { background: var(--bg-soft); }
}
@media (max-width: 980px) {
  .topbar-burger-check { display: block; }
  .topbar-burger { display: inline-flex; }
  .topbar .topbar-burger-check:checked ~ .topbar-menu {
    display: flex; flex-direction: column; align-items: stretch; gap: 2px;
    position: absolute; top: calc(100% + 6px); right: 12px; z-index: 60;
    min-width: 220px; margin: 0; padding: 8px;
    background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, .28);
  }
  .topbar .topbar-burger-check:checked ~ .topbar-menu a {
    display: flex; align-items: center; min-height: var(--palec);
    padding: 0 12px; border-radius: 10px; color: var(--fg); font-size: var(--fs-base);
  }
  .topbar .topbar-burger-check:checked ~ .topbar-menu a:hover { background: var(--bg-soft); }
  .topbar .topbar-burger-check:checked ~ .topbar-menu a.active { color: var(--accent); background: var(--bg-soft); }
}
/* СТАТЬИ В ШАПКЕ ВЕЗДЕ (слово владельца 10.09). Свой блок, а не пункт
   `topbar-menu`: то меню прячется на узком экране и не рисуется ни
   на главной, ни вошедшему, а слово владельца — «в шапке», без оговорок.
   Одна ссылка помещается и там, где меню не помещалось: ширину проверяет
   браузерная проба, а не глаз. */
/* Карточки секции «Возможности» (A_О_269). */
/* Значки вдвое крупнее (A_О_399, макет владельца 17.09): 80 вместо 40. */
.lp-feature .lp-feature-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 80px; height: 80px; border-radius: 22px;
  background: var(--bg-soft); color: var(--accent); margin-bottom: 16px;
}
.lp-feature .lp-feature-icon svg { width: 44px; height: 44px; }
.lp-feature h3 { margin: 0 0 8px; }
.lp-feature p { margin: 0; color: var(--muted); }

/* (5) Капсула «что это» над заголовком первого экрана (текст D 27.08). */
.lp .lp-hero-pill {
  display: inline-flex; align-items: center; width: fit-content;
  border: 1px solid var(--border); border-radius: 999px;
  padding: 6px 14px; margin-bottom: 12px;
  color: var(--muted); font-size: 0.95rem;
}

/* (2) Полоса фактов под первым экраном: три числа из кода. */
.lp .lp-facts {
  display: grid; grid-template-columns: repeat(3, 1fr);
  /* Внутри первого экрана, зазор карточный (слово владельца 27.08). */
  gap: var(--lp-gap); margin-top: var(--lp-gap);
}
.lp .lp-fact {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 14px 18px; text-align: center;
}
.lp .lp-fact b { display: block; font-size: 1.5rem; color: var(--fg-strong); }
.lp .lp-fact span { color: var(--muted); font-size: var(--fs-md); }
@media (max-width: 760px) { .lp .lp-facts { grid-template-columns: 1fr; } }

/* ==== A_О_270 пакет 1: сквозной сайдбар кабинета ==== */
/* ЗАЗОР КАБИНЕТА — ОДНО ЧИСЛО НА ВСЕ ГРАНИЦЫ (A_Д_286_В, владелец 03.09
   глазами на стенде: «одинаковые отступы между левым меню, центральным
   блоком и правым»). Между меню и содержимым было 28, между колонками
   страницы заказа — 16, и три блока в ряд читались как два разных
   расстояния. Число живёт переменной, колонки заказа берут её же. */
/* `minmax(0, 1fr)`, а не `1fr`: колонка `1fr` не сужается меньше своего
   содержимого, и широкая таблица (сравнение пакетов, A_Д_453) раздвигала
   страницу за окно. Таблица сама прокручивается в `.table-scroll`. */
.cab-layout { --cab-gap: 28px; display: grid; grid-template-columns: 224px minmax(0, 1fr); gap: var(--cab-gap); align-items: start; }
/* ВЕРХ КАБИНЕТА ОДИНАКОВ ВО ВСЕХ РАЗДЕЛАХ (A_О_291_В, слово владельца
   в терминале 29.08: «при переходе по левому меню разная высота шапки»).

   Причина была не в шапке сайта, а в первом элементе раздела. Страницы
   с обёрткой `.page-head` держат `h1 { margin: 0 }`, а «Стиль клипов»,
   «Оформление покупки» и «Новая нарезка» ставят голый h1 — и он берёт
   БРАУЗЕРНЫЙ `margin-top` (~0.67em). Отсюда прыжок на два десятка
   пикселей при каждом переходе по меню: «Мои заказы» выше, «Стиль
   клипов» ниже.

   Лечим не в трёх шаблонах, а здесь: у любого первого элемента раздела
   верхнего поля нет. Тогда новая кабинетная страница получает ровный
   верх сама, без правил, которые надо помнить.

   МЕНЮ СТОИТ НА ОДНОЙ ЛИНИИ С ПРАВЫМ БЛОКОМ — по той же причине:
   раньше оно начиналось от верха сетки, а содержимое справа съезжало
   вниз на этот самый браузерный отступ. */
.cab-content > :first-child { margin-top: 0; }
.cab-content > form:first-child > :first-child { margin-top: 0; }
/* Первый блок ПОД полосой заголовка тоже без верхнего поля: расстояние
   задаёт сама полоса (`.page-head`, margin-bottom), а не тот, кто под
   ней оказался. Иначе «Мои заказы» с плашкой (`.callout`, своё поле
   14 px) начинались ниже «Настроек» с обычной карточкой — владелец
   увидел это глазами как «верхушки блоков не совпадают». */
.cab-content .page-head + * { margin-top: 0; }
/* ТОТ ЖЕ СЛУЧАЙ, КОГДА ШАПКА РАЗДЕЛА ЛЕЖИТ ВНУТРИ ФОРМЫ. В «Моих
   заказах» полоса заголовка обёрнута формой удаления заказов, и первый
   блок под ней — плашка про почту — оказывается соседом ФОРМЫ, а не
   полосы: правило выше его не достаёт, и плашка добавляла свои 14 px.
   Замер в браузере: заголовки на 109 у всех разделов, а первый блок
   157 в «Настройках» против 163 в «Моих заказах». */
.cab-content > form:first-of-type + * { margin-top: 0; }
/* ПОЯСНЕНИЕ РАЗДЕЛА — В ТАКОЙ ЖЕ КАРТОЧКЕ, КАК ВЕЗДЕ (слово владельца
   30.08). На «Стиле клипов» подзаголовок висел голым текстом на фоне
   страницы, тогда как на остальных страницах кабинета любой текст лежит
   в скруглённом блоке. Разнобой читается как недоделанная страница. */
/* Строка сохранения настроек: кнопка и подпись рядом (слово владельца
   30.08). Липкую полосу внизу пробовали — она не объясняла, что относится
   и к галкам; кнопка стоит теперь в каждой секции, а подпись говорит,
   что сохраняется вся страница разом. */
.settings-save-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 16px 0 0;
}
.settings-save-row .hint { margin: 0; }

/* ВЫБОР ГОЛОСА (A_О_289, образец владельца 30.08 — скрин «Голоса»).

   СТРОКАМИ, А НЕ ПЛИТКАМИ: голосов семьдесят, и плитками сорок мужских
   давали стену в четырнадцать рядов — женская группа терялась за краем
   экрана. В строке помещается имя целиком, а глаз идёт одним столбцом.

   Группы остаются раздельными (слово владельца: «отдельно мужские,
   отдельно женские»), кнопка «слушать» стоит у каждого голоса. */
.voice-pick { margin: 14px 0 0; }
.voice-pick-title { font-weight: 600; color: var(--fg-strong); margin: 0 0 4px; }
.voice-search {
  width: 100%; max-width: 420px; margin: 10px 0 4px;
  padding: 9px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface); color: var(--fg);
}
.voice-group { margin-top: 14px; }
.voice-group-title { font-weight: 600; margin: 0 0 6px; }
/* Список в своей рамке и с прокруткой: семьдесят строк подряд утопили бы
   форму заказа, а согласие с офертой и кнопка «Дальше» уехали бы
   за третий экран. Высота — примерно шесть строк, дальше прокрутка. */
.voice-list {
  list-style: none; padding: 4px; margin: 0;
  max-height: 320px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface);
}
.voice-item {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 10px; border-radius: 8px;
}
.voice-item + .voice-item { border-top: 1px solid var(--border); }
.voice-item:hover { background: var(--bg-soft); }
/* ВЫБРАННАЯ СТРОКА ВИДНА (слово владельца 30.08: «при клике по нужному
   голосу он выбирается»). Кликается вся строка, а не кружок радио:
   попасть в четырнадцать пикселей мышью — работа, которой мы клиента
   не нагружаем. */
.voice-item.is-chosen { background: var(--bg-soft); box-shadow: inset 0 0 0 1px var(--accent); }
.voice-item { cursor: pointer; }
.voice-item .voice-name {
  display: flex; align-items: center; gap: 10px;
  margin: 0; flex: 1 1 auto; cursor: pointer; min-width: 0;
}
/* Радио НЕ РАСТЯГИВАЕТСЯ. В панелях формы действует `input { width: 100% }`,
   и без этого правила кружок занимал всю строку: имя уезжало под него,
   а строка разваливалась на два уровня (поймано снимком). */
.voice-item input[type="radio"] {
  width: auto; flex: none; margin: 0; accent-color: var(--accent);
}
/* min-width: 0 — иначе nowrap-текст в flex не сжимается и раздувает
   сетку формы под свой min-content (A_Д_276, замер 02.09: форма 342 px,
   колонка 427; казахская подпись длиннее русской, и на kk страница
   уезжала вбок сильнее всего). */
/* ИМЯ НЕ СЖИМАЕТСЯ, СЖИМАЕТСЯ ПОДПИСЬ (снимок 02.09): с общим min-width:0
   на узком экране многоточие съедало имя — «Ал…», «Ар…», — а голос
   выбирают именно по имени; подпись «женский, выше среднего» вторична
   и одинакова у соседей. */
.voice-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 0 auto; max-width: 60%; }
/* ПОДПИСЬ ГОЛОСА — тише имени и НЕ ПЕРЕНОСИТСЯ: строк семьдесят,
   и вторая строка у каждой превратила бы список в простыню. Голос
   без подписи (незнакомый код) не оставляет пустого места: пустой
   span схлопывается сам. */
.voice-caption {
  color: var(--muted); font-size: 12px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto; min-width: 0;
}
/* Кнопка — кружок с треугольником, как в образце: подпись словом
   у семидесяти строк читалась бы как семьдесят одинаковых команд. */
.voice-play {
  flex: 0 0 auto; width: 34px; height: 34px; padding: 0; line-height: 1;
  font-size: 13px;
  border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--fg);
  cursor: pointer; display: inline-flex;
  align-items: center; justify-content: center;
}
.voice-play:hover { border-color: var(--accent); color: var(--accent); }
.voice-play.is-playing { border-color: var(--accent); color: var(--accent); }
.voice-missing { flex: 1 1 100%; margin: 4px 0 0; }

.cab-content > .page-lead {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px 20px;
  max-width: none; margin: 0 0 18px;
}
/* ЗАГОЛОВОК РАЗДЕЛА — ПО ЦЕНТРУ КОЛОНКИ (слово владельца 30.08:
   «Мои заказы, Платежи и Настройки сделать по центру как у Стиля
   клипов»). Раньше по центру стоял только «Стиль клипов», у которого
   полосы `.page-head` нет вовсе, — отсюда и разнобой.

   Действия (кнопка «Новая нарезка», «Пополнить баланс») остаются
   справа и вынуты из потока: иначе заголовок центрировался бы
   по остатку места и уезжал влево тем сильнее, чем длиннее кнопка.

   На узком экране действия возвращаются в поток и встают под
   заголовком: место кончилось, и накладывать их друг на друга нельзя. */
.cab-content .page-head {
  /* ЗАГОЛОВОК ПО ЦЕНТРУ СВОБОДНОГО МЕСТА (решение владельца формой
     30.08). Строгий центр по всей ширине пробовали — заголовок наезжал
     на кнопку «Новая нарезка», проверено снимком. Владелец выбрал
     ровные линии разделов ценой небольшого смещения влево там, где
     рядом стоит действие.

     Заголовок занимает всю строку и центрируется по колонке, а кнопка
     переезжает под него — так все разделы читаются одинаково. Цена:
     на «Моих заказах» и «Платежах» шапка раздела выше на строку. */
  flex-wrap: wrap;
}
/* ЗАГОЛОВОК СТРОГО ПО ЦЕНТРУ КОЛОНКИ, ДЕЙСТВИЯ — СПРАВА В ТОЙ ЖЕ
   СТРОКЕ (слово владельца 30.08: «кнопка должна остаться там, где была,
   и опускаться сама при более низком разрешении»).

   Действия вынуты из потока вправо, поэтому заголовок центрируется
   по ВСЕЙ ширине колонки, а не по остатку места — иначе он уезжал
   влево тем сильнее, чем шире кнопка.

   ПОДПИСЬ ПОД КНОПКУ, А НЕ РЯДОМ: «после подтверждения почты» вместе
   с кнопкой занимали пол-колонки и доезжали до центра — заголовок
   ложился на них (проверено снимком). Под кнопкой она читается так же,
   а полоса остаётся узкой. */
.cab-content .page-head { position: relative; }
.cab-content .page-head > h1 { flex: 1 1 100%; text-align: center; }
.cab-content .page-head > .page-head-actions {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  flex-direction: column; align-items: flex-end; gap: 6px;
}
@media (max-width: 900px) {
  /* Места нет — действия уходят под заголовок и центрируются вместе
     с ним: в одну строку кнопка и название раздела не помещаются. */
  /* Места нет — действия возвращаются в поток и встают под заголовком
     по центру: на телефоне кнопка справа налезала бы на название. */
  .cab-content .page-head { flex-wrap: wrap; }
  .cab-content .page-head > h1 { flex: 1 1 100%; }
  .cab-content .page-head > .page-head-actions {
    position: static; transform: none;
    flex: 1 1 100%; flex-direction: row; flex-wrap: wrap;
    align-items: center; justify-content: center;
  }
}
/* ЗАГОЛОВОК РАЗДЕЛА СТОИТ НА ОДНОЙ ЛИНИИ ВЕЗДЕ, даже там, где полосы
   `.page-head` нет вовсе: «Стиль клипов» и «Новая нарезка» ставят голый
   h1 и оказывались выше на 4 px и ниже на 68 px соответственно (замер
   в браузере, а не на глаз). Даём такому заголовку ту же полосу — минимум
   в палец, без верхнего поля, — и меню с блоками сходятся сами. */
.cab-content > h1:first-child,
.cab-content > form:first-of-type > h1:first-of-type,
.cab-content > .page-narrow:first-child > h1:first-child {
  margin-top: 0; margin-bottom: 8px;
  min-height: var(--palec);
  display: flex; align-items: center;
}
.cab-content > .page-narrow:first-child > h1:first-child,
.cab-content > h1.page-center:first-child { justify-content: center; }
/* МЕНЮ СТОИТ НА УРОВНЕ ПЕРВОГО БЛОКА СПРАВА (слово владельца 29.08:
   «левое меню нужно ниже опустить на уровень блока справа»).

   Справа сверху — заголовок раздела, и только под ним начинается
   карточка. Сайдбар же прижимался к верху сетки и висел выше всего
   правого столбца. Опускаем его на высоту строки заголовка: тогда
   верх меню совпадает с верхом первой карточки, а не с заголовком.

   Сдвиг равен ПОЛОСЕ ЗАГОЛОВКА (`.page-head`: минимум в палец плюс
   её нижнее поле) — тому же числу, которым эта полоса и держится.
   Второго числа здесь нет: поправят полосу — меню поедет за ней. */
.cab-sidebar { margin-top: calc(var(--palec) + 8px); }
@media (max-width: 900px) { .cab-sidebar { margin-top: 0; } }
.cab-sidebar {
  position: sticky; top: 90px;
  display: flex; flex-direction: column; gap: 16px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 14px;
}
/* Имя раздела над меню — заголовком, не ссылкой (слово владельца 27.08).
   Тем же весом, что заголовки панелей, но мельче: это подпись блока. */
.cab-title {
  margin: 0 0 2px; padding: 0 12px;
  font-weight: 600; color: var(--fg-strong); font-size: var(--fs-base);
}
.cab-menu { display: flex; flex-direction: column; gap: 4px; }
.cab-menu a { padding: 9px 12px; border-radius: 10px; color: var(--muted); }
.cab-menu a:hover { color: var(--fg); background: var(--bg-soft); }
.cab-menu a.active { color: var(--fg-strong); background: var(--bg-soft); font-weight: 600; }
.cab-side-bottom { border-top: 1px solid var(--border); padding-top: 12px; display: grid; gap: 10px; }
/* «Выйти» — той же ширины и осью, что «Пополнить баланс» над ней. */
.cab-side-bottom form { display: grid; }
.cab-side-bottom .btn-small { text-align: center; }
/* «Пополнить баланс» — В ОДНУ СТРОКУ (слово владельца 27.08): в узком
   сайдбаре надпись переносилась и кнопка становилась двухэтажной.
   Перенос запрещаем и даём тексту ужаться, а не вылезти за края. */
.cab-side-bottom .btn { white-space: nowrap; padding-left: 10px; padding-right: 10px; }
.cab-balance { margin: 0; color: var(--muted); }
.cab-balance b { color: var(--fg-strong); }
@media (max-width: 900px) {
  /* Телефон: сайдбар складывается в полосу над содержимым. */
  .cab-layout { display: block; }
  .cab-sidebar { position: static; flex-direction: row; align-items: center;
                 justify-content: space-between; flex-wrap: wrap; margin-bottom: 16px; }
  /* МЕНЮ ПЕРЕНОСИТСЯ, А НЕ ОБРЕЗАЕТСЯ (находка D 30.08, снимок 390 px).
     Прокрутка вбок была невидимой: полосы нет, и «Нас…» читалось как
     край страницы — «Настройки» пропадали для человека совсем. Это
     основная навигация, и на телефоне она стоит выше заголовка.

     Перенос вместо прокрутки: пунктов четыре, вторая строка дешевле
     потерянного раздела. `overflow` снят намеренно — с ним перенос
     не спасал бы, обрезка возвращалась при первом длинном названии. */
  .cab-menu { flex-direction: row; flex-wrap: wrap; overflow: visible; }
  .cab-menu a { white-space: nowrap; }
}

/* САМЫЙ УЗКИЙ ТЕЛЕФОН: 320 px, и казахская подпись длиннее экрана
   (A_Д_316). «Төлемдер мен есептен шығарулар» — тридцать знаков против
   двадцати трёх русских «Платежи и списания»; с `nowrap` пункт меню
   кабинета вылезал за край на 11 px, а по-русски всё сходилось,
   поэтому в код это не бросалось глазами и нашлось только замером.

   Перенос ВНУТРИ пункта, а не прокрутка: пунктов четыре, и потерять
   раздел хуже, чем занять вторую строку. */
@media (max-width: 360px) {
  .cab-menu a { white-space: normal; }
  .cab-side-bottom { border-top: 0; padding-top: 0; display: flex; align-items: center; gap: 12px; }
}

/* МЕНЮ КАБИНЕТА НА ТЕЛЕФОНЕ — ОДНОЙ СТРОКОЙ (A_О_514, слово владельца
   02.10). Развёрнутое, оно занимало ≈500 px на 390, и заголовок страницы
   уходил за первый экран. Строка — заголовок «Кабинет», ставший подписью
   флажка; нажатие раскрывает меню под ним, в потоке, столбцом.
   Флажок, а не скрипт: свёрнутость известна стилю до отрисовки, первый
   экран не прыгает (A_О_520). На ПК флажок не рисуется, обёртка `.cab-body`
   держит прежний промежуток, и сайдбар выглядит как раньше. */
.cab-toggle { display: none; }
.cab-body { display: flex; flex-direction: column; gap: 16px; }
@media (max-width: 640px) {
  .cab-sidebar { display: block; position: relative; top: auto; padding: 0; margin-bottom: 12px; }
  .cab-toggle {
    display: block; position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
  .cab-title {
    display: flex; align-items: center; gap: 10px; margin: 0;
    min-height: var(--palec); padding: 0 14px; cursor: pointer;
  }
  /* Значок меню слева и стрелка справа: строку читают как кнопку. */
  .cab-title::before {
    content: ""; width: 16px; height: 12px; flex: 0 0 auto;
    border-top: 2px solid currentColor; border-bottom: 2px solid currentColor;
    background: linear-gradient(currentColor, currentColor) center / 100% 2px no-repeat;
  }
  .cab-title::after {
    content: ""; margin-left: auto; width: 8px; height: 8px;
    border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
    transform: translateY(-2px) rotate(45deg); transition: transform .15s ease;
  }
  .cab-toggle:checked + .cab-title::after { transform: translateY(2px) rotate(-135deg); }
  .cab-toggle:focus-visible + .cab-title { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 14px; }
  .cab-body { display: none; }
  .cab-toggle:checked ~ .cab-body { display: flex; gap: 12px; padding: 0 14px 14px; }
  .cab-menu { flex-direction: column; flex-wrap: nowrap; }
  .cab-menu a { white-space: normal; }
  .cab-side-bottom { display: grid; border-top: 1px solid var(--border); padding-top: 12px; }
}

/* Подвалы карточек «Возможности» — факт за разделителем, как в макете. */
.lp-feature .lp-feature-fact {
  border-top: 1px solid var(--border);
  margin-top: 12px; padding-top: 10px;
  display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  font-size: var(--fs-md); color: var(--fg);
}
.lp-feature .lp-cap {
  border: 1px solid var(--border); border-radius: 8px;
  padding: 2px 8px; font-size: 0.85rem; color: var(--muted);
}

/* Фигура блогерши в hero — выше, чтобы нижняя плашка не наезжала
   (слово владельца 27.08). */
.lp-phone .lp-scene { transform: translateY(-7%); }

/* Заголовки «Сравнение пакетов» и «Сколько стоит один клип» — тем же
   шрифтом и размером, что центрированные заголовки секций (слово
   владельца 27.08); правило общее для страницы цен и главной. */
h2.sec-title, .lp h2.sec-title {
  font-size: clamp(1.6rem, 3.2vw, 2.4rem); text-align: center; letter-spacing: -.01em;
}

/* CTA на витрине — той же ширины, что секции выше (слово владельца
   27.08: блок был шире остальных). */
/* CTA живёт в том же lp-wrap, что секции: края совпадают
   по построению, а не подгонкой (слово владельца 27.08). */
.lp .lp-wrap .cta { max-width: none; margin-left: 0; margin-right: 0; }

/* ==== A_О_270 пакет 2: стат-карты и прогресс на дашборде ==== */
.cab-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: 4px 0 18px; }
.cab-stat {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 14px 18px; text-align: center;
}
.cab-stat b { display: block; font-size: 1.6rem; color: var(--fg-strong); }
.cab-stat span { color: var(--muted); font-size: var(--fs-md); }
@media (max-width: 700px) { .cab-stats { grid-template-columns: 1fr; } }

/* --- Страница «Настройки» (A_О_270 пакет 4) --- */
.settings-block {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px 22px; margin-bottom: 14px;
  /* ВО ВСЮ ШИРИНУ КОЛОНКИ (слово владельца 30.08). Прежние 640 px
     делали заголовок «Настройки» по центру колонки, а карточки под ним —
     уже и левее: центр заголовка не совпадал с центром содержимого.
     Ширину берёт колонка кабинета, как у остальных разделов. */
}
.settings-block h2 { margin: 0 0 12px; font-size: var(--fs-lg); }
.settings-block .settings-row { margin: 0 0 6px; }
.settings-mails { margin: 0 0 12px; padding-left: 20px; }
.settings-mails li { margin-bottom: 6px; }
.settings-langs { display: flex; gap: 8px; flex-wrap: wrap; }
.settings-langs button {
  padding: 8px 14px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--bg-soft); color: var(--fg); cursor: pointer;
}
.settings-langs button[aria-current="true"] {
  border-color: var(--accent); color: var(--fg-strong); font-weight: 600;
}

/* --- Форматы выгрузки на витрине и странице заказа (A_О_272_В) --- */
.lp-export-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
  border-top: 1px solid var(--border); padding-top: 14px; margin-top: 14px;
  font-weight: 600;
}
.lp-export-grid .muted { display: block; font-weight: 400; font-size: var(--fs-md); }
@media (max-width: 700px) { .lp-export-grid { grid-template-columns: 1fr; } }
.clips-export a, .clip-export a { text-decoration: underline; }
.clip-export { margin: 0; font-size: var(--fs-md); }
/* ПОДВАЛ ПЛИТКИ КЛИПА (A_О_280_В): ссылки на файлы отделены от кнопок
   отбивкой — глазами это разные вещи, кнопка делает, ссылка отдаёт файл.
   `justify-content: space-between` ставит SRT · VTT к правому краю,
   как в макете; на узкой плитке строка переносится сама. */
.clip-foot {
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 6px;
}
/* ОДНОЙ СТРОКОЙ (слово владельца 28.08): подпись и SRT · VTT рядом,
   переноса нет. Перенос делал из одной мысли две строки, и в сетке
   из девяти плиток это читалось как оборванный текст.

   РЯДОМ, А НЕ ПО КРАЯМ (слово владельца 06.09, A_О_342_В): было
   `justify-content: space-between`, и ссылки уезжали к правому краю
   плитки — между словом и ними зияла пустота во всю ширину. Ссылки
   стоят через пробел от подписи, как читается строка.

   ТЕСНО — ОБРЕЗАЕТСЯ ПОДПИСЬ, А НЕ ССЫЛКИ: подпись повторяется
   у каждой плитки и угадывается по обрывку, а обрезанная ссылка
   становится некликабельной буквой. Ради этого подпись обёрнута
   своим `span`: у голого текстового узла селектора нет. */
.clip-export { display: flex; justify-content: flex-start; gap: 6px; flex-wrap: nowrap; }
.clip-export-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clip-export-links { white-space: nowrap; flex: none; }
/* Значок в кнопке: размер от текста, чтобы не разъезжался при смене шрифта */
.btn-ico { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.ico {
  width: 1.15em; height: 1.15em; flex: none;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.clip-textlink { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; }

.queue-progress {
  flex: 0 0 90px; height: 6px; border-radius: 999px;
  background: var(--bg-soft); overflow: hidden; align-self: center;
}
.queue-progress i { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.queue-eta { color: var(--muted); font-size: var(--fs-md); white-space: nowrap; }

/* ==== Вход по макету (A_О_269 ряд, К11 + правки D) ==== */
.login-card { display: grid; gap: 8px; }
.login-title { text-align: center; margin: 0 0 8px; font-size: clamp(1.5rem, 3vw, 2rem); }
.login-label { color: var(--muted); font-size: var(--fs-md); }
.login-field { position: relative; display: flex; align-items: center; }
.login-field .login-ic {
  position: absolute; left: 12px; color: var(--muted); display: inline-flex;
  pointer-events: none;
}
.login-field input { width: 100%; padding-left: 40px; padding-right: 40px; }
.login-eye {
  position: absolute; right: 8px; border: 0; background: none;
  color: var(--muted); cursor: pointer; font-size: 1rem; padding: 4px;
}
.login-eye:hover { color: var(--fg); }
.login-forgot { text-align: center; margin: 6px 0 0; }


/* НАСТРОЙКИ: СЕКЦИИ — КАРТОЧКАМИ, ГАЛОЧКИ — СТРОКАМИ (образец владельца
   27.08, ~/Desktop/skriny/Настройки.png). Раньше секции шли подряд
   голым текстом, и блок уведомлений не читался как список выбора. */
.settings-block {
  border: 1px solid var(--border); border-radius: 14px;
  background: var(--surface); padding: 18px 20px; margin-bottom: 18px;
}
.settings-block h2 { margin-top: 0; }
.settings-mails { list-style: none; padding: 0; margin: 12px 0 16px; display: grid; gap: 10px; }
/* Галочка и текст на одной оси; длинная строка переносится под текстом,
   а не под квадратиком. */
.notify-row { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.notify-row input { margin-top: 3px; flex: 0 0 auto; }
.settings-notify .btn-small { margin-top: 4px; }


/* ВКЛАДКИ «ФИЗИЧЕСКОЕ ЛИЦО / ЮРЛИЦО ИЛИ ИП» (образец владельца 27.08).
   Сам кружок переключателя спрятан, но остаётся в разметке: он и есть
   поле, его читают клавиатура и программы чтения с экрана. */
.buyer-tabs {
  display: flex; gap: 4px; margin: 0 0 18px;
  border-bottom: 1px solid var(--border);
}
.buyer-tab {
  padding: 10px 18px; cursor: pointer; color: var(--muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.buyer-tab input {
  /* Прячем ВИДИМО, но не выключаем: `display: none` вынул бы поле
     из порядка обхода клавиатурой, и вкладку нельзя было бы выбрать
     без мыши. Точка размером 1 px остаётся фокусируемой. */
  position: absolute; opacity: 0; width: 1px; height: 1px;
  margin: 0; pointer-events: none;
}
.buyer-tab:has(input:checked) {
  color: var(--fg-strong); font-weight: 600;
  border-bottom-color: var(--accent); background: var(--bg-soft);
  border-radius: 10px 10px 0 0;
}
.buyer-tab:has(input:focus-visible) { box-shadow: var(--ring); border-radius: 10px 10px 0 0; }


/* ЗОНА ЗАГРУЗКИ ФАЙЛА (образец владельца 27.08). Само поле спрятано
   ВИДИМО, но не выключено: `display: none` вынул бы его из обхода
   клавиатурой, а подпись вокруг него — это и есть кнопка. */
/* СЕЛЕКТОР ТЯЖЕЛЕЕ ОБЩЕГО НАРОЧНО: в этом файле живёт `.panel label
   { display: block }` весом (0,1,1), и одиночный класс (0,1,0) он бы
   молча перебил — зона встала бы строкой вместо столбика. Поймал
   сторож core/test_specifichnost_ne_otmenyaet_pravilo.py, шестой
   случай того же класса. */
.panel .dropzone {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 22px; border: 1px dashed var(--border); border-radius: 12px;
  background: var(--bg-soft); color: var(--muted); cursor: pointer;
  transition: border-color .12s, color .12s;
}
.dropzone:hover { border-color: var(--accent); color: var(--fg); }
.dropzone-btn {
  padding: 8px 16px; border-radius: 9px; background: var(--surface);
  border: 1px solid var(--border); color: var(--fg); font-weight: 600;
}
.dropzone-name { font-size: var(--fs-md); }
/* СКРЫТОЕ ПОЛЕ ФАЙЛА ПРИБИТО К УГЛУ (A_Д_276, найдено замером 02.09):
   без left/top оно стояло в потоке справа от подписи и уезжало за край —
   документ на телефоне становился 437 px при окне 390, и вся форма
   заказа скроллилась вбок, на любом языке. Виден был не сам инпут
   (он прозрачный), а лишняя полоса прокрутки. */
.dropzone-input { position: absolute; left: 0; top: 0; opacity: 0; width: 1px; height: 1px; overflow: hidden; }
.dropzone:has(input:focus-visible), .field:has(.dropzone-input:focus-visible) .dropzone {
  border-color: var(--accent); box-shadow: var(--ring);
}
/* «или» между двумя способами — чертой через всю ширину, как в образце. */
.source-or {
  display: flex; align-items: center; gap: 12px;
  color: var(--muted); margin: 18px 0;
}
.source-or::before, .source-or::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}


/* ЯЗЫКИ ПЕРЕВОДА ПОСТРОЧНО (A_О_273, решение владельца 27.08):
   слева язык, справа «ещё и озвучка». Селектор тяжелее общего
   `.panel label` нарочно — иначе строка развалилась бы столбиком
   (тот же класс поломок, что у зоны загрузки). */
/* Строки языка (.lang-rows/.lang-row) сняты 03.09 (A_Д_288_В): выбор языка перевода стал выпадающим списком, как у дубляжа. */
  display: inline-flex; align-items: center; gap: 8px; margin: 0; cursor: pointer;
}
.panel .lang-row .lang-name { min-width: 160px; font-weight: 600; }
.panel .lang-row .lang-voice { color: var(--muted); }
.panel .lang-row input { width: auto; margin: 0; }

/* ГАЛКА ОЗВУЧКИ СТОИТ ОТДЕЛЬНОЙ СТРОКОЙ (A, 29.08, находка B глазами
   на стенде). С 28.08 язык выбирается один, и галка переехала из строки
   языка под список — а правила выше писаны для `.lang-row` и её
   не достали: квадратик получил общий стиль поля формы (во всю ширину)
   и уехал на свою строку по центру, оторвавшись от подписи.

   Отсюда правило шире случая: стиль, привязанный к МЕСТУ в разметке,
   умирает молча при переносе элемента. Поэтому здесь свой селектор,
   не зависящий от соседей.

   `is-off` — состояние «языка нет, озвучивать нечего»: галку не прячем,
   а гасим, иначе исчезнувший элемент читается как поломка страницы. */
.panel .lang-voice {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 4px 0 0; cursor: pointer; color: var(--muted);
}
.panel .lang-voice input { width: auto; margin: 0; }
.panel .lang-voice.is-off { opacity: .45; cursor: default; }

/* КАРТОЧКА ОЗВУЧКИ (слово владельца 30.08: «Ещё и озвучить нужно
   сильнее выделить, теряется»; образец — stitch_unified_ui_design_system).

   ПОЧЕМУ КАРТОЧКА, А НЕ ЖИРНЫЙ ШРИФТ У СТРОКИ: озвучка — единственная
   услуга этой страницы, которая УДВАИВАЕТ счёт, а выглядела как выбор
   языка, обычной галкой в ряду. Рамка и подложка выделяют её из ряда
   однотипных строк — глаз цепляется за границу блока, а не за начертание.

   `is-off` — то же состояние, что у прежней строки: языка нет,
   озвучивать нечего. Гасим прозрачностью, а не прячем: исчезнувший
   элемент читается как поломка страницы. Класс вешает тот же скрипт
   по `[data-voice-row]`, поэтому имя состояния оставлено прежним. */
.panel .voice-card {
  display: flex; align-items: flex-start; gap: 12px;
  margin: 12px 0 4px; padding: 14px 16px;
  border: 1px solid var(--fill); border-radius: var(--radius);
  background: color-mix(in srgb, var(--fill) 8%, var(--surface));
  cursor: pointer;
}
.panel .voice-card:hover { border-color: var(--fill-2); }
.panel .voice-card input {
  width: auto; margin: 2px 0 0; flex: none;
  /* Квадратик крупнее обычного: он один на карточку и он же — само
     действие. Мелкий на 14 px внутри рамки читается как значок. */
  transform: scale(1.3); transform-origin: left top;
  accent-color: var(--fill);
}
.panel .voice-card-body { display: block; }
.panel .voice-card-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 4px;
}
.panel .voice-card-title {
  font-weight: 700; font-size: var(--fs-base); color: var(--fg-strong);
}
/* Метка «ИИ-голос» — пилюлей: она отвечает на вопрос «кто читает»
   раньше, чем человек дойдёт до оговорки у списка голосов. */
.panel .voice-card-mark {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; padding: 2px 8px; border-radius: 999px;
  background: color-mix(in srgb, var(--fill) 22%, transparent);
  color: var(--fill);
}
.panel .voice-card-note {
  display: block; font-size: var(--fs-md); color: var(--muted);
}
.panel .voice-card.is-off { opacity: .45; cursor: default; }

/* ПЕРЕКЛЮЧАТЕЛЬ «СКОЛЬКО ГОЛОСОВ В ЗАПИСИ» (A_О_297).

   Django рисует RadioSelect списком <ul><li><label>, а общее правило
   панели растягивает поля во всю ширину — на снимке 30.08 кружки уехали
   к правому краю, а подписи встали отдельными строками. Тот же класс,
   что с галкой озвучки 29.08: стиль, привязанный к МЕСТУ в разметке,
   умирает при переносе элемента, поэтому здесь свой селектор.

   Рядом пилюль, а не столбец: вариантов семь, и столбцом они заняли бы
   экран высотой со список голосов, ради которого сюда и пришли. */
.panel .voice-count { margin: 14px 0 6px; }
/* Django рисует RadioSelect ВЛОЖЕННЫМИ <div>, а не списком: правила
   пишем по устройству разметки, а не по тому, что помнится. Проверено
   выводом самой формы, а не по памяти. */
.panel .voice-count > div {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 8px;
}
.panel .voice-count > div > div { margin: 0; }
.panel .voice-count label {
  display: inline-flex; align-items: center; gap: 6px; margin: 0;
  padding: 8px 14px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); cursor: pointer; font-weight: 600;
}
.panel .voice-count label:hover { border-color: var(--border-hover); }
.panel .voice-count input { width: auto; margin: 0; accent-color: var(--fill); }
/* Слот — свой блок с отступом сверху: без него шесть списков подряд
   сливаются в один, и человек не видит, где кончается голос 1. */
.panel .voice-slot { margin-top: 18px; }
/* ПОЛ ВНУТРИ СЛОТА (слово владельца 30.08) — двумя пилюлями рядом,
   тем же приёмом, что переключатель числа: это выбор ПОКАЗА, и он
   обязан читаться как часть слота, а не как ещё одно поле заказа. */
.panel .voice-gender { display: flex; gap: 8px; margin: 8px 0 10px; }
.panel .voice-gender label {
  display: inline-flex; align-items: center; gap: 6px; margin: 0;
  padding: 6px 12px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); cursor: pointer;
}
.panel .voice-gender input { width: auto; margin: 0; accent-color: var(--fill); }



/* СВЁРНУТЫЕ СПОСОБЫ ОПЛАТЫ, пока жива ссылка Kaspi (D_Д_349).
   Раскрыватель выглядит строкой-вопросом, а не кнопкой: это не действие
   с деньгами, а «покажите остальное». */
.pay-others { margin: 8px 0 18px; }
.pay-others > summary {
  cursor: pointer; padding: 12px 14px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface);
  font-weight: 600;
}
.pay-others > summary:hover { border-color: var(--accent); }
.pay-others[open] > summary { margin-bottom: 12px; }

/* Глоссарий защищённых слов в настройках (A_О_282): поле во всю ширину
   блока, как соседние списки, и с отбивкой сверху — оно про другое,
   чем галочки писем над ним. */
.settings-block textarea#protected-terms {
  width: 100%; padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface);
  color: inherit; font: inherit; resize: vertical;
}

/* ОКНО ПРАВКИ ПЕРЕВОДА (B_О_182, решение владельца 29.08). Было
   поле на каждую фразу — на длинном заказе это две сотни коротких
   полей, страница на несколько экранов и две трети ширины пустыми.
   Одно окно во всю ширину: текст читается сплошняком, правится
   целиком, а строка по-прежнему равна фразе. */
.translation-window {
  width: 100%; padding: 12px 14px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface);
  color: inherit; font: inherit; line-height: 1.6;
  /* Крупнее обычного поля (просьба владельца 29.08): это не ввод
     реквизитов, а ВЫЧИТКА двух сотен фраз глазами. */
  font-size: 1.05rem;
  resize: vertical; min-height: 260px;
  /* Перенос длинной строки не скрываем: горизонтальная прокрутка
     внутри поля правки — это правка вслепую. */
  white-space: pre-wrap; overflow-wrap: anywhere;
}

/* СОГЛАСОВАНИЕ ПЕРЕВОДА ДВУМЯ ОКНАМИ (B_Д_163_В, решение владельца
   29.08). Слева оригинал только для чтения, справа перевод для правки,
   строка в строку, с подсветкой пары. Одним окном вычитывать нечем:
   человек читает гладкий текст и не знает, о том ли он. */
.translation-pair {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
  align-items: start;
}

/* На узком экране колонки складываются В СТОЛБИК, а не сжимаются:
   две колонки по 160 px рядом — это не сравнение, а два обрубка. */
/* Узкий экран для пары перевода разбирается в общем медиазапросе
   (A_О_354_В): второй блок с тем же условием разводил узкоэкранные
   правила по разным местам файла. */

.translation-side { min-width: 0; }

/* Оригинал живёт по тем же меркам, что и окно правки: одинаковые
   поля, кегль и высота строки — иначе строки двух окон разъедутся
   по вертикали, и пара перестанет читаться как пара. */
.translation-source {
  overflow-y: auto; max-height: 420px; cursor: default;
  background: var(--surface-2, var(--surface));
}

.translation-line { padding: 0 2px; border-radius: 4px; }

/* Подсветка ПАРЫ: строка, на которой стоит курсор, и её оригинал. */
.translation-line.is-current {
  background: var(--accent-soft, rgba(255, 214, 102, 0.28));
}

/* ВЕРХ СТРАНИЦЫ ЗАКАЗА — ДВЕ КОЛОНКИ (владелец 29.08 глазами на стенде).
   Слева ход работы, справа сам заказ: порознь они занимали два экрана
   подряд и отвечали на один вопрос в двух местах. */
/* ОДНА ПАНЕЛЬ — ВСЯ ШИРИНА (владелец 30.08, снимок отменённого заказа:
   «узкий фрейм со статусом»).

   Колонки писаны для пары «Заказ + ход работы». У отменённого,
   у ждущего оплаты и у пробного второй панели нет вовсе — и карточка
   оставалась шириной 340 px посреди пустого экрана (замерено: контейнер
   780, дитя 340). Читается как незагрузившаяся страница, а не как
   «работа окончена».

   ПОЧЕМУ НЕ КЛАСС ИЗ ШАБЛОНА: панель хода работы показывается по трём
   разным условиям, и класс «нас двое» пришлось бы держать в согласии
   со всеми — это список, который надо помнить обновлять. Спрашиваем
   у самой разметки, сколько детей.

   БЕЗ `:has` (старый браузер) остаётся одна колонка на всю ширину:
   панели встанут друг под другом — не красиво, но и не узкая полоса. */
.order-top {
  display: grid; grid-template-columns: 1fr;
  gap: 16px; align-items: start;
}
.order-top:has(> * + *) {
  grid-template-columns: minmax(240px, 340px) 1fr;
}

/* КОЛОНКА ХОДА РАБОТЫ ТЯНЕТСЯ ДО НИЗА СОСЕДНЕЙ (владелец 29.08, глазами
   на стенде). Прежде она обрывалась по своему содержимому, и справа
   от неё оставалась ступенька в пол-экрана — две карточки разной длины
   читаются как незагрузившаяся страница, а не как пара колонок. */
.order-top { align-items: stretch; }
/* Левая колонка — обёртка (A_Д_286_В): ход обработки тянется до низа
   соседней колонки первым рядом, панель заминки под ним — по содержимому. */
.order-top > .order-top-left {
  align-self: stretch; display: grid; grid-template-rows: minmax(0, 1fr);
  grid-auto-rows: auto; gap: var(--order-top-gap); align-items: stretch;
}

/* Зазор под верхними колонками — ТОТ ЖЕ, что между ними (владелец 29.08).
   Разные отступы читаются как разные по смыслу границы, хотя граница
   здесь одна: «верх страницы кончился». Число одно и берётся из
   переменной, чтобы правка не разъехалась по двум местам. */
.order-top { --order-top-gap: var(--cab-gap, 16px); gap: var(--order-top-gap);
             margin-bottom: var(--order-top-gap); }

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

/* ЛЕНТА ШАГОВ В УЗКОЙ КОЛОНКЕ — В ОДИН СТОЛБЕЦ. Два столбца заведены
   для панели во всю ширину; в колонке 340 px они рвут шаги на две строки
   («Скачиваем / видео»), и порядок работы перестаёт читаться сверху вниз.
   Увидено снимком 29.08, из кода не видно ничего. */
.order-top .progress-steps { columns: 1; }

/* Окна согласования ОДНОЙ ВЫСОТЫ, и высота задаётся обоим, а не одному:
   левое короткое, правое на 18 строк — рядом они смотрелись как два
   разных элемента, а не как пара. Пустое место внизу левого честнее:
   оно показывает, что фраз столько же. */
.translation-pair { align-items: stretch; }
.translation-side { display: flex; flex-direction: column; }
.translation-window, .translation-source {
  height: 340px; max-height: 340px; box-sizing: border-box;
}
.translation-window { overflow-y: auto; resize: none; }

/* Строки оригинала — с воздухом между ними: слитый текст не даёт глазу
   найти ту строку, которую он правит справа. */
.translation-line { padding: 2px 4px; margin-bottom: 6px; }

/* Подсвеченная строка — с полосой слева: цвет фона на тёмной теме
   различается плохо, а полоса видна и боковым зрением. */
.translation-line.is-current {
  box-shadow: inset 3px 0 0 var(--accent, #f0a500);
  padding-left: 8px;
}


/* «перевод со звуком: английский» — ОДНОЙ СТРОКОЙ (владелец 29.08).
   Разорванное надвое, оно читается как две разные строчки сведений,
   и глаз ищет, к чему относится «английский». Колонка сведений шире
   этой строки, так что перенос здесь ничего не спасал. */
.nowrap-value { white-space: nowrap; }

/* НИЗКИЙ ЭКРАН: ШАПКА ЖМЁТСЯ (слово владельца 10.09, «уменьши шапку
   на коротком экране»).

   ЗАМЕРЕНО, А НЕ ПРИКИНУТО: на 360×640 шапка занимала 117 px из 640 —
   18% окна, — потому что содержимое переносится на ДВА РЯДА. Вместе
   с плашкой согласия (127 px) первому экрану оставалось 364 px, и обе
   кнопки уходили под плашку (A_О_383, замер D).

   ПОЧЕМУ ПО ВЫСОТЕ ОКНА, А НЕ ПО ШИРИНЕ: тесно не по ширине — на 360×800
   та же шапка не мешает ничему. Порог 700 px берёт короткие телефоны
   и не трогает ноутбук 1280×800.

   ПОЧЕМУ ПЕРЕОПРЕДЕЛЯЕТСЯ ПЕРЕМЕННАЯ, А НЕ `min-height` ПО МЕСТУ:
   от `--topbar-h` считаются кадр редактора и липкие отступы ниже
   (`--frame-h`, `top` у липкой панели). Поджав шапку мимо переменной,
   мы получили бы щель или наезд там, где их не ищут.

   ПОЧЕМУ БЛОК СТОИТ ЗДЕСЬ, В КОНЦЕ. Первую попытку я поставила рядом
   с базовым правилом шапки — и она не сработала: ниже, в запросах
   по ширине, `.topbar-inner` получает свои поля (строки 4734 и 4770),
   и при равной силе выигрывает ПОСЛЕДНЕЕ правило. Замер показал 117 px
   без изменений; порядок здесь — часть правила, а не вкусовщина. */
@media (max-height: 700px) {
  :root { --topbar-h: 52px; }
  .topbar-inner { padding: 6px 12px; gap: 6px 8px; }
  .topbar nav { gap: 6px 8px; }
  /* КЕГЛЬ МАРКИ НЕ ТРОГАЕМ, хотя соблазн был: 1.25rem → 1.1rem давало
     пару пикселей и роняло саму ссылку с 33 px до 29. Она и так ниже
     нормы попадания (44 px) — это отдельная беда, — но делать МЕНЬШЕ
     то, что уже мало, значит собирать пиксели с пальца. Поймал сторож
     сравнением короткого окна с высоким. */
  .topbar .nav-cta { padding: 6px 10px; }
  .topbar nav a, .topbar .linkbtn { line-height: 1.2; }
}

/* ======================================================================
   РЕДАКТОР ОФОРМЛЕНИЯ ПО МАКЕТУ 16.09 (A_О_394_В, слово владельца).
   Кадр в рамке телефона слева, справа — карточка «название и формат»
   и два раздела: «Базовые настройки» (открыт) и «Расширенные» (свёрнут).
   Внешней карточки у колонки полей больше нет — карточками стали
   сами разделы, поэтому поля ввода описаны здесь, а не через `.card`.
   Цвета только токенами: светлая тема получает свои сама.
   ====================================================================== */
.te-head { max-width: 1240px; margin: 0 auto 20px; }
.te-head h1 { margin: 0 0 4px; }
.te-head .hint { margin: 0; }

.te-phone {
  padding: 10px; border-radius: 34px;
  border: 2px solid var(--border-hover); background: var(--surface);
}
.te-phone .template-frame { border-radius: 24px; overflow: hidden; }
.te-chip {
  display: table; margin: 10px auto 0; padding: 4px 12px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); font-size: var(--fs-sm); color: var(--muted);
}

.template-fields { display: flex; flex-direction: column; gap: 18px; padding-bottom: 8px; }
.template-fields p { margin: 0 0 12px; }
.template-fields input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),
.template-fields select {
  width: 100%; padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--bg-soft); color: var(--fg);
}
.template-fields input:focus, .template-fields select:focus {
  border-color: var(--accent); outline: none;
}
.template-fields input[type="color"] {
  width: 64px; height: 38px; padding: 2px; cursor: pointer;
  border: 1px solid var(--border); border-radius: 10px; background: var(--bg-soft);
}
.template-fields label { font-size: var(--fs-sm); font-weight: 500; }
.template-fields p > label:first-child { display: block; margin-bottom: 6px; }
.template-fields .helptext { display: block; margin-top: 4px; font-size: var(--fs-sm); color: var(--muted); }

.te-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 16px 18px; margin: 0;
}
fieldset.te-card { padding-top: 6px; }
.te-card legend { padding: 0 6px; font-weight: 600; }

/* Название и формат рядом; на узком экране — столбиком */
.te-top { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
.te-top > p { margin: 0; }
.te-aspect { border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; margin: 0; background: var(--surface-2); }
.te-aspect .template-aspect-name { margin: 2px 0 0; font-weight: 600; }
.te-label { display: block; font-size: var(--fs-sm); font-weight: 500; color: var(--muted); }

/* Раздел — сворачиваемая карточка */
.te-section {
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  overflow: hidden;
}
.te-section > summary {
  display: flex; flex-direction: column; gap: 2px; padding: 16px 48px 16px 20px;
  cursor: pointer; list-style: none; position: relative; user-select: none;
}
.te-section > summary::-webkit-details-marker { display: none; }
.te-section > summary::after {
  content: ""; position: absolute; right: 22px; top: 50%;
  width: 9px; height: 9px; margin-top: -7px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg); transition: transform .15s;
}
.te-section[open] > summary::after { transform: rotate(-135deg); margin-top: -2px; }
.te-section > summary:hover { background: var(--surface-2); }
.te-section[open] > summary { border-bottom: 1px solid var(--border); }
.te-section-name { font-weight: 700; font-size: var(--fs-lg); color: var(--fg-strong); }
.te-section > summary .hint { margin: 0; font-size: var(--fs-sm); }
.te-section-body { display: flex; flex-direction: column; gap: 16px; padding: 16px; }
.te-section-body .te-card { background: var(--bg-soft); }

/* Сетка полей: двенадцать долей, чтобы в одной строке вставали и пары,
   и тройки. По умолчанию поле во всю ширину; парам и тройкам —
   их доли по имени поля (`data-field` ставит `_template_field.html`). */
.te-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 16px; }
.te-grid > * { grid-column: span 12; }
.te-grid > [data-field="outline"], .te-grid > [data-field="max_line_chars"],
.te-grid > [data-field="pause_cut_sec"], .te-grid > [data-field="punch_in_zoom"],
.te-grid > [data-field="end_fade_sec"], .te-grid > [data-field="outro_seconds"],
.te-grid > [data-field="outro_text"] { grid-column: span 6; }
.te-grid > [data-field="highlight_color"], .te-grid > [data-field="accent_color"],
.te-grid > [data-field="outline_color"],
.te-grid > [data-field="box"], .te-grid > [data-field="uppercase"],
.te-grid > [data-field="loud_caps"] { grid-column: span 4; }

/* Шрифт, размер и цвет одной строкой — у заголовка и у субтитров
   (слово владельца 16.09): шрифту половина, размеру и цвету по четверти
   (доли из двенадцати). */
.te-grid > [data-field="title_font"], .te-grid > [data-field="font"] { grid-column: span 6; }
.te-grid > [data-field="title_size"], .te-grid > [data-field="font_size"],
.te-grid > [data-field="title_color"], .te-grid > [data-field="text_color"] { grid-column: span 3; }

/* Подсказка к размеру — строкой под «шрифт, размер, цвет» */
.template-fields .te-row-help { margin: -6px 0 12px; }

/* Подсветка слова — выделенная плашка, как в макете */
.te-grid > [data-field="karaoke"] {
  padding: 12px 14px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface-2);
}
.template-fields .field-check label { font-size: var(--fs-sm); font-weight: 500; }
.te-grid > [data-field="karaoke"] label,
.te-subcard > .field-check:first-child label { font-size: var(--fs-base); font-weight: 600; }
.te-card > legend label, .te-card > legend { font-size: var(--fs-base); font-weight: 700; color: var(--fg-strong); }

/* Приём монтажа — своя карточка внутри раздела */
.te-subcard {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 14px 2px; margin-top: 12px; background: var(--surface);
}

/* Обложка: поля слева, образец справа */
.te-cover { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; }
.te-cover .cover-preview { margin: 0; max-width: 180px; }

/* Панель действий прилипает к низу окна */
.te-actions {
  position: sticky; bottom: 0; z-index: 20;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; border: 1px solid var(--border); border-radius: 16px;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(10px);
}
.te-actions .hint { margin: 0; }
.te-actions-buttons { display: flex; gap: 10px; flex-wrap: wrap; }
.te-actions .btn { min-height: 44px; }

@media (max-width: 700px) {
  .te-top, .te-cover { grid-template-columns: 1fr; }
  .te-grid > [data-field] { grid-column: span 12; }
  .te-grid > [data-field="box"], .te-grid > [data-field="uppercase"],
  .te-grid > [data-field="loud_caps"] { grid-column: span 12; }
  .te-actions-buttons, .te-actions-buttons .btn { width: 100%; }
}

/* --- ГЛАВНАЯ: ВЕРХ ВИТРИНЫ ПО МАКЕТУ ВЛАДЕЛЬЦА (A_О_399) --------------
   Макет — ~/Desktop/start.zip, слово владельца 17.09 17:24 («нужно так
   сделать»); сменил «смелый» макет мастера того же дня. Макет написан
   на Tailwind — здесь перенесён ВИД, а не классы: цвета живут
   переменными палитры лендинга (`--lp-title-grad`, `--lp-cyan`), чтобы
   светлая тема получила свои тона, а не тёмные.
   Блок стоит В КОНЦЕ файла намеренно: он перекрывает сетку `.lp-hero`
   и общие размеры `.lp h1` / `.lp .btn` только внутри первого экрана. */
.lp-hero.lp-bold, .lp-wide { max-width: 1152px; }
.lp-hero.lp-bold { display: block; text-align: center; margin: 0 auto; }
.lp-bold .lp-hero-text { max-width: none; display: flex; flex-direction: column; align-items: center; }

/* Плашки — слева, одна над другой, мельче заголовка. */
.lp-bold .lp-start-badges {
  align-self: flex-start; display: flex; flex-direction: column;
  align-items: flex-start; gap: 12px; margin-bottom: 32px; text-align: left;
}
.lp-bold .lp-hero-pill {
  display: inline-flex; align-items: center; gap: 8px; margin: 0;
  padding: 6px 14px; font-size: .875rem; font-weight: 500;
  background: var(--lp-surface); border-color: var(--lp-line);
}
.lp-bold .lp-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--lp-cyan);
  flex: none; animation: lp-dot-pulse 2s ease-in-out infinite;
}
@keyframes lp-dot-pulse { 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .lp-bold .lp-dot { animation: none; } }
.lp-bold .lp-eyebrow {
  margin: 0; padding: 4px 12px; gap: 10px; font-size: .75rem;
  color: var(--muted);
}
.lp-bold .lp-eyebrow b {
  margin: 0; padding: 2px 8px; font-size: .6875rem; font-weight: 700;
  color: var(--lp-cyan); border-color: var(--lp-cyan-line);
  background: var(--lp-cyan-soft); border-radius: 4px;
}
.lp-bold .lp-eyebrow svg { width: 14px; height: 14px; color: #34d399; flex: none; }

/* Заголовок в две строки: первая — просто «Час записи —», вторая —
   число неоновым градиентом. Размер — шкала макета (30/48/60 px). */
.lp-bold h1 {
  max-width: 896px; margin: 0; line-height: 1.08;
  letter-spacing: -.025em; font-size: 1.875rem;
}
.lp-bold h1 > span { display: block; font-size: 1.875rem; }
.lp-bold h1 .lp-h1-lead { font-weight: 700; color: var(--fg-strong); }
/* Тире не уезжает на свою строку (казахская версия на 390 так ломалась).
   На самых узких перенос разрешён: «An hour of recording —» не влезает
   в 320 даже мелким кеглем. Кегль ниже 420 — по замеру 360/390. */
@media (max-width: 420px) { .lp-bold h1 > span { font-size: 1.625rem; } }
@media (min-width: 360px) { .lp-bold h1 .lp-h1-lead { white-space: nowrap; } }
.lp-bold h1 .lp-dash { font-weight: 300; color: var(--muted); margin: 0 .3em; }
.lp-bold h1 .g {
  margin-top: 8px; font-weight: 900; letter-spacing: -.025em;
  background: var(--lp-title-grad);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  text-shadow: var(--lp-title-glow);
}
@media (min-width: 640px) { .lp-bold h1 > span { font-size: 3rem; } }
@media (min-width: 768px) { .lp-bold h1 > span { font-size: 3.75rem; } }

.lp-bold .lp-lead {
  max-width: 672px; margin: 24px auto 0; font-size: 1rem;
  line-height: 1.625; color: var(--muted);
}
.lp-bold .lp-lead b { font-weight: 500; color: var(--fg-strong); }
@media (min-width: 640px) { .lp-bold .lp-lead { font-size: 1.25rem; } }

/* Кнопки: главная — тёмно-синий градиент со свечением и белым текстом.
   Тона взяты темнее макетных (#3b82f6 в середине давал белому 3,68 при
   норме 4,5): крайние #2563eb и #4f46e5 дают 5,17 и 6,29. */
.lp-bold .lp-hero-actions { justify-content: center; gap: 14px; margin-top: 32px; }
.lp-bold .lp-hero-actions .btn {
  height: auto; padding: 14px 32px; border-radius: 14px;
  font-size: .875rem; gap: 8px;
}
.lp-bold .lp-hero-actions .btn:not(.btn-ghost) {
  color: #fff; background: linear-gradient(90deg, #2563eb, #3160e4 50%, #4f46e5);
  box-shadow: 0 0 25px -4px rgba(59, 130, 246, .45);
}
.lp-bold .lp-hero-actions .btn-ghost {
  padding: 14px 28px; font-weight: 500; color: var(--muted);
  background: var(--lp-surface-2); border-color: var(--lp-line);
}
.lp-bold .lp-hero-actions .btn-ghost:hover { color: var(--fg-strong); }
.lp-bold .lp-hero-actions svg { width: 16px; height: 16px; flex: none; }
@media (max-width: 639px) {
  .lp-bold .lp-hero-actions { flex-direction: column; align-self: stretch; }
  .lp-bold .lp-hero-actions .btn { width: 100%; }
}

/* Условие и числа — мелко и приглушённо, акценты цветом. */
.lp-bold .lp-free-note, .lp-bold .lp-numbers {
  max-width: 720px; margin: 6px auto 0; font-size: .75rem;
  font-weight: 500; line-height: 1.6; color: var(--muted);
}
.lp-bold .lp-free-note { margin-top: 24px; }
.lp-bold .lp-free-note b { font-weight: 600; color: var(--fg-strong); }
.lp-bold .lp-numbers strong { font-weight: 600; color: var(--fg); }
.lp-bold .lp-numbers .lp-accent { font-weight: 600; color: var(--lp-cyan); }
@media (min-width: 640px) {
  .lp-bold .lp-free-note, .lp-bold .lp-numbers { font-size: .8125rem; }
}

/* Четыре клипа ровным рядом, шириной с плашки под ними. */
.lp-reel {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px;
  margin-top: 64px;
}
.lp-reel figure {
  margin: 0; border-radius: 14px; overflow: hidden;
  border: 1px solid var(--lp-line); background: #000;
  box-shadow: 0 4px 20px -2px rgba(0, 0, 0, .5);
  transition: border-color .3s ease;
}
.lp-reel figure:hover { border-color: rgba(96, 165, 250, .5); }
.lp-reel video { display: block; width: 100%; aspect-ratio: 9 / 16; object-fit: cover; }
@media (max-width: 767px) {
  .lp-reel { grid-template-columns: repeat(2, 1fr); gap: 14px; margin-top: 56px; }
}

/* Плашки доверия и факты под клипами — в той же ширине, значок в рамке. */
.lp-wide .lp-trust { margin-top: 40px; gap: 14px; }
.lp-wide .lp-facts { gap: 14px; margin-top: 14px; }
.lp-wide .lp-trust-card { padding: 20px; gap: 14px; border-radius: 14px; }
.lp-wide .lp-trust-card svg {
  box-sizing: content-box; width: 20px; height: 20px; padding: 9px;
  border: 1px solid var(--lp-line); border-radius: 14px;
  background: var(--bg-soft); color: var(--lp-cyan);
}
.lp-wide .lp-trust-card b { font-size: 1rem; font-weight: 700; color: var(--fg-strong); }
.lp-wide .lp-trust-card i { font-size: .75rem; }
.lp .lp-wide .lp-fact { padding: 20px; background: var(--lp-surface); border-color: var(--lp-line); }
.lp .lp-wide .lp-fact b { font-size: 1.875rem; font-weight: 800; letter-spacing: -.025em; }
.lp .lp-wide .lp-fact span { display: block; margin-top: 4px; font-size: .75rem; }
@media (max-width: 767px) {
  .lp-wide .lp-trust, .lp .lp-wide .lp-facts { grid-template-columns: 1fr; }
}

/* Свечение фона за первым экраном. Гаснет К КРАЯМ КОЛОННЫ (closest-side):
   `.lp` режет всё, что шире колонки 1080 px (`overflow-x: clip`), и
   макетный круг давал видимый прямоугольник с краями. */
.lp-section#top::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 1100px;
  pointer-events: none; z-index: 0; background: var(--lp-top-glow);
}

/* --- СТРАНИЦА ДУБЛЯЖА: ГЕРОЙ В ОДНУ КОЛОНКУ И ЗАЗОР ДО КАРТОЧЕК (A_О_403_В) --
   Слово владельца 17.09: справа у героя пусто, заголовок ломался на две
   строки; между кнопками и карточками — зазор как между карточками. */
.lp-hero.lp-hero-solo { grid-template-columns: 1fr; }
.lp-hero-solo .lp-hero-text { max-width: none; }
.lp-dub-cards { margin-top: var(--lp-gap); }

/* --- СТАТЬЯ «ДУБЛЯЖ ВИДЕО» ПО МАКЕТУ ВЛАДЕЛЬЦА (A_О_404_В) ----------------
   Макет — ~/Desktop/дубляж.zip, Tailwind перенесён видом. Цвета — из
   палитры (`--surface`, `--border`, `--lp-cyan`), обе темы без копий. */
.help-dub { max-width: 780px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }
.help-dub h1 { text-align: center; margin: 4px 0 8px; font-size: clamp(1.9rem, 4vw, 2.6rem); letter-spacing: -.02em; }
.help-dub h2 { font-size: 1.05rem; margin: 0 0 8px; color: var(--fg-strong); }
.help-dub p { margin: 0 0 10px; line-height: 1.6; }
.help-dub-head { text-align: center; margin-top: 8px; }
.help-dub-pill {
  display: inline-flex; align-items: center; gap: 8px; padding: 4px 12px;
  border-radius: 999px; border: 1px solid var(--lp-cyan-line);
  background: var(--lp-cyan-soft); color: var(--lp-cyan);
  font-size: .72rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
}
.help-dub-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.hd-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 20px; min-width: 0;
}
.hd-lead { display: flex; gap: 16px; align-items: flex-start; padding: 24px; }
.hd-lead h2 { font-size: 1.2rem; }
.hd-row, .hd-grid .hd-card.hd-row { display: flex; flex-direction: row; gap: 14px; align-items: flex-start; }
.hd-icon, .hd-ic-box {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 12px;
  background: var(--bg-soft); border: 1px solid var(--border); color: var(--lp-cyan);
}
.hd-ic-box { margin-bottom: 14px; }
.hd-icon svg, .hd-ic-box svg { width: 20px; height: 20px; }
.hd-icon-warn { color: #e0a82e; }
.hd-icon-no { color: #e5484d; }
.hd-eyebrow {
  margin: 12px 0 -4px; font-size: .72rem; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.hd-eyebrow::before { content: "•"; color: var(--lp-cyan); margin-right: 8px; }
.hd-grid { display: grid; gap: 14px; }
.hd-grid-2 { grid-template-columns: repeat(2, 1fr); }
.hd-grid-3 { grid-template-columns: repeat(3, 1fr); }
.hd-grid .hd-card { display: flex; flex-direction: column; font-size: .92rem; }
.hd-title { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.hd-title h2 { margin: 0; }
.hd-ic { display: inline-flex; color: var(--lp-cyan); }
.hd-ic svg { width: 18px; height: 18px; }
.hd-ic-warn { color: #e0a82e; }
.hd-badge {
  margin-left: auto; white-space: nowrap; font-size: .72rem; color: var(--muted);
  padding: 3px 10px; border-radius: 999px; background: var(--bg-soft); border: 1px solid var(--border);
}
.hd-muted { color: var(--muted); }
.hd-note {
  margin-top: auto; display: flex; gap: 10px; align-items: flex-start;
  padding: 12px; border-radius: 12px; background: var(--bg-soft); border: 1px solid var(--border);
  font-size: .82rem;
}
.hd-note p { margin: 0; }
.hd-note-accent { border-color: var(--lp-cyan-line); background: var(--lp-cyan-soft); }
.hd-tag {
  flex: none; font: 600 .7rem ui-monospace, Menlo, Consolas, monospace; color: var(--lp-cyan);
  padding: 2px 6px; border-radius: 4px; border: 1px solid var(--lp-cyan-line);
}
.hd-chips { list-style: none; padding: 0; margin: 0 0 14px; display: flex; flex-wrap: wrap; gap: 6px; }
.hd-chips li {
  font-size: .78rem; padding: 3px 9px; border-radius: 6px;
  background: var(--bg-soft); border: 1px solid var(--border);
}
.hd-foot { margin-top: 4px; padding-top: 10px; border-top: 1px solid var(--border); font-size: .82rem; }
.hd-sum { margin: auto 0 0 !important; padding-top: 12px; font-size: .8rem; color: var(--lp-cyan); }
.hd-sum.hd-muted { color: var(--muted); }
.hd-tail { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; font-size: .9rem; }
.hd-tail a {
  margin-left: auto; padding: 8px 16px; border-radius: 12px; text-decoration: none;
  border: 1px solid var(--border); background: var(--bg-soft); color: var(--fg-strong); font-weight: 600;
}
@media (max-width: 760px) {
  .hd-grid-2, .hd-grid-3 { grid-template-columns: 1fr; }
  .hd-lead { flex-direction: column; }
  .hd-tail a { margin-left: 0; }
}

