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

:root {
  /* ---- Вид по умолчанию: бумага. Светлый, почти белый. ---- */

  /* Поверхности. Между соседними уровнями шаг заметен глазом, иначе
     панель на панели читается как одно пятно. */
  --surface-deep: #EFF2F8;
  --surface-table: #E4E9F4;
  --surface-raised: #FFFFFF;
  --surface-overlay: #FFFFFF;
  /* Свет лампы над столом. Тем же светом подсвечивается верх страницы на
     остальных экранах. */
  --lamp-hot: rgba(255, 255, 255, 0.9);
  --lamp-warm: rgba(255, 255, 255, 0.35);

  /* Текст */
  --ink: #171A24;
  --ink-2: #5C6379;
  --ink-3: #949BB0;
  /* Текст НА заливке чернилами. Отдельная переменная, а не «просто
     белый»: у ночного вида заливка светлая, и текст на ней тёмный. */
  --on-ink: #FFFFFF;

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

  /* Край нажимаемого. Не украшение: это единственное, чем кнопка
     отличается от надписи, а живая колода — от лежащей картинки. */
  --edge: rgba(23, 26, 36, 0.26);
  --edge-strong: rgba(23, 26, 36, 0.55);

  /* Предметы и свет. Рубашка карты ТЁМНАЯ при любом виде стола. */
  --card-edge: #D9DEEA;
  --shadow-card: rgba(23, 30, 58, 0.22);
  --shadow-panel: rgba(23, 30, 58, 0.12);
  --felt-sheen: rgba(255, 255, 255, 0.55);
  --felt-line: rgba(23, 26, 36, 0.07);
  --veil: rgba(23, 26, 36, 0.05);
  --scrim: rgba(18, 20, 30, 0.45);
  --tint: rgba(46, 123, 255, 0.10);
  /* Узор фона: те же четыре знака, что стоят на картах. Фон не
     выдумывает своего языка, а бледный он намеренно — спорить с картами
     ему нечем, он ФОН. Лежит слоем в том же background, что и свет
     лампы: отдельным элементом под страницей его перекрывала бы первая
     же непрозрачная заливка. */
  --pattern: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='152' height='152' viewBox='0 0 152 152'%3E%3Cg fill-opacity='0.20'%3E%3Ccircle cx='26' cy='28' r='9' fill='%23F0384E'/%3E%3Crect x='100' y='16' width='18' height='18' rx='3' fill='%2321C776'/%3E%3Cpath d='M32 88l10 18H22z' fill='%23FFC531'/%3E%3Crect x='102' y='96' width='16' height='16' rx='3' fill='%232E7BFF' transform='rotate(45 110 104)'/%3E%3C/g%3E%3C/svg%3E");
  /* Насколько гасить неиграбельную карту. На светлом столе та же
     полупрозрачность выцветает почти в бумагу, и рука превращается в
     кашу — поэтому число тоже принадлежит виду стола. */
  --dim-bright: 0.94;
  --dim-fade: 0.9;
  --dim-grey: 0.35;
  --glow-alpha: 0.2;

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

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

  --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);
}

/* ---- Мята: светлый, холодный, чуть зеленее бумаги ---- */
[data-table="mint"] {
  --surface-deep: #E8F5EE;
  --surface-table: #D8ECE1;
  --surface-raised: #FFFFFF;
  --surface-overlay: #FFFFFF;
  --ink: #14211B;
  --ink-2: #4E6659;
  --ink-3: #8AA396;
  --hairline: rgba(20, 33, 27, 0.12);
  --line-active: rgba(20, 33, 27, 0.32);
  --edge: rgba(20, 33, 27, 0.26);
  --edge-strong: rgba(20, 33, 27, 0.55);
  --card-edge: #CFE4D8;
  --shadow-card: rgba(16, 48, 32, 0.22);
  --shadow-panel: rgba(16, 48, 32, 0.12);
  --felt-line: rgba(20, 33, 27, 0.07);
  --veil: rgba(20, 33, 27, 0.05);
  --tint: rgba(33, 199, 118, 0.14);
  --ice: #0E8FA8;
}

/* ---- Песок: тёплый, бумажный, приятный вечером ---- */
[data-table="sand"] {
  --surface-deep: #FAF2E4;
  --surface-table: #F2E5CE;
  --surface-raised: #FFFDF8;
  --surface-overlay: #FFFDF8;
  --ink: #241C12;
  --ink-2: #6B5B45;
  --ink-3: #A9977C;
  --hairline: rgba(36, 28, 18, 0.12);
  --line-active: rgba(36, 28, 18, 0.32);
  --edge: rgba(36, 28, 18, 0.26);
  --edge-strong: rgba(36, 28, 18, 0.55);
  --card-edge: #E7D8BC;
  --shadow-card: rgba(60, 40, 12, 0.22);
  --shadow-panel: rgba(60, 40, 12, 0.12);
  --felt-line: rgba(36, 28, 18, 0.07);
  --veil: rgba(36, 28, 18, 0.05);
  --tint: rgba(255, 197, 49, 0.18);
  --ice: #1B87A8;
}

/* ---- Ночь: прежний освещённый стол, для тех, кому светлый режет глаз ---- */
[data-table="night"] {
  --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;
  --on-ink: #12141D;
  --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);
  --card-edge: #1B2036;
  --shadow-card: rgba(0, 0, 0, 0.5);
  --shadow-panel: rgba(0, 0, 0, 0.4);
  --felt-sheen: rgba(255, 255, 255, 0.055);
  --felt-line: rgba(242, 244, 250, 0.055);
  --veil: rgba(245, 247, 255, 0.06);
  --scrim: rgba(10, 12, 22, 0.7);
  --tint: rgba(120, 150, 255, 0.12);
  /* На тёмном столе тот же узор виден сильнее — краски бледнее. */
  --pattern: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='152' height='152' viewBox='0 0 152 152'%3E%3Cg fill-opacity='0.13'%3E%3Ccircle cx='26' cy='28' r='9' fill='%23F0384E'/%3E%3Crect x='100' y='16' width='18' height='18' rx='3' fill='%2321C776'/%3E%3Cpath d='M32 88l10 18H22z' fill='%23FFC531'/%3E%3Crect x='102' y='96' width='16' height='16' rx='3' fill='%232E7BFF' transform='rotate(45 110 104)'/%3E%3C/g%3E%3C/svg%3E");
  --dim-bright: 0.55;
  --dim-fade: 1;
  --dim-grey: 0.25;
  --glow-alpha: 0.42;
  --brass: #E8B04B;
  --ice: #7FE3FF;
}

* { 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:
    var(--pattern),
    radial-gradient(120% 40% at 50% 0%, var(--lamp-warm) 0%, var(--surface-deep) 70%);
  background-size: 152px 152px, cover;
  background-attachment: fixed, fixed;
  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; }

/* Число читается одинаково на всех шестнадцати языках и потому ВСЕГДА
   идёт слева направо, даже когда страница развёрнута целиком. Без
   изоляции арабский переставляет «312 / 500» в «500 / 312», а рейтинг
   −40 показывает как «40−»: цифры не переставляются, а знаки между ними
   считаются нейтральными и уезжают на другую сторону. */
.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  direction: ltr;
  unicode-bidi: isolate;
}

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

.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%, var(--surface-table) 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: var(--on-ink);
  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: var(--surface-raised); 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;
}

/* Поле ввода — единственное на всё приложение (код приглашения), и край
   у него по тому же правилу, что у кнопки: без линии это не поле, а
   пустое место. Шрифт цифровой и разрядка широкая — код диктуют голосом
   и переписывают руками. */
.field {
  min-height: 48px;
  padding: 0 14px;
  background: var(--surface-raised);
  border: 1px solid var(--edge);
  border-radius: var(--r-control);
  color: var(--ink);
  font-family: var(--font-num);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  min-width: 0;
}
.field:focus { 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; }

/* Плитка режима и кнопка его правил стоят В РЯД, а не одна внутри
   другой: кнопка внутри кнопки не нажимается ни в одном браузере, и
   игрок, целившийся в «?», оказался бы в очереди на партию.
   Знак вопроса стоит только у режимов, чьи правила отличаются от
   обычного стола: у остальных он вёл бы к одному и тому же тексту. */
.tile-row { display: flex; align-items: stretch; gap: 8px; }
.tile-row .tile { flex: 1; min-width: 0; }
.btn.info {
  flex: none;
  width: 48px;
  padding: 0;
  background: transparent;
  color: var(--ink-2);
  border: 1px solid var(--edge);
  border-radius: var(--r-panel);
  font-family: var(--font-num);
  font-size: 20px;
}

/* ------------------------------------------------- карточка игрока */

/* Первый экран — лицо игры, и на нём стоит человек: персонаж, имя с
   платформы, уровень и счёт в этой игре.

   Цвет здесь по-прежнему принадлежит картам. Карточка монохромная, и
   хроматических пятен на ней ровно три, каждое со своим смыслом:
   четырёхцветная лента бренда сверху (та же, что на заставке и на
   рубашке колоды), 💎 цвета валюты и точки последних партий, где
   зелёное и красное уже выучены игроком на экране итогов. */
.me {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--surface-overlay) 0%, var(--surface-raised) 52%);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  padding: 20px 16px 16px;
  margin-bottom: 16px;
}
.me::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg,
    var(--play-R) 0 25%, var(--play-Y) 0 50%, var(--play-G) 0 75%, var(--play-B) 0);
}

.me-head { display: flex; align-items: center; gap: 14px; }
.me-head .grow { flex: 1; min-width: 0; }

/* Имя длиннее строки обрезается многоточием, а не переносится: карточка
   обязана иметь одну и ту же высоту у всех, иначе кнопка «играть»
   прыгает по экрану от человека к человеку. */
.me-name {
  font-size: 22px; font-weight: 800; letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.me-sub {
  color: var(--ink-3); font-size: 12px; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Лицо: картинка персонажа, его символ или монограмма. Круг с плотной
   тёмной подложкой — портрет читается на любом фоне, а прозрачный PNG
   не растворяется в карточке. */
.ava-xl {
  position: relative;
  width: 64px; height: 64px; flex: none;
  border-radius: var(--r-pill);
  background: var(--surface-deep);
  border: 1px solid var(--line-active);
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.ava-xl .glyph { font-size: 30px; line-height: 1; }
.ava-xl .mono {
  font-family: var(--font-num); font-weight: 800; font-size: 26px; color: var(--ink-2);
}
.ava-xl .pic { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Персонаж ЗНАЧКОМ В УГЛУ, когда лицом стоит фотография из Telegram.
   Эмодзи, закрывающий лицо, читается как мусор поверх головы, а не как
   украшение — с этого и началась переделка. Значок вылезает за круг
   аватара, поэтому у него своё переполнение. */
.ava-xl { overflow: visible; }
.ava-xl .glyph, .ava-xl .mono, .ava-xl .pic { border-radius: var(--r-pill); }
.char {
  position: absolute;
  bottom: -3px; inset-inline-end: -5px;
  width: 24px; height: 24px;
  border-radius: var(--r-pill);
  background: var(--surface-overlay);
  border: 2px solid var(--surface-deep);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1;
  overflow: hidden;
  z-index: 2;
}
.char img { width: 100%; height: 100%; object-fit: cover; }

/* Валюта — единственное металлическое и холодное пятно на карточке. */
.gem { color: var(--ice); font-weight: 800; font-size: 15px; white-space: nowrap; }

/* Уровень платформенный, а не игровой: он один на все игры, и считает
   его Core. Полоса рисуется из двух присланных чисел — своей формулы
   уровней в приложении нет и быть не может. */
.lvl { margin-top: 16px; }
.lvl-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
.lvl-badge { font-weight: 800; font-size: 15px; flex: none; }
.lvl-name {
  color: var(--ink-2); font-size: 13px; flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lvl-xp { color: var(--ink-3); font-size: 12px; flex: none; }

.bar { height: 4px; background: var(--hairline); border-radius: var(--r-pill); overflow: hidden; }
.bar i {
  display: block; height: 100%;
  background: var(--ink);
  transform-origin: left;
  transition: transform 420ms ease;
}
/* В арабском и фарси страница развёрнута целиком, и полоса обязана
   расти от правого края — иначе прогресс идёт справа налево задом
   наперёд. */
[dir="rtl"] .bar i { transform-origin: right; }

/* Счёт в этой игре: три числа и ни одного лишнего слова. Разделяются
   линией, а не фоном: фоновая плитка на каждое число превратила бы
   карточку в приборную панель. */
.stats {
  display: grid; grid-template-columns: repeat(3, 1fr);
  margin-top: 18px; padding-top: 14px;
  border-top: 1px solid var(--hairline);
}
.stat { text-align: center; padding: 0 4px; }
.stat + .stat { border-inline-start: 1px solid var(--hairline); }
.stat .v { font-size: 24px; font-weight: 800; line-height: 1.1; }
.stat .k {
  color: var(--ink-3); font-size: 11px; margin-top: 3px;
  text-transform: uppercase; letter-spacing: 0.06em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Место в таблице и доля побед — подпись к числу, а не второе число:
   поэтому мельче и тише. */
.stat .r { color: var(--ink-2); font-size: 12px; margin-top: 4px; }

/* Последние партии. Победа — залитая точка, поражение — пустое кольцо.
   Форма важнее цвета: игра на различении красного и зелёного теряет
   каждого двенадцатого мужчину, и полоса обязана читаться без цвета
   так же, как масти читаются по знаку. */
.streak { display: flex; justify-content: center; gap: 7px; margin-top: 14px; }
.streak i { width: 9px; height: 9px; border-radius: 50%; display: block; }
.streak i.won { background: var(--play-G); }
.streak i.lost { border: 2px solid var(--play-R); }

/* Приглашение сыграть первую партию. Оно стоит на месте полосы
   результатов: у нового игрока её нет, и пустота на её месте выглядела
   бы поломкой. */
.me .hint { color: var(--ink-3); font-size: 13px; text-align: center; margin-top: 14px; }

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

/* Карта нарисована как настоящая: БЕЛАЯ РАМКА снаружи, цветное поле
   внутри, белый овал наискось и крупный знак поверх него. Это не
   украшательство — рамка отделяет карту от карты в веере, где они лежат
   внахлёст, а овал даёт знаку белую подложку, на которой одинаково
   читаются и жёлтый, и синий.
 *
   ВСЕ РАЗМЕРЫ СЧИТАЮТСЯ ОТ --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-color: var(--surface-deep);
  background-image:
    var(--pattern),
    radial-gradient(115% 62% at 50% -12%,
      var(--lamp-hot) 0%,
      var(--lamp-warm) 34%,
      var(--surface-table) 66%,
      var(--surface-deep) 100%);
  background-size: 152px 152px, cover;
}

/* Тонкая рамка по краю стола в активном цвете — второй, боковой способ
   его показать. Первый — свечение под сбросом. Подписи нет намеренно. */
.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; overflow: visible; }
/* Лицо соперника собрано теми же тремя слоями, что и своё: снизу
   монограмма (она есть всегда и не зависит ни от сети, ни от настроек
   приватности), поверх — фотография из подписанной initData, сбоку —
   персонаж из Core. */
.seat .ava .mono { font-family: var(--font-num); font-weight: 800; color: var(--ink-2); }
.seat .ava .pic {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border-radius: var(--r-pill);
  object-fit: cover;
}
.seat .ava .char {
  width: 18px; height: 18px;
  font-size: 10px;
  bottom: -2px; inset-inline-start: -4px; inset-inline-end: auto;
  border-width: 1px;
}
/* Число карт — значком на аватаре, а не строкой под ним. Веер отвечает
   на «много или мало», значок — на «сколько именно», и «одна карта» надо
   видеть ТОЧНО: веер из семи от веера из восьми на глаз не отличается. */
.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 var(--veil); }
/* Одна карта у соперника — аватар пульсирует: это надо видеть боковым
   зрением, не переводя взгляд на значок. */
.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));
}

/* ------------------------------------------------- рука партнёра */

/* За партнёрским столом рука напарника лежит ОТКРЫТОЙ — в этом весь
   режим. Отсюда два отличия от чужого веера: карты в нём надо ЧИТАТЬ,
   поэтому наклон меньше (сильно наклонённый номинал не читается), и
   их больше, поэтому шаг веера теснее — открытая рука показывается
   целиком, а обрезанная врала бы о том, чем партнёр может сходить. */
.fan.open { width: 150px; }
.fan.open .held {
  transform: translateX(calc(var(--i) * 11px)) rotate(calc(var(--i) * 3deg));
}
.seat.top .fan.open { transform: rotateX(14deg); }
.seat.left .fan.open { transform: translateX(9px) rotateY(10deg) rotate(60deg); }
.seat.right .fan.open { transform: translateX(-9px) rotateY(-10deg) rotate(-60deg); }
.seat.left .fan.open, .seat.right .fan.open { width: 96px; }
/* Партнёр помечен и БЕЗ открытых рук: команда без них тоже бывает, а
   стол, на котором не видно, кто свой, — это стол, где «+2» кладут в
   напарника. Пометка светлая, а не цветная: цвет за столом принадлежит
   только картам. */
.seat.mate .ava { border-color: var(--ink-2); border-style: dashed; }

.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%,
      var(--felt-sheen) 0%,
      transparent 74%);
  box-shadow:
    inset 0 1px 0 var(--felt-line),
    0 24px 60px var(--shadow-panel);
  border: 1px solid var(--felt-line);
  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: var(--glow-alpha);
  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); }
/* Гасится карта ЯРКОСТЬЮ, и величина принадлежит виду стола: на
   светлой бумаге brightness(0.5) даёт грязное пятно вместо приглушённой
   карты, а на тёмном столе меньшего гашения не видно вовсе. */
/* Гасится карта ЯРКОСТЬЮ и цветом, а не прозрачностью: полупрозрачная
   карта в плотном веере просвечивает соседей насквозь, а на светлой
   бумаге выцветает в неё целиком. Величины принадлежат виду стола: одно
   и то же число даёт на бумаге грязь, а на ночном столе — незаметно. */
.hand .slot.dim .card {
  filter: brightness(var(--dim-bright)) saturate(var(--dim-grey));
  opacity: var(--dim-fade);
}
.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; }
/* Полоса ловли. Отдельно от ряда кнопок намеренно: у игрока с двумя
   картами ряд занимает его собственная заявка, и в общем ряду ловля
   проигрывала бы ей — то есть пропадала в самый интересный момент
   партии. Красный здесь не украшение: это единственное действие в игре,
   которое отнимает у другого человека, и выглядеть оно обязано так. */
.catchbar { padding: 0 4px 8px; }
.btn.catch {
  width: 100%;
  position: relative;
  overflow: hidden;
  background: var(--play-R);
  color: #FAFAF4;
  min-height: 54px;
  gap: 12px;
  animation: pulse 0.9s ease-in-out infinite;
}
.btn.catch .grow { flex: 1; text-align: start; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.btn.catch .gavel { width: 26px; height: 26px; flex: none; }
.btn.catch .pill {
  background: rgba(0, 0, 0, 0.22);
  border-radius: var(--r-pill);
  padding: 3px 10px;
  font-weight: 800;
}
/* Фитиль: сколько осталось до конца окна. Он и есть причина спешить. */
.btn.catch .fuse {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 4px;
  background: rgba(255, 255, 255, 0.85);
  transform-origin: left;
}
.gavel svg { width: 100%; height: 100%; display: block; }

/* Место молчуна становится кнопкой: по нему просто бьют пальцем. */
.seat.catchable { cursor: pointer; }
.seat.catchable .ava { box-shadow: 0 0 0 2px var(--play-R); }
/* Молоток висит НА ЛИЦЕ молчуна, с той стороны, где нет счётчика карт:
   бить надо по человеку, а не по столу рядом с ним. */
.seat .gavel {
  position: absolute;
  left: -6px; top: -4px;
  width: 22px; height: 22px;
  padding: 4px;
  border-radius: var(--r-pill);
  background: var(--play-R);
  color: #FAFAF4;
  z-index: 3;
  animation: pulse 0.9s ease-in-out infinite;
}

/* Объявил — вместо кнопки отметка: нажимать второй раз нечего. */
.declared {
  flex: 1;
  min-height: 54px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-control);
  border: 1px solid var(--play-Y);
  color: var(--play-Y);
  font-weight: 700;
}

/* Кнопка последней карты появляется только когда карт две — огромная,
   промазать по ней невозможно. */
.lastcall {
  background: var(--play-Y);
  color: #14161f;
  min-height: 60px;
  font-size: 18px;
}
/* Спокойная, пока карт две: время ещё есть. Осталась одна и объявления
   не было — счёт пошёл на секунды, и кнопка это показывает. */
.lastcall.now { 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: var(--surface-raised);
  color: var(--ink-2);
  font-size: 13px;
  text-align: center;
  z-index: 20;
}

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

.sheet {
  position: fixed; inset: 0;
  background: var(--scrim);
  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; }

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

/* Образец показывает НАСТОЯЩИЙ стол с настоящей картой, а не цветной
   квадратик: выбирать фон по квадратику — это выбирать вслепую. Каждая
   плитка несёт свой data-table, поэтому переменные внутри неё принимают
   значения того вида, который она показывает. */
.styles { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.style {
  border: 2px solid transparent;
  border-radius: var(--r-panel);
  padding: 0;
  background: none;
  cursor: pointer;
}
.style[aria-selected="true"] { border-color: var(--ink); }
.style-sheet {
  position: relative;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 96px;
  border-radius: calc(var(--r-panel) - 4px);
  background-color: var(--surface-deep);
  /* Тот же узор и той же густоты, что на столе: иначе образец обещал бы
     одно, а стол показывал другое. */
  background-image: var(--pattern);
  background-size: 152px 152px;
  overflow: hidden;
}
.style-sheet .card { width: 40px; height: 58px; --w: 40px; flex: none; }

/* ---------------------------------------------- удар за молчание */

/* Молоток живёт в том же слое, что и летящие карты: он такая же
   реакция на событие партии и так же обязан переживать перерисовку. */
.fx-hammer {
  position: fixed;
  width: 84px; height: 84px;
  margin: -78px 0 0 -14px;
  transform-origin: 60% 90%;
  animation: strike 620ms cubic-bezier(0.3, 0, 0.2, 1) 1;
}
.fx-hammer svg { width: 100%; height: 100%; display: block; }
.fx-hit {
  position: fixed;
  border-radius: var(--r-panel);
  box-shadow: 0 0 0 3px var(--play-R);
  opacity: 0;
  animation: hit 520ms ease 180ms 1;
}
@keyframes strike {
  0% { transform: rotate(-72deg) translateY(-14px); opacity: 0; }
  22% { opacity: 1; }
  45% { transform: rotate(16deg); }
  60% { transform: rotate(4deg); }
  100% { transform: rotate(-30deg) translateY(-10px); opacity: 0; }
}
@keyframes hit {
  0% { opacity: 0.9; transform: scale(1); }
  40% { opacity: 0.5; transform: scale(1.06); }
  100% { opacity: 0; transform: scale(1); }
}

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

.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; }
/* Своя команда в итогах выделена так же, как своя строка в таблице
   лидеров: игрок ищет глазами себя, а за партнёрским столом «себя» —
   это двое. */
.result .line.me { color: var(--ink); font-weight: 700; }
/* Выигрыш и проигрыш — единственный случай, когда игровые цвета выходят
   за пределы карт. Это оправдано: тот же красный и зелёный, уже
   выученные игроком, теперь значат «−» и «+». */
.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: var(--veil); 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; }
}

/* ------------------------------------------------------------ деньги */

/* Деньги — единственное, кроме карт, чему разрешён свой оттенок, и он
   металлический, а не игровой: латунь не спутать с мастью. 💎 платформы
   рядом остаются ледяными — две валюты обязаны отличаться на взгляд, не
   на чтение. */
.purse { font-size: 20px; font-weight: 800; color: var(--brass); }
.purse .num, .pot .num { font-family: var(--font-num); }

/* Кнопка дневных денег узкая: это не главное действие экрана. Главное —
   «играть», и оно остаётся самым большим. */
.btn.daily {
  min-height: 40px;
  padding: 0 14px;
  width: auto;
  background: transparent;
  color: var(--brass);
  border: 1px solid var(--brass);
}
.btn.daily .num { font-weight: 800; }
.daily-done { text-align: end; }

/* Ставки. Ряд «фишек»: выбранная поднята уровнем поверхности, недоступная
   погашена, но НЕ спрятана — исчезнувшая кнопка читается как поломка. */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
.chips .chip {
  flex: 1 0 auto;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-2);
  border: 1px solid var(--hairline);
}
.chips .chip[aria-selected="true"] {
  background: var(--surface-raised);
  color: var(--brass);
  border-color: var(--brass);
}
.chips .chip.off { opacity: 0.35; }

/* Банк висит над столом и не двигается: он один на партию. */
/* Банк висит НАД сукном и не двигается: он один на партию. Цвета берёт
   у стола, а не свои: сукно у игрока может быть светлым, и тёмная
   плашка на нём выглядела бы дыркой. */
.pot {
  position: absolute;
  top: 2px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  padding: 3px 12px;
  border-radius: var(--r-pill);
  background: var(--surface-raised);
  border: 1px solid var(--hairline);
  color: var(--brass);
  font-weight: 700;
  font-size: 13px;
}

/* Сколько стоила партия — прибавкой, крупно, сразу под исходом. */
.result .who-money {
  font-family: var(--font-num);
  font-size: 22px;
  font-weight: 800;
  margin-top: 4px;
}
