/* Оформление игры.
 *
 * Одна идея держит весь файл: ЦВЕТ ПРИНАДЛЕЖИТ ТОЛЬКО КАРТАМ. Интерфейс
 * монохромный, четыре игровых цвета — единственные хроматические пятна во
 * всём приложении. Это не эстетика, а функция: когда цвет означает ровно
 * одну вещь, стол читается за долю секунды, и языковой барьер исчезает.
 * Кнопка не бывает синей, если синий не значит «синяя масть».
 *
 * Второе правило: стол ВСЕГДА тёмный, в светлой теме клиента тоже. Стол —
 * это сцена, а не документ. Тема Telegram уважается только в системных
 * мелочах, а не в игровой плоскости: карта обязана выглядеть одинаково у
 * всех, иначе игроки за одним столом видят разные игры.
 *
 * Тёмный не значит ПОТУШЕННЫЙ. Сцена освещена: над столом висит лампа —
 * тёплое пятно света сверху, — а сами поверхности подняты по яркости
 * настолько, чтобы в метро и на солнце было видно, где кончается стол и
 * начинается карта. Провал в почти чёрное выглядел как выключенный
 * экран, и это чинится здесь, в четырёх строчках палитры.
 *
 * Третье правило: ЧТО МОЖНО НАЖАТЬ, У ТОГО ЕСТЬ КРАЙ. Светлая обводка —
 * единственный признак «нажми меня», и она есть у каждого органа
 * управления и ни у чего больше. Надпись без края — это надпись, панель
 * без края — это фон. Игрок не обязан выяснивать это касанием.
 */

:root {
  /* Поверхности. Сцена освещённая: между соседними уровнями шаг заметен
     глазом, иначе панель на панели читается как одно пятно. */
  --surface-deep: #151A2B;
  --surface-table: #262D46;
  --surface-raised: #343C5B;
  --surface-overlay: #3E476B;
  /* Свет лампы над столом. Отдельными переменными, потому что этим же
     светом подсвечивается верх страницы на остальных экранах. */
  --lamp-hot: #4E5A8A;
  --lamp-warm: #363F63;

  /* Текст */
  --ink: #F5F7FF;
  --ink-2: #BAC1DA;
  --ink-3: #8C94B3;

  /* Линии. Панели отделяются линией и уровнем поверхности, а не тенью:
     тень остаётся физическим объектам — картам и фишкам. */
  --hairline: rgba(245, 247, 255, 0.12);
  --line-active: rgba(245, 247, 255, 0.34);

  /* Край нажимаемого. Не украшение: это единственное, чем кнопка
     отличается от надписи, а живая колода — от лежащей картинки. */
  --edge: rgba(245, 247, 255, 0.34);
  --edge-strong: rgba(245, 247, 255, 0.66);

  /* Игровые цвета — только на картах и на индикаторе активного цвета. */
  --play-R: #F0384E;
  --play-Y: #FFC531;
  --play-G: #21C776;
  --play-B: #2E7BFF;

  /* Валюты: единственное металлическое во всём приложении. */
  --brass: #E8B04B;
  --ice: #7FE3FF;

  --r-card: 14px;
  --r-panel: 20px;
  --r-control: 12px;
  --r-pill: 999px;

  /* Верх занят хедером Telegram: ничего интерактивного там быть не может. */
  --top-guard: 56px;
  --safe-bottom: env(safe-area-inset-bottom, 0px);

  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Цифры на картах — главный типографический объект приложения, поэтому
     у них своя шкала и обязательные табличные цифры: прыгающая ширина
     числа в банке и в таймере читается как дрожание. */
  --font-num: "Archivo", var(--font-ui);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* Атрибут hidden обязан ПОБЕЖДАТЬ.
 *
 * Правило браузера — это [hidden]{display:none}, и у него специфичность
 * атрибута: любое наше .класс{display:flex} его перебивает. Ровно так и
 * вышло со шторкой: #sheet с атрибутом hidden продолжал показываться как
 * flex, и его затемнение висело поверх ВСЕХ экранов — текст читался как
 * тёмный на тёмном, а внизу торчала пустая панель. Поломка тихая: в
 * консоли ни одной ошибки, экран просто выглядит выключенным. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  /* Тот же свет сверху, что и над столом: экран не обязан быть плоским
     чёрным листом, чтобы карты на нём читались. */
  background: var(--surface-deep);
  background-image: radial-gradient(120% 40% at 50% 0%, var(--lamp-warm) 0%, var(--surface-deep) 70%);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.4;
  overscroll-behavior-y: none;
}

body {
  /* Ничего не прокручивается вбок. Широкое прокручивается внутри себя. */
  overflow-x: hidden;
}

button { font: inherit; color: inherit; }

.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* ---------------------------------------------------------- скелет */

.boot { padding: calc(var(--top-guard) + 24px) 16px 24px; }
.boot-brand { display: flex; gap: 5px; margin-bottom: 24px; }
.boot-brand i { width: 22px; height: 5px; border-radius: 9px; display: block; }
.boot-brand i:nth-child(1) { background: var(--play-R); }
.boot-brand i:nth-child(2) { background: var(--play-Y); }
.boot-brand i:nth-child(3) { background: var(--play-G); }
.boot-brand i:nth-child(4) { background: var(--play-B); }

.skeleton {
  background: linear-gradient(90deg, var(--surface-raised) 0%, #454E75 50%, var(--surface-raised) 100%);
  background-size: 200% 100%;
  animation: sheen 1.2s linear infinite;
  border-radius: var(--r-panel);
}
.sk-card { height: 148px; margin-bottom: 16px; }
.sk-row { height: 64px; margin-bottom: 12px; }
.sk-row.short { width: 60%; }

@keyframes sheen { to { background-position: -200% 0; } }

/* ---------------------------------------------------------- каркас */

.screen {
  min-height: 100dvh;
  padding: var(--top-guard) 16px calc(24px + var(--safe-bottom));
}

.panel {
  background: var(--surface-raised);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  padding: 16px;
}

.row { display: flex; align-items: center; gap: 12px; }
.spread { justify-content: space-between; }
.stack { display: flex; flex-direction: column; gap: 12px; }

h1 { font-size: 30px; font-weight: 800; letter-spacing: -0.02em; margin: 0 0 16px; }
h2 { font-size: 20px; font-weight: 700; margin: 0 0 8px; }
.muted { color: var(--ink-2); font-size: 13px; }
.dim { color: var(--ink-3); font-size: 13px; }

/* Кнопка. Зона касания минимум 44 — это не рекомендация, а нижняя
   граница попадания большим пальцем на ходу. */
.btn {
  min-height: 48px;
  padding: 0 20px;
  border: 1px solid transparent;
  border-radius: var(--r-control);
  background: var(--ink);
  color: #12141d;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
}
/* Выключенная кнопка теряет КРАЙ, а не только яркость: полупрозрачная
   обводка на светлом фоне всё ещё читается как «нажми», и игрок жмёт в
   пустоту, считая, что не попал. */
.btn:disabled { opacity: 0.4; border-color: transparent; box-shadow: none; cursor: default; }
.btn.block { width: 100%; }
.btn.big { min-height: 60px; font-size: 17px; border-radius: var(--r-panel); }
/* Кнопка вне карточки обязана иметь обводку: заливка у неё та же, что у
   фона, и без линии она перестаёт быть кнопкой и остаётся надписью. */
.btn.ghost {
  background: var(--surface-raised);
  color: var(--ink);
  border: 1px solid var(--edge);
}
.btn.quiet { background: transparent; color: var(--ink-2); border: 1px solid var(--edge); }

/* Один край на всё нажимаемое и один отклик на нажатие. Список ведётся
   здесь целиком намеренно: новый орган управления, забытый в этом
   списке, сразу выглядит чужим — это и есть проверка. */
.btn, .tile, .tabs button, .colors button, .deck-card.live, .code {
  -webkit-user-select: none;
  user-select: none;
}
.btn:active, .tile:active, .tabs button:active, .colors button:active,
.deck-card.live:active, .code:active {
  transform: translateY(1px);
  filter: brightness(1.08);
}
.btn:disabled:active { transform: none; filter: none; }
/* Клавиатура и внешние манипуляторы: фокус обязан быть виден. */
.btn:focus-visible, .tile:focus-visible, .tabs button:focus-visible,
.colors button:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* Код приглашения нажимается (копируется) — значит, у него есть край.
   Без края это просто число на панели, и никто не догадается его
   тронуть. */
.code {
  display: inline-block;
  border: 1px dashed var(--edge);
  border-radius: var(--r-control);
  padding: 8px 14px;
  cursor: pointer;
}

.tile {
  width: 100%;
  text-align: start;
  background: var(--surface-raised);
  border: 1px solid var(--edge);
  border-radius: var(--r-panel);
  padding: 14px 16px;
  display: flex;
  align-items: center;
  gap: 14px;
  cursor: pointer;
}
.tile:active { border-color: var(--edge-strong); }
.tile .grow { flex: 1; min-width: 0; }
.tile .title { font-weight: 700; }
.tile .online { color: var(--ink-3); font-size: 12px; text-align: end; }
.tile .online b { display: block; color: var(--ink-2); font-size: 15px; font-weight: 700; }

.mode-chip { width: 40px; height: 56px; flex: none; }
.mode-chip .card { width: 100%; height: 100%; --w: 40px; }

/* ------------------------------------------------------------ карта */

/* Карта нарисована как настоящая: БЕЛАЯ РАМКА снаружи, цветное поле
   внутри, белый овал наискось и крупный знак поверх него. Это не
   украшательство — рамка отделяет карту от карты в веере, где они лежат
   внахлёст, а овал даёт знаку белую подложку, на которой одинаково
   читаются и жёлтый, и синий.
 *
   ВСЕ РАЗМЕРЫ СЧИТАЮТСЯ ОТ --w — ширины карты. Карта живёт в четырёх
   размерах (веер, колода, сброс, плитка режима), и набор своих чисел на
   каждый означал бы четыре разные карты: рамка в пиксель на плитке и в
   треть сантиметра на сбросе. Меняешь размер — меняй --w, и всё
   остальное съедет само. */
.card {
  --w: 74px;
  --frame: calc(var(--w) * 0.072);
  --radius: calc(var(--w) * 0.125);
  --mark: calc(var(--w) * 0.10);
  position: relative;
  border-radius: var(--radius);
  /* Не чистый белый: карта — предмет, а бумага не светится. */
  background: #FAFAF4;
  overflow: hidden;
  font-family: var(--font-num);
  font-weight: 800;
  color: #10121a;
  /* У карты есть толщина, светлый срез по ребру и короткая мягкая тень.
     Панели интерфейса теней не имеют вовсе — по этой разнице глаз и
     отличает «предмет» от «экрана».

     Кольцо играбельной карты живёт ЗДЕСЬ ЖЕ, отдельной тенью, а не
     outline: outline по скруглению рисуют не все вебвью Telegram, и на
     части телефонов рамка оказалась бы прямоугольной поверх скруглённой
     карты. По умолчанию кольцо прозрачное. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 -2px 0 rgba(0, 0, 0, 0.18),
    var(--ring, 0 0 rgba(0, 0, 0, 0)),
    0 8px 18px rgba(0, 0, 0, 0.5);
  user-select: none;
}

/* Цветное поле внутри рамки. overflow здесь несущий: им обрезается овал,
   и без него овал вылезал бы на белую рамку — на белом фоне этого не
   видно, пока кто-нибудь не сделает рамку цветной. */
.card .field {
  position: absolute;
  inset: var(--frame);
  border-radius: calc(var(--radius) - var(--frame) * 0.5);
  background: var(--c, var(--surface-overlay));
  overflow: hidden;
}

/* Овал наискось. Числа подобраны так, чтобы повёрнутый эллипс вписался в
   поле почти вплотную: при повороте на 25° габарит эллипса 76%×100%
   выходит в 81%×96% поля. Числа связаны — тронешь угол, пересчитай и
   размеры, иначе овал либо срежется краем, либо повиснет в середине
   узкой полосой. */
.card .oval {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 76%;
  height: 100%;
  transform: translate(-50%, -50%) rotate(25deg);
  border-radius: 50%;
  background: #FAFAF4;
}

.card[data-color="R"] { --c: var(--play-R); }
.card[data-color="Y"] { --c: var(--play-Y); }
.card[data-color="G"] { --c: var(--play-G); }
.card[data-color="B"] { --c: var(--play-B); }
/* Смена цвета — чёрное поле: у неё нет своего цвета, и любой из четырёх
   на её поле означал бы масть, которой у карты нет. Все четыре цвета
   живут внутри самого знака. */
.card[data-color="W"] { --c: #14161F; }
/* У простой смены цвета овала нет вовсе: её знак — сам четырёхцветный
   квадрат, и белая подложка под ним только гасила бы цвета. */
.card[data-kind="wild"] .oval { display: none; }
/* Крупная карта сброса — та же карта, только шире. Размер один, а не
   «покрупнее шрифт»: увеличенное число на прежней рамке выглядит
   другой картой. */
.card.big { --w: 82px; }

/* --------------------------------------------------- крупный знак */

.card .face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
/* Число — цветом карты по чёрному контуру, как на настоящей колоде.
   paint-order кладёт контур ПОД заливку: без него обводка съедает
   половину толщины штриха, и на мелких размерах цифра заплывает. */
.card .face .val {
  font-size: calc(var(--w) * 0.60);
  line-height: 1;
  color: var(--c);
  -webkit-text-stroke: calc(var(--w) * 0.036) #10121a;
  paint-order: stroke fill;
}
.card .face .sign {
  width: calc(var(--w) * 0.58);
  height: calc(var(--w) * 0.58);
  color: var(--c);
}
/* Знак смены цвета крупнее прочих: четыре цветных квадрата — это ЧЕТЫРЕ
   объекта в том же пятне, где у остальных знаков один, и в общий размер
   они складываются в мелкую сетку. */
.card .face .sign.wild { width: calc(var(--w) * 0.70); height: calc(var(--w) * 0.70); }

/* ----------------------------------------------------- углы карты */

/* Два угла, второй вверх ногами. Из веера карта видна только углом, и
   номинал в углу — единственное, по чему её там можно узнать. */
.card .pip {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--w) * 0.02);
  font-size: calc(var(--w) * 0.205);
  line-height: 1;
  color: #FAFAF4;
  -webkit-text-stroke: calc(var(--w) * 0.014) rgba(0, 0, 0, 0.6);
  paint-order: stroke fill;
  pointer-events: none;
}
.card .pip.tl { top: calc(var(--w) * 0.075); left: calc(var(--w) * 0.085); }
.card .pip.br {
  bottom: calc(var(--w) * 0.075);
  right: calc(var(--w) * 0.085);
  transform: rotate(180deg);
}
.card .pip .sign { width: calc(var(--w) * 0.21); height: calc(var(--w) * 0.21); color: #FAFAF4; }

/* Знак масти для дальтоников. Он есть ВСЕГДА, а не в специальном режиме:
   красно-зелёная слепота примерно у каждого двенадцатого мужчины, и игра
   на различении четырёх цветов теряет их на первой же партии. Стоит он
   в тех же двух углах, что и номинал: из веера видны только они. */
.mark {
  width: var(--mark, 10px);
  height: var(--mark, 10px);
  flex: none;
  opacity: 0.9;
}
.mark[data-shape="circle"] { border-radius: 50%; background: #10121a; }
.mark[data-shape="square"] { border-radius: 2px; background: #10121a; }
.mark[data-shape="diamond"] { background: #10121a; transform: rotate(45deg); border-radius: 2px; }
.mark[data-shape="triangle"] {
  width: 0; height: 0; background: none;
  border-left: calc(var(--mark, 10px) / 2) solid transparent;
  border-right: calc(var(--mark, 10px) / 2) solid transparent;
  border-bottom: var(--mark, 10px) solid #10121a;
}

/* ------------------------------------------------- рисованные знаки */

/* Знаковые карты рисуются, а не подписываются: игрок, впервые увидевший
   игру, обязан понять «стоп» и «разворот» без единой буквы.
   Каждый знак стоит в разметке ДВАЖДЫ — .ink рисует тёмный контур
   пошире, .lit кладёт поверх сам цвет. Один и тот же знак ложится и на
   белый овал в середине, и на цветное поле в углу; без контура он
   пропадал бы то на одном, то на другом. */
.sign { display: block; overflow: visible; }
.sign .ink { stroke: #10121a; stroke-linecap: round; stroke-linejoin: round; }
.sign.st .ink { fill: none; stroke-width: 6.4; }
.sign.st .lit { fill: none; stroke: currentColor; stroke-width: 3.4; stroke-linecap: round; stroke-linejoin: round; }
.sign.fl .ink { fill: #10121a; stroke-width: 3.4; }
.sign.fl .lit { fill: currentColor; stroke: none; }
/* У карт смены цвета контур светлый: поле под ними чёрное, и тёмная
   обводка на нём просто исчезает. */
.sign.wild .ink { fill: #FAFAF4; stroke: #FAFAF4; stroke-width: 3.4; }

/* -------------------------------------------------------- рубашка */

/* Та же конструкция, что у лица: рамка, поле, овал. Красный овал на
   чёрном — самый заметный предмет на столе, и по нему колода узнаётся
   раньше, чем игрок успевает прочитать счётчик карт. */
.card.back .field { background: #14161F; }
.card.back .oval { background: var(--play-R); }
/* ------------------------------------------------------------- стол */

.table {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding: var(--top-guard) 14px calc(12px + var(--safe-bottom));
  /* Лакированная доска под ЛАМПОЙ: горячее пятно света сверху,
     отражённый свет по середине, спокойный край внизу. Не сукно казино и
     не неон — но и не потушенный экран: игроку должно быть видно, где
     кончается стол. */
  background:
    radial-gradient(115% 62% at 50% -12%,
      var(--lamp-hot) 0%,
      var(--lamp-warm) 34%,
      var(--surface-table) 66%,
      var(--surface-deep) 100%);
}

/* Тонкая рамка по краю стола в активном цвете — второй, боковой способ
   его показать. Первый — свечение под сбросом. Подписи нет намеренно. */
.table::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  border: 2px solid var(--active, transparent);
  opacity: 0.42;
  transition: border-color 400ms ease;
}

/* ---------------------------------------------- места вокруг стола */

/* Стол — это КРУГ, а не список соперников над центром. Игрок за реальным
   столом узнаёт соседа по месту, а не по подписи: слева тот, кому я
   сейчас передам ход, справа тот, от кого он ко мне пришёл. Полоса подов
   этого не показывала вовсе — по ней нельзя было понять, кому достанется
   «+2», который я кладу.
   Я всегда снизу. Это единственная точка отсчёта, и она не двигается:
   иначе после каждой партии игрок заново ищет на столе себя. */
.arena {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto auto;
  grid-template-areas:
    ".    top   ."
    "left felt  right";
  align-items: center;
  justify-items: center;
  /* Стол собирается в КОМПАКТНЫЙ блок и центрируется целиком. Без этого
     верхний соперник прижимается к самому верху, центр остаётся посреди
     остатка, и между ними встаёт пустота в треть экрана — стол
     разваливается на три отдельные картинки. */
  align-content: center;
  row-gap: 10px;
  /* Перспектива на всю сцену: без неё поворот чужого веера превращается
     в простое сжатие по одной оси, и стол остаётся плоским. */
  perspective: 900px;
  perspective-origin: 50% 38%;
}
.side.top { grid-area: top; }
.side.left { grid-area: left; }
.side.right { grid-area: right; }
.felt { grid-area: felt; }
/* Пустая сторона не занимает места: за столом на двоих центр обязан
   получить всю ширину, иначе колода и сброс слипаются. */
.side:empty { display: none; }

.seat {
  display: flex;
  align-items: center;
  gap: 6px;
  transform-style: preserve-3d;
}
/* Соперник сидит ЗА своими картами: аватар дальше от центра, карты
   ближе. Иначе рука соперника читается как лежащая перед нами. */
/* Все трое стоят столбиком: телефон узкий, и соперник, разложенный
   вширь, съедает центр. Аватар сверху, карты под ним. */
.seat { flex-direction: column-reverse; }

/* Аватар живёт и на главной, и за столом, поэтому правило у него ОДНО и
   не привязано к месту: два похожих кружка в разных файлах разъезжаются
   на первой же правке размера. */
.ava {
  position: relative;
  width: 44px; height: 44px;
  border-radius: var(--r-pill);
  background: var(--surface-raised);
  border: 1px solid var(--hairline);
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; font-weight: 700;
  flex: none;
}

.seat .who { width: 60px; text-align: center; flex: none; }
.seat .ava { width: 40px; height: 40px; margin: 0 auto 3px; font-size: 17px; }
/* Число карт — значком на аватаре, а не строкой под ним. Веер отвечает
   на «много или мало», значок — на «сколько именно», и «одна карта» надо
   видеть ТОЧНО: веер из семи от веера из восьми на глаз не отличается. */
.seat .count {
  position: absolute;
  right: -5px; bottom: -3px;
  min-width: 18px; height: 18px; padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--surface-overlay);
  border: 1px solid var(--hairline);
  color: var(--ink);
  font-size: 11px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.seat .name {
  font-size: 11px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.seat.turn .ava { border-color: var(--ink); box-shadow: 0 0 0 3px rgba(242, 244, 250, 0.14); }
/* Одна карта у соперника — аватар пульсирует: это надо видеть боковым
   зрением, не переводя взгляд на значок. */
.seat.alarm .ava { animation: pulse 1s ease-in-out infinite; border-color: var(--play-Y); }
.seat.alarm .count { background: var(--play-Y); color: #14161f; border-color: transparent; }
.seat.off { opacity: 0.45; }

/* ------------------------------------------------------ чужой веер */

/* Рука соперника — веер РУБАШЕК. Карта, которой не видно, всё равно
   остаётся картой: толщина чужого веера читается боковым зрением, а
   строка «7» требует прочитать её. */
.fan {
  position: relative;
  width: 96px;
  height: 62px;
  flex: none;
  transform-style: preserve-3d;
}
.held {
  position: absolute;
  left: 50%;
  bottom: 2px;
  width: 36px;
  margin-left: -18px;
  /* Веер раскладывается СДВИГОМ, а поворот только доворачивает карту.
     Чистый поворот вокруг точки под веером кладёт шесть рубашек почти
     одна на другую: наружу торчат только белые рамки, и рука соперника
     читается как гребёнка из полосок, а не как карты. Сдвиг раздвигает
     их на честную долю ширины, и каждая рубашка остаётся рубашкой. */
  transform: translateX(calc(var(--i) * 13px)) rotate(calc(var(--i) * 5deg));
}
.held > .card { width: 36px; height: 52px; --w: 36px; }

/* Наклон в перспективе. Соперник сидит с ДРУГОЙ стороны стола, и его
   карты обязаны быть повёрнуты к нему, а не к нам: плоский чужой веер
   читается как вторая своя рука. Боковые вдобавок развёрнуты на
   четверть — так их и держат, сидя сбоку. */
/* Наклон умеренный. Сильный поворот сжимает карту поперёк, и веер
   превращается в гребёнку из белых полосок — рука соперника перестаёт
   читаться как рука. Боковые довёрнуты НЕ ДО конца: чуть недокрученная
   карта остаётся узнаваемой картой, а не полоской. */
.seat.top .fan { transform: rotateX(34deg); }
/* Сдвиг идёт ПЕРВЫМ: приписанный после поворотов, он поехал бы вдоль
   повёрнутой оси и увёл бы веер не внутрь стола, а вбок и вниз. */
.seat.left .fan { transform: translateX(9px) rotateY(20deg) rotate(72deg); }
.seat.right .fan { transform: translateX(-9px) rotateY(-20deg) rotate(-72deg); }
/* Повёрнутый веер ЗАНИМАЕТ одно, а разметке отдаёт другое. Полями
   возвращаем разметке видимый размер: без этого боковой соперник
   выталкивает стол вбок, а горизонтальной прокрутки в приложении не
   бывает. Боковой веер вдобавок короче — отсюда своя ширина. */
.seat.left .fan, .seat.right .fan { width: 75px; margin: 10px -14px; }
.seat.left .fan .held, .seat.right .fan .held {
  transform: translateX(calc(var(--i) * 15px)) rotate(calc(var(--i) * 6deg));
}
.side.left, .side.right { width: 84px; }
/* Соперники сбоку садятся к ВЕРХНЕМУ краю стола, как за настоящим
   круглым столом: вплотную к сбросу им сидеть негде — там лежат карты. */
.side.left, .side.right { align-self: start; padding-top: 6px; }

/* ------------------------------------------------------------ центр */

.felt {
  position: relative;
  width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 22px;
}
/* САМА ПОВЕРХНОСТЬ СТОЛА — овал под центром, вокруг которого сидят
   игроки. Без неё соперники висят в пустоте по краям экрана, и стол
   распадается на четыре отдельные картинки: колода, сброс и две кучки
   рубашек. Овал, а не круг: круглый стол, увиденный не сверху, а с места
   игрока, и есть овал — та самая перспектива, ради которой всё
   остальное наклонено. */
.felt::before {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 368px; height: 356px;
  margin: -178px 0 0 -184px;
  border-radius: 50%;
  background:
    radial-gradient(115% 100% at 50% 14%,
      rgba(255, 255, 255, 0.055) 0%,
      rgba(255, 255, 255, 0.014) 58%,
      rgba(255, 255, 255, 0) 74%);
  box-shadow:
    inset 0 1px 0 rgba(242, 244, 250, 0.07),
    0 24px 60px rgba(0, 0, 0, 0.45);
  border: 1px solid rgba(242, 244, 250, 0.055);
  pointer-events: none;
}
/* Активный цвет — мягкое пятно света под сброшенной картой. */
.glow {
  position: absolute;
  left: 50%; top: 50%;
  margin: -105px 0 0 -105px;
  width: 210px; height: 210px;
  border-radius: 50%;
  background: var(--active, transparent);
  filter: blur(52px);
  opacity: 0.42;
  pointer-events: none;
}
/* Направление хода: кольцо со стрелкой вокруг центра. Очень тихое —
   его замечают, только когда «разворот» его переворачивает. */
.flow {
  position: absolute;
  left: 50%; top: 50%;
  margin: -124px 0 0 -151px;
  width: 302px; height: 248px;
  color: var(--ink-2);
  opacity: 0.4;
  pointer-events: none;
}
.flow[data-back] { transform: scaleX(-1); }

.pile { position: relative; z-index: 1; }
/* Сброс — стопка: под верхней картой видны углы предыдущих, каждая под
   своим углом. Одинокая карта посреди стола читается как первый ход,
   сколько бы их ни было сыграно. */
.thrown { position: relative; width: 82px; height: 118px; }
.thrown .buried, .thrown .discard-card { position: absolute; inset: 0; }
.buried > .card { width: 100%; height: 100%; --w: 82px; transform: rotate(var(--tilt, 0deg)); }
.deck-card, .discard-card { width: 82px; height: 118px; }
/* Размер задаётся ОБЁРТКЕ, а карта тянется на неё целиком. Без этой
   строки карта в центре сжимается до своего содержимого: у рубашки
   содержимого нет вовсе, и колода исчезает со стола, не оставив следа в
   консоли. */
.deck-card > .card, .discard-card > .card { width: 100%; height: 100%; --w: 82px; }
/* Брошенная карта лежит с наклоном. Наклон приходит из разметки и
   посчитан от uid карты: своё случайное число на каждой перерисовке
   дёргало бы сброс на месте. */
.discard-card > .card { transform: rotate(var(--tilt, 0deg)); }
.deck-left { text-align: center; font-size: 12px; color: var(--ink-3); margin-top: 6px; }

/* Колода — тоже кнопка, и самая частая за партию. Пока ход чужой, тянуть
   нельзя, и она обязана выглядеть выключенной: колода, которая всегда
   одинаковая, учит игрока, что нажатие иногда «не срабатывает». */
.deck-card.live { cursor: pointer; }
/* Кольцо — тем же способом, что у играбельной карты в руке: одна и та же
   светлая обводка значит «сюда можно нажать». Тенью на самой карте, а не
   рамкой на обёртке: так кольцо само следует скруглению карты, которое
   считается от её ширины и на разных размерах разное. */
.deck-card.live > .card { --ring: 0 0 0 2px var(--edge-strong); }
.deck-card.idle { opacity: 0.5; }

/* РУКА ПОМЕЩАЕТСЯ ЦЕЛИКОМ. ВСЕГДА.
 *
 * Раньше веер ехал в горизонтальную прокрутку: карт становилось больше
 * десятка, и половина руки уезжала за край экрана. Прокрутка вбок внизу
 * экрана — худший из возможных жестов на телефоне: палец там занят
 * игрой, а не листанием, и карты, которой не видно, для игрока не
 * существует. Поэтому теперь ширина веера подгоняется под экран, а не
 * экран под веер: шаг между картами считает layoutHand() в screens.js и
 * кладёт сюда как --step, а когда карт больше, чем влезает в ряд с
 * читаемым шагом, веер переносится во второй и третий ряд.
 *
 * Поля по бокам — не отступ ради красоты: карта, прижатая к краю экрана,
 * попадает под жест «назад» системы, и вместо хода игрок выходит из
 * приложения.
 */
.hand-wrap { padding: 6px 12px 4px; }
.hand {
  /* Значения по умолчанию — на случай, если разметка отрисовалась, а
     измерить ширину ещё нечем: один ряд, прежний шаг. */
  --slot-w: 74px;
  --step: 48px;
  /* Наезд ряда на ряд. 26 — не круглое число, а вычет: играбельная карта
     приподнята на 10, и наезд 34 съел бы её номинал ровно у верхнего
     ряда, то есть у той половины руки, которую и так видно хуже. */
  --row-lift: 26px;
  display: flex;
  flex-direction: column;
  min-height: 132px;
  justify-content: flex-end;
}
.hand-row {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  position: relative;
  z-index: 1;
}
/* Ряды наезжают друг на друга: два ряда в полную высоту съели бы стол.
   Нижний ряд лежит ПОВЕРХ верхнего — он ближе к пальцу. */
.hand-row + .hand-row { margin-top: calc(var(--row-lift) * -1); z-index: 2; }
.hand-row + .hand-row + .hand-row { z-index: 3; }
/* Перекрытие отрицательным полем. Карта при этом сохраняет свой размер:
   сжимать её нельзя, номинал перестанет читаться на скорости, — и
   ширина слота уменьшается только в вырожденном случае, когда рука не
   лезет даже в три ряда с минимальным шагом. */
.hand .slot {
  flex: 0 0 auto;
  width: var(--slot-w);
  margin-inline-end: calc(var(--step) - var(--slot-w));
  transition: transform 240ms ease, opacity 240ms ease;
}
.hand-row .slot:last-child { margin-inline-end: 0; }
.hand .card {
  width: 100%;
  height: calc(var(--slot-w) * 1.46);
  /* Вся геометрия карты — рамка, скругление, знак, номинал — считается
     от --w. Без этой строки карта, ужатая в вырожденном случае, осталась
     бы с рамкой и знаком от полноразмерной. */
  --w: var(--slot-w);
  transition: filter 240ms ease;
}
/* Играбельные приподняты и в полной яркости, неиграбельные опущены и
   приглушены. Это главный способ учить правилам без единого слова. */
/* Порядок наложения — ЕСТЕСТВЕННЫЙ: каждая следующая карта лежит поверх
   предыдущей, как в веере на руке. Раньше играбельные поднимались над
   всеми, и в плотном веере приглушённая карта между двумя играбельными
   пропадала ЦЕЛИКОМ — не «читалась хуже», а исчезала с экрана вместе со
   своей зоной касания. При естественном порядке у каждой карты открыта
   ровно одна и та же полоска слева, и не пропадает ни одна.
   Играбельность при этом видна не порядком, а подъёмом и светлым краем —
   а левая кромка карты видна всегда. */
/* Приглушение — ЗАТЕМНЕНИЕМ, а не прозрачностью. Полупрозрачная карта в
   плотном веере просвечивает соседей насквозь: под красной семёркой
   проступает синяя двойка, и вместо руки игрок видит витраж. Затемнённая
   карта остаётся непрозрачным предметом. */
.hand .slot.dim { transform: translateY(10px); }
.hand .slot.dim .card { filter: brightness(0.5) saturate(0.75); }
.hand .slot.up { transform: translateY(-10px); }
/* Сыгранная карта уходит поверх всего: её анимацию не должен перекрывать
   сосед. Слоты — флекс-элементы, поэтому z-index здесь работает и без
   position. */
.hand .slot.picked { transform: translateY(-26px) scale(1.06); z-index: 5; }
/* Карта, которую сейчас можно положить, — тоже кнопка: у неё светлый
   край. Подъёма мало, когда карт много и они перекрывают друг друга:
   в плотном веере глаз ловит именно обводку, а кромка видна у каждой
   карты. */
.hand .slot.up .card { --ring: 0 0 0 2px var(--edge-strong); }
.hand .slot.picked .card { --ring: 0 0 0 2px var(--ink); }

.actions {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 10px; padding: 8px 4px 0;
}
/* Кнопки хода тянутся, «сдаться» — нет. Растянутая на всю ширину
   кнопка выхода читается как ГЛАВНОЕ действие экрана, а главное
   действие здесь — положить карту. */
.actions .btn { flex: 1; }
.actions .btn.quiet { flex: 0 0 auto; min-height: 38px; padding: 0 16px; font-weight: 500; }
/* Кнопка последней карты появляется только когда карт две — огромная,
   промазать по ней невозможно. */
.lastcall {
  background: var(--play-Y);
  color: #14161f;
  min-height: 60px;
  font-size: 18px;
  animation: pulse 0.9s ease-in-out infinite;
}

.banner {
  /* Разрыв связи — плашка, а не модальное окно: игрок обязан видеть стол. */
  position: fixed; left: 0; right: 0; top: 0;
  padding: calc(var(--top-guard) - 28px) 12px 8px;
  background: rgba(21, 26, 43, 0.94);
  color: var(--ink-2);
  font-size: 13px;
  text-align: center;
  z-index: 20;
}

/* ------------------------------------------------- выбор цвета и шторка */

.sheet {
  position: fixed; inset: 0;
  background: rgba(10, 12, 22, 0.7);
  display: flex; align-items: flex-end;
  z-index: 30;
}
.sheet-body {
  width: 100%;
  background: var(--surface-overlay);
  border-radius: var(--r-panel) var(--r-panel) 0 0;
  padding: 20px 16px calc(20px + var(--safe-bottom));
  animation: rise 200ms ease;
}
@keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }

/* Четыре крупных сектора по четверти экрана, со знаком внутри. Текста
   нет вовсе — он здесь не нужен ни на одном языке. */
.colors { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.colors button {
  height: 108px; border: 0; border-radius: var(--r-panel);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  /* Край есть и здесь: четыре цветных прямоугольника без обводки на
     светлом столе читаются как рисунок, а не как четыре кнопки. */
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.45), 0 6px 16px rgba(0, 0, 0, 0.4);
}
/* Знак масти на секторе выбора цвета — тот же и того же смысла: без
   него четыре сектора отличаются ровно одним цветом. */
.colors .mark { --mark: 26px; }

/* ------------------------------------------------------- как играть */

/* Экран правил — список «карта и что она делает». Карта здесь мелкая, но
   настоящая: тот же `Cards.render`, что и за столом, только уже. Ради
   этого все размеры карты и считаются от одной переменной. */
.rules .panel { margin-bottom: 12px; }
.rules .rule .grow { font-size: 14px; line-height: 1.45; }
.rules .card { width: 46px; height: 67px; --w: 46px; flex: none; }

/* Пример хода: сверху лежит одна карта, справа — те, что на неё годятся.
   Разделитель — волосяная линия, а не стрелка: стрелка на арабском и
   фарси показывала бы в обратную сторону, а линия направления не имеет. */
.rules .demo { display: flex; align-items: center; gap: 12px; }
.rules .demo .card { width: 58px; height: 84px; --w: 58px; }
.rules .demo .sep { width: 1px; align-self: stretch; background: var(--hairline); }
.rules .demo .fits { display: flex; gap: 8px; }

.timerbar { height: 3px; background: var(--hairline); border-radius: var(--r-pill); overflow: hidden; }
/* Полоса времени — не белая: белый здесь самый громкий цвет во всём
   интерфейсе, и полоса перетягивала внимание с карт на себя. */
.timerbar i { display: block; height: 100%; background: var(--ink-3); transform-origin: left; }

/* --------------------------------------------------------- итоги */

.result .who { font-family: var(--font-num); font-size: 40px; font-weight: 800; }
.result .line { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--hairline); }
.result .pts { font-family: var(--font-num); font-weight: 800; }
/* Выигрыш и проигрыш — единственный случай, когда игровые цвета выходят
   за пределы карт. Это оправдано: тот же красный и зелёный, уже
   выученные игроком, теперь значат «−» и «+». */
.pos { color: var(--play-G); }
.neg { color: var(--play-R); }

.board-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--hairline); }
.board-row .place { width: 28px; color: var(--ink-3); font-weight: 700; }
.board-row.me { color: var(--ink); background: rgba(242, 244, 250, 0.05); border-radius: var(--r-control); padding: 12px 8px; }

.tabs { display: flex; gap: 8px; margin-bottom: 12px; }
.tabs button {
  flex: 1; min-height: 40px; border-radius: var(--r-pill);
  border: 1px solid var(--edge); background: transparent; color: var(--ink-2); cursor: pointer;
}
.tabs button[aria-selected="true"] { background: var(--surface-raised); color: var(--ink); border-color: var(--edge-strong); }

.toast {
  position: fixed; left: 16px; right: 16px; bottom: calc(20px + var(--safe-bottom));
  background: var(--surface-overlay);
  border: 1px solid var(--line-active);
  border-radius: var(--r-control);
  padding: 14px 16px;
  z-index: 40;
  animation: rise 160ms ease;
}

.link-bar {
  position: fixed; left: 16px; right: 16px; bottom: calc(20px + var(--safe-bottom));
  background: var(--surface-raised); border: 1px solid var(--edge);
  border-radius: var(--r-control); padding: 12px 14px; z-index: 40;
  font-family: var(--font-num); letter-spacing: 0.08em;
}

.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ------------------------------------------------------ полёт карт */

/* Слой поверх стола, в котором живут летящие карты. Отдельный слой, а не
   позиционирование внутри стола, по двум причинам: у руки и у центра
   разные контексты наложения (z-index веера, свечение под сбросом), и
   карта, пущенная изнутри одного из них, ныряет под другой на середине
   пути. Кроме того, слой ничего не ловит — pointer-events выключены
   целиком, иначе призрак перехватил бы тап по следующей карте. */
.flight {
  position: fixed;
  inset: 0;
  z-index: 25;
  pointer-events: none;
}

/* Каждая карта позиционируется сама: слой ей нужен только как порядок
   наложения. Размер и место ставит JS — по ЦЕЛИ полёта, а не по старту.
   z-index здесь ниже шторки выбора цвета (30) и подсказок (40): цвет
   выбирают поверх летящей карты, а не наоборот. */
.flight-card {
  position: fixed;
  will-change: transform, opacity;
}
/* --w ставит JS по ширине коробки: размеры внутри карты считаются от
   него, и своё число здесь стало бы пятым размером карты. */
.flight-card > .card { width: 100%; height: 100%; }

/* Доступность важнее украшений: движение отключается целиком. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

/* Низкий экран — а это половина Android'ов с открытой клавиатурой и
   старые айфоны. Ужимается ЦЕНТР стола, а не рука: рука — это то, чем
   игрок ходит, и она обязана остаться целой и под пальцем. */
@media (max-height: 720px) {
  .table { padding-top: calc(var(--top-guard) - 10px); }
  .arena { row-gap: 4px; }
  .seat .ava { width: 34px; height: 34px; font-size: 15px; }
  .felt { gap: 16px; }
  .felt::before { width: 316px; height: 300px; margin: -150px 0 0 -158px; }
  .thrown, .deck-card, .discard-card { width: 70px; height: 101px; }
  /* Размер карты — это --w: от него считаются рамка, овал, скругление и
     знаки. Ужать обёртку и забыть про --w значит получить карту в
     мелком размере с рамкой от крупного. */
  .deck-card > .card, .discard-card > .card, .buried > .card { --w: 70px; }
}

/* Планшет и десктоп: центрированная колонка. Стол не растягивается —
   растянутый стол ломает расстояние между рукой и центром, а оно и есть
   ритм игры. */
@media (min-width: 460px) {
  .screen, .table { max-width: 430px; margin: 0 auto; }
}
