/* ============ Фирменный шрифт ============
   Bungee (Дэвид Джонатан Росс) — шрифт вывесок: узкий, плотный,
   ровно под короткое имя из двух слов. Bungee Shade — та же буква
   с объёмной тенью, для крупной подачи.

   Файлы лежат рядом, в fonts/: политика безопасности страницы
   разрешает только 'self', и внешние адреса вроде Google Fonts
   всё равно не загрузились бы. Лицензия обоих — OFL, её текст
   обязан лежать рядом со шрифтом: fonts/OFL.txt.

   Внимание: подмножества латинские, кириллицы в них нет. Поэтому
   ставим их только на латинские надписи («Karaoke Punch»), а не на
   русские заголовки — там браузер всё равно взял бы запасной шрифт.

   font-display: swap — пока файл не пришёл, надпись видна запасным
   шрифтом, а не пустым местом. */
@font-face {
  font-family: 'Bungee';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/bungee.woff2') format('woff2');
}
@font-face {
  font-family: 'Bungee Shade';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/bungee-shade.woff2') format('woff2');
}

/* Запасные шрифты заданы всюду, где применяется фирменный: не пришёл
   файл — надпись просто станет обычной жирной, но ничего не поедет */
:root {
  --font-brand: 'Bungee', Impact, "Haettenschweiler", "Arial Narrow Bold", system-ui, sans-serif;
  --font-brand-shade: 'Bungee Shade', 'Bungee', Impact, "Haettenschweiler", system-ui, sans-serif;
}

/* ============ Базовые стили ============ */
:root {
  --bg: #0a0a0f;
  --bg-soft: #12121a;
  --bg-card: #16161f;
  --border: #26263a;
  --text: #f2f2f7;
  --text-dim: #9a9ab0;
  /* ---- Зелёный: одна спокойная семья ----
     Прежний зелёный был неоновый: изумруд #10b981 в заливке, лайм
     #84cc16 на конце кнопочного градиента, мятный #34d399 в тексте.
     Человек сказал прямо: «наш зелёный цвет слишком режет глаз», —
     показал кнопку GitHub и назвал её цвет числом: #359C6E. Он и стал
     основанием. Остальные ступени — тот же тон (153°) и та же
     насыщенность, разная только светлота:

       #2C815B  глубокий — низ градиентов и полоса-объявление;
       #359C6E  основной — заливка кнопок, рамки, ход;
       #54C492  светлый  — текст, ссылки, кольцо выделения
                           (контраст 8.3 к панели: читается и мелким);
       #67CB9E  второй   — ручка ползунка, волна, наведение;
       #95DBBB  мягкий   — подписи на зелёном фоне;
       #B3E5CE  самый светлый — огонёк в полосе-объявлении.

     Кнопки больше не радуга: заливка ровная, как у образца. Прежний
     градиент уходил в лайм, и резало глаз сильнее всего именно это.
     --grad-btn остался переменной — нейтральная и стальная темы
     держат в ней свои градиенты, — но в фирменной это ровный цвет. */
  --violet: #359C6E;
  --pink: #67CB9E;
  --orange: #f97316;
  --grad: linear-gradient(90deg, #54C492, #67CB9E);
  --grad-btn: #359C6E;
  /* Тот же зелёный числами и сплошным цветом. Он был написан прямо
     цветом в полутора десятках мест — подсветка активного шага,
     текущая строка, закладки оформления, галки, полосы прогресса, —
     и каждое такое место темы приходилось перекрашивать адресно
     (а половину забывали). Теперь цвет один и берётся отсюда, а тема
     переопределяет его у себя внутри .studio: rgb(var(--accent-rgb) / 0.1)
     даёт любую прозрачность от нынешнего акцента. */
  /* ---- Дуэт: цвета партий ----
     Одни и те же во всех трёх темах: это не оформление, а роль.
     Певец узнаёт свою строку по цвету за два метра от экрана, и цвет
     не должен меняться от того, какую тему он выбрал. Голубой против
     розового — самая читаемая пара и при дальтонизме: они расходятся
     и по светлоте, а не только по тону. */
  --part-1: #38bdf8;
  --part-2: #fb7185;
  --part-both: #fbbf24;
  --accent-rgb: 53 156 110;
  --accent-solid: #54C492;

  /* ============ Скругления: одна лестница на всё ============
     Раньше форм было три: таблетки (999px) у кнопок, круги у пуска
     и вкладок-номеров и прямоугольники у окон. Человек сказал прямо:
     «все зелёные кнопки у нас скруглённые по стандарту, кнопка
     пуск/пауза — круглая… я бы хотел привести это больше к стилю
     Apple… как у нас окна в редакторе — прямоугольные со скруглёнными
     краями. Вот и все остальные окна и кнопки сделай в том же стиле,
     а то у нас разброс по стилю».

     Теперь ступеней три, и все они — прямоугольник со скруглением:
       --r-mini  мелочь: значки-кнопки, ярлыки, номера шагов;
       --r-ctl   всё, что нажимают: кнопки, вкладки, поля, пункты меню;
       --r-box   всё, что держит другое: карточки, окна, меню, панели.
     Круглыми остаются только точки и ручки — они не кнопки:
     точка несохранённого, отсчёт перед строкой, бегунки ползунков.

     Сами числа взяты с образца: человек прислал снимок панели
     инструментов Final Cut — «берём скругление этих кнопок как эталон
     для скругления и окон и кнопок». Там кнопка высотой около трёх
     десятков точек скруглена примерно на шестую часть своей высоты;
     отсюда шесть точек у кнопки, четыре у мелочи и восемь у окна.
     Это заметно строже прежних шестнадцати и совпадает с тем, что
     уже было на дорожке (--ed-radius: 5px) — то есть студия и её
     монтажная область наконец одной породы. */
  --r-mini: 4px;
  --r-ctl: 6px;
  --r-box: 8px;

  /* Высота всего, что стоит в верхнем ряду: чип проекта, значки работы
     с проектом, вкладки шагов, «Как пользоваться». Человек сказал:
     «сделай все верхние кнопки одной высоты, возьми высоту кнопок
     сохранения черновика — они корректной высоты». Их высота и стоит
     здесь; раньше в ряду было четыре разных: 26,6 · 30 · 34,9 · 40. */
  --top-h: 30px;
  --radius: var(--r-box);
}

/* ============ Монтажная плотность студии ============
   Значения по образцу монтажных программ (Logic Pro, Final Cut):
   мельче скругления, ниже строка, вдвое теснее поля. Область
   действия — .studio (все четыре шага), сама сцена караоке
   (.lyrics-stage, .edit-stage, .stage-wrap) эти переменные не
   использует и остаётся нынешнего крупного вида. Цифровое
   начертание — системный моноширинный: свой файл шрифта грузить
   незачем, а табличные цифры (font-variant-numeric) у него уже есть. */
:root {
  --ed-radius: 5px;
  --ed-radius-ctl: 4px;
  --ed-row: 24px;
  --ed-gap: 0.35rem;
  --ed-pad: 0.4rem;
  --ed-font: 12px;
  --ed-num: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, "Liberation Mono", monospace;
}

/* ============ Тема оформления ============
   Тем три.
   «Фирменная» (signature, по умолчанию) держит прежний синевато-чёрный
   грунт, а зелёный — ровно в четырёх местах: кнопка воспроизведения,
   включённый переключатель, выделенный блок, цифры счётчика.
   «Нейтральная» (neutral) убирает оттенок из грунта и держит вместо
   зелёного жёлтую рамку выделения — ей самое место там, где важно
   заметить, а не показать бренд.
   «Стальная» (steel) — холодный серо-стальной грунт, панели светлее
   фона, синий акцент и заголовки полос темнее рабочего поля: манера
   больших монтажных программ (Logic Pro), где студия должна читаться
   как инструмент того же класса.
   Тема — атрибут на <html>, как и язык (см. app.js, «Переключение
   темы»); переменные ниже видны только внутри .studio и не трогают
   остальной сайт. */
.studio {
  /* Грунт фирменной темы совпадает с нынешними --bg-card/--border/--text
     сайта — их не переопределяем, только четыре места акцента и то,
     чего в общих переменных нет (фон самого канваса дорожки). */
  --ed-canvas-bg: #0e0e15;
  /* Колонка заголовков полос. В двух тёмных темах она чуть светлее
     панели (обычная подсветка), в стальной — наоборот, темнее рабочего
     поля, как в Logic Pro. */
  --tl-heads-bg: rgba(255, 255, 255, 0.025);
  --sel-ring: #54C492;
  --sel-ring-bg: rgba(84, 196, 146, 0.14);
  --sel-ring-glow: rgba(84, 196, 146, 0.5);
  --accent-btn-bg: var(--grad-btn);
  --accent-btn-glow: 0 4px 16px rgba(53, 156, 110, 0.4);
  --accent-on-bg: rgba(53, 156, 110, 0.16);
  --accent-on-border: #359C6E;
  --accent-on-text: #95DBBB;
  --accent-clock: #7ED3AD;
  /* Полосы прокрутки студии — те же цвета, что и у страницы, но
     посчитанные ЗДЕСЬ: --border и --bg-soft тема переопределяет
     внутри .studio, и только на этом элементе var() даст жёлтые
     и синие оттенки вместо фирменных. Наследование дальше сделает
     остальное: список строк, инспектор и поле текста песни лежат
     внутри и берут полосу отсюда. */
  scrollbar-color: color-mix(in srgb, var(--border) 70%, var(--text) 30%) var(--bg-soft);
}
html[data-theme="neutral"] .studio {
  /* Серый грунт без оттенка — переопределяем сами общие переменные:
     все селекторы редактора уже читают var(--bg-card)/var(--border)
     и подхватят новые значения сами, без переписывания каждого правила. */
  --bg-card: #232326;
  --bg-soft: #1c1c1e;
  --border: #38383c;
  --text: #e8e8ea;
  --text-dim: #9b9ba1;
  --ed-canvas-bg: #1a1a1c;
  --sel-ring: #f0c419;
  --sel-ring-bg: rgba(240, 196, 25, 0.14);
  --sel-ring-glow: rgba(240, 196, 25, 0.5);
  /* Зелёного в нейтральной теме не остаётся нигде: акцент у неё свой,
     жёлтый. --violet и --pink здесь тоже переопределены (ими покрашены
     рамка активного шага, наведение на кнопки редактора, ручка ползунка
     и рамка поля текста), а --grad и --grad-btn красят главную кнопку
     шага, кружок с номером активного шага, полосы прогресса и кнопку
     простукивания. На жёлтом светлый текст не читается — для него
     заведён --accent-ink. */
  --violet: #f0c419;
  --pink: #f7dd6b;
  --grad: linear-gradient(90deg, #f0c419, #f7dd6b);
  --grad-btn: linear-gradient(90deg, #d9a800, #f0c419, #f7dd6b);
  --accent-rgb: 240 196 25;
  --accent-solid: #f0c419;
  --accent-ink: #17171a;
  --accent-btn-bg: #3a3a40;
  --accent-btn-glow: none;
  --accent-on-bg: rgba(255, 255, 255, 0.08);
  --accent-on-border: #4d4d55;
  --accent-on-text: var(--text);
  --accent-clock: var(--text);
}
/* «Стальная»: холодный серый грунт, панели СВЕТЛЕЕ фона (в двух других
   темах наоборот), синий акцент вместо зелёного и колонка заголовков
   полос темнее рабочего поля дорожки. Канвас остаётся тёмным нарочно:
   волна, блоки строк и слова нарисованы светлыми полупрозрачными
   красками, и на светлом поле от них не осталось бы ничего.
   --violet и --pink здесь тоже переопределены (в нейтральной теме нет):
   ими покрашены рамка активного шага, наведение на кнопки редактора
   и рамка поля текста — зелень в них выдавала бы прежнюю тему. */
html[data-theme="steel"] .studio {
  --bg-soft: #1c1f25;
  --bg-card: #262a33;
  --border: #3a404d;
  --text: #e7ebf2;
  --text-dim: #99a2b3;
  --violet: #4d9fff;
  --pink: #7cc0ff;
  --ed-canvas-bg: #22262e;
  --tl-heads-bg: #191c22;
  --grad: linear-gradient(90deg, #6fb2ff, #3f86e0);
  --grad-btn: linear-gradient(180deg, #4f9bf0, #2f6fc4);
  --accent-rgb: 77 159 255;
  --accent-solid: #4d9fff;
  --sel-ring: #4d9fff;
  --sel-ring-bg: rgba(77, 159, 255, 0.16);
  --sel-ring-glow: rgba(77, 159, 255, 0.55);
  --accent-btn-bg: linear-gradient(180deg, #4f9bf0, #2f6fc4);
  --accent-btn-glow: 0 3px 12px rgba(47, 111, 196, 0.45);
  --accent-on-bg: rgba(77, 159, 255, 0.18);
  --accent-on-border: #4d80c0;
  --accent-on-text: #cfe4ff;
  --accent-clock: #8fc2ff;
}
/* Адресных правил для тем здесь больше нет. Раньше стальная тема
   перекрашивала поимённо четыре места, где зелёный был написан прямо
   цветом (главная кнопка шага, подсветка активного шага, его кружок
   и текущая строка списка), — а нейтральная те же места не
   перекрашивала, и в ней они оставались зелёными. Мест таких на деле
   было не четыре, а полтора десятка: закладки оформления, галки,
   полосы прогресса, кнопка простукивания, ручка ползунка.

   Теперь цвет берётся из переменных (--grad, --grad-btn, --accent-rgb,
   --accent-solid, --violet, --pink), и тема задаёт их у себя один раз.
   Перечислять места не нужно вовсе — забыть их тоже больше нельзя.

   Что нарочно НЕ идёт за темой: сцена караоке и её просмотр (их вид
   человек настраивает сам, и он уходит в видео), значок слов `♪`
   на дорожке (жёлто-зелёный — тем же цветом, что и слова на канвасе,
   это цветовая азбука разметки, а не акцент оболочки) и окно
   «Что нового» — оно вне .studio, как и весь остальной сайт. */

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

/* ============ Тёмная гамма и курсор ввода ============
   Две беды, которые видно только на Windows.

   Первая — полосы прокрутки. Пока страница не сказала, в какой она
   гамме, за неё решает система: на Windows со светлой темой полосы
   выходят БЕЛЫЕ и режут глаз посреди тёмной студии — и в списке
   строк, и в инспекторе, и в поле текста песни, и у самой страницы.
   На macOS беды не видно: там полосы и так тёмные и всплывают только
   при прокрутке. Лечится одним словом: color-scheme: dark — страница
   объявляет свою гамму, и система рисует тёмные полосы на ЛЮБОЙ теме
   Windows, а не только на тёмной. Заодно тёмными становятся и
   системные части полей (список у <select>, галки) — это тоже к месту.

   Вторая — курсор ввода. Без caret-color цвет выбирает не разметка,
   а браузер: обычно берёт цвет текста (тогда курсор сливается
   с буквами), а на Windows решение уходит системе — и в тёмной студии
   курсора случается не видеть вовсе. Ради этого когда-то и завели
   caret-color у текста строки. Раньше цвет был задан адресно и ровно
   в одном месте — у текста строки в сетке (.edit-text), — а прочие
   полтора десятка полей (текст песни, числа инспектора, поиск по
   строкам, темп и смещение сетки долей) оставались на милость системы.
   Теперь правило одно на все поля сразу, и новое поле получает цвет
   курсора само, без единой дописанной строчки. Цвет — акцент темы:
   он свой у каждой из трёх (зелёный, жёлтый, синий) и всюду читается
   на тёмном фоне поля. */
:root {
  color-scheme: dark;
  /* Полосы прокрутки в гамме темы: желоб — грунт, ползунок — рамка,
     подсветленная текстом до заметного. Оба цвета берутся из
     переменных, поэтому четвёртая тема получит свои полосы сама.
     Повторено у .studio нарочно: var() считается на самом элементе,
     и правило, написанное здесь, взяло бы цвета фирменной темы
     и для жёлтой, и для синей. */
  scrollbar-color: color-mix(in srgb, var(--border) 70%, var(--text) 30%) var(--bg-soft);
}

input, textarea, [contenteditable] { caret-color: var(--accent-solid); }

body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
  line-height: 1.6;
  overflow-x: hidden;
}

h2 {
  font-size: clamp(1.6rem, 4vw, 2.4rem);
  text-align: center;
  margin-bottom: 2.2rem;
  letter-spacing: -0.02em;
}

section { padding: 5rem 1.5rem; max-width: 1100px; margin: 0 auto; }

.hidden { display: none !important; }

/* ============ Кнопки ============ */
.btn {
  display: inline-block;
  /* Прозрачная рамка, а не её отсутствие: иначе кнопки с рамкой
     на 2px выше соседних и ряд выглядит неровным */
  border: 1px solid transparent;
  border-radius: var(--r-ctl);
  font-size: 1rem;
  font-weight: 600;
  font-family: inherit;
  /* Явная высота строки: у кнопок она системная, а эмодзи в подписях
     раздувают её по-разному — из-за этого ряд получался неровным */
  line-height: 1.6;
  color: var(--text);
  cursor: pointer;
  text-decoration: none;
  transition: transform 0.15s, box-shadow 0.15s, opacity 0.15s;
  padding: 0.75rem 1.6rem;
}
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary {
  background: var(--grad-btn);
  /* Градиент должен покрывать и область рамки. По умолчанию он
     считается по внутренней области и повторяется в рамке — слева
     проступала светлая полоска с конца градиента */
  background-origin: border-box;
  background-repeat: no-repeat;
  box-shadow: 0 4px 24px rgb(var(--accent-rgb) / 0.35);
  /* На светлом акценте (жёлтый в нейтральной теме) светлый текст
     не читается — там тема задаёт свой --accent-ink */
  color: var(--accent-ink, var(--text));
}
.btn-primary:not(:disabled):hover { transform: translateY(-2px); box-shadow: 0 8px 32px rgb(var(--accent-rgb) / 0.5); }
.btn-ghost {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
}
.btn-ghost:not(:disabled):hover { border-color: var(--violet); color: var(--text); }
.btn-big { font-size: 1.15rem; padding: 1rem 2.4rem; }
.btn-small { font-size: 0.9rem; padding: 0.55rem 1.2rem; }
/* Кнопка-значок среди обычных кнопок. Без этого правила значок
   садился на базовую линию текста (у .btn — inline-block с полями под
   подпись), и в ряду он стоял заметно выше центра: человек прислал
   снимок со стрелкой на этот значок. Делаем кнопку квадратной, а значок
   ставим по центру обеих осей. */
.btn.icon-btn,
/* Второй селектор — не про красоту, а про вес. Правило витрины
   (body.is-desktop .only-desktop { display: revert }) специфичнее
   двух классов и перебивало бы display, а .export-row .btn — padding.
   Поэтому здесь тот же вес плюс элемент. */
body.is-desktop .btn.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.3rem;
  height: 2.3rem;
  padding: 0;
}
.btn.icon-btn .icon,
body.is-desktop .btn.icon-btn .icon { margin: 0; }


/* ============ Значки ============
   Один спрайт (см. index.html, .icon-sprite) на всё приложение вместо
   эмодзи: у эмодзи разный рисунок на macOS и Windows — разная ширина
   и высота строки, это было видно на снимках. Значок наследует цвет
   текста (currentColor) и садится по кеглю кнопки (1em), поэтому
   в любом месте выглядит одинаково и не требует своего размера. */
.icon {
  width: 1em;
  height: 1em;
  flex: none;
  vertical-align: -0.15em;
}
/* Кнопка со значком и подписью: небольшой зазор между ними, значок
   слева. У кнопок с одним только значком (без подписи) зазора нет —
   его и нечему разделять. */
.btn .icon, .nudge-btn .icon, .steps-help .icon, .whatsnew-close .icon,
.stage-full-btn .icon, .side-head .icon { margin-right: 0.4em; }
.whatsnew-close .icon, .stage-full-btn .icon { margin-right: 0; }
.icon-check { margin-left: 0.35em; margin-right: 0 !important; }
#btn-sel-words:not(.marked) .icon-check { display: none; }

/* ============ Полоса обновления ============ */
.update-bar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  flex-wrap: wrap;
  padding: 0.7rem 1.5rem;
  background: linear-gradient(90deg, rgba(44, 129, 91, 0.95), rgba(53, 156, 110, 0.95));
  color: #f2f2f7;
  font-size: 0.95rem;
  font-weight: 600;
}
.update-dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: #B3E5CE;
  box-shadow: 0 0 10px #B3E5CE;
}
.update-bar .btn-primary { background: #0a0a0f; box-shadow: none; }
.update-bar .btn-primary:hover { background: #16161f; transform: none; }
.update-later {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.85);
  font-family: inherit;
  font-size: 0.9rem;
  text-decoration: underline;
  cursor: pointer;
}

/* ============ Переключатели языка и темы ============

   Оба стоят в шапке, рядом, в одном flex-элементе (.header-switches) —
   так пара держится вместе и не съезжает при разной длине подписей
   на разных языках.

   Устройство у них одно: кнопка со значком нынешнего выбора и уголком,
   а по нажатию — список с названиями и галкой у выбранного. Раньше
   язык был парой кнопок «Рус» и «Eng», а тема — списком: два соседних
   органа управления делали одно и то же, а читались как разные.
   Поэтому стили здесь общие (`pick-*`), а свои классы (`theme-*`,
   `lang-*`) остались, чтобы переключатель можно было найти снаружи.

   Без флагов и цветных кружков: флаг обозначает страну, а не язык,
   а образец темы точнее покажет сама студия. Значки — глобус и значок
   нынешней темы (см. ЗНАЧОК_ТЕМЫ в app.js). */
.header-switches { display: inline-flex; align-items: center; gap: 0.5rem; }
.pick-switch {
  /* Опорная точка для выпадающего списка: он встаёт под кнопкой */
  position: relative;
  display: inline-flex;
}
.pick-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  color: var(--text-dim);
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
  /* Значок вместо подписи: названия занимали в шапке больше места,
     чем оба переключателя вместе, а читают их ровно один раз.
     Названия стоят в пунктах списка, в подсказке и в aria-label. */
  padding: 0.35rem 0.6rem;
  cursor: pointer;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.pick-btn .icon { width: 1.05em; height: 1.05em; margin: 0; }
/* Уголок «здесь список»: вдвое мельче основного значка, чтобы не спорил с ним */
.pick-btn .pick-caret { width: 0.6em; height: 0.6em; opacity: 0.7; }
.pick-btn:hover { color: var(--text); }
.pick-btn[aria-expanded="true"] { color: var(--text); background: rgba(255, 255, 255, 0.08); }
/* Сам список. Стоит под кнопкой и прижат к правому краю: обе кнопки
   последние в шапке, и список, растущий вправо, вылезал бы за окно. */
.pick-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 60;
  min-width: 11rem;
  display: flex;
  flex-direction: column;
  padding: 0.25rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-box);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.5);
}
.pick-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.5rem;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 0.82rem;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
.pick-item .icon { flex: none; width: 1.05em; height: 1.05em; margin: 0; }
/* Галка выбранного стоит справа и держит место у всех пунктов —
   иначе подписи ездили бы по горизонтали при смене выбора */
.pick-item .pick-tick { margin-left: auto; opacity: 0; }
.pick-item[aria-checked="true"] { color: var(--text); }
.pick-item[aria-checked="true"] .pick-tick { opacity: 1; }
.pick-item:hover, .pick-item:focus-visible { color: var(--text); background: rgba(255, 255, 255, 0.08); outline: none; }
/* В узком окне шапка набивается плотно — ужимаем кнопки. Размеры
   в em, поэтому вместе с кеглем сжимаются и они. */
@media (max-width: 1120px) {
  .pick-btn { font-size: 0.74rem; padding: 0.3rem 0.5rem; }
}
/* На совсем узком окне меню и так прячется (см. ниже .site-nav) —
   темой можно пожертвовать раньше языка: язык важнее для чтения */
@media (max-width: 760px) {
  .theme-switch { display: none; }
}

/* ============ Шапка ============ */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.8rem 1.5rem;
  background: rgba(10, 10, 15, 0.8);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
}
.logo { display: flex; align-items: center; gap: 0.6rem; text-decoration: none; color: var(--text); font-weight: 700; font-size: 1.1rem; }
/* Имя студии фирменным шрифтом — в шапке и в подвале.

   Bungee заметно шире системного шрифта, поэтому кегль здесь ниже
   обычного, а межбуквенный просвет отрицательный: иначе на узком
   экране имя вытесняло бы кнопку «Открыть студию». Ниже, в разделе
   узких экранов, оно ужимается ещё раз. */
.logo-text {
  font-family: var(--font-brand);
  font-weight: 400;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.logo-icon { font-size: 1.4rem; }
.logo-img {
  display: block;
  width: 32px;
  height: 32px;
  border-radius: var(--r-ctl);
  flex-shrink: 0;
}
.footer-brand { display: inline-flex; align-items: center; gap: 0.5rem; }
.footer-brand .logo-img { width: 24px; height: 24px; border-radius: 6px; }
.footer-brand .logo-text { font-size: 0.9rem; }
/* Меню занимает всю середину шапки, а ссылки в нём стоят по центру.
   Так свободное место шапки собрано в одном месте — внутри меню,
   а не поделено между всеми четырьмя углами: правый край кнопки
   «Открыть студию» и переключатель языка перестают ездить, когда
   перевод меняет длину подписей. Замер на 1440 px: раньше при смене
   языка переключатель уезжал на 51 px, теперь стоит на месте. */
/* Меню стоит ровно посередине шапки, а не посередине остатка. Оно
   делило свободное место между широким логотипом слева и парой узких
   переключателей справа — и выходило заметно правее середины экрана.
   Абсолютное положение считается от самой шапки: она sticky, то есть
   позиционированный элемент. */
.site-nav {
  display: flex;
  gap: 1.5rem;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}
.site-nav a { color: var(--text-dim); text-decoration: none; font-size: 0.95rem; transition: color 0.15s; }
.site-nav a:hover { color: var(--text); }

/* ============ Hero ============ */
.hero {
  position: relative;
  text-align: center;
  /* Отступы посчитаны так, чтобы кнопки скачивания попадали в первый
     экран на 1280×720: упор проекта на настольную версию, и предлагать
     её после сгиба бессмысленно — туда не долистывают. */
  padding: 3.5rem 1.5rem 3rem;
  max-width: none;
}
/* Пятна света за первым экраном.

   Рисуем их радиальным градиентом, а НЕ размытым кругом (filter: blur).
   Размытие на пол-экрана Safari складывает отдельным слоем, и стоило
   рядом чему-нибудь шевельнуться при наведении (кнопка приподнимается
   на две точки), как на месте пятна оставался тёмный прямоугольник —
   его собственная рамка. Градиент выглядит так же, слоя не заводит,
   и перерисовывать нечего. Круги втрое больше прежних: у размытия свет
   уходил далеко за край круга, и без запаса пятна вышли бы мельче. */
.hero-glow {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}
.hero-glow-1 {
  width: 1100px;
  height: 1100px;
  top: -450px;
  left: calc(8% - 300px);
  background: radial-gradient(closest-side, rgb(53 156 110 / 0.35), rgb(53 156 110 / 0));
}
.hero-glow-2 {
  width: 1000px;
  height: 1000px;
  top: -225px;
  right: calc(5% - 275px);
  background: radial-gradient(closest-side, rgb(45 212 191 / 0.22), rgb(45 212 191 / 0));
}
.hero-inner { position: relative; max-width: 800px; margin: 0 auto; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: #54C492; box-shadow: 0 0 8px #54C492; }
/* Имя студии крупно, Bungee Shade. Мельче ставить нельзя: у этого
   шрифта буква нарисована вместе с объёмной тенью, и на маленьком
   кегле тень слипается с самой буквой в грязь. Отсюда и нижняя
   граница clamp — 1.7rem, ниже она не опускается даже на 320 px. */
.hero-wordmark {
  font-family: var(--font-brand-shade);
  font-weight: 400;
  font-size: clamp(1.55rem, 5.2vw, 3.2rem);
  line-height: 1.25;
  letter-spacing: 0.01em;
  color: #54C492;
  margin-bottom: 1rem;
  /* Тень нарисована в самом шрифте, поэтому свечение вокруг делаем
     мягким и слабым — иначе оно забивает рельеф букв */
  text-shadow: 0 0 28px rgba(84, 196, 146, 0.35);
}
.hero h1 {
  font-size: clamp(2.1rem, 5.4vw, 3.6rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
  margin-bottom: 1.5rem;
}
.grad-text {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero-sub {
  color: var(--text-dim);
  font-size: 1.05rem;
  max-width: 620px;
  margin: 0 auto 1.6rem;
}
/* Повод скачать — на первом экране, без прокрутки. Раньше первый экран
   обещал караоке вообще, а чем приложение лучше браузера, человек узнавал
   только пролистав вниз — и кнопки скачивания приходилось рисовать дважды.
   Теперь причина стоит рядом с кнопкой, а вторая пара кнопок убрана. */
.hero-why {
  list-style: none;
  margin: 0.9rem auto 0;
  padding: 0;
  max-width: 30rem;
  display: grid;
  gap: 0.45rem;
  text-align: left;
  color: var(--text-dim);
  font-size: 0.92rem;
  line-height: 1.45;
}
.hero-why li {
  padding-left: 1.5rem;
  position: relative;
}
/* Галочка вместо маркера: это перечень того, что человек получит. */
.hero-why li::before {
  content: '✓';
  position: absolute;
  left: 0;
  top: 0;
  color: var(--accent-solid);
  font-weight: 700;
}
.hero-why b { color: var(--text); font-weight: 600; }

.hero-note.hero-note { margin-top: 1.4rem; color: var(--text-dim); font-size: 0.9rem; display: flex; align-items: center; justify-content: center; gap: 0.6rem; }
.pill {
  background: rgba(84, 196, 146, 0.12);
  color: #54C492;
  padding: 0.15rem 0.7rem;
  border-radius: var(--r-mini);
  font-size: 0.8rem;
  font-weight: 600;
}

/* ============ Настольная версия ============ */
.desktop-cta {
  /* Ровно как студия: секция по умолчанию 1100, и блок скачивания
     выглядел уже всего остального на странице. Ширину считаем тем же
     способом, иначе он окажется шире её на величину полей. */
  max-width: 1360px;
  width: calc(100% - 3rem);
  padding-left: 0;
  padding-right: 0;
  text-align: center;
  background: radial-gradient(ellipse at 50% 0%, rgba(53, 156, 110, 0.14), transparent 70%), var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: calc(var(--r-box) + 4px);
  padding: 3rem 2rem;
}
.desktop-lead {
  color: var(--text-dim);
  max-width: 820px;
  margin: 0 auto 2rem;
}
.desktop-buttons {
  display: flex;
  gap: 1rem;
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: 1.4rem;
}
.desktop-note {
  color: var(--text-dim);
  font-size: 0.88rem;
  max-width: 560px;
  margin: 0 auto;
}
.desktop-note a { color: #54C492; }

.first-run {
  max-width: 620px;
  margin: 1.6rem auto 0;
  text-align: left;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.4rem;
}
.first-run summary { cursor: pointer; font-weight: 600; font-size: 0.95rem; }
.first-run p { color: var(--text-dim); font-size: 0.9rem; margin-top: 0.7rem; }
/* Шаги первого запуска: нумерованный список, а не сплошной абзац —
   человек идёт по ним с окном системы перед глазами и должен видеть,
   на каком он сейчас */
.first-run-steps {
  margin: 0.6rem 0 0;
  padding-left: 1.3rem;
  color: var(--text-dim);
  font-size: 0.9rem;
}
.first-run-steps li { margin-top: 0.5rem; line-height: 1.5; }
.first-run-steps b { color: var(--text); }
.first-run-steps code {
  font-size: 0.85em;
  padding: 0.05em 0.3em;
  border-radius: 5px;
  background: var(--bg);
  border: 1px solid var(--border);
}
.first-run pre {
  margin-top: 0.6rem;
  padding: 0.7rem 0.9rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  color: #54C492;
  font-size: 0.8rem;
  overflow-x: auto;
}

/* ============ Карточки ============ */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.2rem; }
.card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.8rem;
  transition: transform 0.2s, border-color 0.2s;
}
.card:hover { transform: translateY(-4px); border-color: var(--violet); }
.card-icon { font-size: 2rem; margin-bottom: 0.8rem; }
.card h3 { margin-bottom: 0.5rem; font-size: 1.15rem; }
.card p { color: var(--text-dim); font-size: 0.95rem; }
.card a, .faq a { color: #54C492; }

/* ============ Как это работает ============ */
/* Шаги стоят сеткой 2×2, а не строкой из четырёх: в руководстве под
   каждым шагом лежит по абзацу-другому, и в колонке 250 px строка
   выходила в тридцать знаков — читать неудобно. Колонок ровно две
   (не auto-fit: с ним последний шаг срывался вниз в одиночку). */
.how-steps { list-style: none; display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem 2rem; }
.how-steps li { display: flex; gap: 0.7rem; align-items: flex-start; }
.how-num {
  flex-shrink: 0;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: var(--r-mini);
  background: var(--grad);
  font-weight: 700;
}
.how-steps p { color: var(--text-dim); font-size: 0.85rem; }
.how-steps p + p { margin-top: 0.5rem; }
.how-steps b { color: var(--text); }
.how-num { width: 34px; height: 34px; font-size: 0.9rem; }
.how-lead { color: var(--text-dim); margin: -0.6rem 0 1.4rem; }

/* Редактор и экспорт — под свёрнутыми заголовками: рекомендованный
   путь должен читаться за полминуты, а подробности нужны не всем
   и не сразу. Оформление то же, что у «Частых вопросов». */
.how-more { margin-top: 1.6rem; display: grid; gap: 0.6rem; }
.how-more details {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-card);
  padding: 0.9rem 1.2rem;
}
.how-more summary {
  cursor: pointer;
  font-weight: 600;
  list-style: none;
  user-select: none;
}
.how-more summary::-webkit-details-marker { display: none; }
.how-more summary::before { content: '▸ '; color: var(--text-dim); }
.how-more details[open] summary::before { content: '▾ '; }
.how-more-body { margin-top: 0.8rem; }
.how-more-body p { color: var(--text-dim); font-size: 0.9rem; line-height: 1.6; }
.how-more-body p + p { margin-top: 0.7rem; }
.how-more-body b { color: var(--text); }

@media (max-width: 900px) {
  .how-steps { grid-template-columns: minmax(0, 1fr); }
}

/* ============ Студия ============ */
.studio {
  /* Студия — рабочее место, а не кусок витрины: она занимает окно
     целиком, поэтому любой шаг виден весь и страницу крутить не надо. */
  display: flex;
  flex-direction: column;
  max-width: 1360px;
  width: calc(100% - 3rem);
  /* Высота окна за вычетом шапки (она липкая и накрывает верх страницы)
     и собственных полей: студия целиком видна, как только до неё
     доскроллили. --header-h ставит app.js по настоящей высоте шапки. */
  height: calc(100vh - var(--header-h, 69px) - 3rem);
  min-height: 480px;
  scroll-margin-top: calc(var(--header-h, 69px) + 0.5rem);
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: calc(var(--r-box) + 4px);
  /* Боковое поле — переменной: дорожка выходит из него в край окна
     отрицательным полем ровно на эту величину (см. .timeline-wrap). */
  --studio-pad-x: 1.5rem;
  padding: 1.25rem var(--studio-pad-x) 1rem;
  margin-top: 1rem;
  margin-bottom: 2rem;
}
/* Заголовок в рабочем месте — подпись, а не витрина: каждый лишний
   десяток пикселей здесь отнимается у сцены */
.studio h2 { font-size: 1.4rem; margin-bottom: 0.7rem; }
/* Своя подсказка кнопок студии (см. ПОДСКАЗКА в app.js). Лежит внутри
   .studio, а не в body: переменные темы живут на самой студии, и в
   нейтральной теме подсказка из body осталась бы фирменной. Место
   выставляет код, поэтому здесь только вид. */
.tip {
  position: fixed;
  z-index: 300;
  /* Ширина по содержимому, а не по остатку места справа: у элемента
     с position: fixed она иначе зависит от left, и подсказка, которую
     код только что измерил у левого края, после переноса к правому
     становилась уже — и вылезала за край ровно на эту разницу. */
  width: max-content;
  max-width: 280px;
  padding: 0.35rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  background: var(--bg-card);
  color: var(--text);
  font-size: 0.78rem;
  line-height: 1.35;
  font-weight: 400;
  text-align: left;
  text-transform: none;
  letter-spacing: 0;
  white-space: normal;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
  /* Подсказка не должна мешать щелчку и не должна ловить указатель:
     иначе она сама себя закрывала бы, встав под курсором */
  pointer-events: none;
}
/* Ряд шагов: слева работа (чип проекта и значки), посередине сами шаги,
   справа ссылка на руководство. Три столбца сеткой, а не две плашки
   поверх ряда. Пока стороны стояли absolute, узкое окно ломало ряд:
   при 860 точках значок «выгрузить разметку» оказывался ВНУТРИ плашки
   «1 Песня», при 640 значки, стрелки и плашки лежали друг поверх друга
   и прочитать их было нельзя. Теперь стороны занимают место
   по-настоящему: шаги ужимаются и переносятся, но ни на что не лезут.
   Крайние столбцы равны (1fr) — ряд шагов стоит ровно посередине,
   как и стоял, и высоты рабочее место не теряет. */
.steps-nav {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.9rem;
}
/* Столбцы называем поимённо: в разметке шаги идут первыми, и без
   этого они вставали в левый столбец, а работа — в средний. */
.steps-tabs {
  grid-column: 2;
  display: flex;
  gap: 0.5rem;
  justify-content: center;
  flex-wrap: wrap;
}
.steps-help {
  grid-column: 3;
  grid-row: 1;
  justify-self: end;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: var(--top-h);
  padding: 0 0.85rem;
  color: var(--text-dim);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.15s, border-color 0.15s;
}
.steps-help:hover { color: var(--text); border-color: var(--violet); }
/* Что в памяти студии и черновик — левый столбец ряда шагов.

   Коробка стоит в потоке обычным элементом сетки. Никакого absolute
   и никакого transform: translateY(-50%) — перенос заводил для коробки
   свой слой отрисовки, и выпадающее меню чипа уезжало в этот слой:
   на снимке человека сквозь список читалось «поиск по строкам» и весь
   список строк под ним. Разметка при этом была верной — под точками
   внутри меню лежало меню, — но составитель кадра рисовал окно студии
   поверх чужого слоя. */
.proj-box {
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
}
.proj-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
  max-width: 15rem;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  height: var(--top-h);
  padding: 0 0.7rem;
  color: var(--text-dim);
  font-size: 0.8rem;
  font-weight: 600;
}
.proj-chip .icon { flex: none; opacity: 0.7; }
/* Чип стал кнопкой с меню: своя семья шрифта (у button её нет),
   курсор и уголок — как у переключателей темы и языка в шапке. */
.proj-chip { font-family: inherit; cursor: pointer; background: transparent; }
.proj-chip:hover { color: var(--text); }
.proj-chip[aria-expanded="true"] { color: var(--text); background: rgba(255, 255, 255, 0.08); }
.proj-chip .pick-caret { width: 0.6em; height: 0.6em; opacity: 0.7; }
/* Точка несохранённого. Цветом акцента темы, а не красным: это
   не ошибка, а «на диске этого ещё нет». */
.proj-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--sel-ring, #4d9fff);
}
/* Список чипа растёт вправо: чип стоит слева в ряду шагов, и список,
   прижатый к правому краю (как у переключателей в шапке), уехал бы
   под сами шаги. */
.proj-menu { right: auto; left: 0; }

/* Недавние проекты на первом шаге */
.recent { margin: 0.9rem auto 0; max-width: 560px; width: 100%; text-align: left; }
.recent-head {
  color: var(--text-dim);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 0.4rem;
}
.recent-list { display: flex; flex-direction: column; gap: 0.3rem; }
.recent-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.45rem 0.7rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  color: var(--text);
  font-family: inherit;
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
}
.recent-item:hover { border-color: var(--sel-ring); }
.recent-item .icon { flex: none; opacity: 0.7; }
.recent-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recent-path {
  margin-left: auto;
  color: var(--text-dim);
  font-size: 0.75rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 45%;
  text-align: right;
}
/* Имя песни бывает длинным — обрезаем многоточием, целиком оно
   всё равно написано в подсказке */
.proj-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.steps-tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--top-h);
  height: var(--top-h);
  flex: none;
  border: 1px solid var(--border);
  border-radius: var(--r-mini);
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.steps-tool:hover { color: var(--text); border-color: var(--violet); }
/* Кнопки проекта есть только в приложении. Общее правило прячет
   .only-desktop через display: revert, а кнопке этого мало: revert
   отбрасывает и наш inline-flex, на котором держится значок внутри. */
body.is-desktop .steps-tool.only-desktop { display: inline-flex; }
/* Файл разметки — за чертой от кнопок проекта. Черта только
   в приложении: на сайте проекта нет, и отделять там нечего.
   Одинаковые кружки подряд читались как равные способы сохраниться,
   а главный из них в приложении один — проект папкой. */
.steps-tool-group { display: inline-flex; align-items: center; gap: 0.35rem; }
body.is-desktop .steps-tool-group {
  margin-left: 0.3rem;
  padding-left: 0.65rem;
  border-left: 1px solid var(--border);
}
/* Короткий ответ на «сохранить»: кнопка на секунду становится галочкой
   в цвете темы. Окошком «сохранено» отвечать нельзя — Cmd+S жмут часто,
   и закрывать окно пришлось бы каждый раз. */
/* Финала на сцене нет: похвала, логотипы и счёт живут только
   в записанном видео — там же, где водяной знак (см. drawФиналКадра
   в app.js). В студии это было окно поверх сцены, которое приходилось
   закрывать: караоке — это то, что видно на сцене, а праздник
   с логотипами уместен в готовом файле, а не в рабочем окне. */

/* ============ Дуэт ============
   Партия строки видна в трёх местах сразу: в списке строк (полоска
   слева), на дорожке (цвет блока) и на сцене (цвет текста). Выбирается
   она кнопками в инспекторе, и цвет кнопки — тот же самый. */
.part-picker { display: inline-flex; gap: 2px; }
.part-btn {
  width: 26px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-dim);
  font: 600 11px/1 var(--font-num, inherit);
  cursor: pointer;
  border-radius: var(--ed-radius, 5px);
}
.part-btn .icon { width: 12px; height: 12px; margin: 0; }
.part-btn:hover { color: var(--text); border-color: var(--text-dim); }
.part-btn.p1 { color: var(--part-1); }
.part-btn.p2 { color: var(--part-2); }
.part-btn.pb { color: var(--part-both); }
.part-btn.on { border-color: currentColor; background: color-mix(in srgb, currentColor 18%, transparent); }
.part-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.insp-parts .sel-label { flex: 1; }

/* Подсказка «что это и как сделать» внутри панели инспектора.
   Видна, только пока делать нечего: как только отрезок выбран,
   она уходит и не отнимает высоту у того, ради чего панель открыта. */
.insp-how {
  margin-top: 0.5rem;
  color: var(--text-dim);
  font-size: 0.78rem;
  line-height: 1.45;
}

/* Полоска партии слева от строки в списке.

   Отдельным слоем, а не box-shadow: тенью помечены и выбранная строка,
   и попавшая в выделенный диапазон, — и у выбранной полоска партии
   просто исчезала. Голос строки не должен пропадать оттого, что на неё
   нажали. */
.edit-row.part-1, .edit-row.part-2, .edit-row.part-both { position: relative; }
.edit-row.part-1::after, .edit-row.part-2::after, .edit-row.part-both::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  pointer-events: none;
}
.edit-row.part-1::after { background: var(--part-1); }
.edit-row.part-2::after { background: var(--part-2); }
.edit-row.part-both::after { background: var(--part-both); }

/* Текст на сцене, в предпросмотре и в видео. Подменяем не сам цвет,
   а переменную --st-active, которой строка красится: тогда работают
   все три способа подсветки (заливка, подсветка, без эффекта), а
   закраска спетого по-прежнему идёт акцентом. Цвет виден и у ждущей
   строки — певец должен узнать свою ЗАРАНЕЕ, а не когда уже петь. */
.stage-line.part-1 { --st-active: var(--part-1); }
.stage-line.part-2 { --st-active: var(--part-2); }
.stage-line.part-both { --st-active: var(--part-both); }

.steps-tool.saved {
  color: var(--accent-solid);
  border-color: var(--accent-solid);
}
.steps-tool .icon { margin: 0; }
/* В узком окне ряд шагов подбирается к чипу вплотную: сперва прячем
   имя песни (оно есть в подсказке), потом весь чип — кнопки черновика
   важнее, без них работу не сохранить */
@media (max-width: 1200px) {
  .proj-name { display: none; }
  .proj-chip { padding: 0.3rem 0.5rem; }
}
@media (max-width: 900px) {
  .proj-chip { display: none; }
}
.steps-help-text { margin-left: 0.4rem; }
/* В узком окне ряд шагов подбирается к ссылке вплотную: сперва ужимаем
   её, а совсем близко к краю оставляем один знак вопроса — подпись
   всё равно повторяется в подсказке при наведении. */
@media (max-width: 1120px) {
  .steps-help { font-size: 0.78rem; padding: 0.3rem 0.6rem; }
}
@media (max-width: 1000px) {
  .steps-help-text { display: none; }
}
.step-tab {
  display: flex; align-items: center; gap: 0.5rem;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  color: var(--text-dim);
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  height: var(--top-h);
  padding: 0 1.1rem;
  cursor: pointer;
  transition: all 0.15s;
}
.step-tab span {
  width: 20px; height: 20px;
  display: grid; place-items: center;
  border-radius: var(--r-mini);
  background: var(--border);
  font-size: 0.75rem;
}
.step-tab.active { border-color: var(--violet); color: var(--text); background: rgb(var(--accent-rgb) / 0.1); }
/* Кружок активного шага залит ровно. Градиент здесь был последним
   на верхнем ряду, и человек сказал убрать: «убери градиент на верхних
   кнопках». Берём --violet — сплошной акцент темы, тот же, что красит
   рамку самой вкладки: зелёный в фирменной, жёлтый в нейтральной,
   синий в стальной. */
.step-tab.active span { background: var(--violet); color: var(--accent-ink, inherit); }
.step-tab:disabled { opacity: 0.4; cursor: not-allowed; }

.step-panel { display: none; max-width: 720px; width: 100%; margin: 0 auto; }
/* Рабочий шаг делит между своими частями ровно ту высоту, которая
   осталась от окна, и прокручивается внутри себя, а не страницей.
   Раньше шаг рос как хотел: панель «Оформление текста» уводила сцену
   за нижний край экрана, и правку не было видно. */
.step-panel.active { display: flex; flex-direction: column; flex: 1; min-height: 0; }
/* Вся ширина студии — всем четырём шагам. Раньше её получали только
   редактор и караоке, а «Песня» и «Текст» оставались столбцом
   в 720 px посреди рабочего места шириной 1232 px: полстудии пустовало,
   и студия выглядела собранной из разных программ. */
#step-1, #step-2, #step-3, #step-4 { max-width: none; }
/* Первые два шага короткие: ставим их посередине рабочего места,
   а поле текста растягиваем на всю высоту. Если содержимое всё-таки
   не влезло (низкое окно, настольные блоки нейросети) — прокручивается
   сам шаг, а не страница: студия остаётся на месте. */
#step-1.active { justify-content: safe center; }
#step-1.active, #step-2.active { overflow-y: auto; }
#step-2 #lyrics-input { flex: 1; min-height: 140px; }

/* --- Шаг «Песня» на всю ширину ---
   Карточки «Фон для караоке», «Своя минусовка» и (в приложении)
   «Убрать вокал нейросетью» встают в ряд, а не длинным столбцом:
   иначе широкое рабочее место наполовину пустует, а список уезжает
   под нижний край. Сама песня и кнопки шага идут во всю ширину.

   Колонок ровно столько, сколько карточек: на сайте их две, в приложении
   к ним добавляется удаление вокала — тогда три. Считать колонки через
   auto-fit нельзя: песня и кнопки лежат поперёк всех колонок, пустых
   не остаётся, и лишняя колонка не схлопнулась бы. */
#step-1 .track-info {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
  align-content: start;
}
#step-1 .track-info:has(> #ai-block) {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
#step-1 .track-info > .track-row,
#step-1 .track-info > .mono-warning,
#step-1 .track-info > .voice-lost,
#step-1 .track-info > .inst-short,
#step-1 .track-info > .panel-actions { grid-column: 1 / -1; }
/* Просветы задаёт сетка, свои отступы карточкам больше не нужны */
#step-1 .track-info > .bg-upload,
#step-1 .track-info > .voice-lost,
#step-1 .track-info > .inst-short,
#step-1 .track-info > .mono-warning { margin-top: 0; }
#step-1 .track-info > .bg-upload { align-content: start; }

/* --- Шаг «Текст» на всю ширину ---
   Поле текста — главное окно шага, ему и достаётся вся ширина
   и вся оставшаяся высота.

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

   Колонка нарочно широкая, а поле текста уже. Беда была в обратном:
   в колонке 360 px тот же текст занимал вдвое больше строк, ряд
   «Язык — Распознать — Распознать с нуля» переносился на две строки,
   и на ноутбуке 1280×720 блок не помещался по высоте — внутри него
   появлялась своя полоса прокрутки. Замеры на 1280×720: в колонке
   360 px содержимому нужно было 588 px при 432 доступных, в колонке
   640 px нужно 384 — помещается целиком. Поле текста при этом всё
   равно шире, чем строка любой песни.

   Подсказка «Вставь текст песни…» переехала в правую колонку, к полю,
   о котором она и говорит: в левой она отъедала у блока 64 px высоты,
   которых как раз не хватало. */
#step-2.active { gap: 0.8rem 1rem; }
#step-2 > .panel-hint { margin-bottom: 0; }
#step-2 > .panel-actions { margin-top: 0.2rem; }
#step-2.active:has(> .asr-block) {
  display: grid;
  grid-template-columns: clamp(18rem, 54%, 40rem) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
}
/* Подсказки-строки над окнами больше нет вовсе: она делала два окна
   шага разной высоты и с разным верхом, а говорила то же, что и пустое
   поле текста. Теперь её слова стоят в самом поле подсказкой (см.
   placeholder у #lyrics-input) и уходят, как только вставлен текст. */
#step-2.active:has(> .asr-block) > .asr-block {
  grid-area: 1 / 1 / 2 / 2;
  margin-bottom: 0;
  min-height: 0;
  /* Прокрутка остаётся страховкой на совсем низкое окно, но в рабочих
     размерах содержимое помещается целиком и полосы не видно */
  overflow-y: auto;
}
#step-2.active:has(> .asr-block) > #lyrics-input { grid-area: 1 / 2 / 2 / 3; }
#step-2.active:has(> .asr-block) > .panel-actions { grid-area: 2 / 1 / 3 / 3; }
.panel-hint { color: var(--text-dim); margin-bottom: 1rem; text-align: center; }
.panel-actions { display: flex; justify-content: space-between; gap: 1rem; margin-top: 1.5rem; flex-wrap: wrap; }

/* --- Шаг 1: dropzone --- */
.dropzone {
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  padding: 3.5rem 2rem;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s;
}
.dropzone:hover, .dropzone.dragover { border-color: var(--pink); background: rgb(var(--accent-rgb) / 0.05); }
.dropzone-icon { font-size: 3rem; margin-bottom: 1rem; }
.dropzone-title { font-size: 1.15rem; font-weight: 600; margin-bottom: 0.5rem; }
.dropzone-hint { color: var(--text-dim); font-size: 0.9rem; }

.processing { text-align: center; padding: 3rem 0; }
.spinner {
  width: 48px; height: 48px;
  margin: 0 auto 1.2rem;
  border: 4px solid var(--border);
  border-top-color: var(--pink);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Что уже лежит в памяти студии — над зоной загрузки, до всякого
   выбора файла. Тон спокойный: это не ошибка, а напоминание. */
.mem-note {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.8rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--sel-ring);
  border-radius: var(--ed-radius);
  background: var(--bg-card);
  color: var(--text-dim);
  font-size: 0.85rem;
  line-height: 1.5;
  text-align: left;
}
.track-info { padding: 1rem 0; }
.track-row {
  display: flex; align-items: center; gap: 1rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.2rem 1.5rem;
}
.track-icon { font-size: 2rem; }
.track-name { font-weight: 700; word-break: break-all; }
.track-meta { color: var(--text-dim); font-size: 0.9rem; }
.mono-warning { margin-top: 1rem; color: #fbbf24; font-size: 0.92rem; }
/* Минусовка нейросети не пережила перезапуск (см. обновитьПотерюМинусовки
   в app.js). Заметнее, чем предупреждение о моно: то говорит про
   свойство файла, а это — про звук, который человек унесёт в караоке,
   ничего не заметив. Отсюда рамка и подложка, а не одна цветная строка.
   Тон всё равно спокойный: это не ошибка студии, а напоминание. */
.voice-lost {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 1rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid rgba(251, 191, 36, 0.35);
  border-left: 3px solid #fbbf24;
  border-radius: var(--ed-radius);
  background: rgba(251, 191, 36, 0.08);
  color: #fbbf24;
  font-size: 0.88rem;
  line-height: 1.5;
  text-align: left;
}
/* Своя минусовка короче песни — та же тихая строка предупреждения */
.inst-short {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 1rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid rgba(251, 191, 36, 0.35);
  border-left: 3px solid #fbbf24;
  border-radius: var(--ed-radius);
  background: rgba(251, 191, 36, 0.08);
  color: #fbbf24;
  font-size: 0.88rem;
  line-height: 1.5;
  text-align: left;
}
.voice-lost .icon,
.inst-short .icon { flex: none; margin-top: 0.15em; }

.bg-upload {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 1rem;
  padding: 1rem 1.5rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  flex-wrap: wrap;
}
.bg-preview {
  width: 72px;
  height: 48px;
  object-fit: cover;
  border-radius: var(--r-ctl);
  border: 1px solid var(--border);
}
/* Главное действие шага. Порядок на первом шаге — это порядок работы,
   но одного порядка мало: три одинаковые карточки читаются как три
   равнозначных «необязательно». Та, с которой начинают (убрать вокал
   нейросетью), не необязательная — у неё акцентная рамка и подложка,
   и подпись под ней говорит, что дальше. */
.bg-upload.key-action {
  border-color: rgb(var(--accent-rgb) / 0.5);
  background: rgb(var(--accent-rgb) / 0.07);
}
.key-step {
  width: 100%;
  margin: 0.1rem 0 0;
  color: var(--text-dim);
  font-size: 0.85rem;
}
.key-step b { color: var(--text); font-weight: 600; }
.bg-upload-text { flex: 1; min-width: 180px; }
.bg-upload-text b { display: block; }
.bg-upload-text span { color: var(--text-dim); font-size: 0.85rem; }

/* --- Шаг 2: текст --- */
#lyrics-input {
  width: 100%;
  min-height: 300px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.8;
  padding: 1.2rem 1.5rem;
  resize: vertical;
}
#lyrics-input:focus { outline: none; border-color: var(--violet); }

/* --- Общие мелочи разметки --- */
/* Строка, которую нейросеть не расслышала при подгонке текста:
   время подобрано на глазок. Пометка тихая — не крик, а подсказка. */
.guess-mark {
  color: var(--text-dim);
  font-size: 0.9rem;
  cursor: help;
  margin-left: 0;
}
.nudge { display: flex; gap: 0.25rem; }
.nudge-btn {
  min-width: 30px;
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.45rem;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--ed-radius-ctl);
  color: var(--text-dim);
  font-family: inherit;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.nudge-btn:hover:not(:disabled) { border-color: var(--violet); color: var(--text); }
.nudge-btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* Кнопка только со значком, без подписи (отменить/повторить) — квадратная */
.nudge-btn.icon-btn { min-width: 0; width: 26px; justify-content: center; padding: 0.15rem; }
.nudge-btn.icon-btn .icon { margin-right: 0; }

/* --- Шаг 3: редактор ---
   Редактор — самый плотный экран студии. Он делит высоту окна между
   своими частями: сетка строк с предпросмотром забирают весь остаток,
   дорожка и панель выбранной строки стоят своим размером. Прокручивается
   только список строк, а не страница. */
#step-3 { --step-gap: 0.5rem; }
#step-3.active { gap: var(--step-gap); }
#step-3 .panel-hint { font-size: 0.82rem; margin-bottom: 0; flex-shrink: 0; }
/* Окно рабочего места: заголовок и содержимое, как панели в монтажной */
.ed-pane {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  /* Окна — теги section и aside, а у section в общих стилях свои поля
     витринного раздела: здесь они не нужны */
  max-width: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--ed-radius);
  background: var(--bg-card);
  overflow: hidden;
  font-size: var(--ed-font);
}
/* Заголовок окна («СТРОКИ», «ПРОСМОТР», «СТРОКА») стоит дёшево по
   высоте: полтора десятка пикселей вместо трёх. Читаться он обязан,
   а вот межстрочный интервал 1,6 от общего текста страницы и поля
   в 0,3rem съедали 29 px на каждое окно — на редакторе это высота,
   которой не хватало дорожке. */
.ed-pane-head {
  flex-shrink: 0;
  padding: 0.12rem var(--ed-pad);
  border-bottom: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-dim);
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.ed-pane-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--ed-gap);
  padding: 0.25rem var(--ed-pad);
}
/* Сцена просмотра держит 16:9 и всё равно не занимает окно целиком —
   поле вокруг неё вычиталось из высоты дважды. Оставляем самое малое,
   чтобы рамка окна не срезала край сцены. */
.ed-viewer .edit-stage-wrap { margin: 0; padding: 0.2rem; }
/* Три окна рабочего места: строки — просмотр — инспектор.
   Кегль в просмотре пропорционален его ширине (см. fitStageLines),
   поэтому окну просмотра отдана заметная доля.

   Высоту окна делят с дорожкой через разделитель (см. «Деление высоты»
   в app.js): окна забирают остаток, дорожка стоит своим числом
   пикселей. Нижняя граница здесь — только страховка на случай, если
   код деления почему-то не отработал; настоящий предел (сколько окнам
   оставить, когда дорожку тянут вверх) держит МИН_ОКОН в app.js —
   иначе окна упирались бы в свой минимум, а лишнее вылезало бы
   за нижний край подложки студии. */
/* Ширина инспектора — не число в разметке, а доля рабочей ширины:
   её ставит применитьШиринуИнспектора (см. «Ширина инспектора»
   в app.js). Число здесь — запасное: столько инспектор занимал,
   пока границу нельзя было двигать. */
/* Ширина списка строк и ширина инспектора — не числа в разметке,
   а доли рабочей ширины: их ставят применитьШиринуСписка и
   применитьШиринуИнспектора (см. app.js). Числа здесь запасные.

   Дорожка НИЖЕ этой сетки и идёт во всю ширину шага: она в редакторе
   главная, и отдавать её ширину боковым панелям нельзя — панели
   ужимаются по высоте, а дорожка остаётся целой. */
/* Окна идут от края до края — ровно как дорожка под ними. Человек
   написал прямо: «у нас монтажная панель с дорожками во всю ширину,
   а у верхних окон есть отступы — зачем?». Незачем: боковое поле
   студии окна отыгрывают обратно тем же способом, что и дорожка,
   и крайние окна теряют внешнюю рамку со скруглением — там край окна,
   а не край карточки. */
.editor-grid {
  margin-inline: calc(-1 * var(--studio-pad-x, 1.5rem));
  display: grid;
  grid-template-columns:
    var(--list-w, 1.1fr) auto minmax(0, 1fr) auto var(--insp-w, 210px);
  /* По вертикали зазор нужен, по горизонтали — нет: окна примыкают
     к разделителю вплотную (см. .ed-vsplitter). */
  row-gap: var(--ed-gap);
  column-gap: 0;
  flex: 1 1 0;
  min-height: 80px;
}
.editor-grid > .ed-pane:first-child {
  border-left: none;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.editor-grid > .ed-pane:last-child {
  border-right: none;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

/* ---------- Разделители: список — просмотр — инспектор
   Устроены одинаково, поэтому правила общие ----------
   Тянется мышью, как граница окон и дорожки под ним. Полоска узкая,
   а брать её удобно: сама колонка 8 px, а видимая черта внутри —
   3 px посередине. */
/* Разделители стоят ВПЛОТНУЮ между окнами: своей ширины у них почти
   нет, а зазор сетки вокруг них убран (column-gap: 0 у .editor-grid).
   Так сделано в Final Cut: окна примыкают друг к другу, между ними
   тонкая черта. Прежние восемь точек плюс два зазора съедали под
   тридцать точек ширины на каждой границе — а ширина в редакторе
   и есть самое дорогое. Тянуть при этом удобно по-прежнему: сама
   черта в пару точек, а невидимая зона захвата шире её впятеро
   (::after ниже). */
.ed-vsplitter,
.ed-lsplitter {
  position: relative;
  width: 2px;
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  /* Отменяем прокрутку страницы пальцем на самом разделителе: иначе
     на ноутбуке с тачскрином тянущий жест уезжал бы в прокрутку */
  touch-action: none;
}
/* Зона захвата: невидимая, шире самой черты — иначе в двухточечную
   полоску пришлось бы целиться. */
.ed-vsplitter::after,
.ed-lsplitter::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -5px;
  right: -5px;
}
/* Черты не видно вовсе, пока к ней не подвели курсор.

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

   Теперь разделитель невидим, а черта проявляется под курсором и пока
   тянут — как в Final Cut. Тянуть при этом не сложнее: невидимая зона
   захвата (::after) шире черты впятеро, и курсор над ней меняется. */
.ed-vsplitter::before,
.ed-lsplitter::before {
  content: '';
  height: 100%;
  width: 2px;
  border-radius: 2px;
  background: transparent;
  transition: background 0.15s;
}
.ed-vsplitter:hover::before, .ed-vsplitter.тянут::before,
.ed-lsplitter:hover::before, .ed-lsplitter.тянут::before { background: var(--sel-ring); }
.ed-vsplitter:focus-visible, .ed-lsplitter:focus-visible { outline: none; }
.ed-vsplitter:focus-visible::before,
.ed-lsplitter:focus-visible::before { background: var(--sel-ring); width: 5px; }
/* ---------- Разделитель окон и дорожки ----------
   Тянется мышью, как в Logic Pro и Final Cut: сколько высоты у верхних
   окон, столько и решает человек, а не наши числа в коде. Положение
   помнится между сеансами (см. КЛЮЧ_ДЕЛЕНИЯ в app.js).
   Полоска низкая, а брать её удобно: сам ряд 8 px, а видимая черта
   внутри — 3 px посередине. */
.ed-splitter {
  position: relative;
  flex: none;
  height: 2px;
  /* Съедаем зазор шага по обе стороны: окна и дорожка стоят вплотную
     к разделителю, как в Final Cut. Восемнадцать точек пустоты между
     ними были не «воздухом», а потерянной высотой дорожки. */
  margin-block: calc(-1 * var(--step-gap, 0px));
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: row-resize;
  /* Отменяем прокрутку страницы пальцем на самом разделителе: иначе
     на ноутбуке с тачскрином тянущий жест уезжал бы в прокрутку */
  touch-action: none;
}
/* Зона захвата шире черты — та же мера, что у боковых разделителей */
.ed-splitter::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -5px;
  bottom: -5px;
}
.ed-splitter::before {
  content: '';
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: transparent;
  transition: background 0.15s;
}
.ed-splitter:hover::before, .ed-splitter.тянут::before { background: var(--sel-ring); }
.ed-splitter:focus-visible { outline: none; }
.ed-splitter:focus-visible::before { background: var(--sel-ring); height: 5px; }
.edit-list {
  list-style: none;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}
/* Строка сетки: только чтение и правка текста. Кнопок подстройки здесь
   нет — они живут в панели выбранной строки и на дорожке. */
.edit-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  height: var(--ed-row);
  padding: 0 0.5rem;
  /* Волосяной разделитель, не рамка: на плотной сетке из 24-пиксельных
     строк полноценная граница каждой строки рябит сильнее, чем помогает */
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  font-size: var(--ed-font);
}
.edit-row:last-child { border-bottom: none; }
.edit-row.current-row { background: rgb(var(--accent-rgb) / 0.1); }
/* Выбранная строка — та, которой слушаются клавиши, панель и дорожка.
   Рамка яркая и цветом совпадает с выделением на дорожке (тема
   решает, жёлтая она или зелёная — см. --sel-ring) */
.edit-row.selected-row {
  background: var(--sel-ring-bg);
  box-shadow: inset 2px 0 0 var(--sel-ring);
}
/* Строка попала в выделенный диапазон: его двигают и растягивают целиком.
   Помечена справа и сиреневым — тем же цветом, что полоса выделения на
   линейке дорожки, — чтобы не спорить с выбранной строкой: у той метка
   слева и цветом темы. Так обе пометки читаются на одной строке сразу. */
.edit-row.in-range {
  background: rgba(167, 139, 250, 0.12);
  box-shadow: inset -3px 0 0 rgba(167, 139, 250, 0.85);
}
.edit-row.in-range.selected-row {
  background: var(--sel-ring-bg);
  box-shadow: inset 2px 0 0 var(--sel-ring), inset -3px 0 0 rgba(167, 139, 250, 0.85);
}
.edit-row .ts, .edit-row .dur-ts, .edit-row .num {
  font-family: var(--ed-num);
  font-variant-numeric: tabular-nums;
}
/* Начало — «1:22,40», сотые доли: та же запись, что на линейке дорожки
   и в часах, только короче тысячных из полей инспектора. Столбца конца
   здесь больше нет: он выводится из начала и длины, а длину сравнивают
   глазами — конец же и так виден на дорожке и стоит в инспекторе.
   Освободившаяся ширина ушла тексту строки. */
.edit-row .ts { min-width: 3.7rem; flex-shrink: 0; }
.edit-row .dur-ts {
  min-width: 2.6rem;
  color: var(--text-dim);
  font-weight: 400;
  text-align: right;
}
.edit-row .num {
  min-width: 1.5rem;
  flex-shrink: 0;
  color: var(--text-dim);
  font-size: 0.9em;
  text-align: right;
}
/* Тихая пометка: слова этой строки размечены вручную.
   Когда размечено большинство строк, пометка переворачивается (см.
   renderEditList): помечаются, наоборот, строки БЕЗ ручной разметки —
   и тогда она приглушённая и с пунктирной обводкой, чтобы «нет» нельзя
   было спутать с «да». */
.word-mark { color: #92C270; flex-shrink: 0; display: inline-flex; }
.word-mark .icon { width: 0.85em; height: 0.85em; }
.word-mark-off {
  color: var(--text-dim);
  opacity: 0.75;
  border-bottom: 1px dashed currentColor;
}
.edit-text {
  flex: 1;
  min-width: 90px;
  outline: none;
  padding: 0.05rem 0.3rem;
  border-radius: var(--ed-radius-ctl);
  /* Цвет курсора здесь больше не задаётся: он приходит из общего
     правила для всех полей ввода (см. «Тёмная гамма и курсор ввода»
     в начале файла) и точно такой же — акцент темы. */
  /* Длинная строка обрезается многоточием, а не переносится: ряд
     держит высоту 24 px, и перенос печатал текст ПОВЕРХ соседних
     рядов — нечитаемыми становились обе строки разом. Целиком
     длинная строка видна в правке (ниже) и на сцене просмотра. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* В правке поле ведёт себя как поле: курсор ходит, длинный текст
   прокручивается вбок, многоточие не мешает видеть конец строки. */
.edit-text[contenteditable="true"] {
  overflow-x: auto;
  text-overflow: clip;
  cursor: text;
}
.edit-text:focus { background: rgba(255, 255, 255, 0.07); }

/* --- Инспектор выбранной строки ---
   Раньше это была широкая полоса под сеткой: кнопки в один ряд, подпись
   сбоку. В монтажных программах свойства выбранного лежат столбцом
   сбоку, и здесь так же — работа со строкой собрана в одном месте
   и не расползается по ширине экрана. */
.sel-panel.empty { opacity: 0.75; }
/* Начало, конец и длительность: подпись слева, значение справа —
   ряд читается слева направо, а не столбиком, как было при кнопках.
   Ряды невысокие: панель должна быть тихой, значения в ней смотрят
   чаще, чем правят. */
/* Ряд инспектора: подпись прижата к значению, значения выстроены
   в один столбец у правого края — как в инспекторах монтажных
   программ, где числа сверяют глазами сверху вниз.

   Ряды плотные нарочно: инспектор стоит рядом с выросшей дорожкой,
   и на ноутбуке нижние поля обрезались. Просветы тут дороже, чем
   кажется, а числа читаются и вплотную — им помогает не воздух,
   а выравнивание по правому краю. */
.insp-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.4rem;
}
.sel-field { display: flex; align-items: center; gap: 0.25rem; }
.sel-unit { color: var(--text-dim); font-size: 0.75rem; }
/* Поле со значением. Рамка почти не видна, пока в поле не вошли:
   инспектор не должен выглядеть анкетой. */
/* Ширина под "-12.345" — тысячные добавили знак, поле стало чуть шире */
.sel-num {
  width: 5.6rem;
  padding: 0.1rem 0.35rem;
  border: 1px solid transparent;
  border-radius: var(--ed-radius-ctl);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-family: var(--ed-num);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
  line-height: 1.35;
}
input.sel-num:hover:not(:disabled) { border-color: var(--border); }
input.sel-num:focus { outline: none; border-color: var(--sel-ring); background: var(--sel-ring-bg); }
input.sel-num:disabled { opacity: 0.4; cursor: not-allowed; }
/* Введено недопустимое — поле краснеет, но значение не пропадает:
   человек видит, что именно он набрал, и может поправить */
input.sel-num.bad { border-color: #f97316; background: rgba(249, 115, 22, 0.12); }
/* Длительность — показ, а не поле: менять её надо через границы */
.sel-num-ro { background: none; color: var(--text-dim); }
/* Стрелочки у числового поля: место занимают, а шаг у них всё равно
   не тот, что нужен строке. Подстройка живёт на дорожке и на стрелках
   клавиатуры. */
.sel-num::-webkit-outer-spin-button,
.sel-num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.sel-num { -moz-appearance: textfield; appearance: textfield; }
/* Кнопки панели встают в столбец, а на широкой панели — в два-три:
   шесть кнопок столбиком занимали 150 px высоты, и панель из-за них
   прокручивалась. Ряд подстраивается под ширину сам, поэтому чем шире
   человек растянул инспектор, тем меньше кнопки съедают высоты. */
.insp-actions {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
  gap: 0.15rem;
  margin-top: 0.1rem;
}
.insp-actions .nudge-btn {
  display: flex;
  align-items: center;
  text-align: left;
  padding: 0.12rem 0.5rem;
  min-width: 0;
}
/* Подпись длиннее кнопки не растягивает столбец: она переносится
   по словам, а кнопка растёт вниз — обрезать подпись нельзя, по ней
   и узнают, что кнопка делает. */
.insp-actions .nudge-btn > span { overflow-wrap: anywhere; }
/* Плотность панели выбранного. Ужимаем воздух, а не текст: на окне
   с умолчаниями (дорожка забирает свои 46%) панели достаётся 270 точек,
   и раздел «Строка» в них не влезал на тринадцать. */
.sel-panel .ed-pane-body { gap: 0.15rem; }
.sel-label { color: var(--text-dim); font-size: 0.85em; text-align: right; }
.sel-vocal {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--text-dim);
  font-size: 0.8rem;
  cursor: pointer;
  user-select: none;
}
.sel-vocal input { accent-color: var(--accent-solid); cursor: pointer; }
.sel-vocal:has(input:disabled) { opacity: 0.45; cursor: not-allowed; }
#btn-sel-tap { border-color: var(--sel-ring); color: var(--sel-ring); }
#btn-sel-words.marked { border-color: var(--sel-ring); color: var(--sel-ring); }

/* --- Панель выбранного слова ---
   Зеркало панели строки чуть выше: тот же .insp-row, то же .sel-num,
   просто с отдельным маленьким заголовком-разделителем, чтобы было
   видно, где кончается строка и начинается слово. */
/* Черта между столбцом слова и столбцом строки. Подписи у неё нет
   нарочно: заголовок окна и так говорит, что сейчас правят, а у обоих
   столбцов первым рядом стоит своё имя — «слово» и «строка». */
.sel-line {
  margin-top: 0.15rem;
  padding-top: 0.4rem;
  border-top: 1px solid var(--border);
}

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

   Класс `empty` ставит updateWordInfo — тот самый, которым раздел
   и так приглушался; читаем его через :has, чтобы не заводить
   второго признака одного и того же. Заодно снимаем черту у соседа:
   без верхнего раздела она висела бы прямо под заголовком окна. */
.insp-sec:has(> .sel-word.empty) { display: none; }
/* --- Отрезков нет вовсе — раздела «Оригинал» тоже нет ---
   То же правило, что и у слова: пока показывать нечего, место
   не занимаем. Разница одна: отрезок может быть СДЕЛАН, но не выбран, —
   тогда раздел остаётся на месте («не выбран»), иначе выбрать отрезок
   щелчком по дорожке было бы негде. Класс ставит обновитьОтрезок. */
.insp-sec:has(> .sel-line.нет-отрезков) { display: none; }
.insp-sec:has(> .sel-word.empty) + .insp-sec {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* --- Сворачиваемые разделы инспектора ---
   Как в инспекторе Final Cut: «Слово» и «Строка» открываются и
   закрываются треугольником. Беда, которую это лечит: столбец в 210 px
   на ноутбуке не помещался в окно целиком и прокручивался — а числа
   в нём сверяют глазами, прокрутка тут дороже, чем кажется. Что
   свёрнуто, помнится между сеансами (ИНСПЕКТОР в app.js).

   Черта над «Строкой» теперь рисуется у самого раздела, а не у .sel-line:
   иначе у свёрнутого раздела она уезжала бы под заголовок. */
.insp-sec + .insp-sec { margin-top: 0.05rem; padding-top: 0.2rem; border-top: 1px solid var(--border); }
.insp-sec + .insp-sec > .sel-line { margin-top: 0; padding-top: 0.25rem; border-top: none; }
.insp-sec > summary {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  list-style: none;
  cursor: pointer;
  user-select: none;
  color: var(--text-dim);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.insp-sec > summary::-webkit-details-marker { display: none; }
.insp-sec > summary::before {
  content: '';
  width: 0;
  height: 0;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent;
  transition: transform 0.12s ease;
}
.insp-sec[open] > summary::before { transform: rotate(90deg); }
.insp-sec > summary:hover { color: var(--text); }
/* .nudge-btn.on уже красит включённое кольцо строки акцентом темы —
   у слова тот же класс, та же покраска, ничего своего добавлять не надо */

/* --- Режим простукивания ---
   Забирает экран себе: сетка строк, предпросмотр и панель выбранной
   строки уходят с глаз, дорожка и метки на ней остаются. */
.step-panel.tapping > .panel-hint,
.step-panel.tapping > .editor-grid,
.step-panel.tapping > .ed-splitter,
.step-panel.tapping > .ed-bar,
.step-panel.tapping > .timeline-legend,
.step-panel.tapping > .panel-actions { display: none; }

.tap-mode { display: flex; flex-direction: column; gap: 0.7rem; margin-bottom: 1rem; }
.tap-hit {
  display: block;
  width: 100%;
  padding: 1.8rem 1.2rem;
  background: var(--grad-btn);
  background-origin: border-box;
  border: none;
  border-radius: var(--radius);
  color: var(--accent-ink, #fff);
  font-family: inherit;
  text-align: center;
  cursor: pointer;
  transition: transform 0.08s;
}
.tap-hit:active { transform: scale(0.99); }
.tap-now {
  display: block;
  font-size: clamp(1.3rem, 3.4vw, 2rem);
  font-weight: 700;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.tap-next {
  display: block;
  margin-top: 0.5rem;
  font-size: clamp(0.9rem, 2vw, 1.1rem);
  opacity: 0.8;
  overflow-wrap: anywhere;
}
.tap-key { display: block; margin-top: 0.9rem; font-size: 0.8rem; opacity: 0.75; }
.tap-key kbd {
  background: rgba(0, 0, 0, 0.25);
  border-radius: 5px;
  padding: 0.05rem 0.35rem;
  font-family: inherit;
}
.tap-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
  color: var(--text-dim);
  font-size: 0.9rem;
}
.tap-count { font-weight: 600; color: var(--accent-solid); }
.tap-actions { display: flex; gap: 0.8rem; justify-content: space-between; flex-wrap: wrap; }

.word-tap {
  border: 1px solid var(--accent-solid);
  border-radius: var(--radius);
  background: var(--bg-card);
  padding: 0.9rem 1.1rem;
  margin-bottom: 0.8rem;
}
.word-tap-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}
.word-tap-hint { color: var(--text-dim); font-size: 0.85rem; }
.word-tap-hint kbd {
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 0 0.35rem;
  font-family: inherit;
}
.word-tap-line {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  font-size: 1.15rem;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  min-height: 1.8em;
  transition: opacity 0.2s;
}
/* Пока играет вступление, отмечать ещё нечего */
.word-tap.waiting .word-tap-line { opacity: 0.45; }
.wt-word {
  color: var(--text-dim);
  padding: 0.05rem 0.3rem;
  border-radius: 6px;
  transition: color 0.15s, background 0.15s;
}
.wt-word.done { color: var(--accent-solid); }
.wt-word.next {
  color: #0b0b10;
  background: var(--accent-solid);
}
.word-tap-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.7rem;
}
.word-tap-count { color: var(--text-dim); font-size: 0.85rem; margin-right: auto; }

/* Просмотр вписывается в остаток по обеим сторонам — как окно
   предпросмотра в караоке (см. .stage-wrap) */
.edit-stage-wrap {
  flex: 1;
  min-height: 0;
  container-type: size;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 0.5rem;
}
.edit-stage {
  width: min(100%, calc(100cqh * 16 / 9));
  max-width: 100%;
  /* Пропорции кадра: предпросмотр — уменьшенная копия того, что
     запишется в видео, поэтому и форма у него та же. Заодно не остаётся
     пустой высоты, из-за которой текст казался потерянным. */
  aspect-ratio: 16 / 9;
  /* Точка отсчёта для строк на закреплённых местах */
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.5rem;
  text-align: center;
  background: radial-gradient(ellipse at 50% 0%, rgba(53, 156, 110, 0.1), transparent 70%), var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.2rem;
  overflow: hidden;
}
/* Предпросмотр показывает ровно то, что получится: перенос строк
   решается общим правилом (класс wrap от единого кегля), а не особым
   для превью — иначе здесь текст переносился бы, а на сцене нет. */
.edit-stage .stage-line {
  background: none;
  color: var(--st-inactive);
}
.edit-stage .stage-line.current { color: var(--st-active); }
.edit-stage .stage-line.current span.sung { color: var(--st-effect); }
.edit-stage .stage-line.near { color: var(--st-active); }
/* ---- Одна высота на всю панель редактора ----
   Ряд над дорожкой был лесенкой: пуск 32 пикселя, часы 29,5, кнопки
   со значком 18,8, поля темпа 23, метка уверенности 21. Человек
   показал на часы — «сейчас окно с временем корректной высоты» — и
   попросил свести к ним всё. Их 30 и оказались ровно --top-h: та же
   мера, что у верхнего ряда студии (кнопки черновика, вкладки шагов).
   Значит, высота у нажимаемого в студии теперь ОДНА, сверху донизу;
   ширина у каждого своя. Подписи (BPM, «с», косая в часах) высоты не
   получают: это текст между кнопками, а не кнопки. */
.play-btn.small { width: 48px; height: var(--top-h); font-size: 0.9rem; }

/* --- Панель управления редактора ---
   Одна полоса на весь редактор, как в монтажной программе: транспорт
   с часами слева, инструменты правки в середине, масштаб дорожки
   справа. Раньше то же самое было раскидано на две полосы — одна
   под предпросмотром, другая над дорожкой. */
/* Панель управления стоит НАД ДОРОЖКОЙ и той же ширины: всё, что
   на ней есть, — про дорожку, и от неё её больше ничего не отделяет.
   Снизу рамки нет вовсе — дальше сразу дорожка, и две черты подряд
   между ними были бы лишними. */
.ed-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem 0.7rem;
  flex-shrink: 0;
  margin-inline: calc(-1 * var(--studio-pad-x, 1.5rem));
  margin-bottom: calc(-1 * var(--step-gap, 0px));
  padding: var(--ed-pad) 0.7rem;
  border: 1px solid var(--border);
  border-left: none;
  border-right: none;
  border-bottom: none;
  border-radius: 0;
  background: var(--bg-card);
  color: var(--text-dim);
  font-size: var(--ed-font);
}
.ed-group { display: flex; align-items: center; gap: 0.4rem; }
.ed-group .nudge-btn { white-space: nowrap; }
/* Высота — только здесь, в панели над дорожкой. Кнопка с тем же именем
   стоит ещё в инспекторе и в настройках караоке: там ряды тесные,
   и от 30 пикселей инспектор просился бы прокручиваться (самопроверка
   «панельВыбранного» это и поймала). Кнопки со значком заодно
   становятся квадратными — 30 на 30. */
.ed-bar .nudge-btn { height: var(--top-h); }
.ed-bar .nudge-btn.icon-btn { width: var(--top-h); }
.ed-right { margin-left: auto; }
/* Часы — как табло в монтажной: текущее время крупно, длина песни рядом.
   Цифры счётчика — одно из четырёх мест фирменного зелёного
   (--accent-clock); в нейтральной теме это просто цвет текста. */
.ed-clock {
  display: flex;
  /* Ровно по середине, а не по общей базовой линии: у часов задана
     высота, и при baseline цифры прижались бы к верхнему краю. */
  align-items: center;
  line-height: 1;
  height: var(--top-h);
  gap: 0.35rem;
  padding: 0 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--ed-radius-ctl);
  background: var(--bg-soft);
  font-family: var(--ed-num);
  font-variant-numeric: tabular-nums;
}
.ed-clock b { color: var(--accent-clock); font-size: 0.95rem; font-weight: 600; }
/* Косая между текущим временем и длиной песни: разделитель, а не число,
   поэтому и тише обоих */
.ed-clock-sep { opacity: 0.45; }

/* --- Сетка долей в панели редактора ---
   Включатель, найденный темп и уверенность стоят одной группой: решение
   «включать или нет» принимают, глядя ровно на эти числа. Поля узкие
   нарочно — панель обязана остаться в одну строку, иначе редактор
   вырастет и четыре шага перестанут помещаться в экран. */
.ed-beat { gap: 0.25rem; }
.beat-num {
  width: 3.2rem;
  height: var(--top-h);
  padding: 0.15rem 0.3rem;
  border: 1px solid transparent;
  border-radius: var(--ed-radius-ctl);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-family: var(--ed-num);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
  line-height: 1.3;
}
input.beat-num:hover { border-color: var(--border); }
input.beat-num:focus { outline: none; border-color: var(--sel-ring); background: var(--sel-ring-bg); }
/* Набрано негодное число — поле краснеет, а набранное остаётся на месте */
input.beat-num.bad { border-color: #f97316; background: rgba(249, 115, 22, 0.12); }
.beat-unit { color: var(--text-dim); font-size: 0.7rem; }
/* Уверенность. Цвет — сам по себе ответ на вопрос «можно ли верить»:
   зелёное включают не глядя, красное лучше проверить или поправить. */
.beat-conf {
  min-width: 2.6rem;
  height: var(--top-h);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.1rem 0.3rem;
  border-radius: var(--ed-radius-ctl);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text-dim);
  font-family: var(--ed-num);
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.beat-conf[data-уровень="высокая"] { color: #AED98F; }
.beat-conf[data-уровень="средняя"] { color: #facc15; }
.beat-conf[data-уровень="низкая"] { color: #fb7185; }
.beat-conf[data-уровень="руками"] { color: var(--sel-ring); }

/* Дорожка стоит своим числом пикселей, а окна над ней забирают
   остаток: высоту --tl-h ставит код деления (см. применитьДеление
   в app.js), её же тянет разделитель. Запасное значение — прежняя
   высота дорожки: если код почему-то не отработал, редактор
   выглядит как до правки, а не разъезжается. */
/* Дорожка идёт от края до края рабочего места — как в монтажной
   программе, где линейка времени всегда во всю ширину окна. Боковое
   поле студии она отыгрывает обратно отрицательным полем, поэтому
   и рамка по бокам ей не нужна: там край окна. Полсотни пикселей,
   которые от этого достаются дорожке, — это полсекунды песни
   на обычном приближении. */
.timeline-wrap {
  flex: none;
  height: var(--tl-h, 140px);
  display: flex;
  margin-inline: calc(-1 * var(--studio-pad-x, 1.5rem));
  border: 1px solid var(--border);
  border-left: none;
  border-right: none;
  border-radius: 0;
  background: var(--bg-card);
  overflow: hidden;
}
/* Значение ряда инспектора: номер строки, само слово. Тот же цвет,
   что и рамка выделения, — это часть одной и той же обратной связи.
   Ширина ограничена шириной поля рядом: длинное слово не должно
   растягивать столбец. */
/* Границы отрезка — две метки времени в одной строке, и общей меры
   значений (5,6 rem) им мало: подпись обрывалась на втором времени,
   то есть ровно на том, ради чего её читают. Своя мера тут не годится
   тоже: на Windows тот же текст шире, и любое число оказывалось мало
   на одной из систем. Поэтому строка отдаёт этому значению ВСЁ, что
   осталось от подписи слева, и кегль ему на четверть меньше. */
.insp-row:has(> #orig-sel) { grid-template-columns: auto minmax(0, 1fr); }
#orig-sel { max-width: none; font-size: 0.76rem; letter-spacing: -0.01em; }
.sel-value {
  display: block;
  max-width: 5.6rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  color: var(--sel-ring);
  font-size: 0.82rem;
  font-family: var(--ed-num);
  font-variant-numeric: tabular-nums;
  line-height: 1.35;
}
/* Строка с выдуманным временем («≈ на глазок») — тем же цветом, что
   и её пунктирный блок на дорожке */
.sel-value.guess { color: #f59e0b; }
/* Столбец слова и столбец строки в одном окне: между ними черта,
   чтобы было видно, где кончается одно и начинается другое */
.sel-line, .sel-word {
  display: flex;
  flex-direction: column;
  gap: 0.22rem;
}
/* Включённый переключатель дорожки (по кругу, притягивание к голосу) —
   второе из четырёх мест акцента темы */
.nudge-btn.on {
  border-color: var(--accent-on-border);
  color: var(--accent-on-text);
  background: var(--accent-on-bg);
}
/* ---------- Колонка заголовков дорожек ----------
   Слева от полос, вне канваса: подписи и место под будущую кнопку
   «слушать только это» (см. дорожка.заголовок.* в i18n.js). Высота
   каждой строки колонки подгоняется под высоту её полосы кодом
   (см. renderTimelineHeads в app.js) — иначе при появлении полосы
   голоса (только в приложении, после удаления вокала) колонка
   и канвас разъедутся по высоте. */
.tl-heads {
  flex: none;
  /* 160 px, а было 132: сначала рядом с ползунком уровня встало число
     процентов (как «0.0 dB» у Volume в Final Cut) — без восемнадцати
     пикселей оно выдавливало подпись полосы; потом подросла сама ручка
     ползунка, и ей понадобился ещё десяток. У канваса дорожки они
     не отнимают ничего заметного. */
  width: 160px;
  border-right: 1px solid var(--border);
  /* Цвет из темы: в тёмных темах колонка чуть светлее панели, в
     стальной — темнее рабочего поля дорожки, как в Logic Pro */
  background: var(--tl-heads-bg);
  overflow: hidden;
}
.tl-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  padding: 0 6px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  color: var(--text-dim);
  font-size: 10px;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
}
/* Подпись полосы: сжимается первой, длинное «instrumental» обрезается
   многоточием — ползунок и кнопку соло отодвигать некуда */
.tl-head-row > span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Ползунок уровня полосы в наушниках — как фейдер в монтажной
   программе: желобок с внутренней тенью (будто прорезан в панели)
   и крупная круглая ручка со скосом и собственной тенью. Прежние
   волосяная черта в 3 px и точка в 7 px читались как царапина:
   их не было видно, и брать их приходилось прицельно.

   Ручка 14 px в поле ввода высотой 16 px — она обязана помещаться
   в ряд заголовка полосы, а он бывает и 18 px (полоса оригинала на
   самой низкой дорожке). Скос сделан не картинкой, а наложением:
   светлый верх и тёмный низ поверх цвета акцента, — поэтому ручка
   красится темой сама собой.

   Селектор нарочно с тегом и атрибутом: «.tl-gain» весит (0,1,0), а
   общее правило ползунков «input[type="range"]» — (0,1,1), потому что
   квадратные скобки считаются наравне с классом. Общее было сильнее, и
   ВЕСЬ этот блок однажды не применялся вовсе. Проверять это надо
   по вычисленным стилям живой страницы, а не по глазу: раздел
   самопроверки «ползунокУровня» меряет настоящий размер ручки. */
/* Число процентов рядом с ползунком: по положению кружка уровень
   не прочитать, а сверять его глазами приходится постоянно. Моноширинное
   и с табличными цифрами — иначе «100 %» и «45 %» дёргали бы ряд. */
.tl-gain-val {
  flex: none;
  min-width: 2.5em;
  color: var(--text-dim);
  font-family: var(--ed-num);
  font-variant-numeric: tabular-nums;
  font-size: 9px;
  text-align: right;
}
.tl-gain-val.off { opacity: 0.35; }
input[type="range"].tl-gain {
  flex: 1;
  min-width: 30px;
  max-width: 54px;
  height: 16px;
  margin: 0;
  padding: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  cursor: pointer;
}
/* Желобок: тёмный, с внутренней тенью сверху и светлой каймой снизу —
   он должен выглядеть прорезанным в панели, а не нарисованным на ней */
input[type="range"].tl-gain::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.16));
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.65), 0 1px 0 rgba(255, 255, 255, 0.06);
}
input[type="range"].tl-gain::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.16));
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.65), 0 1px 0 rgba(255, 255, 255, 0.06);
}
/* Ручка. Цвет — акцент темы, скос — наложение поверх него: светлая
   верхушка, тень внизу, тёмная кайма по кругу и своя тень на желобке.
   margin-top = (высота желобка − высота ручки) / 2, иначе WebKit
   поставит её от верха желобка и она уедет вниз. */
input[type="range"].tl-gain::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -4px;
  border: 1px solid rgba(0, 0, 0, 0.55);
  border-radius: 50%;
  background-color: var(--sel-ring);
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.55),
    rgba(255, 255, 255, 0) 52%, rgba(0, 0, 0, 0.3));
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
input[type="range"].tl-gain::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: 1px solid rgba(0, 0, 0, 0.55);
  border-radius: 50%;
  background-color: var(--sel-ring);
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.55),
    rgba(255, 255, 255, 0) 52%, rgba(0, 0, 0, 0.3));
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
input[type="range"].tl-gain:disabled { opacity: 0.25; cursor: not-allowed; }
input[type="range"].tl-gain:focus-visible { outline: 1px solid var(--sel-ring); outline-offset: 1px; }

/* «Слушать только это»: буква S, как в монтажных программах. Включённая
   горит акцентом темы — тем же, которым обведён выбранный блок. */
.tl-solo {
  flex: none;
  width: 15px;
  height: 13px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: transparent;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.tl-solo:hover:not(:disabled) { color: var(--text); border-color: var(--violet); }
.tl-solo.on {
  color: var(--sel-ring);
  border-color: var(--sel-ring);
  background: var(--sel-ring-bg);
}
.tl-solo:disabled { opacity: 0.3; cursor: not-allowed; }
/* Поля только по бокам. Сверху и снизу их нет нарочно: колонка
   заголовков полос начинается вплотную к рамке, а канвас начинался
   восемью пикселями ниже — подписи полос стояли не против своих полос,
   и это было видно на «минусовке». Заодно эти шестнадцать пикселей
   вернулись самим полосам. */
.tl-canvas-wrap { position: relative; flex: 1; min-width: 0; padding: 0 0.6rem; }
/* Подпись с точным временем у курсора при перетаскивании границы —
   тысячные доли, как и в полях инспектора (см. applyDrag в app.js) */
.tl-tip {
  position: absolute;
  z-index: 6;
  top: 2px;
  transform: translateX(-50%);
  font-family: var(--ed-num);
  font-variant-numeric: tabular-nums;
  font-size: 10px;
  background: #0d0d10;
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 1px 6px;
  color: var(--text);
  white-space: nowrap;
  pointer-events: none;
}
#timeline {
  display: block;
  width: 100%;
  /* До первого замера (resizeTimeline) — высота дорожек: иначе канвас
     тянется по своим 300×160 и раздувает шаг вчетверо.
     16 линейка + 18 оригинал + 38 волна + 30 строки + 20 слова */
  height: 122px;
  border-radius: var(--ed-radius-ctl);
  cursor: pointer;
  touch-action: none;
}
/* Что каким цветом нарисовано на дорожке. Стоит прямо под дорожкой,
   а не в свёрнутой справке: цвета нужны, когда смотришь на дорожку.

   Сами шесть подписей спрятаны за значком «?»: читают их один раз,
   а строку под дорожкой они занимали всегда. Развёрнутая легенда
   встаёт в тот же ряд, что и кнопка, — ничего не прыгает. */
.timeline-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 1rem;
  margin-top: 0.35rem;
  padding: 0 0.2rem;
  flex-shrink: 0;
  color: var(--text-dim);
  font-size: 0.72rem;
  line-height: 1.4;
}
.tl-legend-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.05rem 0.35rem;
  border: 1px solid transparent;
  border-radius: var(--ed-radius-ctl);
  background: none;
  color: var(--text-dim);
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
}
.tl-legend-btn:hover { color: var(--text); border-color: var(--border); }
.tl-legend-btn[aria-expanded="true"] { color: var(--text); border-color: var(--border); }
.tl-legend-keys {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 1rem;
}
.tl-key { display: flex; align-items: center; gap: 0.35rem; }
.tl-key b { color: var(--text-dim); font-weight: 400; opacity: 0.75; }
.tl-key .sw {
  width: 14px;
  height: 10px;
  border-radius: 3px;
  display: inline-block;
}
.sw-orig { background: rgba(251, 113, 133, 0.4); border: 1px solid #fb7185; }
.sw-voice { background: rgba(56, 189, 248, 0.55); border: 1px solid #38bdf8; }
.sw-line { background: rgba(53, 156, 110, 0.35); border: 1px solid #359C6E; }
.sw-guess { background: rgba(245, 158, 11, 0.3); border: 1px dashed #f59e0b; }
.sw-word { background: rgba(146, 194, 112, 0.3); border: 1px solid #92C270; }
.sw-own { background: transparent; border-left: 2px dashed #38bdf8; border-radius: 0; width: 4px; }
.timeline-keys {
  margin-top: 0.35rem;
  color: var(--text-dim);
  font-size: 0.78rem;
  line-height: 1.7;
}
.timeline-keys kbd {
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 0 0.3rem;
  font-family: inherit;
  font-size: 0.95em;
}

/* --- Экспорт видео --- */
.export-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(5, 5, 10, 0.85);
  backdrop-filter: blur(6px);
}
.export-box {
  width: min(440px, 90vw);
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2rem;
  text-align: center;
}
.export-box p { margin-bottom: 1rem; }
/* Строка хода не должна дёргать окно. Беда была видна на глаз: текст
   «Собираем ролик… 87%, осталось примерно 0:07» то влезал в строку,
   то не влезал, — и всё окно вместе с полосой и кнопкой прыгало вверх-вниз
   несколько раз в секунду. Поэтому здесь две меры: место под ДВЕ строки
   отведено всегда (не влезло — окно не растёт, влезло — не сжимается),
   а цифры моноширинные, чтобы смена 8 на 0 не меняла ширину. */
#export-status {
  /* Две строки с запасом: при 3,1em длинный текст перерастал мерку
     на пиксель, и окно всё равно подрагивало. */
  min-height: 3.45em;
  display: grid;
  place-items: center;
  font-variant-numeric: tabular-nums;
}
.export-bar {
  height: 10px;
  border-radius: 5px;
  background: var(--border);
  overflow: hidden;
  margin-bottom: 1rem;
}
#export-fill {
  height: 100%;
  width: 0%;
  background: var(--grad-btn);
  transition: width 0.3s linear;
}
.export-hint { color: var(--text-dim); font-size: 0.85rem; }

/* --- Шаг 4: караоке --- */
.lyrics-stage {
  /* Настраиваемое оформление текста (панель «Оформление») */
  --st-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
  --st-weight: 600;
  --st-inactive: #9a9ab0;
  --st-active: #f2f2f7;
  --st-effect: #f97316;
  --st-outline-c: #000000;
  --st-outline: 0px;
  --st-ls: 0px;
  --st-gap: 0.35rem;   /* до первого замера; дальше px от fitStageLines */
  --st-dim: 0.45;
  --st-near: 0.75;
  --st-blur: 0px;
  --st-scrim: none;
  /* Подложка живёт в ::before, поэтому сцене нужна своя система координат */
  position: relative;
  /* Пропорции кадра: сцена — то же самое, что запишется в видео, поэтому
     и форма у неё та же. Раньше сцена была шире и ниже кадра, и места
     строк («первая на 40%, вторая на 60%») ложились на экране не так,
     как в готовом ролике. */
  aspect-ratio: 16 / 9;
  min-height: 240px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--st-gap);
  /* Поля по краям задаёт настройка «Поля по краям» (applyStyle) */
  padding: 2rem clamp(1.5rem, 8%, 4rem);
  background: radial-gradient(ellipse at 50% 0%, rgba(53, 156, 110, 0.12), transparent 70%), var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
/* Режим закреплённых мест: строки стоят там, где их поставили,
   и не съезжают вверх по мере пения */
.lyrics-stage[data-slots="on"] { position: relative; display: block; }
.lyrics-stage[data-slots="on"] .stage-line.slot,
.edit-stage[data-slots="on"] .stage-line.slot {
  /* Абсолютный элемент считает left/right от области ВМЕСТЕ с полями
     родителя, поэтому одних нулей мало: поля повторяем на самой строке,
     иначе её коробка шире полей сцены (и мерить по ней ширину нельзя —
     см. lineTextWidth в app.js) */
  position: absolute;
  left: 0;
  right: 0;
  padding-left: inherit;
  padding-right: inherit;
  transform: translateY(-50%);
  margin: 0;
}

/* Картинка фона — и в просмотре редактора: размечать текст на пустом
   грунте, а потом видеть другую картину на шаге «Караоке» — значит
   судить о читаемости не там, где правишь. */
.lyrics-stage.has-bg,
.edit-stage.has-bg {
  background-size: cover;
  background-position: center;
}
/* Читаемость поверх картинки: мягкая полоса ровно под строками,
   а не общее затемнение всего кадра — картинка остаётся видна */
.lyrics-stage.has-bg::before,
.edit-stage.has-bg::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--st-scrim);
  pointer-events: none;
  z-index: 0;
}
/* Строки — над подложкой. Отсчёт сюда не берём: у него своё
   абсолютное позиционирование, и position: relative его ломает */
.lyrics-stage > .stage-line,
.lyrics-stage > .stage-empty,
.edit-stage > .stage-line,
.edit-stage > .stage-empty { position: relative; z-index: 1; }
.lyrics-stage.has-bg .stage-line:not(.current) { text-shadow: 0 1px 8px rgba(0, 0, 0, 0.8); }

/* ---- Обводка: отдельный слой ПОД текстом ----

   Была она на самой строке, через -webkit-text-stroke с paint-order.
   У неактивных строк выходило верно — обводка снаружи буквы. А у той,
   которую поют, нет: её заливка рисуется не цветом текста, а ФОНОМ,
   обрезанным по буквам (background-clip: text — так и делается бегущая
   закраска). Фон лежит ниже текста, обводка — часть текста, значит,
   обводка оказывалась ПОВЕРХ заливки и съедала букву изнутри. Человек
   это и увидел: «неактивная строка — внешняя обводка, активная —
   внутренняя».

   Теперь обводка вынесена в ::before: тот же текст (он есть в data-text),
   без заливки, одним слоем ПОЗАДИ строки. Что бы ни рисовала сама
   строка — ровный цвет, бегущую заливку, подсветку, — обводка всегда
   под ней, то есть всегда наружная. Ровно так же её рисует и кадр
   видео: strokeIfNeeded обводит до заливки, шириной в два раза больше
   заданной, — поэтому и здесь удвоение. */
.stage-line::before {
  content: attr(data-text);
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  /* Пробелы внутри строки хранятся при словах (splitWords клеит пробел
     к слову), поэтому слой обязан их сохранять — иначе текст разъедется
     с настоящим на ширину схлопнутых пробелов. */
  white-space: pre-wrap;
  -webkit-text-stroke: calc(var(--st-outline) * 2) var(--st-outline-c);
  color: transparent;
  pointer-events: none;
  z-index: -1;
}
/* Без обводки слой не нужен вовсе: пустой ::before всё равно создаёт
   строку текста и мог бы влиять на высоту. */
.lyrics-stage:not(.has-outline) .stage-line::before,
.edit-stage:not(.has-outline) .stage-line::before { content: none; }
.stage-line {
  font-family: var(--st-font);
  /* Текст караоке всегда по середине — так же, как его рисует кадр
     видео (drawVideoFrame ставит каждый ряд в (W - ширина) / 2).
     Держим выравнивание на самой строке, а не на сцене: сцена караоке
     лежит внутри .karaoke-layout, а там текст выровнен влево ради
     панели настроек, и строки наследовали это выравнивание — короткие
     строки прижимались к левому краю сцены вместо середины. */
  text-align: center;
  /* --st-fs — единый кегль на всю песню в пикселях: его считает
     fitStageLines (app.js) от ширины самой сцены и выбранного размера.
     Он один для всех строк, поэтому размер не прыгает от строки к строке. */
  font-size: var(--st-fs, 1.15rem);
  /* Высота ряда — та же доля кегля, что в кадре видео: перенесённая
     строка занимает на экране столько же рядов и той же высоты */
  line-height: var(--st-lh, 1.3);
  font-weight: var(--st-weight);
  letter-spacing: var(--st-ls);
  /* Обводки на самой строке нет: она рисуется отдельным слоем под
     текстом, см. .stage-line::before ниже. */
  color: var(--st-inactive);
  /* Приглушение только прозрачностью и размытием: размер строк
     не меняется никогда, иначе текст «прыгает» на каждой смене */
  opacity: var(--st-dim, 0.45);
  filter: blur(var(--st-blur, 0px));
  transition: opacity 0.25s cubic-bezier(0.33, 0, 0.2, 1),
              color 0.25s cubic-bezier(0.33, 0, 0.2, 1),
              filter 0.25s cubic-bezier(0.33, 0, 0.2, 1);
  min-height: calc(var(--st-lh, 1.3) * 1em);
  /* Строка, не влезшая в ряд, переносится, но не обрезается. */
  white-space: nowrap;
  max-width: none;
  overflow: visible;
  text-overflow: clip;
}
/* Строка, которая не влезает даже на самом мелком едином кегле,
   переносится на несколько рядов — так делают в нынешних караоке:
   лучше перенести, чем ужать до нечитаемого. Словам нужен pre-wrap:
   при обычном pre браузеру негде разорвать строку между словами. */
/* Слово длиннее ряда рвём по буквам: переносить его между словами
   негде, и без этого строка из трёхсот знаков без пробелов уезжала
   за край сцены (у неё overflow-x: hidden) — на экране оставалась
   четверть. anywhere, а не break-all: пока в строке есть пробелы,
   перенос идёт по ним, как и раньше. Тот же приём стоит у подписей
   простукивания (.tap-now, .tap-next). */
.stage-line.wrap { white-space: normal; overflow-wrap: anywhere; }
.stage-line.wrap .w { white-space: pre-wrap; overflow-wrap: anywhere; }
.stage-line.current { opacity: 1; filter: none; }
/* Эффект «заливка» — классическое караоке. Идёт ПО СЛОВАМ: каждое
   слово — свой элемент со своей долей закраски, поэтому цвет
   переключается на границах слов, а не ползёт сквозь них с постоянной
   скоростью. */
[data-effect="fill"] .stage-line.current .w {
  background: linear-gradient(90deg, var(--st-effect) var(--wfill, 0%), var(--st-active) var(--wfill, 0%));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* Правила для .w.sung при «подсветке» здесь больше нет: строка целиком
   уже покрашена ниже тем же цветом, и слово-в-слово оно ничего не меняло,
   зато читалось как «подсветка идёт по словам» — и кадр видео был написан
   именно так, по словам. Расхождение вылезло у человека в готовом файле. */
[data-effect="none"] .stage-line.current .w { color: var(--st-active); }
.stage-line .w { white-space: pre; }
/* Эффект «подсветка» — вся строка цветом */
[data-effect="highlight"] .stage-line.current { color: var(--st-effect); }
/* Без эффекта — просто активный цвет */
[data-effect="none"] .stage-line.current { color: var(--st-active); }
.stage-line.near {
  opacity: var(--st-near, 0.75);
  color: var(--st-active);
  filter: blur(calc(var(--st-blur, 0px) * 0.5));
}

/* Появление строк: анимируется только та, что реально появилась.
   Остальные плавно меняют прозрачность своим переходом. */
[data-anim="fade"] .stage-line.enter { animation: lineFade 0.28s cubic-bezier(0.22, 0.61, 0.36, 1); }
[data-anim="slide"] .stage-line.enter { animation: lineSlide 0.28s cubic-bezier(0.22, 0.61, 0.36, 1); }
@keyframes lineFade { from { opacity: 0; } }
@keyframes lineSlide { from { opacity: 0; transform: translateY(16px); } }
.stage-empty { color: var(--text-dim); font-size: 1.1rem; text-align: center; }

/* --- Отсчёт перед вступлением строки --- */
.stage-count {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  z-index: 2;
  transform: translateY(-50%);
  display: flex;
  justify-content: center;
  /* Точки — того же кегля, что строки: и размер, и просвет между ними
     считаются от --st-fs, поэтому отсчёт растёт вместе с текстом */
  font-size: var(--st-fs, 1.15rem);
  gap: 0.7em;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s cubic-bezier(0.33, 0, 0.2, 1);
}
.lyrics-stage.counting .stage-count { opacity: 1; }
/* Пока идёт отсчёт, ноты проигрыша уступают место точкам */
.lyrics-stage.counting .break-line { opacity: 0; }
.stage-count i {
  width: 0.62em;
  height: 0.62em;
  border-radius: 50%;
  background: var(--st-effect);
  box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.55);
  /* Точка слегка сжимается внутри своей секунды */
  transform: scale(calc(0.55 + 0.45 * var(--cd-p, 1)));
  transition: opacity 0.2s linear, transform 0.12s linear;
}
.stage-count i.off { opacity: 0.18; transform: scale(0.55); background: var(--st-inactive); }
/* Точек в разметке заведено по максимуму (в начале песни ряд длиннее,
   внутри короче) — лишние убираем совсем, а не гасим: погашенная точка
   у нас значит «эта секунда прошла», и путать эти два вида нельзя. */
.stage-count i.gone { display: none; }

/* Плеер и громкость вокала — одной строкой: лишний ряд отнимал бы
   высоту у сцены, а сцена здесь главное */
.transport { display: flex; align-items: center; gap: 1.2rem; flex-wrap: wrap; }
.player-bar { display: flex; align-items: center; gap: 0.8rem; flex: 1; min-width: 260px; }
/* Пуск и пауза — не круг, а прямоугольник со скруглением: он в одном
   ряду с полосой времени и кнопками, и круг среди них выбивался
   (см. «Скругления: одна лестница на всё»). Шире, чем выше, — как
   транспорт в монтажной программе. */
.play-btn {
  width: 64px; height: 44px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border: none;
  border-radius: var(--r-ctl);
  background: var(--accent-btn-bg, var(--grad-btn));
  box-shadow: var(--accent-btn-glow, none);
  color: #fff;
  font-size: 1.2rem;
  cursor: pointer;
  transition: transform 0.15s;
}
.play-btn:hover { transform: scale(1.07); }
/* Значок игры/паузы: оба лежат в разметке, виден только нужный —
   так переключение не трогает DOM (см. setPlayIcon в app.js) */
.play-btn .icon { width: 40%; height: 40%; }
.play-btn .i-pause { display: none; }
.play-btn.playing .i-play { display: none; }
.play-btn.playing .i-pause { display: block; }
.time { font-variant-numeric: tabular-nums; font-family: var(--ed-num); color: var(--text-dim); font-size: 0.9rem; min-width: 2.8rem; }

input[type="range"] {
  flex: 1;
  appearance: none;
  -webkit-appearance: none;
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--pink);
  box-shadow: 0 0 10px rgb(var(--accent-rgb) / 0.6);
}
input[type="range"]::-moz-range-thumb {
  width: 16px; height: 16px;
  border: none;
  border-radius: 50%;
  background: var(--pink);
}

.karaoke-main .mixer { width: 250px; flex: 0 0 auto; margin: 0; }
.mixer {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  max-width: 420px;
  margin: 0 auto 1.5rem;
  color: var(--text-dim);
  font-size: 0.95rem;
}
.mixer-icon { font-size: 1.2rem; flex: 0 0 auto; }
.mixer label { flex: 0 0 auto; }
/* Ползунок в полосе должен уметь ужиматься: у полей ввода собственная
   ширина около 129 px, и с min-width: auto флекс её не отнимал — ряд
   не влезал в свои 250 px, а значение процентов выезжало за правый край. */
.mixer input[type="range"] { flex: 1 1 auto; min-width: 0; }
/* Место под число отведено заранее и не зависит от того, «0%» там
   или «100%»: ползунок не дёргается, пока его тянут. */
.mixer-value {
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
  width: 3rem;
  text-align: right;
}

.export-row {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
  flex-shrink: 0;
  margin-top: 0.8rem;
}
/* Кнопки вывода — компактнее обычных: их семь, и в один ряд они
   влезают только так */
.export-row .btn { padding: 0.5rem 0.9rem; font-size: 0.88rem; }

/* Строка про права на песню — под кнопками вывода, тихая: мельче
   и бледнее всего вокруг. Стоит ровно в одном месте студии, там, где
   человек забирает готовый файл. */
.export-rights {
  max-width: 46rem;
  margin: 0.6rem auto 0;
  color: var(--text-dim);
  font-size: 0.78rem;
  line-height: 1.45;
  text-align: center;
}

/* Выбор качества стоит в ряду с кнопками экспорта и выглядит так же.
   Оформление берётся у самой кнопки — классы btn btn-ghost на метке,
   здесь только раскладка и сброс системного вида списка. Дублировать
   рамку и отступы нельзя: они разъезжаются на пару пикселей. */
.export-quality {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding-right: 1.2rem;
}
.export-quality:focus-within { border-color: var(--violet); color: var(--text); }
.export-quality select {
  /* Убираем системный вид — иначе среди кнопок торчит серый прямоугольник */
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: none;
  outline: none;
  padding: 0 1.1rem 0 0;
  color: inherit;
  font-family: inherit;
  /* Ровно как у текста метки, иначе список тянет строку вверх */
  font-size: inherit;
  font-weight: 600;
  line-height: inherit;
  cursor: pointer;
}
/* Своя стрелка вместо системной */
.export-quality select {
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 8px center, right 3px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
/* Выпадающий список рисует система — задаём ему тёмные цвета */
.export-quality option { background: var(--bg-card); color: var(--text); }

/* --- Эквалайзер и настройки в левой колонке --- */
/* Ряд эквалайзера: подпись, ползунок, число. В узкой колонке настроек
   ряд не влезал целиком, и «0 дБ» уезжало за правый край — колонка
   получала горизонтальную прокрутку, которой в панели настроек быть
   не должно. Ужимается здесь ползунок: подпись и число не режутся. */
.eq-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
  font-size: 0.9rem;
}
.eq-row input[type="range"] { flex: 1 1 auto; min-width: 0; }
.eq-row .eq-val { flex: none; }
.eq-label { min-width: 100px; flex: 0 1 auto; }
.eq-label small { display: block; color: var(--text-dim); font-size: 0.75rem; }
.eq-val {
  min-width: 3.4rem;
  text-align: right;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
}
.eq-actions {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin-top: 0.4rem;
}
.eq-hint { color: var(--text-dim); font-size: 0.78rem; }

/* --- Тональность (закладка «Звук») ---
   Число здесь крупное нарочно: это единственная настройка на закладке,
   у которой значение читают ИЗДАЛЕКА, стоя у микрофона. Стрелки по
   бокам, «Применить» появляется только тогда, когда выбранное ещё
   не звучит. */
.key-block {
  margin-bottom: 0.9rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--border);
}
.key-row {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  font-size: 0.9rem;
}
.key-pick {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}
.key-pick .nudge-btn { font-size: 1.05rem; line-height: 1; }
.key-val {
  min-width: 1.8rem;
  text-align: center;
  font-size: 1.3rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
/* Выбрано, но ещё не посчитано: число приглушено, пока не «Применить» */
.key-val.key-pending { color: var(--text-dim); opacity: 0.75; }
/* Нота прямо у переключателя: «+2 (Bm)». Мельче числа — полутона тут
   главные, а нота отвечает на вопрос «а это что за тональность». */
.key-val-note {
  margin-left: 0.15rem;
  color: var(--text-dim);
  font-size: 0.85rem;
  font-weight: 600;
  white-space: nowrap;
}
.key-val-note::before { content: '('; }
.key-val-note::after { content: ')'; }
/* «Применить» — кнопка обычного роста, как соседняя «В ноль»: прежняя
   заливка во всю ширину кричала громче, чем стоит одна настройка
   из десятка. Цветом остаётся акцент: действие всё-таки главное. */
.key-apply {
  border-color: var(--accent-solid);
  color: var(--accent-solid);
  font-weight: 600;
}
.key-apply:hover:not(:disabled) { background: rgb(var(--accent-rgb) / 0.14); }
#key-note { margin: 0.5rem 0 0.4rem; }
#key-fill {
  height: 100%;
  width: 0%;
  background: var(--grad-btn);
  transition: width 0.3s linear;
}
/* Тональность в чипе памяти: тот же размер, что имя песни, но
   не обрезается — это два-три знака */
.proj-key {
  flex: none;
  padding: 0 0.35rem;
  border-radius: var(--r-mini);
  background: var(--bg-soft);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* --- Панель «Оформление»: слева настройки, справа предпросмотр ---
   Раскладка как в монтажной программе. Раньше настройки лежали длинным
   списком под сценой: сцена уезжала за край экрана, и человек, двигая
   ползунок, не видел, что меняется. */
/* Панель настроек стоит СПРАВА — там же, где инспектор редактора.
   Раньше на «Караоке» она была слева, и человек, перейдя с прошлого
   шага, искал параметры не на той стороне: в монтажных программах
   инспектор всегда с одной стороны, и это не украшение, а привычка
   руки. В разметке она идёт первой (порядок чтения: сначала чем
   управляем, потом что видим), а место ей назначает сетка. */
.karaoke-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(240px, 300px);
  gap: 1rem;
  flex: 1;
  min-height: 0;
  text-align: left;
}
.karaoke-layout > .karaoke-side { grid-column: 2; grid-row: 1; }
.karaoke-layout > .stage-wrap { grid-column: 1; grid-row: 1; }
.karaoke-side {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.side-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.8rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.95rem;
}
.side-tabs {
  display: flex;
  gap: 0.25rem;
  padding: 0.35rem 0.6rem 0;
}
.side-tabs button {
  flex: 1;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-ctl);
  color: var(--text-dim);
  font-family: inherit;
  font-size: 0.85rem;
  padding: 0.35rem 0.2rem;
  cursor: pointer;
  transition: all 0.15s;
}
.side-tabs button:hover { color: var(--text); }
.side-tabs button.active {
  background: rgb(var(--accent-rgb) / 0.16);
  border-color: var(--violet);
  color: var(--text);
}
/* Настройки прокручиваются внутри своей колонки, а не всей страницей */
.side-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.65rem 0.8rem;
}

/* Плотность настроек караоке.

   Беда, которую это лечит: в закладке «Цвет» четырнадцать рядов,
   и на ноутбучном экране они не влезали в колонку — приходилось
   прокручивать, чтобы добраться до последних. Прокрутка внутри
   узкой колонки настроек хуже тесноты: человек не знает, что там
   ниже что-то есть.

   Ужимаем не текст, а воздух между рядами: кегль остаётся прежним,
   читаемость не страдает, а колонка перестаёт быть длиннее места. */
.side-body .style-row { margin-bottom: 0.28rem; gap: 0.1rem 0.6rem; }
.side-body .style-row input[type="range"] { margin: 0; }
.side-body .eq-row { margin-bottom: 0.45rem; }
.side-body .eq-hint,
.side-body .side-note { line-height: 1.4; }
.side-body .side-note { margin-top: 0.45rem; padding-top: 0.45rem; }
.side-group { display: none; }
.side-group.active { display: block; }
.side-note {
  color: var(--text-dim);
  font-size: 0.78rem;
  margin-top: 0.8rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
}

/* Правое окно: предпросмотр и всё, что нужно, чтобы его слушать */
.karaoke-main {
  display: flex;
  flex-direction: column;
  min-height: 0;
  gap: 0.7rem;
}
/* Сцена держит пропорции кадра и вписывается в остаток по обеим
   сторонам: по ширине окна и по его высоте (100cqh — высота этого
   самого места). Поэтому шаг целиком влезает в экран без прокрутки. */
.stage-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
  container-type: size;
  display: flex;
  align-items: center;
  justify-content: center;
}
.stage-wrap .lyrics-stage {
  width: min(100%, calc(100cqh * 16 / 9));
  max-width: 100%;
  min-height: 0;
  margin: 0;
}
/* Значок «на весь экран» стоит в углу САМОЙ сцены, а не обёртки.
   Обёртка занимает весь остаток окна, а сцена держит кадр 16:9 и
   вписывается в него — по краям остаются поля. Кнопка считала угол
   от обёртки и висела в этих полях: сбоку от сцены на широком окне
   и над её рамкой на узком, а на «во весь экран» уезжала в угол
   монитора. Поля считаем той же формулой, что задаёт ширину сцены
   (см. .stage-wrap .lyrics-stage), в единицах контейнера: 100cqw и
   100cqh — это ширина и высота обёртки, и они не путаются между
   собой, как «100%» в top и right. */
.stage-full-btn {
  --st-w: min(100cqw, 100cqh * 16 / 9);
  --st-h: max(240px, calc(var(--st-w) * 9 / 16));
  position: absolute;
  top: max(0.6rem, calc((100cqh - var(--st-h)) / 2 + 0.6rem));
  right: max(0.6rem, calc((100cqw - var(--st-w)) / 2 + 0.6rem));
  z-index: 3;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  background: rgba(10, 10, 15, 0.6);
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  color: var(--text-dim);
  font-size: 1rem;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s, color 0.15s, border-color 0.15s;
}
.karaoke-main:hover .stage-full-btn,
.stage-full-btn:focus-visible { opacity: 1; }
.stage-full-btn:hover { color: var(--text); border-color: var(--violet); }

/* Предпросмотр во весь экран: своим режимом, а не системным
   полноэкранным — так он одинаково работает и на сайте, и в приложении.
   Выход по Esc или той же кнопкой. */
body.stage-full .karaoke-main {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: #05050a;
  padding: 1rem;
  gap: 1rem;
}
body.stage-full .stage-full-btn { opacity: 1; }
body.stage-full .site-header { z-index: 1; }

/* --- Панель «Оформление» --- */
.style-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.3rem 0.6rem;
  margin-bottom: 0.75rem;
  font-size: 0.9rem;
}
.style-row > label:first-child, .style-label { min-width: 0; color: var(--text); }
.style-row input[type="range"] { grid-column: 1 / -1; width: 100%; }
.style-row .eq-hint,
.style-row .row-wide { grid-column: 1 / -1; }
.style-row .row-colors {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  /* Зазор между столбиками цветов ужат с 0,6rem: с прежним зазором
     третий цвет уезжал на вторую строку, а закладка «Цвет» от этого
     перестаёт влезать в колонку (см. настройкиВлезают). */
  gap: 0.4rem 0.3rem;
}
.style-row .row-colors input[type="range"] { grid-column: auto; flex: 1; min-width: 90px; }
/* Образец цвета с подписью. Кегль подписи и зазор выбраны так, чтобы
   ТРИ столбика вставали в ряд одной строкой: с прежними числами третий
   переносился, и ряд занимал вдвое больше высоты. */
.color-pick { display: inline-flex; align-items: center; gap: 0.15rem; }
.style-row select {
  width: 100%;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
  padding: 0.45rem 0.7rem;
}
/* Образец цвета. Раньше это был системный <input type="color">,
   и цвет в нём рисовался внутренней плашкой с серой каймой: тёмный
   цвет (фон сцены, #16161f) от этого читался как пустой прямоугольник
   в рамке. Теперь цвет залит во всю кнопку — видно, что он задан. */
.col-chip {
  width: 34px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  background: var(--chip, #000);
  cursor: pointer;
  flex: 0 0 auto;
}
.col-chip:hover { border-color: var(--accent-solid); }
.col-chip.открыт { box-shadow: 0 0 0 2px rgb(var(--accent-rgb) / 0.55); }

/* --- Свой выбор цвета --- */
.color-pop {
  position: fixed;
  z-index: 80;
  width: 232px;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.6rem;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: var(--r-box);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
}
.cp-ready {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 0.25rem;
}
.cp-ready button {
  height: 20px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}
.cp-ready button:hover { border-color: var(--accent-solid); }
.cp-row {
  display: grid;
  grid-template-columns: 4.6em minmax(0, 1fr);
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  color: var(--text-dim);
}
/* Желобок показывает, что получится: полосу кладём через --жел,
   её ставит сам выбор (см. обновитьВыборЦвета) — так ползунок
   насыщенности красится под нынешний тон, а не радугой вообще. */
.cp-row input[type="range"] {
  width: 100%;
  height: 16px;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  cursor: pointer;
}
.cp-row input[type="range"]::-webkit-slider-runnable-track {
  height: 10px;
  border-radius: 5px;
  border: 1px solid var(--border);
  background: var(--жел, var(--bg));
}
.cp-row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -3px;
  border: 1px solid rgba(0, 0, 0, 0.6);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
.cp-bottom { display: flex; align-items: center; gap: 0.35rem; }
.cp-now {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
}
#cp-hex {
  flex: 1;
  min-width: 0;
  padding: 0.3rem 0.4rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  color: var(--text);
  font-family: inherit;
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}
#cp-done { padding: 0.3rem 0.6rem; font-size: 0.8rem; }
.style-row input[type="checkbox"] { accent-color: var(--accent-solid); cursor: pointer; }
.style-row .mini-label { color: var(--text-dim); font-size: 0.75rem; }
.seg {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  overflow: hidden;
}
.seg.row-wide { display: flex; }
.seg button {
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 0.85rem;
  padding: 0.35rem 0.9rem;
  cursor: pointer;
}
.seg.row-wide button { flex: 1; padding: 0.35rem 0.4rem; }
.seg button.active {
  background: rgb(var(--accent-rgb) / 0.18);
  color: var(--text);
}
.inst-status { color: var(--accent-solid); font-size: 0.85rem; font-weight: 600; }

/* --- Строка-проигрыш ---
   Кегль свой: обычно он равен песенному (--st-fs), но если пять нот
   с разрядкой в него по ширине не влезают, fitStageLines кладёт
   в --st-fs-break кегль поменьше. Переносить ноты нельзя — перенесённые
   читаются как две строки текста. */
.stage-line.break-line {
  font-style: italic;
  letter-spacing: 0.35em;
  font-size: var(--st-fs-break, var(--st-fs, 1.15rem));
}

/* ============ FAQ ============ */
.faq details {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem 1.5rem;
  margin-bottom: 0.8rem;
}
.faq summary { cursor: pointer; font-weight: 600; }
.faq details p { color: var(--text-dim); margin-top: 0.7rem; font-size: 0.95rem; }

/* ============ Подвал ============ */
.site-footer {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 2rem 1.5rem;
  border-top: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 0.9rem;
}
/* Подвал лежит ВНЕ .studio, а переменные темы заведены внутри неё —
   поэтому у его ссылок свой цвет по теме. Пока он был вписан числом,
   «Что нового в 1.32.0» оставалось фирменно-зелёным и в стальной теме,
   и в нейтральной: всё вокруг синело или желтело, а ссылка нет. */
:root {
  --chrome-accent: #54C492;
  --chrome-accent-hover: #92C270;
}
html[data-theme="neutral"] {
  --chrome-accent: #f0c419;
  --chrome-accent-hover: #f7dd6b;
}
html[data-theme="steel"] {
  --chrome-accent: #4d9fff;
  --chrome-accent-hover: #7cc0ff;
}
.footer-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--chrome-accent);
  font-family: inherit;
  font-size: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.footer-link:hover { color: var(--chrome-accent-hover); }

/* Под брошенный файл карточка и сцена подсвечиваются так же, как
   большая рамка песни: человек должен видеть, что сюда можно бросить. */
.bg-upload.dragover,
.lyrics-stage.dragover,
.edit-stage.dragover {
  outline: 2px dashed var(--accent-solid, #54C492);
  outline-offset: -4px;
}

/* ============ Что нового ============
   Окно со списком изменений: показывается один раз после обновления
   и по ссылке в подвале. Часть пунктов относится только к приложению —
   их прячет пара классов only-desktop / only-web, а переключает
   класс is-desktop на body (его ставит desktop.js). */
.only-desktop { display: none; }
body.is-desktop .only-desktop { display: revert; }
body.is-desktop .only-web { display: none; }

/* ============ Витрина: только на сайте ============

   БЕДА. Приложение показывало всю рекламную часть сайта — шапочное
   меню, кнопку «Открыть студию», геройский экран «Преврати любую песню
   в караоке», карточки возможностей, вопросы и подвал. Человек прочитал
   это ровно один раз, когда скачивал программу; при каждом следующем
   запуске он получал страницу, которую надо прокрутить, чтобы добраться
   до рабочего места. В приложении окно и так подписано — витрина в нём
   не продаёт ничего, а только отнимает высоту.

   Поэтому в приложении витрины нет вовсе, а студия занимает окно
   целиком: без полей, без скруглений и без прокрутки страницы. В шапке
   остаются логотип, переключатели языка и темы и «Что нового» (её
   переносит туда desktop.js — в подвале ей больше не жить).

   На сайте всё остаётся как было: там витрина и продаёт продукт. */
/* Витрина сайта в приложении не показывается. Прятать её надо СТИЛЯМИ,
   а не сносом из разметки: снос делает desktop.js, а он последний скрипт
   страницы — и до него человек успевает увидеть кусок сайта. Ровно так
   и было с блоком «Версия для компьютера» (.desktop-cta): в списке
   ниже его не хватало, и он оставался единственным видимым разделом
   между шапкой и студией — на Windows это секунда, на Маке миг.
   Правило простое: появился раздел, которого в приложении быть
   не должно, — впиши его СЮДА, а не только в desktop.js. */
body.is-desktop { overflow: hidden; }
body.is-desktop .hero,
body.is-desktop .features,
body.is-desktop .faq,
body.is-desktop .desktop-cta,
body.is-desktop .site-footer,
body.is-desktop .site-nav { display: none; }
/* Заголовок «Студия» над рядом шагов: окно приложения и так подписано */
body.is-desktop .studio > h2 { display: none; }
body.is-desktop .studio {
  width: 100%;
  max-width: none;
  height: calc(100vh - var(--header-h, 69px));
  min-height: 0;
  margin: 0;
  border: none;
  border-radius: 0;
  --studio-pad-x: 1.2rem;
  padding: 0.9rem var(--studio-pad-x) 0.8rem;
}

/* Руководство в приложении — окно поверх студии, а не раздел страницы:
   раздел спрятан вместе с витриной, и ссылка «Как пользоваться» вела бы
   в никуда. Открывает и закрывает его открытьРуководство в app.js. */
body.is-desktop .how { display: none; }
body.is-desktop.guide-open .how {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 130;
  max-width: none;
  margin: 0;
  padding: 2rem 1.5rem 3rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg);
}
body.is-desktop.guide-open .how > * { max-width: 1100px; margin-inline: auto; }
/* Крестик виден только в приложении, где раздел открыт окном. Прячем
   его своим правилом, а не классом only-desktop: у того та же сила
   селектора, и кто кого перебьёт, решал бы порядок строк в файле. */
.guide-close { display: none; }
body.is-desktop .guide-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  background: var(--bg-card);
  color: var(--text-dim);
  cursor: pointer;
}
body.is-desktop .guide-close:hover { color: var(--text); border-color: var(--text-dim); }
/* «Что нового» переехала из подвала в шапку приложения: подвала там нет,
   а узнать, что изменилось в версии, по-прежнему нужно.

   Стоит ровно посередине шапки, а не «между логотипом и переключателями»:
   слева логотип широкий, справа два маленьких переключателя, и на своём
   месте в потоке надпись съезжала вправо — казалась не по центру,
   потому что и не была. Абсолютное положение считается от самой шапки
   (она sticky, то есть позиционированный элемент). */
body.is-desktop .site-header .footer-link {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.85rem;
  white-space: nowrap;
}

.whatsnew {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(5, 5, 10, 0.85);
  backdrop-filter: blur(6px);
}
.whatsnew-box {
  position: relative;
  width: min(560px, 100%);
  /* Ровно столько, сколько остаётся от экрана за вычетом полей подложки —
     иначе на низком окне низ карточки уезжает за край */
  max-height: calc(100vh - 3rem);
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2rem;
  animation: whatsnew-in 0.25s ease-out;
}
/* Прокручивается только содержимое: кнопка «Понятно» обязана остаться
   на виду, сколько бы ни было раскрыто под ней. */
.whatsnew-scroll { overflow-y: auto; min-height: 0; }
.whatsnew-box > .whatsnew-actions { flex: none; padding-top: 0.9rem; }

@keyframes whatsnew-in {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}
.whatsnew-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.9rem;
  border-radius: var(--r-mini);
  background: var(--grad-btn);
  color: #f2f2f7;
  font-size: 0.8rem;
  font-weight: 700;
}
.whatsnew-box h3 {
  margin: 0.9rem 0 1.2rem;
  font-size: 1.35rem;
  letter-spacing: -0.02em;
}
.whatsnew-list { list-style: none; display: grid; gap: 0.9rem; }

/* ---- Что было раньше ----
   Список новостей рос от выпуска к выпуску и дорос до сорока с лишним
   пунктов: свежее тонуло среди прошлогоднего, и человек, открывший окно
   после обновления, читал в основном то, что уже видел. Наверху осталось
   только новое, всё прежнее сложено сюда и разложено по версиям. */
/* Починки этого же выпуска: свёрнуты и стоят под новыми возможностями.
   Окно открывают с вопросом «что нового», и первым делом человек должен
   увидеть НОВОЕ — то, чего вчера нельзя было сделать вовсе. */
/* «Новых возможностей нет» — тихая строка, а не пункт списка: она
   не новость, а объяснение, почему список наверху пуст. */
.whatsnew-only-fixes { color: var(--text-dim); margin: 0.2rem 0 0; }
.whatsnew-fixes { margin-top: 0.6rem; }
.whatsnew-fixes > summary,
.whatsnew-old > summary {
  cursor: pointer;
  color: var(--text-dim);
  padding: 0.4rem 0;
  list-style: none;
}
.whatsnew-fixes > summary::-webkit-details-marker { display: none; }
.whatsnew-fixes > summary::before { content: '▸ '; }
.whatsnew-fixes[open] > summary::before { content: '▾ '; }
.whatsnew-fixes > summary:hover { color: var(--text); }
.whatsnew-old { margin-top: 1rem; }
.whatsnew-old > summary {
  cursor: pointer;
  color: var(--text-dim);
  padding: 0.4rem 0;
  list-style: none;
}
.whatsnew-old > summary::-webkit-details-marker { display: none; }
.whatsnew-old > summary::before { content: '▸ '; }
.whatsnew-old[open] > summary::before { content: '▾ '; }
.whatsnew-old > summary:hover { color: var(--text); }
.whatsnew-ver {
  margin: 1.1rem 0 0.4rem;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.whatsnew-list li {
  border: 1px solid var(--border);
  border-radius: var(--r-box);
  background: var(--bg-soft);
  padding: 0.9rem 1.1rem;
  color: var(--text-dim);
  font-size: 0.93rem;
  line-height: 1.55;
}
.whatsnew-list b { color: var(--text); }
.whatsnew-more {
  margin-top: 1rem;
  color: var(--text-dim);
  font-size: 0.9rem;
}
.whatsnew-more a { color: #54C492; }
.whatsnew-actions {
  display: flex;
  justify-content: center;
  margin-top: 1.4rem;
}
/* Крестик стоит В ПОТОКЕ, а не поверх содержимого. Раньше он был
   absolute в углу карточки, и при прокрутке текст уезжал под него:
   человек это и увидел — кнопка наезжала на блоки. Теперь он занимает
   свою строку над прокруткой, и заехать под него нечему. Отрицательные
   поля возвращают его в угол, каким он и был на вид. */
.whatsnew-close {
  align-self: flex-end;
  flex: none;
  margin: -1rem -1rem 0.3rem 0;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--r-ctl);
  background: transparent;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.whatsnew-close:hover { color: var(--text); border-color: var(--violet); }

/* ============ Меньше движения ============
   Системная настройка «Уменьшить движение»: гасим анимации студии,
   но не сам караоке — подсветка слов остаётся, она несёт смысл. */
@media (prefers-reduced-motion: reduce) {
  .stage-line,
  .stage-count,
  .stage-count i,
  .edit-stage .stage-line,
  .whatsnew-box {
    animation: none !important;
    transition: none !important;
  }
  .stage-count i { transform: none !important; }
}

/* Низкое окно (ноутбук 1280×720 или 1440×900): рабочему месту нужен
   каждый пиксель высоты, поэтому подпись «Студия» уходит — шаг и так
   назван в переключателе — а поля студии становятся уже. */
@media (max-height: 900px) {
  .studio { padding: 0.8rem 1.5rem; }
  .studio h2 { display: none; }
  .steps-nav { margin-bottom: 0.7rem; }
  .export-row { margin-top: 0.6rem; }
  #step-3 .panel-actions { margin-top: 0; }
  #step-3 .panel-actions .btn { padding: 0.5rem 1rem; font-size: 0.9rem; }
  /* Просмотр на низком экране ограничен высотой, а не шириной:
     лишнюю ширину отдаём списку строк — там она нужнее */
  #step-3 .editor-grid {
    grid-template-columns: var(--list-w, 1.6fr) auto minmax(0, 1fr) auto var(--insp-w, 210px);
  }
}

/* ============ Адаптив ============ */
/* На узком экране двух колонок не получится: настройки встают под
   предпросмотр, а студия перестаёт быть высотой в окно — прокрутка
   страницы на телефоне привычна и ничему не мешает. */
@media (max-width: 900px) {
  .studio {
    display: block;
    height: auto;
    min-height: 0;
    width: calc(100% - 1.5rem);
  }
  .step-panel.active { display: block; }
  /* На узком экране три столбца в ряд не встают: работа, шаги
     и ссылка ложатся друг под друга по середине. */
  .steps-nav { grid-template-columns: minmax(0, 1fr); justify-items: center; }
  .steps-tabs, .proj-box, .steps-help { grid-column: 1; grid-row: auto; }
  .proj-box { justify-content: center; flex-wrap: wrap; }
  .steps-help { justify-self: center; }
  .karaoke-layout { grid-template-columns: minmax(0, 1fr); }
  /* В одну колонку места для сторон нет — порядок обычный, разметочный */
  .karaoke-layout > .karaoke-side,
  .karaoke-layout > .stage-wrap { grid-column: auto; grid-row: auto; }
  /* Предпросмотр сверху: на телефоне важнее видеть, что получается */
  .karaoke-main { order: -1; }
  .karaoke-side { margin-bottom: 1rem; }
  .side-body { max-height: 50vh; }
  /* Без высоты у родителя единицы cqh считать не от чего — сцена
     возвращается к обычной ширине во всё окно */
  .stage-wrap { container-type: normal; display: block; }
  .stage-wrap .lyrics-stage { width: 100%; }
  /* Контейнера не стало — считать cq-единицы не от чего, а полей по
     краям здесь и нет: сцена во всю ширину обёртки */
  .stage-full-btn { top: 0.6rem; right: 0.6rem; }
  /* Окна редактора на узком экране идут друг за другом своей высотой.
     Через #step-3 — иначе правило низкого окна (там три колонки)
     оказывается сильнее по весу. */
  #step-3 .editor-grid { grid-template-columns: minmax(0, 1fr); }
  /* Делить между окнами и дорожкой здесь нечего: студия перестала быть
     высотой в окно, всё идёт своей высотой и прокручивается страницей.
     Разделители без общего запаса места только мешали бы — и тот, что
     делит высоту, и тот, что делит ширину: колонка здесь одна. */
  .ed-splitter, .ed-vsplitter, .ed-lsplitter { display: none; }
  .ed-lines .edit-list { max-height: 40vh; }
  .edit-stage-wrap { container-type: normal; display: block; }
  .edit-stage { width: 100%; }
  #step-1.active, #step-2.active { overflow: visible; }
  #step-2 #lyrics-input { min-height: 300px; }
  /* На узком экране две колонки шага «Текст» не помещаются: блок
     разметки нейросетью встаёт над полем, как и раньше. Правило
     адресное — то, что задаёт колонки, сильнее общего .step-panel. */
  #step-2.active:has(> .asr-block) { display: block; }
  #step-2.active:has(> .asr-block) > .asr-block {
    margin-bottom: 0.9rem;
    overflow-y: visible;
  }
  /* Карточки шага «Песня» тоже идут друг под другом */
  #step-1 .track-info,
  #step-1 .track-info:has(> #ai-block) { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
  .site-nav { display: none; }
  /* На узком экране в шапке остаются только имя и «Открыть студию»,
     и места им впритык. Bungee шире системного шрифта примерно на
     четверть, поэтому здесь кегль ниже: замер на 320 px — имя занимает
     119 px против 123,5 px у прежнего системного, то есть шапке
     от фирменного шрифта не стало теснее. */
  .logo-text { font-size: 0.85rem; }
  .hero { padding: 4.5rem 1.2rem 4rem; }
  .studio { padding: 2rem 1rem 2.5rem; }
  .panel-actions { justify-content: center; }
  /* Редактор на узком экране: три окна встают друг под друга */
  .editor-grid { grid-template-columns: 1fr; }
  .ed-bar { font-size: 0.75rem; }
  .ed-right { margin-left: 0; }
  /* Сетка строк на узком экране: времена в одну строку, текст под ними
     во всю ширину. Длительность прячем — на дорожке она и так видна. */
  .edit-row { flex-wrap: wrap; row-gap: 0.1rem; padding: 0.45rem 0.7rem; }
  .edit-row .edit-text { flex-basis: 100%; order: 9; }
  .edit-row .dur-ts { display: none; }
  .tap-actions .btn { flex: 1; padding: 0.75rem 1rem; }
}

/* ============ Скачивание на заставке ============
   Упор проекта — настольная версия, поэтому предлагаем её сразу,
   не заставляя листать до блока «Для компьютера». */
.hero-get {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  margin-top: 1.6rem;
}
.hero-get-label { color: var(--text-dim); font-size: 0.92rem; }
.hero-get-buttons { display: flex; gap: 0.8rem; flex-wrap: wrap; justify-content: center; }
/* Браузерная студия — запасной путь, а не главный: обычная кнопка
   с обводкой под парой крупных «скачать». Отделена сверху чертой
   расстояния, чтобы не читалась как третья кнопка того же ряда. */
.hero-browser { margin-top: 0.4rem; }

/* ---------- Очередь из нескольких песен ----------
   Только в приложении: на сайте нет ни нейросетей, ни папок.
   Список — рядами «имя файла — что с ним сейчас», как в любой
   очереди задач: глазами ищут своё имя, а не порядковый номер. */
.queue-start { margin: 0.7rem auto 0; max-width: 560px; text-align: center; }
.queue-hint { margin: 0.35rem 0 0; font-size: 0.78rem; color: var(--muted); line-height: 1.45; }
.queue-box { max-width: 560px; text-align: left; }
.queue-list { margin: 0.6rem 0 0; max-height: 42vh; overflow-y: auto; }
.queue-row {
  display: flex; justify-content: space-between; gap: 0.8rem;
  padding: 0.32rem 0.1rem; border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}
.queue-row:last-child { border-bottom: none; }
.queue-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.queue-state { color: var(--muted); white-space: nowrap; }
.queue-row.now .queue-state { color: var(--accent); }
.queue-row.done .queue-state { color: var(--ok, #359C6E); }
.queue-row.bad .queue-state { color: var(--bad, #ef4444); overflow: hidden; text-overflow: ellipsis; }
.queue-note { margin: 0.6rem 0 0; font-size: 0.82rem; color: var(--muted); }

.queue-sum { margin: 0.1rem 0 0.2rem; font-size: 0.86rem; color: var(--text); line-height: 1.45; }
.queue-where { color: var(--muted); }
.queue-buttons { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 0.9rem; }
.queue-about { margin: 0 0 0.8rem; font-size: 0.84rem; color: var(--muted); line-height: 1.5; }
.queue-step { margin: 0 0 0.6rem; }
.queue-step-head {
  font-size: 0.78rem; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 0.3rem;
}
.queue-step-text { margin: 0 0 0.6rem; font-size: 0.84rem; line-height: 1.5; color: var(--text); }
.queue-warn { margin: 0.5rem 0 0; font-size: 0.82rem; color: var(--bad, #ef4444); line-height: 1.45; }

/* ---------- Окно «Сохранить видео» ---------- */
.video-box { max-width: 420px; text-align: left; }
.video-row {
  display: flex; align-items: center; justify-content: space-between; gap: 0.8rem;
  margin: 0.5rem 0; font-size: 0.88rem;
}
.video-row select { min-width: 200px; }
/* Цена выбора: вес файла и время записи. Не подсказка мелким шрифтом,
   а число в одну строку под самим выбором — его читают, а подсказки нет. */
.video-size {
  margin: 0.35rem 0 0;
  color: var(--text-dim);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.video-size b { color: var(--text); font-weight: 600; }
.video-size.тяжело b { color: var(--orange); }
.video-hint { margin: 0.8rem 0 0; font-size: 0.8rem; color: var(--muted); line-height: 1.5; }
/* Кнопка «Сохранить…» — та же машинка, что у чипа проекта: кнопка
   с уголком и меню (см. .pick-switch в шапке). Но открывается оно
   ВВЕРХ: ряд вывода стоит у самого низа студии, и вниз меню уезжало
   за её край — на снимке было видно один пункт из четырёх. */
.export-switch { position: relative; display: inline-flex; }
.export-menu {
  top: auto;
  bottom: calc(100% + 6px);
  right: auto;
  left: 0;
  min-width: 230px;
}
