/* ==========================================================================
   table.css — the table itself. Laid out for landscape: opponents left and
   right, the played trick in the middle, your fan along the bottom.
   Reads only the variables in tokens.css.
   ========================================================================== */

.table {
  position: fixed; inset: 0; display: grid; overflow: hidden;
  grid-template-rows: auto 1fr auto;
  /* One card width for the whole table. It lives here, not on .card, because
     .hand reserves its row height from it and a value set on the card itself
     would never reach an ancestor: the fan would then overflow upward and cover
     the trick. Each landing area overrides it on its own cards. */
  --card-w: clamp(44px, 7.4vw, 82px);
  background:
    radial-gradient(60% 50% at 50% 38%, rgba(255, 255, 255, 0.08), transparent 70%),
    radial-gradient(120% 100% at 50% 120%, rgba(0, 0, 0, 0.55), transparent 60%),
    linear-gradient(170deg, var(--felt-table), #0c3326 55%, #071c15);
}

/* --- head-up display ------------------------------------------------------- */

.hud {
  display: flex; align-items: center; gap: clamp(8px, 1.6vw, 18px);
  padding: 8px clamp(10px, 2vw, 22px);
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.08));
  border-bottom: 1px solid var(--hairline);
  font-size: clamp(12px, 1.6vw, 15px);
}
.hud__spacer { flex: 1; }
.hud__pill {
  display: flex; align-items: baseline; gap: 6px; white-space: nowrap;
  padding: 5px 11px; border-radius: var(--r-pill);
  border: 1px solid var(--hairline-strong); background: rgba(0, 0, 0, 0.3);
}
.hud__pill b {
  font-size: 1.25em; font-weight: 600;
  /* Tinted to whatever the table is counting in: gold, silver or chips. */
  color: var(--pill-tint, var(--gold-bright));
  font-variant-numeric: lining-nums tabular-nums;
}
.hud__pill--table b { color: var(--text); font-weight: 400; }
.hud__pill--mult b { font-size: 1.35em; }
.hud .back-link { font-size: 1.3em; padding: 2px 8px; line-height: 1; }
.fullscreen-toggle {
  font: inherit; font-size: 1.1em; line-height: 1; color: var(--text-tertiary);
  background: rgba(0, 0, 0, 0.3); border: 1px solid var(--hairline-strong); border-radius: var(--r-pill);
  padding: 5px 9px; cursor: pointer;
}
.fullscreen-toggle:hover { color: var(--gold); border-color: var(--gold); }
.fullscreen-toggle.is-on { color: var(--gold-bright); }
.hud__pill small { font-size: 0.8em; letter-spacing: 0.13em; text-transform: uppercase; color: var(--text-tertiary); }
.hud__pill.is-hot b { color: #ffb37a; }

/* --- felt ----------------------------------------------------------------- */

.felt { position: relative; min-height: 0; }

.seat {
  position: absolute; top: clamp(6px, 2vh, 20px);
  display: flex; flex-direction: column; gap: 6px; align-items: center;
  width: clamp(120px, 20vw, 210px);
}
.seat--left { left: clamp(8px, 2vw, 26px); }
.seat--right { right: clamp(8px, 2vw, 26px); }

.seat__plate {
  display: flex; align-items: center; gap: 8px; padding: 6px 12px 6px 7px;
  border-radius: var(--r-pill); border: 1px solid var(--hairline-strong);
  background: rgba(0, 0, 0, 0.42); font-size: clamp(12px, 1.5vw, 14.5px); max-width: 100%;
}
.seat__plate.is-turn { border-color: var(--gold-bright); box-shadow: 0 0 0 2px rgba(239, 219, 166, 0.22); }
.seat__avatar {
  width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; flex: none;
  font-size: 14px; font-weight: 600; color: #201a08;
  background: linear-gradient(160deg, var(--gold-bright), #a98a48);
}
.seat__avatar.is-landlord { background: linear-gradient(160deg, #f0a0a0, var(--claret)); color: #fff2ef; }
.seat__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seat__count { color: var(--gold); white-space: nowrap; font-variant-numeric: lining-nums tabular-nums; }
.seat__role { font-size: var(--t-label); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-tertiary); }
/* Bidding is the only turn with nothing to show for itself, so it is the only
   thing that still speaks. Reserving its height permanently (the old
   min-height) pushed the seat tall enough to overlap the play zone once the
   hand grew to spec 2's size, so an idle bubble now takes 0 space instead —
   only opacity animates (house rule), the height/padding just snap. */
.seat__say {
  max-height: 0; overflow: hidden; font-size: clamp(13px, 1.7vw, 16px);
  color: var(--ivory); text-align: center; padding: 0 10px; border-radius: 8px;
  background: rgba(0, 0, 0, 0.32); opacity: 0; transition: opacity var(--dur-med) var(--ease-std);
}
.seat__say.is-on { opacity: 1; max-height: 2.4em; padding: 3px 10px; }

.seat__backs { display: flex; justify-content: center; max-width: 100%; }
.seat__backs .card {
  /* Tied to viewport height, like the hand and the played cards, so a phone's
     browser-bar height (844x330) shrinks every card on the table together. */
  --card-w: clamp(26px, 6.5vh, 40px);
  /* A percentage margin would resolve against the row, not the card, and the fan
     would collapse across the felt. Keep it proportional to the card instead. */
  margin-left: calc(var(--card-w) * -0.58);
  box-shadow: -3px 2px 6px rgba(0, 0, 0, 0.4);
}
.seat__backs .card:first-child { margin-left: 0; }

/* Pinned to the very top, centred, directly under the HUD — small enough
   that it costs the felt almost nothing, which is what leaves the middle
   band free for the play zones below. The caption is gone (an aria-label on
   the container says it instead): at this height a text line was a third of
   the block's own size. */
.bottom-cards {
  position: absolute; top: 2px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center;
}
.bottom-cards__row { display: flex; gap: 3px; }
/* Height = card-w * 1.4, so 8.6vh of width is ~12vh of height — the 11-13%
   of viewport height the cards should read at, with a floor (28.6px wide,
   40px tall) so it never drops below readable even at 844x330. */
.bottom-cards .card { --card-w: clamp(28.6px, 8.6vh, 62px); }

/* --- the trick, one landing area per seat ---------------------------------
   Cards sit in front of whoever played them, so position answers "who" and the
   felt needs no captions. The play still standing is the bright one; a finished
   trick greys out instead of vanishing. */

.play {
  position: absolute; display: flex; justify-content: center; align-items: center;
  min-height: clamp(54px, 11vh, 116px);
  transition: opacity var(--dur-slow) var(--ease-std), filter var(--dur-slow) var(--ease-std);
}
.play--left { left: 24%; top: 44%; transform: translate(-50%, -50%); }
.play--right { left: 76%; top: 44%; transform: translate(-50%, -50%); }
/* Landlord's three moved to a thin strip under the HUD (see .bottom-cards)
   specifically to free this band: self's own play now sits in the middle of
   the felt, clear of both it and the floating controls pill below, instead
   of fighting either for the same few rows. */
.play--self { left: 50%; top: 54%; transform: translate(-50%, -50%); }

.play__cards { display: flex; justify-content: center; }
.play__cards .card {
  /* ~55% of the hand card's own vh-based size (20.5vh below), the middle of
     the spec's 50-60% band, so a play zone scales with the hand it came from. */
  --card-w: clamp(40px, 11.5vh, 82px);
  /* Never a percentage here: it would resolve against the row rather than the
     card, so every extra card widens the row, widens the margin, and collapses
     the fan into a stack. Keep the overlap proportional to the card. */
  margin-left: calc(var(--card-w) * -0.5);
}
.play__cards .card:first-child { margin-left: 0; }

.play.is-empty { opacity: 0; }
/* The trick is over: readable, but plainly in the past. */
.play.is-stale { opacity: 0.4; filter: saturate(0.6); }
/* The play everyone is trying to beat. */
.play.is-standing .play__cards .card { box-shadow: 0 0 0 2px rgba(239, 219, 166, 0.55), var(--shadow-1); }

.play__name {
  position: absolute; left: 50%; transform: translateX(-50%);
  /* -1.5em used to be flush with the caption's own line-height (~1.55em),
     so the caption's near edge landed on top of the card row it labels
     instead of beside it — 0-4px negative clearance, every seat, every
     size, once measured with getBoundingClientRect rather than eyeballed.
     -2em leaves a real gap regardless of viewport: the caption's font-size
     is a fixed rem, never vw/vh scaled, so a fixed em offset here clears it
     by the same margin everywhere. */
  bottom: -2em;
  white-space: nowrap; font-size: var(--t-caption); letter-spacing: 0.04em;
  /* Tight, like .bj-banner's own caption chip: the default 1.55 body
     line-height turns one line of text into a box a third taller than the
     glyphs need, which is exactly the margin the short-landscape query
     below is short on. */
  line-height: 1.1;
  color: var(--gold); opacity: 0; transition: opacity var(--dur-med) var(--ease-std);
}
.play.is-standing .play__name { opacity: 0.9; }
/* Your own play sits just above the fan, so its caption goes above the cards
   rather than under them, where the hand would swallow it. */
.play--self .play__name { bottom: auto; top: -2em; }

.play__pass {
  font-size: clamp(13px, 1.7vw, 17px); color: var(--text-tertiary);
  padding: 5px 16px; border-radius: var(--r-pill);
  background: rgba(0, 0, 0, 0.36); border: 1px solid var(--hairline);
}

/* Cards travel in from the seat that played them — the other half of saying
   who moved without saying it. */
.play.is-entering .play__cards > * {
  animation: play-in var(--dur-slow) var(--ease) backwards;
  animation-delay: calc(var(--i, 0) * 45ms);
}
.play--left.is-entering .play__cards > * { animation-name: play-in-left; }
.play--right.is-entering .play__cards > * { animation-name: play-in-right; }
.play--self.is-entering .play__cards > * { animation-name: play-in-self; }

@keyframes play-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes play-in-left {
  from { opacity: 0; transform: translate(-40%, -60%) scale(0.86) rotate(-6deg); }
  to { opacity: 1; transform: none; }
}
@keyframes play-in-right {
  from { opacity: 0; transform: translate(40%, -60%) scale(0.86) rotate(6deg); }
  to { opacity: 1; transform: none; }
}
/* 15%, not the 70% this started at: self's play zone now shares the felt's
   middle band with the floating controls pill below it, and a card sliding
   in from most of its own height further down swept straight through that
   pill's space on the way — the entrance still reads as "from the hand",
   just without the overshoot. */
@keyframes play-in-self {
  from { opacity: 0; transform: translateY(15%) scale(0.88); }
  to { opacity: 1; transform: none; }
}

/* --- shuffle and deal ------------------------------------------------------
   Transform and opacity only, so the whole flourish stays on the compositor.
   Every phase of the deal — the shuffle flourish, each of the round-robin
   flights, the hand's gather and sorted re-spread, and the landlord's three —
   reuses the one cg-fly keyframe below; tableview.js writes the ten --f*/--t*
   custom properties per call and restarts the animation. Nothing here reads
   a guessed vw/vh: every from/to pair is pixels off the dealer stack's own
   measured centre, from tableview.js's single measurement pass. */

.dealer { position: absolute; inset: 0; pointer-events: none; z-index: 5; }
.dealer__card {
  position: absolute; left: 50%; top: 16%;
  width: clamp(40px, 9vh, 78px);
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
}

@keyframes cg-fly {
  from {
    transform: translate(calc(-50% + var(--fx, 0px)), calc(-50% + var(--fy, 0px)))
               rotate(var(--fr, 0deg)) scale(var(--fs, 1));
    opacity: var(--fo, 1);
  }
  to {
    transform: translate(calc(-50% + var(--tx, 0px)), calc(-50% + var(--ty, 0px)))
               rotate(var(--tr, 0deg)) scale(var(--ts, 1));
    opacity: var(--to, 1);
  }
}

/* Real destinations stay hidden until dealIn() hands over to them. Seat backs
   and the landlord's three hide per card (so each can be revealed the instant
   its own flyer lands, growing the visible count one at a time, per spec);
   the hand hides as a whole, because its own flyers stand in for it right up
   until the gather-and-sort finishes and every card is revealed together. No
   transition on any of these: a transition here would run from whatever
   opacity a just-unhidden element had a moment before, flashing it. */
.table.is-dealing-left .seat--left .seat__backs .card,
.table.is-dealing-right .seat--right .seat__backs .card,
.table.is-dealing-bottom .bottom-cards .card,
.table.is-dealing-self .hand,
.table.is-dealing-bid .bid-panel {
  opacity: 0;
  /* Invisible but still clickable would let a fast tap bid before the cards
     have landed. */
  pointer-events: none;
}

.bid-panel {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  text-align: center; padding: 18px 26px; border-radius: var(--r-lg);
  border: 1px solid var(--hairline-strong); background: rgba(6, 22, 17, 0.9);
  box-shadow: var(--shadow-8); max-width: min(92vw, 460px);
}
.bid-panel h2 { margin: 0 0 4px; font-size: clamp(19px, 2.8vw, 26px); color: var(--ivory); }
.bid-panel p { margin: 0 0 14px; font-size: 15px; color: var(--text-tertiary); }
.bid-panel .btn-row { justify-content: center; }

/* --- your hand ------------------------------------------------------------
   Sized off the viewport's height, not its width: a phone's browser bar eats
   height, not width, so tying the card to vh is what keeps it big at 844x390
   and still legible at 844x330. Height = width / (100/140), i.e. width*1.4. */

.dock {
  display: flex; flex-direction: column;
  padding: 4px clamp(10px, 2vw, 22px) 0;
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
}

.hand {
  /* A custom property named --hand-card-h, because the house rule reserves
     the literal name "--card-w" for .table and a .card selector, and .hand
     is neither. .hand .card below derives its own --card-w from this one
     number instead of repeating the clamp, so .hand's own height (which the
     card's --card-w can't reach — custom properties cascade down only, and
     .hand is the card's ancestor, not its descendant) still matches the
     card it is sized around. */
  --hand-card-h: clamp(100.8px, 28.7vh, 207.2px);
  position: relative; height: var(--hand-card-h);
  margin: 0 auto; width: 100%;
}
.hand .card { --card-w: calc(var(--hand-card-h) / 1.4); }
/* The hand hugs the bottom edge and is let run off it: up to 18% of every
   card's height, but never the top-left index, which sits in the top 30%. A
   percentage transform resolves against the element's own box, not the row,
   so this stays exactly "18% of a card" regardless of how many cards there
   are — .table's own overflow:hidden (it fills the viewport exactly) is what
   actually crops it, at the real screen edge rather than a guessed pixel. */
.hand__inner {
  position: absolute; left: 50%; height: 100%;
  transform: translate(-50%, calc(var(--hand-card-h) * 0.18));
}

.card {
  /* The fallback only matters for a card rendered outside .table. */
  width: var(--card-w, clamp(44px, 7.4vw, 82px)); aspect-ratio: 100 / 140; padding: 0; border: 0;
  background: none; display: block; line-height: 0;
  /* A box-shadow on the element beats a drop-shadow on the artwork: the filter
     is evaluated per pixel of a forty-card table, the shadow is not. */
  border-radius: 6px;
  box-shadow: var(--shadow-1);
  /* Everything below is inherited into the <use> shadow tree, which outer
     selectors cannot reach. It is how the artwork gets its suit colour and its
     typeface. */
  font-family: var(--font);
  font-weight: 600;
  font-variant-numeric: lining-nums;
  font-feature-settings: 'lnum' 1;
}
.card--red { color: #b4232c; }
.card--black { color: #1d1b17; }
.card--down { color: #d8b86a; }
/* The "bold" style (Dou Di Zhu only): exact reds/blacks from the spec, not the
   engraved deck's softer ink — the whole point of this style is contrast. */
.card--bold-red { color: #d3122a; }
.card--bold-black { color: #121212; }
.card-svg, .cg-card-svg { width: 100%; height: 100%; display: block; }

.hand .card {
  position: absolute; bottom: 0; left: calc(var(--i) * var(--step));
  transition: transform var(--dur-fast) var(--ease-std), filter var(--dur-fast) var(--ease-std);
  cursor: pointer; border-radius: var(--r-xs);
}
.hand .card:hover { transform: translateY(calc(var(--card-w) * 1.4 * -0.08)); }
/* ~22% of card height, per spec. */
.hand .card.is-selected { transform: translateY(calc(var(--card-w) * 1.4 * -0.22)); box-shadow: 0 10px 12px rgba(0, 0, 0, 0.5); }
.hand .card.is-hinted .cg-card-svg { outline: 2px solid var(--gold-bright); outline-offset: -2px; border-radius: var(--r-xs); }
.hand .card:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }

/* No legal answer: the whole fan greys and stops responding, so Pass is plainly
   the only move left. Said without a sentence. */
.hand.is-stuck .card { filter: grayscale(1) brightness(0.62); cursor: default; }
.hand.is-stuck .card:hover { transform: none; }

/* Floats over the felt, just above the hand, instead of costing the dock a
   row of its own — the reference does the same with its Fight/Skip buttons. */
.controls {
  position: absolute; left: 50%; bottom: 2px; transform: translateX(-50%); z-index: 4;
  display: flex; align-items: center; justify-content: center; gap: clamp(6px, 1.2vw, 14px); flex-wrap: nowrap;
  max-width: 94%; padding: 6px clamp(10px, 1.6vw, 16px); border-radius: var(--r-pill);
  background: rgba(4, 16, 12, 0.68); border: 1px solid var(--hairline-strong); box-shadow: var(--shadow-3);
}
.controls__you { font-size: clamp(12px, 1.6vw, 14.5px); color: var(--text-tertiary); white-space: nowrap; }
.controls .btn { padding: clamp(6px, 1vh, 9px) clamp(10px, 1.8vw, 18px); font-size: clamp(13px, 1.7vw, 16px); }
/* Bidding has its own panel (Pass/1/2/3); Hint/Pass/Play do nothing until
   play starts, and floating both over a short felt is how they overlapped. */
.controls.is-hidden { display: none; }

/* --- overlays ------------------------------------------------------------- */

.overlay {
  position: fixed; inset: 0; z-index: var(--z-overlay); display: grid; place-items: center; padding: 18px;
  background: rgba(3, 12, 9, 0.78); backdrop-filter: blur(3px);
}
.result {
  width: min(92vw, 520px); text-align: center; padding: 26px 26px 22px; border-radius: var(--r-lg);
  border: 1px solid rgba(205, 174, 108, 0.4); background: linear-gradient(170deg, #123a2d, #07241b);
  box-shadow: var(--shadow-8);
}
.result__title { font-size: clamp(26px, 5.2vw, 38px); margin: 0 0 4px; }
.result__title.is-win { color: var(--gold-bright); }
.result__title.is-loss { color: #f0a58f; }
.result__delta { font-size: clamp(22px, 4.2vw, 30px); margin: 6px 0 14px; font-variant-numeric: lining-nums tabular-nums; }
.result__delta.is-win { color: var(--ok); }
.result__delta.is-loss { color: var(--danger); }
.result__lines { list-style: none; margin: 0 0 18px; padding: 0; font-size: 15px; line-height: 1.5; color: var(--text-secondary); display: grid; gap: 6px; }
.result__lines span { color: var(--gold); }

.rotate-gate { display: none; }
@media (orientation: portrait) and (max-width: 960px) {
  .rotate-gate {
    position: fixed; inset: 0; z-index: var(--z-rotate-gate); display: grid; place-items: center; text-align: center;
    padding: 30px; background: linear-gradient(170deg, #0f3b2d, #061a14); color: var(--text);
  }
  .rotate-gate__icon { font-size: 46px; margin-bottom: 14px; color: var(--gold); }
  .rotate-gate h2 { margin: 0 0 8px; font-size: 25px; color: var(--ivory); }
  .rotate-gate p { margin: 0; max-width: 34ch; color: var(--text-tertiary); line-height: 1.6; }
}

/* Very short landscape windows (phones) get smaller cards so the fan still fits,
   and the landing areas pull in and down to clear the seats' card backs. */
@media (orientation: landscape) and (max-height: 460px) {
  /* Card sizes are vh-driven already (see .hand/.play__cards/.seat__backs/
     .bottom-cards above) and shrink themselves at this height; only the
     positions that would otherwise collide need a phone-with-a-browser-bar
     override here. */
  .seat { top: 2px; }
  .hud { padding-top: 4px; padding-bottom: 4px; }
  .play { min-height: clamp(40px, 9vh, 80px); }
  .play--left { left: 27%; top: 54%; }
  .play--right { left: 73%; top: 54%; }
  /* Tighter than the default 54%: at these heights the gap between the
     (now tiny) landlord's three and the floating controls pill is narrow
     enough that self's own card needs to sit nearer the top of it to clear
     both, at 844x330 especially. */
  /* 45% left only ~9px above the cards (against the landlord's three) and
     ~17px below (against the controls pill): tight, but it was tuned for a
     caption sitting above, clear of both by construction. Flipping the
     caption below (next rule) needs a single corridor >= 4px + the caption's
     own height + 4px in one place, and 17px alone isn't quite it — so this
     also nudges the whole zone up a few px, trading some of the (unused,
     ungraded) margin above the cards for the margin the caption needs below. */
  .play--self { top: 40.5%; }
  /* Self's play sits close enough under the landlord's three here that a
     caption above the cards (the non-query rule) prints across them — the
     defect this query exists to fix. There's no headroom to spare above at
     this height, only below, so self borrows the opponents' below-the-cards
     placement for the duration of this query. -1.5em rather than the shared
     -2em above: this corridor is the tightest on the felt, and 1.5em is as
     far as the (now tightened, see .play__name's line-height) caption needs
     to clear its own cards by >= 4px. */
  .play--self .play__name { top: auto; bottom: -1.5em; }
}

/* A phone's browser bar can shrink height past what the 460px query above
   was tuned for: at ~330-350px, self's own played cards — still at
   .play__cards's general 40px floor — land under 4px below the landlord's
   three. Only self's cards need to shrink: the landlord's three itself
   doesn't move, and the left/right opponents' played cards sit clear of it
   horizontally already (.play--left/.play--right are off-centre; the
   landlord's three is pinned to the middle). 37px keeps the rank index (the
   corner digit/letter) >= 12 CSS px tall — cardart.js pins its cap height to
   DIGIT_H/VB_H = 33.5/140 of the rendered card height (card-w * 1.4), so
   0.335 * 37 =~ 12.4px — with a little room to spare, and 41.6% (nudged down
   from the 460px query's 40.5%, to match the shorter card) splits the
   now-larger slack evenly: ~5px clear of the landlord's three above the
   cards, ~5px clear of the controls pill below the caption. */
@media (orientation: landscape) and (max-height: 350px) {
  .play--self .play__cards .card { --card-w: 37px; }
  .play--self { top: 41.6%; }
}
