/* Speed up a video (speed_up_video_v2) — оформление рабочей области.
   Акцентная (фиолетовая) палитра — ЛОКАЛЬНЫЕ токены на .vsp; сайтовые токены и шапку не трогаем
   (.tool_conventions/_common.md, «Подложка инструмента»). Каркас — Bootstrap, вид догоняется поверх.

   РЕДИЗАЙН 18.09.2026 (.planning/UI_REDESIGN.md). Состояние `idle` НЕ ТРОНУТО — ни разметкой, ни
   этими стилями: стартовый экран остаётся тем же, каким был (решение владельца). Переделано всё,
   что человек видит ПОСЛЕ загрузки ролика: кадр, полоса управления и панель скорости — один блок
   без рамок между частями, перо и музыка — в углу кадра, пресеты — подписи делений ползунка,
   пояснения — один слот в одну строку. */

.vsp {
    --vs-accent:      #6d4ce6;
    --vs-accent-2:    #8b5cf6;
    --vs-accent-dark: #5a3ad1;
    --vs-soft:        #efeafe;  /* светлая заливка иконок и чипов */
    --vs-bg:          #f6f4fb;  /* подложка рабочих блоков */
    --vs-card:        #ffffff;
    --vs-text:        #2c2c3a;
    --vs-muted:       #8b8b9c;
    --vs-line:        #e7e4f1;
    --vs-mark:        #e5322d;  /* красный маркер: тот же цвет, что кладётся в готовый файл */
    --vs-radius:      16px;
    --vs-radius-sm:   10px;
    --vs-zone-h:      20px;     /* высота полосы зон ускорения (под полосой воспроизведения) */
    --vs-marks-h:     16px;     /* высота полосы пометок */
    --vs-music-h:     16px;     /* высота полосы музыки */
    --vs-music:       #0d9488;  /* бирюзовый: звук. Фиолетовое — скорость, красное — маркер */
    --vs-music-soft:  #effbf9;
    --vs-music-line:  #cdece7;
    --vs-grab-w:      36px;     /* ширина ЗОНЫ ЗАХВАТА ручки: не зависит от ширины куска */
    --vs-min-piece:    5px;     /* минимальная НАРИСОВАННАЯ ширина куска: иначе его не видно */
    /* Кадр — фиксированной высоты, чтобы блок не прыгал при смене ролика и при рисовании.
       16:9 в колонке 960 — это ровно 540 (таблица финальных размеров проекта). */
    --vs-frame-h:     clamp(260px, 54vh, 540px);
    --vs-frame-bg:    #0f0e17;
    /* Подложка МОНТАЖНОГО ТАЙМЛАЙНА под кадром. Чуть светлее кадра, но всё равно тёмная:
       дорожки, риски, игла и ручки нарисованы светлым по тёмному, и светлая полоса
       потребовала бы перекрасить каждый из них (см. `.vsp__tl`). */
    --vs-tl-bg:       #191527;
    --vs-hit:         36px;     /* минимальная зона нажатия органа (стол) */

    max-width: 960px;
    margin-inline: auto;
    color: var(--vs-text);
    font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* СТАРТОВЫЙ ЭКРАН ОСТАЁТСЯ ПРЕЖНЕЙ ШИРИНЫ — 760, как был (решение владельца 18.09.2026:
   «стартовый экран не меняется»). Шире колонка нужна КАДРУ, а не рамке приёма файла; обе
   колонки центрованы, поэтому рамка стоит ровно там же, где стояла, и совпадает со старым
   инструментом попиксельно (гейт `_vspeed_v7_layout.js`). */
.vsp[data-state="idle"] { max-width: 760px; }

/* hidden должен скрывать даже то, чему задан display (кнопки .btn и флекс-контейнеры) */
.vsp [hidden] { display: none !important; }

/* Снимаем браузерное/bootstrap обрамление фокуса (правило CLAUDE.md) */
.vsp input:focus,
.vsp button:focus,
.vsp select:focus,
.vsp canvas:focus,
.vsp [tabindex]:focus { outline: none; box-shadow: none; }
/* Селект выбора трека появился 19.09.2026 — у него своя рамка фокуса от Bootstrap, и её
   тоже снимаем: правило CLAUDE.md «никакого обрамления поля при нажатии» без исключений. */
.vsp .form-select:focus { border-color: var(--vs-line); box-shadow: none; }

/* ---------- Шапка карточки ----------
   Живёт ТОЛЬКО до загрузки ролика: дальше H1 страницы уже сказал, что это за инструмент, а
   60 px высоты нужнее кадру (решение владельца 18.09.2026). Сдвиг на её высоту происходит
   один раз — в тот момент, когда ролик открылся. */
.vsp__head {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .35rem .25rem 1rem;
}
.vsp[data-state="ready"] .vsp__head { display: none; }
.vsp__logo {
    display: inline-grid;
    place-items: center;
    width: 34px; height: 34px;
    border-radius: 9px;
    background: linear-gradient(135deg, var(--vs-accent), var(--vs-accent-2));
    color: #fff;
    font-size: 1rem;
}
.vsp__title { font-weight: 700; font-size: 1.15rem; }

/* ---------- Переключение состояний ---------- */
.vsp[data-state="ready"] .vsp__idle  { display: none; }
.vsp[data-state="idle"]  .vsp__ready { display: none; }

/* ====================================================================
   IDLE — НЕ МЕНЯТЬ

   Стартовый экран остаётся ровно таким, каким был у прежнего инструмента (решение владельца
   18.09.2026): рамка приёма файла, три плитки-обещания, те же отступы и размеры. Сверяется
   попиксельно стендом `_vspeed_v7_layout.js`; любая правка здесь — это провал гейта.
   ==================================================================== */
.vsp__drop {
    border: 2px dashed #cbbff2;
    border-radius: var(--vs-radius);
    background: var(--vs-card);
    padding: 2.6rem 1.5rem;
    text-align: center;
    transition: border-color .15s, background .15s;
}
.vsp__drop.is-over { border-color: var(--vs-accent); background: #faf9ff; }

.vsp__drop-icon { position: relative; display: inline-block; font-size: 3.4rem; color: var(--vs-accent-2); line-height: 1; margin-bottom: 1rem; }
.vsp__drop-icon .vsp__drop-spark { position: absolute; right: -.7rem; bottom: -.1rem; font-size: 1.4rem; color: var(--vs-accent); background: #fff; border-radius: 50%; padding: .12rem .1rem; }
.vsp__drop-title { font-weight: 800; font-size: 1.6rem; margin: 0 0 1.1rem; color: #1f1f2b; }
.vsp__drop-note { color: var(--vs-muted); font-size: .86rem; margin: 1rem 0 0; }

/* Первичная кнопка. Селекторы нарочно с `.vsp` впереди: у Bootstrap `.btn:disabled` весит два
   класса и перебивает одноклассовое правило — подпись на выключенной кнопке уезжала бы в тёмный
   цвет темы вместо белого. Выключенное состояние показываем прозрачностью, не цветом. */
.vsp .vsp__btn-primary,
.vsp .vsp__download {
    background: linear-gradient(135deg, var(--vs-accent), var(--vs-accent-2));
    border: 0; color: #fff; font-weight: 600;
    border-radius: var(--vs-radius-sm);
    padding: .7rem 1.5rem;
    display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
}
.vsp .vsp__btn-primary:hover,
.vsp .vsp__download:hover:not(:disabled) { background: linear-gradient(135deg, var(--vs-accent-dark), var(--vs-accent)); color: #fff; }
.vsp__upload { padding-inline: 2rem; }

.vsp__features {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
    background: var(--vs-line);
    border: 1px solid var(--vs-line);
    border-radius: var(--vs-radius);
    overflow: hidden;
    margin-top: 1.1rem;
}
.vsp__feature { display: flex; align-items: center; gap: .7rem; padding: 1rem 1.1rem; background: var(--vs-bg); }
.vsp__feature-ic { flex: none; display: inline-grid; place-items: center; width: 38px; height: 38px; border-radius: 10px; background: var(--vs-soft); color: var(--vs-accent); font-size: 1.05rem; }
.vsp__feature-txt { display: flex; flex-direction: column; line-height: 1.25; }
.vsp__feature-txt strong { font-size: .92rem; }
.vsp__feature-txt span { font-size: .8rem; color: var(--vs-muted); }

/* ====================================================================
   READY: кадр, полоса управления и панель скорости — ОДИН блок

   Объект (ролик и полосы), орган (скорость) и итог (Download) стоят друг под другом в одном
   контейнере без внутренних рамок: глаз идёт сверху вниз, не перескакивая с карточки на
   карточку. На столе панель и кнопка — одна строка сеткой; на телефоне кнопка уходит вниз и
   липнет к окну, поэтому она ОТДЕЛЬНЫЙ элемент блока, а не часть панели.
   ==================================================================== */
/* РАСКЛАДКА v3: четыре яруса сверху вниз — кадр, монтажный таймлайн, вкладки, скачивание.
   Панель скорости отдельным ярусом больше не стоит: она стала первой вкладкой. */
.vsp__block {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
        "stage"
        "tl"
        "tabs"
        "dl";
    background: var(--vs-bg);
    border-radius: var(--vs-radius);
    overflow: hidden;
}
.vsp__stage { grid-area: stage; position: relative; }
.vsp__dl    { grid-area: dl; }

.vsp__frame {
    position: relative;
    display: grid;
    place-items: center;
    background: var(--vs-frame-bg);
    height: var(--vs-frame-h);
    overflow: hidden;
    transition: opacity .18s;
}
/* СБОРКА ИДЁТ — КАДР ТУСКНЕЕТ, а не закрывается оверлеем: человек видит, на каком месте ролика
   он стоит, и что инструмент не «завис» (решение проекта, ЭТАП 5). */
.vsp.is-busy .vsp__frame { opacity: .6; }
@media (prefers-reduced-motion: reduce) { .vsp__frame { transition: none; } }

/* ПОТОЛОК КАРТИНКИ ЗАДАН ТЕМ ЖЕ ТОКЕНОМ, ЧТО И ВЫСОТА КАДРА, а не процентами. Замер
   18.09.2026: `max-height: 100%` у элемента внутри сетки с автоматической строкой браузер
   считает неопределённым и попросту не применяет — вертикальный ролик 480×848 оставался
   848 px высотой и уезжал под нижний край кадра (кадр 443). Процентов здесь быть не должно.
   Ширина и высота остаются `auto`: пропорции картинки держит сам браузер, а слой разметки
   кладётся ровно по её прямоугольнику (по чёрным полям рисовать нельзя). */
.vsp__video {
    display: block;
    max-width: 100%;
    max-height: var(--vs-frame-h);
    width: auto; height: auto;
}
/* Слой разметки лежит ТОЧНО по картинке ролика (координаты считает JS: у вертикального ролика
   поля по бокам, и рисовать по ним нельзя). */
.vsp__ink {
    position: absolute;
    left: 0; top: 0;
    pointer-events: none;
    touch-action: none;
}
.vsp.is-pen .vsp__ink { pointer-events: auto; cursor: crosshair; }

/* ТЕГИ ПЕРЕПАКОВАННОГО ПОКАЗА. Лежат поверх ролика и под слоем разметки, в тех же координатах
   (их считает JS вместе с координатами слоя разметки). Видны только когда показ идёт готовым
   роликом; в остальное время их нет вовсе, и картинку даёт сам проигрыватель. Размер задан
   точно по картинке исходника, поэтому вписывание кадра ничего не режет и не растягивает. */
.vsp__rx {
    position: absolute;
    left: 0; top: 0;
    object-fit: contain;
    background: #000;
    pointer-events: none;
}

/* Надпись «картинка стоит» — посреди кадра, там, где человек и смотрит. Появляется только на
   время настоящего замирания, поэтому подложка плотная: полупрозрачную Chrome над видео не
   блендит, она мерцает (.tool_conventions/for_webcam.md). */
.vsp__stall {
    position: absolute;
    left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    display: flex; align-items: center; gap: .5rem;
    padding: .45rem .85rem;
    background: #241d36;
    color: #fff; font-size: .84rem; font-weight: 600;
    border-radius: 999px;
    pointer-events: none;
    white-space: nowrap;
}
/* Точка мигает — она и говорит, что работа идёт, пока картинка стоит. */
.vsp__stall-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--vs-accent-2);
    animation: vsp-stall-blink 1s steps(2, start) infinite;
}
@keyframes vsp-stall-blink { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) {
    .vsp__stall-dot { animation: none; }
}

/* Полоса ожидания перепаковки. Не закрывает кадр целиком: человек видит, на каком месте ролика
   он стоит, и что работа идёт, а не встала. */
.vsp__prep {
    position: absolute;
    left: 50%; bottom: 18%;
    transform: translateX(-50%);
    display: flex; flex-direction: column; align-items: center; gap: .45rem;
    min-width: min(260px, 70%);
    padding: .55rem .9rem;
    background: rgba(20, 16, 31, .86);
    color: #fff; font-size: .84rem; font-weight: 600;
    border-radius: 999px;
    pointer-events: none;
    text-align: center;
}
.vsp__prep-bar {
    display: block; width: 100%; height: 4px;
    background: rgba(255, 255, 255, .22); border-radius: 999px; overflow: hidden;
}
.vsp__prep-fill {
    display: block; height: 100%; width: 0;
    background: var(--vs-accent-2); border-radius: 999px;
}
/* ХОД НЕИЗВЕСТЕН — ПОЛОСА БЕГУЩАЯ. */
.vsp__prep-bar.is-wait .vsp__prep-fill { width: 34%; animation: vsp-wait 1.15s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
    .vsp__prep-bar.is-wait .vsp__prep-fill { animation: none; width: 100%; opacity: .45; }
}

/* ====================================================================
   ПОДГОТОВКА ЗАПИРАЕТ ИНСТРУМЕНТ (решение владельца 18.09.2026)

   Пока строится шкала, прелоадер стоит НА КАДРЕ и закрывает его целиком, а панель скорости и
   кнопка скачивания погашены: человек загрузил ролик и ЖДЁТ, а не жмёт наугад, не понимая,
   что происходит. Прежде кадр оставался живым, и тык по нему запускал показ прямо во время
   замера — то есть портил ровно тот замер, ради которого его и просили подождать.

   КАДР ЗАТЕМНЯЕТСЯ, НО НЕ ПРЯЧЕТСЯ: человек видит свой ролик и понимает, что готовят именно
   его. Сама надпись при этом стоит на ПЛОТНОЙ подложке — на светлом кадре белый текст поверх
   одной лишь пелены не читается (скрин 18.09.2026: кольцо спиннера терялось на белой стене).
   Обычный запрет на полупрозрачное над видео («Chrome не блендит, она мерцает»,
   .tool_conventions/for_webcam.md) относится к ИДУЩЕЙ картинке; здесь показ стоит и мерцать
   нечему.
   ==================================================================== */
/* Пелена на весь кадр — она же и глотает тычки по картинке. */
.vsp.is-scale-wait .vsp__frame::after {
    content: '';
    position: absolute; left: 0; right: 0; top: 0; bottom: 0;
    background: rgba(12, 9, 20, .62);
    cursor: progress;
    z-index: 11;                   /* выше всего, что живёт в кадре (поповер музыки — 9) */
}
.vsp.is-scale-wait .vsp__prep {
    left: 50%; top: 50%; bottom: auto;
    transform: translate(-50%, -50%);
    min-width: min(300px, 78%);
    gap: .6rem;
    padding: 1rem 1.2rem;
    border-radius: var(--vs-radius);
    background: #171122;
    z-index: 12;
}
/* Кольцо — тот же знак работы, что и у спиннера панели: доли у замера нет по природе. */
.vsp.is-scale-wait .vsp__prep::before {
    content: '';
    width: 36px; height: 36px;
    border-radius: 50%;
    border: 3px solid rgba(255, 255, 255, .22);
    border-top-color: #fff;
    animation: vsp-spin .8s linear infinite;
}
.vsp.is-scale-wait .vsp__prep-bar { max-width: 260px; }
@media (prefers-reduced-motion: reduce) {
    .vsp.is-scale-wait .vsp__prep::before { animation-duration: 2.4s; }
}
/* Нажимать в кадре нечего: ни кнопки посреди него, ни угла инструментов. */
.vsp.is-scale-wait .vsp__play-big,
.vsp.is-scale-wait .vsp__corner { display: none; }
/* ПОЛОСА УПРАВЛЕНИЯ УБИРАЕТСЯ ПРОЗРАЧНОСТЬЮ, А НЕ `display: none`, И ЭТО НЕ ВКУСОВЩИНА.
   Ширины полос инструмент МЕРЯЕТ и запоминает (`measure`), а у спрятанного `display: none`
   элемента ширина равна нулю: полоса, перерисованная в это время, запоминала единицу пикселя
   и дальше рисовала все куски нулевой ширины — надписи на них пропадали, а риски шкалы
   времени не появлялись вовсе (поймано приёмкой `_vspeed_v7_lane` и `_vspeed_v7_time`
   18.09.2026, в тот же час, когда запрет был написан). */
.vsp.is-scale-wait .vsp__controls { opacity: 0; pointer-events: none; }
/* Панель скорости и кнопка — погашены и указателя не принимают (в JS они ещё и `disabled`:
   вид закрывает мышь, `disabled` — клавиатуру и чужие пути в то же действие). */
.vsp.is-scale-wait .vsp__panel,
.vsp.is-scale-wait .vsp__dl { opacity: .45; pointer-events: none; }

/* Кнопка «играть» посреди кадра — только пока ролик стоит. Подложка НЕПРОЗРАЧНАЯ и без тени:
   полупрозрачную тень Chrome над видео не блендит, она мерцает (.tool_conventions/for_webcam.md). */
.vsp__play-big {
    position: absolute;
    width: 68px; height: 68px;
    border: 0; border-radius: 50%;
    background: rgba(109, 76, 230, .92);
    color: #fff; font-size: 2rem; line-height: 1;
    display: grid; place-items: center;
    cursor: pointer;
    transition: transform .12s, background .12s;
}
.vsp__play-big:hover { background: var(--vs-accent); transform: scale(1.05); }
.vsp.is-playing .vsp__play-big,
.vsp.is-pen .vsp__play-big,
.vsp.is-busy .vsp__play-big { display: none; }

.vsp__badge {
    position: absolute; left: .75rem; top: .75rem;
    background: rgba(28, 24, 46, .78);
    color: #fff; font-size: .78rem; font-weight: 700;
    padding: .22rem .6rem; border-radius: 999px;
    pointer-events: none;
}
/* Ярлык скорости в углу кадра нужен ТОЛЬКО когда полоса управления спрятана: пока она видна,
   скорость написана крупно в панели, и два одинаковых числа рядом — лишний шум. */
.vsp__badge { display: none; }
.vsp.is-playing:not(.is-hover) .vsp__badge { display: inline-block; }

/* Полный экран: кадр занимает весь экран, ролик вписывается целиком. */
.vsp__frame:fullscreen { width: 100vw; height: 100vh; background: #000; border-radius: 0; }
.vsp__frame:fullscreen .vsp__video { max-width: 100vw; max-height: 100vh; }
.vsp__frame:-webkit-full-screen { width: 100vw; height: 100vh; background: #000; }
.vsp__frame:-webkit-full-screen .vsp__video { max-width: 100vw; max-height: 100vh; }

/* ====================================================================
   ПРИЗНАК РИСОВАНИЯ (верх-право кадра)

   Кнопок пера, музыки и «другого ролика» в углу кадра больше НЕТ (решение владельца
   19.09.2026): перо включает вкладка «Marks», музыка живёт во вкладке «Sound», а «другой
   ролик» стоит у кнопки скачивания. Здесь остался ОДИН нерабочий значок — он объясняет,
   почему тап по кадру сейчас рисует, а не ставит показ на паузу.
   ==================================================================== */
.vsp__pen-bar {
    position: absolute; right: 12px; top: 12px;
    display: flex; align-items: center; gap: .3rem;
    background: rgba(20, 16, 31, .72);
    border-radius: 999px;
    padding: .2rem .25rem;
    z-index: 5;
}
.vsp__pen-flag {
    display: inline-flex; align-items: center; gap: .3rem;
    background: var(--vs-mark);
    color: #fff; font-size: .74rem; font-weight: 700;
    padding: .3rem .6rem; border-radius: 999px;
    pointer-events: none;
}
/* ---------- Полоса управления внутри кадра (как у плееров в интернете) ---------- */
.vsp__controls {
    position: absolute; left: 0; right: 0; bottom: 0;
    padding: 1.6rem .6rem .5rem;
    background: linear-gradient(to top, rgba(10, 8, 20, .82) 0%, rgba(10, 8, 20, .55) 55%, rgba(10, 8, 20, 0) 100%);
    opacity: 1;
    transition: opacity .18s;
}
/* Прячем полосу, только когда ролик ИДЁТ и мышь ушла: на паузе она нужна всегда. */
.vsp.is-playing:not(.is-hover) .vsp__controls { opacity: 0; pointer-events: none; }
.vsp.is-busy .vsp__controls { opacity: 0; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .vsp__controls { transition: none; } }

/* В РЕЖИМЕ РИСОВАНИЯ ПОЛОСА ПЛЕЕРА УБИРАЕТСЯ СОВСЕМ. Раньше она вместе с дорожками уезжала
   ПОД картинку — так карандашу освобождали кадр, а дорожки оставались на виду. С раскладки v3
   дорожки и так стоят под кадром своим ярусом, и прятать надо только полосу с кнопками:
   она лежит поверх нижней трети картинки, и мазок по ней попадал бы в кнопки. Показ при
   рисовании всё равно стоит (перо ставит паузу), а перемотать можно иглой на таймлайне. */
.vsp.is-pen .vsp__controls { display: none; }

.vsp__seek {
    position: relative;
    height: 16px;
    display: flex; align-items: center;
    cursor: pointer;
    touch-action: none;
}
.vsp__seek-track, .vsp__seek-done {
    position: absolute; left: 0; height: 4px; border-radius: 999px;
    transition: height .12s;
}
.vsp__seek-track { right: 0; background: rgba(255, 255, 255, .3); }
/* Заполнение и бегунок ездят ТОЛЬКО трансформом: запись в width/left на каждом кадре
   заставляла браузер пересчитывать разметку слоя, лежащего ПОВЕРХ ВИДЕО. */
.vsp__seek-done {
    right: 0; background: var(--vs-accent-2);
    transform: scaleX(0); transform-origin: left center;
    will-change: transform;
}
.vsp__seek-knob {
    position: absolute; left: 0;
    width: 12px; height: 12px; margin-left: -6px;
    border-radius: 50%; background: #fff;
    opacity: 0;
    transition: opacity .12s;
    will-change: transform;
}
.vsp__seek:hover .vsp__seek-track,
.vsp__seek:hover .vsp__seek-done,
.vsp__seek.is-drag .vsp__seek-track,
.vsp__seek.is-drag .vsp__seek-done { height: 6px; }
.vsp__seek:hover .vsp__seek-knob,
.vsp__seek.is-drag .vsp__seek-knob { opacity: 1; }

/* ---------- Шкала времени с рисками ----------
   Размечает ГОТОВЫЙ ролик и переразмечается при каждой смене скорости. Растёт и сжимается
   вместе с полосами: приближено ровно то, что она показывает. Тянешь за шкалу — увеличенный
   кусок едет вбок (панорама), поэтому курсор здесь «схватить». */
.vsp__ruler {
    position: relative;
    height: 15px;
    overflow: hidden;
    cursor: grab;
    touch-action: none;
    user-select: none; -webkit-user-select: none;
}
.vsp__ruler.is-pan { cursor: grabbing; }
.vsp__ruler i {                         /* риска */
    position: absolute; top: 0; width: 1px; height: 5px;
    background: rgba(255, 255, 255, .45);
}
.vsp__ruler i.is-big { height: 8px; background: rgba(255, 255, 255, .7); }
.vsp__ruler b {                         /* подпись под риской */
    position: absolute; top: 5px;
    color: rgba(255, 255, 255, .72);
    font-size: .6rem; font-weight: 600; line-height: 1;
    font-variant-numeric: tabular-nums;
    transform: translateX(2px);
    pointer-events: none; white-space: nowrap;
}

/* Ряд кнопок плеера. Сжиматься разрешено ТОЛЬКО времени. */
.vsp__ctl-row { display: flex; align-items: center; gap: .1rem; margin-top: .1rem; min-width: 0; }
.vsp .vsp__ctl, .vsp__zoom { flex: none; }
/* ОДНО ВРЕМЯ — ВРЕМЯ НОВОГО РОЛИКА. Одна пара чисел, поэтому перенос ей не нужен вовсе. */
.vsp__time {
    flex: 0 1 auto; min-width: 0; white-space: nowrap;
    color: #fff; font-size: .8rem; margin-left: .35rem;
    font-variant-numeric: tabular-nums; opacity: .92;
}
.vsp .vsp__ctl {
    display: inline-grid; place-items: center;
    width: 34px; height: 34px;
    border: 0; background: 0; padding: 0;
    color: #fff; font-size: 1.1rem;
    cursor: pointer; opacity: .92;
}
.vsp .vsp__ctl:hover { opacity: 1; }
.vsp .vsp__ctl[disabled] { opacity: .35; cursor: default; }
.vsp__ctl-gap { flex: 1 1 auto; }

/* ---------- Ползунок приближения (ряд монтажного таймлайна) ----------
   Лупы-поповера с готовыми значениями больше нет: приближение переехало в ряд таймлайна
   отдельными кнопками и ползунком (решение владельца 19.09.2026 — «кнопка отмены и зума
   также должны уйти вниз»). Остался сам ползунок; он по-прежнему светлый, потому что полоса
   таймлайна тёмная. */
.vsp .vsp__zrange {
    -webkit-appearance: none; appearance: none;
    flex: 1 1 auto; min-width: 0; height: 20px; background: 0; cursor: pointer;
}
.vsp .vsp__zrange::-webkit-slider-runnable-track { height: 4px; border-radius: 999px; background: rgba(255, 255, 255, .3); }
.vsp .vsp__zrange::-moz-range-track { height: 4px; border-radius: 999px; background: rgba(255, 255, 255, .3); }
.vsp .vsp__zrange::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 14px; height: 14px; margin-top: -5px; border-radius: 50%; background: #fff;
}
.vsp .vsp__zrange::-moz-range-thumb { width: 14px; height: 14px; border: 0; border-radius: 50%; background: #fff; }

/* ====================================================================
   ПОЛОСЫ ПОД ПОЛОСОЙ ВОСПРОИЗВЕДЕНИЯ (внутри плеера)

   Второй линии времени у инструмента нет: полоса воспроизведения и есть шкала времени
   (решение владельца 10.09.2026). Полосы делят с ней ширину — левый край и правый край
   совпадают, поэтому место на полосе и место в ролике — одно и то же место.
   ==================================================================== */
.vsp__lanes {
    position: relative;
    margin: .25rem 0 .1rem;
    touch-action: none;
    user-select: none; -webkit-user-select: none;
}
.vsp__lane {
    position: relative;
    height: var(--vs-zone-h);
    border-radius: 4px;
    background: rgba(255, 255, 255, .12);
    /* При увеличении полосы показывают КУСОК ролика: то, что уехало за край, не должно
       вылезать наружу. */
    overflow: hidden;
}
.vsp__lane--marks { height: var(--vs-marks-h); margin-top: 3px; background: rgba(255, 255, 255, .1); }

/* Свободные места — то, что НЕ ускоряется: подписаны бледным «1×», иначе кусок посреди
   пустоты не читается и человек не понимает, что будет с концами ролика. */
.vsp__gap {
    position: absolute; top: 0; bottom: 0;
    display: flex; align-items: center; justify-content: center;
    color: rgba(255, 255, 255, .5);
    font-size: .62rem; font-weight: 600;
    overflow: hidden; white-space: nowrap;
    pointer-events: none;
}

/* Зона ускорения */
.vsp__seg {
    position: absolute; top: 0; bottom: 0;
    background: linear-gradient(135deg, rgba(109, 76, 230, .95), rgba(139, 92, 246, .95));
    color: #fff; font-size: .64rem; font-weight: 700;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden; white-space: nowrap;
    border-radius: 4px;
    cursor: pointer;
    min-width: var(--vs-min-piece);
}
.vsp__seg.is-active { outline: 2px solid #fff; outline-offset: -2px; }
/* У ВЫБРАННОЙ зоны в правой части сидит крестик — освобождаем ему место, чтобы подпись
   скорости не оказалась под ним. */
.vsp__seg.is-active { padding-right: var(--vs-kill, 22px); }
/* НАДПИСЬ НА ПОЛОСЕ — по наведению на столе, всегда на телефоне: полоса без единой буквы
   читается как «ещё одна перемотка», а горящая постоянно спорит с крупным числом в панели
   (разбор проекта, ЭТАП 6, пункт 7). Наведение считается на ВСЮ линию времени: человек ведёт
   курсор к полосе, а не к конкретному куску. */
.vsp__seg-txt {
    opacity: 0; transition: opacity .12s;
    overflow: hidden; white-space: nowrap; text-overflow: clip;
}
/* Выбранный кусок исключением НЕ является: по умолчанию выбран тот, под которым стоит игла,
   то есть почти всегда какой-нибудь, — и надпись горела бы постоянно (замер 18.09.2026). */
.vsp__lanes:hover .vsp__seg-txt { opacity: 1; }
/* ...а там, где наведения НЕ БЫВАЕТ, надпись стоит всегда. Признак — само наведение, а не
   ширина экрана: ландшафт телефона шире 640 px, и по ширине надпись на нём пропадала бы
   насовсем (замер 18.09.2026 на 844×390). */
@media (hover: none) { .vsp__seg-txt { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .vsp__seg-txt { transition: none; } }

/* Пометка маркера — тот же приём, что у зоны, но красная и тонкая */
.vsp__mk {
    position: absolute; top: 0; bottom: 0;
    background: var(--vs-mark);
    border-radius: 3px;
    cursor: pointer;
    /* Узкий кусок ВСЁ РАВНО видно: пометка в 3 с на десятиминутном ролике — это 1,7 px,
       и без этого минимума её нельзя даже заметить. */
    min-width: var(--vs-min-piece);
}
.vsp__mk.is-active { box-shadow: 0 0 0 2px #fff; }

/* ПОЛОСА МУЗЫКИ — третий ряд той же линии времени. Свой цвет: фиолетовое — скорость, красное —
   маркер, бирюзовое — звук, и перепутать их нельзя. */
.vsp__lane--music { height: var(--vs-music-h); margin-top: 3px; background: rgba(255, 255, 255, .1); }
.vsp__mus {
    position: absolute; top: 0; bottom: 0;
    display: flex; align-items: center;
    padding: 0 .35rem;
    background: linear-gradient(135deg, rgba(13, 148, 136, .95), rgba(45, 212, 191, .9));
    color: #04201d; font-size: .62rem; font-weight: 700;
    border-radius: 4px;
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
    cursor: pointer;
    min-width: var(--vs-min-piece);
}
.vsp__mus.is-active { outline: 2px solid #fff; outline-offset: -2px; padding-right: var(--vs-kill, 22px); }

/* Игла — сквозь обе полосы: она же бегунок полосы воспроизведения */
.vsp__needle {
    position: absolute; left: 0; top: -2px; bottom: -2px; width: 2px;
    background: #fff;
    box-shadow: 0 0 0 1px rgba(20, 16, 31, .55);
    pointer-events: none;
    will-change: transform;
}

/* ====================================================================
   РУЧКИ ВЫБРАННОГО КУСКА
   ==================================================================== */
.vsp__grips {
    position: absolute; left: 0; right: 0; top: 0; bottom: 0;
    pointer-events: none;
    opacity: 0; transition: opacity .12s;
}
.vsp__grips.is-on { opacity: 1; }
/* Ручка, до которой дошли клавишей Tab, обязана быть видна. */
.vsp__grips:focus-within { opacity: 1; }
.vsp__grips:focus-within .vsp__grip,
.vsp__grips:focus-within .vsp__kill { pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .vsp__grips { transition: none; } }
.vsp__grip {
    position: absolute;
    width: var(--vs-grab-w); margin-left: calc(var(--vs-grab-w) / -2);
    display: grid; place-items: center;
    cursor: ew-resize; touch-action: none; pointer-events: none;
    background: 0; border: 0; padding: 0;
}
.vsp__grips.is-on .vsp__grip { pointer-events: auto; }
.vsp__grip::before {                     /* стебель ровно по границе куска */
    content: ''; position: absolute; top: 0; bottom: 0; left: 50%;
    width: 2px; margin-left: -1px; background: #fff;
}
/* РИСУНОК РУЧКИ СТОИТ СНАРУЖИ КУСКА (решение владельца 19.09.2026: «ползунки у дорожек
   должны быть слева от левой границы и справа от правой, а не налезать»). Сдвиг ровно на
   половину собственной ширины: правый край левой ручки ложится на границу куска, левый край
   правой — на его конец. Сам кусок не закрыт ничем, а ЗОНА ЗАХВАТА (--vs-grab-w) как была
   широкой и симметричной, так и осталась: закрывать нельзя вид, а не касание. */
.vsp__grip--a i { transform: translateX(-50%); }
.vsp__grip--b i { transform: translateX(50%); }
/* Рисунок ручки — УЗКИЙ: зона захвата остаётся широкой (--vs-grab-w). */
.vsp__grip i {
    position: relative;
    width: 11px; height: 20px; border-radius: 4px;
    background: #fff; color: #1c1730;
    display: grid; place-items: center;
    font-size: .66rem; font-weight: 700; font-style: normal; line-height: 1;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .45);
}

/* Крестик «убрать выбранное» ВПИСАН В САМ КУСОК светлой иконкой. */
.vsp .vsp__kill {
    position: absolute;
    width: var(--vs-kill, 22px); height: var(--vs-kill, 22px);
    margin-left: calc(var(--vs-kill, 22px) / -2); padding: 0;
    transform: translateY(-50%);
    border: 0; border-radius: 50%;
    background: 0; color: rgba(255, 255, 255, .92);
    font-size: .6rem; line-height: 1;
    display: grid; place-items: center;
    cursor: pointer; pointer-events: none;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}
.vsp__grips.is-on .vsp__kill { pointer-events: auto; }
.vsp .vsp__kill:hover { background: rgba(0, 0, 0, .35); color: #fff; }
/* Кусок оказался у́же крестика — тот встаёт СНАРУЖИ, на пустом месте полосы. */
.vsp .vsp__kill--out { background: rgba(20, 16, 31, .6); }
.vsp .vsp__kill--out:hover { background: var(--vs-mark); }

/* Плашка времени над пальцем: палец закрывает ровно то место, куда целится. */
.vsp__tip {
    position: absolute; top: -22px; transform: translateX(-50%);
    background: #fff; color: #1c1730;
    font-size: .72rem; font-weight: 700;
    padding: .1rem .4rem; border-radius: 6px;
    pointer-events: none; white-space: nowrap; z-index: 6;
    font-variant-numeric: tabular-nums;
}

/* Жёлтая черта прилипания */
.vsp__snap {
    position: absolute; top: 0; bottom: 0; width: 1px;
    background: #ffd84d; pointer-events: none;
}

/* ---------- Громкость музыки (вкладка «Sound») ----------
   Поповера выбора трека в углу кадра больше нет: трек выбирается селектом во вкладке
   «Sound» (решение владельца 19.09.2026). Ползунок громкости переехал туда же — он
   единственное, что осталось от прежней панели свойств трека. */
.vsp__music-vol { display: flex; align-items: center; gap: .4rem; color: var(--vs-muted); margin: 0; }
.vsp .vsp__music-range {
    -webkit-appearance: none; appearance: none;
    flex: 1 1 auto; min-width: 0; height: 20px; background: 0; cursor: pointer;
}
.vsp .vsp__music-range::-webkit-slider-runnable-track { height: 4px; border-radius: 999px; background: var(--vs-music-line); }
.vsp .vsp__music-range::-moz-range-track { height: 4px; border-radius: 999px; background: var(--vs-music-line); }
.vsp .vsp__music-range::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 16px; height: 16px; margin-top: -6px; border-radius: 50%;
    border: 3px solid #fff; background: var(--vs-music);
}
.vsp .vsp__music-range::-moz-range-thumb { width: 16px; height: 16px; border: 3px solid #fff; border-radius: 50%; background: var(--vs-music); }

/* ====================================================================
   ОРГАН СКОРОСТИ (вкладка «Speed»): ползунок, края шкалы, кнопка верхних скоростей
   ==================================================================== */
/* КРУПНОЕ ЧИСЛО СТОИТ НАД ПОЛЗУНКОМ (решение владельца 19.09.2026: «пусть текущая скорость
   будет точно такого же размера, но НАД ползунком скорости — чел двигает и над ползунком
   меняются цифры»). Размер прежний; строки длины рядом с ним больше нет — она дублировала
   часы плеера. */
.vsp__speed-value {
    font-weight: 800; font-size: 1.85rem;
    color: var(--vs-accent-dark);
    font-variant-numeric: tabular-nums;
}

.vsp__scale { min-width: 0; }
.vsp__range-row { display: flex; align-items: center; gap: .45rem; }
.vsp__range-cap { flex: none; font-size: .69rem; font-weight: 700; color: var(--vs-muted); font-variant-numeric: tabular-nums; }

.vsp .vsp__range {
    -webkit-appearance: none; appearance: none;
    flex: 1 1 auto; min-width: 0;
    height: 22px; margin: 0;
    background: transparent; cursor: pointer;
}
.vsp .vsp__range::-webkit-slider-runnable-track {
    height: 6px; border-radius: 999px;
    background: linear-gradient(90deg, var(--vs-accent) 0 var(--vs-fill, 0%), var(--vs-line) var(--vs-fill, 0%) 100%);
}
.vsp .vsp__range::-moz-range-track { height: 6px; border-radius: 999px; background: var(--vs-line); }
.vsp .vsp__range::-moz-range-progress { height: 6px; border-radius: 999px; background: var(--vs-accent); }
.vsp .vsp__range::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 24px; height: 24px; margin-top: -9px;
    border-radius: 50%; border: 3px solid #fff;
    background: var(--vs-accent);
}
.vsp .vsp__range::-moz-range-thumb {
    width: 24px; height: 24px; border-radius: 50%;
    border: 3px solid #fff; background: var(--vs-accent);
}

/* РЯДА ЧИПОВ БЫСТРОГО ВЫБОРА БОЛЬШЕ НЕТ (решение владельца 19.09.2026: «чипы быстрого выбора
   убрать, непонятно зачем они»). Границы шкалы читаются по подписям её краёв; крайняя
   подпись помечается «8× max», когда замер отрезал часть ступеней. */
.vsp__range-cap.is-max { color: var(--vs-accent-dark); }

.vsp__more-box {
    flex: none; display: flex; align-items: center; justify-content: center;
    min-width: 96px; min-height: 32px;
}
.vsp .vsp__more {
    display: inline-flex; align-items: center; gap: .25rem;
    height: 32px; padding: 0 .65rem;
    background: transparent;
    border: 1.5px solid #cbbff2;
    border-radius: 999px;
    color: var(--vs-accent-dark); font-size: .74rem; font-weight: 700;
    white-space: nowrap; cursor: pointer;
    animation: vsp-pop .18s ease-out;
}
.vsp .vsp__more:hover { background: var(--vs-accent); border-color: var(--vs-accent); color: #fff; }
@keyframes vsp-pop { from { transform: scale(.7); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .vsp .vsp__more { animation: none; } }

/* ЗАМЕР КРАЯ ШКАЛЫ ИДЁТ — НА МЕСТЕ ЧИПА СПИННЕР. Ход замера неизвестен по природе (он играет
   кусок и смотрит на часы), поэтому процентов здесь нет и быть не может: врать цифрой нельзя.
   Ползунок на это время ЗАПЕРТ вместе со всей панелью (правка 18.09.2026, см. раздел
   «подготовка запирает инструмент» выше): шкала появляется один раз и сразу с верным краем. */
.vsp__spin { display: inline-grid; place-items: center; width: 32px; height: 32px; }
.vsp__spin > i {
    width: 18px; height: 18px; border-radius: 50%;
    border: 2px solid var(--vs-line);
    border-top-color: var(--vs-accent);
    animation: vsp-spin .8s linear infinite;
}
@keyframes vsp-spin { to { transform: rotate(360deg); } }
@keyframes vsp-wait {
    0%   { transform: translateX(-105%); }
    100% { transform: translateX(305%); }
}
@media (prefers-reduced-motion: reduce) { .vsp__spin > i { animation-duration: 2.4s; } }

/* ---------- Кнопка скачивания и ход сборки ---------- */
.vsp__dl { display: flex; align-items: center; gap: .5rem; padding: .6rem 1.15rem .9rem; }
.vsp .vsp__download {
    position: relative; overflow: hidden;
    width: 200px; height: 48px; padding: 0 1rem;
    font-size: .95rem;
}
.vsp .vsp__download:disabled { color: #fff; opacity: 1; }
.vsp__dl-text { position: relative; z-index: 1; display: inline-flex; align-items: center; gap: .5rem; }
.vsp__dl-prog { position: relative; z-index: 1; font-variant-numeric: tabular-nums; }
/* Полная подпись — только там, где кнопка во всю ширину (телефон); на столе она стоит в
   строке панели, и длинная фраза туда не влезает. */
.vsp__dl-long { display: none; }
.vsp .vsp__download.is-work .vsp__dl-text { display: none; }
/* НА ВРЕМЯ СБОРКИ КНОПКА СТАНОВИТСЯ ПОЛОСОЙ ХОДА НА СВОЁМ МЕСТЕ — той же геометрии, чтобы
   ничего не прыгало (правило «интерфейс не должен прыгать»). */
.vsp__dl-fill {
    position: absolute; left: 0; top: 0; bottom: 0;
    width: 0;
    background: rgba(255, 255, 255, .28);
    transition: width .25s ease;
}
@media (prefers-reduced-motion: reduce) { .vsp__dl-fill { transition: none; } }
.vsp .vsp__download.is-work { background: var(--vs-accent-dark); }
.vsp .vsp__cancel {
    display: inline-flex; align-items: center; gap: .3rem;
    height: 36px; padding: 0 .7rem;
    background: 0; border: 1px solid var(--vs-line); border-radius: var(--vs-radius-sm);
    color: var(--vs-muted); font-size: .8rem; cursor: pointer;
}
.vsp .vsp__cancel:hover { border-color: var(--vs-mark); color: var(--vs-mark); }

/* ====================================================================
   СТРОКА ПОД БЛОКОМ: один слот подсказки

   «Другой ролик» отсюда ушёл — он стоит у кнопки скачивания (решение владельца 19.09.2026),
   и в этой строке остался только слот.
   ==================================================================== */
.vsp__foot {
    display: flex; align-items: center; gap: 1rem;
    margin-top: .4rem;
    min-height: 0;
}
/* ПУСТОЙ СЛОТ СВОРАЧИВАЕТСЯ В НОЛЬ: пустая строка в 24 px ради редких сообщений — это
   потраченное место (разбор проекта, ЭТАП 6, пункт 6). */
.vsp__note {
    flex: 1 1 auto; min-width: 0;
    font-size: .8rem; color: var(--vs-muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    animation: vsp-slot .18s ease-out;
}
.vsp__note:empty { display: none; }
.vsp__note.is-err { color: #c0392b; }
@keyframes vsp-slot { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .vsp__note { animation: none; } }

/* «ДРУГОЙ РОЛИК» — РЯДОМ СО СКАЧИВАНИЕМ (решение владельца 19.09.2026: значок сброса в углу
   кадра убран, «лучше где-то у Download кнопку выбрать другое видео»). Кнопка не заливная:
   заливная на экране одна, и это Download. */
.vsp .vsp__again {
    flex: none;
    display: inline-flex; align-items: center; gap: .35rem;
    height: 44px; padding: 0 .9rem;
    border: 1px solid var(--vs-line); border-radius: var(--vs-radius-sm);
    background: 0; color: var(--vs-muted); font-size: .84rem; cursor: pointer;
}
.vsp .vsp__again:hover { border-color: var(--vs-accent); color: var(--vs-accent); }

/* ПЛЕЕР ЗАМЕРА живёт секунду-две при загрузке ролика и должен быть невидим человеку — но
   ВИДИМ КОМПОЗИТОРУ. Замер 15.09.2026: плеер вне дерева, с `opacity: 0` или накрытый чем-то
   непрозрачным браузер обслуживает иначе — выбрасывает кадры и держит часы, и замер врёт
   («устройство тянет всё»). Поэтому пиксель, а не ноль: `display`, `visibility`, `opacity: 0`
   и любое накрытие здесь запрещены. */
.vsp__probe {
    position: fixed; left: 0; top: 0; width: 1px; height: 1px;
    opacity: .02; pointer-events: none; z-index: 2147483647;
}

/* ---------- Ошибка ---------- */
.vsp__error { margin: .85rem 0 0; min-height: 0; color: #c0392b; font-size: .88rem; }
.vsp__error:empty { margin: 0; }

/* Самопроверка (?diag=1): технический вывод, обычному посетителю не показывается. */
.vsp__diag {
    margin: .6rem 0 0;
    padding: .6rem .7rem;
    font-size: .72rem;
    line-height: 1.35;
    white-space: pre-wrap;
    word-break: break-word;
    background: #f4f2fb;
    border: 1px solid #ddd6f3;
    border-radius: 8px;
    color: #2b2440;
}

/* ====================================================================
   ТЕЛЕФОН

   Ширины здесь нет, поэтому вкладка скорости раскладывается в два яруса (число над
   ползунком, кнопка верхних скоростей рядом), а кнопка скачивания прилипает к низу окна:
   палец достаёт до неё без прокрутки, пока блок инструмента на экране.
   ==================================================================== */
@media (max-width: 640px) {
    .vsp {
        --vs-frame-h: clamp(240px, 44vh, 420px);
        --vs-zone-h: 26px; --vs-marks-h: 20px; --vs-music-h: 20px;
        --vs-grab-w: 44px; --vs-min-piece: 6px; --vs-kill: 28px;
        --vs-hit: 44px;
    }
    .vsp__drop { padding: 2rem 1rem; }
    .vsp__drop-title { font-size: 1.3rem; }
    .vsp__features { grid-template-columns: 1fr; }
    /* Шапка на телефоне — как была у прежнего инструмента, до последнего пикселя: она видна
       только в `idle`, а стартовый экран не меняется. */
    .vsp__head { gap: .4rem; padding-bottom: .7rem; }
    .vsp__title { font-size: 1.02rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

    /* Блок — простой столбик: кнопка скачивания обязана липнуть в границах ВСЕГО блока,
       а `position: sticky` держится в границах родителя. */
    .vsp__block { display: block; }

    .vsp__play-big { width: 58px; height: 58px; font-size: 1.7rem; }
    /* Полоса управления на телефоне не прячется: наведения там нет, а тап по кадру должен
       играть/ставить на паузу, а не «показывать управление».
       Селектор ПОВТОРЯЕТ `:not(.is-hover)` из общего правила нарочно: без него он весит на
       один класс меньше и молча проигрывает — замер 18.09.2026 показал, что на телефоне полоса
       всё-таки пряталась, то есть правило не работало с тех пор, как было написано. */
    .vsp.is-playing:not(.is-hover) .vsp__controls { opacity: 1; pointer-events: auto; }
    .vsp__controls { padding: 1.3rem .4rem .35rem; }
    .vsp .vsp__ctl { width: 38px; height: 38px; }
    .vsp__seek { height: 20px; }
    .vsp__grip i { width: 13px; height: 24px; }
    .vsp__pen-bar { right: 8px; top: 8px; }

    /* МОНТАЖНЫЙ ТАЙМЛАЙН: органы крупнее пальца, ползунок приближения занимает остаток строки.
       Боковые поля — как у полосы плеера на телефоне (.4rem), см. правило на столе. */
    .vsp__tl { padding: .4rem .4rem .35rem; }
    .vsp .vsp__tlb { width: 40px; height: 40px; font-size: .95rem; }
    .vsp .vsp__tl-row .vsp__zrange { flex: 1 1 auto; }
    .vsp__tl-row .vsp__zoom-val { min-width: 40px; }

    /* ВКЛАДКИ: полоса вкладок на всю ширину, по трети на каждую — подпись и значок должны
       помещаться без переноса на 360 px. */
    .vsp__tabs { padding: 0 .8rem .2rem; }
    .vsp .vsp__tab { flex: 1 1 0; justify-content: center; padding: .35rem .3rem; font-size: .82rem; }
    /* Вкладка скорости в два яруса: число над ползунком, кнопка верхних скоростей справа. */
    .vsp__pane--speed { grid-template-columns: minmax(0, 1fr) auto; gap: .3rem .5rem; }
    /* Бегунок крупнее: палец шире мыши (таблица финальных размеров проекта — 28 против 24). */
    .vsp .vsp__range::-webkit-slider-thumb { width: 28px; height: 28px; margin-top: -11px; }
    .vsp .vsp__range::-moz-range-thumb { width: 28px; height: 28px; }
    .vsp .vsp__range { height: 28px; }
    .vsp__speed-value { font-size: 1.75rem; }
    .vsp .vsp__mselect { flex: 1 1 100%; }

    /* КНОПКА СКАЧИВАНИЯ ЛИПНЕТ К НИЗУ ОКНА, пока блок инструмента на экране, и остаётся в
       потоке, когда он ушёл выше. Вкладкам дан нижний отступ, иначе липкая кнопка накрывала бы
       их содержимое (разбор проекта, ЭТАП 6, пункт 8). */
    .vsp__tabs { padding-bottom: .5rem; }
    .vsp__dl {
        position: sticky; bottom: 0;
        padding: .5rem .8rem calc(.5rem + env(safe-area-inset-bottom, 0px));
        background: var(--vs-bg);
    }
    /* Скачивание занимает строку, «другой ролик» — квадратной кнопкой рядом: на телефоне для
       двух подписанных кнопок в строке места нет, а действие «взять другой ролик» редкое. */
    .vsp .vsp__download { flex: 1 1 auto; width: auto; height: 52px; }
    .vsp .vsp__again { height: 52px; padding: 0 .7rem; }
    .vsp .vsp__again span { display: none; }
    .vsp__dl-short { display: none; }
    .vsp__dl-long { display: inline; }
    .vsp .vsp__cancel { height: 44px; }
    .vsp__foot { padding: 0 .8rem; }
}

/* Ландшафт телефона: высоты нет, ширина есть.
   ЛИПКОСТЬ КНОПКИ ЗДЕСЬ ТЕПЕРЬ ОСТАЁТСЯ. Прежде она снималась («иначе кнопка съедает треть
   экрана»), и это работало, пока весь инструмент был кадром и одной строкой панели. С
   раскладки v3 под кадром стоят ещё два яруса — таймлайн и вкладки, — и без липкости кнопка
   скачивания уезжала за нижний край окна: замер гейта `_vspeed_v7_layout.js` 19.09.2026 —
   низ кнопки 526 при окне 390. Кадр взамен ужат: в ландшафте важнее дотянуться до кнопки,
   чем выиграть полтора сантиметра картинки. */
@media (max-width: 960px) and (max-height: 500px) and (orientation: landscape) {
    .vsp { --vs-frame-h: 46vh; }
    .vsp__block { display: grid; }
    .vsp__dl { padding: .45rem .9rem calc(.45rem + env(safe-area-inset-bottom, 0px)); }
    .vsp .vsp__download { flex: 1 1 auto; width: auto; height: 44px; }
    .vsp .vsp__again { height: 44px; }
    .vsp__pane { padding: .45rem 0 .15rem; }
}

@media (max-width: 400px) {
    .vsp__tabs { padding-left: .4rem; padding-right: .4rem; }
    .vsp__dl { padding-left: .4rem; padding-right: .4rem; }
    .vsp__foot { padding: 0 .4rem; }
    .vsp__tl { padding-left: .35rem; padding-right: .35rem; }
    .vsp .vsp__tab { font-size: .78rem; gap: .25rem; }
}

/* ====================================================================
   МОНТАЖНЫЙ ТАЙМЛАЙН — ПОД КАДРОМ, НА ТЁМНОЙ ПОЛОСЕ

   Решение владельца 19.09.2026: «таймлайн и дорожки должны быть под плеером и не закрывать
   видео», «кнопка отмены и зума тоже уходят вниз — это часть редакторского таймлайна».
   Полоса ТЁМНАЯ и продолжает кадр: так монтажка читается как одно целое с картинкой, а все
   цвета дорожек, ручек, иглы и рисок остаются теми же, что были внутри кадра, — светлым по
   тёмному. Светлая подложка потребовала бы перекрасить каждый из них, и это была бы
   переделка ради переделки.
   ==================================================================== */
.vsp__tl {
    grid-area: tl;
    position: relative;
    /* БОКОВЫЕ ПОЛЯ — РОВНО КАК У ПОЛОСЫ ПЛЕЕРА (`.vsp__controls`). Полоса и дорожки теперь в
       разных ярусах и живут в разном времени (полоса — весь ролик, дорожки — окно
       приближения), но выровнены они обязаны быть по пикселю: разъехавшиеся края читаются
       как ошибка вёрстки. Проверяется гейтом `_vspeed_v7_layout.js` с допуском 2 px. */
    padding: .35rem .6rem .3rem;
    background: var(--vs-tl-bg);
    /* Щипок и колесо приближают ВЕСЬ таймлайн, поэтому жест перехватывается всей полосой, а
       не одной дорожкой в 20 px: двумя пальцами в такую дорожку попасть невозможно — ровно
       поэтому щипок «не наблюдался» (жалоба владельца 19.09.2026). */
    touch-action: none;
    user-select: none; -webkit-user-select: none;
}
/* Ряд органов монтажки: отмена слева, приближение справа. */
.vsp__tl-row { display: flex; align-items: center; gap: .25rem; margin-top: .25rem; }
.vsp__tl-gap { flex: 1 1 auto; }
.vsp .vsp__tlb {
    flex: none;
    display: inline-grid; place-items: center;
    width: 30px; height: 30px; padding: 0;
    border: 0; border-radius: 7px;
    background: rgba(255, 255, 255, .12); color: #fff;
    font-size: .82rem; cursor: pointer;
}
.vsp .vsp__tlb:hover:not([disabled]) { background: rgba(255, 255, 255, .26); }
.vsp .vsp__tlb[disabled] { opacity: .35; cursor: default; }
.vsp .vsp__tl-row .vsp__zrange { flex: 0 1 180px; }
.vsp__tl-row .vsp__zoom-val {
    flex: none; min-width: 42px; text-align: right;
    color: rgba(255, 255, 255, .8);
    font-size: .72rem; font-weight: 700; font-variant-numeric: tabular-nums;
}

/* ====================================================================
   ТРИ ВКЛАДКИ: СКОРОСТЬ · ЗВУК · ПОМЕТКИ
   ==================================================================== */
.vsp__tabs { grid-area: tabs; padding: 0 1.15rem .2rem; }
.vsp__tabbar {
    display: flex; gap: .15rem;
    border-bottom: 1px solid var(--vs-line);
}
.vsp .vsp__tab {
    display: inline-flex; align-items: center; gap: .35rem;
    min-height: var(--vs-hit); padding: .35rem .8rem;
    border: 0; border-bottom: 2px solid transparent;
    background: 0; color: var(--vs-muted);
    font-size: .86rem; font-weight: 600; cursor: pointer;
}
.vsp .vsp__tab:hover { color: var(--vs-text); }
.vsp .vsp__tab[aria-selected="true"] {
    color: var(--vs-accent-dark);
    border-bottom-color: var(--vs-accent);
}
.vsp__pane { padding: .7rem 0 .2rem; }

/* Вкладка скорости: крупное число НАД ползунком (решение владельца 19.09.2026). */
.vsp__pane--speed { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .4rem .8rem; align-items: center; }
.vsp__speed-head { grid-column: 1; justify-self: center; line-height: 1; }
.vsp__scale { grid-column: 1; min-width: 0; }
.vsp__more-box { grid-column: 2; grid-row: 1 / span 2; }

/* Вкладка звука. */
.vsp__pane--sound { display: flex; flex-direction: column; gap: .55rem; }
.vsp__check { display: inline-flex; align-items: center; gap: .45rem; margin: 0; cursor: pointer; font-size: .88rem; }
.vsp .vsp__check input { flex: none; margin: 0; cursor: pointer; }
.vsp__musrow { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.vsp .vsp__mselect { flex: 0 1 260px; min-width: 0; height: 36px; padding: 0 2rem 0 .6rem; font-size: .85rem; }
.vsp .vsp__mselect:disabled { opacity: .55; }
.vsp .vsp__mlisten { background: var(--vs-soft); color: var(--vs-accent-dark); }
.vsp .vsp__mlisten.is-playing { background: var(--vs-music); color: #fff; }

/* Вкладка пометок: кисть и подсказка. */
.vsp__pane--marks { display: flex; flex-direction: column; gap: .5rem; }
.vsp__brush { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.vsp__brush-cap { flex: none; min-width: 72px; font-size: .8rem; color: var(--vs-muted); }
.vsp__widths, .vsp__colors { display: flex; align-items: center; gap: .3rem; }
.vsp .vsp__width {
    display: inline-grid; place-items: center;
    width: var(--vs-hit); height: var(--vs-hit);
    border: 0; background: 0; padding: 0; cursor: pointer; border-radius: 8px;
}
.vsp .vsp__width:hover { background: var(--vs-soft); }
.vsp .vsp__width[aria-pressed="true"] { background: var(--vs-soft); }
.vsp .vsp__width i {
    width: var(--vs-dot); height: var(--vs-dot);
    border-radius: 50%; background: var(--vs-text);
}
.vsp .vsp__width[aria-pressed="true"] i { background: var(--vs-accent); }
.vsp .vsp__color {
    width: 28px; height: 28px; padding: 0; cursor: pointer;
    border: 2px solid #fff; border-radius: 50%;
    background: var(--vs-swatch);
    box-shadow: 0 0 0 1px var(--vs-line);
}
.vsp .vsp__color[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--vs-accent); }
.vsp__brush-hint { margin: 0; font-size: .8rem; color: var(--vs-muted); }

/* ====================================================================
   ХОД СБОРКИ «ЕЩЁ БЫСТРЕЕ» — НА КАДРЕ, С РАКЕТОЙ
   ==================================================================== */
/* КАДР ПОД ЭТИМ ОВЕРЛЕЕМ НЕ ТУСКНЕЕТ. Общее правило «идёт сборка — кадр тускнеет» гасит весь
   кадр ЦЕЛИКОМ, вместе с тем, что на нём нарисовано: ракета и полоса выцветали до
   нечитаемого (скрин 19.09.2026). Здесь работа показана самим оверлеем, и притенять картинку
   второй раз незачем. */
.vsp.is-boost .vsp__frame { opacity: 1; }
.vsp__boost {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    display: flex; flex-direction: column; align-items: center; gap: .45rem;
    width: min(340px, 86%);
    padding: 1rem 1.2rem;
    /* Подложка ПЛОТНАЯ: полупрозрачную поверх видео Chrome не блендит, она мерцает
       (.tool_conventions/for_webcam.md, то же правило, что у надписи «картинка стоит»). */
    background: #1a1428;
    border-radius: var(--vs-radius);
    color: #fff; text-align: center;
    pointer-events: none;
    z-index: 6;
}
.vsp__boost-ic { font-size: 1.9rem; color: var(--vs-accent-2); animation: vsp-boost 1.6s ease-in-out infinite; }
@keyframes vsp-boost { 0%, 100% { transform: translateY(2px); } 50% { transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) { .vsp__boost-ic { animation: none; } }
.vsp__boost-text { font-size: .88rem; font-weight: 600; line-height: 1.25; }
.vsp__boost-bar {
    display: block; width: 100%; height: 6px; border-radius: 999px;
    background: rgba(255, 255, 255, .22); overflow: hidden;
}
.vsp__boost-fill { display: block; height: 100%; width: 0; background: var(--vs-accent-2); transition: width .25s ease; }
.vsp__boost-pct { font-size: .78rem; font-weight: 700; font-variant-numeric: tabular-nums; opacity: .85; }
@media (prefers-reduced-motion: reduce) { .vsp__boost-fill { transition: none; } }
