/* ---------------------------------------------------------------------------
   OBS-виджет заказа треков.

   Стили сознательно самодостаточные и не опираются на темы панели: у документа
   браузер-источника нет ни класса light-theme, ни dark-theme, а фон должен остаться
   прозрачным при любой теме.

   prefers-reduced-motion здесь не поддерживается намеренно: это рендерится в OBS
   для зрителей стрима, а не человеку, читающему страницу.
   --------------------------------------------------------------------------- */

/* --- документ оверлея ---------------------------------------------------- */

html.media-widget,
html.media-widget body {
    background: transparent !important;
    margin: 0;
    padding: 0;
    overflow: hidden;
    -webkit-font-smoothing: antialiased;
}

html.media-widget ::-webkit-scrollbar {
    display: none;
}

/* Свой элемент реконнекта: пока он есть, blazor.web.js не вставит собственный видимый диалог. */
.mw-reconnect {
    display: none !important;
}

/* --- корень и размещение -------------------------------------------------- */

/*
   Виджет подстраивается под размер браузер-источника, а не рисуется в фиксированных пикселях.

   Почему так. Совет «сделайте источник 1920x1080» вреден: плеер ставят в маленькое окошко
   в углу сцены, а сжатие 1080p-источника средствами OBS — это пересэмплинг уже отрисованной
   картинки, от которого текст превращается в кашу. Правильный путь обратный: стример задаёт
   источнику тот размер, который ему нужен, а виджет заполняет его собой и рисует текст
   сразу в нужном кегле — векторно и чётко.

   Механика. Вся геометрия скинов выражена в юнитах var(--u); 1 юнит = 16 px исходного макета,
   поэтому пропорции остались ровно теми же. Юнит считается из размера источника: берём
   меньшее из «высота источника на высоту скина» и «ширина на ширину», то есть вписываем
   натуральный макет в коробку. Пол и потолок не дают выродиться в нечитаемую крошку
   на крохотном источнике и в гигантские буквы на полноэкранном.
*/
.mw-root {
    /* Натуральный размер скина в юнитах. Каждый скин переопределяет под себя. */
    --mw-uw: 26.25;
    --mw-uh: 8;

    /*
       Размеры коробки, в которую вписывается макет, и границы юнита — отдельными переменными.
       В бою это сам источник, в предпросмотре — карточка настроек: там достаточно подменить
       эти три строки вместо того, чтобы переписывать формулу целиком и держать две её копии.
    */
    --mw-vw: 100vw;
    --mw-vh: 100vh;
    --mw-umin: 6px;
    --mw-umax: 28px;

    /* Юнит, при котором натуральный макет ровно вписан в коробку. Потолок для скина с видео. */
    --mw-ufit: min(var(--mw-vh) / var(--mw-uh), var(--mw-vw) / var(--mw-uw));

    /*
       Пользовательский Scale — множитель самого юнита, а не отдельный zoom или transform.
       transform здесь не работал вовсе: у каждого скина есть анимация появления со своим
       transform, а анимация в каскаде всегда бьёт обычное объявление, и множитель молча
       терялся. zoom же масштабировал бы и сам корень — он растянут на весь источник
       через inset: 0, и виджет уезжал бы за край. Юнит свободен от обеих проблем.
    */
    --u: calc(var(--mw-scale) * clamp(var(--mw-umin), var(--mw-ufit), var(--mw-umax)));

    /* Масштаб бегущей строки. Приходит из настроек, у остальных скинов не участвует. */
    --mw-tk: 1;

    --mw-scale: 1;
    --mw-accent: #9146ff;
    --mw-radius: calc(0.875 * var(--u));
    --mw-surface: rgba(18, 18, 24, .82);
    --mw-text: #ffffff;
    --mw-muted: rgba(255, 255, 255, .62);

    position: fixed;
    inset: 0;
    display: flex;
    padding: calc(0.5 * var(--u));
    box-sizing: border-box;
    pointer-events: none;
    font-family: 'Lexend', system-ui, sans-serif;
    color: var(--mw-text);
    z-index: 1;
}

/*
   Натуральные габариты скинов. Обе стороны — с учётом отступов корня (по полюнита с каждой),
   иначе виджет вылезал бы за край невысокого или узкого источника.

   Числа не выводятся из значений ниже по файлу, а сняты замером собранной карточки: строки
   текста добирают высоту метриками шрифта и наследованным line-height, а не суммой явных
   размеров. Поэтому сверять их надо тоже замером — считая по CSS, легко получить красивое,
   но не то число: так у всплывающего скина потерялась целая строка «заказал такой-то», и на
   невысоком источнике он обрезался снизу.
*/
.mw-skin-miniplayer {
    --mw-uw: 27.25;
    --mw-uh: 7.7;
}

/*
   Высота плашки приходит из настроек, поэтому натуральная высота считается в MediaWidgetStyle
   и приезжает готовым числом. Здесь — значение по умолчанию: плашка 3 юнита плюс отступы корня.
*/
.mw-skin-compactbar {
    --mw-uw: 39.75;
    --mw-uh: 4;
}

/* Высоту задаёт пластинка: она занимает карточку целиком и образует её левый торец. */
.mw-skin-vinyl {
    --mw-uw: 32.1;
    --mw-uh: 9.6;
}

.mw-skin-ambientcard {
    --mw-uw: 28.5;
    --mw-uh: 11.75;
}

/* Четыре строки: метка, название, автор и «заказал» — последняя включена по умолчанию. */
.mw-skin-toast {
    --mw-uw: 24.75;
    --mw-uh: 7.9;
}

/*
   Скин с видео считается по своей формуле:

       --mw-uh = 32 * 9 / 16 (кадр 16:9) + 2.5625 * --mw-tk (строка) + 1 (отступы корня)

   При масштабе строки 100 % это 21.5625 — значение ниже. При любом другом --mw-uh приходит
   уже посчитанным числом из MediaWidgetStyle: держать здесь calc() нельзя, потому что
   деление длины на calc() появилось только в CSS Values 4, а CEF в сборках OBS бывает
   заметно старше движка, в котором это разрешено. Число делится везде.
*/
.mw-skin-videoticker {
    --mw-uw: 32;
    --mw-uh: 21.5625;

    /*
       Единственный скин, которому Scale не даёт вылезти за источник. Причина не в аккуратности,
       а в арифметике: строка меряется в том же юните, что и кадр, поэтому за точкой, где карточка
       уже заполнила источник, дальнейший рост юнита раздувал бы только строку — и она отъедала бы
       высоту у кадра. Scale 300 % давал кадр меньше, чем Scale 200 %. С потолком карточка растёт
       целиком и останавливается, заполнив источник.
    */
    --u: min(calc(var(--mw-scale) * clamp(var(--mw-umin), var(--mw-ufit), var(--mw-umax))),
    var(--mw-ufit));

    /* Юнит бегущей строки: тот же масштаб, что у всего виджета, плюс собственный множитель. */
    --u-tk: calc(var(--u) * var(--mw-tk));

    /* Высота строки в пикселях — сумма её вертикальных размеров ниже по файлу. */
    --mw-tick-h: calc(2.5625 * var(--u-tk));
}

/*
   align-content здесь ради скина с видео: он единственный раскладывается гридом, а гриду
   вертикальную привязку задаёт именно align-content, а не align-items. Остальным скинам это
   свойство ничего не стоит — во флексбоксе без переноса строк оно не действует.
*/
.mw-pos-topleft {
    align-items: flex-start;
    align-content: start;
    justify-content: flex-start;
}

.mw-pos-topcenter {
    align-items: flex-start;
    align-content: start;
    justify-content: center;
}

.mw-pos-topright {
    align-items: flex-start;
    align-content: start;
    justify-content: flex-end;
}

.mw-pos-middleleft {
    align-items: center;
    align-content: center;
    justify-content: flex-start;
}

.mw-pos-middlecenter {
    align-items: center;
    align-content: center;
    justify-content: center;
}

.mw-pos-middleright {
    align-items: center;
    align-content: center;
    justify-content: flex-end;
}

.mw-pos-bottomleft {
    align-items: flex-end;
    align-content: end;
    justify-content: flex-start;
}

.mw-pos-bottomcenter {
    align-items: flex-end;
    align-content: end;
    justify-content: center;
}

.mw-pos-bottomright {
    align-items: flex-end;
    align-content: end;
    justify-content: flex-end;
}

/* transform-origin по углам больше не нужен: масштаб теперь заложен в юнит и меняет саму
   раскладку, поэтому виджет остаётся прижатым к своему углу без всяких точек отсчёта. */

/* --- обложки -------------------------------------------------------------- */

/*
   Обложки — блочные, а не строчные.

   img по умолчанию строчный, то есть стоит на базовой линии строки и обязан оставить под собой
   место под нижние выносные элементы. Высоту строки задаёт не виджет: страница панели и документ
   браузер-источника грузят bootstrap, а он ставит line-height 1.5 при шрифте 16 px — это строка
   в 24 px. Пока обложка выше этой строки, разницу съедает overflow: hidden и ничего не заметно.
   Как только обложка становится ниже — а в предпросмотре и на небольшом источнике юнит мелкий,
   и обложка тонкой плашки выходит всего 13 px — картинка съезжает вниз на семь пикселей, то есть
   больше чем на половину своей высоты, и от неё остаётся видна одна макушка.

   Отсюда общее правило: блочная картинка не участвует в раскладке строки вовсе и лежит ровно
   в своей коробке при любом размере виджета.
*/
.mw-mini-cover img,
.mw-bar-cover img,
.mw-vinyl-cover img,
.mw-ambient-cover img,
.mw-toast-cover img {
    display: block;
}

/* --- плеер ---------------------------------------------------------------- */

.mw-player-host iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

/*
   Скрытый плеер — не display:none и не visibility:hidden: политика воспроизведения CEF
   считает полностью скрытый плеер фоновым и глушит его. Элемент остаётся отрисованным,
   просто в один пиксель за краем экрана.
*/
.mw-player-hidden {
    position: fixed;
    left: -9999px;
    top: 0;
    width: 1px;
    height: 1px;
    opacity: .01;
    pointer-events: none;
}

.mw-player-visible {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: var(--mw-radius) var(--mw-radius) 0 0;
    background: #000;
}

/* --- скин 1: видео + бегущая строка --------------------------------------- */

/*
   Раскладка «видео сверху, строка снизу» собирается здесь, а не внутри скина: плеер обязан
   оставаться на одном месте дерева при любом скине, иначе переключение пересоздало бы iframe.

   Грид, а не колоночный флекс: во флексе с flex-direction: column оси у .mw-pos-* меняются
   местами, и девять позиций пришлось бы дублировать со swap'ом значений. Гриду хватает
   align-content, добавленного к тем же девяти правилам.

   Ширина колонки — единственный размер, который здесь задаётся: высоту кадра выводит
   aspect-ratio, поэтому 16:9 не может сломаться ни от строки, ни от масштаба. Три ограничения
   в min():
     1) натуральная ширина карточки в юнитах — обычный размер, как у прочих скинов;
     2) ширина источника: отступы корня заложены только в --mw-uh, по ширине карточка
        упирается именно сюда;
     3) высота источника за вычетом строки и отступов, переведённая в ширину через 16/9.
   Третье при потолке юнита выше не должно срабатывать вовсе — оно оставлено страховкой на
   случай, если 2.5625 в размерах строки разойдётся с тем же числом в --mw-uh: тогда съедет
   размер, но строка всё равно не уедет за нижний край. Кадр в любом случае остаётся целым:
   видео нигде не обрезается, а вписывается.
*/
.mw-root.mw-skin-videoticker {
    display: grid;
    grid-template-columns: min(
        calc(var(--mw-uw) * var(--u)),
        100%,
        calc((var(--mw-vh) - var(--mw-tick-h) - var(--u)) * 16 / 9)
    );
    grid-auto-rows: min-content;
}

/*
   Всё внутри строки меряется в --u-tk, а не в --u: ползунок высоты обязан двигать только её.
   Сумма вертикальных размеров — 0.125 (рамка) + 2 × 0.5 (отступы) + 1 × 1.25 (строка текста)
   + 0.1875 (прогресс) = 2.5625 юнита. Это же число стоит в --mw-tick-h и в --mw-uh, так что
   менять любой из размеров ниже можно только вместе с ними.
*/
.mw-ticker {
    width: 100%;
    background: linear-gradient(90deg, rgba(0, 0, 0, .92), rgba(0, 0, 0, .78));
    border-top: calc(0.125 * var(--u-tk)) solid var(--mw-accent);

    /* Нижние углы под верхние у кадра: вместе читается как одна карточка. */
    border-radius: 0 0 var(--mw-radius) var(--mw-radius);
    overflow: hidden;
    animation: mw-slide-in-up 420ms cubic-bezier(.22, 1, .36, 1) both;
}

.mw-ticker-strip {
    overflow: hidden;
    padding: calc(0.5 * var(--u-tk)) 0;
    white-space: nowrap;
}

/*
   Края бегущей строки растворяются, а не обрываются. Без этого второй кусок строки — тот, что
   едет следом ради бесшовной петли, — выныривает у правого края половиной буквы, и она читается
   как случайный мусор поверх виджета.
*/
.mw-ticker-strip,
.mw-bar-strip,
.mw-run {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%);
}

/* Блочный флекс, а не inline-flex: у строчного бокса высоту добирал бы ещё и «распорка»
   родителя со своим кеглем, и полоса выходила бы выше расчётной. */
.mw-ticker-track {
    display: flex;
    width: max-content;
    animation: mw-marquee var(--mw-ticker-dur, 20s) linear infinite;

    /*
       Дорожка должна жить в отдельном композиторном слое. Без явной подсказки CEF в OBS
       иногда совмещает её с маской, текстовой тенью и обновляемым прогресс-баром, из-за чего
       каждый серверный патч заставляет строку пропускать кадры.
    */
    will-change: transform;
    backface-visibility: hidden;
}

/* line-height задан явно: без него высота строки зависела бы от метрик шрифта и разошлась
   бы с 2.5625, из которых посчитана натуральная высота скина. */
.mw-ticker-chunk {
    padding-right: calc(4 * var(--u-tk));
    font-size: var(--u-tk);
    line-height: 1.25;
    font-weight: 500;
    letter-spacing: .01em;
    text-shadow: 0 calc(0.125 * var(--u-tk)) calc(0.375 * var(--u-tk)) rgba(0, 0, 0, .8);
}

.mw-ticker-progress {
    height: calc(0.1875 * var(--u-tk));
    background: rgba(255, 255, 255, .12);
}

.mw-ticker-progress > span {
    display: block;
    height: 100%;
    background: var(--mw-accent);
    transition: width 250ms linear;
}

/* Заглушка кадра в предпросмотре. Пропорции ей задавать не нужно: 16:9 держит .mw-player-visible,
   в который она вложена, — заглушка просто заполняет его целиком. */
.mw-ticker-placeholder {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    background: #0b0b10;
    color: rgba(255, 255, 255, .55);
    overflow: hidden;
}

.mw-ticker-placeholder-cover {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: .45;
}

.mw-ticker-placeholder svg {
    position: relative;
}

/* --- скин 2: мини-плеер --------------------------------------------------- */

.mw-mini {
    position: relative;
    display: grid;
    grid-template-columns: auto 1fr;
    gap: calc(0.875 * var(--u));
    align-items: center;
    width: calc(26.25 * var(--u));
    max-width: 100%;
    padding: calc(0.875 * var(--u)) calc(1 * var(--u)) calc(1.125 * var(--u));
    border-radius: var(--mw-radius);
    background: var(--mw-surface);
    border: 1px solid rgba(255, 255, 255, .08);
    overflow: hidden;
    animation: mw-slide-in-up 420ms cubic-bezier(.22, 1, .36, 1) both;
}

.mw-mini::before {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: calc(0.1875 * var(--u));
    background: var(--mw-accent);
}

/*
   Обложка в мини-плеере. Соотношение 16:9 — ровно то, в котором приходит превью с YouTube,
   поэтому object-fit ничего не режет и кадр виден целиком.
*/
.mw-mini-cover {
    position: relative;
    width: calc(5.5 * var(--u));
    height: calc(3.125 * var(--u));
    border-radius: calc(0.5 * var(--u));
    overflow: hidden;
    background: rgba(255, 255, 255, .06);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .1);
    color: var(--mw-muted);
    flex: none;
}

/* Заглушка и обложка лежат друг на друге: если картинка не загрузилась, она прячет себя
   через onerror, и под ней остаётся значок ноты вместо значка битого изображения. */
.mw-mini-cover img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.mw-mini-cover svg {
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(1.375 * var(--u));
    height: calc(1.375 * var(--u));
    transform: translate(-50%, -50%);
}

.mw-mini-body {
    min-width: 0;
}

.mw-mini-title {
    font-size: calc(1.05 * var(--u));
    font-weight: 600;
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mw-mini-author {
    margin-top: calc(0.125 * var(--u));
    font-size: calc(.85 * var(--u));
    color: var(--mw-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mw-mini-meta {
    display: flex;
    align-items: center;
    gap: calc(0.625 * var(--u));
    margin-top: calc(0.5 * var(--u));
    font-size: calc(.8 * var(--u));
    color: var(--mw-muted);
}

.mw-mini-requester {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mw-mini-requester::before {
    content: '· ';
}

.mw-mini-progress {
    grid-column: 1 / -1;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(0.25 * var(--u));
    background: rgba(255, 255, 255, .1);
}

.mw-mini-progress > span,
.mw-bar-progress > span,
.mw-ambient-progress > span {
    display: block;
    height: 100%;
    width: calc(var(--mw-progress, 0) * 100%);
    background: var(--mw-accent);
    transition: width 250ms linear;
}

/* --- скин 3: тонкая плашка ------------------------------------------------ */

.mw-bar {
    position: relative;
    display: flex;
    align-items: center;
    gap: calc(0.75 * var(--u));
    height: var(--mw-bar-h, calc(3 * var(--u)));
    width: calc(38.75 * var(--u));
    max-width: 100%;
    padding: 0 calc(1 * var(--u));
    border-radius: calc(var(--mw-bar-h, calc(3 * var(--u))) / 2);
    background: var(--mw-surface);
    border: 1px solid rgba(255, 255, 255, .08);
    overflow: hidden;
    animation: mw-slide-in-up 380ms cubic-bezier(.22, 1, .36, 1) both;
}

/*
   Обложка — доля высоты плашки, а не высота минус юнит: у самой низкой плашки вычитание
   съедало почти всё, и от обложки оставалась точка в дюжину пикселей.
*/
.mw-bar-cover {
    flex: none;
    width: calc(0.7 * var(--mw-bar-h, calc(3 * var(--u))));
    height: calc(0.7 * var(--mw-bar-h, calc(3 * var(--u))));
    border-radius: calc(0.15 * var(--mw-bar-h, calc(3 * var(--u))));
    overflow: hidden;
}

.mw-bar-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.mw-bar-strip {
    flex: 1 1 auto;
    overflow: hidden;
    white-space: nowrap;
    min-width: 0;
}

.mw-bar-strip .mw-ticker-chunk {
    font-size: calc(.95 * var(--u));
    padding-right: calc(3 * var(--u));
}

.mw-bar-time {
    flex: none;
    font-size: calc(.8 * var(--u));
    color: var(--mw-muted);
    font-variant-numeric: tabular-nums;
}

.mw-bar-progress {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(0.125 * var(--u));
    background: rgba(255, 255, 255, .12);
}

/* --- скин 4: пластинка ---------------------------------------------------- */

/*
   Слева и по вертикали отступа нет намеренно: пластинка занимает всю высоту карточки и сама
   образует её левый торец. Радиус в пять юнитов браузер всё равно урежет до половины высоты —
   левый край становится ровно полуокружностью, в которую пластинка вписана без зазора.
*/
.mw-vinyl {
    display: flex;
    align-items: center;
    gap: calc(1.125 * var(--u));
    padding: 0 calc(1.375 * var(--u)) 0 0;
    border-radius: calc(5 * var(--u));
    background: var(--mw-surface);
    border: 1px solid rgba(255, 255, 255, .08);
    animation: mw-slide-in-up 460ms cubic-bezier(.22, 1, .36, 1) both;
}

/* Размер пластинки задаёт высоту всей карточки: она здесь самый высокий элемент. */
.mw-vinyl-disc {
    position: relative;
    width: calc(8.5 * var(--u));
    height: calc(8.5 * var(--u));
    flex: none;
}

/*
   Дорожки и блик поверх пластинки. Отдельным слоем и без вращения: дорожки концентрические,
   от поворота они не меняются, а неподвижный блик — единственное, по чему вообще заметно,
   что пластинка крутится. Заодно дешевле — вращается одна обложка, а этот слой браузер
   рисует единожды.
*/
.mw-vinyl-disc::after {
    content: '';
    position: absolute;
    inset: 4.1667%;
    border-radius: 50%;
    pointer-events: none;
    background: repeating-radial-gradient(circle at 50% 50%,
    rgba(255, 255, 255, .02) 0 calc(0.0625 * var(--u)),
    rgba(0, 0, 0, .045) calc(0.0625 * var(--u)) calc(0.1875 * var(--u))),
    linear-gradient(125deg,
            transparent 34%,
            rgba(255, 255, 255, .1) 47%,
            transparent 58%);
}

.mw-vinyl-ring {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
}

.mw-vinyl-ring circle {
    fill: none;
    stroke-width: 5;
    /* 2 * pi * 57.5 — длина окружности кольца из разметки. */
    stroke-dasharray: 361.28;
}

.mw-vinyl-ring-track {
    stroke: rgba(255, 255, 255, .14);
}

.mw-vinyl-ring-value {
    stroke: var(--mw-accent);
    stroke-linecap: round;
    stroke-dashoffset: calc(361.28 * (1 - var(--mw-progress, 0)));
    transition: stroke-dashoffset 250ms linear;
}

/*
   Обложка занимает всё, что осталось внутри кольца прогресса, — зазора между ними нет.
   Отступ задан долей, а не юнитами: кольцо нарисовано в собственных координатах svg
   (радиус 57.5 при половине коробки в 60 и толщине 5), и доля повторяет эту пропорцию
   при любом размере пластинки — 5/60 от половины коробки, то есть 1/24 от неё самой.
*/
.mw-vinyl-cover {
    position: absolute;
    inset: 4.1667%;
    border-radius: 50%;
    overflow: hidden;
    background: #14141a;
    display: grid;
    place-items: center;
    box-shadow: inset 0 0 0 calc(0.5 * var(--u)) rgba(0, 0, 0, .55);
}

/*
   Без кольца прогресса — а его нет, когда позиция неизвестна, — пластинка занимает диск
   целиком. Иначе освободившаяся полоса читалась бы как пустой ободок вокруг обложки.
*/
.mw-vinyl-disc.is-bare::after,
.mw-vinyl-disc.is-bare .mw-vinyl-cover {
    inset: 0;
}

.mw-vinyl-cover.is-spinning {
    animation: mw-spin calc(60s / var(--mw-rpm, 12)) linear infinite;
}

.mw-vinyl-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Доля, а не юниты: отверстие обязано расти вместе с пластинкой, иначе на большой оно теряется. */
.mw-vinyl-hole {
    position: absolute;
    width: 16%;
    height: 16%;
    border-radius: 50%;
    background: #0b0b10;
    box-shadow: 0 0 0 calc(0.25 * var(--u)) rgba(255, 255, 255, .12);
}

/*
   Ширина фиксированная, а не по содержимому. Пластинка — единственный скин без своей ширины:
   тело растягивалось под название, и карточка на каждом треке меняла размер, а на длинном
   ещё и переставала совпадать с натуральными габаритами, по которым считается вписывание.
   Длинное название теперь не растягивает карточку, а едет строкой.
*/
.mw-vinyl-body {
    min-width: 0;
    width: calc(20 * var(--u));
    animation: mw-fade-in 500ms ease both;
}

.mw-vinyl-title {
    font-size: calc(1.05 * var(--u));
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mw-vinyl-author {
    font-size: calc(.85 * var(--u));
    color: var(--mw-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mw-vinyl-time {
    margin-top: calc(0.375 * var(--u));
    font-size: calc(.8 * var(--u));
    color: var(--mw-muted);
    font-variant-numeric: tabular-nums;
}

.mw-vinyl-requester {
    font-size: calc(.78 * var(--u));
    color: var(--mw-accent);
}

/* --- скин 5: карточка с размытой обложкой --------------------------------- */

.mw-ambient {
    position: relative;
    width: calc(27.5 * var(--u));
    max-width: 100%;
    padding: calc(1.125 * var(--u));
    border-radius: calc(1.125 * var(--u));
    overflow: hidden;
    background: rgba(12, 12, 16, .72);
    border: 1px solid rgba(255, 255, 255, .1);
    /*
       Свечение обязано укладываться в отступ корня — в те самые полюнита, которые заложены
       в натуральные габариты скина. Всё, что шире, упирается в край браузер-источника и
       обрезается: у стримера, задавшего источнику размер самой карточки (а именно так велит
       инструкция), прежние пять юнитов размытия превращали фон вокруг в сплошное цветное
       пятно с прямыми краями по границе сцены.
    */
    box-shadow: 0 0 calc(1 * var(--u)) calc(-0.5 * var(--u)) var(--mw-accent);
    animation: mw-slide-in-up 460ms cubic-bezier(.22, 1, .36, 1) both;
}

/*
   Блик, обегающий карточку по краю. Заменяет собой прежнее свечение как признак «карточка живая»:
   в отличие от свечения, он нарисован внутри границ карточки и уехать за край источника не может
   по построению, сколько бы маленьким этот источник ни был.

   Кольцо вырезано маской из двух слоёв: внешний по границе элемента, внутренний по content-box.
   Оба слоя обрезаются с учётом скругления, поэтому кольцо повторяет углы карточки, а не
   срезает их прямоугольником. Там, где вычитания масок нет, кольца просто не будет —
   показывать вместо него залитый градиентом прямоугольник нельзя.
*/
.mw-ambient-wave {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: none;
    border-radius: inherit;
    overflow: hidden;
    pointer-events: none;
}

@supports ((mask-composite: exclude) or (-webkit-mask-composite: xor)) {
    /*
       Край карточки рисует кольцо, поэтому собственная рамка ей больше не нужна. Вдвоём они
       читались как чёрный кант: рамка лежит снаружи кольца, фон карточки под ней почти чёрный,
       и рядом с подсвеченным кольцом эта линия бросалась в глаза — особенно на скруглениях,
       где взгляд идёт вдоль дуги. Ширина не меняется: коробка считается по border-box.
    */
    .mw-ambient {
        border-width: 0;
    }

    .mw-ambient-wave {
        display: block;

        /*
           Отступ — это и есть толщина кольца: маска вычитает content-box из border-box.

           Полтора прежних значения. Прямые грани карточки ложатся на целые пиксели
           и рисуются без сглаживания, а обе границы кольца на скруглении сглаживаются —
           примерно по полпикселя с каждой стороны. Из тонкой линии это съедало треть:
           на замере яркость по средней линии падала с 46 на прямых до 35 на дуге,
           и угол читался как тёмный обрывок прежней рамки. Кольцу в полтора раза
           толще тот же полупиксель уже не заметен.
        */
        padding: calc(0.1875 * var(--u));

        /* Постоянная линия края — то, чем была рамка. Блик проезжает поверх неё. */
        background: rgba(255, 255, 255, .1);
        -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
        mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
        -webkit-mask-composite: xor;
        mask-composite: exclude;
    }
}

/*
   Крутится квадрат с градиентом, а не сам градиент: поворот угла conic-gradient умеет
   анимировать только @property, а поворот элемента — обычный transform, который исполняет
   композитор и понимает любой CEF. Сторона квадрата с запасом больше диагонали карточки,
   иначе на повороте его углы выходили бы из-под кольца.
*/
.mw-ambient-wave > span {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 130%;
    aspect-ratio: 1;
    opacity: .55;
    background: conic-gradient(from 0deg,
    transparent 0 62%,
    var(--mw-accent) 76%,
    transparent 90% 100%);
    animation: mw-ambient-wave 6s linear infinite;
    animation-play-state: paused;
    will-change: transform;
    backface-visibility: hidden;
}

/* Замирает вместе с эквалайзером: на паузе двигаться в кадре нечему. */
.mw-ambient-wave.is-playing > span {
    animation-play-state: running;
}

/*
   Размытие в полтора юнита по площади больше самой карточки — самая дорогая краска во всём виджете.
   Слой поднят на композитор нарочно: без этого он лежит в одном слое с текстом, и обычное
   обновление тайминга раз в секунду заставляет браузер размывать обложку заново. На машине без
   аппаратного ускорения — а в браузер-источнике OBS его отключают чаще, чем кажется, — это
   видно как рывки бегущей строки и эквалайзера.
*/
.mw-ambient-backdrop {
    position: absolute;
    inset: -20%;
    background-size: cover;
    background-position: center;
    filter: blur(var(--mw-blur, calc(1.5 * var(--u)))) saturate(1.4);
    opacity: .55;
    z-index: 0;
    transform: translateZ(0);
    will-change: transform;
    contain: paint;
}

/*
   Затемнение поверх размытой обложки. Без него светлая обложка поднимает фон карточки под самый
   текст: белое название ещё различимо по тени, а приглушённый автор и тайминги — уже нет.
   Гуще слева, где текст, и легче справа, где от подложки нужен только цвет.
*/
.mw-ambient-scrim {
    position: absolute;
    inset: 0;
    z-index: 0;
    background: linear-gradient(90deg,
    rgba(10, 10, 14, .86) 0%,
    rgba(10, 10, 14, .74) 55%,
    rgba(10, 10, 14, .5) 100%);
}

.mw-ambient-content {
    position: relative;
    display: flex;
    gap: calc(1 * var(--u));
    align-items: center;
    z-index: 1;
}

.mw-ambient-cover {
    flex: none;
    width: calc(6 * var(--u));
    height: calc(6 * var(--u));
    border-radius: calc(0.75 * var(--u));
    overflow: hidden;
}

.mw-ambient-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.mw-ambient-body {
    min-width: 0;
    flex: 1 1 auto;
}

.mw-ambient-title {
    font-size: calc(1.15 * var(--u));
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-shadow: 0 calc(0.125 * var(--u)) calc(0.5 * var(--u)) rgba(0, 0, 0, .7);
}

.mw-ambient-author {
    font-size: calc(.88 * var(--u));
    color: var(--mw-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mw-ambient-meta {
    display: flex;
    gap: calc(0.75 * var(--u));
    margin-top: calc(0.375 * var(--u));
    font-size: calc(.8 * var(--u));
    color: var(--mw-muted);
    font-variant-numeric: tabular-nums;
}

.mw-ambient-requester {
    color: var(--mw-accent);
}

.mw-ambient-progress {
    position: relative;
    z-index: 1;
    margin-top: calc(0.875 * var(--u));
    height: calc(0.25 * var(--u));
    border-radius: calc(0.125 * var(--u));
    background: rgba(255, 255, 255, .14);
    overflow: hidden;
}

/* --- скин 6: всплывающий ------------------------------------------------- */

.mw-toast {
    display: flex;
    gap: calc(0.875 * var(--u));
    align-items: center;
    width: calc(23.75 * var(--u));
    max-width: 100%;
    padding: calc(0.875 * var(--u)) calc(1.125 * var(--u));
    border-radius: var(--mw-radius);
    background: var(--mw-surface);
    border: 1px solid rgba(255, 255, 255, .08);
    border-left: calc(0.25 * var(--u)) solid var(--mw-accent);
    animation: mw-toast-in 420ms cubic-bezier(.22, 1, .36, 1) both;
}

.mw-toast-cover {
    flex: none;
    width: calc(4 * var(--u));
    height: calc(4 * var(--u));
    border-radius: calc(0.625 * var(--u));
    overflow: hidden;
}

.mw-toast-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.mw-toast-body {
    min-width: 0;
}

.mw-toast-label {
    font-size: calc(.68 * var(--u));
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--mw-accent);
}

.mw-toast-title {
    font-size: calc(1 * var(--u));
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mw-toast-author,
.mw-toast-requester {
    font-size: calc(.8 * var(--u));
    color: var(--mw-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- строка, которая не влезла ------------------------------------------- */

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

   Отдельные классы, а не те же .mw-ticker-*, по двум причинам: у бегущей строки свой юнит
   --u-tk и свои отступы между копиями, а здесь всё меряется общим юнитом; и появляется эта
   разметка только тогда, когда текст действительно не поместился — решает это MarqueeText.
*/
.mw-run {
    overflow: hidden;
}

.mw-run-track {
    display: flex;
    width: max-content;
    animation: mw-marquee var(--mw-run-dur, 20s) linear infinite;

    /* Тот же отдельный слой, что у бегущей строки: без него CEF роняет кадры на каждом патче. */
    will-change: transform;
    backface-visibility: hidden;
}

/* Просвет между копиями: без него конец строки упирался бы в её же начало. */
.mw-run-chunk {
    padding-right: calc(2 * var(--u));
}

/* --- эквалайзер ----------------------------------------------------------- */

.mw-eq {
    display: inline-flex;
    align-items: flex-end;
    gap: calc(0.1875 * var(--u));
    height: calc(1 * var(--u));
    flex: none;

    /* Столбики не должны затрагивать раскладку соседей во время анимации. */
    contain: layout paint;
}

.mw-eq > span {
    width: calc(0.1875 * var(--u));
    height: 100%;
    border-radius: calc(0.125 * var(--u));
    background: var(--mw-accent);
    animation: mw-eq-bounce 900ms ease-in-out infinite alternate;
    /* Сдвиг по индексу делает столбики независимыми — иначе они прыгали бы синхронно. */
    animation-delay: calc(var(--mw-eq-index, 0) * 110ms);
    animation-play-state: paused;

    /* scaleY исполняется композитором; анимация height пересчитывала layout на каждом кадре. */
    transform: scaleY(.25);
    transform-origin: center bottom;
    will-change: transform;
    backface-visibility: hidden;
}

.mw-eq.is-playing > span {
    animation-play-state: running;
}

.mw-ambient-eq {
    height: calc(1.625 * var(--u));
    margin-top: calc(0.5 * var(--u));
}

.mw-ambient-eq > span {
    width: calc(0.25 * var(--u));
}

.mw-bar-eq {
    height: calc(0.875 * var(--u));
}

/* --- анимации ------------------------------------------------------------- */

@keyframes mw-marquee {
    from {
        transform: translate3d(0, 0, 0);
    }
    to {
        transform: translate3d(-50%, 0, 0);
    }
}

@keyframes mw-eq-bounce {
    from {
        transform: scaleY(.25);
    }
    to {
        transform: scaleY(1);
    }
}

@keyframes mw-spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

@keyframes mw-slide-in-up {
    from {
        opacity: 0;
        transform: translateY(calc(1.75 * var(--u))) scale(.96);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

@keyframes mw-toast-in {
    from {
        opacity: 0;
        transform: translateX(-32px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

/* Сдвиг в transform повторяет положение квадрата: без него он на повороте съезжал бы с центра. */
@keyframes mw-ambient-wave {
    from {
        transform: translate(-50%, -50%) rotate(0deg);
    }
    to {
        transform: translate(-50%, -50%) rotate(360deg);
    }
}

@keyframes mw-fade-in {
    from {
        opacity: 0;
        transform: translateY(calc(0.375 * var(--u)));
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ---------------------------------------------------------------------------
   Предпросмотр в настройках.

   Единственная разметка, которой нет в бою: коробка со «шахматкой», чтобы была видна
   прозрачность, и масштаб, чтобы виджет на 300 % влезал в карточку. Разметки скинов здесь ноль.
   --------------------------------------------------------------------------- */

.mw-preview-stage {
    position: relative;
    display: flex;
    padding: 20px;
    box-sizing: border-box;
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: .5rem;
    overflow: hidden;
    border: 1px solid var(--border-color);
    font-family: 'Lexend', system-ui, sans-serif;
    color: #fff;

    /* Шахматка: без неё прозрачный фон виджета не отличить от тёмной темы панели. */
    background-color: #2a2a32;
    background-image: linear-gradient(45deg, #23232a 25%, transparent 25%),
    linear-gradient(-45deg, #23232a 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #23232a 75%),
    linear-gradient(-45deg, transparent 75%, #23232a 75%);
    background-size: 20px 20px;
    background-position: 0 0, 0 10px, 10px -10px, -10px 0;
}

/*
   Внутри карточки настроек оверлей раскладывается по коробке предпросмотра, а не по экрану.
   Поэтому и юнит здесь считается от размеров самой коробки: vh/vw мерили бы окно панели,
   и предпросмотр перестал бы совпадать с боевым виджетом — а он обязан совпадать.
*/
.mw-preview-stage {
    container-type: size;
}

/*
   Предпросмотр показывает виджет мельче, чем занял бы источник таких же размеров: множитель
   0.55 из MediaWidgetPreview ужимает юнит, чтобы в карточку настроек влез и масштаб 300 %.
   Соотношения внутри скина от этого не страдают — ужимается сам юнит, а всё в скине выражено
   через него. Расходится только доля коробки, которую занимает виджет.
*/
.mw-preview-stage .mw-root {
    position: absolute;
    inset: 0;
    padding: calc(0.5 * var(--u));

    /* Формула юнита не дублируется — подменяется только коробка, от которой он считается. */
    --mw-vw: 100cqw;
    --mw-vh: 100cqh;
    --mw-umin: 4px;
}

/* Заглушка кадра вместо плеера — плеера в предпросмотре нет вовсе.
   Пропорции задаёт .mw-player-visible, здесь остаётся только фон. */
.mw-preview-frame {
    position: relative;
    width: 100%;
    overflow: hidden;
    background: #000;
}


/* ---------------------------------------------------------------------------
   Страницы медиавиджета в панели.

   Здесь, в отличие от оверлея, используются переменные тем панели: страницы должны
   выглядеть роднёй Song Requests, алертов и автобанов. Всё, для чего в дизайне уже
   есть класс (settings-panel, table-container, edit-button, clean-container),
   переиспользуется как есть — ниже только то, чего в наборе не было.
   --------------------------------------------------------------------------- */

/* --- статус источника в тулбаре --- */

.mw-status-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0.75rem;
    border-radius: var(--tearinbot-radii-full);
    border: 1px solid var(--border-color);
    background-color: var(--container-bg);
    color: var(--text-muted);
    font-size: 0.8125rem;
    white-space: nowrap;
}

.mw-status-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background-color: var(--tearinbot-colors-gray-500);
    flex: none;
}

.mw-status-badge.is-online {
    color: var(--tearinbot-colors-textPrimary);
}

.mw-status-badge.is-online .mw-status-dot {
    background-color: var(--tearinbot-colors-green-500);
}

/* Название играющего трека рядом со статусом: длинное имя не должно ломать тулбар. */
.mw-now-playing {
    display: block;
    max-width: 22rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- таблица очереди --- */

/* Кнопка ссылки и шестерёнка держатся вместе: перенос на вторую строку разрывает пару. */
.mw-toolbar-actions {
    flex: none;
}

.mw-link-toggle .mw-label-short {
    display: none;
}

/* --- управление плеером в тулбаре --- */

/*
   Транспорт стоит рядом со статусом источника и оформлен такой же пилюлей: это одна мысль —
   «что с виджетом прямо сейчас» — просто во второй её половине можно нажимать.
*/
.mw-transport {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.125rem 0.375rem;
    border-radius: var(--tearinbot-radii-full);
    border: 1px solid var(--border-color);
    background-color: var(--container-bg);
}

.mw-transport-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background-color: transparent;
    color: var(--text-muted);
    transition: background-color 200ms, color 200ms, opacity 200ms;
}

.mw-transport-button:hover:not(:disabled) {
    background-color: var(--navlink-hover);
    color: var(--tearinbot-colors-textPrimary);
}

/* Кольцо то же, что у .edit-button в Commands.css: фокус по всему сайту выглядит одинаково. */
.mw-transport-button:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(66, 153, 255, 0.6);
}

.mw-transport-button:disabled {
    opacity: 0.45;
    cursor: default;
}

/* Главное действие видно с одного взгляда — остальные кнопки остаются вспомогательными. */
.mw-transport-button.is-primary {
    background-color: var(--tearinbot-colors-primary-500);
    color: #fff;
}

.mw-transport-button.is-primary:hover:not(:disabled) {
    background-color: var(--tearinbot-colors-primary-600, var(--tearinbot-colors-primary-500));
    color: #fff;
}

.mw-transport-icon {
    width: 0.75rem;
    height: 0.75rem;
    pointer-events: none;
}

.mw-transport-volume {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}

/* Компактный вариант ползунка настроек: в тулбаре десять сантиметров под громкость не найти.
   Селектор из двух классов не для красоты: .mw-range со своими 10rem объявлен ниже по файлу
   и при равной силе перебил бы это правило. */
.mw-transport .mw-transport-range {
    width: 6rem;
}

.mw-playing-badge {
    padding: 0.125rem 0.5rem;
    border-radius: var(--tearinbot-radii-full);
    background-color: var(--tearinbot-colors-purpleAlpha-200);
    color: var(--tearinbot-colors-purple-500);
    font-size: 0.7rem;
    font-weight: 600;
    white-space: nowrap;
}

/*
   Пауза — то же место и та же форма, но без акцента: играющий трек в таблице должен остаться
   единственным цветным пятном, иначе взгляд перестаёт его находить.
*/
.mw-paused-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.125rem 0.5rem;
    border-radius: var(--tearinbot-radii-full);
    border: 1px solid var(--border-color);
    color: var(--text-muted);
    font-size: 0.7rem;
    font-weight: 600;
    white-space: nowrap;
}

/* Секунда остановки — моноширинными цифрами: иначе она дёргается при каждой смене трека. */
.mw-paused-time {
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    opacity: 0.8;
}

.mw-queue-track {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
}

/* Номер очереди стоит первым в ячейке трека и не сжимается вместе с названием. */
.mw-queue-track > .track-position {
    flex: none;
}

.mw-queue-sub {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
}

.mw-queue-sub > .text-muted {
    overflow: hidden;
    text-overflow: ellipsis;
}

.mw-queue-thumb {
    width: 4rem;
    height: 2.25rem;
    object-fit: cover;
    border-radius: 0.25rem;
    flex: none;
}

/*
   max-width: 0 на ячейке — приём для table-layout: auto: браузер считает её содержимое
   бесконечно сжимаемым, поэтому остальные колонки получают ширину по содержимому, а название
   забирает ровно остаток и обрезается многоточием. Без этого длинное название с YouTube
   раздувало таблицу шире контейнера и «Длина» с «Действиями» уезжали за край.
*/
.mw-queue-cell {
    width: 100%;
    max-width: 0;
}

.mw-queue-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* Название теперь ссылка, поэтому многоточие вешаем и на неё: без этого длинный заголовок
   с YouTube снова распирал бы ячейку. */
.mw-queue-text > span,
.mw-queue-text > a {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Страховка на узких экранах: таблица прокручивается внутри себя, а не рвёт страницу. */
.mw-queue-table {
    overflow-x: auto;
}

/* --- выбор скина и предпросмотр --- */

/*
   Скины слева, предпросмотр справа — и весь блок закреплён у верхнего края.

   Правил на странице три десятка, и без закрепления стример после каждой галочки крутил бы
   страницу вверх, чтобы увидеть, что она сделала. Докрутив до предпросмотра, он остаётся
   на месте, а карточки настроек уходят под него.
*/
.mw-appearance {
    display: grid;
    grid-template-columns: minmax(15rem, 20rem) 1fr;
    gap: var(--tearinbot-space-6);
    align-items: start;
}

/*
   Блок закрепляется целиком только на просторном экране — широком И достаточно высоком.

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

   Своя подложка обязательна: закреплённый блок позиционирован и рисуется поверх обычных
   карточек, и без фона они просвечивали бы сквозь зазор между колонками. Отступы гасятся
   отрицательным полем — они нужны только закреплённому блоку, чтобы карточки не выныривали
   вплотную к кадру, а на своём месте он стоит как стоял.
*/
@media (min-width: 62.01em) and (min-height: 54em) {
    .mw-appearance {
        position: sticky;
        top: 0;
        z-index: 1;
        padding-block: var(--tearinbot-space-3);
        margin-block: calc(var(--tearinbot-space-3) * -1);
        background-color: var(--bs-body-bg);
    }
}

/*
   Без этого правила закрепление не сработало бы вовсе.

   У .container-xl в макете стоит overflow: auto, то есть она и есть ближайшая к блоку
   прокручиваемая область — по ней sticky и считает границы. Но .page задана только
   min-height: 100vh, поэтому вся цепочка растёт вместе с содержимым, прокручивается окно,
   а сама .container-xl не прокручивается никогда — и закреплять предпросмотр не относительно
   чего. Фиксируем высоту страницы, и прокрутка возвращается туда, где её ждёт макет.

   dvh, а не vh: на телефоне адресная строка прячется и появляется, и от vh страница
   дёргалась бы на каждую прокрутку. Правило намеренно узкое — только эта страница:
   менять прокрутку всей панели ради одного предпросмотра нельзя.
*/
.page:has(.mw-appearance-page) {
    height: 100vh;
    height: 100dvh;
}

.mw-skin-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
}

.mw-skin-card {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.75rem;
    text-align: left;
    border-radius: 0.5rem;
    border: 1px solid var(--border-color);
    background-color: var(--container-bg);
    color: var(--bs-font-color);
    cursor: pointer;
    transition-property: border-color, box-shadow;
    transition-duration: var(--tearinbot-transition-duration-fast);
}

.mw-skin-card:hover {
    border-color: var(--tearinbot-colors-primary-500);
}

.mw-skin-card.is-selected {
    border-color: var(--tearinbot-colors-primary-500);
    box-shadow: var(--tearinbot-shadows-outline);
}

.mw-skin-card-name {
    font-weight: 600;
    font-size: 0.875rem;
}

.mw-skin-card-hint {
    font-size: 0.75rem;
    line-height: 1.3;
}

/* --- элементы управления в settings-panel --- */

/* Сетка 3x3 для выбора угла сцены. */
.mw-position-grid {
    display: grid;
    grid-template-columns: repeat(3, 1.75rem);
    grid-template-rows: repeat(3, 1.25rem);
    gap: 0.25rem;
    width: max-content;
}

.mw-position-cell {
    border-radius: 0.25rem;
    border: 1px solid var(--border-color);
    background-color: var(--hover);
    cursor: pointer;
    padding: 0;
}

.mw-position-cell:hover {
    border-color: var(--tearinbot-colors-primary-500);
}

.mw-position-cell.is-selected {
    background-color: var(--tearinbot-colors-primary-500);
    border-color: var(--tearinbot-colors-primary-500);
}

.mw-slider-cell {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.mw-range {
    width: 10rem;
    max-width: 45vw;
    accent-color: var(--tearinbot-colors-primary-500);
}

.mw-slider-value {
    min-width: 3.5rem;
    text-align: right;
    color: var(--text-muted);
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
}

.mw-color-row {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    max-width: 20rem;
    justify-content: flex-end;
}

/*
   Пара «от — до». Поля выровнены по верху, а не по центру: под каждым может встать подпись
   о поправленной границе, и по центру тире уезжало бы вниз вслед за ней.
*/
.mw-duration-inputs {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

/* Два поля в одной ячейке сжимались до нуля на телефоне — задаём им пол по ширине. */
.mw-duration-inputs .numeric-input {
    min-width: 3.5rem;
}

/* Тире стоит на высоте поля (2.5rem), а не строки текста: так оно остаётся между полями. */
.mw-duration-dash {
    line-height: 2.5rem;
}

.mw-inline-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    cursor: pointer;
}

/* Панель с полем во всю ширину: заголовок сверху, поле под ним. */
.mw-panel-stacked {
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
}

.mw-blocklist {
    width: 100%;
}

/* Предпросмотр описания награды: это цитата того, что прочитает зритель на Twitch,
   а не поле ввода, — поэтому рамка приглушённая и текст не моноширинный. */
.mw-prompt-preview {
    width: 100%;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--border-color);
    border-radius: 0.5rem;
    background: var(--commands-bg);
    line-height: 1.4;
}

/*
   Второй режим закрепления: узкий экран — или широкий, но невысокий.

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

   Карточки скинов при этом уезжают наверх как обычное содержимое: закреплять их вместе с
   кадром здесь и значило бы отдать полосе пол-экрана на одни только карточки. Кадр же,
   наоборот, растягивается во всю строку — в узкой колонке ему было тесно.
*/
@media (max-width: 62em), (max-height: 53.99em) {
    .mw-appearance {
        display: contents;
    }

    /*
       Закреплена обёртка, а не сам кадр: она тянется во всю строку и несёт подложку, иначе
       карточки настроек выныривали бы по бокам от кадра и выглядели бы наехавшими на него.
       Отступы гасятся отрицательным полем — они нужны только закреплённой полосе.
    */
    .mw-preview-slot {
        position: sticky;
        top: 0;
        z-index: 1;
        padding-block: var(--tearinbot-space-3);
        margin-block: calc(var(--tearinbot-space-3) * -1);
        background-color: var(--bs-body-bg);
    }
}

/* --- планшеты --- */

@media (max-width: 62em) {
    .mw-now-playing {
        max-width: 14rem;
    }

}

/* --- телефоны --- */

/* Граница 47.99em, а не 48em: общая схема сайта считает ровно 768 px широким экраном
   (Commands.css, min-width: 48em). Совпадающие границы дали бы на 768 px мешанину правил. */
@media (max-width: 47.99em) {
    /*
       Колонки «Заказал» и «Длина» скрывать не нужно: они на head-commands-middle /
       command-cell-middle, а те по общей схеме сайта (Commands.css) сами гаснут ниже 48em.
    */
    /*
       Узкий экран: длинная подпись уступает место плюсу, но кнопка остаётся на своей строке
       рядом с шестерёнкой. Перенос пары на вторую строку выглядел бы поломкой шапки.
    */
    .mw-link-toggle .mw-label-long {
        display: none;
    }

    .mw-link-toggle .mw-label-short {
        display: inline;
        font-size: 1.1rem;
        line-height: 1;
    }

    /*
       Общие отступы ячеек в 1.5rem на телефоне съедали до 100 px — при трёх видимых колонках
       названию трека оставалось меньше сорока пикселей. Режем отступы только в этой таблице.
    */
    .mw-queue-table .custom-table th,
    .mw-queue-table .custom-table td {
        padding-inline-start: 0.5rem;
        padding-inline-end: 0.5rem;
    }

    .mw-skin-grid {
        grid-template-columns: 1fr;
    }

    .mw-now-playing {
        display: none;
    }

    /* Ползунок уступает место кнопкам, а мьют остаётся: заглушить виджет с телефона надо уметь. */
    .mw-transport .mw-transport-range {
        display: none;
    }

    .mw-queue-thumb {
        width: 2.5rem;
        height: 1.5rem;
    }

    /*
       У .input-number отступы 16/32 px — на телефоне они съедали всё поле и от «600»
       оставался кусок цифры, а подпись о границах в 60 px не помещалась вовсе.
       Отдаём паре всю ширину карточки, как ползункам и палитре ниже, и режем отступы.
    */
    .mw-settings-page .settings-panel:has(.mw-duration-inputs) {
        flex-wrap: wrap;
    }

    .mw-settings-page .settings-panel:has(.mw-duration-inputs) > .action-container {
        flex: 1 0 100%;
        max-width: none;
    }

    .mw-duration-inputs .numeric-input {
        flex: 1 1 0;
        min-width: 0;
    }

    .mw-duration-inputs .input-number {
        padding-inline: 0.5rem;
    }

    /*
       Ползунок и значение занимают всю ширину карточки, иначе не влезают рядом с заголовком:
       в одну строку с ним ползунку доставалось 44 px, за который не ухватиться пальцем.
       Карточка переносит содержимое, ползунок уезжает под заголовок на всю ширину.
    */
    .mw-settings-page .settings-panel:has(> .mw-slider-cell) {
        flex-wrap: wrap;
    }

    .mw-range {
        width: 100%;
        max-width: none;
    }

    .mw-slider-cell {
        flex: 1 0 100%;
        width: 100%;
    }

    /* Палитра в боковой ячейке ужималась до двух образцов в ряд и растягивала карточку
       на семь строк. Отдаём ей всю ширину — умещается по четыре в ряд. */
    .mw-settings-page .settings-panel:has(.mw-color-row) {
        flex-wrap: wrap;
    }

    .mw-settings-page .settings-panel:has(.mw-color-row) > .action-container {
        flex: 1 0 100%;
    }

    .mw-color-row {
        justify-content: flex-start;
        max-width: none;
    }

    .alerts-info-text {
        max-width: min(100%, 320px);
    }
}

/*
   Строка со ссылкой — inline-flex, а он растягивается по содержимому и на телефоне вылезал
   за правый край вместе с кнопкой копирования. Ограничиваем шириной родителя: перенос
   по flex-wrap тогда наконец срабатывает.
*/
.mw-link-row {
    max-width: 100%;
}

/*
   .alerts-info-text прибит к #f1f1f2 — на светлой теме ссылка становится белым по белому.
   Правило намеренно узкое, только для этой строки: чинить общий класс значит менять и алерты,
   а это решение заказчика, а не побочный эффект правки виджета.
*/
.mw-link-row .alerts-info-text {
    color: var(--bs-font-color);
}

/*
   .panel-subtitle по общей схеме режется многоточием. Пояснения к правилам виджета длиннее,
   чем у соседних страниц, и обрезанное объяснение бесполезно — разрешаем перенос.
   Только от планшета: на телефоне перенос раздувал строку и отжимал поля ввода в ноль,
   а односточная обрезка там — сознательный вид всего сайта (см. Song Requests Settings).
*/
@media (min-width: 48em) {
    .mw-settings-page .panel-subtitle {
        white-space: normal;
        text-overflow: clip;
    }
}

/* ————— Окно-инструкция после включения ————— */

/* Общий сброс на сайте оставляет <p> без отступов вовсе, поэтому ритм окна задаём здесь. */
.mw-guide-body > * + * {
    margin-top: var(--tearinbot-space-4);
}

.mw-guide-steps {
    counter-reset: mw-guide;
    /*
       Только нижний отступ, а не margin целиком.

       Сокращённая запись гасила и верхний, а его ставит ритм окна выше — селекторы там равной
       силы, и побеждал тот, что ниже по файлу. Список от этого прилипал к строке «Осталось
       четыре шага», хотя блок под ним отступ получал, и окно выглядело слипшимся сверху.
    */
    margin-bottom: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--tearinbot-space-3-5);
}

/* Номер стоит в своей колонке, а не в поле слева от списка: у маркера <ol> поле уезжает
   под край окна на узком экране, и цифры пропадают. */
.mw-guide-steps li {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: auto auto;
    column-gap: var(--tearinbot-space-3);
    align-items: start;
}

.mw-guide-steps li::before {
    counter-increment: mw-guide;
    content: counter(mw-guide);
    grid-row: 1 / span 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--tearinbot-sizes-6);
    height: var(--tearinbot-sizes-6);
    border-radius: var(--tearinbot-radii-full);
    background-color: var(--commands-bg);
    color: var(--tearinbot-colors-textPrimary);
    font-size: var(--tearinbot-fontSizes-xs);
    font-weight: 600;
    line-height: 1;
}

.mw-guide-step-title {
    font-size: var(--tearinbot-fontSizes-sm);
    font-weight: 600;
    line-height: 1.5;
    color: var(--tearinbot-colors-textPrimary);
}

.mw-guide-step-text {
    font-size: var(--tearinbot-fontSizes-sm);
    line-height: 1.45;
    color: var(--tearinbot-colors-textSecondary);
}

.mw-guide-note {
    padding: var(--tearinbot-space-3) var(--tearinbot-space-4);
    border: 1px solid var(--border-color);
    border-radius: var(--tearinbot-radii-lg);
    background-color: var(--commands-bg);
}

.mw-guide-note-title {
    font-size: var(--tearinbot-fontSizes-sm);
    font-weight: 600;
    line-height: 1.5;
    color: var(--tearinbot-colors-textPrimary);
}

.mw-guide-note-list {
    margin: var(--tearinbot-space-1-5) 0 0;
    padding-left: 1.1rem;
    font-size: var(--tearinbot-fontSizes-sm);
    line-height: 1.45;
    color: var(--tearinbot-colors-textSecondary);
}

.mw-guide-note-list li + li {
    margin-top: var(--tearinbot-space-1);
}
