/* ============================================================================
   Раздел «Игры»: стол «21», лобби, результат - слой ЧИСТО визуальный.

   Язык - «Чеканка» (см. .claude/skills/miniapp-visual): графит с холодным
   уклоном, золото одним акцентом, свет сверху, у каждой поверхности
   внутренний верхний хайлайт. Переменные палитры и шкалы высот приходят из
   chekanka.css - здесь только классы игрового раздела (префикс g*).

   Правила слоя:
   - клиент рисует то, что решил сервер: тут нет ни одного правила игры;
   - анимации только transform/opacity (60fps, без перекладки макета);
   - все анимации разовые и с fill backwards: базовое состояние элемента -
     конечное, поэтому глобальный тормоз prefers-reduced-motion из
     chekanka.css (animation: none) оставляет экран целым и читаемым;
   - ассеты: table-felt.webp - цельная панель с запечённой золотой рамкой
     по краю, поэтому НЕ тайлится и не режется кадрированием (растяжка
     100% 100%, угловые виньетки терпят мягкую деформацию); card-back.webp -
     квадратный гильош во весь кадр, портретный крой делает background-size:
     cover на портретной фишке карты;
   - ЛИЦО КАРТЫ КАРТИНКОЙ НЕ РИСУЕТСЯ И РИСОВАТЬСЯ НЕ МОЖЕТ: ранг и масть
     называет сервер, они обязаны остаться текстом (его читает скринридер),
     а 52 растра означали бы 52 запроса и рассыпающуюся при добавлении
     колоды раскладку. Поле, рамка, индексы и знак масти - CSS; именной
     знак фигур - вектор (см. --je-mark ниже).
   ========================================================================= */

/* ------------------------------------------------ Именной знак ---------- */

/* Монограмма «JE» в чеканном кольце - ЕДИНСТВЕННЫЙ источник именного знака
   раздела: водяной знак сукна и медальон фигурных карт берут отсюда же.
   Вторая копия контура разошлась бы с первой при первой правке, поэтому
   встроенный вектор в этом файле ровно один - на это заведён сторож.

   ПОЧЕМУ ВЕКТОР, А НЕ КАРТИНКА. Знак рисуется в четырёх размерах сразу:
   28px на карте, 152px на сукне, и на экране их до двенадцати штук
   одновременно. Растр пришлось бы отдавать в трёх плотностях, он тянул бы
   свою загрузку (карта не имеет права ждать сети) и не красился бы темой.
   Здесь же это ~400 байт внутри CSS: ни одного лишнего запроса, чёткость
   на любом DPI.

   Геометрия геометрическая намеренно: прямые штрихи одной толщины и
   полукруглый хвост «J» - язык чеканки, а не каллиграфии. Буквы вписаны в
   радиус 22 из 29, то есть кольцо их не режет ни при каком сглаживании.
   Цвет золота запечён: сукно берёт знак фоном как есть, карта берёт его
   МАСКОЙ - там важен только альфа-канал, и заливка роли не играет. */
:root {
  --je-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23D8A24A' stroke-width='3.4'%3E%3Ccircle cx='32' cy='32' r='29' stroke-width='2.2'/%3E%3Cpath d='M27 20v16.4a5.6 5.6 0 0 1 -11.2 0'/%3E%3Cpath d='M38 20v24M38 21.7h12M38 32h10M38 42.3h12'/%3E%3C/svg%3E");
}

/* ------------------------------------------------ Входы в раздел -------- */

/* Кнопка в шапке главной: иконка раздела вместо эмодзи, подпись наследует
   цвет кнопки (currentColor). margin-left:auto остаётся в разметке. */
.brand__games { display: inline-flex; align-items: center; gap: 6px; }
.brand__games img {
  display: block;
  width: 18px; height: 18px;
  border-radius: 50%;
  box-shadow: var(--e1);
}

/* КАРТОЧКА ВХОДА ВО ВСЮ ШИРИНУ (просьба владельца 2026-08-31: «растяни
   карточку во всю ширину и добавь в неё плавающие значки игр»).
   Раньше вход был четвёртой плиткой в сетке из трёх колонок и падал один
   на вторую строку - сиротой рядом с пустотой.

   Язык тот же, что у стола: тёмное поле, золотая волосяная кромка,
   водяной знак монограммы. Карточка - настоящая кнопка со своим id и
   data-go: по id её прячет рубильник раздела (app.js: setGamesEntries), по
   data-go открывает общий обработчик главной.

   ВЫСОТА КАРТОЧКИ ПОСТОЯННА и не зависит от того, сколько игр включено:
   значки лежат абсолютом в своей полосе, подпись не переносится. */
.gent {
  position: relative;
  display: block;
  width: 100%;
  padding: 13px 14px 12px;
  border-radius: 14px;
  border: 1px solid rgba(216, 162, 74, .26);
  background:
    radial-gradient(130% 150% at 86% 18%, rgba(216, 162, 74, .10), transparent 58%),
    linear-gradient(180deg, var(--app-200), var(--app-100));
  box-shadow: var(--e2), inset 0 1px 0 rgba(255, 255, 255, .05);
  color: var(--app-ink);
  text-align: left;
  overflow: hidden;
}
/* Водяной знак дома - ТОТ ЖЕ контур, что на сукне и на фигурных картах
   (--je-mark, единственный источник знака в разделе). Это фон: 6%
   непрозрачности, видно боковым зрением и не спорит ни со словом, ни со
   значками. */
.gent::before {
  content: "";
  position: absolute;
  right: -16px; top: 50%;
  width: 92px; height: 92px;
  transform: translateY(-50%);
  opacity: .06;
  pointer-events: none;
  background: var(--je-mark) center / contain no-repeat;
}
.gent__row { display: flex; align-items: center; gap: 10px; }
.gent__txt { flex: 1; min-width: 0; }
.gent__t { display: block; font-size: 16px; font-weight: 700; letter-spacing: -.02em; }
/* Подпись не переносится, а сокращается: перенос менял бы высоту карточки
   вслед за числом включённых игр - ровно то, чего тут быть не должно.
   Текст собирает app.js по ответу сервера и называет только живые игры. */
.gent__s {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  color: var(--app-mute);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gent__go { flex: none; color: var(--gold); opacity: .85; }

/* ЗНАЧКИ ИГР - ВЕКТОР В РАЗМЕТКЕ, а не картинки из генератора: на 26px
   растр даёт мутную кашу, пять картинок разъехались бы по стилю между
   собой, и это пять лишних загрузок на главном экране. Здесь один контур,
   одна толщина штриха, один язык - и ни одного запроса.

   Полоса значков лежит СВОИМ слоем: сами значки позиционированы абсолютом,
   то есть не участвуют в раскладке и не могут вырастить карточку. Нажатий
   не принимают - это украшение, а не меню: вся карточка одна кнопка. */
.gent__art {
  position: relative;
  display: block;
  height: 34px;
  margin-top: 8px;
  pointer-events: none;
}
.gent__ico {
  position: absolute;
  display: block;
  width: 26px; height: 26px;
  color: rgba(216, 162, 74, .30); /* будущая игра - приглушённое золото */
}
.gent__ico svg { display: block; width: 100%; height: 100%; }
/* Живой считается игра, чей ключ пришёл в modules с enabled: true
   (app.js: paintGamesCard). Подпись карточки называет только их. */
.gent__ico--live { color: var(--gold); }

.gent__ico:nth-child(1) { left: 0; top: 4px; width: 30px; height: 30px; }
.gent__ico:nth-child(2) { left: 22%; top: 0; }
.gent__ico:nth-child(3) { left: 43%; top: 8px; }
.gent__ico:nth-child(4) { left: 64%; top: 1px; }
.gent__ico:nth-child(5) { left: 84%; top: 7px; }

/* Дрейф - ТОЛЬКО transform: перекладки макета на кадре тут быть не должно,
   карточка живёт на главном экране вместе с одометром баланса и гильошем.
   Фазы разведены отрицательной задержкой: одинаковый такт у пяти значков
   читался бы как мигание, а не как дрейф. */
@keyframes gdrift { 50% { transform: translate3d(0, -4px, 0); } }
@keyframes gdriftB { 50% { transform: translate3d(0, 4px, 0); } }
.gent__ico { animation: gdrift 6.5s ease-in-out infinite; }
.gent__ico:nth-child(2) { animation: gdriftB 7.8s ease-in-out infinite -1.4s; }
.gent__ico:nth-child(3) { animation-duration: 8.6s; animation-delay: -3.1s; }
.gent__ico:nth-child(4) { animation: gdriftB 7.1s ease-in-out infinite -2.2s; }
.gent__ico:nth-child(5) { animation-duration: 9.4s; animation-delay: -4.6s; }

/* ------------------------------------------------ Шапка лобби ----------- */

.ghead { display: flex; align-items: center; gap: 12px; padding: 2px 0; }
.ghead__icon {
  display: block;
  width: 44px; height: 44px;
  border-radius: 12px; /* радиус полей, не карточек: это жетон, не карта */
  box-shadow: var(--e2);
  flex: none;
}
.ghead__t { display: block; font-size: 16px; font-weight: 700; letter-spacing: -.02em; }
.ghead__s { display: block; margin-top: 2px; font-size: 12px; color: var(--app-mute); }

/* ------------------------------------------------ Сетка игр ------------- */

/* Плитки выбора игры, две в ряд. Показываются, только когда включено больше
   одной: экран с единственной кнопкой запрещён решением проекта.

   Подложка плитки - фон мира этой игры; поверх него затемнение снизу вверх,
   чтобы имя и подпись читались на любом кадре, а верх плитки оставался
   картинкой. Медаль вверху, текст прижат к низу. Высота задаётся
   отношением сторон, а не содержимым: подписи разной длины не должны делать
   плитки разной высоты в одном ряду. */
.ggrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.gtile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  min-width: 0;
  aspect-ratio: 1 / 1.12;
  padding: 12px;
  border-radius: 14px;
  overflow: hidden;
  text-align: left;
  color: var(--app-ink);
  background-color: var(--app-200);
  background-size: cover;
  background-position: center;
  border: 1px solid var(--app-300);
  border-top-color: var(--gold-line);
  border-bottom-color: #0E1013;
  box-shadow: var(--e2);
  animation: rise var(--t-mid) var(--spring) backwards;
}
.gtile:nth-child(2) { animation-delay: 40ms; }
.gtile:nth-child(3) { animation-delay: 80ms; }
.gtile:nth-child(4) { animation-delay: 120ms; }
.gtile:nth-child(5) { animation-delay: 160ms; }
.gtile:nth-child(6) { animation-delay: 200ms; }
/* Последняя плитка нечётного ряда занимает всю ширину - широкий кадр. */
.gtile--wide { grid-column: 1 / -1; aspect-ratio: 2.35 / 1; }
/* Затемнение: верх почти чистый, низ под текстом почти чёрный. Отдельный
   слой, а не градиент в background, - иначе он перекрыл бы картинку. */
.gtile::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(12, 13, 15, .10) 0%,
    rgba(12, 13, 15, .42) 48%,
    rgba(12, 13, 15, .90) 100%);
}
/* Верхний блик кромки поверх картинки: без него плитка плоская. */
.gtile::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07);
  pointer-events: none;
}
.gtile > * { position: relative; z-index: 1; }
.gtile__emblem {
  display: block;
  width: 46px; height: 46px;
  margin-bottom: auto;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 0 4px 10px rgba(0, 0, 0, .6), 0 0 0 1px rgba(216, 162, 74, .25);
  transition: transform var(--t-mid) var(--spring);
}
.gtile--wide .gtile__emblem { width: 54px; height: 54px; }
.gtile:hover .gtile__emblem { transform: translateY(-2px) rotate(-6deg); }
.gtile__text { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; max-width: 100%; }
.gtile__t {
  display: block;
  font-size: 15px; font-weight: 700; letter-spacing: -.02em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .8);
}
.gtile__s {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 2px;
  font-size: 11px; line-height: 1.3;
  color: var(--app-mute);
}
.gtile--wide .gtile__s { -webkit-line-clamp: 1; }
/* Доступная ставка - единственное число на плитке. Жетон, а не текст. */
.gtile__bet {
  margin-top: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--gold-line);
  background: rgba(12, 13, 15, .55);
  font-size: 10.5px;
  color: var(--gold);
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis;
}
.gtile[disabled] { opacity: .55; }
/* На узком окне (Telegram Desktop, 347 px) плитки ужимаются, а не ломаются. */
@media (max-width: 360px) {
  .ggrid { gap: 8px; }
  .gtile { padding: 10px; }
  .gtile__t { font-size: 14px; }
  .gtile__emblem { width: 40px; height: 40px; }
}

/* ------------------------------------------------ Свеча ----------------- */

/* Карточка свечи: канвас с графиком, крупный множитель поверх него и
   строка фазы. ВЫСОТА ЗАДАНА ЗДЕСЬ И НЕ ЗАВИСИТ ОТ ФАЗЫ РАУНДА - ни отсчёт,
   ни обвал, ни автозакрытие не меняют состав узлов, только их содержимое и
   класс тона. Иначе развязка уносила бы кнопку «Закрыть позицию» вниз ровно
   в тот момент, когда человек к ней тянется.

   График на канвасе, а число - узлом DOM: его читает скринридер, оно
   моноширинное с табличными цифрами (не дёргает соседей при каждом кадре) и
   остаётся чётким на любом DPI. */
.gspark { padding: 12px 12px 10px; }
.gspark__cap {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--app-mute);
}
.gspark__box {
  position: relative;
  height: 156px;          /* постоянная во всех фазах раунда */
  margin-top: 9px;
  border-radius: 10px;
  background: var(--app-100);
  box-shadow: var(--inset);
  overflow: hidden;
}
.gspark__c {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  display: block;
}
/* Крупный множитель: он и есть главное число экрана, поэтому лежит поверх
   графика, а не под ним. Тень мягкая - под ним бежит кривая. */
.gspark__x {
  position: absolute;
  left: 12px; top: 10px;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1;
  white-space: nowrap;
  color: var(--app-ink);
  text-shadow: 0 2px 10px rgba(0, 0, 0, .65);
}
.gspark__st {
  position: absolute;
  left: 12px; right: 12px; bottom: 8px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11.5px;
  color: var(--app-mute);
  text-shadow: 0 1px 6px rgba(0, 0, 0, .7);
}
/* Тон фазы - ТОЛЬКО цвет числа и кромка коробки. Ни размера, ни отступов:
   тон меняется несколько раз за раунд, и любая правка раскладки в нём
   двигала бы экран под рукой. */
.gspark--lead .gspark__x { color: var(--app-mute); }
.gspark--live .gspark__x { color: var(--gold); }
.gspark--win .gspark__x { color: var(--gold); }
.gspark--win .gspark__box { box-shadow: var(--inset), inset 0 0 0 1px var(--gold-line); }
.gspark--loss .gspark__x { color: #D97563; }
.gspark--loss .gspark__box {
  box-shadow: var(--inset), inset 0 0 0 1px rgba(196, 97, 79, .5);
}
/* Отсчёт: число дышит раз в секунду. Это единственная анимация карточки, и
   она значит ровно то, что показывает, - идёт обратный отсчёт. */
@keyframes gbeat {
  0%, 100% { transform: scale(1); }
  22% { transform: scale(1.06); }
}
.gspark--lead .gspark__x {
  transform-origin: left center;
  animation: gbeat 1s var(--snap) infinite;
}

/* ------------------------------------------------ Стакан ---------------- */

/* Лестница стакана - чеканная пластина с прокруткой ВНУТРИ СЕБЯ.
   Высота задана здесь и не зависит ни от числа ступеней, ни от состояния
   раунда: развязка красит строки классами, не трогая раскладку. Без своей
   высоты десять ступеней растянули бы страницу, и кнопка ставки уехала бы
   за нижний край экрана.

   Волна исхода (.gres) вешается на КАРТОЧКУ, а не на прокручиваемый
   список: `overflow: hidden`, который ей нужен, убил бы прокрутку. */
.gbook { padding: 12px 12px 10px; }
.gbook__cap {
  /* Подпись СЖИМАЕТСЯ и обрезается, а не толкает заголовок: на 320px внутри
     карточки 270px, и заголовок с длинной подписью вылезли бы за край. */
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--app-mute);
}
.gladder {
  margin-top: 9px;
  max-height: 216px;      /* пять с половиной ступеней: видно, что список длиннее */
  overflow-y: auto;
  overscroll-behavior-y: contain;  /* прокрутка лестницы не тянет весь экран */
  scrollbar-width: none;
}
.gladder::-webkit-scrollbar { display: none; }

/* Ступень: множитель, шанс, потолок ставки. Три колонки жёсткие -
   плавающая ширина числа дёргала бы соседей при каждом обновлении.
   Высота строки постоянная: перенос запрещён везде. */
.grung {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 40px;
  padding: 0 10px;
  border-radius: 9px;
  border: 1px solid transparent;
  text-align: left;
  color: var(--app-mute);
}
.grung + .grung { margin-top: 4px; }
.grung__m {
  flex: none;
  min-width: 62px;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 13.5px; font-weight: 700;
  color: var(--app-ink);
  white-space: nowrap;
}
.grung__c {
  flex: 1; min-width: 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 12.5px;
  white-space: nowrap;
}
.grung__cap {
  flex: none;
  font-size: 11.5px;
  color: var(--app-mute);
  white-space: nowrap;
}

/* Выбранная ступень: золотая кромка и тёплая подложка. Её видно сразу,
   без прокрутки - список открывается на первой, самой мелкой. */
.grung--sel {
  border-color: var(--gold-line);
  background: var(--gold-dim);
  color: var(--app-ink);
}
.grung--sel .grung__m { color: var(--gold); }

/* Ступень, которой ставка не по росту: приглушена ДО нажатия. Молча она
   не выбирается - нажатие называет её потолок словами под полем ставки. */
.grung--over { opacity: .45; }
.grung--over .grung__cap { color: #D97563; }

/* Ход рынка: строки, до которых он дошёл, светятся золотой левой гранью.
   Каскад задаёт JS через animation-delay - сверху вниз, как читается
   стакан. Класс несёт и статичное состояние, поэтому при выключенном
   движении картина остаётся честной. */
.grung--hit {
  background: rgba(216, 162, 74, .07);
  box-shadow: inset 2px 0 0 var(--gold);
}
@keyframes gwalk {
  from { background-color: rgba(216, 162, 74, .26); }
}
.grung--hit { animation: gwalk 260ms var(--snap) backwards; }

/* Лимитка игрока: исполнена или осталась висеть. Цвет - подсказка глазу,
   слово говорит плашка исхода под лестницей. */
.grung--fill { box-shadow: inset 2px 0 0 var(--gold), var(--e1); }
.grung--fill .grung__cap { color: var(--gold); }
.grung--miss { box-shadow: inset 2px 0 0 #C4614F; }
.grung--miss .grung__cap { color: #D97563; }

/* ------------------------------------------------ Фетр стола ------------ */

/* Стол: фетр, виньетка и вуаль - слоями ОДНОГО фона, а не стопкой узлов.
   Вуаль раньше жила в ::before; она переехала сюда, чтобы освободить
   псевдоэлемент под водяной знак: ::after уже занят волной исхода
   (.gres::after на этом же узле), и третьего псевдоэлемента у стола нет.

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

   Кромка двойная, как у чеканной пластины: золотая волосяная линия по
   краю и тёмная фаска сразу под ней. Ассет несёт свою запечённую рамку,
   но он растянут 100% 100% и в углах её ведёт - эта линия ровная всегда. */
.gtable {
  position: relative;
  padding: 16px 12px 14px;
  border-radius: 16px; /* сцена крупнее карточки - и радиус свой */
  border: 1px solid rgba(216, 162, 74, .26);
  background:
    radial-gradient(115% 85% at 50% 44%,
      rgba(12, 13, 15, 0) 46%, rgba(8, 9, 10, .40) 100%),
    linear-gradient(180deg,
      rgba(12, 13, 15, .30),
      rgba(12, 13, 15, .08) 44%,
      rgba(12, 13, 15, .32)),
    url('../games/table-felt.webp') center / 100% 100% no-repeat,
    var(--app-200);
  box-shadow: var(--e2), inset 0 0 0 1px rgba(12, 13, 15, .55);
  overflow: hidden;
}
/* Водяной знак: именная монограмма по центру сукна. Это ФОН, а не
   участник игры - отсюда 7% непрозрачности: знак виден боковым зрением и
   не спорит ни с картой, ни с жетоном суммы.

   Прозрачность задана самому псевдоэлементу, а не слою фона (у слоёв
   своей opacity нет), поэтому знак берётся картинкой напрямую и НЕ
   требует маски: не поддержи её браузер - на сукне просто не будет
   водяного знака, а не золотой квадрат посреди стола.

   Псевдоэлемент рисуется РАНЬШЕ позиционированных зон (порядок дерева),
   поэтому карты и подписи всегда поверх него - z-index не нужен. */
.gtable::before {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 152px; height: 152px;
  transform: translate(-50%, -50%);
  pointer-events: none;
  opacity: .07;
  background: var(--je-mark) center / contain no-repeat;
}
/* Зоны рук поднимаются над водяным знаком - перечислены поимённо, а НЕ
   универсальным `> *`: тот перебил бы позиционирование у любого накладного
   элемента, который тут заведут (ровно та ловушка, что ломала холст
   гильоша 2026-07-28). Позиционирование нужно им и само по себе: от него
   считается ромб на разделителе зон. */
.gtable > .gzone { position: relative; }

/* Бейджа «ДЕМО» на сукне НЕТ намеренно: режим называет плашка в шапке
   карточки над столом, а на сукне вторая копия лезла на жетон суммы
   дилера. Помечен режим по-прежнему всегда - просто одним местом. */

/* Зоны рук: дилер сверху, игрок ниже, между ними чеканная линия -
   золотая волосяная сверху и тёмная фаска под ней, то есть та же двойная
   кромка, что у самого стола. Тень внутренняя и без размытия: это
   прочерченная канавка, а не свечение. */
.gzone + .gzone {
  margin-top: 12px;
  padding-top: 13px;
  border-top: 1px solid rgba(216, 162, 74, .20);
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, .45);
}
/* Чеканный ромб посередине разделителя - тот же знак, что стоит перед
   подписью зоны. Он ОДИН на весь стол и появляется только у второй зоны,
   поэтому линия читается как разделённое поле, а не как случайный штрих.
   Заливка тёмная и полупрозрачная: фетр под ней угадывается, ромб не
   выглядит заплаткой. */
.gzone + .gzone::before {
  content: "";
  position: absolute;
  top: -4px; left: 50%;
  width: 6px; height: 6px;
  margin-left: -4px;
  transform: rotate(45deg);
  background: rgba(12, 13, 15, .78);
  border: 1px solid rgba(216, 162, 74, .45);
  pointer-events: none;
}
.gzone__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.gzone__cap {
  display: inline-flex; align-items: center;
  font-family: var(--mono);
  font-size: 11px; font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: rgba(233, 235, 238, .78); /* на фетре штатный mute тонет */
}
/* Маркер подписи - маленький ромб той же чеканки, что и на разделителе.
   Он декоративный целиком: скринридеру подпись читается словом. */
.gzone__cap::before {
  content: "";
  width: 5px; height: 5px;
  margin-right: 8px;
  flex: none;
  transform: rotate(45deg);
  border: 1px solid rgba(216, 162, 74, .50);
}

/* Жетон суммы руки - отчеканенная фишка: тёмный металл с уклоном сверху
   вниз, золотая кромка, цифры табличные. Сумма приходит строкой сервера
   («7/17» у мягких рук), клиент её не считает.

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

   Пустой жетон НЕ схлопывается намеренно: между сбросом стола и приходом
   свежей суммы он пуст доли секунды, и `display: none` на это время
   уронил бы высоту шапки зоны, то есть высоту всего сукна. Пустая кромка
   мелькает - стол не прыгает. */
.gtotal {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 13px; font-weight: 700;
  color: var(--gold);
  padding: 3px 11px;
  border-radius: 999px;
  background: linear-gradient(180deg,
    rgba(34, 38, 43, .92), rgba(12, 13, 15, .92));
  border: 1px solid var(--gold-line);
  box-shadow: var(--e1);
  white-space: nowrap;
}
/* Вспышка при изменении суммы: тёплый подсвет и микросдвиг, один раз -
   словарь «вспышки курса» из скилла. Цвет и transform, макет не трогаются. */
@keyframes gmint { from { transform: translateY(-2px); color: #F2C87E; } }
.gtotal--tick { animation: gmint 380ms var(--snap) backwards; }

/* ------------------------------------------------ Карты ----------------- */

/* Ряд карт НЕ переносится на вторую строку: перенос менял бы высоту сукна
   под добором дилера, и стол прыгал бы под рукой (замечание владельца
   2026-08-31). Длинная рука уезжает вбок - JS доматывает ряд к свежей
   карте. Высота ряда постоянная при любом числе карт.

   Отступы ряда ЗАДАНЫ ПАДДИНГОМ, а не margin: горизонтальная прокрутка
   обрезает и по вертикали (CSS: overflow visible по одной оси при auto по
   другой вычисляется в auto). Сверху запас под въезд карты, снизу и по
   бокам - под её тень: без него карта лежала бы на сукне срезанной. */
.gcards {
  display: flex; flex-wrap: nowrap; gap: 6px;
  padding: 12px 4px 8px;
  overflow-x: auto;
  overscroll-behavior-x: contain; /* прокрутка ряда не тянет весь экран */
  perspective: 420px; /* глубина переворота закрытой карты */
  scrollbar-width: none;
}
.gcards::-webkit-scrollbar { display: none; }

/* РУКА СЖИМАЕТСЯ В ВЕЕР, КОГДА КАРТ СТАНОВИТСЯ МНОГО. Расчёт на 320px
   (там `--pad: 13px`: под сукном 294px, минус его паддинги 2x12 и
   паддинги ряда 2x4 - остаётся 262px чистого ряда):

   | карт | нахлёст | шаг | ширина ряда | влезает   |
   | ---- | ------- | --- | ----------- | --------- |
   | 2    | 0       | 52  | 98          | да        |
   | 5    | 0       | 52  | 254         | да        |
   | 6    | 20      | 32  | 206         | да        |
   | 7    | 20      | 32  | 238         | да        |
   | 8    | 26      | 26  | 228         | да        |
   | 9    | 26      | 26  | 254         | да        |
   | 10+  | 26      | 26  | 280+        | прокрутка |

   Перекрытие включается селектором «первая карта, за которой есть ещё
   пять» - то есть по числу карт В РУКЕ, без единой строки JS. Индекс
   ранга живёт в ЛЕВОМ верхнем углу как раз для этого: перекрытая карта
   показывает угол, а угол и есть вся информация. Так же держит руку и
   живой дилер.

   Прокрутка при этом никуда не делась и остаётся последней страховкой:
   теоретический максимум руки в «21» - одиннадцать карт. */
.gcard { flex: none; } /* фишка не сжимается в узком ряду */
.gcard:first-child:nth-last-child(n+6) ~ .gcard { margin-left: -20px; }
.gcard:first-child:nth-last-child(n+8) ~ .gcard { margin-left: -26px; }

/* Карта - настоящая игральная карта, собранная из узлов DOM, а не
   картинка: ранг и масть присылает сервер, и они обязаны остаться
   ТЕКСТОМ - его читает скринридер, а перерисовать в растр их некому.

   Размер 46x64 (пропорция 1.39, как у настоящей карты) и он ЖЁСТКИЙ:
   высота сукна выведена из него, и карта, растущая от содержимого,
   двигала бы стол под рукой. Поэтому всё, что рисуется поверх поля -
   индексы, знак масти, медальон, - лежит абсолютом.

   Тень одна и без второго слоя: карта анимируется трансформом при
   раздаче и перевороте, а каждая лишняя тень на анимируемом узле - это
   лишний растр на каждом кадре. Фаска сделана градиентом поля, а не
   внутренней тенью. */
.gcard {
  position: relative;
  display: block;
  width: 46px; height: 64px;
  border-radius: 7px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, .45);
}

/* Лицо: тёплое светлое поле с едва заметной фрезой - тот же штрих, что
   у графитовых поверхностей «Чеканки», только по светлому. Рамка
   бронзовая волосяная, свет сверху (поле светлеет к верхнему краю). */
.gcard--face {
  border: 1px solid rgba(122, 96, 46, .45);
  background:
    repeating-linear-gradient(115deg,
      rgba(94, 74, 34, .045) 0 1px, transparent 1px 4px),
    linear-gradient(168deg, #F6F1E6 0%, #EDE5D6 55%, #E0D6C1 100%);
  color: #23272C; /* графит, а не чёрный: контраст к полю не хуже 10.4:1 */
}

/* Индекс в левом верхнем углу - ранг и масть, ровно как их назвал
   сервер. Ограничение ширины ранга - страховка от незнакомой формы
   имени: разбор такую строку кладёт в ранг целиком, и без обрезки она
   вылезла бы за карту. */
.gcard__rank {
  position: absolute; top: 4px; left: 5px;
  max-width: 36px; overflow: hidden;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 12.5px; font-weight: 700; line-height: 1;
  letter-spacing: -.04em;
}
.gcard__suit {
  position: absolute; top: 16.5px; left: 5px;
  font-size: 10px; line-height: 1;
}

/* Знак масти в центре поля - крупный и приглушённый: он повторяет то,
   что уже сказано индексом, поэтому не имеет права спорить с рангом. */
.gcard--face::before {
  content: attr(data-s);
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 20px; line-height: 1;
  opacity: .8;
}

/* Второй индекс - в противоположном углу, перевёрнутый, как на настоящей
   карте. Он СОБИРАЕТСЯ ИЗ АТРИБУТОВ, а не из вторых узлов текста: копия
   в разметке читалась бы скринридером дважды («туз пик туз пик»), а
   генерируемое содержимое он не читает вовсе. Правда о карте остаётся
   одна - та, что в индексе выше. */
.gcard--face::after {
  content: attr(data-r) "\A" attr(data-s);
  position: absolute; bottom: 4px; right: 5px;
  display: block;
  white-space: pre;
  text-align: center;
  transform: rotate(180deg);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 9.5px; font-weight: 700; line-height: 1.12;
  letter-spacing: -.04em;
}

/* Красные масти - глубокий геральдический кармин, а не сигнальный
   красный: контраст к самому тёмному краю поля 5.25:1, к светлому
   6.72:1 - выше порога 4.5:1 на всём градиенте.
   Это по-прежнему подсказка глазу; правда - имя карты. */
.gcard--red { color: #A02234; }

/* ИМЕННОЙ ЗНАК: у туза и фигур место центрального знака занимает
   монограмма «JE» в чеканном кольце. Выбор именно этих четырёх рангов
   не случаен - в «21» это ровно карты старшего достоинства, то есть знак
   дома стоит там, где на настоящей колоде стоит фигура.

   Правило идёт ПОСЛЕ центрального знака масти и перебивает его при
   равной силе селектора - порядок здесь несущий, менять нельзя.

   Знак взят маской, а заливка - бронзовый градиент со светом сверху:
   так монограмма выглядит вытесненной в поле карты, а не наклеенной.
   Всё правило спрятано за @supports: не поддержи браузер маску, у
   фигуры останется обычный знак масти вместо бронзового пятна. */
@supports ((-webkit-mask-image: none) or (mask-image: none)) {
  .gcard--court::before {
    content: "";
    inset: auto;
    left: 50%; top: 50%;
    width: 28px; height: 28px;
    margin: -14px 0 0 -14px;
    opacity: .78;
    background: linear-gradient(180deg, #C79A46, #A8802F 46%, #7E5F1E);
    -webkit-mask: var(--je-mark) center / contain no-repeat;
    mask: var(--je-mark) center / contain no-repeat;
  }
}

/* Рубашка закрытой карты дилера: квадратный гильош кроится портретно
   (cover срезает поля слева и справа), значение не знает и сервер.
   Своего лица у неё нет - ни индексов, ни знака масти: их рисуют
   правила .gcard--face, а этого класса у рубашки не бывает. */
.gcard--back {
  border: 1px solid var(--gold-line);
  background: var(--app-100) url('../games/card-back.webp') center / cover no-repeat;
}
.gcard--back::after { /* лёгкая вуаль, чтобы рубашка не спорила с лицами */
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background: rgba(12, 13, 15, .18);
}

/* Раздача: новая карта въезжает сверху с лёгким доворотом. Каскад задаёт
   JS через animation-delay; backwards держит карту невидимой до старта. */
@keyframes gdeal {
  /* Смещение не больше верхнего паддинга ряда: тот теперь режет вылет за
     свои границы, а срезанная на въезде карта выглядит сбоем. */
  from { opacity: 0; transform: translateY(-10px) rotate(-4deg) scale(.92); }
}
.gcard--deal { animation: gdeal 200ms var(--spring) backwards; }

/* Открытие: карта на месте рубашки поворачивается лицом. */
@keyframes gflip {
  from { opacity: .35; transform: rotateY(84deg); }
}
.gcard--flip { animation: gflip 240ms var(--snap) backwards; }

/* ------------------------------------------------ Исход на сукне -------- */

/* Плашка исхода: чеканная печать ПОД столом, между сукном и кнопками.
   Появляется задержкой анимации (её ставит JS по числу доехавших карт),
   поэтому развязка читается как «дилер открылся, добрал, и только потом
   объявили». Ни одной кнопки не держит - те живут сразу, драма никого не
   ждёт.

   СНАРУЖИ СУКНА, и это требование, а не вкус: внутри она меняла бы высоту
   стола при развязке, и стол прыгал под рукой (замечание владельца
   2026-08-31). Наложением по центру - тоже нет: закрыла бы жетон суммы
   игрока до конца раунда. Под столом же читается естественный порядок:
   карты -> чем кончилось -> что делать дальше.

   КОРОБКА ЗАНИМАЕТ СВОЁ МЕСТО С САМОГО НАЧАЛА. Раньше пустая схлопывалась
   (`:empty { display: none }`), и при развязке под сукном ВЫРАСТАЛИ 44px -
   ряд кнопок уезжал вниз прямо под пальцем (замечание владельца
   2026-08-31: «экран прыгает»). Теперь развязка вписывает плашку в уже
   занятое место, и не двигается ничего.

   Цена решения названа честно: у играющейся руки между сукном и кнопками
   стоит пустая полоса в высоту плашки. Это ровное поле, а не прыжок.

   backwards обязателен: до старта плашка обязана быть невидимой, иначе
   исход объявляется раньше, чем открыта закрытая карта. При
   prefers-reduced-motion анимации нет вовсе - плашка просто стоит на
   месте, и экран остаётся честным. */
.gflagbox { min-height: 44px; }
.gflag {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: nowrap; gap: 4px 10px;
  min-height: 44px;
  padding: 9px 13px;
  border-radius: 12px;
  background: var(--app-200);
  border: 1px solid var(--app-400);
  box-shadow: var(--e1);
}
/* Слово исхода не переносится на вторую строку, а сокращается: перенос
   вырастил бы плашку выше зарезервированных 44px, то есть вернул бы тот
   же прыжок с другой стороны. Полностью исход назван в подробностях
   раунда. Сумма не сокращается никогда - она справа и flex: none. */
.gflag__t {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12.5px; font-weight: 700; line-height: 1.25;
}
.gflag__n {
  flex: none;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 16px; font-weight: 700;
  letter-spacing: -.02em;
  white-space: nowrap;
}
/* Цвет - по слову сервера: золото выигрышу, честная красная проигрышу,
   ничья и отмена остаются нейтральными. */
.gflag--ok { border-color: var(--gold-line); }
.gflag--ok .gflag__n { color: var(--gold); }
.gflag--bad { border-color: rgba(196, 97, 79, .55); }
.gflag--bad .gflag__n { color: #D97563; }

@keyframes gstamp {
  from { opacity: 0; transform: scale(1.14); }
  55% { opacity: 1; }
}
.gflag { animation: gstamp 420ms var(--snap) backwards; }

/* ------------------------------------------------ Лобби ----------------- */

/* Блок честности - компактная минт-панель: золотая волосяная кромка и
   едва заметный тёплый штрих фрезы поверх графита. Свёрнута по умолчанию
   (замечание владельца 2026-08-31): развёрнутая занимала почти экран между
   «Раздать» и правилами, а нужна не на каждом заходе. */
.gfair {
  padding: 12px 14px;
  border-color: var(--gold-line);
  background-image: repeating-linear-gradient(115deg,
    rgba(216, 162, 74, .022) 0 1px,
    transparent 1px 4px);
}
.gfair .kvlist { margin-top: 0; padding-top: 0; }
.gfair .calc__hint { font-size: 11.5px; }

/* Раскрыватель - тот же, что у образцов документов на верификации
   (.doc__toggle из ops.css), но с пальцевой высотой: у исходного 6px
   отступа и 12.5px строки дают около 27px, а в такую область на телефоне
   не попасть. */
.gfair__more {
  display: flex; align-items: center; gap: 6px;
  min-height: 32px;
  margin-top: 4px;
}
/* Указатель раскрытия: уголок вниз, поворачивается вверх на раскрытой
   панели. Своей иконки не заводим - это тот же треугольник, что рисуют
   раскрывающиеся строки истории. */
.gfair__more::after {
  content: "";
  width: 6px; height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--t-fast) var(--snap);
}
.gfair__more[aria-expanded="true"]::after {
  transform: translateY(1px) rotate(-135deg);
}

/* Ряд ставок - фишки казино: круглые пилюли с насечкой обода. Ряд едет
   горизонтально внутри себя (.filters), экран 320px не распирает. */
.gchips { gap: 8px; }
.gchips .chip {
  min-width: 54px;
  justify-content: center;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  border-radius: 999px;
  border: 1px solid var(--app-400);
  box-shadow: var(--e1);
  position: relative;
  flex: none;
}
.gchips .chip::before { /* насечка обода фишки */
  content: "";
  position: absolute; inset: 2px;
  border-radius: inherit;
  border: 1px dashed rgba(216, 162, 74, .30);
  pointer-events: none;
}

/* ------------------------------------------------ Результат ------------- */

/* Вспышка исхода на карточке результата и на сукне стола: одна волна света
   сверху вниз по opacity, без конфетти. Выигрыш - золото; проигрыш -
   честная красная, исход за деньги не маскируется весёлой анимацией.
   Базовое состояние - opacity 0, поэтому при reduced-motion вспышки просто
   нет. Задержку на столе ставит JS (--gwash) - волна обязана идти ПОСЛЕ
   последней доехавшей карты; у квитанции результата задержка своя,
   короткая: там карты уже стоят. */
.gres { position: relative; overflow: hidden; }
.gres::after {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(130% 95% at 50% 0%,
    rgba(216, 162, 74, .30), transparent 62%);
}
.gres--loss::after {
  background: radial-gradient(130% 95% at 50% 0%,
    rgba(196, 97, 79, .32), transparent 62%);
}
@keyframes gflash { 28% { opacity: 1; } 100% { opacity: 0; } }
.gres--win::after,
.gres--loss::after {
  animation: gflash var(--t-slow) var(--snap) var(--gwash, 140ms);
}

/* ------------------------------------------- Строка денег и подробности - */

/* ГЛАВНОЕ СВОЙСТВО ШАПКИ: у играющейся и у рассчитанной руки она одной
   высоты. Раньше развязка добавляла в неё строку выплаты, три-четыре
   строки списка и строку баланса - всё это стоит НАД сукном, и стол уезжал
   вниз на добрую сотню пикселей (замечание владельца 2026-08-31).

   Слоты теперь одни и те же и заняты с самого начала:

   .gmoney__top  подпись строки + место чипа проверки + значок подробностей
   .ghero        крупное число: ставка у открытой руки, итог у рассчитанной
   .gmore__panel подробности, свёрнутые по умолчанию

   Высота верхней строки задана min-height, а не содержимым: чип приходит
   только у рассчитанной руки, и без запаса его появление растило бы
   строку. 34px - это 32px чипа (пальцевая область) плюс волосок. */
.gmoney__top {
  display: flex; align-items: center; flex-wrap: nowrap; gap: 8px;
  min-height: 34px;
}
/* Подпись отдаёт место чипу, а не переносится: на 320px «СТАВКА» рядом с
   чипом и значком укладывается в строку с запасом, но страховка дешевле
   разъехавшейся вёрстки. */
.gmoney__top .label {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gmoney__act { display: inline-flex; flex: none; }

/* Крупное число. Перенос запрещён: перенесённая половина суммы читается
   как другое число. Класс свой, а не правка общего .receipt__hero - тот же
   блок рисуют квитанции обмена, покупки и вывода. */
.ghero {
  flex-wrap: nowrap;
  margin-top: 2px;
  min-height: 34px;
  white-space: nowrap;
}
/* Цвет числа - по слову сервера, а не по расчёту клиента: ставка
   нейтральна (это ещё не исход), выигрыш золотой, проигрыш - честный
   красный, ничья и отмена нейтральны. Правила стоят ПОСЛЕ .receipt__hero
   и при равной силе селектора перебивают его золото. */
.ghero--bet, .ghero--flat { color: var(--app-ink); }
.ghero--win { color: var(--gold); }
.ghero--loss { color: #D97563; }

/* Чип проверки честности: скромный, не золотой - золото несёт главное
   действие («Ещё раунд»). Высота больше стандартных 30px чипа: в меньшую
   область на телефоне не попасть. */
.gmoney__act .gverify {
  height: auto;
  min-height: 32px;
  font-size: 12px;
  white-space: nowrap;
}

/* Значок подробностей - тот же уголок, что у блока честности в лобби и у
   раскрывающихся строк истории. Своей иконки не заводим. */
.gmore {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border-radius: 8px;
  border: 1px solid var(--app-300);
  background: var(--app-100);
  color: var(--app-mute);
  cursor: pointer;
}
.gmore::after {
  content: "";
  width: 7px; height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--t-fast) var(--snap);
}
.gmore[aria-expanded="true"] {
  color: var(--gold);
  border-color: var(--gold-line);
}
.gmore[aria-expanded="true"]::after {
  transform: translateY(1px) rotate(-135deg);
}

/* Подробности раунда. Список приносит свой верхний отступ и черту
   (.kvlist), поэтому панели собственного оформления не нужно. */
.gmore__panel .calc__hint { margin: 8px 0 0; }

/* ------------------------------------------------ Тормоз ---------------- */

/* Глобальный тормоз prefers-reduced-motion живёт в chekanka.css (гасит
   animation и transition везде). Здесь - страховка на случай его правки:
   игровые анимации обязаны схлопываться в мгновенные состояния. */
@media (prefers-reduced-motion: reduce) {
  .gcard--deal, .gcard--flip, .gtotal--tick, .gflag,
  .gres--win::after, .gres--loss::after,
  /* Ход рынка по лестнице: без анимации остаётся статичная подсветка
     дошедших ступеней - картина честная и без движения. */
  .grung--hit,
  /* Пульс отсчёта «Свечи»: он бесконечный, пока идут три секунды. Число при
     этом остаётся на месте и продолжает считать - гасится движение, а не
     информация. Кадры самой кривой гасит уже не CSS, а сам клиент: он
     переходит с rAF на редкий такт (games.js: CANDLE_CALM_MS), потому что
     замерший множитель означал бы игру, в которой нечем закрыть позицию. */
  .gspark--lead .gspark__x,
  /* Дрейф значков на входе в раздел: он бесконечный, поэтому гасить его
     важнее прочего - непрерывное движение на главном экране это ровно то,
     от чего просят избавить. */
  .gent__ico { animation: none !important; }
}
