:root {
  --felt: #0b5c3a;
  --felt-dark: #073d27;
  --gold: #e0b04c;
  --panel: #1c1f26;
  --panel2: #262a33;
  --text: #f1f1f1;
  --muted: #9aa0aa;
  --danger: #d9534f;
  --accent: #3d8bfd;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: radial-gradient(circle at 50% 0%, #22262f, #14161b 70%);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  overflow: auto;
}
body.ingame { overflow: hidden; }
body.ingame .top { padding: .35em 1em; gap: .55em; }
body.ingame .top h1 { font-size: 1em; }
.table-meta { color: var(--muted); font-size: .9em; white-space: nowrap; }

#app {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}
body.ingame #app {
  height: 100%;
  min-height: 0;
  overflow: hidden;
  background: transparent;
}
body.ingame .table-page,
body.ingame .table-stage { background: transparent; }

button {
  font: inherit;
  cursor: pointer;
  border: none;
  border-radius: 8px;
  padding: .55em 1.1em;
  background: var(--panel2);
  color: var(--text);
}
button:disabled { opacity: .4; cursor: default; }
button.primary { background: var(--accent); font-weight: 600; }
button.danger { background: var(--danger); }
button.gold { background: var(--gold); color: #221; font-weight: 700; }
input {
  font: inherit;
  background: var(--panel2);
  border: 1px solid #3a3f4a;
  color: var(--text);
  border-radius: 8px;
  padding: .5em .7em;
}

.top {
  display: flex;
  align-items: center;
  gap: .8em;
  padding: .7em 1.2em;
  background: var(--panel);
  border-bottom: 1px solid #000;
  flex-shrink: 0;
}
.top h1 { font-size: 1.2em; margin: 0; }
.top .grow { flex: 1; }
.top a { color: var(--muted); text-decoration: none; }
.to-monopoly-btn {
  background: var(--panel2);
  color: var(--text) !important;
  padding: .5em 1em;
  border-radius: 8px;
  font-weight: 600;
  white-space: nowrap;
}
.to-monopoly-btn:hover { background: var(--accent); }
.volume-ctl { display: inline-flex; align-items: center; gap: 4px; }
/* A fully custom track/fill/thumb (see sound.js's volumeControlHtml doc comment for why this
   replaced a styled native <input type=range>) — all three positioned off the same percentage in
   the same render, so the thumb can never visually disagree with the actual volume. */
.vol-track { position: relative; width: 70px; height: 4px; border-radius: 999px; background: var(--panel2); cursor: pointer; touch-action: none; }
.vol-fill { position: absolute; inset: 0 auto 0 0; border-radius: 999px; background: var(--accent); pointer-events: none; }
.vol-thumb {
  position: absolute; top: 50%; width: 13px; height: 13px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--panel); transform: translate(-50%, -50%);
  pointer-events: none;
}
.vol-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.center-screen {
  min-height: calc(100vh - 60px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1em;
}

.card-box {
  background: var(--panel);
  border-radius: 14px;
  padding: 1.4em;
  width: min(360px, 92vw);
  box-shadow: 0 10px 30px rgba(0,0,0,.4);
}
.card-box h2 { margin-top: 0; }
/* align-items defaults to stretch, which let the bold <b>nickname</b> flex item's line box render
   at a different vertical position than its plain-text siblings (the label, the "— hand (amount)"
   trailing text) — "никнейм не отцентрирован". Explicit center keeps every item's text on the same
   baseline-ish row regardless of which one happens to be a real element vs an anonymous text node. */
.highlight-row { display: flex; align-items: center; justify-content: space-between; gap: .6em; padding: .3em 0; font-size: .92em; }
.highlight-row span { color: var(--muted); }
.highlight-row b { color: var(--gold); }
.card-box form, .card-box .row { display: flex; flex-direction: column; gap: .6em; }
.card-box .row-inline { display: flex; gap: .6em; }
.card-box .row-inline input { flex: 1; width: 0; }

.preset-row { display: flex; gap: .5em; margin-bottom: 1em; }
.preset-btn {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: .15em;
  background: var(--panel2); border: 1px solid #3a3f4a; border-radius: 10px;
  padding: .55em .3em; cursor: pointer; color: var(--text, #eee);
}
.preset-btn:hover { border-color: var(--gold); }
.preset-btn.active { border-color: var(--gold); background: rgba(224,176,76,.12); box-shadow: 0 0 8px rgba(224,176,76,.25); }
.preset-name { font-weight: 700; font-size: .88em; }
.preset-sub { font-size: .7em; color: var(--muted); }
.create-form { display: flex; flex-direction: column; gap: .7em; }
.field-row { display: flex; gap: .6em; }
/* min-width: 0 guards the classic flex-item sizing bug (default min-width:auto lets a flex item's
   own intrinsic content width — an input's number-spinner chrome differs slightly by value/browser
   — win out over flex:1's "split evenly", most visible in Safari/WebKit): without it, two same-flex
   siblings can render at visibly different widths depending on what's typed into each. */
.field { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .25em; }
.field-label { font-size: .72em; color: var(--muted); }
.field input { width: 100%; box-sizing: border-box; }

.lobby-list { display: flex; flex-direction: column; gap: .8em; padding: 1em; max-width: 480px; margin: 0 auto; }
.lobby-row {
  display: flex;
  align-items: center;
  gap: .8em;
  background: var(--panel);
  border-radius: 10px;
  padding: .8em 1em;
}
.hint { color: var(--muted); }

/* --- table --- */
.table-page {
  display: grid;
  /* Table takes whatever is left; journal/chat hug the right edge instead of sitting in a
     centered 900+480 pair with empty window chrome on both sides. */
  grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
  justify-content: stretch;
  width: 100%;
  gap: .6em;
  padding: .25em .5em .25em .6em;
  align-items: stretch;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.table-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .2em;
  min-width: 0; /* let the oval shrink instead of forcing the grid column wide */
  min-height: 0;
  height: 100%;
  overflow: hidden;
}
.table-wrap > .table-tools,
.table-wrap > .action-bar,
.table-wrap > .action-slot,
.table-wrap > .spectator-banner { flex-shrink: 0; }
/* Reserves space for the tallest thing that ever lands here (the raise action-bar) so switching
   between it, a one-line hint ("ждём игроков…"), or nothing at all doesn't resize .table-stage
   above it — which otherwise visibly jumped the whole table on every turn/hand transition. */
.action-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Tall enough for the raise action-bar's controls wrapping onto a second line (fold/call/slider/
     number/raise/all-in buttons don't all fit on one row under ~700px) — a shorter reservation here
     just moves the jank from "row appears" to "row grows a second line", same complaint either way. */
  min-height: 92px;
  width: 100%;
}
/* Seats hang off the felt rim; this box is the playground they must stay inside so they
   cannot cover the header, blinds line, emoji bar or betting controls — and so the page
   itself never grows a scrollbar. */
.table-stage {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* Narrow screens: log drops below the table instead of squeezing it into a sliver of a column. */
@media (max-width: 860px) {
  .table-page {
    grid-template-columns: 1fr;
    grid-template-rows: minmax(0, 1fr) minmax(120px, 26vh);
  }
  /* "журнал закрывает где кнопки фолд, колл ва-банк" — the log/chat row above always reserved at
     least 120px (up to 26vh) whether or not a player actually wanted to see it, which is exactly
     what was pushing the action buttons below the fold. Collapsed (the mobile default — see
     S.logCollapsed), the row shrinks to just the toggle bar itself and that space goes straight
     back to the table/actions above. */
  .table-page:has(.log-sidebar.collapsed) { grid-template-rows: minmax(0, 1fr) auto; }
  .log-toggle {
    display: block; width: 100%; text-align: center; font-weight: 600;
    padding: .5em; background: var(--panel2); border: 1px solid #3a3f4a; border-radius: 8px;
    color: var(--text); flex-shrink: 0;
  }
  .log-sidebar.collapsed { gap: 0; }
  .log-sidebar.collapsed .log-panel,
  .log-sidebar.collapsed .chat-panel { display: none; }
}
@media (min-width: 861px) {
  .log-toggle { display: none; } /* desktop always has its own column for this — nothing to toggle */
}
.table-oval {
  position: relative;
  isolation: isolate;
  width: min(1200px, 96%);
  aspect-ratio: 2.15 / 1;
  height: auto;
  max-height: 90%;
  margin: 0;
  flex: 0 0 auto;
  border: 16px solid #3d2618;
  border-radius: 999px;
  background: radial-gradient(ellipse 72% 68% at 50% 42%, #2a9a4e, #1c7a3c 58%, #176833 100%);
  box-shadow:
    0 22px 48px rgba(0, 0, 0, .7),
    0 0 0 2px #1a1008,
    inset 0 0 48px rgba(0, 0, 0, .14);
}
.table-oval::before { content: none; }
.table-oval::after {
  content: '';
  position: absolute;
  inset: 7.2% 5.4%;
  border: 1.5px solid #c9a84a;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .12);
}
.pot-area {
  position: absolute;
  top: 40%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  /* Same z-index as .seat (1) used to mean DOM order broke the tie — seats render after the pot
     area in the markup, so on a cramped mobile table where a seat's card genuinely overlaps the
     center, it painted over the community cards instead of the other way round ("5 карт на столе
     плохо видно, закрывается игроками"). The board is the one thing every seated player needs to
     be able to read at a glance regardless of layout — it wins any overlap now. */
  z-index: 3;
}
.pot-amount {
  background: rgba(8, 14, 10, .55);
  border: 1px solid rgba(224, 176, 76, .35);
  border-radius: 22px;
  padding: .35em 1.1em;
  font-weight: 700;
  color: var(--gold);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .35);
}
.community { display: flex; gap: .4em; margin-top: .65em; justify-content: center; }

.seat {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) translate(var(--sx), var(--sy));
  width: 150px;
  text-align: center;
  z-index: 1;
}
/* A fixed-size seat card works fine up to ~8 players — applyTableLayout()'s shrink-to-fit pass
   keeps the whole stadium off the stage edges regardless of count, but past 8 the cards themselves
   are still full-size and start overlapping each other (see tableScreen()'s crowdClass comment).
   Scaling the card down as the table fills up (9-10 mid, 11-12 max) is what keeps it readable. */
.table-oval.crowd-9 .seat { width: 120px; }
.table-oval.crowd-9 .seat .seat-card { padding: .4em .45em; }
.table-oval.crowd-9 .seat .name { font-size: .78em; }
.table-oval.crowd-9 .seat .stack { font-size: .7em; }
.table-oval.crowd-9 .seat-avatar-wrap, .table-oval.crowd-9 .seat-avatar { width: 24px; height: 24px; }
.table-oval.crowd-9 .playing-card { width: 40px; height: 56px; font-size: .9em; }
.table-oval.crowd-9 .playing-card.big { width: 52px; height: 73px; font-size: 1.05em; }

.table-oval.crowd-11 .seat { width: 96px; }
.table-oval.crowd-11 .seat .seat-card { padding: .3em .35em; }
.table-oval.crowd-11 .seat .name { font-size: .68em; }
.table-oval.crowd-11 .seat .stack { font-size: .6em; }
.table-oval.crowd-11 .seat-avatar-wrap, .table-oval.crowd-11 .seat-avatar { width: 20px; height: 20px; }
.table-oval.crowd-11 .playing-card { width: 32px; height: 45px; font-size: .78em; }
.table-oval.crowd-11 .playing-card.big { width: 42px; height: 59px; font-size: .9em; }
.seat .seat-card {
  position: relative;
  background: linear-gradient(180deg, rgba(32, 26, 20, .9), rgba(12, 10, 8, .94));
  border-radius: 14px;
  padding: .5em .6em;
  border: 1px solid rgba(212, 176, 106, .28);
  cursor: pointer;
  box-shadow: 0 8px 18px rgba(0, 0, 0, .4);
  transition: opacity .3s ease, border-color .2s ease, box-shadow .2s ease;
  /* .seat centres itself with translate(-50%, -50%), which is relative to its OWN current box —
     so if the card's content height changes at all (the hand-preview line, or a badge appearing,
     or the bankroll chip-pile needing a different number of denomination piles for a new stack
     total after a bet), that self-centering silently shifts by the same amount, reading as the
     whole seat — or, since chip clearance measures off real rects, even the table — visibly
     nudging every time someone bets. A fixed minimum height absorbs that instead of translating it
     into a shift. 108px covers name + stack row + a folded hand-preview line + two mini hole cards
     with room to spare; bump it in poker.css's mobile block too if cards ever grow there. */
  min-height: 108px;
  box-sizing: border-box;
}
.seat.to-act .seat-card {
  border-color: #e0b04c;
  box-shadow: 0 0 0 1px #e0b04c, 0 0 18px rgba(224, 176, 76, .45), 0 8px 18px rgba(0, 0, 0, .4);
}
.seat.folded .seat-card { opacity: .45; }
/* A dead connection (tab closed, network dropped) used to look completely normal — no way to tell
   it apart from someone just quietly still there. A red ring flags it at a glance; the
   "· нет связи"/"· покидает стол" text (see main.js) says what's actually going on. */
.seat.offline .seat-card { border-color: #c0392b; box-shadow: 0 0 0 1px #c0392b, 0 0 10px rgba(192, 57, 43, .4); }
.seat-who {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .35em;
  min-width: 0;
}
.seat-avatar-wrap {
  position: relative;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}
.seat-avatar {
  width: 28px; height: 28px; border-radius: 50%; object-fit: cover;
  display: block; border: 1px solid #3a3f4a;
  position: absolute; inset: 0;
}
.seat-avatar.placeholder {
  display: grid; place-items: center;
  background: var(--panel2); color: var(--gold);
  font-weight: 700; font-size: .75em; border-color: #3a3f4a;
}
/* Both the real <img> and the letter placeholder are absolutely positioned on top of each other
   (see avatarHtml()) — the placeholder paints later in DOM order, so with no rule to hide it, it
   permanently covers a real photo the instant it finishes loading and un-hides itself. Once the
   image is genuinely loaded (its own `hidden` attribute cleared — see the document-level load/error
   listeners in main.js), hide its sibling placeholder instead. */
img.hdr-avatar:not([hidden]) ~ .placeholder,
img.seat-avatar:not([hidden]) ~ .placeholder,
img.profile-avatar:not([hidden]) ~ .placeholder {
  display: none;
}
.seat .name {
  font-weight: 600; font-size: .9em; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: .15em;
}
/* The username text truncates on its own — the kills badge sits outside it as a flex sibling
   (flex-shrink: 0) so a long name never pushes the skulls into the clipped/ellipsized part. */
.seat .name > .name-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.seat .kills { flex-shrink: 0; }
/* A thin countdown bar hugging the bottom edge of the seat card, instead of a text badge that used
   to get wedged into the name row — that badge was wide enough to push the player's own name out of
   view on a crowded table. Sits just outside the card's own bottom border (not inside the padding)
   so it never steals space from anything already there. */
.act-clock-bar {
  position: absolute; left: 8%; right: 8%; bottom: -5px; height: 4px;
  background: rgba(255, 255, 255, .15); border-radius: 2px; overflow: hidden; z-index: 1;
}
.act-clock-fill { height: 100%; width: 100%; background: #2ecc71; transition: width .25s linear, background-color .2s ease; }
.act-clock-fill.act-clock-warn { background: #e74c3c; animation: actClockPulse .5s ease-in-out infinite; }
@keyframes actClockPulse { 0%, 100% { opacity: 1; } 50% { opacity: .4; } }
.seat .kick-btn {
  margin-left: .3em; font-size: .75em; line-height: 1; padding: .05em .35em;
  border-radius: 50%; opacity: .6; vertical-align: middle;
}
.seat .kick-btn:hover { opacity: 1; background: #c0392b; color: #fff; border-color: #c0392b; }
.records-card {
  background: var(--panel2); border: 1px solid #3a3f4a; border-radius: 8px;
  padding: .5em .6em; margin-bottom: .6em; font-size: .85em;
}
.records-card h3 { margin: 0 0 .3em; font-size: .95em; }
.records-row { padding: .1em 0; }
.records-row b { color: var(--gold); }
.log-highlight { color: var(--gold); font-weight: 600; }
.seat .stack { color: var(--gold); font-size: .82em; }
.seat .hole { display: flex; gap: .25em; justify-content: center; margin-top: .4em; }
.seat .dealer-btn {
  position: absolute;
  top: -6px;
  right: -6px;
  background: radial-gradient(circle at 35% 30%, #fff6d6, #e0b04c 55%, #8a6415);
  color: #3a2410;
  border-radius: 50%;
  width: 22px;
  height: 22px;
  font-size: .7em;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #6a4a18;
  box-shadow: 0 2px 4px rgba(0,0,0,.45);
  z-index: 2;
}
.seat .blind-btn {
  position: absolute;
  top: -6px;
  left: -6px;
  background: var(--panel2);
  border: 1px solid var(--muted);
  color: var(--text);
  border-radius: 50%;
  width: 20px;
  height: 20px;
  font-size: .62em;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
}
.seat .blind-btn.sb { background: #3a5a8f; border-color: #3a5a8f; }
.seat .blind-btn.bb { background: #8f3a3a; border-color: #8f3a3a; }
.hand-preview {
  display: inline-block;
  margin-top: .3em;
  font-size: .72em;
  color: var(--gold);
  background: rgba(0,0,0,.4);
  border-radius: 8px;
  padding: .1em .5em;
}
.reveal-row { display: flex; gap: .5em; justify-content: center; margin-top: .3em; }
.reveal-row button { font-size: .8em; padding: .3em .7em; }
.spectator-banner {
  background: rgba(255,255,255,.06);
  border: 1px dashed var(--muted);
  border-radius: 10px;
  padding: .6em 1em;
  width: min(720px, 94vw);
  text-align: center;
  color: var(--muted);
}
.seat-chip {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 3; /* above the dealer/blind badges (z-index 2) — a hidden bet chip is worse than a hidden D */
  pointer-events: none;
}

/* --- chips: real denomination colors (see DENOMS in main.js — white 1, red 5, green 25, black
   100, purple 500, gold 1000), rendered as one stacked pile per denomination actually present in
   the amount, so e.g. 340 chips reads as a black pile + a green pile + a red pile, not one
   indistinct blob. Swap a .chip's background for an image later (real chip art) without touching
   the breakdown logic. */
.chip-stack {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  vertical-align: middle;
}
.chip-pile {
  position: relative;
  display: inline-block;
}
.chip-pile.mini { width: 15px; height: 20px; }
.chip-pile.pot { width: 32px; height: 42px; }
.chip {
  position: absolute;
  left: 0;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, transparent 55%, var(--chip-edge) 57%, var(--chip-edge) 62%, transparent 64%),
    repeating-conic-gradient(var(--chip-color) 0deg 22deg, var(--chip-edge) 22deg 44deg);
  border: 1px solid rgba(0,0,0,.4);
  box-shadow: 0 2px 2px rgba(0,0,0,.6);
}
.chip.mini { width: 15px; height: 15px; bottom: calc(var(--i, 0) * 3px); }
.chip.pot { width: 32px; height: 32px; bottom: calc(var(--i, 0) * 6px); }
.chip-count {
  position: absolute;
  right: -4px;
  bottom: -2px;
  background: rgba(0,0,0,.75);
  color: #fff;
  font-size: .6em;
  font-weight: 700;
  border-radius: 6px;
  padding: 0 .25em;
  z-index: 2;
}
.chip-amt {
  font-size: .78em;
  font-weight: 700;
  background: rgba(0,0,0,.6);
  color: var(--gold);
  border-radius: 8px;
  padding: .1em .55em;
  white-space: nowrap;
}
.chip-stack.pot-grow .chip-amt { animation: potWin .5s ease-in-out; }
.chip-stack.chip-in { animation: chipIn .3s ease-out; }
.chip-stack.bankroll { gap: .3em; }

.chip-legend {
  position: absolute;
  top: .4em;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .55em;
  font-size: .72em;
  color: var(--muted);
  opacity: .75;
  pointer-events: none;
  z-index: 2;
}
.legend-item { display: inline-flex; align-items: center; gap: .3em; }
.legend-item .chip { position: static; }

.seat .stack-line { display: flex; align-items: center; justify-content: center; gap: .4em; margin-top: .1em; }
.equity-badge {
  font-size: .8em; font-weight: 800; padding: .05em .4em; border-radius: 6px;
  background: rgba(0,0,0,.35);
}
.equity-badge.equity-good { color: #4fce72; }
.equity-badge.equity-bad { color: #e07a7a; }
.equity-badge.equity-mid { color: var(--gold); }

@keyframes chipCollect {
  from { opacity: 1; transform: translate(var(--cx, 0), var(--cy, 0)) scale(1); }
  to { opacity: 0; transform: translate(calc(-1 * var(--sx) * 1), calc(-1 * var(--sy) * 1)) scale(.5); }
}
.seat-chip.collecting { animation: chipCollect .55s ease-in forwards; }

.playing-card {
  position: relative;
  width: 50px;
  height: 70px;
  border-radius: 6px;
  background: #fff;
  color: #111;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 1.15em;
  font-family: 'Iowan Old Style', Palatino, Georgia, 'Times New Roman', serif;
  flex-direction: column;
  line-height: 1;
  overflow: hidden;
  box-shadow: 0 3px 8px rgba(0,0,0,.4), 0 0 0 1px rgba(0,0,0,.08);
  font-variant-numeric: tabular-nums;
}
.playing-card .pip.mid {
  position: static;
  font-size: 1.85em;
  font-weight: 400;
  line-height: 1;
}
.playing-card .pip.tl,
.playing-card .pip.br {
  position: absolute;
  z-index: 2;
  width: 1.85em;
  line-height: .95;
  text-align: center;
  letter-spacing: -.04em;
}
.playing-card .pip.tl small,
.playing-card .pip.br small { display: block; font-size: .85em; margin-top: 1px; font-weight: 400; }
.playing-card .pip.tl { top: 3px; left: 2px; font-size: .72em; }
.playing-card .pip.br { bottom: 3px; right: 2px; font-size: .72em; transform: rotate(180deg); }
.playing-card.red { color: #c41e3a; }
.playing-card.back {
  background:
    radial-gradient(circle at 50% 50%, rgba(255,220,180,.18) 0 14%, transparent 15%),
    repeating-linear-gradient(45deg, #7a1824 0 3px, #4e0f18 3px 6px);
  box-shadow: 0 3px 8px rgba(0,0,0,.4), 0 0 0 1px #3a1014;
  color: transparent;
}
.playing-card.back .pip { display: none; }
.playing-card.big { width: 70px; height: 98px; font-size: 1.5em; }
.playing-card.big .pip.tl,
.playing-card.big .pip.br { font-size: .62em; }
.playing-card.big .pip.mid { font-size: 1.7em; }
.log-cards .pip.mid,
.log-cards .pip.br,
.hist-cards .pip.mid,
.hist-cards .pip.br,
.hist-community .pip.mid,
.hist-community .pip.br { display: none; }
.log-cards .pip.tl,
.hist-cards .pip.tl,
.hist-community .pip.tl {
  position: static;
  width: auto;
  font-size: 11px;
  line-height: 1.05;
}
.log-cards .pip.tl small,
.hist-cards .pip.tl small,
.hist-community .pip.tl small { font-size: 11px; margin: 0; }

@keyframes dealIn {
  from { opacity: 0; transform: scale(.25) translateY(-40px) rotate(-8deg); }
  to { opacity: 1; transform: scale(1) translateY(0) rotate(0); }
}
.playing-card.deal-in { animation: dealIn .38s cubic-bezier(.22,.66,.16,1); }

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

@keyframes foldFade {
  to { opacity: .4; transform: translateY(10px) rotate(-5deg); }
}
.seat.fold-anim .seat-card { animation: foldFade .45s ease-out forwards; }

@keyframes potWin {
  0% { transform: scale(1); }
  40% { transform: scale(1.18); box-shadow: 0 0 24px var(--gold); }
  100% { transform: scale(1); }
}
.seat.won-anim .seat-card { animation: potWin .7s ease-in-out; }

.action-bar {
  display: flex;
  gap: .5em;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  background: var(--panel);
  border-radius: 12px;
  padding: .5em .8em;
  width: min(760px, 96%);
  z-index: 6;
  box-shadow: 0 6px 18px rgba(0,0,0,.45);
}
.raise-pct-row { display: flex; gap: .35em; width: 100%; justify-content: center; order: -1; }
.pct-btn { padding: .25em .7em; font-size: .82em; }
.action-bar input[type=range] { flex: 1; min-width: 120px; }
.action-bar .amount {
  width: 4.6em;
  text-align: center;
  color: var(--gold);
  font-weight: 700;
  background: var(--panel2);
  border: 1px solid #3a3f4a;
  border-radius: 6px;
  padding: .3em .2em;
}

.hint-line { color: var(--muted); text-align: center; }
.table-tools {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .2em;
  width: min(780px, 96%);
  margin: 0 auto;
  flex-shrink: 0;
  overflow: hidden; /* clips the emoji/sticker bars' own negative-margin scrollbar fallback */
}
.preact-bar {
  display: flex;
  flex-wrap: wrap;
  gap: .6em 1em;
  justify-content: center;
  width: 100%;
}
.preact-btn {
  color: var(--muted);
  font-size: .9em;
  font-weight: 600;
  cursor: pointer;
  margin: 0;
  background: var(--panel);
  border: 1px solid #3a3f4a;
  border-radius: 8px;
  padding: .4em .8em;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.preact-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.log-panel {
  width: 100%;
  /* Belt-and-suspenders: a burst of log lines (e.g. an offline seat's action clock repeatedly
     firing) should never be able to grow this past its flex share (see the flex:1 1 42% rule
     below) — this is the actual hard ceiling that guarantees it, on top of that. */
  max-height: 60vh;
  overflow-y: auto;
  background: var(--panel);
  border-radius: 10px;
  padding: .6em 1em;
  font-size: .9em;
  color: var(--muted);
  box-sizing: border-box;
}
.log-panel div { padding: .1em 0; }
/* Shrunk-down copies of the same table cards (see logCardsHtml() in main.js) — the board first,
   then each winner's hole cards grouped in their own little cluster. */
.log-cards { display: flex; flex-wrap: wrap; align-items: center; gap: .15em; margin: .25em 0 .5em; }
.log-cards .playing-card {
  width: 28px;
  height: 38px;
  font-size: 12px;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0,0,0,.35);
}
.log-hand { display: inline-flex; gap: .1em; margin-left: .5em; padding-left: .5em; border-left: 1px solid #3a3f4a; }
/* Chat sits right under the log in the same sidebar column — same card look, its own scroll box
   so a burst of messages doesn't push the log (or the input) out of view. */
.chat-panel {
  width: 100%;
  background: var(--panel);
  border-radius: 10px;
  padding: .6em 1em;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
.chat-panel h3, .log-panel h3 { margin: 0 0 .4em; font-size: .9em; color: var(--text); }
.chat-log {
  flex: 1;
  min-height: 80px;
  max-height: none;
  overflow-y: auto;
  font-size: .9em;
}
.chat-line { padding: .15em 0; overflow-wrap: anywhere; }
.chat-line b { color: var(--accent); font-weight: 600; }
.chat-input-row { display: flex; gap: .4em; margin-top: .5em; }
.chat-input-row input { flex: 1; min-width: 0; }
.chat-input-row button { padding: .5em .8em; }
/* The sidebar column (log + chat stacked) is its own flex column so both can share the available
   height next to the table, instead of two independently-sized boxes fighting for space. */
.sidebar-col {
  width: 100%;
  height: 100%;
  max-height: none;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* Visibly separates the log from the chat below it — they used to sit right on top of each
     other with only a thin 1em gap, easy to misread as one continuous block. */
  gap: 1.2em;
  overflow: hidden;
}
/* Pushes the chat panel down to the bottom of the sidebar column instead of sitting right under
   the log — .chat-panel's own flex:1 (see below) still lets it grow to fill whatever's left. */
.log-panel { flex: 1 1 42%; min-height: 0; }
@media (max-width: 860px) {
  .sidebar-col { max-height: none; height: 100%; }
  .log-panel { max-height: 120px; }
  .chat-log { max-height: 160px; }
}

/* --- emoji reactions --- */
.emoji-bar {
  display: flex;
  flex-wrap: nowrap;
  gap: .15em;
  justify-content: center;
  width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Belt-and-suspenders against a Chromium build (some Yandex Browser releases included) that
     doesn't fully honor scrollbar-width/::-webkit-scrollbar's `display:none` and still reserves
     the native scrollbar's track height: push it past the bottom edge with a negative margin, then
     clip it off with the padding-matched wrapper's own overflow — the bar itself never grows. */
  padding-bottom: 17px;
  margin-bottom: -17px;
}
.emoji-bar::-webkit-scrollbar { display: none; width: 0; height: 0; }
.emoji-btn {
  background: var(--panel2);
  border-radius: 8px;
  padding: .1em .28em;
  font-size: 1.15em;
  line-height: 1;
  flex-shrink: 0;
}
.emoji-btn:hover { background: var(--accent); }
.emoji-btn img { width: 1.5em; height: 1.5em; object-fit: contain; vertical-align: middle; }

/* --- sticker reactions (image/video, some with a voice line heard by everyone — see poker:sticker) --- */
.sticker-bar {
  display: flex; flex-wrap: nowrap; gap: .3em; justify-content: center;
  width: 100%; overflow-x: auto; margin-top: .3em;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: 17px; /* see .emoji-bar's comment on this fallback */
  margin-bottom: -17px;
}
.sticker-bar::-webkit-scrollbar { display: none; width: 0; height: 0; }
.sticker-btn {
  background: var(--panel2); border-radius: 8px; padding: .2em; flex-shrink: 0;
  width: 40px; height: 40px; display: grid; place-items: center; overflow: hidden;
}
.sticker-btn:hover { background: var(--accent); }
.sticker-btn img, .sticker-btn video { width: 100%; height: 100%; object-fit: cover; border-radius: 5px; }

/* --- reaction packs: one button per pack (emoji, or a themed sticker group), opening a panel of
   its individual items below the row instead of always showing every item at once. --- */
.pack-row {
  display: flex; flex-wrap: nowrap; gap: .3em; justify-content: center;
  width: 100%; overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: 17px; /* see .emoji-bar's comment on this fallback */
  margin-bottom: -17px;
}
.pack-row::-webkit-scrollbar { display: none; width: 0; height: 0; }
.pack-btn {
  background: var(--panel2); border-radius: 8px; padding: .2em; flex-shrink: 0;
  width: 40px; height: 40px; display: grid; place-items: center; overflow: hidden;
  border: 2px solid transparent;
}
.pack-btn:hover { background: var(--accent); }
.pack-btn.active { border-color: var(--accent); }
.pack-btn img, .pack-btn video { width: 100%; height: 100%; object-fit: cover; border-radius: 5px; }
.pack-emoji-icon { font-size: 1.4em; line-height: 1; }
.pack-panel {
  display: flex; flex-wrap: wrap; gap: .3em; justify-content: center;
  width: 100%; max-height: 180px; overflow-y: auto;
  background: var(--panel); border-radius: 10px; padding: .5em;
  margin-top: .3em; box-sizing: border-box;
}

/* A big reaction floating above the sender's seat, visible to the whole table — see
   poker:emoji/S.reactions. Deliberately much larger than the hole cards so it reads at a glance. */
.seat-reaction {
  position: absolute;
  left: 50%;
  bottom: 100%;
  transform: translateX(-50%);
  font-size: 2.6em;
  line-height: 1;
  z-index: 5;
  pointer-events: none;
  animation: reactionPop 2.4s ease-out forwards;
  filter: drop-shadow(0 3px 6px rgba(0,0,0,.5));
}
/* height-based, not a fixed square: a wide source (e.g. a landscape GIF) was getting squeezed down
   to a fraction of the intended size inside a 1em x 1em box via object-fit — letting width follow
   the source's own aspect ratio keeps every reaction visually the same height regardless of shape. */
.seat-reaction img, .seat-reaction video { height: 1.3em; width: auto; max-width: 3em; vertical-align: middle; }

/* A seat in the table's top half (see the .seat-top class, set from the seat's own computed y
   offset) has little to no room above it before running off the visible table/viewport — floating
   the reaction there the same way as every other seat made it "поплохо видно"/cut off at the top
   edge. Flip it to float below the seat instead for exactly those seats. */
.seat-top .seat-reaction { bottom: auto; top: 100%; }
@keyframes reactionPop {
  0% { opacity: 0; transform: translateX(-50%) scale(.3) translateY(10px); }
  12% { opacity: 1; transform: translateX(-50%) scale(1.15) translateY(-6px); }
  22% { transform: translateX(-50%) scale(1) translateY(0); }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translateX(-50%) scale(.9) translateY(-14px); }
}

/* Floats over the felt instead of sitting in the page flow below the table — that used to push the
   action bar (and everything else) further down, forcing a scroll to reach the betting controls
   right when a hand just ended. See tableScreen()'s `result-overlay` wrapper. Anchored toward the
   BOTTOM of the oval rather than dead-centre on purpose: the community cards (the actual board the
   winning hand was made from) sit up near the pot around 40% down, and people want to see that,
   not just be told a hand name — centering the banner used to cover them entirely. */
.result-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 4;
  pointer-events: none;
  /* Extra bottom clearance — the seat right at the bottom-centre of the oval ("me") sits closest to
     this edge, and the old, smaller gap let the banner's own top edge overlap that seat's hole
     cards ("плашку победителя закрывает мои карты"). Raising it clear of that seat's card row
     fixes it for every crowd size (applyTableLayout already keeps the oval itself off the page
     edges, so this doesn't need its own responsive tweak). */
  padding: 1em 1em 6.5em;
}
.result-banner {
  background: linear-gradient(180deg, rgba(20,22,26,.94), rgba(20,22,26,.88));
  border: 2px solid var(--gold);
  border-radius: 14px;
  padding: .9em 1.2em;
  width: min(420px, 92%);
  text-align: center;
  box-shadow: 0 0 30px rgba(0,0,0,.6), 0 0 22px rgba(224,176,76,.35);
  animation: resultPulse .9s ease-out;
  pointer-events: auto;
}
.result-banner .winner-line {
  font-size: 1.5em;
  font-weight: 800;
  color: var(--gold);
  text-shadow: 0 0 10px rgba(224,176,76,.6);
}
.result-banner .sub-line { font-size: .95em; color: var(--text); margin-top: .2em; }
@keyframes resultPulse {
  0% { transform: scale(.85); box-shadow: 0 0 0 rgba(224,176,76,0); }
  40% { transform: scale(1.04); }
  100% { transform: scale(1); box-shadow: 0 0 22px rgba(224,176,76,.35); }
}

.toasts, #toasts {
  position: fixed;
  top: 1em;
  right: 1em;
  display: flex;
  flex-direction: column;
  gap: .5em;
  z-index: 50;
}
.toast {
  background: var(--panel2);
  border-radius: 8px;
  padding: .6em 1em;
  box-shadow: 0 4px 12px rgba(0,0,0,.4);
}
.toast.error { background: var(--danger); }
.toast.success { background: linear-gradient(135deg, #3a2f10, var(--panel2)); border: 1px solid var(--gold); }

.error-text { color: #ff8a80; }

/* Phone widths: seats at their desktop size (150px) crowd into each other around a narrow oval —
   shrink the seat card, its text and the cards so a full ring of players actually fits on screen
   without overlapping (their *positions* already rescale via applyTableLayout() in main.js; this
   just keeps their on-screen footprint small enough to fit those tighter positions). Placed last
   in the file on purpose — same specificity as the base `.seat` etc. rules above, so it must come
   after them in source order to actually win the cascade. */
@media (max-width: 480px) {
  .table-oval { width: min(1200px, 96%); max-height: 90%; margin: 0; }
  /* Shrunk another ~15% from the previous defaults (84px seat / 30x42 card) per direct feedback
     that both the seats and the hole cards read as too big on a phone, crowding the table and
     leaving less room to actually see what's happening. */
  .seat { width: 72px; }
  .seat .seat-card { padding: .25em .3em; border-width: 1px; }
  .seat-avatar-wrap, .seat-avatar { width: 19px; height: 19px; }
  .seat-avatar.placeholder { font-size: .58em; }
  .seat .name { font-size: .6em; }
  .seat .stack { font-size: .56em; }
  .seat .dealer-btn, .seat .blind-btn { width: 14px; height: 14px; font-size: .5em; }
  .hand-preview { font-size: .52em; padding: .05em .3em; }
  .playing-card { width: 26px; height: 36px; font-size: .65em; }
  .playing-card.big { width: 34px; height: 48px; font-size: .82em; }
  /* The desktop-oriented crowd-9/crowd-11 sizes above (120px/96px) are actually *bigger* than this
     breakpoint's own already-shrunk default — on a phone, more players needs to shrink further
     still, not jump back up. These override them back down, smaller as the table fills past 8. */
  .table-oval.crowd-9 .seat { width: 58px; }
  .table-oval.crowd-9 .seat .seat-card { padding: .2em .25em; }
  .table-oval.crowd-9 .seat .name { font-size: .52em; }
  .table-oval.crowd-9 .seat .stack { font-size: .48em; }
  .table-oval.crowd-9 .seat-avatar-wrap, .table-oval.crowd-9 .seat-avatar { width: 15px; height: 15px; }
  .table-oval.crowd-9 .playing-card { width: 20px; height: 28px; font-size: .58em; }
  .table-oval.crowd-9 .playing-card.big { width: 25px; height: 35px; font-size: .67em; }
  .table-oval.crowd-11 .seat { width: 50px; }
  .table-oval.crowd-11 .seat .seat-card { padding: .15em .2em; }
  .table-oval.crowd-11 .seat .name { font-size: .46em; }
  .table-oval.crowd-11 .seat .stack { font-size: .43em; }
  .table-oval.crowd-11 .seat-avatar-wrap, .table-oval.crowd-11 .seat-avatar { width: 13px; height: 13px; }
  .table-oval.crowd-11 .playing-card { width: 17px; height: 24px; font-size: .5em; }
  .table-oval.crowd-11 .playing-card.big { width: 21px; height: 30px; font-size: .56em; }
  .table-oval.crowd-11 .seat .dealer-btn, .table-oval.crowd-11 .seat .blind-btn { width: 10px; height: 10px; font-size: .4em; }
  .chip-pile.mini { width: 11px; height: 15px; }
  .chip.mini { width: 11px; height: 11px; }
  .emoji-btn { font-size: 1.15em; padding: .15em .3em; }
  .seat-reaction { font-size: 1.9em; }
  .result-banner { padding: .6em .8em; }
  .result-banner .winner-line { font-size: 1.15em; }
  .result-banner .sub-line { font-size: .8em; }

  /* The header never wrapped or scrolled — on a narrow phone its items (title, table info, leave
     button, the Monopoly link, volume, profile/username/logout) add up to way more than the
     viewport width, and with nothing to shrink further or wrap onto, everything past a certain
     point (the leave button included — see the "Встать" report) just renders past the right edge,
     unreachable by any scroll. Force a two-line header instead of letting content run off. The
     `.grow` spacer, given a full-width 0-height flex-basis here, is what forces the actual line
     break: everything before it (title/table meta/leave) ends up on line 1, everything after
     (Monopoly link onward) on line 2, each side then free to wrap further/shrink within its own line. */
  .top { flex-wrap: wrap; row-gap: .35em; padding: .5em .6em; }
  .top .grow { flex-basis: 100%; width: 0; height: 0; }
  .top h1 { font-size: 1em; }
  .table-meta { font-size: .74em; }
  .hdr-profile-label { display: none; } /* keep the avatar, drop the "Профиль" text label next to it */
  .top .me { max-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* Same root cause as the header above, on a smaller scale: no side padding meant the first/last
     button in the horizontally-scrolled row sat flush against the viewport edge, so it read as
     "half eaten by the screen" even though scrolling could technically reach it. */
  .emoji-bar, .sticker-bar, .pack-row { padding: 0 8px; scroll-padding-inline: 8px; }

  /* The header above only stops content running off-screen — it still wraps into 5 separate rows at
     the table (title / table-meta+leave / "В Монополию"+volume+avatar / username+"Сменить имя"),
     eating ~450px before the table even starts. On a phone the table itself is what actually needs
     that space: with so little left, applyTableLayout()'s own shrink-to-fit logic has to squash the
     oval down to near-zero height, and at that point two seats on opposite sides of the stadium (top
     and bottom) land almost exactly on top of each other instead of at opposite ends — the "table
     looks like a pile of overlapping cards" report. Collapse the in-game header to two tight rows
     instead of four, and drop what a player mid-hand doesn't need at all (their own name — they
     know who they are; "Сменить имя" — not a mid-game action). */
  body.ingame .top { padding: .35em .5em; gap: .3em; row-gap: .25em; }
  body.ingame .top .grow { flex-basis: 0; width: auto; height: auto; } /* undo the generic layout's forced 2-line split above */
  body.ingame .top h1 { font-size: .85em; }
  body.ingame .table-meta { font-size: .68em; }
  body.ingame .top [data-act="sitOut"],
  body.ingame .top [data-act="leave"] { font-size: .78em; padding: .3em .55em; }
  body.ingame .top .me,
  body.ingame .top [data-act="logout"] { display: none; }
  body.ingame .to-monopoly-btn { font-size: 0; padding: .35em .5em; } /* icon-only: the emoji glyph is a child text node, not markup, so hiding text needs the font-size trick rather than an empty rule */
  body.ingame .to-monopoly-btn::before { content: '🎲'; font-size: 1rem; }
  body.ingame .volume-ctl .vol-track { width: 46px; }
  body.ingame .hdr-profile { padding: .2em; }

  /* Give the table itself a real floor: without this, every stacked sibling above/below it (header,
     emoji/sticker bars, action bar, journal, chat) claims whatever height it wants first and the
     oval — the only flexible element — gets whatever's left, which on a short viewport can be next
     to nothing. 44svh guarantees the stadium (and therefore the seats) always has enough room to
     lay out without collapsing into itself. */
  .table-stage { min-height: 44svh; }
  /* The chip-denomination key floats at the very top of .table-stage, which is exactly where a
     top-seated player's card now sits once the stage actually has real height — it's purely
     informational (every chip already shows its own value), so it's the one thing worth just
     dropping on a phone rather than fighting the top seat for the same few pixels. */
  .chip-legend { display: none; }
  /* The desktop oval is very flat (2.15:1) because a wide screen has width to spare and seats ring
     the whole rim; on a narrow phone that same flatness puts the top/bottom seats (where 2-4
     players always land) right on top of the pot in the middle, since there's barely any vertical
     radius between "edge of stage" and "center". A rounder oval trades some width for real vertical
     clearance around the pot — applyTableLayout()'s own shrink-to-fit pass still runs on top of this
     for anything it still doesn't fit. */
  .table-oval { aspect-ratio: 1.3 / 1; }
}

/* A phone on its side: plenty of width, very little height. The portrait breakpoint above collapses
   to one column and lets the table grow tall, which is exactly backwards here — force the two-column
   layout back on (table + a slim sidebar) even under the 860px width threshold, and claw back
   vertical space everywhere the table doesn't strictly need it. True forced rotation (locking the
   screen to landscape via the Orientation Lock API) needs a fullscreen prompt and isn't reliably
   supported everywhere, so this just makes actually-landscape phones work well rather than trying
   to force the device into that orientation. */
@media (orientation: landscape) and (max-height: 500px) {
  .table-page { grid-template-columns: minmax(0, 1fr) minmax(210px, 280px); gap: .6em; padding: .4em; }
  .table-oval { width: min(1200px, 96%); max-height: 88%; margin: 0; }
  .hint-line { font-size: .85em; }
  .chip-legend { font-size: .68em; gap: .4em; }
  .seat { width: 84px; }
  .seat .seat-card { padding: .3em .35em; border-width: 1px; }
  .seat-avatar-wrap, .seat-avatar { width: 22px; height: 22px; }
  .seat .name { font-size: .68em; }
  .seat .stack { font-size: .62em; }
  .playing-card { width: 30px; height: 42px; font-size: .75em; }
  .playing-card.big { width: 38px; height: 53px; font-size: .9em; }
  .action-bar { padding: .45em .7em; bottom: .3em; }
  .emoji-bar { gap: .15em; }
  .emoji-btn { font-size: 1em; padding: .1em .25em; }
  .sidebar-col { max-height: 92vh; gap: .8em; }
  .log-panel { max-height: 90px; }
  .chat-log { min-height: 50px; max-height: 110px; }
}

.profile-overlay {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(10, 12, 18, .55);
  display: grid; place-items: center; padding: 16px;
  /* Without this, a tall achievements grid (24-28 badges) could overflow past the top/bottom of the
     viewport with no way to reach the rest of it — place-items: center has no built-in scrolling. */
  overflow-y: auto;
}
.profile-card {
  background: var(--panel); border: 1px solid #3a3f4a; border-radius: 12px;
  padding: 1.4em 1.6em 1.2em; width: min(560px, 94vw); position: relative;
  box-shadow: 0 8px 28px rgba(0,0,0,.45); text-align: center;
}
.profile-close { position: absolute; top: .5em; right: .5em; }
.profile-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .45em;
  margin-bottom: 1em;
}
.profile-avatar-wrap {
  display: block;
  position: relative;
  width: 88px; height: 88px; flex-shrink: 0;
}
.profile-avatar {
  width: 88px; height: 88px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--gold); display: block;
  position: absolute; inset: 0;
}
.profile-avatar.placeholder {
  display: grid; place-items: center; font-size: 2.4em;
  background: var(--panel2); border-color: #3a3f4a; color: var(--gold);
}
.hdr-profile { display: inline-flex; align-items: center; gap: .4em; }
.hdr-avatar-wrap { position: relative; width: 22px; height: 22px; flex-shrink: 0; }
.hdr-avatar {
  width: 22px; height: 22px; border-radius: 50%; object-fit: cover;
  border: 1px solid #3a3f4a; display: block;
  position: absolute; inset: 0;
}
.hdr-avatar.placeholder {
  display: grid; place-items: center; font-size: .7em; font-weight: 700;
  background: var(--panel2); color: var(--gold);
}
.profile-card h2 { margin: .2em 0 .6em; }
.avatar-upload {
  display: inline-block; cursor: pointer; color: var(--accent);
  font-size: .85em; margin-bottom: .8em; text-decoration: underline;
}
.default-avatars {
  display: flex; flex-wrap: wrap; justify-content: center; gap: .4em;
  margin-top: .5em;
}
.default-avatar {
  width: 34px; height: 34px; border-radius: 50%; font-size: 1.1em;
  border: 1px solid #3a3f4a; cursor: pointer; display: grid; place-items: center;
  line-height: 1; padding: 0; transition: transform .1s ease, border-color .1s ease;
}
.default-avatar:hover { transform: scale(1.08); border-color: var(--gold); }
.fighter-picker { margin-top: .7em; }
.fighter-picker-label { font-size: .8em; color: var(--muted); margin-bottom: .35em; }
.fighter-list { display: flex; flex-wrap: wrap; justify-content: center; gap: .4em; }
.fighter-btn {
  width: 44px; height: 44px; border-radius: 8px; overflow: hidden;
  border: 1px solid #3a3f4a; cursor: pointer; display: grid; place-items: center;
  line-height: 1; padding: 0; background: var(--panel2); transition: transform .1s ease, border-color .1s ease;
}
.fighter-btn:hover { transform: scale(1.08); border-color: var(--gold); }
.fighter-btn.active { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(224,176,76,.35); }
/* Shared by both the fighter picker (small, in a .fighter-btn) and the duel overlay (large, in
   .duel-fighter) — see main.js's fighterIconHtml. The emoji fallback is what's actually visible by
   default; the <img> only takes over once it's confirmed to have loaded (see the 'load' listener),
   so a fighter with no .gif dropped into public/img/fighters/ never shows a broken-image icon. */
.fighter-icon-wrap { position: relative; display: grid; place-items: center; width: 100%; height: 100%; }
.fighter-icon-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.fighter-icon-emoji { font-size: 1.3em; line-height: 1; }
.fighter-icon-emoji.fighter-emoji-fallback-hidden { display: none; }
.profile-stats { display: grid; gap: .35em; text-align: left; margin: 0; }
.profile-stats > div { display: flex; justify-content: space-between; gap: 1em; font-size: .92em; }
.profile-stats dt { color: var(--muted); }
.profile-stats dd { margin: 0; font-weight: 600; color: var(--gold); }

.profile-hero {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: .6em;
  margin-bottom: 1em;
}
.hero-stat {
  background: var(--panel2); border: 1px solid #3a3f4a; border-radius: 10px;
  padding: .6em .3em; text-align: center;
}
.hero-val { font-size: 1.35em; font-weight: 700; color: var(--gold); line-height: 1.1; }
.hero-label { font-size: .68em; color: var(--muted); margin-top: .2em; }

.profile-tabs { display: flex; gap: .4em; margin-bottom: .9em; border-bottom: 1px solid #3a3f4a; }
.profile-tab {
  flex: 1; background: none; border: none; color: var(--muted); cursor: pointer;
  padding: .5em .4em; font-size: .85em; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.profile-tab.active { color: var(--gold); border-bottom-color: var(--gold); font-weight: 600; }

.ach-progress { color: var(--muted); font-size: .8em; text-align: center; margin-bottom: .7em; }
.ach-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); gap: .6em;
  max-height: 46vh; overflow-y: auto; padding: .1em;
}
.ach-badge {
  display: flex; flex-direction: column; align-items: center; gap: .15em;
  padding: .5em .3em .4em; border-radius: 10px; border: 1px solid #3a3f4a;
  background: var(--panel2); cursor: default;
}
.ach-icon { width: 34px; height: 34px; }
.ach-icon svg { width: 100%; height: 100%; }
.ach-name { font-size: .65em; font-weight: 600; text-align: center; line-height: 1.15; }
.ach-tier { font-size: .55em; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }

.ach-badge.locked { opacity: .55; color: #6b7280; }
.ach-badge.locked .ach-icon { filter: grayscale(1) brightness(.7); }
.ach-badge.unlocked { color: var(--gold); border-color: currentColor; box-shadow: 0 0 10px rgba(224,176,76,.25); }
.ach-badge.unlocked.tier-easy { color: #cd7f32; }
.ach-badge.unlocked.tier-medium { color: #c0c0c0; }
.ach-badge.unlocked.tier-hard { color: #ffd700; }
.ach-badge.unlocked .ach-name { color: #f2f2f2; }

.fav-hand {
  display: flex; align-items: center; gap: .8em; margin-bottom: 1em;
  background: var(--panel2); border: 1px solid #3a3f4a; border-radius: 10px; padding: .6em .8em;
}
.fav-hand-cards { display: flex; gap: .3em; flex-shrink: 0; }
.fav-hand-info { text-align: left; }
.fav-hand-title { font-size: .78em; font-weight: 600; }
.fav-hand-stats { font-size: .72em; color: var(--muted); margin-top: .15em; }
.nemesis-card {
  display: flex; flex-direction: column; gap: .3em; margin-bottom: 1em;
  background: var(--panel2); border: 1px solid #3a3f4a; border-radius: 10px; padding: .6em .8em;
  text-align: left; font-size: .85em;
}
.nemesis-row b { color: var(--gold); }

.hist-list { display: flex; flex-direction: column; gap: .5em; max-height: 46vh; overflow-y: auto; padding: .1em; }
.hist-row {
  display: flex; align-items: center; gap: .6em; padding: .5em .6em;
  background: var(--panel2); border-radius: 8px; border-left: 3px solid transparent;
}
.hist-row.won { border-left-color: #3fae5c; }
.hist-row.lost { border-left-color: #b94a4a; }
.hist-cards, .hist-community { display: flex; gap: .2em; flex-shrink: 0; }
.hist-info { flex: 1; text-align: left; min-width: 0; }
.hist-amount { font-weight: 800; font-size: 1.05em; }
.hist-row.won .hist-amount { color: #4fce72; }
.hist-row.lost .hist-amount { color: #e07a7a; }
.hist-meta { font-size: .78em; color: var(--muted); margin-top: .15em; }
.hist-community { opacity: .85; }
.hist-community .playing-card, .hist-cards .playing-card { width: 34px; height: 47px; font-size: .78em; }
/* At this size a corner-positioned index reads as a stray number in the corner of an otherwise
   blank card — same reasoning as .log-cards, centered is what's actually legible in a list. */
.hist-community .pip.tl,
.hist-cards .pip.tl {
  position: static;
  font-size: 1.05em;
  line-height: 1.1;
}
.hist-community .pip.tl small,
.hist-cards .pip.tl small { font-size: .95em; margin-top: 1px; }

/* Mortal-Kombat-style finish for a heads-up all-in elimination — see main.js's showDuelAnimation.
   Deliberately not part of the normal render() tree: appended/removed straight from the socket
   handler as a one-shot, so it isn't torn down or replayed by an unrelated table re-render mid-clip
   the way anything living inside #app would be. */
.duel-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 45%, rgba(90,0,0,.55), rgba(5,4,6,.9));
  animation: duelFadeOut .4s ease-in 7.6s forwards;
}
/* No `gap` — the two fighters are pulled toward each other with negative margins instead (see
   winner/loser below) so the winner's attack clip (a swing/projectile reaching to its right) visibly
   connects with the loser standing right there, instead of swinging at empty air a whole fighter's
   width away. Sized down ~1.5x from the original (very pixelated at native-art resolution blown up
   large — "модели очень пиксельные"). */
.duel-vs { display: flex; align-items: center; justify-content: center; }
.duel-fighter {
  width: min(23vw, 6em); height: min(23vw, 6em);
  font-size: min(12vw, 3.5em); filter: drop-shadow(0 0 14px rgba(255,255,255,.5));
}
.duel-fighter.winner { animation: duelSlideInLeft .5s ease-out both; margin-right: -1.3em; }
/* No shatter/fade here anymore — that was an emoji-only-era effect (before there was a real death
   .gif) and, on top of the real death clip main.js swaps in, was fading the loser to invisible right
   as the death pose should be the whole point — exactly the "не успел понять, что произошло"
   complaint. The real death sprite does that storytelling now; nothing extra needed on top of it. */
.duel-fighter.loser { animation: duelSlideInRight .5s ease-out both; margin-left: -1.3em; }
.duel-vs-text {
  font-size: min(9vw, 2.2em); font-weight: 900; color: #ff3b3b;
  text-shadow: 0 0 14px #ff3b3b; animation: duelPulse .45s ease-in-out infinite;
}
.duel-fatality {
  margin-top: .35em; font-size: min(11vw, 2.6em); font-weight: 900; letter-spacing: .07em;
  color: #ffcf3b; text-shadow: 0 0 18px #ff3b3b, 0 0 34px #ff3b3b;
  animation: duelFatalityIn .5s ease-out 2.2s both;
}
.duel-names { margin-top: .3em; font-size: 1.05em; color: #fff; opacity: .92; text-align: center; padding: 0 1em; }
@keyframes duelSlideInLeft { 0% { transform: translateX(-45vw) scale(.5); opacity: 0; } 100% { transform: translateX(0) scale(1); opacity: 1; } }
@keyframes duelSlideInRight { 0% { transform: translateX(45vw) scale(.5); opacity: 0; } 100% { transform: translateX(0) scale(1); opacity: 1; } }
@keyframes duelPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.28); } }
@keyframes duelFatalityIn {
  0% { opacity: 0; transform: scale(2.2); }
  60% { opacity: 1; transform: scale(.9); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes duelFadeOut { to { opacity: 0; visibility: hidden; } }
