/* ---------------------------------------------------------------------------
   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-shadow: 0 calc(1.125 * var(--u)) calc(2.8 * var(--u)) rgba(0, 0, 0, .55);
    --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;
}

/*
   Натуральные габариты скинов. Высота — с учётом отступов корня, иначе виджет вылезал бы
   за нижний край невысокого источника.
*/
.mw-skin-miniplayer { --mw-uw: 27.25; --mw-uh: 7.9; }
.mw-skin-compactbar { --mw-uw: 39.75; --mw-uh: 4; }
.mw-skin-vinyl { --mw-uw: 30; --mw-uh: 9.6; }
.mw-skin-ambientcard { --mw-uw: 28.5; --mw-uh: 11.5; }
.mw-skin-toast { --mw-uw: 24.75; --mw-uh: 6.5; }

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

       --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 по углам больше не нужен: масштаб теперь заложен в юнит и меняет саму
   раскладку, поэтому виджет остаётся прижатым к своему углу без всяких точек отсчёта. */

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

.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;
}

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

/* 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);
    box-shadow: var(--mw-shadow);
    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 приходит кадром
   480x360, у которого сверху и снизу чёрные поля — на широкой плитке 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);
    box-shadow: var(--mw-shadow);
    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(var(--mw-bar-h, calc(3 * var(--u))) - calc(1 * var(--u)));
    height: calc(var(--mw-bar-h, calc(3 * var(--u))) - calc(1 * var(--u)));
    border-radius: calc(0.375 * 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: calc(1 * var(--u)) calc(1.375 * var(--u)) calc(1 * var(--u)) calc(1 * var(--u));
    border-radius: calc(5 * var(--u));
    background: var(--mw-surface);
    box-shadow: var(--mw-shadow);
    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(6.5 * var(--u));
    height: calc(6.5 * var(--u));
    flex: none;
}

.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 * 56 — длина окружности кольца из разметки. */
    stroke-dasharray: 351.86;
}

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

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

.mw-vinyl-cover {
    position: absolute;
    inset: calc(0.75 * var(--u));
    border-radius: 50%;
    overflow: hidden;
    background: #14141a;
    display: grid;
    place-items: center;
    box-shadow: inset 0 0 0 calc(0.375 * var(--u)) rgba(0, 0, 0, .55);
}

.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: calc(1 * var(--u));
    height: calc(1 * var(--u));
    border-radius: 50%;
    background: #0b0b10;
    box-shadow: 0 0 0 calc(0.1875 * var(--u)) rgba(255, 255, 255, .12);
}

.mw-vinyl-body {
    min-width: 0;
    max-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: var(--mw-shadow), 0 0 calc(5 * var(--u)) -10px var(--mw-accent);
    animation: mw-slide-in-up 460ms cubic-bezier(.22, 1, .36, 1) both;
}

.mw-ambient-backdrop {
    position: absolute;
    inset: -20%;
    background-size: cover;
    background-position: center;
    filter: blur(var(--mw-blur, 24px)) saturate(1.4);
    opacity: .55;
    z-index: 0;
}

.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;
    box-shadow: 0 calc(0.625 * var(--u)) calc(1.5 * var(--u)) rgba(0, 0, 0, .5);
}

.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);
    box-shadow: var(--mw-shadow);
    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-eq {
    display: inline-flex;
    align-items: flex-end;
    gap: calc(0.1875 * var(--u));
    height: calc(1 * var(--u));
    flex: none;
}

.mw-eq > span {
    width: calc(0.1875 * var(--u));
    height: 30%;
    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;
}

.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: translateX(0); }
    to { transform: translateX(-50%); }
}

@keyframes mw-eq-bounce {
    from { height: 25%; }
    to { height: 100%; }
}

@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); }
}

@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;
}

.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-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-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;
}

.mw-queue-text > span {
    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;
}

.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: center;
    gap: 0.5rem;
}

/* Два поля в одной ячейке сжимались до нуля на телефоне — задаём им пол по ширине. */
.mw-duration-inputs .input-number {
    min-width: 3.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%;
}

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

@media (max-width: 62em) {
    .mw-appearance {
        grid-template-columns: 1fr;
    }

    .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-queue-thumb {
        width: 2.5rem;
        height: 1.5rem;
    }

    /*
       У .input-number отступы 16/32 px — на телефоне они съедали всё поле и от «600»
       оставался кусок цифры. Режем отступы только у пары полей длительности.
    */
    .mw-duration-inputs .input-number {
        min-width: 0;
        width: 3.75rem;
        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;
    }
}
