@font-face {
    font-family: 'NoteLocal';
    src: url('resources/fonts/ShantellSans-Regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'CommonFont';
    src: url('resources/fonts/Neucha-Regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

/* ============================================
   СЦЕНА

   Игра свёрстана под один размер — 1236×659, кадр Яндекс.Игр.
   Под окно она не переверстывается, а масштабируется целиком
   (js/stage.js ставит transform: scale). Поэтому внутри сцены
   не должно быть ни vw/vh, ни медиазапросов по ширине окна:
   они меряют окно, а не сцену, и разъезжаются с макетом.

   Размеры вынесены в переменные, чтобы бывшие проценты экрана
   читались как проценты сцены: 36vh стало calc(var(--stage-h) * 0.36).
   ============================================ */
:root {
    --stage-w: 1236px;
    --stage-h: 659px;
}

html {
    height: 100%;

    /* КОРНЕВОЙ ШРИФТ ЗАКРЕПЛЁН.
       
       Сцена свёрстана в пикселях (1236×659), а размеры текста — в rem,
       то есть от шрифта корня. Браузер берёт его из своих настроек:
       на ноутбуке с масштабом Windows 125% или при увеличенном шрифте
       по умолчанию он не 16px. Тогда текст внутри сцены растёт, а сама
       сцена нет — тетрадь раздувается и наезжает на соседей. Замер:
       при корне 20px тетрадь стала 496 вместо 400, ровно на четверть.
       
       Авторское правило перебивает настройку по умолчанию, и все rem
       снова означают одно и то же на любой машине. Читаемость от этого
       не страдает: игру целиком масштабирует js/stage.js под размер окна.
       
       Чего это НЕ лечит: в Chrome есть отдельная настройка «Минимальный
       размер шрифта», она применяется при отрисовке и правилами
       не перебивается. Если текст всё равно крупный — смотреть туда. */
    font-size: 16px;

    /* Автоподгонку текста выключаем. Chrome на Android и встроенный
       браузер приложений (в нём игру показывает VK) сами увеличивают
       текст в широких блоках, чтобы его было удобно читать без масштаба.
       У нас масштаб свой, на всю сцену, и подгонка только раздувала
       строки тетради — «Итого» уезжало за экран */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    /* За сценой — чернота. Раньше доски стола лежали на <html> и
       заливали всё окно: в полях сверху и снизу продолжался пол,
       а фигуры обрывались посреди него. Теперь доски живут внутри
       сцены, снаружи идёт стена избы, а за ней ничего — игровой
       экран кончился */
    background: #000;

    height: 100%;

    /* Сцена садится по центру окна, по краям остаются поля со скатертью.
       overflow: hidden — потому что уменьшенная сцена всё равно занимает
       свои 1236×659 в раскладке, и без обрезки окно получало бы полосы
       прокрутки на пустое место */
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

/* Во ВКонтакте за сценой не чернота, а тёмно-коричневый — цвет заставки.
   Чек-лист VK требует под экраном цветную подложку «не чёрную и не белую»:
   её видно в полях по бокам и когда тянут экран за край. В Яндексе
   оставлено как было. data-platform ставит встроенный скрипт в index.html */
html[data-platform="vk"],
html[data-platform="vk"] body {
    background: #3a2718;
}

/* Пол продолжается за край сцены на полосу --floor-band.
   Ровно на неё же съезжают фигуры, поэтому их внешний край приходится
   точно на границу пола и стены — не в середину стены.

   Рисуем тем же фоном, что у сцены, и кладём под содержимое (z-index -1):
   так полоса и внутренность сцены остаются одной поверхностью без шва. */
#stage::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: calc(var(--floor-band, 0px) * -1) 0;
    background-image:
            radial-gradient(circle, rgba(30, 20, 10, 0.05) 0%, rgba(0, 0, 0, 0.15) 100%),
            url(resources/images/table.webp);
    background-size: auto, auto;
    pointer-events: none;
}

/* ============================================
   СТЕНА В ПОЛЯХ

   Когда пропорции окна не совпали со сценой, сверху и снизу остаются
   поля. Раньше там продолжались доски пола, и фигуры обрывались посреди
   них ровной линией — читалось как обрезанная наклейка.

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

   Ширина — во всё окно (сцена плюс боковой вылет), высота считается из
   неё по отношению сторон картинки 1680×197. Растягивать по высоте
   нельзя: брёвна поплывут.
   ============================================ */
.stage-wall {
    position: absolute;
    left: calc(var(--stage-bleed-x, 0px) * -1);
    width: calc(var(--stage-w) + var(--stage-bleed-x, 0px) * 2);

    /* Всё, что осталось от поля после полосы пола, — до самого края
       экрана. Черноты за стеной больше нет: стена и есть край */
    height: calc(var(--stage-bleed-y, 0px) - var(--floor-band, 0px));

    min-height: 300px;
    /* cover, а не 100% 100%: пропорции брёвен сохраняются при любом поле.
       Поле высокое — картинка растёт по высоте и вылезает по бокам,
       торцы брёвен уходят за экран, это допустимо. Поле низкое —
       наоборот, видна полоса стены, прилегающая к столу: положение
       снизу как раз и держит её у края сцены */
    background: url("resources/images/wall2.webp") center bottom / cover no-repeat;
    pointer-events: none;

    /* Ниже персонажей (у них 3): они сидят у стены, а не за ней */
    z-index: 4;
}

/* Прижаты вплотную к краю сцены и растут наружу. Полей нет — обе
   полосы целиком за экраном, их не видно */
/* Отсчёт от края сцены плюс полоса пола */
.stage-wall-top    { bottom: calc(100% + var(--floor-band, 0px) - 25px); }
.stage-wall-bottom { top: calc(100% + var(--floor-band, 0px) - 25px); transform: scaleY(-1); }

/* Свет и тень: одна виньетка на всё, что видит игрок.
   Растянута на сцену ВМЕСТЕ с полями, поэтому темнеет до самого края
   экрана, а не до края сцены — иначе поля оставались бы светлой доской.

   Источник света там же, где был на скатерти: 973×331 в координатах
   сцены (85% и 50% доски). Размер круга не задаём — по умолчанию это
   дальний угол, то есть угол ЭКРАНА: чем шире окно, тем дальше растянут
   спад, и самая тёмная точка всегда на краю.

   z-index 50: выше стола, меню и панели, ниже затемнения модалок (100). */
#stage::after {
    content: "";
    position: absolute;
    inset: calc(var(--stage-bleed-y, 0px) * -1) calc(var(--stage-bleed-x, 0px) * -1);
    pointer-events: none;
    z-index: 50;
    background:
            radial-gradient(
                    circle at calc(var(--stage-bleed-x, 0px) + 973px)
                              calc(var(--stage-bleed-y, 0px) + 331px),
                    rgba(255, 255, 255, 0.10) 0%,   /* высветление у источника */
                    rgba(255, 255, 255, 0.05) 18%,
                    rgba(0, 0, 0, 0.00) 30%,        /* нейтральная зона */
                    rgba(0, 0, 0, 0.18) 60%,
                    rgba(0, 0, 0, 0.38) 85%,
                    rgba(0, 0, 0, 0.55) 100%
            );
}

#stage {
    width: var(--stage-w);
    height: var(--stage-h);

    /* Доски стола и общее затемнение — теперь принадлежность сцены,
       а не всего окна */
    background-image:
            radial-gradient(circle, rgba(30, 20, 10, 0.05) 0%, rgba(0, 0, 0, 0.15) 100%),
            url(resources/images/table.webp);
    background-size: auto, auto;

    /* flex: none — иначе центрирующий контейнер сожмёт сцену
       по своим правилам и весь расчёт масштаба поедет */
    flex: none;
    position: relative;
    transform-origin: center center;

    /* Колонку держала раньше body — теперь её роль у сцены */
    display: flex;
    flex-direction: column;
}

/* ============================================
   ПЕРСОНАЖИ (для анимации)
   ============================================ */
/* Золото — цвет диковин. Правило одно на всю игру: золотое число это
   число, которое изменила или изменит диковина. Больше никуда этот
   цвет не тратим, иначе он перестанет что-либо значить */
:root {
    --relic-gold: #b8860b;
    --relic-gold-glow: rgba(212, 160, 23, 0.55);
}

table.score td.relic-touched {
    color: var(--relic-gold);
    font-weight: 700;
}

.character {
    position: fixed;
    pointer-events: none;
    z-index: 3;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.character img {
    display: block;
    height: auto;
}

/* ============================================
   ОБЛАЧКО С ИМЕНЕМ СОПЕРНИКА

   Только в игре по ссылке: против бота напротив сидит Бабушка, и её имя
   написано на её же лице. Висит слева от фигуры — справа полка диковин
   и меню, там места нет.

   Кегль подгоняет fitOneLine (js/text_fit.js): имена с площадки бывают
   длинными, а переносить их в две строки облачко не даёт — оно тогда
   наползает на тетрадь.
   ============================================ */
.name-cloud {
    position: absolute;

    /* Отсчёт от верха фигуры, а не от верха экрана: холст персонажа
       начинается ВЫШЕ сцены — его нарочно выпускают за край (--floor-band
       в js/stage.js). При top: 54px облачко вставало на 35 единиц выше
       сцены и на невысоком окне срезалось бы вместе с ним */
    top: 100px;
    right: 56%;

    max-width: 200px;
    padding: 8px 16px;

    background: #fffdf5;
    border: 2px solid #7a5c3a;
    border-radius: 18px;
    box-shadow: 0 3px 9px rgba(0, 0, 0, 0.32);

    color: #3a2a18;
    font-weight: 700;
    line-height: 1.2;
    text-align: center;
    white-space: nowrap;

    z-index: 4;
}

/* Хвостик к фигуре. Двумя слоями, чтобы у него была та же рамка:
   одним треугольником обводку не нарисовать */
.name-cloud::before,
.name-cloud::after {
    content: "";
    position: absolute;
    top: 50%;
    width: 0;
    height: 0;
    border-top: 9px solid transparent;
    border-bottom: 9px solid transparent;
}

.name-cloud::before {
    right: -14px;
    margin-top: -9px;
    border-left: 14px solid #7a5c3a;
}

.name-cloud::after {
    right: -11px;
    margin-top: -9px;
    border-left: 14px solid #fffdf5;
}

/* ============================================
   ТАБЛИЧКА С ИМЕНЕМ СОПЕРНИКА

   В игре по ссылке напротив сидит живой человек, и знать надо ровно
   две вещи: кто это и на связи ли он. Своё имя не пишем — игрок его
   и так знает, а соперник видит его у себя.

   Стоит в пустой полосе между тетрадью и фигурой, у верхнего края.
   Отсчёт от сцены, а не от фигуры: имя — не реплика, и ездить вместе
   с персонажем ему незачем. Заодно табличка никогда не выйдет за край.
   ============================================ */
.name-plate {
    position: absolute;

    /* Табличка — <button>, чтобы её можно было нажать, когда друг
       вышел. Своего вида у кнопки здесь нет: всё рисуется ниже,
       а по умолчанию она обычная надпись, нажимать нечего */
    appearance: none;
    -webkit-appearance: none;
    font-family: inherit;
    text-align: left;
    cursor: default;

    /* Место выбрано теснотой, а не вкусом. Слева от верхней фигуры
       свободна полоса всего в два десятка единиц — табличка туда
       не встаёт и наезжает на рукав. Тёмная доска НАД скатертью просторна,
       но это поле: при широком и низком окне (а в кадре Яндекса оно
       такое) --stage-bleed-y равен нулю, и полосы там нет вовсе.
       Остаётся верхний левый угол: над строками тетради и левее её шапки
       («Дедушка» начинается с 250-й единицы по горизонтали и с 49-й
       по вертикали — табличка не достаёт ни до того, ни до другого).
       Пусто, всегда на месте и дальше всего от фигуры */
    /* Отсчёт от края ОКНА, а не сцены: сцена уже окна, когда пропорции
       не совпали, и табличка отъезжала к середине вместе с её краем.
       Тот же приём, что у затемнения диалогов, — вычесть поля */
    left: calc(var(--stage-bleed-x, 0px) * -1 + 14px);
    top: calc(var(--stage-bleed-y, 0px) * -1 + 14px);

    display: flex;
    align-items: center;
    gap: 16px;

    max-width: 420px;
    padding: 8px 24px 8px 8px;

    background: rgba(255, 253, 245, 0.94);
    border: 3px solid #7a5c3a;
    border-radius: 999px;
    box-shadow: 0 3px 9px rgba(0, 0, 0, 0.3);

    color: #3a2a18;
    font-size: 1.7rem;
    font-weight: 700;
    line-height: 1;
    white-space: nowrap;

    z-index: 4;
}

.name-plate-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;

    /* Пока картинка не пришла, кружок не должен быть дырой */
    background: #d9cbb4;
}

/* Имя соперника. overflow нужен по ГОРИЗОНТАЛИ — длинные ники режет
   fitOneLine, — но у таблички line-height: 1, и по вертикали та же
   отсечка съедала хвосты букв: «g», «y», «р», «д». Даём строке
   вздохнуть и компенсируем отрицательным полем, чтобы табличка
   не подросла */
.name-plate-text {
    overflow: hidden;

    /* 1.5, а не 1.3: у шрифта NoteLocal длинные хвосты, замер показал
       отношение 1,4 от кегля — при 1.3 «g» всё ещё цеплялась. Кратность
       кеглю тут важна: fitOneLine меняет размер под длину ника,
       и фиксированный отступ в пикселях подошёл бы только одному */
    line-height: 1.5;
    margin-block: -0.25em;
}

/* Точка связи. Только два состояния: зелёная — соперник здесь,
   красная — его нет. Промежуточного «подключаемся» не показываем:
   для того, кто смотрит, это то же самое, что «нет» */
.name-plate-dot {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    flex-shrink: 0;

    background: #b23b3b;
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.7);
}

.name-plate.is-online .name-plate-dot {
    background: #4a9c2a;
}

/* Пока друг на связи, звать его неоткуда и незачем: табличка просто
   табличка. Кнопкой она становится, когда он вышел */
.name-plate-qr {
    display: none;
    align-items: center;
    color: #7a5c3a;
    flex-shrink: 0;
}

.name-plate.is-callable {
    cursor: pointer;
    transition: background 0.15s ease, box-shadow 0.15s ease;
}

.name-plate.is-callable .name-plate-qr {
    display: inline-flex;
}

.name-plate.is-callable:hover {
    background: #fffdf5;
    box-shadow: 0 3px 14px rgba(0, 0, 0, 0.38);
}

/* Общего .hidden в проекте нет — он объявлен покомпонентно (см. заметку
   в menu.css у завесы обучения). Без этих двух строк табличка висела бы
   и в партии с ботом, а заготовка-облачко торчала бы пустым овалом */
.name-cloud.hidden,
.name-plate.hidden {
    display: none;
}

/* Дедушка - внизу экрана */
.character-bottom {
    bottom: 0;
    left: calc(50% + 80px);
    transform: translateX(-50%);
}

/* Персонажей держим чуть мельче, чем помещается: руки Дедушки лезли
   на кнопку броска, а Бабушкины — на поднос с кубиками. Полностью
   развести не выйдет (пришлось бы ужать вдвое), но лёгкое пересечение
   допустимо — руки лежат поверх дощечек, слой z-index: 3 это и делает.

   ТЕЛО И ГОЛОВА — ОДИН ХОЛСТ 1440×720, два слоя одного кадра. Значит
   высота у них обязана совпадать, иначе слои разъезжаются: голова
   не просто крупнее, она ещё и смещена, потому что прижата к своему
   краю. Раньше стояли разные потолки, 240 и 264, и голова была на 10%
   больше положенного.

   clamp убран: он остался от единиц vh, а со сценой множитель стал
   постоянным (0.36 × 659 = 237) и молча срезал оба потолка — поднять
   их было бы бесполезно, пока в правиле сидит это число */
.character-bottom .character-body {
    height: 264px;
}

.character-bottom .character-head {
    position: absolute;
    bottom: 0;
    height: 264px;
}

/* Бабушка - вверху экрана */
.character-top {
    top: 0;
    left: calc(50% + 80px);
    transform: translateX(-50%);
}

/* Та же пара слоёв, тот же холст — высота общая */
.character-top .character-body {
    height: 248px;
}

.character-top .character-head {
    position: absolute;
    top: 0;
    height: 248px;
}

/* ============================================
   ФИГУРЫ УХОДЯТ ВСЛЕД ЗА ПОЛЕМ

   Сдвиг и рост считает js/stage.js — там же потолок сдвига и величина
   роста. Точка роста на внешнем краю: тогда внутренний край, за которым
   тянутся руки, движется обратно к столу и гасит часть сдвига.

   translateX(-50%) сохраняем — им фигура ставится по центру стола.
   Разворот на чужой стороне и анимация живут на .character-body
   и .character-head — здесь их не трогаем.
   ============================================ */
.character-top {
    transform: translateX(-50%)
               translateY(calc(var(--floor-band, 0px) * -1))
               translateY(var(--away-shift, 0px))
               scale(var(--char-scale, 1));
    transform-origin: top center;

    /* Уход и возвращение соперника — плавно: человек встаёт из-за стола
       и садится обратно, а не мигает. Переход на всём transform, потому
       что сдвиг живёт в общей с ним цепочке */
    transition: transform 0.55s ease;
}

/* Кроме одного случая: соперник уже сидел за столом, когда мы вошли.
   Тогда он просто есть — выезд читался бы неправдой, будто он подошёл
   к нам, хотя это мы пришли к нему */
.character.no-move {
    transition: none;
}

.character-bottom {
    transform: translateX(-50%)
               translateY(var(--floor-band, 0px))
               translateY(var(--away-shift, 0px))
               scale(var(--char-scale, 1));
    transform-origin: bottom center;

    /* Свой выезд — когда садимся за стол в комнате. Тот же переход,
       что и у соперника: за стол садятся одинаково */
    transition: transform 0.55s ease;
}


.score_container {
    margin: auto 0;
}

#main {
    --bg-repeat: repeat;
    --bg-color: #FFFFFF;
    --bg-size: auto;

    background:
            radial-gradient(circle, rgba(255,255,255,0.1) 40%, rgba(0,0,0,0.15) 90%),
            var(--bg-image) 0 0 var(--bg-repeat);
    background-color: var(--bg-color);

    /* Два слоя: затемняющий круг и сама скатерть. Размер задаём ТОЛЬКО
       второму — одно значение применилось бы к обоим, и круг начал бы
       размножаться плиткой вслед за узором */
    background-size: auto, var(--bg-size);

    width: calc(var(--stage-w) * 0.9);
    max-width: 1400px;
    margin: auto auto auto 1%;
    display: flex;
    justify-content: center;
    height: calc(100% - 40px);

    border: 8px solid rgba(128, 128, 128, 0.55);
    box-shadow: 0 10px 25px rgba(35, 18, 8, 0.65),
                0 0 35px rgba(25, 12, 5, 0.4);

    border-radius: 20px;
}

.table_container {
    display: flex;
    flex-direction: row;
    justify-content: start;
    gap: 0;
    width: 100%;
}

#main:after {
    --light-x: 85%;
    --light-y: 50%;

    content: "";
    position: absolute; inset: 0;
    pointer-events: none;              /* не блокирует клики */

    /* ОТКЛЮЧЕНО: виньетка переехала на сцену, см. #stage::after.
       Здесь она была привязана к краю СКАТЕРТИ, и на широком низком
       окне тень на тетрадь падала от края доски, а дальше до края
       экрана доска снова светлела — свет читался как из ниоткуда.
       Теперь затемнение одно на всё видимое и доходит до края экрана.
    background:
            radial-gradient(
                    circle at var(--light-x, 50%) var(--light-y, 40%),
                    rgba(255,255,255,0.10) 0%,
                    rgba(255,255,255,0.05) 18%,
                    rgba(0,0,0,0.00) 30%,
                    rgba(0,0,0,0.18) 60%,
                    rgba(0,0,0,0.38) 85%,
                    rgba(0,0,0,0.55) 100%
            );
    */
    /* можно добавить мягкое общее затемнение краёв: */
    /*box-shadow: inset 0 0 220px 40px rgba(0,0,0,0.25);*/
}

.dice_container {
    margin: auto 0;
    display: flex;
    flex-direction: column;
    align-items: center;
}

table.score {
    background-color: #F4F4F4;
    box-shadow:
            0 1px 0 rgba(30,20,10,.25),     /* лёгкий контур */
            0 8px 18px rgba(30,20,10,.25);

    margin: 20px;
    color: rgb(77, 86, 117);
    /*border: 10px solid rgb(77, 86, 117);*/
    padding: 10px;
    border-radius: 6px;
    transform: rotate(359deg);
    border-collapse: collapse;
}

table.score:hover {
    cursor: url("resources/images/cursors/pencil.webp") 4 28, pointer;
}

table.score tr {
    border-bottom: 1px solid #A8D4D7;
}

/* --row-h: высота строки тетради. На телефоне пальцем в строку
   в 26 пикселей легко промахнуться, а тетрадь занимает сцену не до
   краёв: при 659 единицах высоты сверху и снизу оставалось по 45.
   27,5 — предел, при котором «Итого» ещё не упирается в низ (при 28
   тетрадь вылезала на три единицы).

   Условие — не ширина окна (медиазапросы по размеру здесь запрещены,
   см. js/stage.js), а способ ввода: грубый указатель без наведения —
   это палец. На компьютере с мышью тетрадь остаётся прежней */
@media (hover: none) and (pointer: coarse) {
    table.score {
        --row-h: 27.5px;
    }
}

/* --line-fix: множитель высоты строк тетради. Обычно 1. Встроенный
   браузер Android раздувает и шрифт, и высоту строки, заданную
   в пикселях, по системной настройке «Размер шрифта», — тетрадь
   вырастала и «Итого» уезжало за экран. js/stage.js мерит раздувание
   и ставит сюда обратное число, см. undoTextZoom */

table.score th {
    line-height: calc(var(--row-h, 26px) * var(--line-fix, 1));
    font-family: NoteLocal, sans-serif;
    font-size: 1.5em;
    text-align: right;
}

/* Английские названия строк короче русских («Pair» против «Пара»),
   и колонка названий ужималась по содержимому — тетрадь получалась
   заметно уже русской и выглядела тесной. Отступ слева задаёт
   колонке запас, названия остаются прижатыми вправо */
html[lang="en"] table.score tbody th {
    padding-left: 20px;
}

/* Колонки игроков одинаковой ширины и не ужимаются под короткое имя.
   Подписи в сетевой партии — имена людей, и «Вы» не должно делать
   колонку вдвое уже «Дедушки». Мерку задают сами персонажи: под них
   тетрадь и размерена, длиннее имя обрезается (clipName в main.js) */
table.score thead th:nth-child(2),
table.score thead th:nth-child(3),
table.score td[data-player] {
    width: 4.2em;
    text-align: center;
}

/* Имя игрока в шапке — СТРОГО в одну строку.
   В сетевой партии колонки подписаны именами, а они бывают любой
   ширины. Счёта знаков (clipName в js/main.js) мало: буквы разной
   ширины, и семь узких влезают, а семь широких — нет («No Kong»
   переносился). Перенос двигал вниз всю тетрадь, и «Итого» уходило
   за нижний край.
   Поэтому втрое: шрифт шапки мельче цифр, колонка шире, и всё, что
   всё-таки не влезло, прячется многоточием. Полное имя видно
   на табличке соперника рядом со сценой */
table.score thead th:nth-child(2),
table.score thead th:nth-child(3) {
    font-size: 1.2em;
    width: 5.2em;
    padding: 0;
}

/* Коробка имени. Ширина жёсткая, и это главное: ячейка таблицы
   растягивается по содержимому, а вместе с ней разъезжается вся
   тетрадь и оттесняет вправо полку диковин. Коробка не растёт,
   поэтому длинное имя просто обрывается многоточием.
   Ставит её setHeadName в js/main.js */
table.score thead th .head-name {
    display: block;
    width: 5.2em;
    margin: 0 auto;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

table.score .table_header {
    min-width: 110px;
}

/* ВЕРХНЯЯ ЗОНА — строки от «Единиц» до «Шестёрок» плюс их подытог
   и бонус.

   Бонус за верх — самое мудрёное правило на листе: он считается не по
   всей тетради, а по шести строкам, и на глаз эти шесть ничем не были
   выделены. Тёплая подложка обводит зону целиком, и объяснение бонуса
   теперь может ссылаться прямо на неё: «наберите в жёлтой зоне».

   Фон на ячейках, а не на строке: у tr фон уходит под границы клеток
   и зона распадается на полоски */
table.score tr.upper-zone.sum_row[data-alias="upper_bonus"]  {
    border-bottom: 2px dashed #777;
}
table.score tr.upper-zone.sum_row[data-alias="upper_sum"] th {
    font-weight: normal;
}

/* Цель бонуса рядом с набранным: «45 из 63».

   Пока порог не взят, одного числа мало — по нему не видно, далеко ли
   до бонуса, а бонус здесь самое мудрёное правило на листе. Взяли
   порог — цель исчезает, остаётся одно число.

   Серая и мельче нарочно: в числовой колонке всё, что выглядит как
   очки, читается как записанные очки. Цель не очки, и спорить
   с ними размером она не должна */
table.score td[data-player] .upper-goal {
    font-size: 0.72em;
    color: #8b8b8b;
    font-weight: normal;
    white-space: nowrap;
}
table.score tr.upper-zone th {
    background-color: rgba(214, 188, 130, 0.75);
}

/* Названия и числа шести рабочих строк зоны — тёмные. По плотной
   подложке синевато-серый #4d5675 отдавал в фиолетовое и терял контраст.
   Подытог и бонус сюда не входят: они зелёные, и это их отличает
   от строк, куда можно писать.

   Исключения перечислены поимённо: подсказка, ноль, ход бота и следы
   диковин красят ячейку со смыслом, и перебивать их цветом зоны нельзя */
table.score tr.upper-zone[data-row] th,
table.score tr.upper-zone[data-row] td:not(.hint-positive):not(.hint-zero):not(.bot-choosing):not(.relic-touched):not(.is-relic-firing) {
    color: #333;
}

/* Скруглить углы всей зоны, а не каждой ячейки */
table.score tr.upper-zone:first-of-type th {
    border-top-left-radius: 8px;
}
table.score tr.upper-zone:first-of-type td:last-child {
    border-top-right-radius: 8px;
}
table.score tr.sum_row[data-bonus] th {
    border-bottom-left-radius: 8px;
}
table.score tr.sum_row[data-bonus] td:last-child {
    border-bottom-right-radius: 8px;
}

table.score td {
    /* Высота строки фиксирована и НЕ зависит от содержимого. Подсказка,
       записанное число и прочерк набраны разным кеглем, и без этого
       строки прыгали на 3 пикселя при каждой записи */
    line-height: calc(var(--row-h, 26px) * var(--line-fix, 1));

    font-family: NoteLocal, sans-serif;
    margin: 0;
    text-align: center;
    font-weight: bold;
    font-size: 1.5rem;
    color: #adadad;
    text-shadow: 0.6px 0.6px 0 rgba(0,0,0,0.12),
                -0.5px -0.5px 0 rgba(255,255,255,0.5);

    cursor: url("resources/images/cursors/pencil.webp") 4 28, pointer;
    transition: all 0.3s ease;
}

/* Зафиксированный счёт.

   Зелёный, тот же оттенок, каким вспыхивает ячейка при записи
   (td.bot-choosing). Логика та же: запись очков — это прибыль,
   а прибыль в игре зелёная. Раньше записанное серело, и лист
   к концу партии выглядел погашенным, хотя всё в нём — заработок.

   Прозрачность, а не сплошной цвет: под верхними строками лежит
   подложка зоны, и непрозрачная плашка вырезала бы в ней дыры */
table.score td.clicked {
    color: #4d5675;
    text-shadow: none;
    background-color: rgba(122, 168, 90, 0.28);
    border-radius: 6px;
}

/* Подсказка: положительное значение */
table.score td.hint-positive {
    color: #555;
    text-shadow: 0.5px 0.5px 0 rgba(0,0,0,0.1);
    font-size: 1.3rem;
    opacity: 0.5;
}

/* Подсказка: ноль (очень бледный) */
table.score td.hint-zero {
    color: #555;
    text-shadow: 0.5px 0.5px 0 rgba(0,0,0,0.1);
    font-size: 1.3rem;
    opacity: 0.5;
}

table.score td:hover:not(.clicked) {
    color: rgb(77, 86, 117);
    text-shadow: none;
    opacity: 1;
}

/* Строки итогов (не кликабельные) */
table.score .sum_row td,
table.score .total_row td {
    cursor: default;
    pointer-events: none;
    color: #4d5675;
}

table.score .sum_row td:hover,
table.score .total_row td:hover {
    color: #4d5675;
}

/* Строка суммы за верх и итого (курсив, мелкий, зелёный) */
table.score .sum_row[data-sum] td,
table.score .sum_row[data-sum] th,
table.score .total_row td,
table.score .total_row th {
    font-style: italic;
    font-size: 1.3rem;
    color: #7a8a6a;
}

/* «Сумма за верх» держит общую высоту строки. Здесь стояло 32px,
   и подытог был выше соседей на шесть пикселей — заметнее всего рядом
   с «Бонусом», у которого высота обычная. Внутри жёлтой зоны разнобой
   бросается в глаза ещё сильнее */
table.score .sum_row[data-sum] td,
table.score .sum_row[data-sum] th {
    line-height: calc(var(--row-h, 26px) * var(--line-fix, 1));

    /* Обычное начертание. Жирный достался числу от общего правила
       table.score td, и подытог спорил по весу с записанными очками,
       хотя сам ничего не решает — он их пересказ */
    font-weight: normal;
    font-size: 1.1rem;
    background-color: transparent;
}

/* «Итого» остаётся выше намеренно: это последняя строка листа,
   её отделяют черта сверху и отступ */
table.score .total_row td,
table.score .total_row th {
    line-height: calc(32px * var(--line-fix, 1));
}

/* Линия над итого */
table.score .total_row td,
table.score .total_row th {
    border-top: 2px solid rgba(77, 86, 117, 0.3);
    padding-top: 4px;
}

/* Строка бонуса зелёная, как «Сумма за верх»: в неё тоже нельзя
   записывать, значение считается само. Но БЕЗ курсива и обычного
   размера — она всё-таки про очки, а не служебный подытог.
   Раньше она была синей, как рабочие строки, и звала записать в себя */
table.score .sum_row[data-bonus] td,
table.score .sum_row[data-bonus] th {
    color: #7a8a6a;
    font-style: normal;
    background-color: transparent;
}

/* Название кликабельно: бонус — самая мудрёная часть листа,
   а объяснения по клику у неё до сих пор не было */
table.score .sum_row[data-bonus] th {
    cursor: pointer;
    pointer-events: auto;
}

table.score .sum_row[data-bonus] th:hover {
    color: #5f7050;
}

/* Знак вопроса, пока бонус не взят: честно говорит «пока неизвестно».

   Он и сам кликабелен — объяснение открывается не только с названия
   строки. Знак вопроса выглядит как приглашение спросить, и странно,
   когда спросить можно только соседнюю клетку. Как только бонус
   посчитан, класс снимается и клик снова не нужен: там уже число */
table.score .sum_row[data-bonus] td.bonus-unknown {
    color: #a8b09c;
    cursor: pointer;
    pointer-events: auto;
}

/* Строка бонуса — обычный стиль, но без hover */
table.score .sum_row[data-bonus] td {
    pointer-events: none;
    cursor: default;
}

td + td {
    /*background-color: rgb(255, 255, 255);*/
}

/**********************************************************************************************************************/
/* DICES */
.dice_row {
    position: relative;
    z-index: 2;
    display: flex;
    gap: 10px;
    padding: 20px 30px;
    background-image: url(resources/images/dices/dice_tray.webp);
    background-size: contain;
    background-position: center;
    border-radius: 12px;
    background-repeat: no-repeat;
    /* Минимальные размеры чтобы не схлопывался при пустом содержимом */
    min-height: 120px;
    min-width: 400px;
    align-items: center;
    margin: -40px 0 20px 10px;
}

.dice_img {
    width: 70px;
    height: auto;
    margin: 8px 0;
}

/* Подложка, скругление и тень нужны только ПЛОСКИМ картинкам: они
   дают им объём. У скинов с уже нарисованными эффектами это выходит
   тенью поверх тени, поэтому включается классом на ряду */
/* Шесть костей вместо пяти: «Запасная кость».

   Поднос не растим — он вписан в окружение, и лишняя высота тут же
   налезала на салфетки с диковинами справа. Вместо этого ужимаем
   содержимое: тень, грань и замок.

   Считано под ширину подноса. При пяти костях занято
   5×70 + 4×10 зазора + 60 полей = 450 из 460. Шестая по тем же меркам
   даёт 530 — не влезает. При 58 и зазоре 8 выходит
   6×58 + 5×8 + 60 = 448, снова с запасом. Замок ужимается в той же
   доле 58/70, иначе он повиснет мимо кости. */
.dice_row.has-spare {
    gap: 8px;
}

.dice_row.has-spare .dice_img,
.dice_row.has-spare .dice-preview,
.dice_row.has-spare .dice_preview {
    width: 58px;
}

.dice_row.has-spare .dice-preview,
.dice_row.has-spare .dice_preview {
    margin-left: -29px;
}

.dice_row.has-spare .dice_empty > div.dice_shadow {
    width: 58px;
    height: 58px;
}

.dice_row.has-spare .dice_wrapper.saved::after {
    width: 90px;
    height: 96px;
    top: 4px;
    left: -6px;
}

.dice_row.flat-dice .dice_img,
.dice-example.flat-dice .dice_img {
    border: 0px #fff solid;
    background-color: #fff;
    border-radius: 8px;
    box-shadow: 0px 2px 4px #333;
}

.dice_wrapper {
    position: relative;
    display: inline-block;
    cursor: grab;
}

.dice_wrapper:hover {
    /*transform: scale(1.25);*/
    /*transition: transform 0.2s ease;*/
}

.dice_wrapper.saved img {
    filter: brightness(0.9);
    cursor: grabbing;
}

/* ОТКЛЮЧЕНО: золотое свечение на кубике, которого коснулась диковина.
   Задумывалось как «вот его и тронули», но показывать это нечем нужным:
   у Зеркальца грань меняется на глазах сразу после нажатия, у Катушки
   кость и так лежит на подносе под замком. Свечение оставалось до конца
   хода и просто мозолило глаз на давно обычном кубике.

   Класс relic-touched сохраняется — на нём держится запрет снимать
   замок с обещанной Катушкой кости (activateDie в dice_turn_ui.js).
.dice_wrapper.relic-touched img {
    filter: drop-shadow(0 0 7px var(--relic-gold-glow))
            drop-shadow(0 0 14px var(--relic-gold-glow));
}

.dice_wrapper.saved.relic-touched img {
    filter: brightness(0.9)
            drop-shadow(0 0 7px var(--relic-gold-glow))
            drop-shadow(0 0 14px var(--relic-gold-glow));
}
*/

/* Призрак будущего значения: показывается при наведении в режиме
   выбора, чтобы не нажимать вслепую. Полупрозрачный и золотой —
   сразу видно, что это ещё не результат, а обещание */
/* Кость под призраком прячем совсем: иначе на подносе видны ДВЕ грани
   разом — старая просвечивает сквозь новую, и вместо ответа «станет
   вот таким» выходит каша из точек */
.dice_wrapper.dice-hinted .dice_img {
    visibility: hidden;
}

.dice-preview {
    position: absolute;

    /* Геометрия ровно как у настоящей кости (70px, высота по картинке,
       поля 8px сверху и снизу). Раньше стояло inset: 0 с растяжкой
       на всю коробку — картинку плющило по её пропорциям, и кубик
       при наведении будто сжимался и вытягивался */
    left: 50%;
    top: 8px;
    width: 70px;
    height: auto;
    margin-left: -35px;

    pointer-events: none;
    z-index: 4;

    /* Чуть крупнее — этого хватает, чтобы прочиталось «станет вот
       таким». Ни свечения, ни полупрозрачности: вместе они давали
       дешёвый казуальный блеск, а сказать надо всего лишь число */
    transform: scale(1.15);
    animation: relic-hint-in 0.12s ease-out;
}

/* Плоским скинам подложка нужна и здесь — как у кости на подносе */
.dice_row.flat-dice .dice-preview {
    background-color: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 4px #333;
}

@keyframes relic-hint-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Кубик, выбранный на первом шаге «Курительной трубки»: ждём второй
   выбор, и игрок должен помнить, что именно он уже указал */
.dice_wrapper.relic-picked img {
    outline: 3px solid var(--relic-gold);
    outline-offset: -2px;
    border-radius: 10px;
}

.dice_wrapper.saved::after {
    content: "";
    position: absolute;
    width: 108px;
    height: 116px;
    background: url(resources/images/full_lock.webp) no-repeat center / contain;
    pointer-events: none;
    z-index: 3;
    top: 3px;
    left: -5px;
}

.dice_wrapper.saved::after {
    transition: opacity 0.2s ease;
}

.dice_empty {
    pointer-events: none;
}

/* У заглушки прячем ЕЁ картинку — грани ещё нет, на подносе тень.
   Но правило било по любой картинке внутри, а внутрь кладутся ещё
   и призраки: предсказание «Сонника» до первого броска и подсказка
   при наведении. Они честно создавались и были невидимы — диковина
   выглядела сломанной. Поэтому исключаем их явно */
.dice_empty img:not(.dice_preview):not(.dice-preview) {
    display: none;
}

.dice_empty > div.dice_shadow {
    width: 70px;
    height: 70px;
    background-color: #333;
    border-radius: 8px;
    opacity: 0.5;
    margin: 8px 0;
}

.dice_controls {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-direction: column;
}

.roll_dice_button {
    /* === Размеры и позиционирование === */
    width: 280px;            /* Фиксируем ширину */
    height: 60px;            /* Фиксируем высоту (опционально, для стабильности) */
    display: flex;           /* Центрируем содержимое */
    justify-content: center;
    align-items: center;
    gap: 10px;               /* Расстояние между иконкой и текстом */

    /* === Базовый стиль === */
    padding: 0;              /* Паддинг можно убрать, так как есть flex centering */
    border: none;
    border-radius: 8px;      /* Чуть более скругленные углы для дерева */
    cursor: pointer;

    /* === Текст === */
    font-family: 'CommonFont', serif;
    font-size: 1.5rem;
    letter-spacing: 1px;
    /*font-weight: bold;*/
    color: #fff;
    /*text-shadow: 2px 2px 0px rgba(0, 0, 0, 0.6); !* Более жесткая тень для текста ("выгравировано") *!*/

    /* === Текстура и цвет === */
    background-color: #8B4513;
    background-image:
            linear-gradient(to bottom, rgba(255, 255, 255, 0.15) 0%, rgba(0, 0, 0, 0.2) 100%),
            url('resources/images/wood_pattern.webp');
    background-size: cover;

    /* === Границы (создаем объем доски) === */
    /* Светлый верх и лево (блик), темный низ и право (тень) */
    border-top: 2px solid #bd7b4d;
    border-left: 2px solid #bd7b4d;
    border-right: 3px solid #522807;
    border-bottom: 4px solid #522807; /* Снизу толще для объема */

    /* Внутренняя тень для фактуры */
    box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.3);

    /* Плавность анимации. Перечисляем свойства поимённо, а не all:
       с all в переход попадал и transform, а любой его переход внутри
       масштабированной сцены мылит соседей — см. :hover ниже */
    transition: box-shadow 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94),
                filter 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);

    /* Запрещаем выделение текста при быстрых кликах */
    user-select: none;
}

/* === Наведение (Hover) === */
.roll_dice_button:hover {
    /* Ни transform, ни filter здесь быть не должно.

       Сцена целиком лежит под transform: scale(...). Когда внутри неё
       что-то анимирует transform или filter, браузер выносит это
       в отдельный слой и растрирует его в СВОЁМ масштабе, а потом
       масштабирует ещё раз родительским. На дробном масштабе — а он
       дробный при любом окне, кроме 1236×659, — соседи после этого
       выглядят подмыленными: кости на подносе, салфетки справа.
       В полноэкранном режиме масштаб оказывался ближе к целому,
       и потому там было чисто.

       Отклик даём тенью: она не порождает слоя и на соседей не влияет */

    /* Внешняя тень растет (табличка "отлипает" от фона) */
    box-shadow:
            inset 0 0 8px rgba(0, 0, 0, 0.3), /* Сохраняем инсет */
            5px 8px 13px rgba(0, 0, 0, 0.4);  /* Добавляем тень на "стол" */
}

/* === Нажатие (Active) === */
.roll_dice_button:active {
    /* Эффект: Чуть уменьшаем (давление) и меняем наклон */
    transform: rotate(1deg) scale(0.98);

    /* Тень уменьшается (прижимаем к столу) */
    box-shadow:
            inset 0 0 13px rgba(0, 0, 0, 0.6),
            2px 3px 5px rgba(0, 0, 0, 0.3);

    border-bottom-width: 2px; /* Визуально "утапливаем" толстый край */
}

/* === Отключенная кнопка (Disabled) === */
.roll_dice_button:disabled {
    cursor: not-allowed;
    filter: grayscale(0.8) brightness(0.7); /* Серая и темная */
    transform: none !important;
    box-shadow: none;
    opacity: 0.7;
}

.dice_icon {
    width: 32px;
    vertical-align: middle;
}

/**********************************************************************************************************************/
/* ============================================
   БОТ - блокировка и подсветка
   ============================================ */

/* Блокировка таблицы во время хода бота */
body.bot-playing table.score {
    pointer-events: none;
}

body.bot-playing table.score td {
    cursor: default;
}

/* Подсветка ячейки которую выбирает бот */
/* Подсветка выбранной строки — зелёная, а не оранжевая.
   Оранжевый в игре занят предупреждением и ожиданием: им горит
   диковина, которая ждёт применения, и режим выбора строки
   «Чернильницей». Запись очков — это прибыль, а прибыль в игре
   зелёная: тот же #5a7a3a, что у прибавки конфет и сумм за верх */
table.score td.bot-choosing {
    background-color: rgba(122, 168, 90, 0.4) !important;
    color: rgb(77, 86, 117) !important;
    animation: bot-pulse 0.3s ease-in-out 2;
    box-shadow: 0 0 15px rgba(90, 122, 58, 0.6);
    opacity: 1;
}

@keyframes bot-pulse {
    0%, 100% {
        transform: scale(1);
        background-color: rgba(255, 200, 100, 0.4);
    }
    50% {
        transform: scale(1.1);
        background-color: rgba(255, 220, 120, 0.6);
    }
}

/* Запись соперника: клетка вместе с числом коротко вырастает и оседает.

   Отдельно от подсветки бота (bot-choosing), и намеренно. У бота
   клетка мигает ПУСТОЙ, пока он «выбирает», и только потом в неё
   ложится число. У соперника выбирать нечего — число приходит готовым,
   вместе со снимком, — поэтому расти должно оно само: глаз ловит
   движение ровно там, где появилась запись.

   Пробовали и по-бонтовски, придерживая число на полсекунды: выходило
   два вялых события вместо одного.

   position + z-index нужны, чтобы выросшая клетка легла ПОВЕРХ соседних,
   а не подлезла под них. Цвет в конце — тот же, что у записанной клетки
   (td.clicked), чтобы анимация осела в него без скачка */
table.score td.peer-write {
    position: relative;
    z-index: 2;
    animation: peer-write-pop 1.2s cubic-bezier(0.2, 0.9, 0.25, 1) 1;
}

@keyframes peer-write-pop {
    0% {
        transform: scale(1);
        background-color: rgba(122, 168, 90, 0.7);
        box-shadow: 0 0 0 rgba(90, 122, 58, 0);
    }
    35% {
        transform: scale(1.3);
        background-color: rgba(122, 168, 90, 0.6);
        box-shadow: 0 0 18px rgba(90, 122, 58, 0.7);
    }
    100% {
        transform: scale(1);
        background-color: rgba(122, 168, 90, 0.28);
        box-shadow: 0 0 0 rgba(90, 122, 58, 0);
    }
}

/* Затемнение колонки неактивного игрока.
   Заголовки берём ПО ИМЕНИ, а не по месту в строке: в партии по ссылке
   своя колонка всегда левая, кем бы ты ни сел, и столбцы для этого
   переставляются местами (orderNotebookColumns в main.js). Клетки уже
   искались по номеру игрока, а заголовки — по позиции, и после
   перестановки гасился бы заголовок не той колонки */
body.player-1-turn table.score td[data-player="2"],
body.player-1-turn table.score th#p2-name {
    opacity: 0.45;
}

body.player-2-turn table.score td[data-player="1"],
body.player-2-turn table.score th#p1-name {
    opacity: 0.45;
}

/* Курсор и hover для неактивной колонки */
body.player-1-turn table.score td[data-player="2"],
body.player-2-turn table.score td[data-player="1"] {
    cursor: default;
    pointer-events: none;
}

body.player-1-turn table.score td[data-player="2"]:hover,
body.player-2-turn table.score td[data-player="1"]:hover {
    color: inherit;
}

/**********************************************************************************************************************/
/* Responsive adjustments */
/* Было @media (max-height: 800px) — на базе 1236×659 условие истинно,
   поэтому правила раскрыты и действуют всегда */
table.score th {
    font-size: 1.2em;
}
table.score td {
    font-size: 1.2rem;
}

/* ОТКЛЮЧЕНО вместе с переходом на сцену (js/stage.js).
   Игра теперь всегда свёрстана в 1236×659, и это условие на базе
   не срабатывало — значит его правила в макет никогда и не входили.
   Но медиазапрос смотрит на ОКНО, а не на сцену, и в маленьком окне
   он выстрелил бы поверх готовой раскладки. Оставлено на случай,
   если вернёмся к отдельной вёрстке под телефон.
@media (max-width: 1200px) {
    .character-top,
    .character-bottom {
        left: calc(75% + 80px);
    }

    .character-top .character-body {
        height: clamp(0px, calc(var(--stage-h) * 0.39), 250px);
    }
    .character-top .character-head {
        height: clamp(0px, calc(var(--stage-h) * 0.39), 265px);
    }

    .character-bottom .character-body {
        height: clamp(0px, calc(var(--stage-h) * 0.38), 250px);
    }
    .character-bottom .character-head {
        height: clamp(0px, calc(var(--stage-h) * 0.38), 275px);
    }
} */
/* Ограничения раунда от диковин: «Очки» оставляют чётные, «Шепоток»
   убирает одну грань. Без этого игрок узнаёт о них по странному броску */
.round-limits {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;

    width: fit-content;
    max-width: 92%;

    /* Доска выглядывает из-под тарелки: заезжаем под поднос
       отрицательным отступом, а лишний padding сверху опускает
       содержимое ниже края тарелки */
    margin: -58px auto 10px;
    padding: 40px 20px 10px;
    
    background: linear-gradient(to bottom,
        #7a5232 0%,
        #6b4527 55%,
        #5a3a20 100%);
    border-radius: 0 0 14px 14px;
    box-shadow: inset 0 6px 10px rgba(0, 0, 0, 0.35),
                0 6px 14px rgba(35, 18, 8, 0.45);

    font-family: 'NoteLocal', serif;
    font-size: 1rem;
    color: #f3e0c4;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);

    /* Ниже подноса, но выше стола — тарелка накрывает верхний край */
    position: relative;
    z-index: 1;
}

.round-limits.hidden { display: none; }

.round-limits img {
    width: 30px;
    height: 30px;
    vertical-align: middle;
    filter: drop-shadow(0 1px 2px rgba(70, 55, 35, 0.35));
}

.round-limits .limit-banned {
    position: relative;
    display: inline-block;
}

/* Перечёркивание запрещённой грани — рисуем, а не картинкой,
   чтобы работало с любым скином кубиков */
.round-limits .limit-banned::after {
    content: "";
    position: absolute;
    left: -3px;
    right: -3px;
    top: 50%;
    height: 3px;
    background: #b3261e;
    transform: rotate(-20deg);
    border-radius: 2px;
}

/* Каждое ограничение — своей строкой: «только чётные» и «не выпадет»
   это разные правила, в одну строку их читать неудобно */
.round-limits .limit-line {
    display: flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
}

/* Придержанная кость в прогнозе: её значение при броске не изменится,
   и рисуем мы не предсказание, а то, что уже лежит. Приглушаем —
   видно, что она в раскладе, но менять её нечему */
.round-limits img.limit-kept {
    opacity: 0.4;
}

/* ОТКЛЮЧЕНО: золотая рамка вокруг шестого кубика от «Запасной кости».
   Что кость лишняя, видно и так — их шесть вместо пяти. Рамка же
   держалась весь ход и мозолила глаз на обычном кубике.
[data-spare] img {
    outline: 3px solid var(--relic-gold);
    outline-offset: -2px;
    border-radius: 10px;
    box-shadow: 0 0 10px var(--relic-gold-glow);
}
*/

/* ОТКЛЮЧЕНО: золотая подсветка счётчика бросков от «Стаканчика».
   Число в скобках и так выросло — этого достаточно, а оранжевый текст
   на деревянной кнопке выглядел чужеродно.
#throw-text.relic-touched {
    color: var(--relic-gold);
    font-weight: 700;
    text-shadow: 0 0 8px var(--relic-gold-glow);
}
*/

/* Доплата за нули от «Золотой подковы» — под счётом игрока */
.player-zero-bonus {
    display: block;
    margin-top: 2px;
    font-size: 0.82rem;
    color: var(--relic-gold);
    font-weight: 700;
}

/* Кубик, не попавший в зачёт по наведённой строке.

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

   Причина та же, что у кнопки броска: filter внутри масштабированной
   сцены выносит элемент в отдельный слой и мылит соседей. Обесцвечивание
   тут и не нужно — приглушения хватает, чтобы отделить зачтённые кости
   от лишних */
.dice_wrapper img {
    transition: opacity 0.18s ease;
}

.dice_wrapper.dice-unused img {
    opacity: 0.3;
    transition: opacity 0.25s ease;
}

/* Голова Дедушки нарисована мельче, чем следовало бы по фигуре, —
   рядом с его же ладонями это видно. Прибавляем ей 5%.

   Через высоту, а не через scale. Голова приколота к внешнему краю
   (top: 0 либо bottom: 0), поэтому прибавка высоты растит её ВНУТРЬ
   от этого края — за экран ничего не уходит, и работает это одинаково
   при любом развороте.

   Через scale не вышло: отдельные свойства применяются раньше
   transform, а в нём у Дедушки на чужой стороне стоит rotate(180deg)
   от .is-flipped. Он разворачивал и компенсирующий сдвиг — вместо
   того чтобы гасить прирост, сдвиг его удваивал, и макушка уезжала
   за край.

   Только Дедушка: у Бабушки пропорции в порядке.

   По классу персонажа, а не по узлу: узлы теперь зовутся местами
   за столом, и Дедушка может оказаться в любом из них — хоть в обоих */
.character.is-grandpa.character-top .character-head    { height: 260px; }
.character.is-grandpa.character-bottom .character-head { height: 277px; }

/* Персонаж сел на чужую сторону: тело разворачиваем, чтобы руки
   тянулись к столу, а не от него. Голову не трогаем — она должна
   остаться в правильном положении */
.character.is-flipped .character-body,
.character.is-flipped .character-head {
    transform: rotate(180deg);
}
