/* ==========================================================================
   ICE FISHING — ВЗВЕШИВАНИЕ УЛОВА

   Играется поверх колеса, внутри .ice-wheel: у того overflow: hidden, поэтому
   сцена сама обрезается по окну и выглядит как действие в кадре, а не как
   всплывшее поверх страницы окно.

   Разметку строит public/js/ice-scene.js, там же система координат и шкалы.

   ХОРЕОГРАФИЯ:

       0.00  затемнение, колесо уходит в расфокус
       0.10  выезжает прибор: шкала, деления
       0.14  снизу выезжает тарелка весов и встаёт к 0.46
       0.30  рыба начинает падать, тень сбегается под неё
       0.50  УДАР: рыба легла, тарелка просела, стрелка пошла, табло считает
       …     длительность взвешивания зависит от ступени (1.25…1.9 с)
       +0.9  пауза на прочитать число
             сцена уходит

   ВСЁ СХОДИТСЯ В ОДИН КАДР 0.50. Раньше три события стояли врозь: рыба
   касалась тарелки около 0.5, стрелка трогалась в 0.35 (то есть взвешивала
   ещё летящую рыбу), а тарелка приседала в 0.62 — через треть секунды после
   удара, будто спохватившись. Теперь удар, просадка и начало отсчёта — одно
   мгновение, и оно же LEAD_MS в ice-scene.js. Двигать только вместе.

   САМОГО ХОДА ВЗВЕШИВАНИЯ ЗДЕСЬ НЕТ, и это осознанно.

   Заливка дуги, бегунок и число на табло показывают одну и ту же величину, и
   разъезжаться им нельзя ни на кадр. Пока их вело CSS, они разъезжались:
   поворот бегунка — это transform, браузер уносит его на композитор и считает
   отдельным потоком, а stroke-dashoffset заливки на композитор не переносится
   никогда и остаётся на основном — вместе с раскладкой, размытием пелены и
   распаковкой картинки рыбы. Задержись основной поток на пару кадров, и
   кружок уезжал по пустой шкале, а линия догоняла его рывком.

   Одинаковые ключевые кадры это не лечили: у двух потоков разные часы.

   Поэтому все три величины выставляет один цикл rAF — runSweep() в
   ice-scene.js. Здесь остались только выезд прибора, тарелка, падение рыбы и
   оформление: то, чему синхронность ни с чем не нужна.

   --------------------------------------------------------------------------
   КАРТИНКИ УЛОВА

   Прибор рисует код, улов рисует художник (почему так — в шапке
   ice-scene.js). Четыре файла в /images/fishing/:

       pan.webp            2117×743   тарелка весов, одна на все ступени
       catch-blues.webp    1774×887   рыба Lil' Blues
       catch-oranges.webp  1774×887   рыба Big Oranges
       catch-reds.webp     1774×887   рыба Huge Reds

   ГДЕ ЛЕЖАТ ЧИСЛА. Не здесь. Холст у файлов общий, но рисунок внутри у
   каждого свой — своей длины, высоты и с разными пустыми полями. Поэтому
   код опирается не на требования ТЗ, а на ОБМЕР присланных файлов: доли
   холста лежат в ART в ice-scene.js, а снимаются они с альфа-канала
   скриптом measure.js. Переэкспортируют файл — перемерить и поправить одну
   строку в ART.

   ЧТО ОСТАЁТСЯ ТРЕБОВАНИЕМ К ЛЮБОМУ НОВОМУ ФАЙЛУ:
       прозрачный фон, без нарисованной тени и подложки — и то и другое
       кладёт сцена, в цвет ступени;
       строго вид сбоку, головой ВПРАВО, тело горизонтально;
       свет сверху, тон в тон с дольками колеса;
       поля вокруг рисунка любые — код смотрит на сам рисунок, а не на края
       холста.

   Пока пути в ART пусты, работает векторный запасной улов.
   ========================================================================== */

.ice-wg {
    position: absolute;
    inset: 0;
    z-index: 6;
    overflow: hidden;
    border-radius: inherit;
    transition: opacity .34s ease;

    /* Цвет ступени. Задаётся один раз здесь и растекается по дуге, бегунку,
       рыбе и свечению — перекрашивать каждую деталь порознь значит однажды
       забыть одну.

       ТРИ ТОНА, А НЕ ОДИН: --wg-deep уходит в начало шкалы, --wg-lite — в
       её конец, --wg остаётся основным и красит бегунок, плашку и подсветку
       под уловом.

       --wg-deep НАРОЧНО НЕ САМЫЙ ТЁМНЫЙ. Сначала я взял под него настоящую
       тень цвета (#1d4ed8 у синей) — и на малых множителях, когда видна
       только левая треть дуги, эта треть сливалась с тёмным пазом под ней:
       заливка выглядела не начавшейся. Тон подобран так, чтобы читаться на
       пазу с первого же деления. */
    --wg: #60a5fa;
    --wg-lite: #a9d0ff;
    --wg-deep: #2f6fe0;
    --wg-soft: rgba(96, 165, 250, .28);
}

.ice-wg--oranges {
    --wg: #fb923c;
    --wg-lite: #ffc98f;
    --wg-deep: #e2600f;
    --wg-soft: rgba(251, 146, 60, .3);
}

.ice-wg--reds {
    --wg: #f87171;
    --wg-lite: #ffb3a7;
    --wg-deep: #d8322c;
    --wg-soft: rgba(248, 113, 113, .32);
}

.ice-wg.is-out { opacity: 0; }

/* ПОЛНОЭКРАННАЯ СЦЕНА — только телефон, см. FULL_MAX_W в ice-scene.js.
 *
 * В окне колеса на телефоне сцена занимала полоску в пятую часть экрана по
 * высоте, и взвешивание читалось мелкой заставкой. Здесь она забирает экран
 * целиком: ставки в этот момент уже закрыты, колесо остановилось, и смотреть
 * всё равно больше не на что.
 *
 * 1040 — выше шапки сайта (1000), нижней панели (950) и панели ставки (940),
 * но ниже меню (1100): открытое меню должно оставаться сверху. */
.ice-wg--full {
    position: fixed;
    inset: 0;
    z-index: 1040;
    border-radius: 0;
}

/* Затемнение с расфокусом: колесо остаётся видно, но перестаёт спорить за
   внимание. Полностью гасить его нельзя — игрок теряет, где находится.
   Подсветка снизу в цвет ступени: красная рыба «греет» кадр сильнее синей.

   Третьим слоем — притемнение сверху. Оно появилось не для красоты: подписи
   делений стоят у самого верха кадра, а под ними просвечивают дольки колеса,
   и на светлой дольке цифра теряется. */
.ice-wg__veil {
    position: absolute;
    inset: 0;
    background:
        linear-gradient(180deg, rgba(4, 12, 22, .5) 0%, rgba(4, 12, 22, 0) 34%),
        radial-gradient(60% 90% at 50% 105%, var(--wg-soft) 0%, transparent 70%),
        radial-gradient(120% 140% at 50% 100%, rgba(10, 24, 40, .84) 0%, rgba(6, 14, 24, .97) 100%);
    backdrop-filter: blur(5px);
    animation: wgVeil .3s ease both;
}

/* meet, а не slice: прибор должен помещаться целиком при любой пропорции
   окна. Обрежь мы его по краям — исчезнут крайние деления шкалы, то есть
   ровно те, по которым читается предел. */
.ice-wg__art {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    animation: wgRise .4s cubic-bezier(.2, .9, .3, 1.1) .1s both;
}

/* ------------------------------------------------------------- ШКАЛА */

/* ПАЗ ПОД ДУГОЙ. Широкая тёмная полоса ровно по траектории заливки: дуга
   лежит не на фоне, а в углублении, и прибор получает толщину. Одна фигура
   вместо любых теней и фильтров — дешевле для слабых машин не бывает. */
.ice-wg__groove {
    fill: none;
    stroke: rgba(4, 12, 22, .55);
    stroke-width: calc(26px * var(--s, 1));
    stroke-linecap: round;
}

/* Базовая линия делений. Без неё штрихи висят в пустоте по отдельности и
   пояс шкалы разваливается на пунктир. */
.ice-wg__rim {
    fill: none;
    stroke: rgba(148, 180, 220, .16);
    stroke-width: calc(1.5px * var(--s, 1));
}

/* НЕЗАЛИТАЯ ЧАСТЬ ШКАЛЫ. Была почти невидима (.13 поверх тёмного паза), и
   правая половина купола читалась не как «сюда лента ещё не дошла», а как
   провал в приборе. Теперь это различимый жёлоб. */
.ice-wg__track {
    fill: none;
    stroke: rgba(160, 192, 232, .2);
    stroke-width: calc(12px * var(--s, 1));
    stroke-linecap: round;
}

/* Заливка дуги. Длину ей выставляет runSweep() из ice-scene.js через
   stroke-dashoffset — здесь только вид.
   Цвет приходит градиентом из разметки (stroke=url(...)), поэтому stroke тут
   не задаётся: напиши его — и он перебьёт градиент, дуга станет одноцветной.
   Свечение — drop-shadow по мягкому тону, не по основному: по основному оно
   на светлых ступенях превращается в ореол и съедает деления. */
.ice-wg__fill {
    fill: none;
    /* На единицу толще жёлоба: лента не лежит в нём заподлицо, а чуть
       выступает — прибор получает ещё один слой глубины даром. */
    stroke-width: calc(13px * var(--s, 1));
    stroke-linecap: round;
    filter: drop-shadow(0 0 12px var(--wg-soft));
}

/* Блик вдоль внешнего края ленты. Узкий и полупрозрачный: он должен
   читаться как свет на выпуклости, а не как вторая линия рядом. */
.ice-wg__fill-hi {
    fill: none;
    stroke: var(--wg-lite);
    stroke-width: calc(3px * var(--s, 1));
    stroke-linecap: round;
    opacity: .5;
}

.ice-wg__tick {
    stroke: rgba(148, 180, 220, .32);
    stroke-width: calc(2px * var(--s, 1));
    stroke-linecap: round;
}

.ice-wg__tick--big {
    stroke: rgba(203, 224, 245, .72);
    stroke-width: calc(3.5px * var(--s, 1));
}

.ice-wg__tick-num {
    fill: rgba(186, 209, 233, .78);
    font-size: calc(15px * var(--s, 1));
    font-weight: 700;
    text-anchor: middle;
    font-variant-numeric: tabular-nums;
    paint-order: stroke;
    stroke: rgba(6, 14, 24, .6);
    stroke-width: calc(4px * var(--s, 1));
    stroke-linejoin: round;
}

/* БЕГУНОК ЕДЕТ ПОВОРОТОМ ВСЕЙ ГРУППЫ вокруг центра дуги, а не пересчётом
   координат на каждом кадре. Так он идёт строго по дуге и не может с неё
   соскочить из-за округления.
   transform-box: view-box обязателен: без него точка поворота считается от
   рамки группы, а она меняется вместе с содержимым. */
.ice-wg__headwrap {
    transform-box: view-box;
    /* Центр дуги приходит из разметки (--cx/--cy), а не записан здесь числом.
       Держи мы его копией — поменяй кто-то радиус или высоту прибора в
       ice-scene.js, и бегунок пошёл бы по окружности мимо дуги, а причину
       пришлось бы искать сверкой двух файлов. */
    transform-origin: var(--cx, 450px) var(--cy, 210px);
}

/* Три круга вместо одного: ореол, белое кольцо, точка в центре. Точка и есть
   то, что читается как «указатель»: у сплошного белого кружка нет центра, и
   на глаз непонятно, чем именно он показывает. */
.ice-wg__halo {
    fill: var(--wg);
    opacity: .22;
}

.ice-wg__head {
    fill: #ffffff;
    stroke: var(--wg);
    stroke-width: calc(5px * var(--s, 1));
    filter: drop-shadow(0 0 9px var(--wg));
}

.ice-wg__pin { fill: var(--wg); }

/* ----------------------------------------------------------- УЛОВ */

/* Одна группа на весь улов — и картинки, и запасной вектор живут в ней. Она
   поднимает улов в кадр (wgPan) и она же проседает под весом (wgSettle).
   Разделены на две анимации, а не на одни ключевые кадры: склей их — и момент
   просадки окажется привязан к моменту выезда, сдвинешь один, поедет другой. */
/* ФОРМЫ ЗАПОЛНЕНИЯ У НИХ РАЗНЫЕ, И ЭТО НЕ ОПЕЧАТКА.
 *
 * Обе анимации правят одно свойство — transform, — и при наложении не
 * складываются: побеждает та, что позже в списке. У wgSettle стояло both, то
 * есть она применяла свой нулевой кадр (translateY(0)) С САМОГО НАЧАЛА сцены,
 * ещё до своей задержки, — и всё это время перебивала wgPan.
 *
 * Тарелка не выезжала снизу вообще НИ РАЗУ: она просто стояла на месте, а
 * потом, через полсекунды после падения рыбы, вдруг приседала. Ровно то, что
 * выглядело сломанным.
 *
 * forwards у второй анимации назад не заполняет — до своей задержки она
 * молчит, и выезд снова виден. */
.ice-wg__plate {
    animation:
        wgPan .32s cubic-bezier(.2, .9, .3, 1.15) .14s both,
        wgSettle .44s cubic-bezier(.25, .8, .3, 1.1) .5s forwards;
}

/* Подсветка под уловом — часть сцены, а не картинки. Поэтому она одинаково
   работает и с рисованной тарелкой, и с векторной, и поэтому же в ТЗ стоит
   требование прозрачного фона: подсвети она прямоугольную подложку, вокруг
   улова проступил бы светящийся паспарту. */
.ice-wg__glow { pointer-events: none; }

/* ПАДЕНИЕ РЫБЫ. Класс общий для картинки и для вектора: движение принадлежит
   сцене, а не тому, чем именно нарисована рыба.

   Кривая РАЗГОННАЯ, а не пружинная. Раньше здесь стояла cubic-bezier с
   перелётом (.3, 1.5, .5, 1): рыба подлетала выше конечной точки и мягко
   возвращалась, то есть касалась тарелки где-то в середине анимации и потом
   ещё колыхалась. Момент удара размазывался, и приседанию тарелки не за что
   было зацепиться. Теперь рыба просто разгоняется вниз и в конце анимации
   ровно в этот кадр ударяется — на нём и стоит просадка. */
.ice-wg__fish {
    transform-box: fill-box;
    transform-origin: 50% 88%;
    animation: wgDrop .2s cubic-bezier(.5, .05, .75, .3) .3s both;
}

/* Тень улова на тарелке. Не появляется готовой, а СБЕГАЕТСЯ под падающую
   рыбу: пока та высоко, тень широкая и бледная, к удару — короткая и
   плотная. Это единственная подсказка о высоте падения, потому что сама рыба
   при взгляде сбоку падает по прямой и своей высоты не показывает. */
.ice-wg__shadow {
    fill: rgba(4, 12, 22, .45);
    transform-box: fill-box;
    transform-origin: center;
    animation: wgShadow .2s cubic-bezier(.5, .05, .75, .3) .3s both;
}

/* КАРТИНКИ УЛОВА. Тени у тарелки и рыбы разной глубины, но одного
   направления — сцена освещена сверху, и рисованный улов обязан отбрасывать
   тень так же, как отбрасывал векторный: иначе подмена ассетов читается как
   сдвиг света в кадре. */
.ice-wg__pan-art { filter: drop-shadow(0 5px 7px rgba(4, 12, 22, .55)); }
.ice-wg__catch   { filter: drop-shadow(0 4px 6px rgba(4, 12, 22, .5)); }

/* --------------------------------------------- ЗАПАСНОЙ ВЕКТОРНЫЙ УЛОВ

   Всё, что ниже, живёт только пока в ART (ice-scene.js) не вписаны пути к
   картинкам. Удалять после их появления не нужно и вредно: файл может не
   доехать до продакшена, а пустая тарелка вместо улова — это сломанная
   сцена, а не «ассеты в пути». */

.ice-wg__pan { filter: drop-shadow(0 4px 6px rgba(4, 12, 22, .55)); }

/* Блик по переднему краю тарелки. Тонкая светлая дуга поверх градиента —
   ровно тот минимум, который отличает металл от серого овала. */
.ice-wg__pan-lip {
    fill: none;
    stroke: rgba(232, 243, 255, .55);
    stroke-width: 2;
    stroke-linecap: round;
}

/* Стойка уходит за нижний край кадра, поэтому основания у неё нет и быть не
   должно: обрубленная стойка читается как «прибор больше окна». */
.ice-wg__post {
    fill: rgba(41, 56, 74, .95);
    stroke: rgba(148, 180, 220, .16);
    stroke-width: 1.5;
}

/* Тонкая тёмная обводка отделяет рыбу от металла тарелки: цвета у них разной
   светлоты, но на маленьком экране силуэт всё равно слипался бы. */
.ice-wg__body {
    stroke: rgba(6, 16, 28, .45);
    stroke-width: 2;
    stroke-linejoin: round;
}

.ice-wg__fin { opacity: .82; }

/* Грудной плавник лежит ПОВЕРХ тела, поэтому он светлее прочих: иначе он
   сливается с брюхом, к которому и примыкает. */
.ice-wg__fin--front { opacity: .6; }

/* Жабра и рот — обводкой, а не заливкой: это линии, а не фигуры. */
.ice-wg__gill {
    fill: none;
    stroke: rgba(6, 16, 28, .3);
    stroke-width: 2;
    stroke-linecap: round;
}

.ice-wg__eye { fill: #0b1a2c; }
.ice-wg__glint { fill: rgba(255, 255, 255, .9); }

/* ХВОСТ БЬЁТ ОДИН РАЗ, когда рыба легла. Не по кругу и не бесконечно:
   бесконечное движение в углу кадра тянет взгляд на себя, а смотреть игрок
   должен на табло. Один удар говорит «улов живой» и замолкает.

   ТОЧКА ВРАЩЕНИЯ — ПРАВЫЙ КРАЙ САМОГО ХВОСТА, и это не приблизительно:
   фигура хвоста нарисована так, что её самая правая точка и есть место
   примыкания к телу (см. путь tail в ice-scene.js — вырез между лопастями
   уходит влево и за край не выходит). Поэтому fill-box + 100% 50% попадают
   в стык ровно, без единого числа, которое пришлось бы держать синхронным с
   разметкой. Через view-box так не выйдет: там начало координат зависит от
   вложенных transform у групп рыбы и платформы. */
.ice-wg__tail {
    transform-box: fill-box;
    transform-origin: 100% 50%;
    animation: wgTail .85s cubic-bezier(.4, .1, .4, 1) .52s both;
}

/* --------------------------------------------------------------- ТАБЛО */

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

   КЕГЛЬ ЗАДАЁТ РАЗМЕТКА (style="font-size"), а не это правило: он зависит от
   длины итогового числа, и ×10 000 набирается мельче, чем ×16. Ставить его
   здесь значило бы либо обрезать длинные числа, либо мельчить короткие. */
.ice-wg__value {
    fill: #ffffff;
    font-weight: 900;
    letter-spacing: -.03em;
    text-anchor: middle;
    font-variant-numeric: tabular-nums;
    paint-order: stroke;
    stroke: rgba(6, 14, 24, .6);
    stroke-width: calc(8px * var(--s, 1));
    stroke-linejoin: round;
    animation: wgValue .3s ease .42s both;
}

/* Ступень — плашкой в цвет: надпись перестаёт висеть строкой и связывается с
   дугой и рыбой того же цвета. */
.ice-wg__chip { animation: wgValue .3s ease .2s both; }

.ice-wg__chip rect {
    fill: rgba(8, 20, 34, .55);
    stroke: var(--wg);
    stroke-width: calc(1.5px * var(--s, 1));
    opacity: .9;
}

.ice-wg__chip text {
    fill: var(--wg);
    /* Кегль и разрядка ужаты вместе с плашкой: она стоит внутри купола, а
       тот кверху сужается — прежние 15px с разрядкой .16em выносили углы
       плашки на паз дуги. */
    font-size: calc(13px * var(--s, 1));
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
    text-anchor: middle;
}

.ice-wg__unit {
    /* Чуть светлее прежнего: подпись стояла на грани различимости и на
       телефоне пропадала вовсе. */
    fill: rgba(182, 206, 232, .72);
    font-size: calc(13px * var(--s, 1));
    font-weight: 600;
    letter-spacing: .1em;
    text-anchor: middle;
    animation: wgValue .3s ease .4s both;
}

/* Досчитали — короткая вспышка табло. Единственная награда, которую сцена
   даёт помимо самого числа, и она же говорит «всё, это окончательный вес». */
.ice-wg.is-done .ice-wg__value { animation: wgPop .34s cubic-bezier(.2, .9, .3, 1.6) both; }

/* Длительность взвешивания у каждой ступени своя — она же в TIER.sweep.
   Ступеней ровно три: на белых и серых дольках сцены нет вовсе, там взвешивать
   нечего (см. шапку ice-scene.js). */

/* -------------------------------------------------------------- КАДРЫ */

@keyframes wgVeil { from { opacity: 0 } to { opacity: 1 } }

@keyframes wgRise {
    from { transform: translateY(16px); opacity: 0 }
    to   { transform: translateY(0);    opacity: 1 }
}

@keyframes wgPan {
    from { transform: translateY(34px); opacity: 0 }
    to   { transform: translateY(0);    opacity: 1 }
}

/* ПРОСАДКА ПОД ВЕСОМ. Начинается в кадр удара, а не после него.
   Вниз резко — за первую пятую долю, — потом отдача вверх и затухание: так
   отрабатывает пружина весов. Небольшая: шесть единиц viewBox это около двух
   пикселей на телефоне, и именно столько нужно, чтобы движение прочиталось
   как «приняло вес», а не как батут. */
@keyframes wgSettle {
    0%   { transform: translateY(0) }
    18%  { transform: translateY(6px) }
    46%  { transform: translateY(-2.5px) }
    72%  { transform: translateY(1px) }
    100% { transform: translateY(0) }
}

/* Рыба падает и в последний момент доворачивается на брюхо: небольшой наклон
   отличает «уронили» от «опустили». Угол маленький — на рисованной рыбе
   заметный поворот сразу читается как подмена картинки. */
@keyframes wgDrop {
    from { transform: translateY(-38px) rotate(-6deg); opacity: 0 }
    to   { transform: translateY(0)     rotate(0deg);  opacity: 1 }
}

@keyframes wgShadow {
    from { transform: scaleX(1.5) scaleY(.7); opacity: 0 }
    to   { transform: scaleX(1)   scaleY(1);  opacity: 1 }
}

/* Один удар хвостом и затухание. Первый взмах самый сильный, дальше рыба
   успокаивается — так и выглядит живая рыба, которую положили на весы. */
@keyframes wgTail {
    0%   { transform: rotate(0deg) }
    18%  { transform: rotate(-11deg) }
    42%  { transform: rotate(8deg) }
    66%  { transform: rotate(-4deg) }
    85%  { transform: rotate(2deg) }
    100% { transform: rotate(0deg) }
}

@keyframes wgValue { from { opacity: 0 } to { opacity: 1 } }


@keyframes wgPop {
    0%   { transform: scale(1) }
    45%  { transform: scale(1.07) }
    100% { transform: scale(1) }
}

/* Табло масштабируется от своего центра, а не от начала координат svg. */
.ice-wg__value {
    transform-box: fill-box;
    transform-origin: center;
}

/* Кому движение мешает — показываем конечный кадр: дуга налита, бегунок на
   месте, число окончательное. Сцена остаётся понятной. */
@media (prefers-reduced-motion: reduce) {
    .ice-wg *,
    .ice-wg *::before,
    .ice-wg *::after {
        animation-duration: .01ms !important;
        animation-delay: 0s !important;
    }
}
