/* ── полка ─────────────────────────────────────────────────────── */

/* Список — точка в центре сцены, а не колонка: где стоит каждый конверт,
   считает ShelfLayout, и ShelfScroller пишет слоту один transform.
   Раскладка страницы при прокрутке не меняется вовсе. */
.list {
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 0;
    margin: 0;
    padding: 0;
    list-style-type: none;

    transform-style: preserve-3d;
}

/* Пробник: его ширина — --vinyl-size в пикселях (он зависит от размера
   сцены и настройки «Размер конвертов»). Его и меряет ShelfScroller. */
.shelf-probe {
    position: absolute;
    top: 0;
    left: 0;
    width: var(--vinyl-size);
    height: 0;
    visibility: hidden;
    pointer-events: none;
}

/* ── размытие слоем поверх полки (настройка «Как размывать» → «Слоем») ──
   Размывается не каждая грань своим filter, а то, что видно сквозь
   слой (backdrop-filter): швы между корешком и обложкой не расплываются
   по отдельности, и покадрово в стиль пишутся три числа слою, а не
   фильтры тридцати граней.

       ░░░░░░░░░░░░░░░  --edge-alpha — у краёв (и не меньше --floor)
       ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       ═══ резкая зона ═══  ±--sharp от центра: конверт, что через него
       ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒       проходит (играющий стоит — резок целиком);
       ░░░░░░░░░░░░░░░       в движении и она мутнеет до --floor

   Слой ОДИН: у backdrop-filter один радиус, нарастание — маской, а
   размытие в движении — той же маской, поднятой по всей высоте. Каждый
   лишний слой — ещё один проход размытия по всей сцене на каждом кадре.
   Пока размывать нечего, слой выключен (.shelf-blur--on снят).

   Сам .shelf-blur не должен быть «backdrop root»: никаких opacity,
   filter и mask на нём — иначе слою внутри нечего было бы размывать. */
.shelf-blur {
    --sharp: calc(var(--vinyl-size) / 7);
    --edge-alpha: 0;
    --floor: 0;

    display: none;
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.shelf-blur--on { display: block; }

.shelf-blur__layer {
    --to: calc(var(--sharp) + 220px);

    /* Только колонка полки: размывать пустой фон по бокам — та же цена
       за пиксель, что и полку (на всю ширину десктопа было вдвое медленнее).
       Ширина — с играющим: конверт отъезжает вправо, пластинка влево,
       вместе это ±0.85 обложки от центра; плюс запас на перспективу. */
    position: absolute;
    inset: 0 max(0px, calc(50% - var(--vinyl-size) * 0.95));

    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    -webkit-mask-image: linear-gradient(to bottom,
        rgb(0 0 0 / var(--edge-alpha)) calc(50% - var(--to)),
        rgb(0 0 0 / var(--floor)) calc(50% - var(--sharp)),
        rgb(0 0 0 / var(--floor)) calc(50% + var(--sharp)),
        rgb(0 0 0 / var(--edge-alpha)) calc(50% + var(--to)));
    mask-image: linear-gradient(to bottom,
        rgb(0 0 0 / var(--edge-alpha)) calc(50% - var(--to)),
        rgb(0 0 0 / var(--floor)) calc(50% - var(--sharp)),
        rgb(0 0 0 / var(--floor)) calc(50% + var(--sharp)),
        rgb(0 0 0 / var(--edge-alpha)) calc(50% + var(--to)));
}

/* нечем размывать — слоя нет, полка просто резкая */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .shelf-blur { display: none !important; }
}

.list:focus-visible { outline: none; }
.list:focus-visible .slot--focused .vinyl__side {
    outline: 2px solid var(--focus);
    outline-offset: 4px;
}

/* ── слот: место в стопке ─────────────────────────────────────────
   Высоту места в стопке (у текущего — целая обложка, он стоит; у
   отсеянного — ноль) ведёт модель, а не отступы: раньше их анимировал
   CSS, а камера догоняла — отсюда и «не успевает», и «не по центру».
   Сам слот — полоска толщиной в конверт, центр которой в точке y. */
.slot {
    --vinyl-thickness: calc(var(--vinyl-size) / 20);
    --vinyl-half-thickness: calc(var(--vinyl-thickness) / 2);

    user-select: none;
    -webkit-user-select: none;

    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: var(--vinyl-thickness);
    margin-top: calc(var(--vinyl-thickness) / -2);

    display: flex;
    justify-content: center;
    align-items: center;

    transform-style: preserve-3d;
}

/* Далеко за экраном — не рисуем вообще. Классом, а не inline-стилем:
   так правило ниже про диск может его учесть. */
.slot--culled { visibility: hidden; }

/* ── состояния конверта ───────────────────────────────────────
   Селекторы взаимоисключающие: :not() гарантирует, что два
   правила никогда не совпадут одновременно. Поэтому ни порядок
   строк, ни специфичность на результат не влияют.             */

@media (hover: hover) {
    .slot:not(.slot--current, .slot--focused, .slot--gone, .slot--swap, .slot--loading):hover .vinyl {
        transform: translateZ(calc(var(--vinyl-size) / 4));
    }
}

/* ближайший к центру — слегка подан вперёд, плашмя */
.slot--focused:not(.slot--current, .slot--gone, .slot--swap, .slot--loading) .vinyl {
    transform: translateZ(calc(var(--vinyl-size) / 4));
}

/* Текущий — конверт встаёт и разворачивается лицом к зрителю.
   translateX уводит пару «пластинка + конверт» влево ровно настолько,
   чтобы она осталась в центре сцены: конверт отъезжает вправо, и без
   этой поправки вся связка висела бы правее середины. */
.slot--current .vinyl {
    transform: translateX(calc(var(--vinyl-size) * -0.38))
               translateZ(calc(var(--vinyl-size) / 2))
               rotateX(-90deg);
}

/* ── скачивается ──────────────────────────────────────────────
   Конверт проявляется вместе с загрузкой: прозрачность граней и есть
   полоса прогресса. Лёгкий призрак с самого начала — чтобы было видно,
   куда встанет трек. Опять на гранях, не на .slot: см. ниже про
   transform-style. Правило стоит ДО «ухода»: если скачать не вышло,
   конверт гаснет, как отсеянный. */
.slot--pending .vinyl__box > * {
    opacity: calc(.08 + var(--progress, 0) * .92);
    transition: opacity 700ms linear;
}

/* ── уход и приход ─────────────────────────────────────────────
   Двигаем .vinyl, а НЕ .slot: слоту покадрово пишет transform
   ShelfScroller, и второй хозяин у того же свойства — это ровно
   та ошибка, на которой уже спотыкались. Правила идут последними,
   чтобы при равной специфичности побеждать состояния выше.

   Отсеянные уходят ВНИЗ и гаснут. Прозрачность вешаем на грани, а не на
   .slot и не на .vinyl: opacity меньше единицы принудительно даёт
   transform-style: flat, и коробка схлопнулась бы. Грани плоские,
   им терять нечего. */
.slot--gone { pointer-events: none; }

.slot--gone .vinyl,
.slot--swap .vinyl,
.slot--loading .vinyl { transform: translateY(160px); }

.slot--gone .vinyl__box > *,
.slot--swap .vinyl__box > *,
.slot--loading .vinyl__box > * { opacity: 0; }

/* Уход и приход длиннее обычной смены состояния: тут важна плавность,
   а не отзывчивость. Правило действует, пока класс висит, — то есть
   на уходе; возвращается конверт базовым переходом .vinyl. */
.slot--gone .vinyl,
.slot--swap .vinyl,
.slot--loading .vinyl {
    transition: transform var(--dur-slow) var(--ease-dive);
}

/* Отсеянный и уже схлопнутый моделью — не рисуем вовсе (класс ставит
   ShelfScroller, когда место конверта сошло на нет). */
.slot--hidden {
    visibility: hidden;
    pointer-events: none;
}

/* Меньше движения: конверты не ныряют — просто гаснут на месте */
:root[data-motion="reduce"] .slot--gone .vinyl,
:root[data-motion="reduce"] .slot--swap .vinyl,
:root[data-motion="reduce"] .slot--loading .vinyl { transform: none; }
