/* Оболочка PLINK — повторяет нативную.
 *
 * ── Цвета ──────────────────────────────────────────────────────────────────
 *
 * Своей темы у PLINK нет. Нативный клиент построен на системных семантических цветах
 * (`.primary`, `.secondary`, `.quaternary`, `.separator`, материалы), а единственный
 * настоящий цвет в интерфейсе — акцент, вычисленный из обложки текущего трека
 * (`CoverStore.palette`, переменная `--tint` приезжает из JS). Розовый закреплён ровно
 * за одним предметом — сердечком избранного.
 *
 * ── Раскладка ──────────────────────────────────────────────────────────────
 *
 * Узко (`ContentView`): четыре вкладки внизу, над ними мини-плеер каруселью, всё одной
 * панелью. Широко (`MacShell`): боковое меню и плеер плавающей капсулой с полями по краям,
 * как в Музыке, — сплошная полоса во всю ширину читается как продолжение списка.
 *
 * Числа взяты из свифтового кода, а не подобраны: высота карточки 58, по 18 выглядывает
 * соседняя, зазор 4, скругление 13, обложка 38/6; капсула 50 с полями 16 и 14; иконка
 * вкладки 19, подпись caption2, отступ сверху 8; колонка меню 210.
 *
 * Высота сетки определённая (`height`, не `min-height`), строки — `minmax(0, 1fr)`.
 * Иначе `1fr` считается как `minmax(auto, 1fr)`, строка растягивается по содержимому,
 * внутренняя прокрутка не включается, и уезжает вся страница вместе с меню и вкладками. */

/* Акцент обложки объявлен как цвет — иначе он не анимируется.
 *
 * `background-image` с градиентом не интерполируется: смена `--accent` меняла фон
 * большого плеера скачком. Зарегистрированное свойство с типом `<color>` анимируется
 * само, и градиент пересчитывается плавно вместе с ним. */
@property --accent {
    syntax: '<color>';
    inherits: true;
    initial-value: #292929;
}

:root {
    color-scheme: dark light;
    transition: --accent 700ms ease-in-out;

    --bg: #000;
    --bg-raised: #1c1c1e;
    --bg-sidebar: #161618;
    --separator: rgb(255 255 255 / 0.12);

    --primary: rgb(255 255 255 / 1);
    --secondary: rgb(255 255 255 / 0.55);
    --tertiary: rgb(255 255 255 / 0.3);
    --quaternary: rgb(255 255 255 / 0.18);

    --tint: #808080;   /* `Color(white: 0.5)` — до первой обложки */
    --pink: #ff2d55;
    /* Системный акцент. Выделенная вкладка красится им (`.tint` в `ContentView`),
       а НЕ цветом обложки: цвет обложки — это подкраска плашки и фон большого плеера. */
    --accent-system: #0a84ff;

    --card-height: 3.625rem;   /* 58 */
    --capsule-height: 3.125rem; /* 50 */
    --tabs-height: 3.125rem;

    font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
    font-size: 16px;
}

@media (prefers-color-scheme: light) {
    :root {
        --bg: #fff;
        --bg-raised: #f2f2f7;
        --bg-sidebar: #ebebf0;
        --separator: rgb(0 0 0 / 0.12);
        --primary: rgb(0 0 0 / 1);
        --secondary: rgb(0 0 0 / 0.55);
        --tertiary: rgb(0 0 0 / 0.3);
        --quaternary: rgb(0 0 0 / 0.14);
        --accent-system: #007aff;
    }
}

* { box-sizing: border-box; }

/* Никакого синего свечения при нажатии.
 *
 * Три разные вещи дают его, и убирать надо все три, иначе оно остаётся то на телефоне,
 * то на маке: подсветка тапа в WebKit, обводка фокуса у кнопок и выделение текста,
 * которое возникает при быстрых повторных нажатиях по строке.
 *
 * Фокус при этом не выбрасывается — он остаётся для клавиатуры через `:focus-visible`:
 * убрать его совсем значит сделать интерфейс непроходимым с клавиатуры. */
* {
    -webkit-tap-highlight-color: transparent;
}

:focus { outline: none; }

/* Видимая обводка — только когда до элемента дошли клавиатурой. */
:focus-visible {
    outline: 2px solid var(--accent-system);
    outline-offset: 2px;
}

/* Строки, вкладки и кнопки — предметы для нажатия, а не текст: выделять в них нечего,
   а случайное выделение при быстром листании выглядит как поломка. */
.row, .tab, .card, .slot, .segmented, .icon-button, .button, .switch, .chip, .sheet-head {
    user-select: none;
    -webkit-user-select: none;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--primary);
    -webkit-font-smoothing: antialiased;
    overscroll-behavior: none;
}

/* ── Оболочка ─────────────────────────────────────────────────────────────── */

.shell {
    display: grid;
    height: 100dvh;
    grid-template-areas: 'content' 'player' 'sidebar';
    grid-template-rows: minmax(0, 1fr) auto auto;
}

.content {
    grid-area: content;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

/* ── Съезд экранов ────────────────────────────────────────────────────────────
 *
 * Новый экран **отодвигает** прежний, а не наезжает на него: оба едут на всю ширину
 * разом, как лента. Вглубь — новый приходит справа, прежний уходит за левый край
 * целиком; назад — зеркально. Ни параллакса, ни затемнения: они нужны там, где нижний
 * экран остаётся виден из-под верхнего, а здесь он уходит совсем.
 *
 * Ездит только содержимое. Панель вкладок, боковое меню и плеер лежат вне `.content`
 * отдельными областями сетки — они не участвуют в движении вовсе, как и положено
 * навигационной оболочке.
 *
 * Экраны на время перехода кладутся друг на друга не ради перекрытия, а потому что
 * в обычном потоке второй встал бы **под** первым по вертикали: вместо сдвига вбок
 * получилась бы длинная страница, едущая вместе с прокруткой. Наложения при этом нет —
 * они разведены ровно на ширину экрана.
 */
.content.animating {
    position: relative;
    overflow: hidden;
}

.content.animating > .screen {
    position: absolute;
    inset: 0;
    overflow-y: auto;
}

.screen[data-anim] {
    transition: transform 320ms cubic-bezier(0.32, 0.72, 0, 1);
    will-change: transform;
}

.screen[data-anim='in-forward'] { transform: translateX(100%); }
.screen[data-anim='in-back'] { transform: translateX(-100%); }
.screen[data-anim='out-forward'] { transform: translateX(-100%); }
.screen[data-anim='out-back'] { transform: translateX(100%); }
.screen[data-anim='settled'] { transform: none; }

@media (prefers-reduced-motion: reduce) {
    .screen[data-anim] { transition: none; }
}

/* Пункты, которые есть только в одной раскладке. Селектор с классом, а не один атрибут:
   у `.tab` ниже свой `display`, и при равной специфичности он побеждал по порядку —
   пункты не скрывались вовсе. */
.tab[data-wide-only] { display: none; }

/* ── Вкладки (узко) ───────────────────────────────────────────────────────── */

/* Мини-плеер и вкладки — одна панель, как `PlayerTabBar`: плеер часть панели, а не
   лежит на ней. Подложка поэтому одинаковая у обоих, а разделитель — сверху всей
   панели, то есть над плеером, а не между ним и вкладками. */
.sidebar,
.player {
    background: color-mix(in srgb, var(--bg-raised) 78%, transparent);
    backdrop-filter: saturate(180%) blur(20px);
}

.player { border-top: 0.5px solid var(--separator); }

.sidebar {
    grid-area: sidebar;
    display: flex;
    padding-bottom: calc(.5rem + env(safe-area-inset-bottom));
}

.section-title { display: none; }

.tab {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1875rem;                         /* VStack(spacing: 3) */
    min-height: var(--tabs-height);
    border: 0;
    background: none;
    color: var(--secondary);
    font: inherit;
    font-size: 0.6875rem;                   /* caption2 */
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.tab[aria-current] { color: var(--accent-system); }

/* Размер значка — та величина, которая видна на экране: у SVG рисунок занимает почти
   весь квадрат, поэтому квадрат и задаётся. У юникодных глифов, которые были здесь
   раньше, рисунок занимал меньше половины кегли — отсюда и бралось «маловаты». */
.tab .glyph { display: block; width: 1.625rem; height: 1.625rem; }
.tab .glyph svg { width: 100%; height: 100%; display: block; }

/* ── Боковое меню (широко) ────────────────────────────────────────────────── */

@media (min-width: 860px) {
    .shell {
        /* Плеер не занимает своей строки: он лежит слоем поверх списка, как плавающая
           панель системного плеера. Отведи ему строку — и внизу появится полоса,
           обрезающая зону прокрутки. */
        grid-template-areas: 'sidebar content';
        grid-template-columns: 13.125rem minmax(0, 1fr);  /* 210 — ideal у колонки */
        grid-template-rows: minmax(0, 1fr);
    }




    .tab[data-wide-only] { display: flex; }
    .tab[data-narrow-only] { display: none; }

    /* Плавающая панель по языку капсулы: те же поля, та же рамка, тень и размытие.
       Это отход от нативного macOS — там системная боковая колонка во всю высоту без
       полей, — и он сделан по просьбе, а не подгонкой под приложение.

       Скругление 20, а не 999 как у капсулы: у высокой панели полная скруглённость
       превратила бы её в лепесток. Радиус подобран так, чтобы читался как та же семья
       форм, а не как та же формула. */
    .sidebar {
        flex-direction: column;
        align-items: stretch;
        gap: 0.0625rem;
        margin: 1rem 0 0.875rem 1rem;
        padding: 0.75rem 0.625rem;
        border: 0.5px solid var(--separator);
        border-radius: 1.25rem;
        background: color-mix(in srgb, var(--bg-raised) 82%, transparent);
        backdrop-filter: saturate(180%) blur(20px);
        box-shadow: 0 0.25rem 0.75rem rgb(0 0 0 / 0.22);
        overflow-y: auto;
        z-index: 4;
    }

    /* Разделы сгруппированы, как в боковом меню: плоский список кнопок — это
       телефонный таббар, положенный на бок. */
    .section-title {
        display: block;
        padding: 0.75rem 0.5rem 0.25rem;
        color: var(--secondary);
        font-size: 0.6875rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.04em;
    }

    .section-title:first-child { padding-top: 0.125rem; }

    .tab {
        flex: 0 0 auto;
        flex-direction: row;
        justify-content: flex-start;
        gap: 0.5rem;
        min-height: 1.75rem;
        padding: 0 0.5rem;
        border-radius: 0.375rem;
        font-size: 0.8125rem;
        color: var(--primary);
    }

    .tab .glyph { width: 1.125rem; height: 1.125rem; color: var(--accent-system); }
    .tab[aria-current] { background: var(--quaternary); }
    .tab:hover:not([aria-current]) { background: color-mix(in srgb, var(--quaternary) 50%, transparent); }
}

/* ── Экраны ───────────────────────────────────────────────────────────────── */

.screen { padding-bottom: 1.5rem; }
.screen[hidden] { display: none; }

/* Отступ до текста заголовка — 1rem на всех экранах, столько же, сколько до бокового
   меню. Раньше он зависел от того, что лежит в шапке: кнопка «+» выше строки заголовка,
   растягивала шапку, и `align-items: center` опускала текст ниже, чем на экранах без неё.
   Поэтому высота содержимого шапки задана одна (`--head-line`), и всё, что в неё
   попадает, в неё и укладывается. */
.screen-head {
    /* Высота содержимого шапки. Чем она ближе к строке заголовка, тем меньше добавляет
       центрирование — а его добавка и есть разница между «1rem до текста» и тем, что
       видно. 1.5rem — минимум, в который укладываются и сегментный переключатель,
       и круглая «+». */
    /* Высота элементов управления в шапке. Переключатель и круглая «+» — предметы для
       нажатия, а не строки текста: в нативном они около 31 px, и тринадцатый кегль
       в 24 px сидит тесно. */
    --head-line: 1.9375rem;

    position: sticky;
    top: 0;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0 1.25rem 0.625rem;
    /* Отступ уменьшен ровно на добавку центрирования, чтобы до **текста** получился 1rem.
       Замерено: так все пять экранов дают одно и то же значение. */
    padding-top: max(1rem, env(safe-area-inset-top));
    background: color-mix(in srgb, var(--bg) 80%, transparent);
    backdrop-filter: saturate(180%) blur(20px);
}

.screen-head h1 {
    margin: 0;
    flex: 1;
    /* Своей высоты у заголовка нет: тогда отступ до текста равен ровно `padding-top`
       шапки, без добавки от центрирования. Раньше я задавал ему высоту шапки и
       компенсировал добавку уменьшенным отступом — из-за этого приходилось зажимать
       и переключатель, которому на своём экране мешать было нечему. */
    line-height: 1.16;
    font-size: 1.0625rem;      /* inline-заголовок, как navigationBarTitleDisplayMode(.inline) */
    font-weight: 600;
    letter-spacing: -0.01em;
}

/* Заголовок один на все экраны — тот же, что у «Избранного»: строчный, как
   `navigationBarTitleDisplayMode(.inline)`. Раньше три экрана имели крупный, и разница
   читалась как две разные программы. */
.screen[data-screen='search'] .screen-head,
.screen[data-screen='unison'] .screen-head,
.screen[data-screen='settings'] .screen-head,
.screen[data-screen='yandex'] .screen-head { flex-wrap: wrap; }

.screen[data-screen='search'] h1 { flex: 0 0 auto; }
.screen[data-screen='search'] .search { flex: 1 1 100%; }

/* Сегментный переключатель — только там, где выбор не сделан снаружи. */
.segmented {
    display: flex;
    flex: 1;
    height: var(--head-line, 1.9375rem);
    gap: 0.125rem;
    padding: 0.1875rem;
    border-radius: 0.5rem;
    background: var(--quaternary);
}

.segmented button {
    flex: 1;
    min-width: 0;
    padding: 0.25rem 0.5rem;
    /* Высота у переключателя задана, поэтому перенос надо запретить: иначе «Мой плейлист»
       разъезжается на две строки и вылезает за свою плашку. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    border: 0;
    border-radius: 0.4375rem;
    background: none;
    color: var(--primary);
    font: inherit;
    font-size: 0.8125rem;
    cursor: pointer;
}

.segmented button[aria-selected='true'] { background: var(--bg-raised); font-weight: 600; }

/* Селектор с классом И элементом: правило `.screen-head h1` задаёт `display: flex`,
   и одного `.fixed-title` не хватало по специфичности — заголовок показывался всегда,
   рядом с переключателем и поверх него. */
.screen-head h1.fixed-title { display: none; }

/* Выбор снаружи: ни переключателя, ни жеста, ни второй страницы — как `fixedScope`
   в `LibraryView`. Прятать надо именно страницу, а не только прокрутку: пейджер это
   сетка в две колонки по 100%, и с одним `overflow: hidden` вторая страница остаётся
   в потоке и наезжает на первую. */
.screen[data-fixed] .segmented { display: none; }
.screen[data-fixed] .screen-head h1.fixed-title { display: flex; }

.screen[data-fixed] .pager {
    display: block;
    overflow-x: hidden;
    scroll-snap-type: none;
}

.screen[data-fixed] .page { display: none; }
.screen[data-fixed='mine'] .page[data-scope='mine'],
.screen[data-fixed='favorites'] .page[data-scope='favorites'] { display: block; }

.search {
    width: 100%;
    padding: 0.4375rem 0.75rem;
    border: 0;
    border-radius: 0.625rem;
    background: var(--quaternary);
    color: var(--primary);
    font: inherit;
    font-size: 1rem;      /* меньше 1rem — и iOS зазумит страницу при фокусе */
}

.search::placeholder { color: var(--secondary); }
.search:focus { outline: 2px solid var(--tint); outline-offset: -1px; }

.hint {
    margin: 0;
    padding: 0.75rem 1.25rem;
    color: var(--secondary);
    font-size: 0.8125rem;
    line-height: 1.5;
    max-width: 42rem;
}

/* ── Пейджер ──────────────────────────────────────────────────────────────── */

/* Прокрутка со снапом, а не пересборка страниц: `TabView(.page)` пересобирал их
   на каждое переключение, и постраничное листание в нативном сделано скроллом. */
/* Пейджер занимает всю оставшуюся высоту, а прокручивается **каждая страница
   отдельно**, а не экран целиком.

   Иначе высота строки грида равна самой длинной странице: в «Избранном» с тремя треками
   прокрутка продолжалась ещё на полсотни строк «Моего плейлиста» и упиралась в пустоту.
   Так же устроено и в нативном: `TabView(.page)` со своим `ScrollView` внутри каждой
   страницы, а не один общий на обе. */
/* `:not([hidden])` здесь обязательно. У `.screen[hidden] { display: none }` ровно та же
   специфичность, а это правило ниже в файле — и своим `display: flex` оно отменяло
   скрытие: экран «Музыка» оставался на месте, а «Поиск» рисовался под ним, уезжая
   за нижний край. Те же грабли, что с `[data-wide-only]` и `grid-area: auto`. */
.screen[data-screen='music']:not([hidden]) {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding-bottom: 0;
}

.pager {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 100%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    flex: 1;
    min-height: 0;
}

.pager::-webkit-scrollbar { display: none; }

.page {
    scroll-snap-align: start;
    min-width: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 1.5rem;
}

.page::-webkit-scrollbar { display: none; }

/* ── Строки ───────────────────────────────────────────────────────────────── */

/* Строки не подписаны ни на что — им передают значения. Подписка строк на координатор
   перерисовывала список четыре раза в секунду. */
.rows { display: flex; flex-direction: column; padding: 0 0.625rem; }

/* Отступ до содержимого одинаков со всех сторон — 10. Число одно, а не два, тогда его
   нельзя рассогласовать; раньше по вертикали стояло меньше, и обложка казалась прижатой
   к краям плашки. Подсветка текущего — именно padding, а не margin: фон должен включать
   отступы, иначе он обрезается по содержимому и выглядит случайным. */
.row {
    display: grid;
    grid-template-columns: 3.625rem minmax(0, 1fr) auto auto auto;
    align-items: center;
    gap: 0.75rem;                  /* HStack(spacing: 12) */
    padding: 0.625rem;             /* inset: 10 */
    border: 0;
    border-radius: 0.75rem;        /* cornerRadius: 12 */
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.row[aria-current] { background: color-mix(in srgb, var(--primary) 10%, transparent); }
.row:hover:not([aria-current]) { background: color-mix(in srgb, var(--primary) 4%, transparent); }

.cover {
    width: 3.625rem;               /* size: 58 */
    height: 3.625rem;
    border-radius: 0.5rem;         /* corner: 8 */
    background: var(--quaternary) center / cover no-repeat;
}

/* Пульсация прозрачностью, а не размером: обложка не дёргает соседние строки
   и не сдвигает разметку. */
.row[data-playing] .cover { animation: pulse 2.8s ease-in-out infinite; }

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
    .row[data-playing] .cover { animation: none; }
}

.row-text { display: flex; flex-direction: column; gap: 0.1875rem; min-width: 0; }
.row-title, .row-artist { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-title { font-size: 1.0625rem; }                             /* .body */
.row-artist { color: var(--secondary); font-size: 0.75rem; }     /* .caption */
.row-heart, .row-status { width: 1.0625rem; height: 1.0625rem; }
.row-heart svg, .row-status svg { width: 100%; height: 100%; display: block; }
.row-heart { color: var(--pink); }
.row-status { color: var(--secondary); }
.row-duration { color: var(--secondary); font-size: 0.75rem; font-variant-numeric: tabular-nums; }

/* ── Панель плеера ────────────────────────────────────────────────────────── */

.player {
    grid-area: player;
    position: relative;
}

.player[hidden] { display: none; }

/* ── Карусель мини-плеера (узко) ──────────────────────────────────────────── */

.carousel {
    position: relative;
    height: var(--card-height);
    margin: 0.625rem 0 0.375rem;    /* padding(.top, 10) / padding(.bottom, 6) */
    overflow: hidden;
    /* Вытягивание вверх — наш жест, а не прокрутка: `pan-y` отдавал его браузеру. */
    touch-action: none;
}

/* Все слоты центрируются одинаково: расстояние между центрами задаёт, насколько
   выглядывают соседи. Прижми их к краям — и при плавном изменении размеров края
   начнут расходиться. */
.slot {
    position: absolute;
    top: 0;
    left: 50%;
    height: 100%;
    margin-left: calc(var(--slot-width, 0px) / -2);
    will-change: transform, opacity;
}

.card {
    display: grid;
    grid-template-columns: 2.375rem minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 0.625rem;
    height: 100%;
    padding: 0 0.625rem;
    border-radius: 0.8125rem;       /* 13 */
    background:
        linear-gradient(to right,
            color-mix(in srgb, var(--tint) 30%, transparent) 0 var(--played, 0%),
            transparent var(--played, 0%) 100%),
        color-mix(in srgb, var(--quaternary) 45%, transparent);
    overflow: hidden;
    cursor: pointer;
}

.card-cover {
    width: 2.375rem;               /* 38 */
    height: 2.375rem;
    border-radius: 0.375rem;       /* corner: 6 */
    background: var(--quaternary) center / cover no-repeat;
}

.card-text { min-width: 0; }
.card-title { font-size: 0.9375rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-artist { color: var(--secondary); font-size: 0.75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Кнопки живут внутри карточки и едут вместе с ней. На время листания убираются вовсе:
   нажать их всё равно нельзя, а мельтешение под пальцем мешает смотреть, куда листаешь. */
.card-mute { width: 1.375rem; height: 1.375rem; }
.card-play { width: 1.5rem; height: 1.5rem; opacity: var(--centrality, 1); }
.slot.settling .card-mute,
.slot.settling .card-play { opacity: 0; pointer-events: none; }

/* ── Капсула (широко) ─────────────────────────────────────────────────────── */

.capsule { display: none; }

@media (min-width: 860px) {
    .carousel { display: none; }

    /* Плеер слоем поверх списка: своей строки в сетке он не занимает, иначе внизу
       появляется полоса, обрезающая зону прокрутки.
       
       `grid-area: auto` обязательно: имя `player` в широком шаблоне не объявлено,
       а размещение по несуществующему имени создаёт неявную колонку — она съедала
       ширину контента. И этот блок обязан идти ПОСЛЕ базового `.player`: специфичность
       у них равная, и при обратном порядке базовое `grid-area: player` побеждало. */
    .player {
        /* Подложки у панели здесь нет: её роль играет сама капсула, а полоса во всю
           ширину читалась бы как продолжение списка. */
        background: none;
        backdrop-filter: none;
        border-top: 0;
        grid-area: auto;
        position: fixed;
        left: 13.125rem;
        right: 0;
        bottom: 0;
        z-index: 4;
        pointer-events: none;   /* мимо капсулы клики идут в список под ней */
    }

    .capsule { pointer-events: auto; }

    /* Последний трек не должен уезжать под капсулу. Высота и поля считаются одним
       выражением здесь, а не подставляются на глаз в другом месте.

       Запас нужен **тому, кто прокручивается**, — и в этом была ошибка: когда страницы
       пейджера получили собственную прокрутку, `padding-bottom` остался на `.content`,
       то есть на неподвижном контейнере. Двигать содержимое из-под плавающей панели он
       перестал, и капсула вместо «поверх списка» встала отдельной полосой под ним.
       На «Музыке» прокручивается страница, на остальных экранах — сам экран. */
    .screen:not([data-screen='music']) { padding-bottom: calc(var(--capsule-height) + 1.75rem); }
    .page { padding-bottom: calc(var(--capsule-height) + 1.75rem); }

    /* Плавающая капсула с полями по краям, как у системного плеера: отдельный предмет,
       лежащий поверх окна. Поля 16 по горизонтали и 14 снизу — из `PlaybackBarPlacement`. */
    .capsule {
        display: grid;
        grid-template-columns: auto minmax(16.25rem, 38.75rem) auto;
        align-items: center;
        gap: 1rem;
        min-height: var(--capsule-height);
        /* Ширина по содержимому, а не во всю колонку: у системного плеера капсула —
           отдельный предмет, лежащий поверх окна. `justify-content: center` центрировал
           только содержимое внутри, а сама плашка оставалась во всю ширину и читалась
           как продолжение списка — ровно то, чего в `MacShell` велено избегать. */
        width: fit-content;
        max-width: calc(100% - 2rem);
        margin: 0 auto 0.875rem;
        padding: 0 0.875rem;
        border: 0.5px solid var(--separator);
        border-radius: 999px;
        background: color-mix(in srgb, var(--bg-raised) 82%, transparent);
        backdrop-filter: saturate(180%) blur(20px);
        box-shadow: 0 0.25rem 0.75rem rgb(0 0 0 / 0.22);
    }

    /* Табло растягивается, края нет: иначе кнопка воспроизведения ездила бы от длины
       названия, а глаз ищет её на одном месте. Своей подложки у табло нет — она была бы
       рамкой внутри рамки и дробила капсулу на части. */
    .display {
        display: grid;
        grid-template-columns: 1.875rem minmax(0, 1fr);
        align-items: center;
        gap: 0.625rem;
        min-width: 0;
    }

    .p-cover {
        width: 1.875rem;
        height: 1.875rem;
        border-radius: 0.25rem;
        background: var(--quaternary) center / cover no-repeat;
    }

    .stack { min-width: 0; text-align: center; }
    .c-title { font-size: 0.75rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .c-artist { color: var(--secondary); font-size: 0.625rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

    .transport { display: flex; align-items: center; gap: 1rem; }
    .extras { display: flex; align-items: center; gap: 0.875rem; }
}

/* ── Полоса позиции ───────────────────────────────────────────────────────── */

.bar-row {
    display: grid;
    grid-template-columns: 1.875rem minmax(0, 1fr) 1.875rem;
    align-items: center;
    gap: 0.375rem;
    color: var(--secondary);
    font-size: 0.5625rem;
    font-variant-numeric: tabular-nums;
}

.bar-row .elapsed { text-align: right; }

.bar { height: 0.1875rem; border-radius: 999px; background: var(--quaternary); overflow: hidden; }

/* Полоса двигается трансформацией, а не шириной: ширина заставляет браузер считать
   раскладку заново на каждом кадре, а меняется она каждый кадр. */
.fill {
    height: 100%;
    background: var(--secondary);
    transform-origin: left;
    transform: scaleX(0);
}

/* ── Кнопки ───────────────────────────────────────────────────────────────── */

.icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 1.5rem;
    height: 1.5rem;
    border: 0;
    background: none;
    padding: 0;
    color: var(--primary);
    line-height: 1;
    cursor: pointer;
}

.icon-button svg { width: 100%; height: 100%; display: block; }
.icon-button:disabled { color: var(--tertiary); cursor: default; }
.icon-button.play { width: 1.75rem; height: 1.75rem; }
.icon-button.play.big { width: 3.5rem; height: 3.5rem; }

/* «+» — круглая кнопка на подложке, как в заголовке нативного экрана, а не голый глиф. */
.icon-button.add {
    width: var(--head-line, 1.9375rem);
    height: var(--head-line, 1.9375rem);
    padding: 0.375rem;
    border-radius: 999px;
    background: var(--quaternary);
}
.icon-button.heart { color: var(--pink); }
.icon-button.heart[aria-pressed='false'] { color: var(--tertiary); }

.button {
    padding: 0.375rem 0.875rem;
    border: 0;
    border-radius: 0.5rem;
    background: var(--quaternary);
    color: var(--primary);
    font: inherit;
    font-size: 0.8125rem;
    cursor: pointer;
}

.button:disabled { color: var(--tertiary); cursor: default; }

/* ── Панели настроек ──────────────────────────────────────────────────────── */

.panel {
    margin: 0.75rem 1.25rem;
    padding: 0.875rem 1rem;
    border-radius: 0.75rem;
    background: var(--bg-raised);
    max-width: 42rem;
}

.switch-row { display: flex; align-items: center; gap: 1rem; }

/* Соседние строки внутри одной панели разделяются чертой, а не воздухом: в нативном
   это `Form`, где строки разделены линией по левому краю текста, и без неё «Комната»
   и «Ваше имя» читались одним слипшимся абзацем. */
.switch-row + .switch-row {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--quaternary);
}

/* Растягивается именно текстовый блок, а не «первый ребёнок»: аватарка стоит первой,
   и `:first-child` (специфичность выше, чем у `.avatar`) растягивал её по горизонтали. */
.switch-row .switch-text { flex: 1; min-width: 0; }
.switch-title { font-size: 0.9375rem; }
.switch-note { color: var(--secondary); font-size: 0.75rem; line-height: 1.45; margin-top: 0.125rem; }

.switch {
    flex: none;
    width: 3.1875rem;
    height: 1.9375rem;
    border: 0;
    border-radius: 999px;
    background: var(--quaternary);
    cursor: pointer;
    position: relative;
}

.switch::after {
    content: '';
    position: absolute;
    top: 0.125rem;
    left: 0.125rem;
    width: 1.6875rem;
    height: 1.6875rem;
    border-radius: 999px;
    background: #fff;
    box-shadow: 0 0.125rem 0.25rem rgb(0 0 0 / 0.2);
    transition: transform 180ms ease;
}

.switch[aria-checked='true'] { background: var(--tint); }
.switch[aria-checked='true']::after { transform: translateX(1.25rem); }
.switch:disabled { opacity: 0.45; cursor: default; }

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: 0.5rem;
    margin-top: 0.875rem;
}

.cell {
    display: flex;
    flex-direction: column;
    gap: 0.1875rem;
    padding: 0.5rem 0.625rem;
    border-radius: 0.5rem;
    background: color-mix(in srgb, var(--quaternary) 40%, transparent);
}

.key { color: var(--secondary); font-size: 0.6875rem; }
.value { font-size: 1rem; font-variant-numeric: tabular-nums; }

.actions { display: flex; gap: 0.5rem; margin-top: 0.75rem; flex-wrap: wrap; }

.report {
    margin: 0.75rem 0 0;
    padding: 0.625rem 0.75rem;
    border-radius: 0.5rem;
    background: color-mix(in srgb, var(--quaternary) 40%, transparent);
    color: var(--secondary);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.75rem;
    line-height: 1.6;
    white-space: pre;
    overflow-x: auto;
}

.report:empty { display: none; }

/* ── Большой плеер ───────────────────────────────────────────────────────── */

/* По `NowPlayingView`. На большом экране его нет: прятать содержимое ради обложки
   незачем, там очередь и обложка живут панелью.

   Фон — чёрное дно под градиентом от акцента обложки. Дно обязательно: у среднего
   перехода прозрачность 0.55, и без непрозрачной подложки сквозь плеер просвечивал бы
   экран, над которым он лежит. Анимация висит на самом цвете, а не на смене трека:
   цвет приезжает асинхронно вместе с обложкой и на смену идентификатора не попадает. */
.sheet {
    position: fixed;
    inset: 0;
    z-index: 20;
    /* Все жесты внутри листа наши: и листание обложек по горизонтали, и оттягивание вниз.
       Без этого браузер считает вертикальное движение прокруткой, забирает его себе
       и присылает `pointercancel` — драг не работал вовсе, сколько бы ни правился
       обработчик. */
    touch-action: none;
    overscroll-behavior: contain;
    display: flex;
    flex-direction: column;
    padding: 0 1.5rem max(1.5rem, env(safe-area-inset-bottom));
    padding-top: max(0.5rem, env(safe-area-inset-top));
    background:
        linear-gradient(180deg,
            var(--accent, #292929),
            color-mix(in srgb, var(--accent, #292929) 55%, transparent) 50%,
            transparent 100%),
        #000;
    color: #fff;
    /* Сам градиент не анимируется — анимируется `--accent`, из которого он собран. */
    will-change: transform;
}

.sheet[hidden] { display: none; }

/* Выезжает снизу, уезжает обратно. Пока тянут пальцем — переход выключен, иначе лист
   отстаёт от пальца на длительность анимации и жест кажется вязким. */
/* Сдвиг от пальца идёт переменной, а не подменой `transform` целиком: у модалки очереди
   в широкой раскладке в том же свойстве живёт центрирование, и запись поверх сбивала бы
   её по горизонтали. */
.sheet { transform: translateY(var(--drag, 0px)); transition: transform 320ms cubic-bezier(0.2, 0.9, 0.2, 1); }
.sheet[data-entering] { transform: translateY(100%); }
.sheet[data-dragging] { transition: none; }
/* Большой плеер — только на узком: на большом экране прятать содержимое ради обложки
   незачем. Очередь же нужна в обеих раскладках, поэтому её модалка из этого правила
   исключена. */
@media (min-width: 860px) { .sheet:not(.queue-sheet) { display: none !important; } }

.sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.5rem 0;                     /* padding(.vertical, 8) */
    flex: none;
}

.sheet-head .icon-button { color: #fff; }
.sheet-group { text-align: center; font-size: 0.8125rem; font-weight: 600; }

/* Карусель обложек. Расстояние между центрами равно ширине экрана, поэтому соседние
   обложки стоят за краями и появляются только по ходу свайпа. */
.sheet-carousel {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    margin: auto 0;
    overflow: visible;
    touch-action: none;
}

.sheet-carousel .slot { display: flex; align-items: center; justify-content: center; }

/* Размер плавно идёт от соседнего к активному по мере приближения к центру: на паузе
   эти размеры совпадают, и при свайпе ничего не меняется. Масштаб, а не ширина —
   ширина заставляла бы считать раскладку на каждом кадре. */
/* Сторона обложки — **одно число**, посчитанное от меньшей из доступных величин.
 *
 * Двумя ограничениями это не задаётся, и я на этом уже ошибся: при `height: 100%` плюс
 * `max-width: 100%` ширина обрезалась, высота оставалась, и квадрат превращался в
 * вытянутый прямоугольник. `aspect-ratio` уступает `max-*`, а не наоборот.
 *
 * Поэтому сторону считает карусель — она и так меряет ширину слота и знает высоту
 * области, — и отдаёт её переменной `--cover-side`. Здесь остаётся только применить.
 * В приложении такого расчёта нет, потому что там сторона это «ширина экрана минус 48»:
 * телефон не бывает шириной 700, а в вебе узкая раскладка тянется до 859. */
.sheet-cover {
    width: var(--cover-side, 100%);
    height: var(--cover-side, auto);
    aspect-ratio: 1;
    border-radius: 0.875rem;               /* corner: 14 */
    background: rgb(255 255 255 / 0.1) center / cover no-repeat;
    box-shadow: 0 0.625rem 1.25rem rgb(0 0 0 / 0.4);
    transform: scale(var(--cover-scale, 1));
    transition: transform 450ms cubic-bezier(0.2, 0.9, 0.2, 1);
}

/* Во время листания размер идёт за пальцем без перехода: с переходом он отставал
   и «догонял» уже после того, как палец отпустили. */
.sheet[data-dragging] .sheet-cover,
.slot.settling .sheet-cover,
.swapping .sheet-cover { transition: none; }

.sheet-title-row { flex: none; padding-bottom: 1.125rem; }
.sheet-title { font-size: 1.375rem; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sheet-artist { font-size: 1.125rem; color: rgb(255 255 255 / 0.6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.sheet-progress { flex: none; padding-bottom: 1.375rem; }

.scrubber {
    position: relative;
    height: 1.25rem;
    display: flex;
    align-items: center;
    cursor: pointer;
    touch-action: none;
}

.scrubber .bar { flex: 1; height: 0.1875rem; background: rgb(255 255 255 / 0.25); }
.scrubber .fill { background: #fff; }

/* Пока тянут — ползунок крупнее: без видимой реакции жест кажется неработающим. */
.scrubber .knob {
    position: absolute;
    width: 0.5rem;
    height: 0.5rem;
    margin-left: -0.25rem;
    border-radius: 999px;
    background: #fff;
    left: var(--knob, 0%);
    transition: width 120ms ease, height 120ms ease, margin-left 120ms ease;
}

.scrubber.scrubbing .knob { width: 0.875rem; height: 0.875rem; margin-left: -0.4375rem; }

/* Тот же ползунок в капсуле: механика одна, отличается только высотой полосы отклика
   и цветами — там светлая тема тоже возможна, поэтому берём семантические переменные. */
.scrubber.thin { height: 0.75rem; }
.scrubber.thin .bar { background: var(--quaternary); }
.scrubber.thin .fill { background: var(--secondary); }
.scrubber.thin .knob { width: 0.375rem; height: 0.375rem; margin-left: -0.1875rem; background: var(--primary); opacity: 0; }
.scrubber.thin:hover .knob, .scrubber.thin.scrubbing .knob { opacity: 1; }
.scrubber.thin.scrubbing .knob { width: 0.625rem; height: 0.625rem; margin-left: -0.3125rem; }

.sheet-progress .times {
    display: flex;
    justify-content: space-between;
    margin-top: 0.375rem;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: rgb(255 255 255 / 0.55);
}

/* Фиксированные ширины по краям уравновешивают друг друга и держат кнопку
   воспроизведения по центру экрана. */
.sheet-transport {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-bottom: 0.5rem;
}

.sheet-transport .icon-button { color: #fff; }
.sheet-transport .edge { width: 2rem; }
.sheet-transport .heart[aria-pressed='false'] { color: rgb(255 255 255 / 0.55); }
.sheet-transport .heart[aria-pressed='true'] { color: var(--pink); }

/* ── Сообщение ────────────────────────────────────────────────────────────── */

.toast {
    position: fixed;
    left: 50%;
    bottom: calc(var(--card-height) + var(--tabs-height) + 2rem);
    transform: translateX(-50%);
    z-index: 30;
    max-width: min(26rem, calc(100vw - 2rem));
    padding: 0.5rem 0.875rem;
    border-radius: 999px;
    background: var(--bg-raised);
    border: 0.5px solid var(--separator);
    box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 0.3);
    font-size: 0.8125rem;
}

.toast[hidden] { display: none; }

@media (min-width: 860px) {
    .toast { bottom: calc(var(--capsule-height) + 2rem); }
}

@media (prefers-reduced-motion: no-preference) {
    .row, .tab, .button, .icon-button { transition: background-color 120ms ease, color 120ms ease; }
}

/* ── Профиль, привязка, Яндекс ────────────────────────────────────────────── */

.avatar {
    flex: none;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 999px;
    background: var(--quaternary) center / cover no-repeat;
}

.button.accent { background: var(--accent-system); color: #fff; }
.button.accent:disabled { background: var(--quaternary); color: var(--tertiary); }

.link-code { margin-top: 0.875rem; }

/* Код набирают руками — значит крупно, цифра к цифре и с разрядкой. */
.code {
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    font-variant-numeric: tabular-nums;
    text-align: center;
    padding: 0.5rem 0;
}

.code-note {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    font-size: 0.8125rem;
    color: var(--secondary);
}

.code-note a { color: var(--accent-system); }

.warn {
    margin: 0.75rem 0 0;
    padding: 0.625rem 0.75rem;
    border-radius: 0.5rem;
    background: color-mix(in srgb, var(--pink) 12%, transparent);
    color: var(--secondary);
    font-size: 0.75rem;
    line-height: 1.5;
}

.segmented.wide { margin: 0 1.25rem 0.5rem; }
.icon-button.back { width: 1.75rem; height: 1.75rem; }

/* Строка Яндекса: у перенесённого трека индикатор, у переносящегося — что идёт. */
.row-transfer { color: var(--accent-system); font-size: 0.6875rem; white-space: nowrap; }
.row[data-unavailable] { opacity: 0.4; }

/* ── Чипы большого плеера ─────────────────────────────────────────────────── */

.chips { display: flex; gap: 0.5rem; justify-content: center; padding-top: 0.75rem; flex: none; }

.chip {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.375rem 0.75rem;
    border-radius: 999px;
    background: rgb(255 255 255 / 0.12);
    color: rgb(255 255 255 / 0.85);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
}

.chip-icon { width: 0.875rem; height: 0.875rem; }
.chip-icon svg { width: 100%; height: 100%; display: block; }

/* Круглая белая кнопка воспроизведения, как в приложении: это главный предмет панели,
   и плоский глиф рядом с остальными читался как равный им. */
.icon-button.play.big {
    width: 4.5rem;
    height: 4.5rem;
    padding: 1.25rem;
    border-radius: 999px;
    background: #fff;
    color: #000;
}

/* ── Лайк ─────────────────────────────────────────────────────────────────── */

/* Движения два, и они намеренно разные: постановка лайка — событие, сердце упруго
   наезжает и от него расходится кольцо; снятие — отмена события, короткое сжатие
   и ничего больше. Одинаковая анимация в обе стороны читалась бы как выключатель
   света, а не как «нравится». Числа из `FavoriteHeart`: кольцо от 0.6 до 1.9 кегля,
   толщина 0.12 убывает, длительность вдвое больше наезда. */
.heart { position: relative; overflow: visible; }

.heart svg { transition: color 160ms ease; }
.heart[data-pop='on'] svg { animation: heart-pop 260ms cubic-bezier(0.3, 1.6, 0.5, 1); }
.heart[data-pop='off'] svg { animation: heart-squeeze 180ms ease-out; }

@keyframes heart-pop {
    0% { transform: scale(1); }
    45% { transform: scale(1.28); }
    100% { transform: scale(1); }
}

@keyframes heart-squeeze {
    0% { transform: scale(1); }
    50% { transform: scale(0.86); }
    100% { transform: scale(1); }
}

/* Кольцо рисуется только во время вспышки — в покое его нет вовсе, чтобы не участвовало
   в раскладке и не ловило нажатия. */
.heart::after { content: none; }

.heart[data-pop='on']::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 100%;
    height: 100%;
    border-radius: 999px;
    border: 0.12em solid var(--pink);
    animation: heart-ring 520ms ease-out forwards;
    pointer-events: none;
}

@keyframes heart-ring {
    0%   { transform: translate(-50%, -50%) scale(0.6); opacity: 1; border-width: 0.12em; }
    100% { transform: translate(-50%, -50%) scale(1.9); opacity: 0; border-width: 0.01em; }
}

@media (prefers-reduced-motion: reduce) {
    .heart[data-pop] svg { animation: none; }
    .heart[data-pop]::after { content: none; }
}

/* ── Плей-пауза ───────────────────────────────────────────────────────────── */

/* Треугольник разъезжается в две полосы — тот же морф, что делает `anim_playpause`
   из telegram-ios. Сам ассет здесь не используется: Lottie без библиотеки-плеера
   не отрисовать, а зависимостей у проекта нет. Поэтому две половины с одинаковым
   числом вершин и интерполяция `clip-path` — она анимируется везде, в отличие от `d`.
   Длительность 0.35 с, как в оригинале. */
.playpause { position: relative; width: 100%; height: 100%; }

.playpause i {
    position: absolute;
    inset: 0;
    background: currentColor;
    transition: clip-path 350ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Пауза — две полосы. */
.playpause i.left  { clip-path: polygon(25% 8%, 42% 8%, 42% 92%, 25% 92%); }
.playpause i.right { clip-path: polygon(58% 8%, 75% 8%, 75% 92%, 58% 92%); }

/* Плей — те же половины, сложенные в треугольник. */
.playpause[data-state='play'] i.left  { clip-path: polygon(29% 17%, 58% 36%, 58% 64%, 29% 83%); }
.playpause[data-state='play'] i.right { clip-path: polygon(58% 36%, 83% 50%, 83% 50%, 58% 64%); }

@media (prefers-reduced-motion: reduce) {
    .playpause i { transition: none; }
}

/* ── Скелетоны ────────────────────────────────────────────────────────────── */

/* Нужны только на первом запуске, когда показать нечего: дальше список приезжает
   из сохранённого до всякой сети. Пустая подсказка вместо загрузки — это интерфейс,
   который говорит неправду, пока ждёт ответа. */
.row.skeleton { pointer-events: none; }

.row.skeleton .cover,
.row.skeleton .row-title,
.row.skeleton .row-artist,
.row.skeleton .row-duration {
    background: var(--quaternary);
    border-radius: 0.375rem;
    color: transparent;
}

.row.skeleton .cover { border-radius: 0.5rem; }
.row.skeleton .row-title { width: 60%; height: 0.9375rem; }
.row.skeleton .row-artist { width: 35%; height: 0.75rem; }
.row.skeleton .row-duration { width: 2rem; height: 0.75rem; }

/* Пульсация одна на всю группу, а не на каждый элемент: разнобой фаз читается как
   мигание, а не как ожидание. */
@media (prefers-reduced-motion: no-preference) {
    .rows.loading,
    .albums.loading { animation: skeleton 1.4s ease-in-out infinite; }
}

@keyframes skeleton {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

/* ── Модалка очереди ──────────────────────────────────────────────────────── */

/* Тот же лист, что у большого плеера: выезжает снизу до верха, тянется и закрывается
   пальцем. Фон нейтральный, а не от обложки: здесь смотрят список, а не обложку. */
.queue-sheet {
    background: var(--bg);
    color: var(--primary);
    padding-bottom: 0;
}

.queue-sheet .sheet-head { flex: none; padding: 0.5rem 0; }
.queue-sheet .sheet-head .icon-button { color: var(--primary); }
.queue-sheet .sheet-group { color: var(--primary); font-size: 1.0625rem; }

/* Отступ по краям здесь **один** — тот, что уже задал сам лист.
   `.rows` добавляет свои 10 и строка ещё 10, и в узком листе это складывалось в двойное
   поле: содержимое очереди стояло заметно уже, чем список на экране под ним. Строки при
   этом остаются с вертикальными отступами — они про высоту строки, а не про поля. */
.queue-rows {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0 0 max(1.5rem, env(safe-area-inset-bottom));
}

.queue-rows .row {
    padding-left: 0;
    padding-right: 0;
    border-radius: 0.625rem;
}

/* Строка очереди целиком: сама строка плюс то, что появляется в режиме правки.
   Обёртка нужна, чтобы не ломать сетку строки — у неё пять колонок с точными
   размерами, и вставленные внутрь ручка с минусом сдвинули бы обложку и название. */
.queue-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.queue-item > .row { flex: 1; min-width: 0; }

/* Пока тянут — строка едет за пальцем и лежит поверх соседей, а соседи расступаются
   плавно. Переход только у них: у самой строки он превратил бы движение в вязкое. */
.queue-item { transition: transform 160ms ease; }
.queue-item[data-dragging] {
    transition: none;
    position: relative;
    z-index: 2;
    background: var(--bg);
    box-shadow: 0 8px 24px rgb(0 0 0 / 0.45);
    border-radius: 0.625rem;
}

.queue-remove { width: 1.375rem; height: 1.375rem; flex: none; color: #ff453a; }
.queue-handle { width: 1.375rem; height: 1.375rem; flex: none; color: var(--tertiary); }
.queue-handle { cursor: grab; touch-action: none; }
.queue-item[data-dragging] .queue-handle { cursor: grabbing; }

/* Кнопка «Править» в шапке листа: текстовая, без плашки — рядом с ней только заголовок,
   и залитая кнопка перетягивала бы на себя всё внимание. */
.button.ghost {
    background: none;
    color: var(--accent-system);
    padding: 0.25rem 0.375rem;
    font-size: 0.9375rem;
}

@media (min-width: 860px) {
    /* На большом экране лист во всю ширину читался бы как отдельная страница.
       Ограничиваем и центрируем — это по-прежнему модалка поверх содержимого. */
    .queue-sheet {
        left: 50%;
        right: auto;
        transform: translateX(-50%) translateY(var(--drag, 0px));
        width: min(34rem, 100%);
        border-radius: 1.25rem 1.25rem 0 0;
        border: 0.5px solid var(--separator);
        box-shadow: 0 -0.5rem 2rem rgb(0 0 0 / 0.35);
    }

    /* Выезд снизу считается вместе с центрированием: одно `transform` на оба,
       иначе они перебивают друг друга и лист прыгает по горизонтали. */
    .queue-sheet[data-entering] { transform: translateX(-50%) translateY(100%); }
}

/* ── Вместе ───────────────────────────────────────────────────────────────── */

.code-input {
    width: 6.5rem;
    flex: none;
    text-align: center;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    font-weight: 600;
}

/* Имя и кубик — одной группой: кубик прижат к полю, а не висит отдельной кнопкой
   у края строки, иначе связь между ними приходится угадывать. */
.name-field {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    flex: none;
}

.name-field .search {
    width: 10rem;
    text-align: right;
}

.name-field .icon-button {
    width: 1.5rem;
    height: 1.5rem;
    color: var(--secondary);
}

/* ── Исполнитель ──────────────────────────────────────────────────────────── */

/* Отступ по краям — общий для всего экрана исполнителя.
   1.25rem, а не 1rem: столько же от края до обложки в строке трека (10 у списка плюс 10
   у самой строки). Разные числа читались как перекос — портрет, заголовки и плитки
   стояли левее списка под ними. */
.artist-head,
.albums,
.block-title,
.block-head {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
}

/* Внутри строки заголовка отступ уже задан ей самой — иначе он сложился бы вдвое. */
.block-head > .block-title { padding-left: 0; padding-right: 0; }

/* Заголовок раздела, который сам является переходом («Популярное ›»). Кнопкой он стал,
   а выглядеть должен заголовком: ни рамки, ни фона — только шеврон, который и говорит,
   что здесь есть продолжение. */
.block-head {
    width: 100%;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: default;
}

.block-head.as-link { cursor: pointer; }
.block-head:not(.as-link) .block-more { display: none; }

.block-more {
    display: block;
    width: 1.125rem;
    height: 1.125rem;
    color: var(--tertiary);
    align-self: center;
}

.block-more svg { width: 100%; height: 100%; display: block; }
.block-head.as-link:active .block-title { opacity: 0.6; }

/* Шапка списка треков: обложка альбома квадратная, портрет исполнителя круглый —
   разница та же, что между «это вещь» и «это человек». */
.album-head {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.5rem 1.25rem 1.125rem;
}

.album-art {
    width: 5.5rem;
    height: 5.5rem;
    flex: none;
    border-radius: 0.5rem;
    background: var(--quaternary) center/cover no-repeat;
}

.album-art.round { border-radius: 50%; }
.album-meta { min-width: 0; }

.album-name {
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.2;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

/* Плитка-заглушка: та же геометрия, что у настоящей. Пульсация — на всей ленте, а не
   на каждой плитке: разнобой фаз читается как мигание, а не как ожидание (то же правило,
   что у скелетонов строк). */
.album.skeleton { pointer-events: none; }
.album.skeleton .album-cover,
.album.skeleton .album-title,
.album.skeleton .album-note {
    background: var(--quaternary);
    border-radius: 0.25rem;
}

.album.skeleton .album-title { height: 0.8125rem; margin-top: 0.5rem; width: 80%; }
.album.skeleton .album-note { height: 0.6875rem; margin-top: 0.375rem; width: 55%; }

/* Заголовок раздела и действие рядом с ним — по краям одной строки: «Все треки» тут
   читается как «показать больше того же», а не как самостоятельная кнопка, которую
   надо искать глазами под списком. */
.block-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}

.artist-head {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding-top: 0.5rem;
    padding-bottom: 1.125rem;
}

/* Портрет круглый, как в системной Музыке: так он отличается от квадратных обложек
   и читается как «человек», а не «ещё один альбом». */
.artist-photo {
    width: 5.5rem;
    height: 5.5rem;
    flex: none;
    border-radius: 50%;
    background: var(--quaternary) center/cover no-repeat;
}

.artist-meta { min-width: 0; }
.artist-name {
    font-size: 1.4375rem;
    font-weight: 700;
    line-height: 1.15;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.artist-note {
    color: var(--secondary);
    font-size: 0.8125rem;
    margin-top: 0.25rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Подпись раздела внутри экрана. Отдельно от `.section-title` бокового меню: там она
   набрана капителью и мелко, а здесь это заголовок содержимого. */
.block-title {
    font-size: 1.0625rem;
    font-weight: 600;
    padding-top: 1rem;
    padding-bottom: 0.5rem;
}

/* Лента альбомов: горизонтальная прокрутка с прилипанием, как полки в Музыке.
   Отступ по краям задан выше общим правилом, а `scroll-padding` повторяет его — иначе
   прилипание доводит плитку вплотную к краю, и первая карточка выглядит обрезанной. */
.albums {
    display: flex;
    gap: 0.75rem;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scroll-padding-left: 1.25rem;
    padding-bottom: 0.375rem;
    scrollbar-width: none;
}

.albums::-webkit-scrollbar { display: none; }
.albums:empty { display: none; }

.album {
    flex: none;
    width: 8.25rem;
    scroll-snap-align: start;
    border: 0;
    padding: 0;
    background: none;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.album-cover {
    width: 8.25rem;
    height: 8.25rem;
    border-radius: 0.5rem;
    background: var(--quaternary) center/cover no-repeat;
}

.album-title {
    font-size: 0.8125rem;
    margin-top: 0.375rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.album-note {
    font-size: 0.75rem;
    color: var(--secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.album:active .album-cover { opacity: 0.7; }

/* Имя исполнителя нажимается, но выглядит текстом, а не ссылкой: подчёркивание и цвет
   в списке из полусотни строк превратили бы его в частокол. Что оно нажимается, видно
   по нажатию — так же, как в системной Музыке. */
.artist-link {
    border: 0;
    padding: 0;
    margin: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.artist-link:hover { text-decoration: underline; }
.artist-link:active { color: var(--tint); }

.members { display: flex; flex-wrap: wrap; gap: 0.375rem; margin-top: 0.875rem; }

.member {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.3125rem 0.625rem;
    border-radius: 999px;
    background: var(--quaternary);
    font-size: 0.8125rem;
}

/* Ведущий идёт первым и помечен: он не один из участников, а тот, чьи решения все слышат,
   и искать его в алфавитном списке неправильно. */
.member[data-leader] { background: color-mix(in srgb, var(--accent-system) 22%, transparent); }
.member[data-leader]::before { content: '★'; font-size: 0.6875rem; color: var(--accent-system); }

/* ── Диалог ───────────────────────────────────────────────────────────────── */

.modal {
    position: fixed;
    inset: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: rgb(0 0 0 / 0.45);
    backdrop-filter: blur(4px);
}

.modal[hidden] { display: none; }

.modal-box {
    width: min(22rem, 100%);
    padding: 1.25rem;
    border-radius: 1rem;
    background: var(--bg-raised);
    box-shadow: 0 1rem 3rem rgb(0 0 0 / 0.4);
}

.modal-title { font-size: 1.0625rem; font-weight: 600; }
.modal-text { color: var(--secondary); font-size: 0.875rem; line-height: 1.45; margin-top: 0.375rem; }
.modal-actions { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 1.125rem; }
