/* Каруселька — стили лендинга и приложения */
:root {
  --bg: #f2f2ee;
  --surface: #ffffff;
  --ink: #17163a;
  --ink-2: #44436a;
  --muted: #5f5e7c;
  --line: rgba(23, 22, 58, 0.14);
  --line-strong: rgba(23, 22, 58, 0.48);
  --accent: #ff4d1f;
  --accent-deep: #e03f13;
  --accent-ink: #ffffff;
  --accent-soft: #ffe8e0;
  /* оранжевый, который читается текстом на --accent-soft (4.54:1); сам --accent там даёт 3.65:1 */
  --accent-on-soft: #c23b18;
  --ok: #157a4f;
  --warn: #8a5a00;
  --danger: #b3261e;
  /* мягкие фоны полосы статуса: ожидание — accent-soft, готово / без нейросети / ошибка */
  --ok-soft: #e4f3ea;
  --warn-soft: #fff2d9;
  --danger-soft: #fde8e6;
  --font-display: "Unbounded", system-ui, sans-serif;
  --font-body: "Manrope", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* шкала отступов */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;
  /* кегли: шесть */
  --t-xs: 13px; --t-s: 15px; --t-m: 17px; --t-l: 20px; --t-xl: 24px;
  --lh-display: 1.08;
  /* радиусы: input → слайд → форма */
  --r-s: 8px; --r-m: 16px; --r-l: 24px;
  /* тени: карточка формы и «парящий» слайд.
     У карточки spread −20px съедал широкое пятно целиком: по бокам тень уходила на −4px, то есть
     внутрь самой карточки, а снизу выступала на 8px — 1,2 % её высоты против 5,6 % у слайда.
     Самая крупная поверхность отбрасывала самую мелкую тень. Спред ослаблен до −12px: пятно снова
     выходит вбок на 8px и вниз на 24px, оставаясь легче слайдовой — карточка лежит на столе, а не парит */
  --shadow-card: 0 1px 2px rgba(23, 22, 58, .05), 0 16px 40px -12px rgba(23, 22, 58, .18);
  --shadow-float: 0 2px 4px rgba(23, 22, 58, .08), 0 24px 48px -20px rgba(23, 22, 58, .35);
  /* слайд под курсором: приподнят на 2px, тень глубже */
  --shadow-lift: 0 4px 8px rgba(23, 22, 58, .1), 0 28px 52px -20px rgba(23, 22, 58, .45);
  /* миниатюра: тень по размеру карточки. «Парящая» рассчитана на слайд в полэкрана — у 90-пиксельной
     её край выходит за поля рельса и срезается прокруткой ровной вертикальной линией */
  --shadow-thumb: 0 1px 2px rgba(23, 22, 58, .07), 0 8px 16px -8px rgba(23, 22, 58, .34);
  --shadow-thumb-lift: 0 2px 4px rgba(23, 22, 58, .1), 0 10px 20px -8px rgba(23, 22, 58, .42);

  /* движение: длительности и кривые (docs/superpowers/specs/2026-09-12-motion-contract.md).
     dur-1 — наведение и нажатие, dur-2 — смена состояния мелких элементов, dur-3 — блоки, dur-4 — появление экрана.
     app.js держит те же числа в DUR_2/DUR_3/DUR_4 */
  --dur-1: 120ms; --dur-2: 200ms; --dur-3: 320ms; --dur-4: 480ms;
  --ease: cubic-bezier(.2, .7, .2, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-in: cubic-bezier(.4, 0, 1, 1);

  /* иконки — рисованные, один штрих 2px; красятся currentColor через mask, а не набираются глифами */
  --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  --icon-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M8 3v10M3 8h10'/%3E%3C/svg%3E");
  --icon-prev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 4L6 8l4 4'/%3E%3C/svg%3E");
  --icon-next: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 4l4 4-4 4'/%3E%3C/svg%3E");
  --icon-download: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 2v7.5M4.5 6.5L8 10l3.5-3.5M2.5 13h11'/%3E%3C/svg%3E");
  --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.5 3.5L13 5'/%3E%3C/svg%3E");
  /* карусель: карточка и края соседних — знак главного действия */
  --icon-carousel: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='3' width='6' height='10' rx='1.5'/%3E%3Cpath d='M2 5.5v5M14 5.5v5'/%3E%3C/svg%3E");
  /* заново: круговая стрелка — повторная нарезка исходного поста */
  --icon-refresh: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.5 8a5.5 5.5 0 1 1-1.6-3.9'/%3E%3Cpath d='M13.5 2.5V6H10'/%3E%3C/svg%3E");
  /* вернуть: та же круговая стрелка, но против часовой — шаг назад, а не заново */
  --icon-undo: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 8a5.5 5.5 0 1 0 1.6-3.9'/%3E%3Cpath d='M2.5 2.5V6H6'/%3E%3C/svg%3E");
  /* копирование: два листа */
  --icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='6' y='6' width='8' height='8' rx='1.5'/%3E%3Cpath d='M4 10H3.5A1.5 1.5 0 0 1 2 8.5v-5A1.5 1.5 0 0 1 3.5 2h5A1.5 1.5 0 0 1 10 3.5V4'/%3E%3C/svg%3E");

  --maxw: 1160px;
  --gutter: clamp(18px, 4vw, 40px);
  /* демо-карточка: 400px на 1280, у́же на планшетных ширинах; соседняя выглядывает ≥ 25 % */
  --demo-w: min(400px, 31vw);
}

* { box-sizing: border-box; }
:root { color-scheme: light; accent-color: var(--accent); }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scrollbar-color: var(--line-strong) transparent; }

body {
  margin: 0;
  overflow-x: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-m);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }
button, input, textarea { font: inherit; color: inherit; }
fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
figure { margin: 0; }
img, canvas { max-width: 100%; display: block; }
/* Висячая строка одинаково некрасива в абзаце и в подписи: правило шло по p и li, а половина
   прозы живёт в span.hint, figcaption, .caption и summary — отсюда одинокие слова в подписи
   к посту, в подсказке про ник и в строке раскрытия */
p, li, summary, figcaption, .hint, .caption, .saved { text-wrap: pretty; }
[hidden] { display: none !important; }
::placeholder { color: var(--muted); opacity: 1; }
/* поверхности, которые рисует браузер, тоже принадлежат системе: выделение, каретка, полосы, маркеры */
/* Цвет выделенного текста не назначаем: глобальное color: var(--ink) клало чернила на чернила —
   на индиговой заливке (выбранный чип, .btn-ink) выделенное слово давало 1.43:1. Без этой строки
   текст сохраняет свой цвет, и белый на той же заливке читается на 12.13:1 */
::selection { background: rgba(255, 77, 31, .28); }
:is(input, textarea) { caret-color: var(--accent); }
::-webkit-scrollbar { width: 10px; height: 10px; }
/* полоса прокрутки — такая же несущая границу поверхность, как рамка поля: rgba(.35) давала 2.17:1 */
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 999px; border: 3px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }
/* маркеры — часть текста, а не действие: оранжевый в системе означает действие или состояние */
li::marker { color: var(--muted); }

/* иконка: форма из маски, цвет — из текста кнопки */
.ico { flex: 0 0 auto; width: 16px; height: 16px; background: currentColor; -webkit-mask: var(--ico) center / contain no-repeat; mask: var(--ico) center / contain no-repeat; }

/* фокус — индиго; оранжевый только у состояния «выбрано» */
:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.btn-primary:focus-visible, .btn-ink:focus-visible, .chip:has(input:focus-visible) {
  outline: 3px solid var(--ink); outline-offset: 3px; box-shadow: 0 0 0 3px var(--bg);
}

.wrap { max-width: var(--maxw); margin: 0 auto; padding-inline: var(--gutter); }

/* Постоянная ссылка /c/… : экран результата виден с первого кадра. Во время подмены правило
   молчит: display:none обрывает затухание на первом же кадре, и кроссфейда не получается */
html.is-result:not(.is-crossfade) #home { display: none !important; }
html.is-result #result[hidden] { display: block !important; }
/* якорь для уходящего экрана, который на время подмены вынимается из потока */
main { position: relative; }
/* на телефоне и планшете экран результата не повторяет лендинг: остаются «Вопросы» и футер;
   на десктопе разделы стоят под рабочим столом, и меню шапки ведёт к ним */
@media (max-width: 1023px) {
  html.is-result :is(#how, #who, #price) { display: none !important; }
  html.is-result .top nav a:not([href="#faq"]) { display: none; }
}

/* ---------- движение: ключевые кадры ---------- */
/* всё движение — ответ на действие; prefers-reduced-motion в конце файла гасит и анимации, и переходы */
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes note-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes pop { from { opacity: 0; transform: scale(.5); } to { opacity: 1; transform: none; } }
@keyframes pop-soft { 0%, 100% { transform: none; } 50% { transform: scale(1.03); } }
@keyframes ghost-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- переходы экранов (классы ставит app.js) ---------- */
/* Уходящий экран гаснет и чуть уезжает вверх. Кривые у прозрачности и сдвига разные, и это
   намеренно: по системе уход идёт на --ease-in, но там уход — сольный номер, под ним пусто.
   Здесь уходящий лежит ПОВЕРХ входящего, и медленный старт держал его выше половины семь кадров
   из двенадцати — два плотных экрана читались друг сквозь друга. Прозрачности нужен --ease:
   он уводит верхний слой под половину за два кадра, а дальше идёт растворение на семь кадров
   (0.27 0.17 0.11 0.07 0.04 0.02 0.01). --ease-out для этого слишком резок: 0.57 уже на первом
   кадре — это обрыв, а не растворение. Сдвиг остаётся на --ease-in: разгон читается как уход. */
#home.is-leaving, #result.is-leaving {
  opacity: 0; transform: translateY(-8px);
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease-in);
}
/* Кроссфейд. Оба экрана на миг стоят без hidden: входящий забирает поток и место, уходящий
   вынимается из потока и гаснет ПОВЕРХ него (z-index) — старое растворяется в новом.
   Поверх, а не под: снизу его затухания просто не видно, и новый экран возникал бы хлопком.
   Правило само ждёт вторую половину — swapScreens в app.js показывает входящий до того,
   как спрятать уходящий; пока этого не было, правило молчало. */
main:has(#home:not([hidden])):has(#result:not([hidden])) :is(#home, #result).is-leaving {
  /* top: 0 обязателен. Без него absolute оставляет экран на статическом месте, а #result идёт
     в разметке вторым — уходящий результат гас на 790px ниже входящей формы, то есть за краем
     экрана: в одну сторону кроссфейд был, в другую его не было вовсе */
  position: absolute; top: 0; inset-inline: 0; z-index: 2; pointer-events: none;
}
/* входящий экран: дети с классом rise поднимаются по очереди, шаг задаёт инлайновая --i (0, 1, 2…) */
.is-entering .rise { animation: rise var(--dur-4) var(--ease-out) both; animation-delay: calc(var(--i, 0) * 40ms); }

/* ---------- шапка ---------- */
.top { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); padding-block: var(--s5) var(--s2); }
.wordmark {
  font-family: var(--font-display); font-weight: 700; font-size: var(--t-l); letter-spacing: -0.02em;
  text-decoration: none; display: inline-flex; align-items: center; gap: var(--s3);
  padding: var(--s2) 0; margin: calc(-1 * var(--s2)) 0;
}
.wordmark .mark { flex: 0 0 auto; }
/* при наведении три точки прокручиваются на шаг (см. блок hover: hover); шаг между центрами — 12 единиц viewBox */
.mark circle { transition: transform var(--dur-3) var(--ease); transform-box: fill-box; }
.top nav { display: flex; gap: var(--s5); font-weight: 600; font-size: var(--t-s); }
/* Цель 44px, а строка шапки прежней высоты: зона выходит за строку отрицательным полем — тот же
   приём, которым уже набран вордмарк рядом. До этого в пункт меню целились в 39px на десктопе
   и в 36px на телефоне; теперь 47px и 44px, а внешний габарит не изменился */
.top nav a { text-decoration: none; color: var(--muted); padding-block: var(--s3); margin-block: calc(-1 * var(--s1)); }
.nav-short { display: none; }
/* на телефоне меню остаётся, но короткими подписями: до цены и вопросов должен быть один тап */
@media (max-width: 640px) {
  .top { gap: var(--s3); }
  .top nav { gap: var(--s4); font-size: var(--t-xs); }
  .nav-long, .nav-wide { display: none; }
  .nav-short { display: inline; }
}
/* на узких экранах в шапке остаётся то, что ищет недоверчивый визитёр: цена и вопросы */
@media (max-width: 420px) { .top nav a[href="#how"] { display: none; } }

/* точка действия в конце раздела: ниже хиро страница не должна быть тупиком */

/* ---------- хиро ---------- */
.hero {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(28px, 5vw, 72px); align-items: start;
  /* 20…40 сверху, как до 12 сентября: серия «действие на первый экран» ужала герой до 16…28,
     и первый экран стал тесным — владелец заметил это раньше, чем измерил я */
  padding-block: clamp(20px, 3vw, 40px) clamp(48px, 6vw, 80px);
}

h1 {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(32px, 4vw, 48px); line-height: 1.06; letter-spacing: -0.02em;
  margin: 0 0 var(--s5); text-wrap: balance;
}
.lead { font-size: var(--t-l); line-height: 1.5; color: var(--ink-2); max-width: 34em; margin: 0 0 var(--s6); }

/* ---------- форма ---------- */
.tool {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-l);
  padding: var(--s5); box-shadow: var(--shadow-card);
}
.field { display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.field + .field { margin-top: var(--s4); }
.field-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3); }
label.block, .field label, .field .label, .styles legend {
  display: block; font-size: var(--t-s); font-weight: 600; color: var(--ink-2); line-height: 1.4;
}
/* мера подсказки — та же 34em, что у прозы разделов: без неё подсказки формы растягивались
   по всей ширине карточки, до 67 знаков в строке при кегле 13px */
.hint { font-size: var(--t-xs); color: var(--muted); line-height: 1.45; max-width: 34em; }
/* подсказка под ником становится мягким предупреждением, пока ник не примет сервер */
.hint.warn { color: var(--warn); }
/* подсказка под чипами: «справа» на десктопе, «выше» — когда пример стоит над формой */
.styles-hint { margin: var(--s2) 0 0; }
.styles-hint .at-top { display: none; }
.count { font-size: var(--t-xs); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.count.warn { color: var(--warn); }
/* Счётчик встаёт на строку подписи — ровно туда, где он стоит в редакторе (.field-head).
   Собственной строкой он появлялся на 5001-м знаке и толкал кнопку вниз на 28px прямо во время
   набора; на строке подписи справа для него уже есть пустое место, и высота поля не меняется */
.tool .field:has(> .count) {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto; column-gap: var(--s3);
}
.tool .field:has(> .count) > label { grid-area: 1 / 1; align-self: baseline; }
.tool .field:has(> .count) > .btn-clear { grid-area: 1 / 2; justify-self: end; align-self: baseline; }
.tool .field:has(> .count) > .count { grid-area: 1 / 3; justify-self: end; align-self: baseline; }
.tool .field:has(> .count) > :is(textarea, input, .error, .hint) { grid-column: 1 / -1; }
/* «Очистить» появляется только когда в поле что-то есть. Раньше поле очищала за человека кнопка
   «Новая карусель», и это было необратимо: программное присваивание value не кладётся в стек
   отмены — проверено, Cmd+Z пост не возвращал. Теперь стирание намеренное и обратимое. */
.btn-clear {
  border: 0; background: none; padding: 2px 0; cursor: pointer;
  font: inherit; font-size: var(--t-xs); font-weight: 600; color: var(--muted);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; text-decoration-skip-ink: none;
  transition: color var(--dur-1) var(--ease);
}
.btn-clear { position: relative; }
/* Правило цели 44px: подпись всего 13px и 24px по высоте — цель доводим прозрачной зоной,
   чтобы попадать пальцем, а строка подписи при этом не разъезжалась */
.btn-clear::after { content: ""; position: absolute; inset: -10px -8px; }
.btn-clear:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; border-radius: var(--r-s); }
.field-note { margin: 0; font-size: var(--t-xs); color: var(--muted); }
.field-note.warn { color: var(--warn); }

.tool :is(textarea, input), .panel :is(textarea, input), .pro input {
  width: 100%; min-width: 0; padding: var(--s3) var(--s4);
  border: 1px solid var(--line-strong); border-radius: var(--r-s);
  background: #fbfbf9; font-size: 16px; line-height: 1.5;
  transition: border-color var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
/* в редакторе поля оживают (disabled → enabled) спокойнее, чем отвечают на фокус */
.panel :is(textarea, input):not(:focus-visible) { transition-duration: var(--dur-2); }
.tool textarea, .panel textarea { min-height: 120px; resize: vertical; display: block; }
.tool :is(textarea, input):focus-visible, .panel :is(textarea, input):focus-visible, .pro input:focus-visible {
  border-color: var(--ink); background: #fff; outline: 3px solid var(--ink); outline-offset: 2px;
}
[aria-invalid="true"] { border-color: var(--danger) !important; }
/* у невалидного поля одно кольцо — цвета ошибки */
.tool [aria-invalid="true"]:focus-visible, .panel [aria-invalid="true"]:focus-visible, .pro [aria-invalid="true"]:focus-visible {
  outline-color: var(--danger);
}
.error { font-size: var(--t-s); margin: var(--s2) 0 0; color: var(--danger); font-weight: 600; }
/* любая заметка входит снизу: анимация стартует, когда app.js снимает hidden (или добавляет .thanks / .warn) */
.error:not([hidden]), .field-note:not([hidden]), .count:not([hidden]), .thanks, .hint.warn {
  animation: note-in var(--dur-2) var(--ease-out);
}

/* ник и стиль — необязательные: свёрнуты, чтобы кнопка стояла на первом экране.
   Сводка в заголовке показывает, что выбрано, не открывая блок */
.more { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); margin-top: var(--s4); }
.more + .guarantee { margin-top: var(--s4); }
.more summary {
  display: flex; align-items: center; gap: var(--s3); justify-content: space-between;
  padding: var(--s3) 0; min-height: 44px; cursor: pointer; list-style: none;
  font-size: var(--t-s); font-weight: 600; color: var(--ink-2);
}
.more summary::-webkit-details-marker { display: none; }
.more summary::after {
  content: ""; flex: 0 0 auto; width: 16px; height: 16px; margin-left: auto; color: var(--muted);
  background: currentColor; -webkit-mask: var(--icon-chevron) center / contain no-repeat; mask: var(--icon-chevron) center / contain no-repeat;
  transition: transform var(--dur-2) var(--ease);
}
.more[open] summary::after { transform: rotate(180deg); }
.more-value { font-weight: 400; color: var(--muted); text-align: right; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.more[open] .more-value { display: none; }
/* сверху --s4: без него «Ник на слайдах» упирался в строку сводки (замерено 0px) */
.more-body { padding-top: var(--s4); padding-bottom: var(--s4); animation: note-in var(--dur-2) var(--ease-out); }
.more-body .field + .styles { margin-top: var(--s4); }
/* гарантия — прямо над кнопкой: это то, ради чего человек решается нажать */
.guarantee { font-size: var(--t-s); font-weight: 600; line-height: 1.4; color: var(--ink); max-width: 24em; text-wrap: pretty; }

/* пять чипов в одну строку и в форме (534px), и в редакторе (516px) на 1280 */
/* чипы стилей — сетка равных ячеек: их шесть, и при переносе потоком последний ряд оставался рваным
   (5 + 1 в форме, 4 + 1 в редакторе). Колонок ровно столько, сколько влезает по 130px — самый широкий
   чип «Градиент» занимает 123px, — поэтому в форме получается 3 × 2, в узком редакторе тоже, на телефоне 2 × 3 */
/* Шесть чипов ложатся ровно только по 2 или 3 в ряд. Чистый minmax(130px) давал на 768px
   пять колонок — тот самый рваный ряд 5 + 1, который правило запрещает. Доля 30% держит
   потолок в три колонки на любой ширине, 130px — пол на узких. */
.styles { display: grid; grid-template-columns: repeat(auto-fit, minmax(max(130px, 30%), 1fr)); gap: var(--s2); }
.styles legend { padding: 0; margin-bottom: var(--s2); grid-column: 1 / -1; }
.tool .styles { margin-top: var(--s4); }
.chip {
  position: relative; display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 44px; padding: 10px; border-radius: 999px; border: 2px solid var(--line-strong);
  background: #fff; cursor: pointer; font-weight: 600; font-size: var(--t-s); line-height: 1.3;
  transition: border-color var(--dur-2) var(--ease), background-color var(--dur-2) var(--ease), color var(--dur-2) var(--ease), transform var(--dur-1) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.chip input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; margin: 0; cursor: pointer; }
/* Кольцо свотча — такая же несущая смысл граница, как рамка поля, и считается по тому же порогу 3:1.
   Прежние rgba(0,0,0,.08) давали 1.19:1 на белом чипе, и «Белый», «Крем» и «Мята» становились
   невидимыми кружками — человек выбирал стиль по кружку, а четырёх из шести не было видно.
   На тёмном чипе задачу уже решало светлое кольцо ниже; правило просто дописано для светлой стороны */
.chip i {
  /* образец — мини-слайд 4:5: фон палитры, буква её чернилами, полоска акцентом. Так чип
     показывает все три цвета, которыми палитра рисует, а не один фон */
  width: 22px; height: 27px; border-radius: 4px; display: grid; place-items: center; flex: 0 0 auto;
  position: relative; border: 1px solid var(--line-strong); background: var(--swatch);
}
.chip i::before { content: "А"; font: 700 12px/1 var(--font-display); color: var(--swatch-ink); }
.chip i::after { content: ""; position: absolute; left: 4px; bottom: 4px; width: 9px; height: 2px; background: var(--swatch-accent); }
/* Чип набора несёт подпись набора: имя набрано его же гарнитурой и со своим служебным знаком —
   рельс у «Журнала», номер у «Титра», капитель у «Вывески» и «Конспекта», курсив у «Книги».
   Голое слово в пилюле не говорило, что выберешь; браузер подтянет гарнитуру сам, когда чип
   покажется (у шрифтов unicode-range), и она же будет уже тёплой к моменту рендера */
.chip-set { padding-inline: var(--s4); }
.chip-set i { display: none; }
.chip-set .chip-name { display: inline-flex; align-items: baseline; gap: 6px; }
.chip-set[data-set="poster"] .chip-name { font-family: 'Unbounded', var(--font-display); font-weight: 700; letter-spacing: -0.01em; }
.chip-set[data-set="sign"] .chip-name { font-family: 'Oswald', var(--font-display); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; font-size: calc(var(--t-s) + 1px); }
.chip-set[data-set="titleCard"] .chip-name { font-family: 'Golos Text', var(--font-display); font-weight: 600; letter-spacing: 0.04em; }
.chip-set[data-set="titleCard"] .chip-name::before { content: "01"; font-weight: 600; font-size: 0.8em; opacity: .45; letter-spacing: 0; }
.chip-set[data-set="magazine"] .chip-name { font-family: 'Unbounded', var(--font-display); font-weight: 700; letter-spacing: -0.01em; }
.chip-set[data-set="magazine"] .chip-name::after { content: ""; display: block; width: 100%; height: 2px; background: currentColor; opacity: .55; margin-top: 2px; }
.chip-set[data-set="magazine"] .chip-name { display: inline-block; }
.chip-set[data-set="book"] .chip-name { font-family: 'Lora', Georgia, serif; font-weight: 700; font-style: italic; font-size: calc(var(--t-s) + 2px); }
.chip-set[data-set="notes"] .chip-name { font-family: 'Manrope', var(--font-display); font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; font-size: calc(var(--t-s) - 2px); }
.chip:active { border-color: var(--ink); transform: scale(.97); }
.chip:has(input:disabled):active { transform: none; }
.chip:has(input:checked) { border-color: var(--ink); background: var(--ink); color: #fff; }
/* на индиго тёмный свотч «Ночи» держит светлое кольцо */
.chip:has(input:checked) i { border-color: transparent; box-shadow: 0 0 0 1px rgba(255, 255, 255, .55); }
/* «Выключено» здесь говорится тем же языком, что у полей ниже: пунктирная рамка, заливка цвета
   полотна, подпись --muted. Прозрачность .55 гасила не состояние, а сам чип — рамка падала
   до 1.68:1, подпись до 4.45:1, и все 20–40 секунд ожидания выбранный стиль нельзя было прочесть.
   Выбранный чип остаётся индиговым: «выключено» не должно стирать «выбрано» */
.chip:has(input:disabled) {
  cursor: not-allowed; border-style: dashed; border-color: var(--line-strong);
  background: var(--bg); color: var(--muted);
}
.chip:has(input:checked:disabled) { background: var(--ink); color: #fff; border-color: rgba(255, 255, 255, .55); }
.chip:has(input:disabled) input { cursor: not-allowed; }

/* Строка под выбором набора: во сколько слов он вмещает. Это то, по чему набор и выбирают,
   поэтому она меняется вместе с выбором и стоит прямо под его чипами, а не в общей подсказке ниже */
.set-hint { margin: var(--s2) 0 var(--s5); color: var(--muted); }
.set-hint:empty { display: none; }
/* Две строки чипов подряд: без отбивки «Цвет» читался продолжением «Набора» */
.styles + .styles { margin-top: var(--s5); }

/* кнопки: один оранжевый CTA на экран */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  padding: 14px 24px; border-radius: 999px; border: 2px solid transparent; cursor: pointer;
  font-weight: 800; font-size: 19px; line-height: 26px; text-decoration: none; text-align: center;
  background: transparent; color: var(--ink);
  transition: background-color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.btn:active { transform: translateY(1px) scale(.99); }
/* Заливка остаётся --accent, а рамку держит --accent-deep: на полотне --accent даёт силуэту
   кнопки 2.95:1 при пороге 3, --accent-deep — 3.83:1. Оранжевый остаётся заливкой, тонкой
   линией на бумаге работает глубокий — то же правило, что у кольца выбора и полосы прогресса */
.btn-primary { background: var(--accent); border-color: var(--accent-deep); color: var(--accent-ink); }
.btn-ghost { border-color: var(--line-strong); }
.btn-ink { background: var(--ink); border-color: var(--ink); color: #fff; }
.btn-sm { padding: 10px 16px; font-size: var(--t-s); line-height: 20px; min-height: 44px; }
/* кнопка без видимой подписи: имя ей даёт .btn-label в .sr-only, наведению его повторяет title */
.btn-icon { flex: 0 0 auto; width: 44px; height: 44px; padding: 0; }
/* подпись отдельным узлом: app.js подменяет её текст («Готовим PNG…», «Скачали ✓»), не стирая иконку */
.btn-label { min-width: 0; }
/* ссылочная кнопка — тоже цель: 42px не хватало до 44px, добираем высотой, а не отступом,
   чтобы подчёркивание осталось там же, где было. skip-ink выключен у всех подчёркиваний:
   Chrome рвал линию 1px вокруг выносных «у», «р», «д», и на 14px она читалась как пунктир */
.btn-link {
  display: inline-flex; align-items: center; min-height: 44px; padding: var(--s2) 0;
  border: 0; background: none; cursor: pointer;
  font-weight: 600; font-size: var(--t-m); color: var(--ink);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; text-decoration-skip-ink: none;
}
/* выключенная кнопка приглушена; занятая (aria-busy) сохраняет цвет, курсор «ждите» и спиннер перед текстом —
   повторные нажатия по занятой игнорирует app.js, поэтому pointer-events ей не снимаем (иначе не видно курсор) */
.btn[disabled], .btn[aria-disabled="true"]:not([aria-busy="true"]) { opacity: .45; cursor: not-allowed; pointer-events: none; }
/* «Призрачная» выключается тем же пунктиром, что поля и чипы рядом: в одной панели должно быть
   одно «выключено», а не три. Прозрачность .45 роняла её рамку до 1.56:1, а нарисованный знак
   внутри — до 2.84:1, тогда как соседнее выключенное поле держит на пунктире 3.07:1 */
.btn-ghost[disabled], .btn-ghost[aria-disabled="true"]:not([aria-busy="true"]) {
  opacity: 1; border-style: dashed; border-color: var(--line-strong); color: var(--muted);
}
/* главное действие ждёт нейросеть 20–40 секунд: выцветший оранжевый читается как поломка, нейтральный — как «пока рано» */
.btn-primary[disabled], .btn-primary[aria-disabled="true"]:not([aria-busy="true"]) {
  opacity: 1; background: rgba(23, 22, 58, .07); border-color: transparent; color: var(--muted);
}
.btn[aria-busy="true"] { cursor: progress; }
.btn[aria-busy="true"]:active { transform: none; }
.btn[aria-busy="true"]::before {
  content: ""; flex: 0 0 auto; box-sizing: border-box; width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent; animation: spin .8s linear infinite;
}
.btn-sm[aria-busy="true"]::before { width: 14px; height: 14px; }
/* у кнопки с иконкой спиннер занимает её место, а не встаёт вторым кружком рядом */
.btn[aria-busy="true"] > .ico { display: none; }
/* «Скачали ✓», «Скопировано»: лёгкий отскок; класс снимает app.js по animationend */
.btn.is-flash { animation: pop-soft var(--dur-3) var(--ease-out); }

@media (hover: hover) {
  .btn-clear:hover { color: var(--ink); text-decoration-thickness: 2px; }
  .top nav a:hover { color: var(--ink); }
  /* вордмарк: точки уезжают на шаг вправо — средняя оранжевая на край, крайняя правая в начало */
  .wordmark:hover .mark circle:nth-child(1), .wordmark:hover .mark circle:nth-child(2) { transform: translateX(12px); }
  .wordmark:hover .mark circle:nth-child(3) { transform: translateX(-24px); }
  .chip:hover { border-color: var(--ink); }
  /* занятая кнопка на наведение не отвечает */
  .btn-primary:hover:not([aria-busy="true"]) { background: var(--accent-deep); border-color: var(--accent-deep); }
  .btn-ghost:hover:not([aria-busy="true"]) { border-color: var(--ink); background: rgba(23, 22, 58, .04); }
  .btn-ink:hover:not([aria-busy="true"]) { background: #2a2960; border-color: #2a2960; }
  .btn-link:hover { text-decoration-thickness: 2px; }
  .strip .slide:hover { transform: translateY(-2px); --slide-shadow: var(--shadow-lift); }
  /* знак «+» под курсором — --accent-deep: тонкий штрих 2px на бумаге даёт у --accent 2.95:1
     против 15.43:1 в покое, то есть жест наведения отнимал у знака видимость вместо того,
     чтобы её добавить. У --accent-deep — 3.83:1, и знак остаётся знаком */
  .faq summary:hover::after { color: var(--accent-deep); }
  /* раскрытия отвечают на наведение только там, где наведение есть */
  .more summary:hover { color: var(--ink); }
  ::-webkit-scrollbar-thumb:hover { background: var(--ink-2); background-clip: content-box; }
  /* точка примера под курсором темнеет до чернил: до этого лента отвечала на наведение только курсором */
  .demo-dots > *:hover::before { background: var(--ink); }
  .demo-dots > [aria-current="true"]:hover::before, .demo-dots > .on:hover::before { background: var(--accent-deep); }
}

.tool .submit { display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap; margin-top: var(--s5); }
.tool .submit .btn { flex: 0 0 auto; }

/* ---------- демо-лента: главный элемент ---------- */
.demo { position: sticky; top: var(--s5); min-width: 0; }
.demo-strip {
  display: flex; overflow-x: auto; overflow-y: clip; scroll-snap-type: x mandatory;
  scrollbar-width: none; -ms-overflow-style: none; overscroll-behavior-x: contain;
  padding-bottom: var(--s7); margin-bottom: calc(-1 * var(--s7));
  min-height: calc(var(--demo-w) * 1.25 + var(--s7));
  /* края уходят в фон: справа — ширина градиента --fade (как у ленты результата),
     слева — только зона внутреннего отступа --fade-l, чтобы первая карточка оставалась целой,
     а тень и выглядывающая при прокрутке предыдущая карточка гасли плавно, без среза */
  --fade: var(--s7);
  --fade-l: 0px;
  /* слева всё, что дальше от карточки, чем межкарточный зазор, скрыто полностью: при прокрутке край предыдущей
     карточки не проступает; в последних 16px перед карточкой гаснет только её собственная тень */
  --fade-l-start: 0px;
  -webkit-mask-image: linear-gradient(90deg, transparent var(--fade-l-start), #000 var(--fade-l), #000 calc(100% - var(--fade)), transparent);
  mask-image: linear-gradient(90deg, transparent var(--fade-l-start), #000 var(--fade-l), #000 calc(100% - var(--fade)), transparent);
}
.demo-strip::-webkit-scrollbar { display: none; }
/* на десктопе лента уходит навылет к правому краю окна: соседний слайд выглядывает, а не обрезается по колонке */
@media (min-width: 901px) {
  .demo-strip {
    --bleed: calc(var(--gutter) + max(0px, (100vw - var(--maxw)) / 2));
    margin-right: calc(-1 * var(--bleed)); padding-right: var(--bleed);
    /* место под тень первой карточки слева и сверху: край скролл-контейнера не должен её резать */
    margin-left: calc(-1 * var(--s6)); padding-left: var(--s6); scroll-padding-left: var(--s6);
    --fade-l: var(--s6); --fade-l-start: calc(var(--s6) - var(--s4));
    margin-top: calc(-1 * var(--s2)); padding-top: var(--s2);
    min-height: calc(var(--demo-w) * 1.25 + var(--s7) + var(--s2));
  }
}
.demo-track { display: flex; gap: var(--s4); transition: none; transform: none; }
.demo-track canvas, .demo-track:empty::before, .demo-track:empty::after {
  flex: 0 0 auto; width: var(--demo-w); height: auto; border-radius: var(--r-m); scroll-snap-align: start;
}
.demo-track canvas { box-shadow: var(--shadow-float); transition: opacity var(--dur-2) var(--ease); }
/* первая вставка демо: карточки поднимаются по очереди (класс и --i ставит app.js, снимает по animationend) */
.demo-track canvas.is-new { animation: rise var(--dur-3) var(--ease-out) both; animation-delay: calc(var(--i, 0) * 60ms); }
/* смена стиля в форме: короткий «вдох» — приглушить быстро, проявить спокойно (класс на один кадр ставит app.js) */
.demo-track.is-restyling canvas { opacity: .6; transition-duration: var(--dur-1); }
/* воздух после последнего слайда (вместе с gap — ширина градиента): докрученная лента не прячет его под фон */
.demo-track::after { content: ""; flex: 0 0 var(--s6); }
/* та же заглушка, что в ленте результата: место слайда держит его тень, а не волосяная рамка */
.demo-track:empty::before, .demo-track:empty::after {
  content: ""; aspect-ratio: 4 / 5; background: var(--surface); box-shadow: var(--shadow-float);
}

.demo-note {
  margin: var(--s3) 0 0; font-size: var(--t-s); color: var(--muted); line-height: 1.4;
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--s2);
}
/* точки стоят сразу под лентой, подпись — под ними */
/* зона нажатия 24×24, видимая точка 8 px; первая точка стоит на левой вертикали */
.demo-dots { display: flex; flex: 0 0 auto; order: -1; margin-left: calc(-1 * var(--s2)); }
.demo-dots > * {
  position: relative; width: 24px; height: 24px; padding: 0; margin: 0; border: 0; background: none;
  display: grid; place-items: center; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.demo-dots > *::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--line-strong);
  transition: transform var(--dur-2) var(--ease), background-color var(--dur-2) var(--ease);
}
/* активная точка отличается размером, а не только тоном: по светлоте оранжевая и серая точки
   расходятся всего на 1.24:1, и при цветовой слепоте ряд читался бы как пять одинаковых.
   8px → 12px — разница видна и без цвета, и зона нажатия 24×24 её держит */
.demo-dots > [aria-current="true"]::before, .demo-dots > .on::before { background: var(--accent-deep); transform: scale(1.5); }
.demo-dots > :focus-visible { outline-offset: 0; border-radius: 999px; }

/* ---------- результат ---------- */
.result { display: flex; flex-direction: column; gap: var(--s4); padding-block: clamp(24px, 4vw, 48px) clamp(48px, 6vw, 80px); }
.result > .error { margin-top: calc(var(--s2) - var(--s4)); }
.result-head { display: block; }
.workbench, .strip { margin-top: var(--s2); }
.extras { margin-top: var(--s6); }
/* до 1023px: заголовок → статус → лента → скачивание → редактор → подпись и Pro (на телефоне лента — это и есть сцена).
   Рабочий стол раскрывается: лента, сцена и редактор становятся прямыми детьми .result и встают в стопку по order */
@media (max-width: 1023px) {
  .result-top, .workbench { display: contents; }
  .result-head { order: 0; }
  .status { order: 1; }
  #result-back { order: 2; }
  .strip { order: 3; }
  /* сразу под лентой — то, ради чего пришли; правка следом, одной кнопкой ниже */
  .download { order: 4; margin-top: var(--s3); }
  #dl-error { order: 5; }
  .editor { order: 6; margin-top: var(--s5); }
  .extras { order: 7; }
  .result-again { order: 8; }
  .stage { display: none; }
  #result-back { align-self: flex-start; }
}
.result h1 {
  font-size: clamp(24px, 3vw, 36px); line-height: var(--lh-display); margin: 0; max-width: 18em;
}
.result h1:focus-visible { outline: none; }
 /* ---------- полоса статуса: заголовок → панель → лента ---------- */
.status {
  /* Полоса — единственный индикатор хода работы, и она нетекстовая: считается по 3:1 к своему треку.
     --accent давал там 2.29:1, --accent-deep даёт 3.00:1. Тот же выбор и по той же причине,
     что у пульсирующей точки и у кольца выбранного слайда */
  --status-fill: var(--accent-deep);
  position: relative; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s3) var(--s5); margin-top: 0; padding: var(--s4) var(--s5) var(--s5); border-radius: var(--r-m);
  background: var(--accent-soft);
  /* height анимирует app.js (замер до/после смены текста или сжатия), чтобы лента под панелью не прыгала */
  transition: background-color var(--dur-3) var(--ease), height var(--dur-3) var(--ease), margin-top var(--dur-3) var(--ease);
}
/* смена текста: заголовок экрана, тексты панели и «Сохраняем… / Сохранено» входят снизу (класс переставляет app.js) */
#result-title.is-swapping, .saved.is-swapping { animation: note-in var(--dur-2) var(--ease-out); }
/* Смена текста панели: прежняя строка (призрак, клон колонки поверх) уходит вверх и размывается,
   новая входит снизу, подсказка — на шаг позже заголовка. Это одно из двух авторских движений
   экрана результата (второе — замена черновика в ленте); раньше текст просто подменялся */
.status-copy { position: relative; }
/* Последовательно, а не внахлёст: прежняя строка гаснет за 160 мс (сдвиг 4px вверх), пауза, новая
   входит за 360 мс снизу на 6px, подсказка на 70 мс позже. Без размытия и без двух строк разом —
   наложение текста читалось дёшево (владелец), размытие тоже */
.status-ghost { position: absolute; inset: 0; min-height: 0 !important; pointer-events: none; animation: status-out 160ms var(--ease-in) both; }
.status-copy.is-swapping > .status-title, .status-copy.is-swapping > .status-sub { animation: status-in 360ms var(--ease-out) both; animation-delay: 160ms; }
.status-copy.is-swapping > .status-sub { animation-delay: 230ms; }
@keyframes status-out { to { opacity: 0; transform: translateY(-4px); } }
@keyframes status-in { from { opacity: 0; transform: translateY(6px); } }
/* Основа 30em, а не 24em: на 840–960px строка статуса делила ряд с шагами, ей оставалось 408px,
   и половина сообщений ожидания разворачивалась во вторую строку, а половина нет — панель над
   лентой прыгала на 61px каждые 6 секунд. С 30em шаги в этой полосе уходят на свою строку,
   и тексту хватает одной. Основа по-прежнему шире строки телефона — ради этого «Сохранено»
   и живёт внутри .status-main, а не рядом */
.status-main { display: flex; align-items: flex-start; gap: var(--s3); flex: 1 1 30em; min-width: 0; }
/* Текст забирает остаток строки и сжимается, а не переносится под иконку: с базой по содержимому
   он на 840–960px сваливался на свою строку у одних сообщений ожидания и оставался на месте
   у других — панель прыгала на 61px каждые 6 секунд, ровно над лентой слайдов */
.status-copy { flex: 1 1 0; min-width: 0; }
/* Резерв под самое высокое из сообщений ожидания: они сменяются каждые 6 секунд, и панель над
   лентой дышать не должна. Считается по шкале кеглей, по одной строке заголовка и подсказки —
   ровно столько сообщения и занимают на планшетных ширинах, так что пустоты резерв не добавляет */
.status:not(.is-compact) .status-copy { min-height: calc(1.3 * var(--t-m) + 1.45 * var(--t-xs) + 2px); }
.status-title { margin: 0; font-size: var(--t-m); font-weight: 700; line-height: 1.3; color: var(--ink); text-wrap: balance; }
/* подсказка панели набрана тем же кеглем, что все подсказки интерфейса: 14px были седьмым размером в системе */
.status-sub { margin: 2px 0 0; font-size: var(--t-xs); line-height: 1.45; color: var(--muted); }
/* иконка 12px: точка с пульсом в ожидании, круг с галочкой — когда готово; стоит на первой строке заголовка */
.status-icon {
  flex: 0 0 auto; width: 12px; height: 12px; margin-top: 5px; border-radius: 50%;
  background: var(--accent-deep) center / 10px no-repeat; animation: pulse 1.2s infinite ease-in-out;
}
@keyframes pulse { 0%, 100% { transform: scale(.7); opacity: .5; } 50% { transform: scale(1); opacity: 1; } }
.status[data-mode="done"] .status-icon, .status[data-mode="fallback"] .status-icon, .status[data-mode="warn"] .status-icon, .status[data-mode="error"] .status-icon {
  animation: none; background-image: var(--check);
}
.status { --check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M2.4 5.3l1.9 1.9 3.4-3.8' fill='none' stroke='%23fff' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.status { --dash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M2.6 5h4.8' fill='none' stroke='%23fff' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E"); }
.status { --cross: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M3.2 3.2l3.6 3.6M6.8 3.2L3.2 6.8' fill='none' stroke='%23fff' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E"); }
.status[data-mode="done"] { --status-fill: var(--ok); background-color: var(--ok-soft); }
/* галочка «готово» выскакивает один раз — при переходе в done */
.status[data-mode="done"] .status-icon { background-color: var(--ok); animation: pop var(--dur-3) var(--ease-out); }
.status[data-mode="fallback"], .status[data-mode="warn"] { --status-fill: var(--warn); background-color: var(--warn-soft); }
.status[data-mode="fallback"] .status-icon, .status[data-mode="warn"] .status-icon { background-color: var(--warn); background-image: var(--dash); }
.status[data-mode="error"] { --status-fill: var(--danger); background-color: var(--danger-soft); }
.status[data-mode="error"] .status-icon { background-color: var(--danger); background-image: var(--cross); }

/* шаги: сделанный — галочка и --ink, активный — пульсирующая точка и --accent-deep, будущий — пустой круг и --muted, пропущенный — --warn с зачёркиванием */
.status-steps { display: flex; flex: 0 0 auto; align-items: center; gap: 0; margin: 0; padding: 0; list-style: none; font-size: var(--t-xs); font-weight: 600; line-height: 1.3; }
.status-steps li { position: relative; display: flex; align-items: center; gap: 6px; color: var(--muted); white-space: nowrap; }
.status-steps li + li { padding-left: 26px; }
/* соединительная линия между шагами */
.status-steps li + li::after { content: ""; position: absolute; left: 5px; top: 50%; width: 16px; height: 1px; background: var(--line-strong); }
.status-steps li::before { content: ""; flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%; box-sizing: border-box; border: 1.5px solid var(--line-strong); background: center / 10px no-repeat; }
.status-steps li[data-state="done"] { color: var(--ink); }
.status-steps li[data-state="done"]::before {
  border: 0; background-color: var(--ok); background-image: var(--check);
  animation: pop var(--dur-3) var(--ease-out) both;
}
/* шаги закрываются по очереди: 0 / 80 / 160 мс */
.status-steps li:nth-child(2)[data-state="done"]::before { animation-delay: 80ms; }
.status-steps li:nth-child(3)[data-state="done"]::before { animation-delay: 160ms; }
.status-steps li[data-state="active"] { color: var(--accent-on-soft); }
.status-steps li[data-state="active"]::before { border: 0; background-color: var(--accent-deep); animation: pulse 1.2s infinite ease-in-out; }
.status-steps li[data-state="skipped"] { color: var(--warn); text-decoration: line-through; text-decoration-thickness: 1px; }
.status-steps li[data-state="skipped"]::before { border: 0; background-color: var(--warn); background-image: var(--dash); }

/* полоса прогресса по нижнему краю панели */
/* Трек светлее на три сотых: на .10 глубокий оранжевый давал по нему 2.97:1, на .07 — 3.19:1.
   Сам трек к панели так и остаётся на 1.21:1, и иначе быть не может: два условия — «трек виден
   на персиковом» и «полоса видна на треке» — тянут яркость в разные стороны. Видимой обязана
   быть полоса, она и несёт смысл; трек только показывает, где она пройдёт */
.status-bar { position: absolute; left: var(--s5); right: var(--s5); bottom: var(--s2); height: 3px; border-radius: 999px; background: rgba(23, 22, 58, .07); overflow: hidden; }
/* растёт трансформом, а не шириной: раскладка не пересчитывается все 20–40 секунд ожидания */
.status-bar i { display: block; height: 100%; width: 100%; transform: scaleX(0); transform-origin: left center; border-radius: inherit; background: var(--status-fill); transition: transform 1s linear, background-color var(--dur-3) var(--ease); }
/* финиш: полоса добегает до 100 % быстрее и мягче, чем ползёт в ожидании. Такт финиша (is-finishing)
   идёт до зелёной панели — в ней полосы уже нет, см. ниже */
.status.is-finishing .status-bar i, .status[data-mode="fallback"] .status-bar i { transition: transform .5s var(--ease-out), background-color var(--dur-3) var(--ease); }

/* когда всё сделано, три галочки и полоса на 100% не сообщают ничего: они живут, пока идёт работа */
.status[data-mode="done"] :is(.status-steps, .status-bar) { display: none; }
.status[data-mode="done"]:not(.is-compact) { padding-block: var(--s4); }
/* готовая панель сообщений не меняет — резерв под две строки оставлял пустоту под единственной */
.status[data-mode="done"]:not(.is-compact) .status-copy { min-height: 0; }
.status[data-mode="done"] .status-main { align-items: center; }
.status[data-mode="done"] .status-icon { margin-top: 0; }
/* Ссылка стоит в строке заголовка и не должна её растить: цель 44px даёт прозрачная зона ::after,
   а не высота кнопки — иначе строка вырастала до 44px и значок слева уезжал вверх на 3px */
.status-undo { position: relative; display: inline-flex; align-items: center; gap: 6px; min-height: 0; padding: 0; line-height: 1.3; font-size: var(--t-xs); --ico: var(--icon-undo); }
/* ссылка стоит в самом правом углу панели, знак правки — слева от неё: его поле 16px зарезервировано
   всегда, и «Сохраняем…» во время правки ссылку не двигает. Вправо обоих прижимает авто-поле знака */
.status-main > .status-undo { flex: 0 0 auto; margin-left: var(--s4); white-space: nowrap; }
.status-undo::after { content: ""; position: absolute; inset: -12px 0; }
.status-undo .ico { width: 14px; height: 14px; }

/* готовая карусель в покое: одна строка, без фона, шагов и полосы */
/* отступ и компенсирующее поле выросли одинаково: строка встаёт ровно там же, где стояла */
.status.is-compact { padding: var(--s2) 0; margin-top: calc(-1 * var(--s2)); background: transparent; }
.status.is-compact .status-steps, .status.is-compact .status-bar { display: none; }
.status.is-compact .status-copy { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: .35em; }
.status.is-compact .status-title { font-size: var(--t-s); font-weight: 600; }
.status.is-compact .status-sub { margin: 0; }
.status.is-compact .status-sub::before { content: "— "; }
/* компактная панель всегда «готово», а там значок центрируется по строке; 5px сверху сдвигали его
   на 2.7px ниже центра прописных */
.status.is-compact .status-icon { margin-top: 0; }
/* покой без слов (по ссылке и после ухода зелёной панели за край): значок и колонка текста уходят,
   знак правки — кольцо «Сохраняем…» и галочка «Сохранено» — встаёт слева, где стоял значок.
   Ряд остаётся: его 20px держат высоту, и «Сохраняем…» во время набора ничего не сдвигает */
/* .is-compact в селекторе — ради веса: на телефоне правило .status.is-compact .status-copy { display: block } стоит ниже и перебивало бы */
.status.is-compact.is-quiet :is(.status-icon, .status-copy) { display: none; }
.status.is-quiet .saved { margin-left: 0; }
/* в молчащей строке знак не один: рядом с ним слово — «Сохраняем…» у кольца, «Сохранено» у галочки.
   Без слова одинокая галочка слева читалась сиротливо; в полной панели слово по-прежнему прячется
   (там оно спорило бы с текстом панели). Высота ряда та же — 20px */
.status.is-compact.is-quiet .saved:not(.err):not([data-mode="file"]) {
  flex: 0 0 auto; width: auto; height: 20px; line-height: 20px; margin-top: 0; padding-left: 22px; text-indent: 0; overflow: visible;
}
.status.is-compact.is-quiet .saved:not(.err):not([data-mode="file"])::after { inset: 2px auto 2px 0; width: 16px; }
.status.is-compact.is-quiet .saved[data-mode="busy"]:not(.err):not(:empty)::after { inset: 3px auto 3px 1px; width: 14px; }
.status[data-mode="error"] .status-steps { display: none; }
#result-back { margin-top: var(--s2); align-self: flex-start; }

/* состояния экрана */
/* «Подпись к посту» показываем только тогда, когда она окончательная: в черновике панель уже
   стояла на месте, текст в ней выделялся мышью — а через секунды нейросеть переписывала его заново */
#result:is([data-state="loading"], [data-state="draft"]) .extras,
#result[data-state="error"] :is(.strip, .workbench, .download, .extras),
#result[data-state="missing"] :is(.strip, .workbench, .download, .extras) { display: none; }
/* заметка «редактор откроется…» сворачивается по высоте (grid-rows), а не пропадает: текст обёрнут в <span> (index.html) */
#result[data-state="done"] .panel-note {
  grid-template-rows: 0fr; opacity: 0; margin-bottom: 0; visibility: hidden;
  transition-delay: 0s, 0s, 0s, var(--dur-3);
}
/* пока текст не обёрнут в span, строка 0fr не сожмётся — тогда, как раньше, прячем целиком */
.panel-note:not(:has(> span)) { display: block; }
#result[data-state="done"] .panel-note:not(:has(> span)) { display: none; }

/* пока ответа нет, сцена держит своё место скелетом — иначе рабочий стол вставляется после ответа и сдвигает страницу */
#result[data-state="loading"] .stage canvas { visibility: hidden; }
#result[data-state="loading"] .stage {
  background: var(--surface); border-radius: var(--r-m); box-shadow: var(--shadow-float);
  animation: ghost-pulse 1.6s ease-in-out infinite;
}

/* лента слайдов навылет; правый край уходит в фон так же, как у демо-ленты */
.strip {
  --gap: var(--s4);
  /* тень ленты живёт на самой ленте, а не на карточке: её наследует и заглушка, которая стоит
     на месте слайда, — иначе подмена призрака карточкой читается как подъём на пустом месте */
  --slide-shadow: var(--shadow-float);
  display: flex; gap: var(--gap); overflow-x: auto; scroll-snap-type: x mandatory;
  margin-inline: calc(-1 * var(--gutter)); padding: var(--s3) var(--gutter) var(--s4);
  /* снизу — место под тень карточек (она уходит на ~50px), чтобы край контейнера её не резал; внешний зазор прежний */
  padding-bottom: calc(var(--s7) + var(--s2)); margin-bottom: calc(var(--s4) - var(--s7) - var(--s2));
  scroll-padding-inline: var(--gutter); scrollbar-width: none; -ms-overflow-style: none; overscroll-behavior-x: contain;
  --fade: var(--s7);
  /* слева видна только собственная тень слайда (гаснет в последних --gap перед ним);
     всё, что дальше — в том числе край предыдущего слайда при прокрутке, — скрыто полностью */
  --fade-l-start: max(0px, calc(var(--gutter) - var(--gap)));
  -webkit-mask-image: linear-gradient(90deg, transparent var(--fade-l-start), #000 var(--gutter), #000 calc(100% - var(--fade)), transparent);
  mask-image: linear-gradient(90deg, transparent var(--fade-l-start), #000 var(--gutter), #000 calc(100% - var(--fade)), transparent);
}
/* воздух после последнего слайда (вместе с полем и gap — ширина градиента): докрученная лента не прячет его под фон */
.strip::after { content: ""; flex: 0 0 calc(var(--fade) - var(--gutter)); }
.strip::-webkit-scrollbar { display: none; }
/* В горизонтальной ленте маска начинает гаснуть от самого края поля и срезает кольцо фокуса
   у первой карточки — пока лента в фокусе с клавиатуры, маску снимаем. Вертикальному рельсу
   (≥1024px) это правило только вредит: кольцу там хватает полей (12px против 9px кольца),
   а снятие маски гасит разом оба затухания — при переходе табом края рельса из мягких
   становятся обрубленными. Поэтому правило живёт только в горизонтальной раскладке. */
@media (max-width: 1023px) {
  .strip:has(.slide:focus-visible) { -webkit-mask-image: none; mask-image: none; }
}
.strip > [role="listitem"] { flex: 0 0 auto; display: flex; scroll-snap-align: start; }
.strip .slide, /* один слот на обещание и на ошибку: ошибка заменяет строку над кнопкой, а не вставляется выше.
   Раньше она выталкивала кнопку на 62px и демо-ленту на 122px — ровно в тот миг, когда по кнопке нажали */
/* Обещание стоит справа от кнопки, в её строке: то, ради чего нажимают, — рядом с тем, что нажимают.
   Ошибка формы ложится в ту же ячейку и на время подменяет обещание */
/* основа 12em, а не 16: в колонке формы рядом с кнопкой остаётся ~225px, и 16em переносило обещание под кнопку */
.assure { display: grid; flex: 1 1 12em; min-width: 0; }
.assure > * { grid-area: 1 / 1; margin: 0; }
.tool:has(#form-error:not([hidden])) .guarantee { visibility: hidden; }

/* заглушка держит место слайда — и его высоту тоже: без тени подмена призрака карточкой
   читалась как подъём, хотя это одно и то же место */
.strip .slide-ghost { box-shadow: var(--shadow-float); }
.strip .slide-ghost { flex: 0 0 auto; width: 200px; border-radius: var(--r-m); scroll-snap-align: start; }
.strip .slide {
  overflow: hidden; cursor: pointer; border: 0; background: transparent; padding: 0;
  box-shadow: var(--slide-shadow); -webkit-tap-highlight-color: transparent;
  /* width — для мобильной липкой мини-ленты (#result.editing): карточки сжимаются плавно */
  transition: box-shadow var(--dur-2) var(--ease), transform var(--dur-1) var(--ease), width var(--dur-3) var(--ease);
}
/* первое появление карточек: подъём по очереди (класс и --i ставит app.js, снимает по animationend) */
.strip .slide.is-new { animation: rise var(--dur-3) var(--ease-out) both; animation-delay: calc(var(--i, 0) * 40ms); }
.strip .slide:active { transform: scale(.98); }
/* тихая пометка «не всё влезло»: точка в углу; класс ставит приложение по canvas.dataset.overflow */
.strip .slide { position: relative; }
.strip .slide.is-overflow::after {
  content: ""; position: absolute; top: var(--s2); right: var(--s2); width: 8px; height: 8px;
  border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 2px #fff;
  animation: pop var(--dur-3) var(--ease-out);
}
/* выбранный слайд: оранжевое кольцо с зазором цвета фона — читается на любом стиле слайда.
   Кольцо неконтентное: --accent даёт на фоне 2.95:1, --accent-deep — 3.83:1 (порог 3:1) */
.strip .slide.selected, .strip .slide[aria-current="true"] {
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 5px var(--accent-deep), var(--slide-shadow);
}
/* кольцо фокуса — снаружи оранжевого кольца выбора */
.strip .slide:focus-visible { outline-offset: 7px; }
.strip .slide canvas { width: 100%; height: auto; transition: opacity var(--dur-2) var(--ease); }
/* лента приглушается, пока нейросеть заменяет черновик */
.strip.is-updating canvas { opacity: .35; transition-duration: var(--dur-3); }
/* смена стиля или ника в редакторе: короткий «вдох» (класс на один кадр ставит app.js) */
.strip.is-restyling canvas { opacity: .6; transition-duration: var(--dur-1); }
/* Заглушка держит тень того слайда, чьё место занимает: волосяная рамка вместо тени давала ей
   1.12:1 к фону, и появление настоящей карточки читалось как подъём, хотя это одно и то же место.
   Скелет сцены так сделан с самого начала — теперь так же сделана и лента */
.strip .slide-ghost {
  aspect-ratio: 4 / 5; background: var(--surface); box-shadow: var(--slide-shadow);
  animation: ghost-pulse 1.6s ease-in-out infinite;
}

.download { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: center; }

/* редактор: плоские редакционные секции */
.editor { min-width: 0; }
/* сцена: выбранный слайд крупно (только на десктопе, см. медиазапрос ниже) */
.stage { margin: 0; position: relative; flex: 0 0 auto; }
.stage canvas {
  /* слайд на сцене — тот же слайд, что в ленте: радиус --r-m, а не четвёртый на 20px */
  display: block; width: 100%; height: 100%; border-radius: var(--r-m); box-shadow: var(--shadow-float);
  transition: opacity var(--dur-2) var(--ease);
}
.stage.is-updating canvas { opacity: .35; transition-duration: var(--dur-3); }
.stage.is-restyling canvas { opacity: .6; transition-duration: var(--dur-1); }
.stage.is-overflow::after {
  content: ""; position: absolute; top: var(--s3); right: var(--s3); width: 12px; height: 12px;
  border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px #fff; animation: pop var(--dur-3) var(--ease-out);
}
.extras { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s6); align-items: start; }
.panel { background: none; border: 0; border-top: 2px solid var(--ink); border-radius: 0; padding: var(--s4) 0 0; min-width: 0; }
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); margin-bottom: var(--s4); }
.panel :is(h2, h3) { margin: 0; font-size: var(--t-l); font-weight: 800; line-height: 1.3; display: flex; align-items: baseline; gap: var(--s3); flex: 1 1 auto; min-width: 0; }
.panel-head + .field, .panel :is(h2, h3) + .field, .panel :is(h2, h3) + .caption { margin-top: 0; }
.panel-head :is(h2, h3) { margin: 0; }
.panel > :is(h2, h3) { margin-bottom: var(--s4); }
/* статус правки встаёт в строку состояния, у правого края: там уже есть своя высота, и появление
   «Сохраняем…» во время набора ничего не сдвигает — отдельной строки заводить не пришлось.
   Живёт он внутри .status-main: соседом ей он переносился бы на свою строку, потому что basis
   у .status-main — 24em, больше ширины строки на телефоне */
.status .saved { flex: 0 0 auto; margin-left: auto; }
/* «что скачалось» — это событие, а не подсказка: набрано чуть темнее соседней строки-подсказки */
.status .saved[data-mode="file"] { color: var(--ink-2); }
/* Ход правки говорит знаком, а не словом: «Сохраняем…» — кольцо из штриха (тот же приём, что у занятой
   кнопки), «Сохранено» — галочка цветом ok. Оба в одном поле 16×16, поэтому смена одного на другое
   ничего не двигает; текст остаётся доступным именем, уходит в role="status" и повторяется в title.
   Словами остаются только ошибка правки и заметка о скачанном файле — их знаком не передать. */
.status .saved:not(.err):not([data-mode="file"]) {
  flex: 0 0 16px; width: 16px; height: 16px; margin-top: 4px; overflow: hidden; text-indent: -100em; position: relative;
}
.status .saved:not(.err):not([data-mode="file"])::after {
  content: ""; position: absolute; inset: 0; opacity: 0; transition: opacity var(--dur-2) var(--ease);
}
.status .saved[data-mode="ok"]:not(.err):not(:empty)::after {
  opacity: 1; background: var(--ok);
  -webkit-mask: var(--icon-check) center / contain no-repeat; mask: var(--icon-check) center / contain no-repeat;
}
.status .saved[data-mode="busy"]:not(.err):not(:empty)::after {
  opacity: 1; inset: 1px; box-sizing: border-box; border: 2px solid var(--muted); border-top-color: transparent;
  border-radius: 50%; animation: spin .8s linear infinite;
}
/* «что скачалось» и «не сохранилось» знаком не передашь — ниже 1024px такая заметка занимает
   свою строку под подписью, вровень с ней. Она появляется по явному действию, а не во время
   набора, поэтому сдвиг здесь уместен.
   Ошибку правки сюда пришлось добавить по находке на живом айфоне: она делит строку с подписью,
   и на 390px её 22em съедали всё — подпись «Слова ваши, разложенные по слайдам» сжималась
   до 32px и ломалась по одному слову в строку, а ошибка ложилась поверх неё. Замерено:
   copy 302×20 до появления ошибки и 32×98 после. */
@media (max-width: 1023px) {
  #again-top { display: none; }
  .status-main { flex-wrap: wrap; }
  .status .saved:is([data-mode="file"], .err) { flex: 1 0 100%; margin: 0; padding-left: var(--s5); white-space: normal; max-width: none; }
  /* на телефоне текст панели берёт всю ширину, ссылка «Вернуть» встаёт строкой ниже, у правого края;
     если заметка или ошибка заняла свою строку, ссылку прижимает вправо её собственное авто-поле */
  .status-main > .status-undo { margin-top: var(--s1); margin-left: 0; }
  /* знак правки уходит за ссылку, в правый угол: ссылка остаётся у левого края, под текстом */
  .status-main > .saved { order: 1; }
  /* в молчащей строке выравнивать не под что — заметка и ошибка идут от левого края */
  .status.is-quiet .saved:is([data-mode="file"], .err) { padding-left: 0; }
}
.saved { font-size: var(--t-xs); font-weight: 400; color: var(--muted); white-space: nowrap; transition: opacity var(--dur-2) var(--ease); }
/* «Не сохранилось…» длиннее строки: с nowrap оно вылезало за экран телефона и обрезалось */
.saved.err { color: var(--danger); white-space: normal; max-width: 22em; }
/* пара стрелок стоит чуть особняком от кнопки скачивания: рядом три одинаковых кружка,
   но два из них листают, а третий действует */
.slide-nav { display: flex; gap: var(--s2); flex: 0 0 auto; margin-left: var(--s1); }
.slide-nav .btn { width: 44px; height: 44px; padding: 0; }
.slide-nav #prev-slide { --ico: var(--icon-prev); }
.slide-nav #next-slide { --ico: var(--icon-next); }
#dl-all, #dl-one { --ico: var(--icon-download); }
#again-top { --ico: var(--icon-plus); }
#submit { --ico: var(--icon-carousel); }
#regen { --ico: var(--icon-refresh); }
/* взведённая «разбить заново»: чернильная заливка, как у выбранного, — второе нажатие запускает */
#regen.is-armed { background: var(--ink); color: #fff; border-color: var(--ink); }
#copy-caption { --ico: var(--icon-copy); }
/* «Скачали»: у кнопки с подписью это текст, у кнопки-иконки — та же галочка цветом «готово» */
#dl-one[data-flash] { --ico: var(--icon-check); color: var(--ok); border-color: var(--ok); }
.panel-note {
  display: grid; grid-template-rows: 1fr; margin: 0 0 var(--s4); font-size: var(--t-s); color: var(--muted); max-width: 38em;
  transition: grid-template-rows var(--dur-3) var(--ease), opacity var(--dur-2) var(--ease), margin-bottom var(--dur-3) var(--ease), visibility 0s;
}
.panel-note > span { min-height: 0; overflow: hidden; }
.panel .field + .styles, .panel .styles + .field { margin-top: var(--s5); }
/* «Стиль» и «Ник» меняют всю карусель, а не выбранный слайд — говорим это до, а не после */
/* общее для всей карусели отделено от полей слайда одним воздухом: ни подзаголовка, ни линии —
   охват называют сами подписи, «Стиль всех слайдов» и «Ник на всех слайдах» */
.panel textarea { min-height: 120px; }
.panel :is(input, textarea):disabled { background: var(--bg); color: var(--muted); border-style: dashed; cursor: not-allowed; }
.panel .actions { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s4); }
.panel .caption { white-space: pre-wrap; font-size: var(--t-m); line-height: 1.55; color: var(--ink-2); overflow-wrap: anywhere; }

/* редакционный блок, как и «Подпись к посту»: персиковый в системе означает ожидание, а не апселл */
.pro {
  background: none; border-top: 2px solid var(--ink); border-radius: 0; padding: var(--s4) 0 0; box-shadow: none;
  display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: var(--s4) var(--s5); align-items: center;
}
.pro p { margin: 0; font-size: var(--t-s); color: var(--ink-2); }
.pro p b { color: var(--ink); font-weight: 600; }
.pro p.error { color: var(--danger); }
.pro form { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: flex-start; }
/* почта записана: форма гаснет (класс ставит app.js), затем «Записали…» входит как заметка */
#lead-form.is-leaving { opacity: 0; transition: opacity var(--dur-2) var(--ease-in); }
.pro .lead-field { flex: 1 1 200px; display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.pro input { background: #fff; }
.pro .btn-sm { flex: 0 0 auto; }
.pro .thanks { font-weight: 600; color: var(--ok); }
/* ---------- рабочий стол: слайд крупно + редактор рядом, миниатюры под ними, действия в шапке ---------- */
@media (min-width: 1024px) {
  .result { padding-top: var(--s4); }
  .result h1 { font-size: clamp(24px, 2.4vw, 32px); }
  /* шапка экрана: заголовок и компактный статус слева, действия справа; полная панель статуса — на всю ширину */
  /* строка статуса всегда занимает свой ряд: переход «ожидание → готово» не сдвигает рабочий стол */
  .result-top {
    display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "title actions" "status status";
    gap: var(--s2) var(--s6); align-items: start;
  }
  .result-head { grid-area: title; align-self: center; }
  .download { grid-area: actions; }
  .status { grid-area: status; }
  /* полная панель на рабочем столе — одной строкой: заголовок и подсказка рядом, шаги справа, полоса внизу */
  .status:not(.is-compact) { margin-top: var(--s2); padding: var(--s3) var(--s5) var(--s4); gap: var(--s2) var(--s5); }
  .status:not(.is-compact) .status-copy { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 10px; }
  /* Резерв остаётся, но строка в нём центрируется: заголовок с подсказкой в одну строку жались к верху
     резерва, а шаги справа стояли по его середине — расходились на 9px (замечание владельца) */
  .status:not(.is-compact) .status-copy { min-height: calc(1.3 * var(--t-m) + 1.45 * var(--t-xs)); align-content: center; }
  .status:not(.is-compact) .status-main { align-items: center; }
  .status:not(.is-compact) .status-sub { margin-top: 0; }
  .status:not(.is-compact) .status-icon { margin-top: 0; }
  /* пара «вторичное + главное»: «Новую карусель» ставим слева, у правого края остаётся то,
     ради чего пришли. Ниже 1024px кнопка скрыта: там шапка идёт до редактора, и звать начинать
     заново раньше, чем что-то сделано, незачем — исходная ссылка внизу рабочей области остаётся */
  .download { display: flex; flex-direction: row; align-items: center; gap: var(--s3); }
  /* Обе кнопки шапки одной высоты — 44px: главная отличается заливкой и кеглем, а не габаритом.
     Кегль у неё остаётся 19px/800 не ради вида: белый на оранжевом даёт 3.32:1, и это проходит
     только как крупный текст (порог 3:1). На 15px тот же белый на том же оранжевом — нарушение
     1.4.3, там порог 4.5:1. Поэтому ужимается высота, а не буквы. */
  .download #dl-all { line-height: 24px; padding: var(--s2) var(--s5); min-height: 44px; }
  /* ряд считает себя от ширины контейнера, а не от окна: 100cqw ниже — это она и есть,
     без поправок на поля и на полосу прокрутки, которую 100vw в себя включает */
  .result { container-type: inline-size; }
  /* три колонки: рельс миниатюр — сцена — редактор. Лента ушла из-под рабочего стола наверх, в свободное поле слева,
     поэтому на MacBook 14 в первый экран помещается и выбранный слайд крупно, и редактор, и часть ленты */
  .workbench {
    display: flex; gap: var(--s5); align-items: stretch;
    /* мерки колонок живут здесь, а не каждая у своего правила: из них считается бюджет ниже */
    --rail-thumb: clamp(64px, 5.4vw, 88px);
    /* поля рельса под кольцо выбора (4px) и кольцо фокуса (offset 6 + ширина 3) — иначе overflow их срежет */
    --rail-pad: 12px;
    /* ниже этой ширины поля редактора перестают читаться, и уступает уже не он, а сцена */
    --editor-min: 380px;
    /* Бюджет высоты ряда — меньшее из двух. По вертикали: окно минус шапка сайта, шапка экрана
       и воздух до сгиба; на MacBook 14 (окно 880) даёт 650. По горизонтали: остаток ширины
       после рельса, двух зазоров, отбивки и редактора, переведённый в высоту по 4:5.
       Вторая половина и есть починка: бюджет знал только про высоту окна, и на 1024×1000
       сцена просила 576px ширины там, где ряду оставалось 432, — редактор выдавливало
       за контейнер на 144px, из них 104 за край окна, где overflow-x резал его без прокрутки */
    --stage-h: min(
      clamp(320px, calc(100vh - 230px), 720px),
      calc((100cqw - var(--rail-thumb) - var(--rail-pad) - 2 * var(--s5) - var(--s2) - var(--editor-min)) * 5 / 4)
    );
  }
  .workbench > * { margin-top: 0; }
  /* Ширину сцене даёт бюджет, и ведёт теперь именно ширина, а не высота: при сжатии высота
     идёт за ней и 4:5 остаётся целым, тогда как растянутая по ряду высота (align-self: stretch)
     пропорцию бы сломала — потому сцена и не тянется, а стоит от верха ряда.
     Сжиматься она при этом умеет одна во всём ряду (flex-shrink против min-width у редактора):
     под давлением уступает сцена, как и обещает DESIGN.md. Это страховка поверх бюджета —
     если расчёт разойдётся с раскладкой, ряд всё равно останется в контейнере */
  .stage {
    flex: 0 1 auto; min-width: 0; align-self: start;
    width: calc(var(--stage-h) * 4 / 5); height: auto; aspect-ratio: 4 / 5;
  }
  .stage canvas { height: 100%; width: auto; margin-inline: auto; }
  /* Редактор не ужимается ниже читаемой ширины полей — под давлением уступает сцена.
     База 0, а не auto: с базой по содержимому длинные заголовки просили больше остатка,
     и сжатие делилось между редактором и сценой пополам — на «максимуме» сцена отдавала
     16px сверх бюджета ни за что. С нулевой базой редактор просто забирает остаток,
     а сцена уступает только тогда, когда остаток меньше --editor-min */
  .editor { flex: 1 1 0; min-width: min(var(--editor-min), 100%); margin-left: var(--s2); display: flex; flex-direction: column; }
  /* остаток высоты забирает поле «Текст»: колонка редактора кончается там же, где слайд,
     а не обрывается на середине, оставив под собой пустое место */
  .editor .panel { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
  .editor .panel .field:has(> textarea) { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .editor .panel textarea { flex: 1 1 auto; }
  .editor .panel { border-top: 0; padding-top: 0; }
  .editor .panel-head { min-height: 44px; margin-bottom: var(--s3); }
  .editor .field + .field { margin-top: var(--s3); }
  .editor .panel .styles + .field { margin-top: var(--s4); }
  /* без линии-разделителя воздух перед общим для всей карусели держит группировку сам */
  .editor .panel .field + .styles { margin-top: var(--s5); }
  .editor .chip { padding-inline: var(--s3); }
  .editor textarea { min-height: 100px; }
  /* на рабочем столе о том, что редактор ждёт нейросеть, говорит панель статуса прямо над ним — заметка не нужна */
  .editor .panel-note { display: none; }
  /* рельс миниатюр: колонка слева от сцены, прокручивается внутри себя.
     На MacBook 14 (рельс 640px) видно пять миниатюр из девяти целиком, шестая уходит в градиент — остальные в прокрутке, край гаснет сверху и снизу */
  .strip {
    --gap: var(--s3);
    /* --rail-thumb и --rail-pad заданы на .workbench: из них же считается бюджет высоты ряда */
    --rail-fade: var(--s5);
    flex: 0 0 auto; width: calc(var(--rail-thumb) + 2 * var(--rail-pad));
    flex-direction: column; align-self: stretch;
    /* зоны градиента вынесены за пределы ряда — вверх в зазор под строкой статуса, вниз в зазор до «Подписи»:
       первая и последняя миниатюры встают вровень с верхом и низом сцены, а не на 24px внутрь */
    max-height: calc(var(--stage-h) + 2 * var(--rail-fade));
    /* поля рельса вынесены в margin: сами миниатюры стоят по левому краю колонки, как заголовок над ними */
    margin: calc(-1 * var(--rail-fade)) 0 calc(-1 * var(--rail-fade)) calc(-1 * var(--rail-pad));
    padding: 0 var(--rail-pad);
    /* Упоров у рельса нет намеренно: y proximity вредила дважды. Она возвращала прокрутку
       на ближайший упор и съедала поправку, посчитанную revealSlideInRail, — выбранная
       миниатюра оставалась срезанной снизу на 22px, оранжевое кольцо уходило под градиент.
       Она же запирала колесо: над докрученным до конца рельсом три щелчка давали странице
       0, 0, 0 вместо 0, 300, 300. Место миниатюры в рельсе считает JS, упоры ему только мешают */
    overflow: hidden auto; scroll-snap-type: none; overscroll-behavior: auto;
    scroll-padding-block: var(--rail-fade);
    -webkit-mask-image: linear-gradient(180deg, transparent, #000 var(--rail-fade), #000 calc(100% - var(--rail-fade)), transparent);
    mask-image: linear-gradient(180deg, transparent, #000 var(--rail-fade), #000 calc(100% - var(--rail-fade)), transparent);
  }
  /* распорки в зонах градиента: в покое первая и последняя миниатюры стоят целиком, гаснет только то, что уехало за край.
     Ширина распорки — за вычетом зазора, чтобы первый упор прокрутки совпал с нулём и лента не дёргалась при загрузке */
  .strip::before, .strip::after { content: ""; flex: 0 0 calc(var(--rail-fade) - var(--gap)); }
  .strip > [role="listitem"] { width: 100%; }
  .strip .slide, .strip .slide-ghost { width: 100%; border-radius: var(--r-s); }
  .strip .slide-ghost { box-shadow: var(--shadow-thumb); }
  /* в рельсе тень по размеру миниатюры — и у карточки, и у заглушки на её месте */
  .strip { --slide-shadow: var(--shadow-thumb); }
  @media (hover: hover) { .strip .slide:hover { --slide-shadow: var(--shadow-thumb-lift); } }
  .strip .slide.selected, .strip .slide[aria-current="true"] { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent-deep), var(--slide-shadow, var(--shadow-float)); }
  .strip .slide:focus-visible { outline-offset: 6px; }
  .strip .slide.is-overflow::after { top: var(--s1); right: var(--s1); }
  /* рабочий стол кончается тенью слайда (она уходит на ~50px) и зоной затухания рельса;
     жирная линия «Подписи к посту» в 32px под ним попадала прямо в эту тень */
  .extras { margin-top: var(--s8); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s7); }
  .pro { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- разделы лендинга ---------- */
section.block { padding-block: clamp(48px, 6vw, 80px); border-top: 1px solid var(--line); }
section.block h2 {
  font-family: var(--font-display); font-weight: 700; font-size: clamp(24px, 3vw, 36px);
  letter-spacing: -0.02em; margin: 0 0 var(--s6); max-width: 18em; line-height: var(--lh-display); text-wrap: balance;
}
.steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s6); counter-reset: step; padding: 0; margin: 0; }
.steps li { list-style: none; padding-top: var(--s4); border-top: 2px solid var(--ink); }
.steps li::before {
  counter-increment: step; content: counter(step);
  font-family: var(--font-display); font-weight: 700; font-size: 40px; line-height: var(--lh-display); display: block; margin-bottom: var(--s3);
}
.steps h3 { margin: 0 0 var(--s2); font-size: var(--t-l); font-weight: 800; line-height: 1.3; }
.steps p { margin: 0; color: var(--ink-2); }

/* Без линий между пунктами: волосяная линия уже делит секции страницы, и та же линия внутри блока
   читалась «маслом масляным» (владелец). Пункты разделяет воздух и вес подписи */
.who { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s6) var(--s7); padding: 0; margin: 0; }
.who li { list-style: none; padding: 0; display: grid; grid-template-columns: minmax(0, 11em) 1fr; gap: var(--s4); }
.who li b { font-size: var(--t-m); font-weight: 600; }
.who li span { color: var(--ink-2); }

.price { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s6); }
.price > div { padding-top: var(--s4); border-top: 2px solid var(--ink); }
.price .big { font-family: var(--font-display); font-weight: 700; font-size: 40px; line-height: var(--lh-display); letter-spacing: -0.02em; margin: 0 0 var(--s3); display: inline-flex; align-items: baseline; gap: var(--s2); }
.price .big small { font-family: var(--font-body); font-weight: 600; font-size: var(--t-s); letter-spacing: 0; color: var(--muted); }
/* цена Pro не решена: колонка «Скоро» звучит тише бесплатного тарифа, а не крупнее его */
.price-soon .big { font-size: var(--t-xl); }
.price-note { color: var(--muted) !important; font-size: var(--t-s); }
.price p { margin: 0; color: var(--ink-2); }
.price p + p, .price ul + p { margin-top: var(--s3); }
.price ul { margin: var(--s3) 0 0; padding-left: var(--s5); color: var(--ink-2); }
.price li { margin: var(--s1) 0; }

.faq details { border-bottom: 1px solid var(--line); padding: 0; max-width: 46em; }
.faq summary {
  cursor: pointer; font-weight: 600; font-size: var(--t-m); list-style: none;
  display: flex; justify-content: space-between; align-items: center; gap: var(--s4); padding: var(--s3) 0; min-height: 44px;
}
.faq summary::-webkit-details-marker { display: none; }
/* знак «плюс» не меняется на «минус», а поворачивается в «крестик»; при наведении — оранжевый */
.faq summary::after {
  content: ""; flex: 0 0 auto; width: 16px; height: 16px; color: var(--ink);
  background: currentColor; -webkit-mask: var(--icon-plus) center / contain no-repeat; mask: var(--icon-plus) center / contain no-repeat;
  transition: transform var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}
.faq details[open] summary::after { transform: rotate(45deg); }
.faq details[open] > p { animation: note-in var(--dur-2) var(--ease-out); }
.faq p { margin: 0; padding-bottom: var(--s4); color: var(--ink-2); max-width: 38em; }

footer { padding-block: var(--s6) var(--s7); border-top: 1px solid var(--line); font-size: var(--t-xs); color: var(--muted); }
/* мера — та же, что у прозы разделов (34em): на 44em строка в 13px растягивалась до 72 знаков,
   и глаз терял начало следующей */
footer p { margin: var(--s1) 0; max-width: 34em; }

.result-again { margin: 0; text-align: center; }

.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* Ссылка перескока молчит, пока до неё не дошли клавиатурой, и тогда встаёт пилюлей цвета чернил
   в левом верхнем углу — теми же мерками, что малая кнопка: 44px, 10px/16px, Manrope 600 15px.
   Специфичность .skip:focus-visible (0, 2, 0) перебивает .sr-only (0, 1, 0), поэтому отменять
   его приходится по свойствам: положение, размеры, обрезку и переполнение */
.skip:focus-visible {
  position: fixed; top: var(--s3); left: var(--s3); z-index: 10;
  width: auto; height: auto; clip: auto; overflow: visible;
  display: inline-flex; align-items: center; min-height: 44px; padding: 10px 16px;
  border-radius: 999px; background: var(--ink); color: var(--surface);
  font-size: var(--t-s); font-weight: 600; line-height: 20px; text-decoration: none;
}
.nowrap { white-space: nowrap; }

/* ---------- планшет и телефон ---------- */
@media (max-width: 900px) {
  .hero { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .hero > div:first-child { display: contents; }
  .hero h1 { order: 1; }
  .hero .lead { order: 2; }
  /* На телефоне пример стоит между лидом и формой, как 11 сентября: человек сначала видит,
     что получит, и только потом — куда вставлять. С 12 сентября форма шла первой, а пример
     уезжал на 650px вниз, и первый экран телефона показывал форму без обещания */
  .hero .demo { order: 3; position: static; margin: 0 0 var(--s5); }
  .hero .tool { order: 4; }
  .hero > div:first-child > :is(h1, .lead, .tool) { max-width: 640px; }
  .styles-hint .at-side { display: none; }
  .styles-hint .at-top { display: inline; }
  .hero > div:first-child > .demo { max-width: 640px; }
  :root { --demo-w: min(300px, 54vw); }
  .demo-strip {
    margin-inline: calc(-1 * var(--gutter)); padding-inline: var(--gutter); scroll-padding-inline: var(--gutter);
    --fade-l: var(--gutter); --fade-l-start: max(0px, calc(var(--gutter) - var(--s4)));
  }
  .editor { grid-template-columns: minmax(0, 1fr); }
  /* прилипшая лента закрывает прокрутку сплошным фоном — без градиента на краю */
  #result.editing .strip {
    position: sticky; top: 0; z-index: 2; background: var(--bg); padding-block: var(--s2); -webkit-mask-image: none; mask-image: none;
    box-shadow: 0 8px 16px -12px rgba(23, 22, 58, .28);
  }
  #result.editing .strip .slide { width: 112px; box-shadow: 0 1px 3px rgba(23, 22, 58, .18); }
  #result.editing .strip .slide.selected, #result.editing .strip .slide[aria-current="true"] {
    box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent-deep), 0 1px 3px rgba(23, 22, 58, .18);
  }
}

@media (max-width: 800px) { .pro { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 760px) { .steps, .price { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 1000px) { .who li { grid-template-columns: minmax(0, 1fr); gap: var(--s1); } }
@media (max-width: 700px) { .who { grid-template-columns: minmax(0, 1fr); gap: var(--s5); } }

@media (max-width: 640px) {
  /* первый экран компактнее: поле ввода начинается до 640px, кнопка — до 1100px на 390×844 */
  .top { padding-block: var(--s4) var(--s2); }
  .hero { padding-top: var(--s3); }
  /* первый экран поджимаем отступами, а не кеглем: 30px перебивали собственный clamp(32px, …),
     а лид на 16px оказывался мельче всей прозы документа — два размера мимо шкалы шести */
  h1 { margin-bottom: var(--s3); }
  .lead { font-size: var(--t-m); margin-bottom: var(--s5); }
  :root { --demo-w: 52vw; }
  .hero .demo { margin-bottom: 20px; }
  .demo-note { font-size: var(--t-xs); }
  .demo-dots { display: none; }
  .tool, .panel { padding-inline: var(--s4); }
  .tool { padding-block: var(--s4); }
  .tool textarea { min-height: 132px; resize: none; }
  .btn:not(.btn-sm):not(.btn-link) { width: 100%; }
  /* на телефоне кнопка во всю ширину, обещание — над ней: справа места нет */
  .tool .submit { flex-direction: column; align-items: stretch; gap: var(--s3); }
  /* flex: auto обязателен: в колонке основа 12em стала бы высотой, и обещание растягивалось на 270px */
  .tool .submit .assure { order: -1; flex: 0 0 auto; }
  .download { flex-direction: column; align-items: stretch; }
  .pro { padding: var(--s4); }
  .pro .btn-sm { width: 100%; }
  /* полоса статуса: шаги под текстом в одну строку, соединительные линии растягиваются между подписями, полоса под ними */
  .status { padding: var(--s3) var(--s4) var(--s5); gap: var(--s3); }
  .status-main { flex-basis: 100%; }
  .status-steps { width: 100%; }
  .status-steps li + li { padding-left: 0; }
  .status-steps li:not(:last-child) { flex: 1 1 auto; }
  .status-steps li:not(:last-child)::after { content: ""; position: static; display: block; flex: 1 1 8px; min-width: 8px; width: auto; height: 1px; margin-inline: 8px; background: var(--line-strong); }
  .status-steps li:last-child::after { display: none; }
  /* На телефоне текст берёт всю ширину и переносится под точку: 28px, которые съедали точка
     и место под знак правки, стоили самому длинному сообщению третьей строки, а пары подобраны
     под две. Резерв здесь — на две строки заголовка и строку подсказки: столько занимает
     каждое из сообщений ожидания на 360–430, поэтому пустоты он не оставляет */
  .status:not(.is-compact) .status-copy {
    flex-basis: 100%;
    min-height: calc(2 * 1.3 * var(--t-m) + 1.45 * var(--t-xs) + 2px);
  }
  .status-bar { left: var(--s4); right: var(--s4); }
  .status.is-compact { padding-inline: 0; }
  .status.is-compact .status-copy { display: block; }
  .status.is-compact .status-sub::before { content: none; }
  .status.is-compact .status-sub { margin-top: 2px; }
  .strip { --gap: var(--s3); }
  /* на телефоне лента — это сцена: слайд занимает почти всю ширину, следующий выглядывает */
  .strip .slide, .strip .slide-ghost { width: min(64vw, 260px); }
}

@media (max-width: 400px) {
  .price .big { display: block; }
  .price .big small { display: block; margin: var(--s1) 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, ::before, ::after { transition: none !important; animation: none !important; }
  /* застывшее кольцо с прорехой читается как поломка — без вращения показываем целое */
  .status .saved[data-mode="busy"]:not(.err):not(:empty)::after { border-top-color: var(--muted); }
}
