/* ==========================================================================
   Симулятор Таро — Райдер—Уэйт—Смит
   Вёрстка под телефон. Цвета — в themes.css, шрифты — в fonts.css.

   Два независимых состояния, оба живут атрибутами на .phone__screen:
     data-view  = table | full     обычный вид карты или во весь экран
     data-sheet = hidden | peek | open   шторка с описанием

   Слои карты (важно для плавности переворота):
     .card        — только наведение
     .card__float — покачивание в покое и подъём во время переворота
     .card__inner — ТОЛЬКО rotateY
   Пока покачивание и переворот делили один transform на одном элементе,
   карта в момент нажатия скачком сбрасывалась в базовое положение.
   ========================================================================== */

:root {
  /* Геометрия рассчитана на экран 390 × 844 — типичный телефон
     и типичное окно мини-аппа Telegram. */

  /* Цвет валюты. Настоящие значения приходят из темы (themes.css),
     здесь — запас на случай, если тема их не задала: без него значок
     остался бы вовсе без краски и пропал. */
  --c-orb-line: #84c3f3;
  --c-orb-fill: #db879b;
  --c-orb-num:  #84c3f3;

  --screen-w: 390px;
  --screen-h: 844px;

  /* Карта ВСЕГДА свёрстана в максимальном размере и только уменьшается.
     Если верстать её мелкой и растягивать трансформом, браузер
     растрирует картинку мелкой и увеличивает готовый растр — отсюда
     пикселизация в движении; а по остановке перерисовывает заново,
     и это читается как двухступенчатое замыливание. Уменьшение
     готового растра таких артефактов не даёт. */
  --card-w: min(358px, calc(100vw - 32px));   /* во весь экран: ширина минус поля */
  --scale-closed: .66;            /* 236 px — колода ждёт касания */
  --scale-open:   .838;           /* 300 px — открытая карта */
  /* Формат карты один для всех колод — настоящий формат карты
     Уэйта — Смит, он же у её рубашки. Узкой Сола-Буске добирается
     бумажное поле по бокам; резать чужой рисунок нельзя
     (см. docs/reference/deck-fitting.md). */
  --card-ratio: 0.579;
  /* То же самое числом: делить на «0.579» в calc() нельзя, а высота
     карты нужна, чтобы поставить подпись ровно над ней. */
  --card-h: calc(var(--card-w) * 1000 / 579);

  /* Насколько поднять однострочную подпись, чтобы по центру оказались
     строчные буквы, а не прописная. align-items и симметричные отступы
     центрируют площадку шрифта, а её середина лежит выше базовой линии
     ровно настолько, что по центру встаёт прописная, — корпус слова
     при этом уходит вниз.

     Величины измерены по самим шрифтам, а не подобраны:
     sh tools/font-metrics.sh печатает их для Kurale и Alice. */
  --lc-head: .125em;   /* Kurale */
  --lc-body: .107em;   /* Alice  */

  --topbar-h: 78px;
  --controls-h: 66px;
  --peek-h: 150px;                /* высота свёрнутой шторки */

  /* Снизу шторка упирается в нижнюю панель. Там, где панели нет —
     в справочнике и на отдельной карте, — она доходит до самого низа,
     иначе внизу остаётся пустая полоса. */
  --sheet-bottom: var(--controls-h);


  --t-flip: 1100ms;
  --t-move: 620ms;
  --t-text: 620ms;
  --t-sheet: 520ms;

  --ease-flip: cubic-bezier(.25, .86, .28, 1.02);
  --ease-move: cubic-bezier(.22, .82, .24, 1);
  --ease-out:  cubic-bezier(.2, .7, .3, 1);
  --ease-soft: cubic-bezier(.4, 0, .2, 1);

  --font-head: "Kurale", Georgia, serif;
  --font-body: "Alice", Georgia, serif;
}

* { box-sizing: border-box; }

/* Атрибут hidden браузер гасит своей таблицей стилей, а она слабее
   любого нашего правила: элементу с display в собственном классе
   hidden ничего не делает. Правило ниже возвращает атрибуту силу. */
[hidden] { display: none !important; }


/* Кнопки и поля НЕ наследуют шрифт: по умолчанию браузер подставляет им
   системный. Без этого правила подписи внутри кнопок — названия карт
   в справочнике, позиции на доске, подписи режимов, крестик шторки —
   уходили мимо выбранной пары гарнитур. */
button, input, select, textarea { font-family: inherit; }

html { height: 100%; }

/* ВАЖНО: у body НЕ фиксированная высота. При height:100% вместе с
   justify-content:center рамка телефона (868 px) оказывается выше окна,
   переполнение уходит за оба края и прокруткой не достаётся — нижняя
   часть экрана вместе со шторкой просто обрезается. min-height даёт
   body расти и странице прокручиваться. */
body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  color: var(--c-text);
  background: var(--c-bg-glow-2);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 20px 0;
  -webkit-font-smoothing: antialiased;
  /* Синтетическую жирность НЕ запрещаем. У Kurale и Alice есть только
     обычное начертание — жирного не существует вовсе. Пока запрет стоял,
     font-weight: 600 у заголовков не делал ничего, и весь интерфейс шёл
     одной толщиной без всякой иерархии. Для двух спокойных антикв
     подделка выглядит приемлемо, а её отсутствие — нет. */
}

/* ------------------------------------------------------- рамка телефона */

/* Корпус намеренно не зависит от палитры: это физическое устройство,
   а не часть приложения, поэтому цвета здесь литеральные. */
.phone {
  flex: none;
  padding: 12px;

  /* Рамка 868 px высотой не влезает в типичное окно ноутбука. Масштаб
     считает JS (--phone-scale), а отрицательный нижний отступ убирает
     место, которое рамка перестала занимать после уменьшения:
     transform на раскладку не влияет. */
  transform: scale(var(--phone-scale, 1));
  transform-origin: top center;
  margin-bottom: calc((var(--phone-scale, 1) - 1) * (var(--screen-h) + 24px));
  border-radius: 46px;
  background: linear-gradient(160deg, #2a2a2e, #121214);
  box-shadow:
    0 30px 70px rgba(0, 0, 0, .55),
    0 0 0 1px rgba(255, 255, 255, .06) inset;
}

.phone__screen {
  position: relative;
  width: var(--screen-w);
  height: var(--screen-h);
  overflow: hidden;
  border-radius: 34px;
  background:
    radial-gradient(120% 60% at 50% -10%, var(--c-bg-glow-1) 0%, transparent 60%),
    var(--c-bg);
}

/* Вырез под фронтальную камеру — только чтобы кадр читался как телефон. */
.phone__screen::before {
  content: "";
  position: absolute;
  top: 9px;
  left: 50%;
  width: 104px;
  height: 26px;
  transform: translateX(-50%);
  border-radius: 999px;
  background: #0a0a0c;
  z-index: 40;
  pointer-events: none;
}

/* ---------------------------------------------------------- верхняя панель */

.topbar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  height: var(--topbar-h);
  padding: 44px 14px 0;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 8px;
}

/* Кнопка меню набрана как заголовок «Расклад целиком» — тем же
   акцентным цветом темы, мелким капсом с разрядкой. */
.back {
  font-family: var(--font-body);
  font-size: 17px;
  text-align: center;
  letter-spacing: .06em;
  border: 0;
  background: none;
  color: var(--c-accent-label);
  cursor: pointer;
  transition: color 200ms var(--ease-out), transform 160ms var(--ease-out);
}

.back:hover { color: var(--c-accent-head); }

/* Счётчик расклада и переключатель сортировки делят одну ячейку
   справа: одновременно они не нужны — счётчик живёт на столе,
   сортировка в справочнике. */
.progress, .sort { grid-row: 1; grid-column: 2; justify-self: end; }

.progress {
  font-size: 11.5px;
  letter-spacing: .16em;
  color: var(--c-muted);
  font-variant-numeric: tabular-nums;
}

.spread {
  grid-column: 1 / -1;
  display: flex;
  gap: 4px;
  justify-content: center;
  padding-top: 6px;
}

.spread:empty { display: none; }

/* Собственного эффекта появления у миниатюр нет намеренно: карта
   прилетает сюда перелётом, а анимация со scale ломала бы замер —
   getBoundingClientRect отдаёт УЖЕ преобразованный прямоугольник,
   и копия приземлялась мимо, после чего настоящая карта прыгала. */
.spread img {
  width: 22px;
  border-radius: 2px;
  display: block;
  border: 1px solid var(--c-line);
}

.spread img.rev { transform: rotate(180deg); }

/* Пока карта во весь экран, верхняя панель уходит. */
[data-view="full"] .topbar { opacity: 0; pointer-events: none; }
.topbar { transition: opacity var(--t-move) var(--ease-out); }

/* ------------------------------------------------------------- экраны */

.menu {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 100%;
  justify-content: center;
}

@keyframes introIn {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

.mode {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 16px 24px;
  text-align: center;
  cursor: pointer;
  background: linear-gradient(180deg, var(--c-surface), var(--c-surface-deep));
  box-shadow: 0 12px 30px rgba(var(--c-shadow-rgb), var(--a-shadow-panel));
  transition: transform 240ms var(--ease-out), border-color 240ms var(--ease-out);
}

.mode:hover { border-color: rgba(var(--c-accent-rgb), var(--a-accent-border)); }
.mode:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 3px; }

.mode__title {
  font-family: var(--font-head);
  font-size: 23px;
  line-height: 1.1;
  color: var(--c-accent-head);
}

.mode__sub {
  margin-top: 4px;
  font-size: 12.5px;
  letter-spacing: .06em;
  color: var(--c-muted);
  /* Подпись должна стоять в одну строку у всех раскладов: в две она
     разъезжает кнопки по высоте, и список перестаёт читаться столбцом.
     Что все подписи влезают, проверяет sh tools/test-fit.sh — он меряет
     их настоящую ширину в Alice, а не считает символы. */
  white-space: nowrap;
}

/* --------------------------------------------------------------- сцена */

/* Карта центрируется не в экране, а в полосе между верхней панелью
   и шторкой. Зазор получается сам, без подобранных вручную сдвигов,
   и не ломается при другой высоте экрана. */
.stage {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--topbar-h);
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: top var(--t-move) var(--ease-move), bottom var(--t-move) var(--ease-move);
}

[data-sheet="peek"] .stage,
[data-sheet="open"] .stage {
  bottom: calc(var(--sheet-bottom) + var(--peek-h) + 12px);
}

/* Со свёрнутой шторкой кнопка хода стоит между нею и картой, а не под
   шторкой: рука уже там, и следующий шаг напрашивается сразу под
   прочитанным. Шторка тогда ложится на самый низ. */
[data-sheet="peek"] { --sheet-bottom: 0px; }
[data-sheet="peek"] .controls { bottom: var(--peek-h); }
[data-sheet="peek"] .stage { bottom: calc(var(--peek-h) + var(--controls-h) + 6px); }

[data-view="full"] .stage { top: 0; bottom: 0; }

/* Подпись над закрытой картой: в раскладах из нескольких карт надо
   знать, какую позицию тянешь, ещё до переворота.

   Места в потоке не занимает — стоит абсолютно над картой. Иначе
   карта прыгала бы каждый раз, когда подпись появляется и исчезает,
   а от её положения зависят перелёты миниатюр. */
.pos-hint {
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: calc(50% + var(--card-h) * var(--scale-closed) / 2 + 16px);
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 320ms var(--ease-out);
}

.pos-hint.is-on { opacity: 1; }

/* Подпись — принадлежность стола. Экран отдельной карты показывает ту же
   сцену теми же узлами, и подпись от брошенного расклада всплывала
   за картой из справочника. Убираем её вне стола совсем, а не
   прозрачностью: прозрачный узел остаётся и в отрисовке, и в дереве
   доступности, а на нём висит aria-live. Плавно гаснуть вне стола
   всё равно нечему — сцены там не видно. */
html:not([data-screen="table"]) .pos-hint { display: none; }

.pos-hint__name {
  display: block;
  font-family: var(--font-head);
  font-size: 21px;
  line-height: 1.15;
  color: var(--c-accent-head);
}

.pos-hint__note {
  display: block;
  margin-top: 3px;
  font-family: var(--font-body);
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--c-muted);
}

.card-wrap {
  position: relative;
  transform: scale(var(--scale-closed));
  transition: transform var(--t-move) var(--ease-move);
  will-change: transform;
}

/* Карта приподнимается, освобождая место шторке. */
[data-sheet="peek"] .card-wrap,
[data-sheet="open"] .card-wrap { transform: scale(var(--scale-open)); }

/* Во весь экран: шторка уходит вниз, карта разворачивается по центру. */
[data-view="full"] .card-wrap { transform: scale(1); }

.card-wrap::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--card-w) * .95);
  height: calc(var(--card-w) * .95);
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, rgba(var(--c-accent-rgb), var(--a-glow)), transparent 70%);
  pointer-events: none;
}

/* ------------------------------------------------ колода под картой

   Закрытая карта лежит НА колоде, а не висит одна в воздухе: колода —
   отдельный слой под ней, той же рубашки, с толщиной из нескольких
   тёмных срезов. Раздача идёт как у правши за столом, тактами;
   такт — атрибут data-deal на .phone__screen:

     rest   карта на колоде, стопка ровно под ней;
     lift   верхняя карта снята — сдвинута чуть вверх и вправо;
     flick  колода, уже ушедшая в левую руку, подцепляет край карты
            коротким тычком вправо — и карта в этот миг переворачивается;
     dealt  карта открыта, колода остаётся слева до следующей.

   Сдвиги заданы свойством translate, а не transform: transform у
   карты занят наклоном за указателем, у обёртки — масштабом под
   шторку, у .card__float — подъёмом при перевороте. Свойство translate
   от них независимо и складывается с ними само. */
.deck {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: 10px;
  overflow: hidden;
  background: var(--c-surface-card);
  /* Толщина стопки — четыре тёмных среза книзу-вправо, плюс та же
     волосяная кромка и тень, что у самой карты. */
  box-shadow:
    1px 1px 0 rgba(var(--c-shadow-rgb), .55),
    2px 2px 0 rgba(var(--c-shadow-rgb), .5),
    3px 3px 0 rgba(var(--c-shadow-rgb), .45),
    4px 4px 0 rgba(var(--c-shadow-rgb), .4),
    0 0 0 1px rgba(var(--c-accent-rgb), var(--a-accent-hairline)),
    0 14px 30px rgba(var(--c-shadow-rgb), var(--a-shadow-card));
  translate: 0 0;
  rotate: 0deg;
  /* Положение колоды ведут только кадры тактов (ниже), переходов по
     translate/rotate нет: переход, стартующий из положения, которое
     держала анимация, Safari то запускает, то нет — и стопка прыгала
     на стыке подрезки и снятия. */
  transition: opacity 260ms var(--ease-out);
  pointer-events: none;
}
.deck img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
}

/* Карта — поверх колоды. Её движение в раздаче живёт на самом узле
   .card, а не на .card__float: у того animation занят покачиванием
   и подъёмом при перевороте, и любая наша анимация с ними спорила бы.
   У .card свободны translate и rotate (transform занят наклоном за
   указателем, и его не трогаем).

   Каждый такт — отдельные кадры, и у каждого «from» равен концу
   предыдущего такта. Переходов между тактами нет нарочно: переход
   из положения, которое держала анимация, Safari то запускает,
   то нет — и слой прыгал на стыке подрезки и снятия. */
.card-wrap > .card { z-index: 1; translate: 0 0; rotate: 0deg; }

/* Снять — третье расхождение: карта уходит вправо (колода в тот же
   миг влево, правило ниже). Тычок — карта возвращается к центру уже
   в самом перевороте. */
[data-deal="lift"]  .card-wrap > .card { animation: liftCard 300ms var(--ease-move) both; }
[data-deal="flick"] .card-wrap > .card { animation: flickCard 480ms var(--ease-move) both; }
@keyframes liftCard {
  from { translate: 0 0;       rotate: 0deg; }
  to   { translate: 30% -10px; rotate: 3deg; }
}
@keyframes flickCard {
  from { translate: 30% -10px; rotate: 3deg; }
  to   { translate: 0 0;       rotate: 0deg; }
}

/* Подрезка перед следующей картой — два раза, на третий раздача.

   Половины расходятся в ПРОТИВОПОЛОЖНЫЕ стороны и полностью выходят
   из наложения: верхняя карта вправо, колода влево, каждая на 62 %
   своей ширины — между ними просвет. Пока двигалась одна карта, а
   стопка стояла, глаз читал наоборот: рубашки у них одинаковые, и
   казалось, что уехала колода, а карта осталась. Разъединение до
   просвета снимает двусмысленность.

   Два цикла по 420 мс; третий цикл — это уже такт «снять» (lift):
   карта снова уходит вправо, а колода — влево, в левую руку, и там
   остаётся, чтобы оттуда подцепить карту. Сторона у колоды одна
   на всё: влево. За два цикла лицо и рельеф следующей карты успевают
   загрузиться, и разворот идёт уже с готовыми картинками.

   Анимируются translate и rotate, а не transform: transform у карты
   занят наклоном за указателем, у колоды — свободен, но пусть оба
   слоя двигаются одним и тем же способом. */
[data-deal="riffle"] .card-wrap > .card {
  animation: riffleCard 420ms var(--ease-move) 2 both;
}
[data-deal="riffle"] .deck {
  animation: riffleDeck 420ms var(--ease-move) 2 both;
}
@keyframes riffleCard {
  0%   { translate: 0 0;        rotate: 0deg; }
  45%  { translate: 62% -6px;   rotate: 5deg; }
  100% { translate: 0 0;        rotate: 0deg; }
}
@keyframes riffleDeck {
  0%   { translate: 0 0;        rotate: 0deg; }
  45%  { translate: -62% 8px;   rotate: -6deg; }
  100% { translate: 0 0;        rotate: 0deg; }
}

/* Колода уходит в левую руку и слегка наклоняется — на время снятия
   и тычка. Уход ведут кадры от нулевого положения (там колоду
   оставляет и покой, и конец подрезки), а кончаются они ровно
   значением состояния — стыка нет ни на входе, ни на выходе. */
[data-deal="lift"]  .deck,
[data-deal="flick"] .deck { translate: -46% 10px; rotate: -7deg; }
[data-deal="lift"]  .deck { animation: liftDeck 300ms var(--ease-move) both; }
@keyframes liftDeck {
  from { translate: 0 0;       rotate: 0deg; }
  to   { translate: -46% 10px; rotate: -7deg; }
}

/* Карта перевёрнута — колода заезжает за неё целиком, на своё место
   под картой. Возврат ведёт отдельная анимация, а не переход: предыдущее
   положение держала анимация тычка, и переход из него Safari порой
   не запускал — колода не заезжала, а пропадала в один кадр. Кадры
   начинаются от положения «в левой руке» независимо от того, что было
   до, и кончаются ровно базовым значением: скачка после них нет. */
[data-deal="dealt"] .deck {
  translate: 0 0;
  rotate: 0deg;
  animation: deckBack 460ms var(--ease-move) both;
}
@keyframes deckBack {
  from { translate: -46% 10px; rotate: -7deg; }
  to   { translate: 0 0;       rotate: 0deg; }
}

/* Тычок: колода коротко идёт к карте и возвращается. Конец совпадает
   со значением состояния, поэтому скачка после анимации нет. */
[data-deal="flick"] .deck { animation: deckFlick 340ms var(--ease-out) both; }
@keyframes deckFlick {
  0%   { translate: -46% 10px; rotate: -7deg; }
  38%  { translate: -20% 3px;  rotate: -2deg; }
  100% { translate: -46% 10px; rotate: -7deg; }
}

/* Где колоде не место: отдельная карта из справочника и карта во весь
   экран. Добранный расклад гасит всю обёртку и колоду вместе с ней. */
html[data-screen="card"] .deck,
[data-view="full"] .deck { opacity: 0; }

/* ------------------------------------------------------- слой 1: .card */

.card {
  position: relative;
  width: var(--card-w);
  aspect-ratio: var(--card-ratio);
  cursor: pointer;
  border: 0;
  padding: 0;
  background: none;
  display: block;
  -webkit-tap-highlight-color: transparent;

  /* Наклон плоскости под указателем. Углы ставит код, здесь — только
     ровное положение по умолчанию и опора: transform-origin по центру,
     то есть карта качается на игле, воткнутой в её середину.
     Глубина та же, что у переворота, — плоскость и карта живут в одной
     оптике. */
  transform: perspective(1200px)
             rotateX(var(--tilt-x, 0deg))
             rotateY(var(--tilt-y, 0deg));
  transition: transform 160ms var(--ease-out);
}

/* Возврат в плоскость мягче слежения: уход указателя не должен
   читаться как срыв. */
.card.is-level { transition: transform 460ms var(--ease-out); }

.card:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 8px; border-radius: 14px; }

/* --------------------------------------------- слой 2: .card__float */

.card__float { position: absolute; inset: 0; will-change: transform; }

.card:not(.is-flipped):not(.is-turning) .card__float {
  animation: breathe 5s var(--ease-soft) infinite;
}

@keyframes breathe {
  0%, 100% { transform: translateY(0)    scale(1); }
  50%      { transform: translateY(-8px) scale(1.005); }
}

.card.is-turning .card__float { animation: flipLift var(--t-flip) var(--ease-move) both; }

@keyframes flipLift {
  0%   { transform: translateY(0)     scale(1); }
  45%  { transform: translateY(-22px) scale(1.05); }
  100% { transform: translateY(0)     scale(1); }
}

/* --------------------------------------- слой 3: .card__inner (rotateY) */

.card__inner {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  /* perspective задаётся прямо в transform: свойство ancestor-а до внука
     не доходит, а слоёв между .card и .card__inner теперь два. */
  transform: perspective(1200px) rotateY(0deg);
  transition: transform var(--t-flip) var(--ease-flip);
  will-change: transform;
}

.card.is-flipped .card__inner { transform: perspective(1200px) rotateY(180deg); }

.face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border-radius: 10px;
  overflow: hidden;
  box-shadow:
    0 18px 40px rgba(var(--c-shadow-rgb), var(--a-shadow-card)),
    0 0 0 1px rgba(var(--c-accent-rgb), var(--a-accent-hairline));
  transition: box-shadow var(--t-flip) var(--ease-soft);
}

.card.is-turning .face {
  box-shadow:
    0 30px 60px rgba(var(--c-shadow-rgb), var(--a-shadow-card-hi)),
    0 0 0 1px rgba(var(--c-accent-rgb), .4);
}

.face img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
  transform: translateZ(0);
}

.face--front { transform: rotateY(180deg); background: var(--c-paper); }
.face--back  { background: var(--c-surface-card); }

.face--front.is-reversed img { transform: rotate(180deg) translateZ(0); }

/* ------------------------------------------------- лицо «металлик»

   Весь рельеф — SVG-фильтр #fx-metal в index.html, применённый к самой
   картинке лица: браузер считает карту высот из яркости и освещает её.
   Дополнительных картинок нет, свет двигает app.js (setLight). */
[data-face-fx="metal"] #frontImg { filter: url(#fx-metal); }

/* Запечённый рельеф. Лежит поверх живой картинки и проявляется ровно
   тогда, когда живой фильтр снят, — то есть на всё время движения.
   Обе картинки одинаковы по виду, поэтому подмены не видно, зато
   браузеру не приходится пересчитывать девятнадцать примитивов под
   каждый промежуточный масштаб. */
#frontBake {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms var(--ease-out);
}

.face--front.is-reversed #frontBake { transform: rotate(180deg) translateZ(0); }

/* Показывается только у металлика и только пока движение идёт. */
[data-face-fx="metal"][data-fx="off"] #frontBake { opacity: 1; }
[data-face-fx="metal"] .card.is-turning #frontBake,
[data-face-fx="metal"] .card.is-shuffling #frontBake { opacity: 1; }

/* Пока карта растёт из справочника, живой фильтр как раз греется —
   и до его готовности карту держит запечённый кадр. */
[data-face-fx="metal"][data-zoom="in"] #frontBake { opacity: 1; transition: none; }
[data-face-fx="metal"][data-zoom="in"][data-land] #frontBake { opacity: 0; transition: opacity 180ms var(--ease-out); }

/* Без адреса картинки показывать нечего. Правило выше однажды забыло
   про условие на металлик, у остальных колод запечённого кадра нет —
   и пустая картинка легла поверх карты белым прямоугольником прямо
   во время роста. Страховка на случай, если такое повторится. */
#frontBake:not([src]) { opacity: 0 !important; }
/* Во время движения рельеф снимается. Фильтр со светом пересчитывается
   на каждом кадре, и на переворотe и при развороте во весь экран это
   давало моргание слоёв и дрожь картинки: браузер растрирует фильтр
   заново под каждый промежуточный масштаб. В покое он возвращается. */
[data-face-fx="metal"][data-fx="off"] #frontImg,
[data-face-fx="metal"] .card.is-turning #frontImg,
[data-face-fx="metal"] .card.is-shuffling #frontImg { filter: none; }

/* Блик живёт ВНУТРИ грани: так он обрезается по её контуру и сужается
   в перспективе вместе с картой. Отдельный слой поверх этого не умел. */
.face::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-130%);
  background: linear-gradient(105deg,
    transparent 32%,
    rgba(var(--c-shine-rgb), .32) 47%,
    rgba(var(--c-shine-rgb), .08) 57%,
    transparent 72%);
}

.card.is-turning .face::after { animation: sheen var(--t-flip) var(--ease-move) both; }

/* Лицевая грань повёрнута на 180°, её ось X зеркальна — направление
   разворачиваем, чтобы на экране блик шёл в ту же сторону. */
.card.is-turning .face--front::after { animation-name: sheenRev; }

@keyframes sheen {
  0%   { opacity: 0;   transform: translateX(-130%); }
  22%  { opacity: .85; }
  70%  { opacity: .45; }
  100% { opacity: 0;   transform: translateX(130%); }
}

@keyframes sheenRev {
  0%   { opacity: 0;   transform: translateX(130%); }
  22%  { opacity: .85; }
  70%  { opacity: .45; }
  100% { opacity: 0;   transform: translateX(-130%); }
}

/* ========================================================= ШТОРКА */

.panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--sheet-bottom);
  z-index: 25;
  display: flex;
  flex-direction: column;
  height: var(--peek-h);
  border-radius: 20px 20px 0 0;
  border-top: 1px solid var(--c-line);
  background: linear-gradient(180deg, var(--c-surface), var(--c-surface-deep));
  box-shadow: 0 -14px 40px rgba(var(--c-shadow-rgb), var(--a-shadow-panel));
  /* Шторка поднята над кнопками, поэтому просто 110% своей высоты
     не хватает: снизу оставался торчать её край. Уводим на всю высоту
     плюс высоту нижней панели. */
  transform: translateY(calc(100% + var(--sheet-bottom) + 20px));
  transition:
    transform var(--t-sheet) var(--ease-move),
    height var(--t-sheet) var(--ease-move);
  /* Только transform: высота не композитится, и намёк на неё ничего
     не ускоряет, зато держится всё время жизни шторки. */
  will-change: transform;
}

[data-sheet="peek"] .panel { transform: translateY(0); }

[data-sheet="open"] .panel {
  transform: translateY(0);
  height: calc(100% - var(--topbar-h) - var(--sheet-bottom) - 10px);
}

/* Во весь экран шторка полностью уезжает и не мешает смотреть карту. */
[data-view="full"] .panel { transform: translateY(calc(100% + var(--sheet-bottom) + 20px)); }

/* ----------------------------------------------------- шапка шторки */

/* Шапка шторки — обычный div, не кнопка: нажатие всё равно ловится
   на всей шторке, а у кнопок бывают свои браузерные умолчания. */
.sheet__head { flex: none; padding: 16px 48px 12px 20px; }

.sheet__grip {
  position: absolute;
  top: 8px;
  left: 50%;
  width: 34px;
  height: 3px;
  border-radius: 3px;
  transform: translateX(-50%);
  background: var(--c-line-strong);
}

.sheet__title {
  font-family: var(--font-head);
  font-size: 21px;
  line-height: 1.2;
  color: var(--c-accent-head);
}

.sheet__sub {
  margin-top: 3px;
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--c-muted);
}

.sheet__sub.rev { color: var(--c-rev-text); }

/* Крестик появляется только у раскрытой шторки: в свёрнутой закрывать
   нечего. Без рамки и заливки, тем же акцентным цветом, что и кнопка
   меню наверху. */
.sheet__close {
  position: absolute;
  top: 8px;
  right: 10px;
  z-index: 3;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--c-accent-label);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 240ms var(--ease-out), color 200ms var(--ease-out);
}

[data-sheet="open"] .sheet__close { opacity: 1; pointer-events: auto; }
.sheet__close:hover { color: var(--c-accent-head); }

/* ------------------------------------------------------ тело шторки

   Скрываем через display, а не opacity: невидимый, но занимающий место
   текст в свёрнутой шторке приводил к путанице с высотой. */

/* Содержимое шторки ОДНО И ТО ЖЕ в обоих состояниях: свёрнутая просто
   ниже ростом и обрезает его. Отдельного укороченного варианта нет —
   иначе при раскрытии текст подменялся бы, и это сбивало.

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

.sheet__body {
  flex: 1;
  min-height: 0;
  padding: 0 20px 22px;
  overflow: hidden;
}

[data-sheet="peek"] .sheet__body {
  -webkit-mask-image: linear-gradient(to bottom, #000 38%, transparent 96%);
  mask-image: linear-gradient(to bottom, #000 38%, transparent 96%);
}

[data-sheet="open"] .sheet__body {
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.sheet__body::-webkit-scrollbar { width: 6px; }
.sheet__body::-webkit-scrollbar-thumb { background: var(--c-scroll); border-radius: 6px; }
.sheet__body::-webkit-scrollbar-track { background: transparent; }

/* Подсказка «нажмите, чтобы раскрыть» — видна только в свёрнутом виде. */

/* ------------------------------------------------- содержимое описания */

.meta {
  margin-bottom: 20px;
  font-size: 12px;
  line-height: 1.9;
  color: var(--c-text-tag);
}

.meta span + span::before { content: "·"; margin: 0 .5em; color: var(--c-line-strong); }

.block { margin-bottom: 18px; }

.block h3 {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--c-accent-label);
  margin: 0 0 7px;
}

.block p { margin: 0; font-size: 14px; line-height: 1.62; color: var(--c-text-2); }

.prophecy {
  border-left: 2px solid rgba(var(--c-accent-rgb), .55);
  padding-left: 14px;
  margin-bottom: 22px;
}

.prophecy p,
.prophecy-alt {
  font-family: var(--font-head);
  font-size: 15px;
  line-height: 1.75;
  color: var(--c-text);
}

.prophecy-alt {
  display: block;
  margin: 12px 0 0;
  padding-left: 12px;
  border-left: 2px solid rgba(var(--c-accent-rgb), .3);
}

.kw {
  font-family: var(--font-head);
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--c-accent-label);
}

.kw span + span::before { content: ", "; color: var(--c-text-tag); }
.kw.rev { color: var(--c-rev-text); }

.spheres { display: grid; gap: 9px; }

.sphere {
  display: grid;
  gap: 2px;
  font-size: 13px;
  line-height: 1.5;
  padding-bottom: 9px;
  border-bottom: 1px solid rgba(var(--c-line-rgb), .55);
}

.spheres .sphere:last-child { border-bottom: 0; padding-bottom: 0; }
.sphere dt { color: var(--c-accent-label); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; }
.sphere dd { margin: 0; color: var(--c-text-3); }

.divider { height: 1px; background: var(--c-line); margin: 18px 0; border: 0; }

.sheet__body details summary { cursor: pointer; color: var(--c-muted); font-size: 12.5px; }
.sheet__body details summary:hover { color: var(--c-text); }

/* ------------------------------------------------------ нижние кнопки */

.controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  height: var(--controls-h);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 0 16px;
  transition: opacity var(--t-move) var(--ease-out);
}

[data-view="full"] .controls { opacity: 0; pointer-events: none; }

/* Текст на кнопках выровнен по центру. У .slot выравнивание своё:
   там под картой подпись, и её левый край должен совпадать с краем
   изображения. */
.btn {
  font-family: var(--font-body);
  font-size: 14.5px;
  text-align: center;
  letter-spacing: .02em;
  /* Выключка по строчным: сверху отнимаем столько же, сколько
     прибавляем снизу, поэтому высота кнопки не меняется. */
  padding: calc(11px - var(--lc-body)) 22px calc(11px + var(--lc-body));
  background: rgba(var(--c-accent-rgb), var(--a-accent-fill));
  color: var(--c-accent-head);
  cursor: pointer;
  transition: background 220ms var(--ease-out), border-color 220ms var(--ease-out),
              opacity 260ms var(--ease-out), transform 120ms var(--ease-out);
}

.btn:disabled { opacity: .3; cursor: default; }

/* «Следующая карта» проступает вместе со шторкой. Та выезжает снизу за
   --t-sheet, и кнопка набирает плотность в том же движении, а не
   появляется готовой над пустым местом. Переход переписан целиком, а не
   одной длительностью: порядок свойств в .btn менять опаснее, чем
   повторить строку. */
#btnNext {
  transition: background 220ms var(--ease-out), border-color 220ms var(--ease-out),
              opacity var(--t-sheet) var(--ease-out), transform 120ms var(--ease-out);
}

#btnNext.is-veiled { opacity: 0; }
.btn.ghost { border-color: var(--c-line); color: var(--c-muted); background: transparent; }

/* ------------------------------------------------- переключатель тем */

.themes {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border: 1px solid var(--c-line);
  border-radius: 10px;
  background: var(--c-surface);
}

.swatch {
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--c-line);
  background: var(--sw-bg);
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: transform 200ms var(--ease-out), box-shadow 240ms var(--ease-out);
}

.swatch span { width: 7px; height: 7px; border-radius: 50%; background: var(--sw-accent); }
.swatch:hover { transform: translateY(-2px); }

.swatch[aria-pressed="true"] {
  border-color: var(--sw-accent);
  box-shadow: 0 0 0 2px var(--c-surface), 0 0 0 3px var(--sw-accent);
}

/* --------------------------------------------------------------- адаптив

   На узком экране рамка убирается: приложение занимает всё,
   как и должно вести себя мини-апп. */

@media (max-width: 520px) {
  :root {
    /* Шторка забирает пятую часть экрана, но не больше ста пятидесяти
       точек: на коротком телефоне прежние 150 съедали столько, что
       карте не оставалось места. */
    --peek-h: min(150px, 21dvh);

    /* Карта считается и от ВЫСОТЫ тоже. Прежде ширина ограничивалась
       только шириной экрана, и на коротком телефоне открытая карта
       не влезала в полосу между шапкой и шторкой. Высота карты — это
       ширина, умноженная на 1000/579, а в раскрытом виде она ещё и
       уменьшена до 0.838; отсюда делитель 1.4473. */
    --card-fit: calc((100dvh - var(--topbar-h) - var(--peek-h)
                      - var(--controls-h) - 18px) / 1.4473);
    --card-w: min(358px, calc(100vw - 32px), var(--card-fit));
  }

  body { padding: 0; gap: 0; display: block; }

  .phone { padding: 0; border-radius: 0; box-shadow: none; background: none; }

  .phone__screen {
    width: 100vw;
    /* Высоту даёт Telegram, если приложение открыто в нём: во время
       растягивания шторки его окно меняется раньше, чем 100dvh.
       Но не больше того, что видит браузер: если Telegram отдал
       величину раньше, чем окно выросло, вёрстка ушла бы за край. */
    height: min(100dvh, var(--tg-h, 100dvh));
    border-radius: 0;
  }

  .phone__screen::before { display: none; }

  .topbar { padding-top: max(14px, env(safe-area-inset-top)); }
  .controls { padding-bottom: env(safe-area-inset-bottom); }

  /* Шторка упирается в полосу домой-индикатора, а не в самый низ
     стекла: на экране отдельной карты нижней панели нет, и без этого
     последняя строка описания оказывалась под полосой. */
  .sheet__body { padding-bottom: calc(22px + env(safe-area-inset-bottom)); }

  /* Палец толще курсора. Переключатель сортировки был высотой в
     тридцать четыре точки при норме сорок четыре. Растим область
     нажатия, надпись остаётся на месте. */
  .sort { min-height: 44px; }

}

/* Экран шире телефона, но это всё-таки не десктоп — планшет или
   развёрнутое окно мини-аппа. Рамку не рисуем, но и на всю ширину
   не растягиваем: строка в семьсот точек не читается. */
@media (min-width: 521px) and (max-width: 900px) and (pointer: coarse) {
  body { padding: 0; gap: 0; }
  .phone { padding: 0; border-radius: 0; box-shadow: none; background: none; }
  .phone__screen { height: 100dvh; border-radius: 0; }
  .phone__screen::before { display: none; }
  .topbar { padding-top: max(14px, env(safe-area-inset-top)); }
  .controls { padding-bottom: env(safe-area-inset-bottom); }
}

/* --------------------------------------------------------- режим движения

   Системная настройка «Уменьшение движения» глушит все CSS-анимации.
   Раньше здесь стоял безусловный @media (prefers-reduced-motion: reduce),
   из-за чего на такой машине страница не анимировалась вовсе. Теперь
   решает атрибут data-motion на <html>. */

html[data-motion="reduced"] *,
html[data-motion="reduced"] *::before,
html[data-motion="reduced"] *::after {
  animation-duration: .01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: .01ms !important;
}

@media (prefers-reduced-motion: reduce) {
  html[data-motion="auto"] *,
  html[data-motion="auto"] *::before,
  html[data-motion="auto"] *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* --------------------------------------------------------- показ ошибок

   Если что-то падает в рантайме, сообщение видно прямо на странице,
   а не только в консоли. Обычно скрыто. */

.crash {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: 12px;
  z-index: 999;
  padding: 10px 14px;
  border-radius: 8px;
  border: 1px solid #a33;
  background: #2a1416;
  color: #f0b8b8;
  font-family: var(--font-body);
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
}

.crash[hidden] { display: none; }

/* ==================================================== доска расклада

   Карты стоят в канонических позициях своего расклада. Сетка задаётся
   числом колонок, конкретные row/col приходят инлайном из SPREADS —
   так раскладка описана в одном месте, а не размазана по CSS. */

.board {
  position: absolute;
  inset: 0;
  display: none;
  padding: calc(var(--topbar-h) + 20px) 16px 24px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;

  /* Та же причина, что и у экранов: доска прокручивается под панелью. */
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0, transparent var(--topbar-h), #000 calc(var(--topbar-h) + 20px));
  mask-image: linear-gradient(to bottom,
    transparent 0, transparent var(--topbar-h), #000 calc(var(--topbar-h) + 20px));
}

[data-view="spread"] .board { display: block; }
[data-view="spread"] .stage { display: none; }

.board__grid {
  display: grid;
  gap: 10px 8px;
  align-items: start;
  margin-bottom: 20px;
}

.board__grid[data-cols="1"] { grid-template-columns: 180px; justify-content: center; }
.board__grid[data-cols="3"] { grid-template-columns: repeat(3, 1fr); }
.board__grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); }
.board__grid[data-cols="5"] { grid-template-columns: repeat(5, 1fr); gap: 10px 5px; }

/* Звезда и зодиакальный круг: карты стоят по координатам в процентах,
   а поле держит пропорции, чтобы круг оставался кругом при любой
   ширине телефона. */
.board__grid[data-free] {
  display: block;
  position: relative;
  gap: 0;
}
.board__grid[data-free] .slot {
  position: absolute;
  width: var(--slot-w);
  transform: translate(-50%, -50%);
}
/* У зодиака карта 46 px — подпись под ней не помещается, название
   позиции читается по нажатию и в разборе. */
.board__grid[data-free="bare"] .slot__pos { display: none; }
.board__grid[data-free] .slot__pos { font-size: 9.5px; }

.slot {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.slot img {
  display: block;
  width: 100%;
  aspect-ratio: var(--card-ratio);
  object-fit: cover;
  border-radius: 5px;
  border: 1px solid var(--c-line);
  box-shadow: 0 6px 16px rgba(var(--c-shadow-rgb), var(--a-shadow-panel));
  transition: border-color 200ms var(--ease-out), transform 160ms var(--ease-out);
}

.slot:hover img { border-color: rgba(var(--c-accent-rgb), var(--a-accent-border)); }
.slot img.rev { transform: rotate(180deg); }

/* Подписи позиций проступают ПОСЛЕ того, как карты встали по местам,
   и одна за другой — иначе они читаются раньше самих карт. */
.slot__pos {
  display: block;
  margin-top: 5px;
  font-size: 9.5px;
  line-height: 1.3;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--c-muted);
  opacity: 0;
}

.board__grid.is-ready .slot__pos {
  animation: labelIn 420ms var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 160ms);
}

@keyframes labelIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

/* Карта, лежащая поперёк первой, — подпись Кельтского креста.
   Своей подписи не имеет: место читается по самой позе. */
/* Карту поперёк замеряем в плоском виде (класс is-flat), иначе замер
   отдаёт повёрнутую рамку и перелёт уходит мимо. Поворачивается она
   уже после приземления — заодно читается как настоящий жест. */
.slot--cross {
  z-index: 2;
  align-self: center;
  transform: rotate(90deg) scale(.86);
  transition: transform 460ms var(--ease-out);
}

.slot--cross.is-flat { transform: none; }

.slot--cross .slot__pos { display: none; }

/* ------------------------------------------- чтение расклада целиком */

.board__read h3 {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--c-accent-label);
  margin: 0 0 10px;
}

.board__read p {
  margin: 0 0 10px;
  font-family: var(--font-head);
  font-size: 14px;
  line-height: 1.6;
  color: var(--c-text-2);
}

.board__read p:last-child { margin-bottom: 0; }

/* ================================================== перелёты карт

   Летящая копия кладётся сразу в конечное место и сдвигается обратно
   трансформом — дальше анимируется только transform, поэтому десять
   карт разъезжаются без рывков. */

.fly {
  position: absolute;
  z-index: 35;
  pointer-events: none;
  transform-origin: top left;
  will-change: transform;
}

.fly img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 5px;
  border: 1px solid var(--c-line);
  box-shadow: 0 12px 30px rgba(var(--c-shadow-rgb), var(--a-shadow-card));
}

.fly img.rev { transform: rotate(180deg); }

/* Копия карты, которая растёт из плитки справочника и садится обратно.
   Грань у неё та же, что у настоящей карты, — то же скругление и та же
   тень, — иначе подмена в конце читается сменой рамки. Слой тот же, что
   у .fly: пониженный увёл бы копию под шторку, и карта из нижних плиток
   рождалась бы из-под выезжающей панели. */
.fly--card img {
  border: 0;
  border-radius: 10px;
  box-shadow: 0 18px 40px rgba(var(--c-shadow-rgb), var(--a-shadow-card));
}

/* Возврат карты рубашкой вверх должен быть мгновенным: она не
   переворачивается обратно, она улетела, а здесь лежит уже новая. */
.card.no-anim .card__inner { transition: none !important; }

/* В раскладе полоса миниатюр не нужна — карты уже на доске. */
[data-view="spread"] .spread,
[data-view="spread"] .progress { visibility: hidden; }

/* Текст расклада проявляется, когда все карты встали по местам. */
/* Текст расклада идёт последним и заметно мягче подписей:
   задержку выставляет JS по числу карт. */
.board__read {
  opacity: 0;
  transition: opacity 900ms var(--ease-out);
}

.board__read.is-ready { opacity: 1; }

/* Когда расклад добран, следующей карты нет — колода за улетающей
   последней картой на столе не остаётся. */
[data-deck="empty"] .card-wrap {
  opacity: 0;
  transition: opacity 260ms var(--ease-out);
}

/* Выход в меню — часть содержимого доски, а не плавающая панель:
   на больших раскладах кнопка не должна наезжать на карты, до неё
   надо долистать. */
.board__menu {
  display: block;
  /* Отступы под внешнюю линию рамки: при ширине 100% кнопка вылезла бы
     за поля доски. */
  width: calc(100% - 12px);
  margin: 26px 6px 8px;
}

/* В раскладе нижняя панель не нужна — все действия внутри доски. */
[data-view="spread"] .controls { display: none; }

/* ==================================================== образ колоды */

.backs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
}

.back-opt {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  text-align: center;
}

.back-opt img {
  display: block;
  width: 100%;
  aspect-ratio: var(--card-ratio);
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--c-line);
  box-shadow: 0 8px 20px rgba(var(--c-shadow-rgb), var(--a-shadow-panel));
  transition: border-color 200ms var(--ease-out), box-shadow 200ms var(--ease-out),
              transform 160ms var(--ease-out);
}

/* Выбранный вид — цветом уведомления и толстым кольцом: акцентная
   обводка в тон рамки на плитках терялась, и какой вид сейчас стоит,
   было не разобрать. */
.back-opt[aria-pressed="true"] img {
  border-color: var(--c-alert);
  box-shadow: 0 0 0 3px var(--c-alert), 0 8px 20px rgba(var(--c-shadow-rgb), var(--a-shadow-panel));
}

.back-opt__name {
  display: block;
  margin-top: 8px;
  font-family: var(--font-head);
  font-size: 15.5px;
  line-height: 1.25;
  color: var(--c-accent-head);
}

.back-opt[aria-pressed="true"] .back-opt__name { color: var(--c-accent-head); }


/* ==================================================== справочник карт

   Та же сетка, что у расклада на три карты, только рядов двадцать шесть,
   а вместо названия позиции — название самой карты. */

.library { margin-bottom: 0; }
/* В справочнике под картой её название, и оно акцентного цвета:
   это не служебная подпись позиции, а сама подпись карты. */
.library .slot__pos {
  opacity: 1;
  text-transform: none;
  font-size: 12px;
  letter-spacing: .01em;
  color: var(--c-accent-label);
}

/* ==================================================== ПЕРЕКЛЮЧЕНИЕ ЭКРАНОВ

   Блок стоит в самом конце намеренно. Базовые правила .stage, .panel
   и .controls задают display: flex и имеют ту же специфичность, что
   и селекторы ниже; пока этот блок стоял выше, побеждало более
   позднее правило и стол показывался поверх всех экранов. */

/* Показывается ровно один экран навигации. Стол, доска и нижняя панель
   привязаны и к экрану, и к виду: иначе в разложенном раскладе кнопки
   вылезали бы поверх доски. */

.screen {
  position: absolute;
  inset: 0;
  display: none;
  padding: calc(var(--topbar-h) + 20px) 16px 24px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  animation: introIn 480ms var(--ease-out) both;

  /* Содержимое прокручивается ПОД верхней панелью, а у неё нет фона.
     На всей высоте панели содержимое невидимо ПОЛНОСТЬЮ, и только
     ниже неё проступает за двадцать пикселей: плавный переход прямо
     под кнопкой оставлял бы её на полупрозрачном тексте. Отступ сверху
     равен концу перехода, поэтому в покое ничего не теряется. */
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0, transparent var(--topbar-h), #000 calc(var(--topbar-h) + 20px));
  mask-image: linear-gradient(to bottom,
    transparent 0, transparent var(--topbar-h), #000 calc(var(--topbar-h) + 20px));
}

html[data-screen="home"]    .screen[data-s="home"],
html[data-screen="modes"]   .screen[data-s="modes"],
html[data-screen="deck"]    .screen[data-s="deck"],
html[data-screen="about"]   .screen[data-s="about"],
html[data-screen="profile"] .screen[data-s="profile"],
html[data-screen="history"] .screen[data-s="history"],
html[data-screen="backs"]   .screen[data-s="backs"],
html[data-screen="looks"]   .screen[data-s="looks"],
html[data-screen="library"] .screen[data-s="library"] { display: block; }

.stage, .controls, .board, .panel { display: none; }

/* Экран card — отдельная карта из справочника: тот же стол и та же
   шторка, но без нижней панели и без колоды за картой. */

html[data-screen="table"] [data-view="table"] .controls { display: flex; }
html[data-screen="table"] [data-view="spread"] .board { display: block; }

html[data-screen="table"] [data-view="table"] .stage,
html[data-screen="table"] [data-view="full"]  .stage,
html[data-screen="card"]  [data-view="table"] .stage,
html[data-screen="card"]  [data-view="full"]  .stage { display: flex; }

html[data-screen="table"] .panel,
html[data-screen="card"]  .panel { display: flex; }

/* На отдельной карте и в справочнике нижней панели нет — шторка
   доходит до самого низа экрана. */
html[data-screen="card"] { --sheet-bottom: 0px; }

/* На разложенном раскладе нижних кнопок тоже нет — они прячутся,
   когда карты уже на доске. Без этого под раскрытой шторкой оставалась
   прозрачная полоса в высоту исчезнувшей панели, и сквозь неё
   просвечивал фон. */
[data-view="spread"] { --sheet-bottom: 0px; }

/* На домашнем экране возвращаться некуда. */
html[data-screen="home"] .back { visibility: hidden; }

/* ==================================================== сортировка справочника

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

.sort {
  display: none;
  align-items: center;
  gap: 8px;
  padding: 6px 2px 6px 10px;
  border: 0;
  background: none;
  color: var(--c-accent-label);
  cursor: pointer;
  transition: color 200ms var(--ease-out), transform 160ms var(--ease-out);
}

html[data-screen="library"] .sort { display: flex; }
html[data-screen="library"] .progress { display: none; }

.sort:hover { color: var(--c-accent-head); }

.sort__label {
  font-size: 14px;
  letter-spacing: .04em;
  white-space: nowrap;
}

.sort__icon { width: 16px; height: 16px; flex: none; display: block; }

/* Раздел справочника: подпись и линия во всю ширину сетки. Без такого
   разделения «по колоде» и «по числу» отличаются только начиная
   с двадцать третьей карты, и разницу не увидеть. */
.lib-group {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0 2px;
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--c-accent-label);
}

.lib-group::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--c-line);
}

.lib-group:first-child { margin-top: 0; }

/* Кнопка подъёма в конце справочника: во всю ширину сетки, прокручивается
   вместе с содержимым — до неё нужно долистать. */
.lib-top {
  grid-column: 1 / -1;
  margin: 26px 6px 8px;
  padding: calc(11px - var(--lc-body)) 22px calc(11px + var(--lc-body));
  background: transparent;
  color: var(--c-accent-label);
  font-family: var(--font-body);
  font-size: 14.5px;
  text-align: center;
  cursor: pointer;
  transition: border-color 200ms var(--ease-out), color 200ms var(--ease-out),
              transform 160ms var(--ease-out);
}

.lib-top:hover { border-color: var(--c-line-strong); color: var(--c-accent-head); }

/* Заголовок экрана. Стоит над кнопками, а меню центрируется в том,
   что осталось, — иначе заголовок уехал бы вместе с ним. */
/* Заголовок раздела. Опущен ниже верхней панели: он относится
   не к ней, а к тому, что под ним, и стоять должен между кнопками
   меню и кнопками раздела.

   По бокам ромбы — те же, что в торцах кнопочной рамки, но без самой
   рамки: здесь нечего обрамлять, нужна только её примета. */
/* Заголовок отпущен ещё на просвет вниз: он относится к тому, что
   под ним, и стоять должен ближе к середине экрана, а не жаться
   к панели. */
.screen__title {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 26px 0 48px;
  font-family: var(--font-head);
  font-weight: 400;
  font-size: 21px;
  line-height: 1.2;
  text-align: center;
  color: var(--c-accent-head);
}

/* По бокам — тот самый торцевой орнамент, что стоит по краям кнопок,
   а не отвлечённый ромб. Берётся он той же заготовкой: маской по цвету
   темы. Левый торец слева, правый справа, поэтому острия смотрят
   наружу, а срез — к тексту, где у кнопки продолжалась бы линия. */
.screen__title::before,
.screen__title::after {
  content: "";
  width: 38.5px;
  height: 27.17px;
  flex: none;
  background: rgba(var(--c-accent-rgb), .82);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.screen__title::before {
  -webkit-mask-image: var(--f-cap-l);
  mask-image: var(--f-cap-l);
}

.screen__title::after {
  -webkit-mask-image: var(--f-cap-r);
  mask-image: var(--f-cap-r);
}

.screen[data-s="deck"] .menu { min-height: calc(100% - 46px); }

/* ==================================================== календарь истории */

.cal__month {
  font-family: var(--font-head);
  font-size: 23px;
  line-height: 1.2;
  text-align: center;
  color: var(--c-accent-head);
  margin-bottom: 20px;
}

.cal__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px 2px;
}

.cal__wd {
  text-align: center;
  font-size: 11px;
  letter-spacing: .06em;
  color: var(--c-faint);
  padding-bottom: 8px;
}

.cal__day {
  position: relative;
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

.cal__n { font-size: 15px; color: var(--c-text-3); }

/* Сегодняшнее число обведено: календарь без отметки «сегодня»
   заставляет считать по неделям. */
.cal__day.is-today::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32px;
  height: 32px;
  margin: -16px 0 0 -16px;
  border-radius: 50%;
  border: 1px solid var(--c-line-strong);
}

.cal__day.is-today .cal__n { color: var(--c-text); }

/* Точка под числом — в этот день был расклад. */
.cal__dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--c-accent);
}

.cal__empty {
  margin-top: 24px;
  text-align: center;
  font-size: 13px;
  color: var(--c-faint);
}

/* Числа с раскладами нажимаются, поэтому это кнопки — сбрасываем
   их браузерное оформление. */
button.cal__day {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.cal__day.is-sel .cal__n { color: var(--c-accent-head); }

.cal__day.is-sel::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  background: rgba(var(--c-accent-rgb), var(--a-accent-chip-bg));
}

.cal__n, .cal__dot { position: relative; z-index: 1; }

/* ------------------------------------------- список раскладов за день */

.hist-list { display: flex; flex-direction: column; gap: 10px; }
.hist-list:empty { display: none; }

/* Шапка выбранного дня. Появляется вместо календаря: он сворачивается,
   а дата остаётся наверху с крестиком для возврата к выбору. */
.day-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}

.day-head__title {
  font-size: 13px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--c-accent-label);
}

.day-head__close {
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--c-accent-label);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: color 200ms var(--ease-out), transform 160ms var(--ease-out);
}

.day-head__close:hover { color: var(--c-accent-head); }

.hist {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--c-line);
  border-radius: 10px;
  background: linear-gradient(180deg, var(--c-surface), var(--c-surface-deep));
}

.hist__left { display: flex; flex-direction: column; gap: 2px; flex: none; }

.hist__name {
  font-family: var(--font-head);
  font-size: 16px;
  color: var(--c-accent-head);
}

.hist__time { font-size: 11.5px; color: var(--c-faint); }

/* Миниатюры те же, что в полосе во время расклада. */
.hist__cards {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 3px;
}

.hist__cards img {
  display: block;
  width: 18px;
  border-radius: 2px;
  border: 1px solid var(--c-line);
}

.hist__cards img.rev { transform: rotate(180deg); }

/* Записи в списке нажимаются — сбрасываем браузерное оформление кнопки. */
button.hist {
  width: 100%;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-body);
}

button.hist:hover { border-color: var(--c-line-strong); }

/* Уходящий экран истории ещё мгновение виден поверх стола и растворяется:
   так карты читаются вылетающими из списка, а не возникающими на пустом
   месте. */
html[data-exit="history"] .screen[data-s="history"] {
  display: block;
  pointer-events: none;
  z-index: 5;
  animation: screenOut 380ms var(--ease-out) both;
}

@keyframes screenOut {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Возврат в историю: список проявляется, пока карты съезжают в него
   обратно. Зеркало ухода — там экран растворялся. */
html[data-enter="history"] .screen[data-s="history"] {
  animation: screenIn 420ms var(--ease-out) both;
}

@keyframes screenIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ============================== рост карты из справочника

   Карта не возникает поверх списка, а вырастает из своей плитки, и тем
   же движением наоборот в неё возвращается. Движется КОПИЯ (.fly--card):
   настоящая карта всё это время стоит уже конечного размера, чтобы
   браузер посчитал рельеф металлика ПОКА идёт движение, а не после
   него. Поэтому собственные переходы сцены на это время сняты — иначе
   движений было бы два и они разъезжались бы. */
[data-zoom] .stage,
[data-zoom] .card-wrap,
[data-zoom] .card__inner { transition: none !important; }

/* Почти прозрачна, но не прозрачна: невидимое браузер не рисует и не
   фильтрует, и ждать рельефа пришлось бы ровно столько же. Свой слой
   у .card-wrap уже есть (will-change: transform), поэтому смена
   прозрачности идёт композитором и посчитанный растр не выбрасывает. */
[data-zoom="in"] .card-wrap { opacity: .02; }

/* Копия начала растворяться — настоящая карта под ней уже во всей силе. */
[data-zoom="in"][data-land] .card-wrap { opacity: 1; }

/* На обратном пути греть нечего: карту ведёт копия. */
[data-zoom="out"] .card-wrap { opacity: 0; }

/* Уходя с карты, шторка обязана уехать вниз, а не пропасть: на экране
   справочника у .panel display: none, и без этого правила она исчезала
   бы в тот же кадр, в котором тронулась. */
[data-zoom="out"] .panel { display: flex; }

/* И упирается она в тот же низ, что и на экране карты. Иначе после
   ухода с него --sheet-bottom возвращается к высоте нижней панели,
   а перехода у bottom нет — шторка прыгала бы на 66 px вверх ровно
   в тот кадр, когда тронулась вниз. */
[data-zoom="out"] { --sheet-bottom: 0px; }

/* Нажатия по шторке подождут: они меняют размер карты, а копия летит
   в уже отмеренное место. */
[data-zoom] .panel { pointer-events: none; }

/* Выезд шторки на открытии карты ведёт анимация, а не переход. Переходу
   нужно предыдущее состояние, а шторка до этого кадра была display: none —
   и чтобы состояние появилось, приходилось принудительно пересчитывать
   раскладку в самом тяжёлом кадре. Анимации это не нужно. */
[data-zoom="in"] .panel { animation: sheetUp var(--t-sheet) var(--ease-move) both; }

@keyframes sheetUp {
  from { transform: translateY(calc(100% + var(--sheet-bottom) + 20px)); }
  to   { transform: translateY(0); }
}

/* Уходя, шторка только съезжает. Высоте меняться незачем, а её переход —
   это пересчёт раскладки каждый кадр: из раскрытой шторки возврат
   тянул бы за собой всю текстовую колонку. */
[data-zoom="out"] .panel { transition-property: transform; }

/* Справочник виден всё время роста карты и растворяется вместе с ним.
   Растворяется при этом НЕ он сам: список в 78 плиток, с маской и своей
   прокруткой, пришлось бы на время анимации держать отдельным слоем, и
   это стоило бы кадра ровно там, где начинается движение. Гаснет завеса
   поверх него — с тем же фоном, что у экрана. На вид это одно и то же:
   список растворяется в том самом фоне, который под ним и окажется. */
html[data-exit="library"] .screen[data-s="library"] {
  display: block;
  pointer-events: none;
  z-index: 5;
}

/* Возврат: базовое introIn у .screen сдвигает секцию на 14 px и
   растворяет её — плитка уезжала бы из-под садящейся карты. Проявление
   здесь ведёт та же завеса, а списку двигаться незачем. */
html[data-enter="library"] .screen[data-s="library"] { animation: none; }

.phone__screen::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  opacity: 0;
  background:
    radial-gradient(120% 60% at 50% -10%, var(--c-bg-glow-1) 0%, transparent 60%),
    var(--c-bg);
}

.phone__screen[data-zoom="in"]::after  { animation: veilIn  var(--t-sheet) var(--ease-move) both; }
.phone__screen[data-zoom="out"]::after { animation: veilOut var(--t-sheet) var(--ease-move) both; }

@keyframes veilIn  { from { opacity: 0; } to { opacity: 1; } }
@keyframes veilOut { from { opacity: 1; } to { opacity: 0; } }

/* Карта поднимается над завесой: под ней она стояла бы вместе
   со списком и к моменту подмены оказалась бы закрашена. */
[data-zoom] .stage { z-index: 7; }

/* Листание месяцев. Окно ограничено месяцем самой ранней записи и
   нынешним: дальше в обе стороны смотреть нечего, и стрелка гаснет. */
.cal__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 20px;
}

.cal__nav .cal__month { margin-bottom: 0; flex: 1; }

.cal__arrow {
  flex: none;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--c-accent-label);
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: color 200ms var(--ease-out), opacity 200ms var(--ease-out),
              transform 160ms var(--ease-out);
}

.cal__arrow svg { width: 18px; height: 18px; display: block; }
.cal__arrow:hover:not(:disabled) { color: var(--c-accent-head); }
.cal__arrow:disabled { opacity: .22; cursor: default; }

/* ------------------------------------------------ выбор расклада

   Список строится из таблицы раскладов и разбит на разделы по типу
   вопроса: одиннадцать схем плоским списком читаются плохо. */

.mode-group {
  font-family: var(--font-head);
  font-size: 13px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--c-accent-label);
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 10px 18px 0;
}

/* Линия с обеих сторон, а не только справа: тогда название типа стоит
   по центру, как и всё остальное на этом экране — названия раскладов,
   их подписи и число карт. */
.mode-group::before,
.mode-group::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--c-line);
}

/* Первый заголовок не должен отрываться от верха: у .menu своё
   центрирование, и лишний отступ сверху сдвигает весь список. */
.mode-group:first-child { margin-top: 0; }

/* Меню раскладов длиннее любого другого — центрировать его по высоте
   нельзя, оно должно прокручиваться от начала. */
.screen[data-s="modes"] .menu { min-height: 0; justify-content: flex-start; }

/* Отметка о числе карт стоит справа от названия, но вне его потока:
   в потоке она сдвинула бы название влево на свою ширину, а название
   должно оставаться по центру кнопки. Отсюда абсолютная привязка
   к правому краю самого названия. */
.mode { position: relative; }
.mode__title { position: relative; display: inline-block; }

/* Только там, где подпись у кнопки одна: в выборе расклада под названием
   стоит ещё вопрос, и пара центрируется как целое. */
.mode__title:only-child { transform: translateY(calc(-1 * var(--lc-head))); }

.mode__count {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-left: 9px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1;
  white-space: nowrap;
  color: var(--c-accent-label);
  opacity: .85;
}

/* Цена разбора в сферах. Висит слева от названия ровно так же, как
   счётчик карт справа: название остаётся по середине кнопки, а обе
   отметки свешиваются за его края. */
.mode__price {
  position: absolute;
  right: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-right: 9px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1;
  white-space: nowrap;
  color: var(--c-accent-label);
  opacity: .85;
}

/* Значок ростом с прописную букву рядом: тогда его середина и середина
   цифр совпадают сами, без поправок на метрики шрифта. Раньше он был
   выше строки, центры расходились, и горизонт казался рваным.
   Толщина линии задана в единицах viewBox и подобрана так, чтобы на
   этом росте выйти примерно в тот же вес, что у цифр. */
.mode__deck {
  display: block;
  width: 9.9px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.85;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Значок не должен спорить с названием: он подпись, а не украшение. */
  opacity: .6;
}

/* Сфера рядом с ценой: ростом со значок колоды напротив. Приглушать её
   прозрачностью, как значок колоды, нельзя: тот подпись одного цвета
   с текстом, а эта — цветная метка валюты, и от прозрачности её краски
   выцветают в фон. Тише она за счёт роста, а не бледности. */
.mode__orb {
  display: block;
  width: 12px;
  height: 12px;
  flex: none;
}

/* В двенадцать пикселей линия в долях значка должна быть толще: при
   общей с крупной сферой она обращается в паутину. */
.mode__orb .orb__ring  { stroke-width: 3.2; }
.mode__orb .orb__gleam { stroke-width: 3.8; }

/* Цифры цены — цветом контура сферы: цена и есть та же валюта, и
   читаться они должны как одно целое, а не как подпись рядом. */
.mode__price .mode__mult { color: var(--c-orb-num); }

/* ---------------------------------------------- описание расклада */

.about { padding-top: 26px; padding-bottom: 24px; }

/* ------------------------------------------------- заход в расклад */

/* Затемнение: экран уходит в чёрный ровно за секунду и так же
   возвращается. Линейно, без ускорений. */
.veil {
  position: absolute;
  inset: 0;
  z-index: 40;
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 1000ms linear;
}
.veil.is-on { opacity: 1; pointer-events: auto; }

/* Окно захода поверх стола: один вопрос, два-четыре ответа кнопками.
   Выбранный остаётся, остальные бледнеют, и появляется «Перемешать
   колоду». */
/* Окно стоит по центру экрана: столбик кнопок выше сетки, и от
   фиксированного отступа сверху низ уезжал за край. Проявляется оно
   прозрачностью, а не сдвигом, — сдвиг занят центрированием. */
.ask {
  position: absolute;
  left: 8px;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 30;
  padding-top: 22px;
  padding-bottom: 20px;
  text-align: center;
  animation: askIn 360ms var(--ease-out) both;
}
@keyframes askIn { from { opacity: 0; } to { opacity: 1; } }
.ask__title {
  font-family: var(--font-head);
  font-size: 22px;
  line-height: 1.2;
  color: var(--c-accent-head);
  margin: 0 0 16px;
  text-wrap: balance;
}
/* Варианты — обычные кнопки проекта, с тем же торцевым орнаментом,
   что у остальных. Орнамент вылезает на 18 px в каждую сторону, поэтому
   рядом такие кнопки не встают — только столбиком, во всю ширину. */
.ask__opts { display: flex; flex-direction: column; gap: 11px; }
/* Выбранный — залит; остальные бледнеют, но остаются нажимаемыми. */
.ask__opt.is-on::before { background: rgba(var(--c-accent-rgb), .8); }
.ask__opt.is-on { color: var(--c-bg); }
.ask__opt.is-dim { opacity: .45; }

/* Выход из выбора — тихой строкой, не пятой кнопкой. */
.ask__plain:empty { display: none; }
.ask__plain { margin-top: 12px; }
.ask__link {
  border: 0;
  background: none;
  padding: 6px 4px;
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--c-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.ask__link.is-on { color: var(--c-accent-head); }

/* Кнопка хода видна сразу и бледнеет, пока выбор не сделан. */
.ask__go { margin-top: 15px; }
.ask__go:disabled { opacity: .3; }

/* Такт тасовки после «Перемешать колоду»: рубашка сдвигается, как
   пакеты при снятии. Короче секунды — иначе это уже сцена. */
.card.is-shuffling { animation: shuffle 620ms var(--ease-out) both; }
@keyframes shuffle {
  0%, 100% { transform: translateX(0) rotate(0); }
  18%      { transform: translateX(-7px) rotate(-.7deg); }
  40%      { transform: translateX(7px) rotate(.7deg); }
  62%      { transform: translateX(-4px) rotate(-.3deg); }
  82%      { transform: translateX(4px) rotate(.3deg); }
}

/* Если описание короче экрана, панель встаёт по центру, а не липнет
   к верхнему краю. safe center — чтобы длинное описание не срезало
   сверху при прокрутке; браузер, который его не понимает, просто
   оставит панель наверху, как было. */
html[data-screen="about"] .screen[data-s="about"] {
  display: flex;
  flex-direction: column;
  justify-content: safe center;
}

.about__name {
  font-family: var(--font-head);
  font-size: 24px;
  line-height: 1.15;
  margin: 0 0 6px;
  color: var(--c-accent-head);
  text-align: center;
}

.about__question {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--c-accent-label);
  text-align: center;
  margin: 0 0 16px;
}

.about__text {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--c-text);
  margin: 0 0 20px;
  /* Последнее, что оставалось выключкой влево: на этом экране всё
     прочее — название, вопрос, позиции, кнопка — уже по центру. */
  text-align: center;
}

/* «Когда брать» — та же речь, что и описание, но тише: это уже не про
   расклад, а про то, в какую минуту за него садиться. */
.about__when {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--c-text-2);
  margin: 0 0 22px;
  text-align: center;
}

/* Подпись над списком — как заголовки групп в меню раскладов. */
.about__label {
  font-family: var(--font-head);
  font-size: 13px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--c-accent-label);
  text-align: center;
  margin: 0 0 10px;
}

.about__asks {
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
  text-align: center;
}

/* Между вопросами такой же просвет, как между строками описания:
   отдельный отступ у пунктов сбивал ритм страницы — глаз спотыкался
   о два разных межстрочных на одном экране. */
.about__asks li {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--c-text);
  padding: 0;
}

/* Позиции идут в том же порядке, что карты на доске, поэтому нумеровать
   их незачем — номер только утяжелял строку. Разделительные линии тоже
   сняты: список короткий, и без них он читается спокойнее. */
.about__pos {
  list-style: none;
  margin: 0 0 26px;
  padding: 0;
  text-align: center;
}

.about__pos li { padding: 9px 0; }

.about__pos-name {
  display: block;
  font-family: var(--font-head);
  font-size: 16px;
  color: var(--c-accent-head);
}

.about__pos-note {
  display: block;
  margin-top: 1px;
  font-family: var(--font-body);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--c-muted);
}

/* Кнопка по центру, а не во всю ширину: панель центрирована целиком,
   и растянутая кнопка ломала бы эту ось. Специфичность выше .btn —
   иначе поля из блока рамки в конце файла перебьют auto. */
.btn.about__start {
  display: block;
  width: fit-content;
  margin: 4px auto 0;
}

/* ----------------------------------------------------- фоновая гравюра

   «Смерть и ландскнехт», немецкая ксилография XVI века, общественное
   достояние. Лежит под всем содержимым экрана.

   Три вещи делаются намеренно:

     размытие   гравюра штриховая, в резкости она рябит и спорит
                с текстом; размытая читается как фактура бумаги;
     прозрачность  сквозь неё просвечивает краска темы, поэтому
                фон сам оказывается в тон палитре — на светлых
                палитрах гравюру приходится делать заметнее,
                отсюда токен --a-bg-art;
     вынос за края  у листа белые поля и своя рамка. Растягиваем
                крупнее экрана, чтобы ни поля, ни размытый край
                в кадр не попали.

   Разрешение исходника меньше экрана на ретине, но это не важно:
   размытие всё равно съедает детали, и увеличение не видно. */

.bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  transition: opacity 420ms var(--ease-out);
}

.bg::before {
  content: "";
  position: absolute;
  /* Крупнее экрана со всех сторон, но ровно настолько, чтобы за кадром
     остались белые поля листа и размытая кромка: при 6 % край гравюры
     уходит за экран примерно на полсотни пикселей — этого хватает. */
  inset: -6%;
  background: url("../img/bg/woodcut.jpg") center / cover no-repeat;
  filter: grayscale(1) blur(2px);
  opacity: var(--a-bg-art);
}

/* Там, где на экране карты, фон убирается: на раскладе, на отдельной
   карте и в значении колоды он спорит с самими картами. */
html[data-screen="table"]   .bg,
html[data-screen="card"]    .bg,
html[data-screen="library"] .bg { opacity: 0; }

/* ------------------------------------------------- внешний вид колоды

   Экран показывает обе стороны карты сразу: лицо колоды и рубашку.
   Каждая — кнопка, ведущая к списку вариантов именно этой стороны;
   редактируются они по отдельности. */

.look-switch {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
}

/* Экран короткий — две карты и подписи. Ставим их по центру, иначе
   они висят у верхнего края с пустотой под ними. */
html[data-screen="backs"] .screen[data-s="backs"] {
  display: flex;
  flex-direction: column;
  justify-content: safe center;
}

.look-opt {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  text-align: center;
  color: inherit;
}

/* Окно карты задаётся контуром, а не картинкой. Иначе высота
   берётся из файла, и любая разница между лицевой и рубашкой —
   хоть в доли процента, хоть от старой картинки в кэше — видна
   как разные по размеру окна. */
.look-opt img {
  display: block;
  width: 100%;
  aspect-ratio: var(--card-ratio);
  object-fit: cover;
  border-radius: 4px;
  box-shadow: 0 0 0 1px var(--c-line);
  transition: box-shadow 200ms var(--ease-out), transform 200ms var(--ease-out);
}

.look-opt:hover img { box-shadow: 0 0 0 2px var(--c-accent); }

.look-opt__label {
  display: block;
  margin-top: 12px;
  font-family: var(--font-head);
  font-size: 19px;
  color: var(--c-accent-head);
}

.look-opt__now {
  display: block;
  margin-top: 3px;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--c-muted);
}

/* ----------------------------------------------------------- профиль

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

.prof {
  position: absolute;
  right: 14px;
  /* Ниже, чем «Назад» на других экранах: на главном слева пусто,
     равняться не на что, а у верхнего края кнопка смотрелась поджатой. */
  top: 52px;
  width: 136px;
  height: 40px;
  display: none;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* Только на главном: на остальных экранах слева стоит «Назад»,
   и две кнопки в шапке спорили бы друг с другом. */
html[data-screen="home"] .prof { display: block; }

/* ------------------------------------------------------------- сферы

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

   Ширина задана жёстко под четыре цифры: от суммы кнопка прыгать не
   должна, иначе шапка дёргается при каждой трате. */

.coin {
  position: absolute;
  left: 14px;
  top: 52px;                    /* вровень с профилем напротив */
  width: 116px;
  height: 40px;
  display: none;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* Только на главном — как и профиль: на прочих экранах слева «Назад». */
html[data-screen="home"] .coin { display: block; }

.coin__strip {
  position: absolute;
  left: 30px;
  right: 0;
  top: 5.07px;
  bottom: 5.07px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Зеркально профилю: орнамент справа уходит внутрь на 18 px,
     сфера слева заходит под полоску на 10 px. */
  padding: 0 18px 0 10px;
}

.coin__num {
  font-family: var(--font-head);
  font-size: 16px;
  transform: translateY(calc(-1 * var(--lc-head)));
  letter-spacing: .02em;
  line-height: 1;
  white-space: nowrap;
  /* Цифры одной ширины: сумма меняется, а надпись не ёрзает. */
  font-variant-numeric: tabular-nums;
  color: var(--c-accent-head);
}

.coin__orb {
  position: absolute;
  left: 0;
  top: 0;
  width: 40px;
  height: 40px;
}

.coin__orb .orb { display: block; width: 100%; height: 100%; }

/* Шар в шапке — готовая картинка со своим светом. Фильтра рельефа на
   нём нет намеренно: он посчитал бы вторую подсветку поверх запечённой,
   и блики разошлись бы. Как этот рельеф был устроен, записано в
   docs/reference/metal-face.md — на случай, если шар вернётся к
   рисованному. */
.coin__orb .orb { user-select: none; -webkit-user-drag: none; }

/* --------------------------------------------------------- сфера-валюта

   Значок валюты собран из одних и тех же частей и в шапке ростом
   сорок пикселей, и у цены расклада ростом двенадцать: тело, кольцо,
   дуга-блик, точка и искры. Отличаются только толщины и состав —
   мелкому не по росту ни точка, ни вторая искра.

   Цвет приходит из темы двумя переменными: контур и заливка. Задавать
   его прямо в картинке нельзя: var() в presentation-атрибутах
   поддержан не везде, и значок местами вышел бы чёрным. Поэтому у
   каждой части свой класс, а красит её отсюда тема. */

.orb__body  { fill: var(--c-orb-fill); }
.orb__ring  { fill: none; stroke: var(--c-orb-line); stroke-width: 3.1; }
.orb__gleam { fill: none; stroke: var(--c-orb-line); stroke-width: 3; stroke-linecap: round; }
.orb__dot,
.orb__spark { fill: var(--c-orb-line); }

/* Полоска обрывается за 30 px до правого края кнопки — там начинается
   круг, и линии заходят под него на 10 px. Высота рамки полоски 29.86 px,
   отсюда и отступы сверху и снизу.

   Слева торцевой орнамент уходит внутрь полоски на 18 px — надпись
   отодвинута, чтобы на него не наезжать. */
.prof__strip {
  position: absolute;
  left: 0;
  right: 30px;
  top: 5.07px;
  bottom: 5.07px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Надпись центрируется в свободном поле между орнаментом и кругом:
     орнамент на высоте строки доходит до 18 px, круг начинается за
     10 px до правого края полоски. Отсюда и несимметричные отступы —
     иначе текст уезжает вправо. */
  padding: 0 10px 0 18px;
}

.prof__name {
  /* Kurale, как заголовки кнопок меню под ней: подпись в шапке
     из той же пары, что и остальной интерфейс. */
  font-family: var(--font-head);
  font-size: 16px;

  /* Выключка по строчным — та же, что у остальных однострочных
     кнопок; величина в токене --lc-head. */
  transform: translateY(calc(-1 * var(--lc-head)));
  letter-spacing: .02em;
  line-height: 1;
  white-space: nowrap;
  color: var(--c-accent-head);
}

.prof__ava {
  position: absolute;
  right: 0;
  top: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  /* Толщина кольца равна линии полоски, иначе круг выглядит приклеенным. */
  border: 2.57px solid rgba(var(--c-accent-rgb), .82);
  background: var(--c-surface-deep) center / cover no-repeat;
  transition: border-color 220ms var(--ease-out), box-shadow 160ms var(--ease-out);
}

.prof:hover .prof__ava { border-color: var(--c-accent); }

/* --------------------------------------------------- экран профиля */

.prof-card {
  padding-top: 30px;
  padding-bottom: 26px;
  text-align: center;
}

.prof-card__ava {
  display: block;
  width: 92px;
  height: 92px;
  margin: 0 auto 24px;
  border-radius: 50%;
  border: 3px solid rgba(var(--c-accent-rgb), .82);
  background: var(--c-surface-deep) center / cover no-repeat;
}

.prof-card__rows { margin: 0 0 26px; }

.prof-card__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--c-line);
}

.prof-card__row:last-child { border-bottom: 0; }

.prof-card__row dt {
  font-family: var(--font-body);
  font-size: 15.5px;
  color: var(--c-muted);
}

.prof-card__row dd {
  margin: 0;
  font-family: var(--font-head);
  font-size: 18px;
  color: var(--c-accent-head);
}

.prof-card__label {
  display: block;
  font-family: var(--font-body);
  font-size: 15.5px;
  color: var(--c-muted);
  margin-bottom: 14px;
}

/* Переключатель тем переехал сюда из-под макета телефона: в мини-
   приложении всё, что вне экрана, недоступно. Своя рамка ему тут
   не нужна — он и так внутри панели. */
.prof-card__themes .themes {
  justify-content: center;
  gap: 14px;
  padding: 0;
  border: 0;
  background: none;
}

.prof-card__themes .swatch { width: 26px; height: 26px; }

/* Замер устройства. Служебная строка: мелкая, приглушённая, переносится
   по словам. Читается с экрана телефона, где консоли нет. */
.prof-card__probe:empty { display: none; }
.prof-card__probe {
  margin: 14px 0 0;
  font-family: var(--font-body);
  font-size: 11px;
  line-height: 1.5;
  color: var(--c-muted);
  opacity: .7;
  word-break: break-word;
}

/* Пополнение сфер. Кнопка стоит над оформлением и отделена от него
   линией: это другое действие, а не ещё одна настройка вида. */
.prof-card__buy {
  margin-top: 4px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--c-line);
  margin-bottom: 22px;
  text-align: center;
}

.prof-card__orbs { min-height: 44px; }

/* Подложка под окно пополнения: приглушает экран, но не гасит его.
   Нажатие мимо окна закрывает окно. */
.dim {
  position: absolute;
  inset: 0;
  z-index: 29;
  background: rgba(var(--c-shadow-rgb), .62);
  opacity: 0;
  pointer-events: none;
  transition: opacity 240ms var(--ease-out);
}
.dim.is-on { opacity: 1; pointer-events: auto; }

/* Окно способов пополнения — сразу над подложкой. */
.buy {
  position: absolute;
  left: 8px;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 30;
  padding-top: 22px;
  padding-bottom: 20px;
  text-align: center;
  animation: askIn 360ms var(--ease-out) both;
}

.buy .ask__opt { min-height: 44px; }

/* Подсказка о награде за приглашение: между кнопками и «Не сейчас»,
   видна всегда. */
.buy__hint {
  margin: 12px 0 2px;
  font-family: var(--font-body);
  font-size: 13.5px;
  line-height: 1.45;
  text-align: center;
  color: var(--c-muted);
}
.buy__hint:empty { display: none; }

/* Пояснение под кнопками: появляется только когда есть что сказать. */
.buy__note:empty { display: none; }
.buy__note {
  margin: 14px 0 0;
  font-family: var(--font-body);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--c-muted);
}
.prof-card__themes .swatch span { width: 9px; height: 9px; }

/* --------------------------------------------------------- уведомление

   Метка на кнопке: что-то за ней изменилось и стоит зайти. Цвет для неё
   в каждой палитре свой и взят из её же авторского набора — не подобран
   на глаз. Он нарочно контрастен акценту: у розовой темы акцент золотой,
   а метка розовая; у нордической акцент холодный голубой, метка тёплая
   оранжевая. Иначе метка сливается с рамкой и перестаёт быть меткой. */
/* Знак стоит не в углу кнопки, а над самой подписью и заезжает на неё
   углом ромба: так видно, к чему он относится, а не просто что
   на кнопке что-то есть. Поэтому он и лежит внутри подписи. */
.mode__badge {
  position: absolute;
  top: -8px;
  right: -19px;
  z-index: 1;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-head);
  font-size: 14px;
  line-height: 1;
  /* Правится по габариту знака, а не по его чернилам: в ромбе глаз
     ловит крайние точки — верх палочки и точку внизу, — а не центр
     тяжести. Он у «!» лежит на 0.6 px выше габаритного центра, и
     равнение по нему как раз и задирало знак вверх.
     Мерено (sh tools/font-metrics.sh, Kurale): габарит знака стоит
     на 0.0134 em ниже середины площадки шрифта, по которой равняет
     flex. Поле сверху опускает содержимое на половину своей
     величины — отсюда удвоенная поправка. */
  padding-top: .027em;
  color: var(--c-alert);
  /* Гарнитуры в проекте только обычного начертания, жирного нет.
     Обводка по контуру даёт нужную плотность, не подтягивая
     синтетический полужирный и лишний файл шрифта. */
  -webkit-text-stroke: .85px currentColor;
  animation: badgeIn 320ms var(--ease-out) both;
}

/* Ромб — контуром, без заливки: знак читается сам по себе, а рамка
   вокруг него повторяет ту же геометрию, что в торцах кнопок. */
.mode__badge::before {
  content: "";
  position: absolute;
  inset: 4px;
  border: 1.5px solid var(--c-alert);
  transform: rotate(45deg);
}

@keyframes badgeIn {
  from { opacity: 0; transform: scale(.4); }
  to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------ отклик на указатель

   Одного цвета мало: на светлых палитрах смена оттенка у рамки почти
   не читается, и кнопка выглядит мёртвой. Поэтому она ещё и подрастает.

   На мыши это наведение. На телефоне наведения нет вовсе — там
   то же самое по нажатию, и :active работает на обоих.

   Крупные растут меньше мелких: два процента от кнопки во всю ширину
   это уже шесть-семь пикселей, а от иконки в двадцать столько же
   незаметно. Отсюда две ступени. */

button.hist, .prof { transition: transform 160ms var(--ease-out); }

@media (hover: hover) {
  .mode:hover,
  .btn:hover:not(:disabled),
  .lib-top:hover,
  button.hist:hover     { transform: scale(1.02); }

  .look-opt:hover img,
  .back-opt:hover img,
  .slot:hover img       { transform: scale(1.03); }

  /* «Назад» и «Профиль» — не значки, а те же кнопки с резной рамкой,
     только мельче. Восемь процентов им много: полоска рамки при этом
     заметно толстеет. */
  .back:hover,
  .prof:hover           { transform: scale(1.03); }

  .sort:hover,
  .cal__arrow:hover:not(:disabled) { transform: scale(1.08); }

  /* у образца темы уже был подъём — оставляем и его */
  .swatch:hover         { transform: translateY(-2px) scale(1.08); }
}

/* Тень под кнопкой с резной рамкой.

   Первая попытка была drop-shadow по подложке: он идёт по прозрачности
   и точно повторил бы силуэт со всеми вырезами. Не работает — маска
   применяется ПОСЛЕ фильтра, и тень срезается той же маской, которая
   и делает форму. На экране не появлялось ничего.

   Поэтому тень обычная, на самой кнопке. Её собственная коробка на
   7 px короче и на 18 px уже подложки, то есть тень целиком прячется
   под непрозрачной заливкой — кроме той части, которую вынесли вниз.
   Сдвиг 14 px при поджатии на 6 даёт полоску, начинающуюся на 8 px
   ниже подложки: ровно просвет под приподнятым краем, и ничего лишнего
   по бокам.

   Правило стоит здесь, а не в блоке рамки: тот блок целиком
   переписывается сборщиком (sh tools/frame/build.sh), и правка в нём
   пропала бы при первой же пересборке. */
.mode,
.btn,
.lib-top,
.back,
.prof__strip { transition: box-shadow 160ms var(--ease-out); }

/* Приподнялась — значит, под ней есть просвет, а в просвете тень.
   Она нарочно слабая и близкая: это отрыв на пару миллиметров,
   а не карточка, повисшая над столом. */
@media (hover: hover) {
  .mode:hover,
  .btn:hover:not(:disabled),
  .lib-top:hover {
    box-shadow: 0 14px 18px -6px rgba(var(--c-shadow-rgb), .3);
  }
  button.hist:hover {
    box-shadow: 0 5px 12px rgba(var(--c-shadow-rgb), .22);
  }
  /* У полоски «назад» и профиля подложка по высоте вровень с самой
     кнопкой — её выносит только вбок, — поэтому вниз хватает меньшего
     сдвига, чем у больших кнопок. */
  .back:hover,
  .prof:hover .prof__strip,
  .prof:hover .prof__ava {
    box-shadow: 0 8px 12px -4px rgba(var(--c-shadow-rgb), .28);
  }
  .look-opt:hover img,
  .back-opt:hover img,
  .slot:hover img {
    box-shadow: 0 12px 26px rgba(var(--c-shadow-rgb), var(--a-shadow-card));
  }
}

.mode:active,
.btn:active:not(:disabled),
.lib-top:active {
  box-shadow: 0 18px 22px -6px rgba(var(--c-shadow-rgb), .34);
}
button.hist:active {
  box-shadow: 0 7px 16px rgba(var(--c-shadow-rgb), .26);
}
.back:active,
.prof:active .prof__strip,
.prof:active .prof__ava {
  box-shadow: 0 11px 15px -4px rgba(var(--c-shadow-rgb), .32);
}
.look-opt:active img,
.back-opt:active img,
.slot:active img {
  box-shadow: 0 16px 32px rgba(var(--c-shadow-rgb), var(--a-shadow-card-hi));
}

/* Нажатие — то же самое и чуть сильнее: палец закрывает кнопку,
   и отклик должен быть заметнее наведения. */
.mode:active,
.btn:active:not(:disabled),
.lib-top:active,
button.hist:active      { transform: scale(1.03); }

.look-opt:active img,
.back-opt:active img,
.slot:active img        { transform: scale(1.05); }

/* Плитку справочника замеряют в момент нажатия, когда она ещё увеличена
   под пальцем. На время замера её кладут ровно — иначе копия стартует
   крупнее плитки и в первый же кадр видно скачок. Переход снимаем
   заодно: без него плитка возвращалась бы к своему размеру эти же
   160 мс, и замер попадал бы на середину. */
.slot.is-flat img,
.slot.is-flat:active img { transform: none; transition: none; }

.back:active,
.prof:active            { transform: scale(1.05); }

.sort:active,
.cal__arrow:active:not(:disabled) { transform: scale(1.12); }
.swatch:active          { transform: translateY(-2px) scale(1.12); }

/* ============================================ СРЕДНЕВЕКОВАЯ РАМКА КНОПОК

   Векторная копия docs/reference/frame-medieval.avif. Не нарисована
   на глаз: растр образца обведён по контуру, спрямлён по Дугласу—
   Пойкеру и сверен с оригиналом наложением — совпадение 99.96 %.
   Пересборка: sh tools/frame/build.sh

   Толщина линии выровнена. В образце верхняя линия сужается к пику
   с 18 единиц до 14; на трёхпиксельной полосе это читалось как
   разнотолщинность. Линия перерисована по медиальной оси с постоянным
   радиусом, подобранным так, чтобы средняя толщина в подъёме совпала
   с прямым участком.

   Пути — ломаные, а не кривые. Сглаживание через середины звеньев
   скругляло углы, которыми деталь срезана по краю окна, и на стыке
   с прямой полосой появлялся вырез. Сверх того заготовки заходят
   на полосы на 1.2 px — против волосяных щелей от округления.

   Образец симметричен по обеим осям, поэтому деталей всего четыре:
   торцевой орнамент, центральный подъём и такие же две для подложки.
   Вторые половины — те же пути, отражённые transform. Каждая объявлена
   переменной: список маски выписывается дважды, с префиксом и без,
   и подставлять картинку целиком по два раза незачем.

   Кнопки разной ширины, растянуть картинку целиком нельзя: орнаменты
   поплывут. Поэтому и рамка, и подложка собраны слоями маски:

     орнаменты        обведённые SVG в пикселях — пропорции целы
     полосы и стойки  градиентные полоски, тянутся под размер

   Разметка снята с образца, единица = 1/6 px:

     полоса и стойка  18 ед. = 3 px
     торец            231x163 ед., по вертикали по центру рамки
     подъём           224x58 ед., по горизонтали по центру

   Стойка нарисована двумя отрезками, а не насквозь: в образце её
   разрывает торцевой орнамент.

   Подложка (::before) идёт по внутреннему контуру рамки, а не
   прямоугольником: заходит внутрь центрального подъёма и обтекает
   торцевой орнамент снаружи, поэтому торцы читаются на фоне страницы,
   а не на заливке кнопки. Контур взят заливкой от центра по «не-рамке»,
   так что пустоты внутри орнамента подложкой не закрываются.

   Картинки встроены как data-URI, а не лежат файлами: на file://
   внешний SVG в mask-image считается чужим источником и молча
   отбрасывается — остаются одни полосы.

   Цвет берётся из темы: в образце золото задано намертво, а рамка
   должна работать и на Nord, и на двух светлых палитрах. */

:root {
  --f-cap-l:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 231 163'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M105,0L123,0L123,10L125,10L125,13L127,14L128,19L130,19L130,21L132,21L132,24L135,26L135,28L137,28L137,30L139,31L139,33L141,33L141,35L142,35L144,38L146,38L146,40L148,40L148,42L150,42L150,43L151,43L151,44L153,44L155,47L157,47L157,49L160,49L160,51L162,51L162,52L164,52L165,54L167,54L167,56L170,56L171,58L176,59L176,60L177,60L177,61L179,61L180,63L183,63L183,64L185,64L185,65L188,65L188,66L190,66L191,68L195,68L195,70L200,70L200,72L208,73L209,75L214,75L215,77L220,77L220,79L225,79L225,80L227,80L227,81L228,81L228,82L227,82L227,83L225,83L225,84L220,84L220,86L215,86L214,88L209,88L208,90L200,91L200,93L195,93L195,95L191,95L191,96L190,96L190,97L188,97L188,98L185,98L185,99L183,99L183,100L180,100L180,101L179,101L179,102L177,102L176,104L173,104L173,105L171,105L170,107L167,107L167,109L165,109L164,111L160,112L160,114L157,114L157,116L155,116L153,119L151,119L150,121L148,121L148,123L146,123L146,125L144,125L144,126L141,128L141,130L139,130L139,132L137,133L137,135L135,135L135,137L132,139L132,142L130,142L130,144L128,144L128,147L127,147L127,149L125,150L125,153L123,153L123,163L105,163L105,155L87,155L87,154L85,153L85,150L84,150L83,144L82,144L82,142L81,142L81,139L80,139L80,138L79,138L79,136L77,135L77,133L76,133L76,131L74,130L74,128L72,128L72,126L68,125L67,123L65,123L65,121L62,121L62,120L61,120L61,119L59,119L59,118L56,118L56,117L54,117L54,116L49,116L49,115L47,115L47,114L39,114L39,113L37,112L37,51L38,51L39,49L47,49L47,48L49,48L49,47L54,47L54,46L56,46L56,45L61,44L62,42L65,42L65,40L67,40L68,38L72,37L72,35L74,35L74,33L76,32L77,28L78,28L78,27L79,27L79,25L81,24L81,21L82,21L82,19L83,19L83,16L84,16L85,10L86,10L87,8L105,8L105,1ZM99,25L97,26L97,30L95,30L95,33L93,33L93,35L92,35L92,37L91,37L91,40L93,41L93,43L94,43L94,44L95,44L95,45L96,45L96,46L100,49L100,51L102,51L102,53L104,53L104,54L105,54L105,56L107,56L107,58L109,58L109,59L110,59L113,63L115,63L116,65L118,65L120,68L122,68L123,70L125,70L125,72L129,73L130,75L132,75L132,76L133,76L133,77L135,77L136,79L139,79L139,81L141,81L141,82L139,82L139,84L136,84L135,86L133,86L133,87L132,87L132,88L130,88L129,90L125,91L125,93L123,93L122,95L120,95L118,98L116,98L115,100L113,100L113,101L112,101L109,105L107,105L107,107L105,107L105,109L104,109L104,110L102,110L102,112L100,112L100,114L99,114L99,115L98,115L98,116L97,116L97,117L93,120L93,122L91,123L91,126L92,126L93,130L95,130L95,133L97,133L97,137L98,137L98,138L109,138L109,137L111,137L111,135L112,135L112,134L113,134L113,133L114,133L114,132L115,132L115,131L119,128L119,126L121,126L121,124L123,124L123,122L124,122L124,121L126,121L126,119L128,119L128,117L132,116L132,114L133,114L135,111L137,111L137,109L139,109L140,107L142,107L142,105L144,105L146,102L149,102L149,100L151,100L152,98L154,98L154,97L155,97L155,96L157,96L158,94L162,93L162,91L165,91L166,89L170,88L171,86L174,86L174,84L177,84L177,82L179,82L179,81L177,81L177,79L174,79L174,77L171,77L170,75L166,74L165,72L162,72L162,70L158,69L157,67L155,67L154,65L152,65L151,63L149,63L149,61L146,61L144,58L142,58L142,56L140,56L139,54L137,54L137,52L135,52L135,51L132,49L132,47L128,46L128,44L126,44L126,42L124,42L124,41L123,41L123,39L121,39L121,37L119,37L119,35L118,35L118,34L117,34L117,33L116,33L116,32L115,32L115,31L111,28L111,26L109,26L109,25L100,25ZM77,51L76,51L76,53L72,54L71,56L68,56L68,58L65,58L65,59L68,61L68,63L70,63L70,65L72,65L72,67L74,67L74,69L77,71L77,73L79,73L79,75L81,75L81,77L83,77L83,79L84,79L84,81L85,81L85,82L84,82L84,84L83,84L83,86L81,86L81,88L79,88L79,90L77,90L77,92L74,94L74,96L72,96L72,98L70,98L70,100L68,100L68,102L65,104L65,105L68,105L68,107L71,107L72,109L76,110L76,112L81,111L81,109L83,109L83,107L86,105L86,103L88,103L88,102L89,102L89,101L90,101L90,100L91,100L91,99L92,99L95,95L97,95L97,93L99,93L100,91L102,91L102,89L104,89L104,88L106,88L107,86L109,86L109,84L112,84L113,82L116,82L116,81L113,81L112,79L109,79L109,77L107,77L106,75L102,74L102,72L100,72L99,70L97,70L97,68L95,68L95,67L94,67L94,66L93,66L93,65L92,65L92,64L91,64L88,60L86,60L86,58L83,56L83,54L81,54L81,52L79,52L79,51L78,51ZM27,65L28,65L28,98L27,98L27,97L25,97L25,96L23,96L23,95L21,95L21,94L19,94L17,91L14,91L14,90L12,90L11,88L8,88L8,87L7,87L7,86L5,86L4,84L1,84L1,83L0,83L0,80L1,80L1,79L4,79L4,78L5,78L5,77L7,77L8,75L11,75L12,73L17,72L19,69L21,69L21,68L23,68L23,67L27,66ZM52,67L51,67L51,96L53,96L53,94L56,92L56,90L58,90L58,88L60,88L60,86L61,86L61,84L62,84L62,82L63,82L63,81L62,81L62,79L61,79L61,77L60,77L60,75L58,75L58,73L56,73L56,71L53,69L53,67Z'/%3E%3C/svg%3E");
  --f-cap-r:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 231 163'%3E%3Cg transform='translate(231,0) scale(-1,1)'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M105,0L123,0L123,10L125,10L125,13L127,14L128,19L130,19L130,21L132,21L132,24L135,26L135,28L137,28L137,30L139,31L139,33L141,33L141,35L142,35L144,38L146,38L146,40L148,40L148,42L150,42L150,43L151,43L151,44L153,44L155,47L157,47L157,49L160,49L160,51L162,51L162,52L164,52L165,54L167,54L167,56L170,56L171,58L176,59L176,60L177,60L177,61L179,61L180,63L183,63L183,64L185,64L185,65L188,65L188,66L190,66L191,68L195,68L195,70L200,70L200,72L208,73L209,75L214,75L215,77L220,77L220,79L225,79L225,80L227,80L227,81L228,81L228,82L227,82L227,83L225,83L225,84L220,84L220,86L215,86L214,88L209,88L208,90L200,91L200,93L195,93L195,95L191,95L191,96L190,96L190,97L188,97L188,98L185,98L185,99L183,99L183,100L180,100L180,101L179,101L179,102L177,102L176,104L173,104L173,105L171,105L170,107L167,107L167,109L165,109L164,111L160,112L160,114L157,114L157,116L155,116L153,119L151,119L150,121L148,121L148,123L146,123L146,125L144,125L144,126L141,128L141,130L139,130L139,132L137,133L137,135L135,135L135,137L132,139L132,142L130,142L130,144L128,144L128,147L127,147L127,149L125,150L125,153L123,153L123,163L105,163L105,155L87,155L87,154L85,153L85,150L84,150L83,144L82,144L82,142L81,142L81,139L80,139L80,138L79,138L79,136L77,135L77,133L76,133L76,131L74,130L74,128L72,128L72,126L68,125L67,123L65,123L65,121L62,121L62,120L61,120L61,119L59,119L59,118L56,118L56,117L54,117L54,116L49,116L49,115L47,115L47,114L39,114L39,113L37,112L37,51L38,51L39,49L47,49L47,48L49,48L49,47L54,47L54,46L56,46L56,45L61,44L62,42L65,42L65,40L67,40L68,38L72,37L72,35L74,35L74,33L76,32L77,28L78,28L78,27L79,27L79,25L81,24L81,21L82,21L82,19L83,19L83,16L84,16L85,10L86,10L87,8L105,8L105,1ZM99,25L97,26L97,30L95,30L95,33L93,33L93,35L92,35L92,37L91,37L91,40L93,41L93,43L94,43L94,44L95,44L95,45L96,45L96,46L100,49L100,51L102,51L102,53L104,53L104,54L105,54L105,56L107,56L107,58L109,58L109,59L110,59L113,63L115,63L116,65L118,65L120,68L122,68L123,70L125,70L125,72L129,73L130,75L132,75L132,76L133,76L133,77L135,77L136,79L139,79L139,81L141,81L141,82L139,82L139,84L136,84L135,86L133,86L133,87L132,87L132,88L130,88L129,90L125,91L125,93L123,93L122,95L120,95L118,98L116,98L115,100L113,100L113,101L112,101L109,105L107,105L107,107L105,107L105,109L104,109L104,110L102,110L102,112L100,112L100,114L99,114L99,115L98,115L98,116L97,116L97,117L93,120L93,122L91,123L91,126L92,126L93,130L95,130L95,133L97,133L97,137L98,137L98,138L109,138L109,137L111,137L111,135L112,135L112,134L113,134L113,133L114,133L114,132L115,132L115,131L119,128L119,126L121,126L121,124L123,124L123,122L124,122L124,121L126,121L126,119L128,119L128,117L132,116L132,114L133,114L135,111L137,111L137,109L139,109L140,107L142,107L142,105L144,105L146,102L149,102L149,100L151,100L152,98L154,98L154,97L155,97L155,96L157,96L158,94L162,93L162,91L165,91L166,89L170,88L171,86L174,86L174,84L177,84L177,82L179,82L179,81L177,81L177,79L174,79L174,77L171,77L170,75L166,74L165,72L162,72L162,70L158,69L157,67L155,67L154,65L152,65L151,63L149,63L149,61L146,61L144,58L142,58L142,56L140,56L139,54L137,54L137,52L135,52L135,51L132,49L132,47L128,46L128,44L126,44L126,42L124,42L124,41L123,41L123,39L121,39L121,37L119,37L119,35L118,35L118,34L117,34L117,33L116,33L116,32L115,32L115,31L111,28L111,26L109,26L109,25L100,25ZM77,51L76,51L76,53L72,54L71,56L68,56L68,58L65,58L65,59L68,61L68,63L70,63L70,65L72,65L72,67L74,67L74,69L77,71L77,73L79,73L79,75L81,75L81,77L83,77L83,79L84,79L84,81L85,81L85,82L84,82L84,84L83,84L83,86L81,86L81,88L79,88L79,90L77,90L77,92L74,94L74,96L72,96L72,98L70,98L70,100L68,100L68,102L65,104L65,105L68,105L68,107L71,107L72,109L76,110L76,112L81,111L81,109L83,109L83,107L86,105L86,103L88,103L88,102L89,102L89,101L90,101L90,100L91,100L91,99L92,99L95,95L97,95L97,93L99,93L100,91L102,91L102,89L104,89L104,88L106,88L107,86L109,86L109,84L112,84L113,82L116,82L116,81L113,81L112,79L109,79L109,77L107,77L106,75L102,74L102,72L100,72L99,70L97,70L97,68L95,68L95,67L94,67L94,66L93,66L93,65L92,65L92,64L91,64L88,60L86,60L86,58L83,56L83,54L81,54L81,52L79,52L79,51L78,51ZM27,65L28,65L28,98L27,98L27,97L25,97L25,96L23,96L23,95L21,95L21,94L19,94L17,91L14,91L14,90L12,90L11,88L8,88L8,87L7,87L7,86L5,86L4,84L1,84L1,83L0,83L0,80L1,80L1,79L4,79L4,78L5,78L5,77L7,77L8,75L11,75L12,73L17,72L19,69L21,69L21,68L23,68L23,67L27,66ZM52,67L51,67L51,96L53,96L53,94L56,92L56,90L58,90L58,88L60,88L60,86L61,86L61,84L62,84L62,82L63,82L63,81L62,81L62,79L61,79L61,77L60,77L60,75L58,75L58,73L56,73L56,71L53,69L53,67Z'/%3E%3C/g%3E%3C/svg%3E");
  --f-arc-t:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 224 58'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M111,0L113,0L113,1L114,1L114,4L116,4L116,6L117,6L120,10L122,10L123,12L125,12L125,14L127,15L127,17L129,17L131,20L133,20L134,22L136,22L136,23L138,23L138,24L140,24L140,25L142,25L142,26L145,26L145,27L148,27L148,28L151,28L151,29L166,29L166,28L169,28L169,27L178,26L178,25L181,25L181,24L183,24L183,23L185,23L185,22L191,21L192,23L194,23L195,25L197,25L198,27L200,27L201,29L203,29L203,31L205,31L207,34L211,35L212,37L214,37L214,38L216,38L216,39L224,39L224,57L216,57L216,56L214,56L214,55L210,55L209,53L207,53L207,52L204,52L204,51L200,50L200,49L199,49L199,48L197,48L195,45L193,45L193,44L192,44L192,42L190,42L189,40L188,40L188,41L185,41L185,42L183,42L183,43L181,43L181,44L170,45L170,46L167,46L167,47L153,47L153,46L150,46L150,45L143,45L143,44L141,44L141,43L137,43L137,42L135,42L135,41L133,41L133,40L129,39L128,37L126,37L125,35L123,35L122,33L120,33L119,31L117,31L117,30L116,30L116,28L114,28L114,26L110,26L110,28L108,28L108,30L107,30L107,31L105,31L104,33L102,33L101,35L99,35L99,36L98,36L98,37L96,37L95,39L93,39L93,40L91,40L91,41L89,41L89,42L87,42L87,43L83,43L83,44L81,44L81,45L74,45L74,46L71,46L71,47L57,47L57,46L54,46L54,45L43,44L43,43L41,43L41,42L39,42L39,41L36,41L36,40L35,40L35,41L34,41L34,42L32,42L32,44L31,44L31,45L29,45L27,48L25,48L24,50L22,50L22,51L20,51L20,52L15,53L14,55L10,55L10,56L8,56L8,57L0,57L0,39L8,39L8,38L12,37L13,35L17,34L19,31L21,31L21,29L23,29L24,27L26,27L27,25L29,25L29,24L30,24L30,23L32,23L33,21L39,22L39,23L41,23L41,24L43,24L43,25L46,25L46,26L55,27L55,28L58,28L58,29L73,29L73,28L76,28L76,27L82,26L82,25L84,25L84,24L86,24L86,23L90,22L91,20L93,20L95,17L97,17L97,15L99,14L99,12L101,12L102,10L104,10L104,9L108,6L108,4L110,4L110,1L111,1ZM110,35L114,35L114,37L117,37L117,39L119,39L119,41L122,41L122,42L124,42L124,44L127,44L127,45L128,45L126,48L122,49L121,51L119,51L118,53L116,53L113,57L111,57L108,53L106,53L105,51L103,51L102,49L100,49L100,48L98,48L98,47L96,46L97,44L100,44L100,42L105,41L105,39L107,39L107,37L110,37L110,36Z'/%3E%3C/svg%3E");
  --f-arc-b:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 224 58'%3E%3Cg transform='translate(0,58) scale(1,-1)'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M111,0L113,0L113,1L114,1L114,4L116,4L116,6L117,6L120,10L122,10L123,12L125,12L125,14L127,15L127,17L129,17L131,20L133,20L134,22L136,22L136,23L138,23L138,24L140,24L140,25L142,25L142,26L145,26L145,27L148,27L148,28L151,28L151,29L166,29L166,28L169,28L169,27L178,26L178,25L181,25L181,24L183,24L183,23L185,23L185,22L191,21L192,23L194,23L195,25L197,25L198,27L200,27L201,29L203,29L203,31L205,31L207,34L211,35L212,37L214,37L214,38L216,38L216,39L224,39L224,57L216,57L216,56L214,56L214,55L210,55L209,53L207,53L207,52L204,52L204,51L200,50L200,49L199,49L199,48L197,48L195,45L193,45L193,44L192,44L192,42L190,42L189,40L188,40L188,41L185,41L185,42L183,42L183,43L181,43L181,44L170,45L170,46L167,46L167,47L153,47L153,46L150,46L150,45L143,45L143,44L141,44L141,43L137,43L137,42L135,42L135,41L133,41L133,40L129,39L128,37L126,37L125,35L123,35L122,33L120,33L119,31L117,31L117,30L116,30L116,28L114,28L114,26L110,26L110,28L108,28L108,30L107,30L107,31L105,31L104,33L102,33L101,35L99,35L99,36L98,36L98,37L96,37L95,39L93,39L93,40L91,40L91,41L89,41L89,42L87,42L87,43L83,43L83,44L81,44L81,45L74,45L74,46L71,46L71,47L57,47L57,46L54,46L54,45L43,44L43,43L41,43L41,42L39,42L39,41L36,41L36,40L35,40L35,41L34,41L34,42L32,42L32,44L31,44L31,45L29,45L27,48L25,48L24,50L22,50L22,51L20,51L20,52L15,53L14,55L10,55L10,56L8,56L8,57L0,57L0,39L8,39L8,38L12,37L13,35L17,34L19,31L21,31L21,29L23,29L24,27L26,27L27,25L29,25L29,24L30,24L30,23L32,23L33,21L39,22L39,23L41,23L41,24L43,24L43,25L46,25L46,26L55,27L55,28L58,28L58,29L73,29L73,28L76,28L76,27L82,26L82,25L84,25L84,24L86,24L86,23L90,22L91,20L93,20L95,17L97,17L97,15L99,14L99,12L101,12L102,10L104,10L104,9L108,6L108,4L110,4L110,1L111,1ZM110,35L114,35L114,37L117,37L117,39L119,39L119,41L122,41L122,42L124,42L124,44L127,44L127,45L128,45L126,48L122,49L121,51L119,51L118,53L116,53L113,57L111,57L108,53L106,53L105,51L103,51L102,49L100,49L100,48L98,48L98,47L96,46L97,44L100,44L100,42L105,41L105,39L107,39L107,37L110,37L110,36Z'/%3E%3C/g%3E%3C/svg%3E");
  --i-cap-l:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 118 163'%3E%3Cpath fill='%23fff' fill-rule='evenodd' stroke='%23fff' stroke-width='9' stroke-linejoin='round' d='M0,0L118,0L118,163L0,163L0,153L2,153L2,150L4,149L5,144L7,144L7,142L9,142L9,139L12,137L12,135L14,135L14,133L16,132L16,130L18,130L18,128L19,128L21,125L23,125L23,123L25,123L25,121L27,121L27,120L28,120L28,119L30,119L32,116L34,116L34,114L37,114L37,112L39,112L39,111L41,111L42,109L44,109L44,107L47,107L48,105L53,104L53,103L54,103L54,102L56,102L57,100L60,100L60,99L62,99L62,98L65,98L65,97L67,97L68,95L72,95L72,93L77,93L77,91L85,90L86,88L91,88L92,86L97,86L97,84L102,84L102,83L104,83L104,82L105,82L105,81L104,81L104,80L102,80L102,79L97,79L97,77L92,77L91,75L86,75L85,73L77,72L77,70L72,70L72,68L68,68L68,67L67,67L67,66L65,66L65,65L62,65L62,64L60,64L60,63L57,63L57,62L56,62L56,61L54,61L53,59L50,59L50,58L48,58L47,56L44,56L44,54L42,54L41,52L37,51L37,49L34,49L34,47L32,47L30,44L28,44L27,42L25,42L25,40L23,40L23,38L21,38L21,37L18,35L18,33L16,33L16,31L14,30L14,28L12,28L12,26L9,24L9,21L7,21L7,19L5,19L5,16L4,16L4,14L2,13L2,10L0,10L0,1Z'/%3E%3C/svg%3E");
  --i-cap-r:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 118 163'%3E%3Cg transform='translate(118,0) scale(-1,1)'%3E%3Cpath fill='%23fff' fill-rule='evenodd' stroke='%23fff' stroke-width='9' stroke-linejoin='round' d='M0,0L118,0L118,163L0,163L0,153L2,153L2,150L4,149L5,144L7,144L7,142L9,142L9,139L12,137L12,135L14,135L14,133L16,132L16,130L18,130L18,128L19,128L21,125L23,125L23,123L25,123L25,121L27,121L27,120L28,120L28,119L30,119L32,116L34,116L34,114L37,114L37,112L39,112L39,111L41,111L42,109L44,109L44,107L47,107L48,105L53,104L53,103L54,103L54,102L56,102L57,100L60,100L60,99L62,99L62,98L65,98L65,97L67,97L68,95L72,95L72,93L77,93L77,91L85,90L86,88L91,88L92,86L97,86L97,84L102,84L102,83L104,83L104,82L105,82L105,81L104,81L104,80L102,80L102,79L97,79L97,77L92,77L91,75L86,75L85,73L77,72L77,70L72,70L72,68L68,68L68,67L67,67L67,66L65,66L65,65L62,65L62,64L60,64L60,63L57,63L57,62L56,62L56,61L54,61L53,59L50,59L50,58L48,58L47,56L44,56L44,54L42,54L41,52L37,51L37,49L34,49L34,47L32,47L30,44L28,44L27,42L25,42L25,40L23,40L23,38L21,38L21,37L18,35L18,33L16,33L16,31L14,30L14,28L12,28L12,26L9,24L9,21L7,21L7,19L5,19L5,16L4,16L4,14L2,13L2,10L0,10L0,1Z'/%3E%3C/g%3E%3C/svg%3E");
  --i-arc-t:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 224 58'%3E%3Cpath fill='%23fff' fill-rule='evenodd' stroke='%23fff' stroke-width='9' stroke-linejoin='round' d='M110,26L114,26L114,28L116,28L116,30L117,30L117,31L119,31L120,33L122,33L123,35L125,35L125,36L126,36L126,37L128,37L129,39L131,39L131,40L133,40L133,41L135,41L135,42L137,42L137,43L141,43L141,44L143,44L143,45L150,45L150,46L153,46L153,47L167,47L167,46L170,46L170,45L181,44L181,43L183,43L183,42L185,42L185,41L188,41L188,40L189,40L189,41L190,41L190,42L192,42L192,44L193,44L193,45L195,45L197,48L199,48L200,50L202,50L202,51L204,51L204,52L209,53L210,55L214,55L214,56L216,56L216,57L224,57L224,58L0,58L0,57L8,57L8,56L10,56L10,55L14,55L15,53L17,53L17,52L20,52L20,51L24,50L24,49L25,49L25,48L27,48L29,45L31,45L31,44L32,44L32,42L34,42L35,40L36,40L36,41L39,41L39,42L41,42L41,43L43,43L43,44L54,45L54,46L57,46L57,47L71,47L71,46L74,46L74,45L81,45L81,44L83,44L83,43L87,43L87,42L89,42L89,41L91,41L91,40L95,39L96,37L98,37L99,35L101,35L102,33L104,33L105,31L107,31L107,30L108,30L108,28L110,28L110,27ZM111,35L110,35L110,37L107,37L107,39L105,39L105,41L102,41L102,42L100,42L100,44L97,44L97,45L96,45L98,48L100,48L100,49L102,49L103,51L105,51L106,53L108,53L111,57L113,57L116,53L118,53L119,51L121,51L122,49L124,49L124,48L126,48L126,47L128,46L127,44L124,44L124,42L119,41L119,39L117,39L117,37L114,37L114,35L112,35Z'/%3E%3C/svg%3E");
  --i-arc-b:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 224 58'%3E%3Cg transform='translate(0,58) scale(1,-1)'%3E%3Cpath fill='%23fff' fill-rule='evenodd' stroke='%23fff' stroke-width='9' stroke-linejoin='round' d='M110,26L114,26L114,28L116,28L116,30L117,30L117,31L119,31L120,33L122,33L123,35L125,35L125,36L126,36L126,37L128,37L129,39L131,39L131,40L133,40L133,41L135,41L135,42L137,42L137,43L141,43L141,44L143,44L143,45L150,45L150,46L153,46L153,47L167,47L167,46L170,46L170,45L181,44L181,43L183,43L183,42L185,42L185,41L188,41L188,40L189,40L189,41L190,41L190,42L192,42L192,44L193,44L193,45L195,45L197,48L199,48L200,50L202,50L202,51L204,51L204,52L209,53L210,55L214,55L214,56L216,56L216,57L224,57L224,58L0,58L0,57L8,57L8,56L10,56L10,55L14,55L15,53L17,53L17,52L20,52L20,51L24,50L24,49L25,49L25,48L27,48L29,45L31,45L31,44L32,44L32,42L34,42L35,40L36,40L36,41L39,41L39,42L41,42L41,43L43,43L43,44L54,45L54,46L57,46L57,47L71,47L71,46L74,46L74,45L81,45L81,44L83,44L83,43L87,43L87,42L89,42L89,41L91,41L91,40L95,39L96,37L98,37L99,35L101,35L102,33L104,33L105,31L107,31L107,30L108,30L108,28L110,28L110,27ZM111,35L110,35L110,37L107,37L107,39L105,39L105,41L102,41L102,42L100,42L100,44L97,44L97,45L96,45L98,48L100,48L100,49L102,49L103,51L105,51L106,53L108,53L111,57L113,57L116,53L118,53L119,51L121,51L122,49L124,49L124,48L126,48L126,47L128,46L127,44L124,44L124,42L119,41L119,39L117,39L117,37L114,37L114,35L112,35Z'/%3E%3C/g%3E%3C/svg%3E");
}

.mode,
.btn,
.lib-top,
.plate {
  position: relative;
  margin-left: 18px;
  margin-right: 18px;
  border: 0;
  border-radius: 0;
  outline: 0;
  background: transparent;
  box-shadow: none;
  /* Торцевой орнамент уходит внутрь на 20.5 px — текст не должен
     на него наезжать. */
  padding-left: 26.5px;
  padding-right: 26.5px;
}

/* Подложка. Рисуется раньше рамки, поэтому рамка ложится поверх. */
.mode::before,
.btn::before,
.lib-top::before,
.plate::before {
  content: "";
  position: absolute;
  inset: -7px -18px;
  pointer-events: none;
  z-index: -1;

  -webkit-mask-image:
    var(--i-cap-l),
    var(--i-cap-r),
    var(--i-arc-t),
    var(--i-arc-b),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  -webkit-mask-position:
    left 21px center, right 21px center, center top 0.5px, center bottom 0.5px, left 21px top 10px, left 21px bottom 10px, center center;
  -webkit-mask-size:
    19.67px 27.17px, 19.67px 27.17px, 37.33px 9.67px, 37.33px 9.67px, calc(100% - 42px) calc(50% - 22.38px), calc(100% - 42px) calc(50% - 22.38px), calc(100% - 78.93px) 27.17px;
  -webkit-mask-repeat: no-repeat;

  mask-image:
    var(--i-cap-l),
    var(--i-cap-r),
    var(--i-arc-t),
    var(--i-arc-b),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  mask-position:
    left 21px center, right 21px center, center top 0.5px, center bottom 0.5px, left 21px top 10px, left 21px bottom 10px, center center;
  mask-size:
    19.67px 27.17px, 19.67px 27.17px, 37.33px 9.67px, 37.33px 9.67px, calc(100% - 42px) calc(50% - 22.38px), calc(100% - 42px) calc(50% - 22.38px), calc(100% - 78.93px) 27.17px;
  mask-repeat: no-repeat;
}

.mode::before,
.plate::before { background: linear-gradient(180deg, var(--c-surface), var(--c-surface-deep)); }
.btn::before  { background: rgba(var(--c-accent-rgb), var(--a-accent-fill)); }
.btn.ghost::before,
.lib-top::before { background: transparent; }

/* Сама рамка. */
.mode::after,
.btn::after,
.lib-top::after,
.plate::after {
  content: "";
  position: absolute;
  inset: -7px -18px;
  pointer-events: none;
  background: rgba(var(--c-accent-rgb), .82);
  transition: background 220ms var(--ease-out);

  -webkit-mask-image:
    var(--f-cap-l),
    var(--f-cap-r),
    var(--f-arc-t),
    var(--f-arc-b),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  -webkit-mask-position:
    left 0.5px center, right 0.5px center, center top 0.5px, center bottom 0.5px, left 18px top 7px, right 18px top 7px, left 18px bottom 7px, right 18px bottom 7px, left 18px top 7px, left 18px bottom 7px, right 18px top 7px, right 18px bottom 7px;
  -webkit-mask-size:
    38.5px 27.17px, 38.5px 27.17px, 37.33px 9.67px, 37.33px 9.67px, calc(50% - 35.47px) 3px, calc(50% - 35.47px) 3px, calc(50% - 35.47px) 3px, calc(50% - 35.47px) 3px, 3px calc(50% - 19.38px), 3px calc(50% - 19.38px), 3px calc(50% - 19.38px), 3px calc(50% - 19.38px);
  -webkit-mask-repeat: no-repeat;

  mask-image:
    var(--f-cap-l),
    var(--f-cap-r),
    var(--f-arc-t),
    var(--f-arc-b),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  mask-position:
    left 0.5px center, right 0.5px center, center top 0.5px, center bottom 0.5px, left 18px top 7px, right 18px top 7px, left 18px bottom 7px, right 18px bottom 7px, left 18px top 7px, left 18px bottom 7px, right 18px top 7px, right 18px bottom 7px;
  mask-size:
    38.5px 27.17px, 38.5px 27.17px, 37.33px 9.67px, 37.33px 9.67px, calc(50% - 35.47px) 3px, calc(50% - 35.47px) 3px, calc(50% - 35.47px) 3px, calc(50% - 35.47px) 3px, 3px calc(50% - 19.38px), 3px calc(50% - 19.38px), 3px calc(50% - 19.38px), 3px calc(50% - 19.38px);
  mask-repeat: no-repeat;
}

.mode:hover::after,
.btn:hover:not(:disabled)::after,
.lib-top:hover::after { background: var(--c-accent); }

.btn:disabled::after { background: rgba(var(--c-accent-rgb), .2); }

/* Подъём съедает 37.33 px посередине грани: у слишком узкой кнопки прямые
   участки схлопнутся в ноль и останутся одни торцы. */
.btn, .lib-top { min-width: 150px; }

/* Псевдоэлементы вынесены на 18 px по бокам и 7 px сверху и снизу —
   под торцы и подъёмы. Отсюда поля кнопок и расширенные зазоры. */
.menu { gap: 22px; }
.controls { gap: 40px; }
.board__menu { display: block; width: calc(100% - 36px); margin: 30px 18px 10px; }
.lib-top { margin: 30px 18px 10px; }

/* ------------------------------------------- полоска профиля в шапке

   Та же рамка в масштабе 1/8: линия 2.57 px вместо 3 — мельче кнопки,
   как и положено значку в шапке. Правый торец срезан: полоска
   упирается в круг с аватаром, поэтому полосы доходят до правого края
   псевдоэлемента и уходят под круг. */

.prof__strip::before,
.prof__strip::after {
  content: "";
  position: absolute;
  inset: 0 0 0 -15px;
  pointer-events: none;
}

.prof__strip::before {
  z-index: -1;
  background: linear-gradient(180deg, var(--c-surface), var(--c-surface-deep));

  -webkit-mask-image:
    var(--i-cap-l),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  -webkit-mask-position:
    left 17.57px center, left 17.57px top 2.57px, left 17.57px bottom 2.57px, right 0 center;
  -webkit-mask-size:
    16.86px 23.29px, calc(100% - 17.57px) calc(50% - 13.01px), calc(100% - 17.57px) calc(50% - 13.01px), calc(100% - 33.23px) 23.29px;
  -webkit-mask-repeat: no-repeat;

  mask-image:
    var(--i-cap-l),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  mask-position:
    left 17.57px center, left 17.57px top 2.57px, left 17.57px bottom 2.57px, right 0 center;
  mask-size:
    16.86px 23.29px, calc(100% - 17.57px) calc(50% - 13.01px), calc(100% - 17.57px) calc(50% - 13.01px), calc(100% - 33.23px) 23.29px;
  mask-repeat: no-repeat;
}

.prof__strip::after {
  background: rgba(var(--c-accent-rgb), .82);
  transition: background 220ms var(--ease-out);

  -webkit-mask-image:
    var(--f-cap-l),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  -webkit-mask-position:
    left 0px center, left 15px top 0, left 15px bottom 0, left 15px top 0, left 15px bottom 0;
  -webkit-mask-size:
    33px 23.29px, calc(100% - 15px) 2.57px, calc(100% - 15px) 2.57px, 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px);
  -webkit-mask-repeat: no-repeat;

  mask-image:
    var(--f-cap-l),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  mask-position:
    left 0px center, left 15px top 0, left 15px bottom 0, left 15px top 0, left 15px bottom 0;
  mask-size:
    33px 23.29px, calc(100% - 15px) 2.57px, calc(100% - 15px) 2.57px, 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px);
  mask-repeat: no-repeat;
}

.prof:hover .prof__strip::after { background: var(--c-accent); }

/* Полоска сфер: та же рамка, что у профиля, отражённая. Срезан левый
   торец — там сфера, и линии уходят под неё; орнамент правый. */

.coin__strip::before,
.coin__strip::after {
  content: "";
  position: absolute;
  inset: 0 -15px 0 0;
  pointer-events: none;
}

.coin__strip::before {
  z-index: -1;
  background: linear-gradient(180deg, var(--c-surface), var(--c-surface-deep));

  -webkit-mask-image:
    var(--i-cap-r),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  -webkit-mask-position:
    right 17.57px center, right 17.57px top 2.57px, right 17.57px bottom 2.57px, left 0 center;
  -webkit-mask-size:
    16.86px 23.29px, calc(100% - 17.57px) calc(50% - 13.01px), calc(100% - 17.57px) calc(50% - 13.01px), calc(100% - 33.23px) 23.29px;
  -webkit-mask-repeat: no-repeat;

  mask-image:
    var(--i-cap-r),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  mask-position:
    right 17.57px center, right 17.57px top 2.57px, right 17.57px bottom 2.57px, left 0 center;
  mask-size:
    16.86px 23.29px, calc(100% - 17.57px) calc(50% - 13.01px), calc(100% - 17.57px) calc(50% - 13.01px), calc(100% - 33.23px) 23.29px;
  mask-repeat: no-repeat;
}

.coin__strip::after {
  background: rgba(var(--c-accent-rgb), .82);
  transition: background 220ms var(--ease-out);

  -webkit-mask-image:
    var(--f-cap-r),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  -webkit-mask-position:
    right 0px center, right 15px top 0, right 15px bottom 0, right 15px top 0, right 15px bottom 0;
  -webkit-mask-size:
    33px 23.29px, calc(100% - 15px) 2.57px, calc(100% - 15px) 2.57px, 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px);
  -webkit-mask-repeat: no-repeat;

  mask-image:
    var(--f-cap-r),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  mask-position:
    right 0px center, right 15px top 0, right 15px bottom 0, right 15px top 0, right 15px bottom 0;
  mask-size:
    33px 23.29px, calc(100% - 15px) 2.57px, calc(100% - 15px) 2.57px, 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px);
  mask-repeat: no-repeat;
}

.coin:hover .coin__strip::after { background: var(--c-accent); }

/* --------------------------------------------- «Меню» и «Назад»

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

   Высота задана жёстко (29.86 px — высота рамки в этом масштабе),
   поля по бокам 15 px под наконечники, внутренние отступы — чтобы
   надпись не наезжала на орнамент. */

.back {
  position: relative;
  height: 29.86px;
  margin-left: 15px;
  margin-right: 15px;
  /* Выключка по строчным: нижний отступ вдвое больше сдвига,
     тогда при align-items: center строка поднимается ровно на него. */
  padding: 0 22px calc(2 * var(--lc-body)) 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.back::before,
.back::after {
  content: "";
  position: absolute;
  inset: 0 -15px;
  pointer-events: none;
}

.back::before {
  z-index: -1;
  background: linear-gradient(180deg, var(--c-surface), var(--c-surface-deep));

  -webkit-mask-image:
    var(--i-cap-l),
    var(--i-cap-r),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  -webkit-mask-position:
    left 17.57px center, right 17.57px center, left 17.57px top 2.57px, left 17.57px bottom 2.57px, center center;
  -webkit-mask-size:
    16.86px 23.29px, 16.86px 23.29px, calc(100% - 35.14px) calc(50% - 13.01px), calc(100% - 35.14px) calc(50% - 13.01px), calc(100% - 66.46px) 23.29px;
  -webkit-mask-repeat: no-repeat;

  mask-image:
    var(--i-cap-l),
    var(--i-cap-r),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  mask-position:
    left 17.57px center, right 17.57px center, left 17.57px top 2.57px, left 17.57px bottom 2.57px, center center;
  mask-size:
    16.86px 23.29px, 16.86px 23.29px, calc(100% - 35.14px) calc(50% - 13.01px), calc(100% - 35.14px) calc(50% - 13.01px), calc(100% - 66.46px) 23.29px;
  mask-repeat: no-repeat;
}

.back::after {
  background: rgba(var(--c-accent-rgb), .82);
  transition: background 220ms var(--ease-out);

  -webkit-mask-image:
    var(--f-cap-l),
    var(--f-cap-r),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  -webkit-mask-position:
    left 0px center, right 0px center, left 15px top 0, left 15px bottom 0, left 15px top 0, left 15px bottom 0, right 15px top 0, right 15px bottom 0;
  -webkit-mask-size:
    33px 23.29px, 33px 23.29px, calc(100% - 30px) 2.57px, calc(100% - 30px) 2.57px, 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px);
  -webkit-mask-repeat: no-repeat;

  mask-image:
    var(--f-cap-l),
    var(--f-cap-r),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0),
    linear-gradient(#000 0 0);
  mask-position:
    left 0px center, right 0px center, left 15px top 0, left 15px bottom 0, left 15px top 0, left 15px bottom 0, right 15px top 0, right 15px bottom 0;
  mask-size:
    33px 23.29px, 33px 23.29px, calc(100% - 30px) 2.57px, calc(100% - 30px) 2.57px, 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px), 2.57px calc(50% - 10.44px);
  mask-repeat: no-repeat;
}

.back:hover::after { background: var(--c-accent); }

/* ---------------------------------------------------- перемешивание

   Карты лежат на столе рубашкой вверх. Их мешают две руки: каждая
   тянет карту тем сильнее, чем та ближе к её середине, — вес падает
   по гауссиане. Положение каждой карты ставит код (transform), здесь
   только вид стола и надпись. */
/* Пока карты на столе, ожидающая рубашка не висит на фоне: колода
   сейчас в руках, а не под рукой. */
[data-shuffle="on"] .stage { opacity: 0; pointer-events: none; }

.shuffle {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--topbar-h);
  bottom: 0;
  z-index: 25;
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  animation: askIn 420ms var(--ease-out) both;
}
/* Надпись стоит над столом и в своей полосе, а не посреди карт:
   карты кладутся в остаток высоты и на неё не заезжают. */
.shuffle__hint {
  flex: none;
  margin: 0;
  padding: 18px 22px 14px;
  font-family: var(--font-head);
  font-size: 24px;
  line-height: 1.22;
  text-align: center;
  text-wrap: balance;
  color: var(--c-accent-head);
  animation: hintPulse 2.8s var(--ease-soft) infinite;
}
@keyframes hintPulse { 0%, 100% { opacity: .72; } 50% { opacity: 1; } }
.shuffle__table {
  position: relative;
  flex: 1;
  width: 100%;
}
.shuffle__card {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--card-w) * .30);
  aspect-ratio: var(--card-ratio);
  margin-left: calc(var(--card-w) * -.15);
  margin-top: calc(var(--card-w) * -.15 * 1000 / 579);
  border-radius: 3px;
  background-size: cover;
  background-position: center;
  box-shadow: 0 2px 6px rgba(var(--c-shadow-rgb), .35);
  will-change: transform;
}
/* Колода подъезжает к читателю: собралась — и подана под руку. */
.card-wrap.is-dealt { animation: dealIn 460ms var(--ease-out) both; }
@keyframes dealIn {
  from { transform: scale(calc(var(--scale-closed) * .42)); opacity: .4; }
  to   { transform: scale(var(--scale-closed)); opacity: 1; }
}

/* Пока колоду не мешали, рубашка — это кнопка «перемешать», а не карта.
   Зовёт она свечением под собой: сама карта занята наклоном за
   указателем, и трогать её transform нельзя. */
[data-invite="on"] .card-wrap::before { animation: invitePulse 2.6s var(--ease-soft) infinite; }
@keyframes invitePulse {
  0%, 100% { opacity: .55; transform: translate(-50%, -50%) scale(.92); }
  50%      { opacity: 1;   transform: translate(-50%, -50%) scale(1.06); }
}

/* Пока колоду не мешали, поверх рубашки стоит настоящая кнопка — такая
   же, как в меню, с тем же торцевым орнаментом. Бьётся она сердцем:
   два коротких удара и пауза. Пульс задан свойством scale, а не
   transform: transform у кнопки занят подъёмом при наведении. */
.deck-call {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 5;
  margin: 0;
  translate: -50% -50%;
  font-size: 17px;
  padding-top: calc(14px - var(--lc-body));
  padding-bottom: calc(14px + var(--lc-body));
  animation: heartbeat 2s var(--ease-soft) infinite;
}
/* Кнопка лежит на карте, а её подложка в меню полупрозрачная — сквозь
   неё просвечивал рисунок. Здесь подложка сплошная. */
.deck-call::before { background: var(--c-surface); }
@keyframes heartbeat {
  0%   { scale: 1; }
  9%   { scale: 1.055; }
  18%  { scale: 1; }
  27%  { scale: 1.04; }
  38%  { scale: 1; }
  100% { scale: 1; }
}
