/* =========================================================
   XXII: Ex Tenebris — GAME APP STYLES
   ========================================================= */

.game-body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.game-nav {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 28px;
  /* No rule under the bar. Over the lobby's video it read as a hard black line
     cutting the screen in two, which is the opposite of what a chromeless
     top bar is for. The HUD chips already carry their own edges. */
}
.game-nav .brand { font-family: var(--serif); font-size: 20px; letter-spacing: 0.24em; color: #fff; }
.game-nav .brand span { color: var(--gold); }

/* ---------- Nav menu icon + overlay (game.html only) ---------- */
.nav-menu-btn {
  width: 31px; height: 31px; border-radius: 50%; border: 1px solid var(--line);
  background: rgba(255,255,255,0.02); display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px; cursor: pointer; transition: border-color 0.3s, background 0.3s;
}
.nav-menu-btn:hover { border-color: var(--line-gold); background: rgba(197,164,96,0.06); }
.nav-menu-btn span { width: 12px; height: 1px; background: var(--muted); transition: background 0.3s; }
.nav-menu-btn:hover span { background: var(--gold-bright); }

.nav-overlay {
  position: fixed; inset: 0; z-index: 500; display: none;
  align-items: center; justify-content: center;
  background: rgba(5,5,6,0.86); backdrop-filter: blur(4px);
}
.nav-overlay.open { display: flex; animation: navOverlayIn 0.25s var(--ease); }
@keyframes navOverlayIn { from { opacity: 0; } to { opacity: 1; } }
.nav-overlay-card {
  display: flex; flex-direction: column; gap: 14px; width: min(280px, 84vw);
  padding: 34px 28px; border: 1px solid var(--line-gold); border-radius: 14px;
  background: linear-gradient(170deg, var(--panel-2), var(--panel));
  box-shadow: 0 30px 70px -20px rgba(0,0,0,0.9), 0 0 40px -14px rgba(107,63,160,0.4);
  animation: navCardIn 0.28s var(--ease);
}
@keyframes navCardIn { from { opacity: 0; transform: scale(0.92) translateY(10px); } to { opacity: 1; transform: scale(1) translateY(0); } }
.nav-overlay-card .btn { width: 100%; justify-content: center; }

/* ---------- Loading screen ---------- */
.loading-screen {
  position: fixed; inset: 0; z-index: 600; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 22px; background: #050506;
  transition: opacity 0.5s var(--ease), visibility 0.5s;
}
.loading-screen.hide { opacity: 0; visibility: hidden; pointer-events: none; }
.loading-logo { width: 64px; height: 64px; animation: loadingPulse 1.6s ease-in-out infinite; }
@keyframes loadingPulse {
  0%, 100% { opacity: 0.55; filter: drop-shadow(0 0 6px rgba(197,164,96,0.3)); transform: scale(1); }
  50% { opacity: 1; filter: drop-shadow(0 0 22px rgba(197,164,96,0.7)); transform: scale(1.06); }
}
.loading-text {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--gold-dim); transition: opacity 0.3s;
}

.game-stage { flex: 1; display: flex; align-items: center; justify-content: center; padding: 60px 24px; }
/* The hub does NOT vertically centre. Centring floats the whole screen in the
   middle of whatever height the browser happens to give it — on a real laptop
   (macOS menu bar + tab strip trims a "900px" display down to ~816px, and many
   laptops are 768px tall to begin with) that produced a large dead gap above
   the cards and pushed the row of utility chips past the fold, forcing a
   scroll a taller screen never showed. Anchored to the top, the layout is the
   same regardless of how much chrome the browser is wearing. */
#screenHub.active { align-self: flex-start; margin-top: 0; }
/* Both edges, not just the top — the shorthand above still set a 60px BOTTOM
   padding that nothing was using once the screen stopped centring, and it was
   most of what pushed short laptops into a scroll. */
.game-stage:has(#screenHub.active) {
  align-items: flex-start;
  padding-top: clamp(16px, 2.6vh, 30px);
  padding-bottom: clamp(14px, 2.2vh, 26px);
}

/* styles.css sets a global `section { padding: 120px 0 }` for the landing
   page's bands, and every game screen is a <section>, so each one was carrying
   240px of invisible padding. That is what actually created the dead gap above
   the lobby and forced the scroll — .game-stage already supplies its own
   padding. This trap is documented in HANDOFF and it bit twice. */
.game-screen { display: none; width: 100%; max-width: 760px; margin: 0 auto; padding: 0; text-align: center; }
.game-screen.active { display: block; animation: fade 0.5s var(--ease); }

/* ---------- Onboarding ---------- */
#screenOnboard .eyebrow { display: block; }
#screenOnboard h2 { font-family: var(--serif); font-weight: 500; font-size: clamp(1.8rem, 4vw, 2.6rem); color: #fff; margin: 18px 0 14px; }
#screenOnboard p { color: var(--muted); max-width: 480px; margin: 0 auto 34px; }

.result-row { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin: 30px 0; }
.result-card {
  position: relative; overflow: hidden;
  width: 150px; padding: 20px 14px; border-radius: 12px;
  border: 1px solid var(--el-color, var(--line-gold));
  background-image:
    linear-gradient(to top, rgba(5,5,6,0.92) 0%, rgba(5,5,6,0.22) 52%, rgba(5,5,6,0.4) 100%),
    var(--art-url, none),
    linear-gradient(170deg, var(--panel-2), var(--panel));
  background-size: cover, cover, cover;
  background-position: center, center, center;
  animation: rise 0.6s var(--ease) both;
}
.rc-roman { font-family: var(--serif); font-size: 1.8rem; color: var(--el-color); }
.rc-name { font-family: var(--serif); font-size: 1rem; color: #fff; margin-top: 8px; }
.rc-rarity { font-family: var(--mono); font-size: 9px; letter-spacing: 0.15em; text-transform: uppercase; color: var(--faint); margin-top: 6px; }

.draw-note { font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em; color: var(--faint); text-align: center; margin-top: 14px; }
.draw-levelup { color: var(--gold-bright); }

/* Daily Draw reveal — face-down card flips to reveal, text fades in after. */
.draw-flip-stage { margin: 8px auto 0; width: 190px; }
.draw-flip-stage .flip-card { width: 190px; }
.draw-reveal-text { opacity: 0; transition: opacity 0.5s var(--ease); margin-top: 18px; }
.draw-reveal-text.show { opacity: 1; }

/* ---------- Hub ---------- */
.hub-stats { display: flex; gap: 20px; justify-content: center; margin: 30px 0 40px; flex-wrap: wrap; }
.stat-pill {
  padding: 14px 26px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel); min-width: 140px;
}
.stat-pill .v { font-family: var(--serif); font-size: 1.7rem; color: var(--gold-bright); }
.stat-pill .k { font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint); margin-top: 4px; }

.hub-actions { display: flex; flex-direction: column; gap: 14px; max-width: 380px; margin: 0 auto; }
.hub-actions .btn { width: 100%; justify-content: center; padding: 18px; font-size: 13px; }
.hub-actions .btn:disabled { opacity: 0.35; cursor: not-allowed; }
.hub-actions .btn:disabled:hover { border-color: var(--line); color: var(--text); background: transparent; }
.hub-secondary-row { display: flex; gap: 10px; }
.hub-secondary-row .btn { flex: 1; }

/* ---------- Arcade tiers ---------- */
.tier-list { display: flex; flex-direction: column; gap: 14px; margin-top: 30px; }
.tier-card {
  text-align: left; padding: 22px 24px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel); cursor: pointer; transition: all 0.3s;
  display: flex; justify-content: space-between; align-items: center; gap: 16px;
}
.tier-card:hover { border-color: var(--line-gold); background: rgba(197,164,96,0.05); }
.tier-card h4 { font-family: var(--serif); font-size: 1.2rem; color: #fff; font-weight: 500; }
.tier-card p { color: var(--muted); font-size: 0.85rem; margin-top: 4px; }
.tier-reward { font-family: var(--mono); font-size: 12px; color: var(--gold-bright); white-space: nowrap; }
.tier-card.locked { cursor: not-allowed; opacity: 0.5; filter: grayscale(0.6); }
.tier-card.locked:hover { border-color: var(--line); background: var(--panel); }
.tier-lock-hint { font-family: var(--mono); font-size: 10px; letter-spacing: 0.04em; color: var(--fire); margin-top: 8px; text-transform: uppercase; }

/* ---------- Deck picker ---------- */
.pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 14px; margin: 30px 0; }
.pick-card {
  position: relative; overflow: hidden;
  padding: 16px 10px; border-radius: 10px; border: 1px solid var(--line);
  aspect-ratio: 3 / 5; /* same scale as every other card (source art ratio) */
  display: flex; flex-direction: column; justify-content: flex-end;
  background-image:
    linear-gradient(to top, rgba(5,5,6,0.92) 0%, rgba(5,5,6,0.22) 52%, rgba(5,5,6,0.4) 100%),
    var(--art-url, none),
    linear-gradient(170deg, var(--panel-2), var(--panel));
  background-size: cover, cover, cover;
  background-position: center, center, center;
  cursor: pointer; transition: all 0.3s; color: var(--text);
}
.pick-card:hover { border-color: var(--el-color); }
/* `background:` shorthand here used to RESET background-image — a selected
   card instantly lost its art and became a flat colour swatch (visible in the
   deck picker and the Ritual grid). Only longhand properties that don't touch
   the image are safe on this element. */
/* No pseudo-element for the tint either — ::after on .pick-card is already
   taken by the rarity frame's border-image. Inset shadows carry both the ring
   and the wash without touching art or frame. */
.pick-card.picked {
  border-color: var(--el-color);
  box-shadow: 0 0 0 2px var(--el-color) inset,
              inset 0 0 44px color-mix(in srgb, var(--el-color) 26%, transparent),
              0 0 18px -4px var(--el-color);
}
.pick-card.locked { filter: grayscale(1) brightness(0.55); opacity: 0.7; cursor: default; }
.pick-card.locked:hover { border-color: var(--line); }
.pc-roman { font-family: var(--serif); font-size: 1.3rem; color: var(--el-color); }
.pc-name { font-family: var(--serif); font-size: 0.85rem; margin-top: 6px; }
.pick-count { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; color: var(--faint); text-transform: uppercase; margin-bottom: 10px; display: block; }

/* Selected-slots preview strip — one box per required pick, filled with the
   chosen card's art as they're picked, dim logo watermark while empty. */
.slot-preview { display: flex; gap: 10px; justify-content: center; margin-bottom: 22px; flex-wrap: wrap; }
.slot-box {
  position: relative; width: 52px; aspect-ratio: 3 / 5; border-radius: 6px; overflow: hidden;
  border: 1px solid var(--line); background: var(--panel);
  background-image: var(--art-url, none); background-size: cover; background-position: center;
  transition: border-color 0.3s;
}
.slot-box.filled { border-color: var(--el-color, var(--line-gold)); }
.slot-box img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 46%; opacity: 0.18; }
.slot-box.filled img { display: none; }

/* ---------- Battle (flat HTML/CSS grid) ----------
   #screenBattle breaks out of the normal centered/max-width game-screen
   layout to fill .game-stage edge to edge, full viewport, no scroll. */
.game-stage { position: relative; }
#screenBattle.active {
  position: absolute; inset: 0; display: block;
  max-width: none; margin: 0; padding: 0; text-align: left; overflow: hidden;
}
.battle-board {
  position: absolute; inset: 0; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  padding: 88px 4vw 118px;
  background:
    linear-gradient(180deg, rgba(5,5,6,0.65) 0%, rgba(5,5,6,0.1) 20%, rgba(5,5,6,0.1) 78%, rgba(5,5,6,0.75) 100%),
    radial-gradient(ellipse at center, rgba(107,63,160,0.1) 0%, rgba(10,10,13,0.25) 60%, rgba(5,5,6,0.55) 100%),
    #050506;
  perspective: 1100px; perspective-origin: 50% 10%;
}
.battle-board.shake { animation: boardShake 0.4s var(--ease); }
.battle-board.shake-big { animation: boardShakeBig 0.42s var(--ease); }
@keyframes boardShake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-6px); } 75% { transform: translateX(6px); } }
@keyframes boardShakeBig { 0%,100% { transform: translate(0,0); } 20% { transform: translate(-10px,2px); } 40% { transform: translate(9px,-2px); } 60% { transform: translate(-7px,1px); } 80% { transform: translate(6px,-1px); } }

/* ---------- 3D scene — surface (board art) and table (cards) share ONE
   rotated plane, so the board texture tilts exactly in sync with the cards
   sitting on it instead of looking like a flat backdrop cards float over. */
.battle-3d {
  /* The tilt lives in a variable because it now appears in three places — the
     resting transform, the zoom-punch keyframes, and the preview shift. A bare
     `scale()` or `translateY()` written without re-declaring rotateX would
     REPLACE the tilt and flatten the whole scene for the duration, so every
     rule that touches this transform must restate the tilt from this var. */
  --board-tilt: 64deg;
  --board-shift: 0px;
  width: 100%; max-width: 780px; height: 100%; position: relative; z-index: 2;
  transform-style: preserve-3d;
  transform: translateY(var(--board-shift)) rotateX(var(--board-tilt));
  transition: transform 0.34s var(--ease);
}
/* Camera push-in on a super-effective hit. */
.battle-3d.zoom-punch { animation: zoomPunch 0.4s var(--ease); }
@keyframes zoomPunch {
  0%   { transform: translateY(var(--board-shift)) scale(1) rotateX(var(--board-tilt)); }
  24%  { transform: translateY(var(--board-shift)) scale(1.055) rotateX(var(--board-tilt)); }
  100% { transform: translateY(var(--board-shift)) scale(1) rotateX(var(--board-tilt)); }
}
.battle-surface {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 160%; height: 230%; z-index: 1;
  background: url('../assets/battle-board.webp') center / cover no-repeat, #0d0b12;
  border-radius: 4%;
  -webkit-mask-image: radial-gradient(ellipse 62% 55% at center, black 55%, transparent 88%);
  mask-image: radial-gradient(ellipse 62% 55% at center, black 55%, transparent 88%);
  /* Purely decorative, but it's a large plane sharing the SAME 3D-rotated
     space as the cards (both children of .battle-3d) — real depth-sorted
     hit-testing in that shared space could pick this oversized backdrop
     over a card at some pixels, silently swallowing clicks meant for the
     card underneath/behind it. Taking it out of hit-testing entirely fixes
     that regardless of depth ambiguity. */
  pointer-events: none;
}
.battle-table {
  width: 100%; max-width: 780px; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); z-index: 2;
  display: flex; flex-direction: column; gap: 58px;
  transform-style: preserve-3d;
}
.battle-divider {
  position: absolute; left: 6%; right: 6%; top: 50%; height: 2px; transform: translateY(-50%);
  background: linear-gradient(90deg, transparent, var(--line-gold), transparent); opacity: 0.5;
}

/* ---------- Card rows — dynamic column count per tier (3v3/4v4/5v5) ---------- */
.battle-row {
  display: grid; grid-template-columns: repeat(var(--slot-count, 3), minmax(0, 1fr));
  gap: clamp(10px, 2.2vw, 26px); justify-items: center; align-items: end;
  width: 100%; position: relative; z-index: 2; transform-style: preserve-3d;
}
.enemy-row { align-items: start; }

/* turn banner */
.turn-banner {
  position: absolute; top: 18px; left: 50%; transform: translateX(-50%);
  text-align: center; padding: 9px 20px; width: max-content; max-width: min(340px, 80vw);
  font-family: var(--serif); font-size: 1.15rem; letter-spacing: 0.14em; color: var(--gold-bright);
  border-top: 1px solid var(--line-gold); border-bottom: 1px solid var(--line-gold);
  background: linear-gradient(90deg, transparent, rgba(10,10,13,0.75), transparent);
  backdrop-filter: blur(2px); z-index: 5;
  pointer-events: none; /* display-only, and it sits over the enemy row on short viewports */
}
.turn-banner.enemy { color: var(--fire); border-color: rgba(217,102,63,0.4); }
.turn-banner.flash { animation: bannerFlash 0.6s var(--ease); }
@keyframes bannerFlash { from { opacity: 0.25; transform: translateX(-50%) scale(0.97); } to { opacity: 1; transform: translateX(-50%) scale(1); } }

/* battle prompt — reads "your turn / tap a card / the dark takes its turn" */
.battle-prompt {
  position: absolute; bottom: 108px; left: 50%; transform: translateX(-50%);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--gold-dim); text-align: center; text-shadow: 0 1px 6px rgba(0,0,0,0.8);
  width: max-content; max-width: 90vw; z-index: 5;
  /* Pure instruction text with no listener, but at 1024x768 it lies across the
     middle player card and was eating ~3% of that card's taps. */
  pointer-events: none;
}

.battle-log {
  position: absolute; bottom: 0; left: 0; right: 0;
  max-height: 96px; margin: 0; padding: 12px 18px; overflow-y: auto;
  border-top: 1px solid var(--line); background: rgba(10,10,13,0.82); backdrop-filter: blur(3px);
  font-family: var(--mono); font-size: 12px; color: var(--muted); line-height: 1.7;
  z-index: 5;
}
.battle-log div:last-child { color: var(--gold-bright); }

/* ---------- Battle card slot — a FIXED-size grid item. Pure layout, no
   listeners (see .bf-hitzone below for why click lives on a separate, inset
   element instead). The card fills it exactly, always — cards never resize
   or move themselves. */
.bf-slot {
  width: 100%; max-width: 150px; aspect-ratio: 3 / 5; position: relative;
  transform-style: preserve-3d;
}
/* Click target — the WHOLE card, and the only thing in the slot that takes
   pointer events at all.

   This used to be inset 16% on each side, to stop a click near a shared edge
   landing on the neighbouring card. That was a real problem back when
   selecting a card made it grow in place: an expanded card genuinely did
   overlap its neighbours. The expand behaviour was removed (cards never
   resize themselves now, see below) but the inset that compensated for it
   stayed behind, and it cost a third of every card: a sweep of every visible
   pixel of every card, at every viewport, found only 64-67% of a card
   responded to a click — and the dead part was the left/right band, exactly
   where a finger lands when tapping a card in a row.

   At inset 0 the same sweep reports 100% coverage and ZERO clicks landing on
   the wrong card, at 3v3, 4v4 and 5v5 alike. Hit-testing here runs against
   each slot's real (tilted, trapezoidal) quad, not an axis-aligned box, so
   neighbours simply do not overlap — as long as nothing scales a card past
   its own slot. Keep it that way: see the note on .bf-card.targetable. */
.bf-hitzone {
  position: absolute; inset: 0;
  z-index: 5; cursor: default;
}
.bf-slot:has(.bf-card.selectable) .bf-hitzone,
.bf-slot:has(.bf-card.targetable) .bf-hitzone { cursor: pointer; }

/* ---------- Battle card (.bf-card) — always compact. Tapping it shows full
   detail in the separate #battleDetailPanel; selecting it as an attacker
   only adds a border/glow highlight (see .bf-card.expanded below). ---------- */
.bf-card {
  appearance: none; -webkit-appearance: none;
  position: absolute; inset: 0; overflow: visible;
  border-radius: 10px;
  border: 1px solid var(--line); background: none; padding: 0; cursor: default;
  color: var(--text); text-align: left; font: inherit;
  transform-style: preserve-3d;
  transform-origin: center bottom;
  /* Purely visual — every click goes to .bf-hitzone, which is the only element
     in the slot with a listener. Without this the card competes for the same
     pixels, and a card that is momentarily transformed (mid-lunge it carries
     translateZ(58px), which puts it nearer the camera than any hitzone) would
     swallow the click or, worse, cover a NEIGHBOUR's hitzone. */
  pointer-events: none;
  /* Grounding shadow — painted as part of the card's own (tilted) plane, so it
     reads as contact shadow against the table rather than a floating card. */
  box-shadow: 0 18px 22px -12px rgba(0,0,0,0.75);
  transition: transform 0.28s var(--ease), box-shadow 0.28s var(--ease), filter 0.28s var(--ease), z-index 0s;
}
.enemy-row .bf-card { transform-origin: center top; }
/* Atmospheric depth — the far row reads as farther away if it's very
   slightly dimmer/cooler than the near row, same trick matte painters use. */
.enemy-row .bf-card { filter: brightness(0.92) saturate(0.92); }
.bf-card::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; overflow: hidden;
  background-image:
    linear-gradient(to top, rgba(5,5,6,0.9) 0%, rgba(5,5,6,0.1) 45%, rgba(5,5,6,0.25) 100%),
    var(--art-url, none), linear-gradient(170deg, var(--panel-2), var(--panel));
  background-size: cover; background-position: center;
}
.bf-card.selectable, .bf-card.targetable { cursor: pointer; }

/* "You can hit this one" — a breathing rim, drawn on the SLOT, not the card.
   It used to be `transform: scale(1.045)` on the card itself, which made the
   pulse a hit-testing problem: a card that is 4.5% larger than its slot laps
   over its neighbours' click areas, and because it breathes, whether a tap
   landed depended on where in the 1.1s cycle it happened. That is the
   "sometimes it just doesn't register" bug. At 5v5, where the cards crowd
   closest, the sweep caught it directly — scaling on, some taps resolved to
   the wrong card; scaling off, none did.
   Nothing here changes geometry, so the click area stays exactly the card. */
.bf-slot:has(.bf-card.targetable)::after {
  content: ""; position: absolute; inset: 0; z-index: 4;
  border-radius: 10px; pointer-events: none;
  animation: targetPulse 1.3s ease-in-out infinite;
}
@keyframes targetPulse {
  0%, 100% { box-shadow: 0 0 0 1px rgba(230,200,132,0.22), 0 0 9px -4px rgba(230,200,132,0.35); }
  50%      { box-shadow: 0 0 0 2px rgba(230,200,132,0.85), 0 0 24px -2px rgba(230,200,132,0.75); }
}
.bf-card.dimmed { filter: brightness(0.55); }

/* Pending target — first tap on an enemy arms it (no attack yet) and shows
   this; a second tap on the SAME card, or the panel's Confirm button,
   commits. The pulse stops once armed so it reads as "held," not "still
   choosing." Border+glow only, same as .expanded — never resizes. */
.bf-card.pending-target {
  border-color: var(--gold-bright); border-width: 2px;
  box-shadow: 0 18px 22px -12px rgba(0,0,0,0.75), 0 0 22px -3px var(--gold-bright);
}
/* Stop the breathing rim once armed. It lives on the slot now, so it has to be
   cancelled there — cancelling it on the card no longer reaches it. */
.bf-slot:has(.bf-card.pending-target)::after { animation: none; box-shadow: none; }

/* Selected attacker — a lightweight highlight only (border + glow), no size
   or position change. Cards never move/resize themselves on selection —
   that was the root of the earlier flicker/instability bugs. Full detail
   is shown separately via click, in #battleDetailPanel (see game.html /
   js/battle-render.js), not by growing the card in place. */
.bf-card.expanded {
  border-color: var(--el-color); border-width: 2px;
  box-shadow: 0 18px 22px -12px rgba(0,0,0,0.75), 0 0 18px -3px var(--el-color);
}

/* Mid-lunge (attacker striking): lifted off the table with a deep cast
   shadow + elemental rim glow so it's unmistakably the one on the move.
   Its transform is driven inline by js/battle-render.js (lungeAttack) — this
   only handles the look. Brightness override keeps a lunging enemy card from
   staying dimmed by the far-row atmospheric filter while it's in the
   spotlight. targetPulse never applies here (attackers aren't targetable),
   so there's no transform-animation fighting the inline lunge transform. */
.bf-card.fx-lunging {
  box-shadow: 0 34px 44px -14px rgba(0,0,0,0.9), 0 0 30px -3px var(--el-color);
  filter: brightness(1.08) saturate(1.05);
}

.card-lv-badge { position: absolute; top: 6px; right: 6px; z-index: 3; }
.bf-el-icon {
  position: absolute; top: 6px; left: 6px; z-index: 3;
  width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center;
  font-size: 11px; border: 1px solid var(--el-color); color: var(--el-color);
  background: rgba(5,5,6,0.7);
}
.bf-hpbar {
  position: absolute; left: 8%; right: 8%; bottom: 8px; z-index: 3;
  height: 4px; border-radius: 2px; background: rgba(0,0,0,0.55); border: 1px solid rgba(255,255,255,0.08);
  overflow: hidden;
}
.bf-hpbar .fill, .bf-hpbar .lag { position: absolute; left: 0; top: 0; height: 100%; }
.bf-hpbar .fill { z-index: 2; background: linear-gradient(90deg, var(--gold-dim), var(--gold-bright)); transition: width 0.22s var(--ease); }
/* Lag bar — a red ghost that trails the gold fill down. The WIDTH OF THE GAP
   is the damage, which is far more legible than "the bar is shorter now".
   Chases only downward; healing snaps it forward (handled in setHp). */
.bf-hpbar .lag { z-index: 1; width: 100%; background: linear-gradient(90deg, #8f2f2f, #c94a3a); transition: width 0.55s cubic-bezier(0.3, 0, 0.2, 1) 0.22s; }
.bf-hpbar.low .fill { background: linear-gradient(90deg, #7a2a2a, var(--fire)); }

/* Name + HP/ATK — always visible on the compact card. The passive blurb no
   longer lives here at all; tapping the card shows full detail in the
   separate side panel instead (see .battle-detail-panel below). */
.bf-info {
  position: absolute; left: 0; right: 0; bottom: 14px; z-index: 4; padding: 8px 9px 4px;
  background: linear-gradient(to top, rgba(5,5,6,0.92) 30%, rgba(5,5,6,0.5) 75%, transparent);
  pointer-events: none; border-radius: 0 0 10px 10px;
}
.bf-name {
  font-family: var(--serif); font-weight: 600; font-size: clamp(0.72rem, 1.6vw, 0.98rem);
  color: #fff; line-height: 1.15; letter-spacing: 0.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-shadow: 0 1px 4px rgba(0,0,0,0.9);
}
.bf-variant { font-family: var(--serif); font-style: italic; font-size: 0.66rem; color: var(--gold-bright); line-height: 1.1; margin-top: 1px; }
.bf-stats { font-family: var(--mono); font-size: 9.5px; color: var(--muted); margin-top: 4px; letter-spacing: 0.02em; }

/* ---------- Card preview panel ----------
   Shown on click. Sits OUTSIDE the tilted 3D scene, as a flat overlay, so it's
   never subject to perspective distortion or 3D hit-testing quirks.

   Two layouts, one markup. Wide screens get the tall card docked to the right
   gutter. Below 1300px there IS no right gutter — the board fills the width —
   so the panel used to be centred, where it sat directly on top of the middle
   enemy and middle player cards and (being pointer-events:auto) swallowed taps
   meant for them: with a preview open you physically could not attack the
   centre card. Below that breakpoint it becomes a wide bar pinned above the
   log, and .preview-open lifts the board clear of it. */
.battle-detail-panel {
  position: absolute; top: 50%; right: 26px; transform: translateY(-50%);
  width: 246px; z-index: 20; opacity: 0; pointer-events: none;
  max-height: calc(100% - 150px);
  transition: opacity 0.28s var(--ease);
}
.battle-detail-panel.show { opacity: 1; pointer-events: auto; }

.preview-card {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  border-radius: 14px; border: 1px solid var(--line-gold);
  background: linear-gradient(168deg, var(--panel-2), #08080b);
  box-shadow: 0 34px 70px -20px rgba(0,0,0,0.95), 0 0 0 1px rgba(255,255,255,0.03) inset;
  animation: previewIn 0.3s var(--ease);
  max-height: 100%;
}
/* Inner gold hairline — the same ornament language as the legendary card
   frame in styles.css, applied to every rarity here so the panel itself reads
   as a piece of the deck rather than a tooltip. */
.preview-card::after {
  content: ""; position: absolute; inset: 4px; border-radius: 10px; pointer-events: none;
  border: 1px solid rgba(197,164,96,0.16); z-index: 4;
}
.preview-card.rar-rare { border-color: rgba(197,164,96,0.7); box-shadow: 0 34px 70px -20px rgba(0,0,0,0.95), 0 0 26px -10px var(--gold); }
.preview-card.rar-legendary { border-color: var(--gold-bright); box-shadow: 0 34px 70px -20px rgba(0,0,0,0.95), 0 0 38px -8px var(--gold-bright); }
.preview-card.rar-legendary::after { border-color: rgba(230,200,132,0.42); }
@keyframes previewIn { from { opacity: 0; transform: translateY(8px) scale(0.97); } to { opacity: 1; transform: none; } }

/* Art header. The scrim is a hard requirement, not decoration — the roman
   numeral, element glyph and rarity all sit on top of full-bleed illustration
   whose brightness varies wildly card to card. */
.preview-art {
  position: relative; flex: none; height: 148px;
  background-image: var(--art-url, none); background-size: cover; background-position: center 22%;
}
.preview-art::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(5,5,6,0.72) 0%, rgba(5,5,6,0.12) 38%, rgba(5,5,6,0.55) 78%, #08080b 100%);
}
.preview-roman {
  position: absolute; left: 14px; bottom: 10px; z-index: 2;
  font-family: var(--serif); font-size: 1.9rem; line-height: 1; color: #fff;
  text-shadow: 0 2px 10px rgba(0,0,0,0.95);
}
.preview-el {
  position: absolute; top: 12px; left: 13px; z-index: 2;
  width: 27px; height: 27px; border-radius: 50%; display: grid; place-items: center;
  font-size: 14px; border: 1px solid var(--el-color); color: var(--el-color);
  background: rgba(5,5,6,0.78); backdrop-filter: blur(2px);
}
.preview-rarity {
  position: absolute; right: 12px; bottom: 11px; z-index: 2;
  font-family: var(--mono); font-size: 8px; letter-spacing: 0.18em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 3px; background: rgba(5,5,6,0.7);
}
.preview-close {
  position: absolute; top: 10px; right: 10px; z-index: 5;
  width: 27px; height: 27px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.24);
  background: rgba(5,5,6,0.7); color: #fff; font-size: 16px; line-height: 1;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.preview-close:hover { background: rgba(5,5,6,0.95); border-color: var(--gold-bright); color: var(--gold-bright); }

.preview-body { padding: 13px 15px 15px; overflow-y: auto; min-height: 0; }
.preview-name { font-family: var(--serif); font-size: 1.14rem; color: #fff; line-height: 1.15; }
.preview-variant { font-family: var(--serif); font-style: italic; font-size: 0.82rem; color: var(--gold-bright); margin-top: 1px; }

.preview-vitals { display: flex; align-items: center; gap: 9px; margin-top: 11px; }
.preview-hpbar {
  flex: 1; height: 6px; border-radius: 3px; overflow: hidden;
  background: rgba(0,0,0,0.6); border: 1px solid rgba(255,255,255,0.09);
}
.preview-hpbar i { display: block; height: 100%; background: linear-gradient(90deg, var(--gold-dim), var(--gold-bright)); transition: width 0.35s var(--ease); }
.preview-hpbar.low i { background: linear-gradient(90deg, #7a2a2a, var(--fire)); }
.preview-hpnum { font-family: var(--mono); font-size: 10px; color: var(--muted); white-space: nowrap; }

.preview-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.preview-chips .chip {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted); border: 1px solid var(--line); border-radius: 20px; padding: 3px 9px;
}
.preview-chips .chip-el { color: var(--el-color); border-color: color-mix(in srgb, var(--el-color) 45%, transparent); }
.preview-chips .chip-lv { color: var(--gold-bright); border-color: var(--line-gold); }

/* The four skills. The battle engine has always had these; nothing in the
   battle UI ever showed them, so a player could not see what their card
   actually does without leaving the fight. */
.preview-passive {
  margin-top: 11px; padding-top: 9px; border-top: 1px dashed var(--line-gold);
  font-family: var(--sans); font-size: 10.5px; line-height: 1.5; color: var(--muted);
}
.preview-passive b { color: var(--gold-bright); font-weight: 600; }
.preview-confirm { display: block; width: 100%; margin-top: 13px; padding: 11px; font-size: 11px; }

.preview-card.is-fallen .preview-art { filter: grayscale(1) brightness(0.5); }
.preview-card.is-fallen .preview-hpnum { color: var(--oxblood-bright); }

/* ---------- Below 1300px: bar layout, and lift the board clear of it ---------- */
@media (max-width: 1300px) {
  .battle-detail-panel {
    top: auto; bottom: 106px; right: auto; left: 50%;
    transform: translateX(-50%); width: min(660px, 94vw); max-height: none;
  }
  .preview-card { flex-direction: row; max-height: none; }
  .preview-art { width: 84px; height: auto; align-self: stretch; background-position: center 18%; }
  .preview-art::before { background: linear-gradient(to right, rgba(5,5,6,0.35), rgba(5,5,6,0.1) 45%, #0b0b0f 100%); }
  .preview-roman { font-size: 1.4rem; left: 10px; bottom: 8px; }
  .preview-el { width: 23px; height: 23px; font-size: 12px; top: 8px; left: 8px; }
  .preview-rarity { display: none; }
  .preview-body {
    flex: 1; display: grid; align-content: start; gap: 0 16px;
    grid-template-columns: minmax(150px, 1fr) auto;
    grid-template-areas: "head action" "vitals action" "passive passive";
    padding: 12px 14px;
  }
  .preview-head { grid-area: head; }
  .preview-vitals { grid-area: vitals; margin-top: 8px; }
  .preview-chips { grid-area: vitals; align-self: end; margin: 0 0 0 0; display: none; }
  .preview-passive { grid-area: passive; margin-top: 8px; padding-top: 7px; }
  .preview-confirm { grid-area: action; align-self: center; width: 148px; margin: 0; white-space: nowrap; font-size: 10px; letter-spacing: 0.12em; }
  .preview-close { top: 6px; right: 6px; width: 23px; height: 23px; font-size: 14px; }
  /* Raise the whole board so the bar never covers a card. The bar itself can't
     drop any lower — it has to clear .battle-log, which grows to 96px. */
  .battle-board.preview-open .battle-3d { --board-shift: -106px; }
  .battle-board.preview-open .battle-prompt { opacity: 0; }
}
@media (max-width: 720px) {
  .battle-detail-panel { bottom: 102px; width: 96vw; }
  .preview-body {
    grid-template-columns: 1fr auto;
    grid-template-areas: "head action" "vitals action" "passive passive";
    padding: 10px 12px;
  }
  .preview-passive { font-size: 10px; }
  .preview-confirm { width: 116px; }
  .battle-board.preview-open .battle-3d { --board-shift: -76px; }
}

/* fainted — grayscale illustration + dim logo watermark, no longer interactive */
.bf-fainted-logo {
  position: absolute; top: 30%; left: 50%; transform: translate(-50%, -50%);
  width: 42%; opacity: 0; z-index: 3; pointer-events: none; transition: opacity 0.4s;
}
.bf-card.fainted { filter: grayscale(1) brightness(0.45); transform: scale(0.94) translateY(6px) !important; box-shadow: none !important; }
.bf-card.fainted .bf-fainted-logo { opacity: 0.22; }
.bf-card.fainted .bf-hpbar, .bf-card.fainted .bf-el-icon { opacity: 0.4; }

/* ---------- Elemental impact plates ----------
   The hit itself is now a photographic VFX element (assets/fx/), keyed to
   alpha from luminance so it composites over the dark board as light. Two
   plates play per strike with randomised rotation, mirroring and variant, so
   repeated hits never line up — see elementalBurst() in battle-render.js.

   This replaced four hand-drawn CSS treatments and, before them, a canvas
   particle system. Neither could look like real flame or real water; a few
   hundred circles read as circles however well they move. */
.fx-plate {
  position: absolute; pointer-events: none; z-index: 1;
  background-repeat: no-repeat; background-size: 100% 100%;
  transform-origin: 50% 50%;
  animation: plateBurst var(--d, 520ms) cubic-bezier(0.1, 0.72, 0.26, 1) both;
  will-change: transform, opacity;
}
@keyframes plateBurst {
  0%   { opacity: 0; transform: translate(-50%, -50%) translateY(var(--y0, 0px)) rotate(var(--r)) scaleX(var(--mx)) scale(var(--s0)); }
  12%  { opacity: var(--peak, 1); }
  52%  { opacity: var(--peak, 1); }
  100% { opacity: 0; transform: translate(-50%, -50%) translateY(var(--y1, 0px)) rotate(calc(var(--r) + var(--spin))) scaleX(var(--mx)) scale(var(--s1)); }
}

/* The struck card still takes a short elemental wash so the eye knows WHICH
   card was hit — the plate is centred on it but spills well past its edges. */
.bf-card.fx-fire::after, .bf-card.fx-water::after, .bf-card.fx-air::after, .bf-card.fx-earth::after {
  content: ""; position: absolute; inset: 0; z-index: 5; border-radius: inherit; pointer-events: none;
  animation: fxBurst 0.42s ease-out;
}
.bf-card.fx-fire::after  { background: radial-gradient(circle, rgba(255,196,120,0.5), transparent 72%); }
.bf-card.fx-water::after { background: radial-gradient(circle, rgba(150,205,255,0.5), transparent 72%); }
.bf-card.fx-air::after   { background: radial-gradient(circle, rgba(245,232,175,0.45), transparent 72%); }
.bf-card.fx-earth::after { background: radial-gradient(circle, rgba(170,212,162,0.45), transparent 72%); }

@keyframes fxBurst { 0% { opacity: 0.95; } 100% { opacity: 0; } }
.bf-card.fx-rarity-legendary::after { filter: brightness(1.45) saturate(1.15); }
.bf-card.fx-pulse::before { filter: brightness(1.5) saturate(1.2); }

/* A struck card must not also be running targetPulse — that animation owns
   `transform` and would override the inline knockback entirely. */
.bf-card.fx-hit { animation: none !important; z-index: 6; }

/* Dodge — slips sideways instead of recoiling. Safe to animate transform here:
   a dodging card is never simultaneously being knocked back. */
.bf-card.fx-dodge { animation: fxDodge 0.45s var(--ease) !important; }
@keyframes fxDodge {
  0%   { transform: translateX(0) rotate(0); }
  35%  { transform: translateX(-18px) rotate(-5deg); opacity: 0.72; }
  70%  { transform: translateX(7px) rotate(2deg); opacity: 1; }
  100% { transform: translateX(0) rotate(0); }
}

/* ---------- Hitstop ----------
   The freeze frame at contact. Pausing descendant animations leaves the
   board's own perspective/transform untouched — a filter or transform on
   .battle-board itself would disturb the 3D scene built on top of it. */
.battle-board.hitstop .bf-card,
.battle-board.hitstop .bf-popup,
.battle-board.hitstop .bf-impact,
.battle-board.hitstop .battle-fx-layer * { animation-play-state: paused; }

/* ---------- Death: slow-mo, then the card breaks apart ---------- */
.battle-board.slowmo .battle-fx-layer::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 32%, rgba(5,5,6,0.62) 100%);
  backdrop-filter: saturate(0.45) brightness(0.82);
  -webkit-backdrop-filter: saturate(0.45) brightness(0.82);
  animation: slowmoIn 0.24s var(--ease);
}
@keyframes slowmoIn { from { opacity: 0; } to { opacity: 1; } }

.bf-card.fx-cracking { opacity: 0; transition: opacity 0.12s linear; }

.bf-shatter {
  position: absolute; inset: 0; z-index: 8; pointer-events: none;
  transform-style: preserve-3d;
}
/* The break flash. Without it the eye has nothing to latch onto at the moment
   the card gives way — the pieces just appear already in flight. */
.bf-shatter::before {
  content: ""; position: absolute; inset: -8%; border-radius: 14px; z-index: 1;
  background: radial-gradient(circle, rgba(255,255,255,0.95), rgba(255,255,255,0.35) 45%, transparent 72%);
  animation: shatterFlash 0.22s ease-out both;
}
@keyframes shatterFlash { 0% { opacity: 0; transform: scale(0.5); } 25% { opacity: 1; } 100% { opacity: 0; transform: scale(1.25); } }

/* Each shard repaints the card's own art through its clip-path, so the pieces
   are unmistakably THAT card breaking rather than generic debris.
   The lit edge and the brightness floor are load-bearing: the art is dark, the
   board underneath is dark, and the slow-mo vignette darkens both — pieces
   that merely fade out are invisible for most of their flight. */
.bf-shard {
  position: absolute; inset: 0; border-radius: 10px; z-index: 2;
  background-image:
    linear-gradient(to top, rgba(5,5,6,0.9) 0%, rgba(5,5,6,0.1) 45%, rgba(5,5,6,0.25) 100%),
    var(--art-url, none), linear-gradient(170deg, var(--panel-2), var(--panel));
  background-size: cover; background-position: center;
  box-shadow: 0 0 22px -1px var(--el-color, var(--gold)), inset 0 0 0 1px rgba(255,236,190,0.45);
  animation: shardFly 0.72s cubic-bezier(0.16, 0.62, 0.36, 1) both;
}
@keyframes shardFly {
  0%   { transform: translate3d(0,0,6px) rotate(0deg); opacity: 1; filter: brightness(2.4) saturate(1.2); }
  12%  { opacity: 1; filter: brightness(1.85) saturate(1.15); }
  58%  { opacity: 1; filter: brightness(1.25); }
  100% { transform: translate3d(var(--sx), var(--sy), var(--sz)) rotate(var(--sr)); opacity: 0; filter: brightness(0.85); }
}

/* ---------- Flat FX overlay ----------
   Sits above the tilted 3D scene but below the preview panel. Everything here
   is positioned in SCREEN space (impact rings, travel streaks, labels, the
   legendary cinematic) so it stays undistorted by the board's rotateX. */
.battle-fx-layer { position: absolute; inset: 0; z-index: 12; pointer-events: none; overflow: hidden; }

.fx-streak {
  position: absolute; height: 3px; transform-origin: 0 50%; border-radius: 3px;
  animation: streakGo 0.34s cubic-bezier(0.3, 0, 0.2, 1) forwards;
}
.fx-streak-fire  { background: linear-gradient(90deg, transparent, rgba(255,180,110,0.9), rgba(217,102,63,0.2)); box-shadow: 0 0 14px 2px rgba(217,102,63,0.55); }
.fx-streak-water { background: linear-gradient(90deg, transparent, rgba(140,200,255,0.9), rgba(74,134,201,0.2)); box-shadow: 0 0 14px 2px rgba(74,134,201,0.5); }
.fx-streak-air   { background: linear-gradient(90deg, transparent, rgba(240,228,170,0.9), rgba(200,178,94,0.2)); box-shadow: 0 0 14px 2px rgba(200,178,94,0.5); }
.fx-streak-earth { background: linear-gradient(90deg, transparent, rgba(160,205,152,0.9), rgba(111,160,106,0.2)); box-shadow: 0 0 14px 2px rgba(111,160,106,0.5); }
@keyframes streakGo {
  0%   { opacity: 0; transform: rotate(var(--r,0)) scaleX(0.2); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(var(--r,0)) scaleX(1); }
}

.fx-ring {
  position: absolute; width: 26px; height: 26px; margin: -13px 0 0 -13px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.9);
  animation: ringOut 0.55s cubic-bezier(0.15, 0.75, 0.3, 1) forwards;
}
.fx-ring.strong { border-width: 3px; animation-duration: 0.62s; }
.fx-ring-fire  { border-color: rgba(255,190,130,0.95); box-shadow: 0 0 20px 4px rgba(217,102,63,0.5); }
.fx-ring-water { border-color: rgba(150,205,255,0.95); box-shadow: 0 0 20px 4px rgba(74,134,201,0.5); }
.fx-ring-air   { border-color: rgba(245,232,175,0.95); box-shadow: 0 0 20px 4px rgba(200,178,94,0.5); }
.fx-ring-earth { border-color: rgba(170,212,162,0.95); box-shadow: 0 0 20px 4px rgba(111,160,106,0.5); }
@keyframes ringOut {
  0%   { opacity: 1; transform: scale(0.3); }
  100% { opacity: 0; transform: scale(4.6); }
}

.fx-label {
  position: absolute; left: 50%; top: 38%; transform: translate(-50%, -50%);
  font-family: var(--serif); letter-spacing: 0.2em; text-transform: uppercase;
  white-space: nowrap; text-shadow: 0 3px 16px rgba(0,0,0,0.95);
}
.fx-label-super {
  font-size: clamp(1.5rem, 3.4vw, 2.5rem); color: var(--gold-bright);
  animation: labelPunch 0.9s var(--ease) forwards;
}
.fx-label-resist {
  font-size: clamp(0.95rem, 1.8vw, 1.3rem); color: #8a8a94; letter-spacing: 0.28em;
  animation: labelFade 0.75s var(--ease) forwards;
}
@keyframes labelPunch {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.55); letter-spacing: 0.5em; }
  18%  { opacity: 1; transform: translate(-50%, -50%) scale(1.12); letter-spacing: 0.2em; }
  30%  { transform: translate(-50%, -50%) scale(1); }
  72%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -62%) scale(1.04); }
}
@keyframes labelFade {
  0%   { opacity: 0; transform: translate(-50%, -46%); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -58%); }
}

/* ---------- Legendary strike cinematic ----------
   Rationed by js/battle-render.js to once per battle plus killing blows — a
   700ms pre-roll on every legendary attack stops reading as an event by the
   third turn. */
.fx-cinematic { position: absolute; inset: 0; }
/* Without this the roman/name/tag sit straight on the lit board art and the
   busy illustration underneath eats them — the letterbox bars alone only
   frame the shot, they don't darken it. */
.fx-cinematic::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(ellipse 74% 62% at 50% 50%, rgba(5,5,6,0.74), rgba(5,5,6,0.94));
  animation: cineScrim 0.98s var(--ease) both;
}
@keyframes cineScrim { 0% { opacity: 0; } 16%, 80% { opacity: 1; } 100% { opacity: 0; } }
.cine-bar {
  position: absolute; left: 0; right: 0; height: 13%; background: #050506;
  border-color: var(--line-gold); animation: cineBar 0.98s var(--ease) both;
}
.cine-bar-top { top: 0; border-bottom: 1px solid var(--line-gold); }
.cine-bar-bottom { bottom: 0; border-top: 1px solid var(--line-gold); animation-name: cineBarB; }
@keyframes cineBar  { 0% { transform: translateY(-100%); } 18%,82% { transform: translateY(0); } 100% { transform: translateY(-100%); } }
@keyframes cineBarB { 0% { transform: translateY(100%); }  18%,82% { transform: translateY(0); } 100% { transform: translateY(100%); } }
/* The engraved sigil turning behind the hero card during the legendary
   pre-roll. Centred on the card, not between card and text — the arc cutting
   behind the name hurt legibility more than it framed anything. */
.cine-sigil {
  position: absolute; left: 50%; top: 50%; width: min(460px, 54vw); aspect-ratio: 1;
  transform: translate(-111%, -50%); z-index: 0;
  background-size: 100% 100%; background-repeat: no-repeat;
  animation: cineSigil 0.98s var(--ease) both;
}
@keyframes cineSigil {
  0%   { opacity: 0; transform: translate(-111%, -50%) rotate(-40deg) scale(0.7); }
  22%  { opacity: 0.62; }
  78%  { opacity: 0.62; }
  100% { opacity: 0; transform: translate(-111%, -50%) rotate(34deg) scale(1.12); }
}
.cine-art {
  position: absolute; left: 50%; top: 50%; width: min(232px, 30vw); aspect-ratio: 3 / 5;
  transform: translate(-118%, -50%); background-size: cover; background-position: center;
  border: 1px solid var(--gold-bright); border-radius: 12px;
  box-shadow: 0 0 60px -6px rgba(230,200,132,0.55), 0 30px 60px -20px #000;
  animation: cineArt 0.98s var(--ease) both;
}
@keyframes cineArt {
  0%   { opacity: 0; transform: translate(-150%, -50%) scale(0.9); }
  20%  { opacity: 1; transform: translate(-118%, -50%) scale(1); }
  78%  { opacity: 1; transform: translate(-112%, -50%) scale(1.02); }
  100% { opacity: 0; transform: translate(-108%, -50%) scale(1.04); }
}
.cine-text {
  position: absolute; left: 50%; top: 50%; transform: translate(6%, -50%);
  text-align: left; animation: cineText 0.98s var(--ease) both;
}
@keyframes cineText {
  0%   { opacity: 0; transform: translate(14%, -50%); }
  24%  { opacity: 1; transform: translate(6%, -50%); }
  78%  { opacity: 1; }
  100% { opacity: 0; transform: translate(2%, -50%); }
}
.cine-roman { font-family: var(--serif); font-size: clamp(2rem, 5vw, 3.4rem); color: var(--el-color); line-height: 1; }
.cine-name { font-family: var(--serif); font-size: clamp(1.3rem, 2.6vw, 2rem); color: #fff; margin-top: 6px; }
.cine-variant { font-family: var(--serif); font-style: italic; font-size: clamp(0.9rem, 1.6vw, 1.15rem); color: var(--gold-bright); margin-top: 2px; }
.cine-rule { width: 74px; height: 1px; background: linear-gradient(90deg, var(--gold-bright), transparent); margin: 14px 0 10px; }
.cine-tag { font-family: var(--mono); font-size: 10px; letter-spacing: 0.32em; text-transform: uppercase; color: var(--gold-dim); }

/* Floating damage / heal / passive popups */
.bf-popup {
  position: absolute; left: 50%; top: 10%; z-index: 20; transform: translateX(-50%);
  font-family: var(--serif); font-weight: 600; font-size: 1.1rem; pointer-events: none;
  text-shadow: 0 2px 8px rgba(0,0,0,0.9); animation: popPunch 0.95s var(--ease) forwards;
  white-space: nowrap;
}
.bf-popup.big { font-size: 1.6rem; }
/* Overshoot then settle — a number that snaps into place reads as an impact;
   the old linear rise read as a notification. */
@keyframes popPunch {
  0%   { opacity: 0; transform: translate(-50%, 8px) scale(0.45); }
  16%  { opacity: 1; transform: translate(-50%, -4px) scale(1.3); }
  30%  { transform: translate(-50%, -9px) scale(0.94); }
  44%  { transform: translate(-50%, -14px) scale(1.06); }
  60%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -46px) scale(1); }
}
.bf-popup.dmg-advantage { color: var(--gold-bright); }
.bf-popup.dmg-normal { color: #f0f0f0; }
.bf-popup.dmg-resisted { color: #8a8a94; }
.bf-popup.pop-heal { color: var(--earth); }
.bf-popup.pop-burn { color: var(--fire); }
.bf-popup.pop-passive { color: var(--gold-bright); font-size: 1.3rem; }

/* ---------- Battle end ---------- */
#screenBattleEnd h2 { font-family: var(--serif); font-size: clamp(2rem, 5vw, 3rem); color: var(--gold-bright); }
#screenBattleEnd p { color: var(--muted); margin: 16px 0 30px; }

.reading-text {
  max-width: 480px; margin: 0 auto 30px; min-height: 70px;
  font-family: var(--serif); font-style: italic; color: var(--gold-bright);
  font-size: 1.15rem; line-height: 1.5; transition: opacity 0.4s;
}

.tut-block { text-align: left; padding: 22px 0; border-top: 1px solid var(--line); }
.tut-block:first-of-type { border-top: none; }
.tut-block h4 { font-family: var(--serif); font-size: 1.15rem; color: #fff; font-weight: 500; margin-bottom: 8px; }
.tut-p { color: var(--muted); font-size: 0.94rem; line-height: 1.6; }
.tut-p b { color: var(--gold-bright); font-weight: 600; }

/* ---------- Draw result modal (reuses .modal from styles.css) ---------- */
#drawResultContent { padding: 30px; text-align: center; position: relative; }
#drawResultContent .blurb { font-family: var(--serif); font-style: italic; color: var(--gold-bright); margin-top: 20px; }

/* ---------- Toast ---------- */
.toast {
  position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%) translateY(20px);
  background: var(--panel-2); border: 1px solid var(--line-gold); color: var(--text);
  /* Above .nav-overlay (500) and .loading-screen (600). A toast raised from
     the nav menu used to render behind it, so dev actions that worked looked
     like nothing had happened. */
  padding: 13px 22px; border-radius: 30px; font-size: 0.9rem; z-index: 700;
  opacity: 0; pointer-events: none; transition: all 0.4s var(--ease); box-shadow: 0 10px 30px -10px #000;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast .ic { color: var(--gold); }

/* ---------- Reincarnation variant subtitles (game screens) ---------- */
.rc-variant { font-family: var(--serif); font-style: italic; font-size: 0.85rem; color: var(--gold-bright); margin-top: 2px; }
.pc-variant { font-family: var(--serif); font-style: italic; font-size: 0.72rem; color: var(--gold-bright); line-height: 1.1; margin-top: 1px; }

/* ---------- The Ritual (EXP upgrade) ---------- */
.fodder-bar { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin: 22px 0 8px; }
.fodder-chip {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--faint); border: 1px solid var(--line); border-radius: 20px; padding: 7px 14px;
}
.fodder-chip.has { color: var(--muted); border-color: var(--line-gold); }
.fodder-chip b { color: var(--gold-bright); font-size: 12px; margin-left: 4px; }

.ritual-target {
  border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
  padding: 20px; margin: 16px auto 0; max-width: 520px; text-align: left; min-height: 60px;
}
.ritual-target.active { border-color: var(--el-color, var(--line-gold)); }
.ritual-target .ritual-hint { color: var(--faint); text-align: center; font-family: var(--mono); font-size: 12px; }
.rt-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.rt-name { font-family: var(--serif); font-size: 1.25rem; color: #fff; }
.rt-variant { font-style: italic; font-size: 0.9rem; color: var(--gold-bright); }
.rt-lvl { font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); margin-top: 4px; }
.rt-lvl b { color: var(--gold-bright); }
.rt-el { font-family: var(--serif); font-size: 1.8rem; line-height: 1; }
.exp-bar { height: 8px; background: #17171d; border: 1px solid var(--line); border-radius: 4px; overflow: hidden; margin: 14px 0 6px; }
.exp-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--gold-dim), var(--gold-bright)); border-radius: 4px; transition: width 0.5s var(--ease); }
.exp-label { font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em; color: var(--muted); }
.sac-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.sac-btn { padding: 10px 14px; font-size: 10.5px; }
.sac-btn:disabled { opacity: 0.35; cursor: not-allowed; }

/* =========================================================
   BATTLE OVERLAY — synergy banner/badges + screen flash, floating over the
   flat card grid. Per-card HP bars, info, and FX popups live on the card
   elements themselves (see .bf-card rules above), not as a separate overlay.
   ========================================================= */

/* ---------- Element synergy banner ---------- */
/* Announcement only — no listener. It sits over the enemy row, so taking
   pointer events would silently eat taps meant for a card underneath. */
.synergy-banner { position: absolute; top: 66px; left: 50%; transform: translateX(-50%); display: none; flex-direction: column; gap: 6px; width: max-content; max-width: min(480px, 88vw); pointer-events: none; }
.synergy-banner.show { display: flex; animation: synIn 0.5s var(--ease); }
.syn-line {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.04em; text-align: center;
  color: var(--muted); border: 1px solid var(--el-color); border-radius: 20px; padding: 6px 12px;
  background: color-mix(in srgb, var(--el-color) 30%, rgba(10,10,13,0.85));
}
.syn-line b { color: var(--el-color); font-family: var(--serif); font-size: 0.95rem; letter-spacing: 0; }
@keyframes synIn { from { opacity: 0; transform: translateX(-50%) translateY(-6px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } }

/* ---------- Persistent synergy badges (corner, always visible while active) ----------
   Distinct from .synergy-banner above: the banner is a one-time announcement
   that fades after a few seconds; these stack quietly in the corners for the
   whole battle and update the instant a faint changes a team's element mix. */
.synergy-badges { position: absolute; top: 16px; display: flex; flex-direction: column; gap: 6px; pointer-events: none; }
.synergy-badges-player { left: 16px; align-items: flex-start; }
.synergy-badges-enemy { right: 16px; align-items: flex-end; }
.synergy-badge {
  display: flex; align-items: center; gap: 7px;
  background: rgba(10,10,13,0.85); border: 1px solid var(--el-color);
  border-radius: 6px; padding: 5px 10px;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.02em; color: var(--text);
  animation: synIn 0.4s var(--ease);
}
.synergy-badge .sb-icon { color: var(--el-color); font-size: 13px; line-height: 1; }
.synergy-badge .sb-tier { color: var(--el-color); font-weight: 700; font-size: 9px; }
.synergy-badge .sb-effect { color: var(--muted); white-space: nowrap; }
@media (max-width: 700px) {
  .synergy-badge .sb-effect { display: none; } /* icon + tier only on narrow screens */
}

/* ---------- Screen flash — legendary strikes, and element-tinted on a
     super-effective hit so the advantage reads as a colour, not just a label. */
.battle-screen-flash {
  position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none;
}
.battle-screen-flash.flash { animation: battleScreenFlash 0.18s ease-out; }
.battle-screen-flash.tint-fire  { background: #ffb277; }
.battle-screen-flash.tint-water { background: #9ecdff; }
.battle-screen-flash.tint-air   { background: #f2e6b0; }
.battle-screen-flash.tint-earth { background: #a9d3a2; }
@keyframes battleScreenFlash { 0% { opacity: 0; } 40% { opacity: 0.5; } 100% { opacity: 0; } }

/* ---------- The Reading (flavor-only tarot pull) ---------- */
.reading-spread-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); gap: 12px; margin: 28px 0; max-width: 880px; margin-left: auto; margin-right: auto; }
.reading-back-card {
  position: relative; aspect-ratio: 3 / 5; border-radius: 10px; overflow: hidden;
  border: 1px solid var(--line-gold); cursor: pointer;
  background: #0a0a0e url('../assets/cards/card-back.webp') center / cover no-repeat;
  transition: transform 0.35s var(--ease), border-color 0.35s, box-shadow 0.35s;
}
.reading-back-card:hover { transform: translateY(-4px); border-color: var(--gold-bright); }
.reading-back-card.picked { transform: translateY(-8px); border-color: var(--gold-bright); box-shadow: 0 0 20px -4px var(--gold-bright); cursor: default; }
.reading-pick-badge {
  position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%);
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--gold-bright); background: rgba(5,5,6,0.85); border: 1px solid var(--line-gold);
  border-radius: 20px; padding: 2px 8px; white-space: nowrap;
}

.reading-reveal-cols { display: grid; grid-template-columns: repeat(3, minmax(160px, 1fr)); gap: 24px; margin: 30px 0; max-width: 780px; margin-left: auto; margin-right: auto; }
@media (max-width: 700px) { .reading-reveal-cols { grid-template-columns: 1fr; max-width: 300px; } }
.reading-col { display: flex; flex-direction: column; align-items: center; }
.reading-col .flip-stage { width: 100%; }
.reading-col .flip-card { width: 100%; max-width: 200px; aspect-ratio: 3 / 5; height: auto; }
.reading-role { font-family: var(--mono); font-size: 11px; letter-spacing: 0.25em; text-transform: uppercase; color: var(--gold-dim); margin-bottom: 10px; }
.reading-blurb { font-family: var(--serif); font-style: italic; color: var(--gold-bright); font-size: 0.92rem; line-height: 1.4; text-align: center; margin-top: 10px; min-height: 1.4em; }

/* Illustration layer inside the reveal card — this is the only part that
   physically flips for a reversed pull; the roman/element glyph and the
   name below stay upright and legible. */
.reading-col .tcard { position: relative; overflow: hidden; }
.reading-art {
  position: absolute; inset: 0; z-index: 0; border-radius: inherit;
  background-image: var(--art-url, none); background-size: cover; background-position: center;
  transition: transform 0.6s var(--ease);
}
.reading-art.reversed { transform: rotate(180deg); }
.reading-col .tcard-top { position: relative; z-index: 1; }
.reading-card-name { font-family: var(--serif); font-size: 1.05rem; color: #fff; margin-top: 12px; text-align: center; }
.reading-reversed-badge {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--oxblood-bright); border: 1px solid var(--oxblood); border-radius: 20px;
  padding: 2px 10px; margin-top: 6px; background: color-mix(in srgb, var(--oxblood) 14%, transparent);
}
.reading-synthesis {
  font-family: var(--serif); font-style: italic; color: var(--text); font-size: 1.02rem; line-height: 1.5;
  text-align: center; max-width: 620px; margin: 8px auto 0; padding-top: 22px; border-top: 1px solid var(--line);
  opacity: 0; transition: opacity 0.6s var(--ease);
}
.reading-synthesis.show { opacity: 1; }

/* =========================================================
   REDUCED MOTION
   styles.css already carries a global guard for the landing page; the battle
   FX added here are the most motion-heavy thing in the project and were not
   covered by it. js/battle-render.js checks the same query and skips the
   lunge, knockback, shatter and cinematic outright; this handles the purely
   declarative half (the effects CSS starts on its own).
   ========================================================= */
@media (prefers-reduced-motion: reduce) {
  .battle-board.shake, .battle-board.shake-big { animation: none; }
  .battle-3d, .battle-3d.zoom-punch { animation: none; transition: none; }
  .bf-slot:has(.bf-card.targetable)::after { animation: none; box-shadow: 0 0 0 1px rgba(230,200,132,0.5); }
  .bf-card.fx-dodge { animation: none !important; }
  .bf-card.fx-fire::after, .bf-card.fx-water::after, .bf-card.fx-air::after, .bf-card.fx-earth::after,
  .bf-impact, .fx-streak, .fx-ring, .bf-shard, .fx-plate,
  .cine-bar, .cine-art, .cine-text, .cine-sigil { animation-duration: 0.01ms !important; }
  .bf-popup { animation: popPunchReduced 0.9s linear forwards; }
  @keyframes popPunchReduced { 0% { opacity: 0; } 12%, 70% { opacity: 1; } 100% { opacity: 0; } }
  .fx-label-super, .fx-label-resist { animation: labelFadeReduced 0.8s linear forwards; }
  @keyframes labelFadeReduced { 0% { opacity: 0; } 15%, 70% { opacity: 1; } 100% { opacity: 0; } }
  .preview-card { animation: none; }
  .battle-board.slowmo .battle-fx-layer::before { animation: none; }
}


/* =========================================================
   RARITY FRAMES (assets/frames/*.webp)
   Generated carved ornament on pure black, keyed to alpha, applied as a
   9-slice `border-image`. The slice keeps the corner flourishes unstretched
   while the edges repeat — the only way a square source sits on a 3:5 card
   without the corners going oval. `round`, not `stretch`, so the carving
   repeats at its own scale instead of smearing.

   The slice is a PERCENTAGE on purpose: it is otherwise measured in source
   pixels, and the phone tier ships the same frames at 224px instead of 384px,
   which would need a second set of numbers to stay aligned.

   Rarity reads through MATERIAL, not ornament density — bare chiselled basalt,
   then carved slate with metal inlaid in the channels, then deep gold relief
   with set gemstones. That difference survives being 14px tall on a phone;
   "slightly more filigree" does not.
   ========================================================= */
.bf-card .bf-frame {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  border-style: solid; border-color: transparent; border-image-repeat: round;
}
.rar-common .bf-frame    { border-width: 1px; border-image-slice: 12.2%; border-image-width: 3.2%;
                           border-image-source: url('../assets/frames/frame-common.webp'); }
.rar-rare .bf-frame      { border-width: 1px; border-image-slice: 12.5%; border-image-width: 4%;
                           border-image-source: url('../assets/frames/frame-rare.webp'); }
.rar-legendary .bf-frame { border-width: 1px;  border-image-slice: 10.9%; border-image-width: 5%;
                           border-image-source: url('../assets/frames/frame-legendary.webp'); }
.assets-sm .rar-common .bf-frame    { border-image-source: url('../assets/frames/sm/frame-common.webp'); }
.assets-sm .rar-rare .bf-frame      { border-image-source: url('../assets/frames/sm/frame-rare.webp'); }
.assets-sm .rar-legendary .bf-frame { border-image-source: url('../assets/frames/sm/frame-legendary.webp'); }

/* Legendary breathes. A slow gold bloom over the carving costs nothing and
   does more for "this card is rare" than any amount of static ornament. */
.rar-legendary .bf-frame { animation: legendaryBreathe 3.6s ease-in-out infinite; }
@keyframes legendaryBreathe {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(230,200,132,0.3)) brightness(0.94); }
  50%      { filter: drop-shadow(0 0 11px rgba(230,200,132,0.8)) brightness(1.24); }
}
.bf-card.fainted .bf-frame { animation: none; filter: grayscale(1) brightness(0.5); }
@media (prefers-reduced-motion: reduce) { .rar-legendary .bf-frame { animation: none; } }

/* Same frames on every other card surface. These use ::after because their
   markup is generated in several places (and the Grimoire is a separate
   build) — a pseudo-element needs no markup change and picks up anything
   already carrying a .rar-* class. .bf-card is the exception: its ::after is
   the elemental hit wash, so it gets the .bf-frame span instead. */
.tcard::after, .pick-card::after, .result-card::after {
  content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border-style: solid; border-color: transparent; border-image-repeat: round;
}
.tcard.rar-common::after, .pick-card.rar-common::after, .result-card.rar-common::after {
  border-width: 1px; border-image-slice: 12.2%; border-image-width: 3.2%;
  border-image-source: url('../assets/frames/frame-common.webp');
}
.tcard.rar-rare::after, .pick-card.rar-rare::after, .result-card.rar-rare::after {
  border-width: 1px; border-image-slice: 12.5%; border-image-width: 4%;
  border-image-source: url('../assets/frames/frame-rare.webp');
}
.tcard.rar-legendary::after, .pick-card.rar-legendary::after, .result-card.rar-legendary::after {
  border-width: 1px; border-image-slice: 10.9%; border-image-width: 5%;
  border-image-source: url('../assets/frames/frame-legendary.webp');
  animation: legendaryBreathe 3.6s ease-in-out infinite;
}
.assets-sm .tcard.rar-common::after, .assets-sm .pick-card.rar-common::after, .assets-sm .result-card.rar-common::after
  { border-image-source: url('../assets/frames/sm/frame-common.webp'); }
.assets-sm .tcard.rar-rare::after, .assets-sm .pick-card.rar-rare::after, .assets-sm .result-card.rar-rare::after
  { border-image-source: url('../assets/frames/sm/frame-rare.webp'); }
.assets-sm .tcard.rar-legendary::after, .assets-sm .pick-card.rar-legendary::after, .assets-sm .result-card.rar-legendary::after
  { border-image-source: url('../assets/frames/sm/frame-legendary.webp'); }
.tcard.locked::after, .pick-card.locked::after { filter: grayscale(1) brightness(0.5); animation: none; }

/* The carved frame supersedes the old hand-drawn inset rule in styles.css,
   which would otherwise double up as a second border inside the ornament. */
.tcard.rar-legendary::before, .pick-card.rar-legendary::before, .bf-card.rar-legendary::before { border: none; }


/* =========================================================
   DEATH — one exit per element, and the card leaves the board
   The card's own transform does the acting. Photographic MATTER laid over an
   illustrated card is what made earlier effects look pasted on; light does
   not have that problem. So the generated plates stay light and dust, and
   every piece of solid matter on screen is the card itself.
   ========================================================= */
.bf-card.dying { pointer-events: none; z-index: 7; }
.bf-card.gone  { opacity: 0 !important; pointer-events: none; transition: none; }
.bf-card.gone .bf-frame { display: none; }

/* FIRE — chars from the edges in, then lifts away as ash. */
.bf-card.die-fire { animation: dieFire 900ms cubic-bezier(0.3, 0, 0.4, 1) forwards; }
@keyframes dieFire {
  0%   { filter: none; transform: none; opacity: 1; }
  18%  { filter: brightness(1.5) sepia(0.5) saturate(2.2); }
  45%  { filter: brightness(0.42) sepia(1) saturate(0.5) contrast(1.5); transform: scale(0.99); }
  70%  { filter: brightness(0.2) sepia(1) saturate(0.2) contrast(1.7); transform: translateY(-10px) scale(0.97); opacity: 0.7; }
  100% { filter: brightness(0.05) grayscale(1); transform: translateY(-40px) scale(0.9); opacity: 0; }
}

/* WATER — sinks through the whirlpool that opened beneath it. */
.bf-card.die-water { animation: dieWater 900ms cubic-bezier(0.45, 0, 0.7, 0.9) forwards; }
@keyframes dieWater {
  0%   { transform: none; opacity: 1; filter: none; }
  22%  { transform: translateY(6px) rotate(-2deg) scale(0.99); }
  100% { transform: translateY(34px) rotate(9deg) scale(0.32); opacity: 0; filter: brightness(0.5) saturate(1.4) blur(2px); }
}

/* AIR — ripped off the table and flung out of frame. */
.bf-card.die-air { animation: dieAir 900ms cubic-bezier(0.5, 0, 0.75, 0.4) forwards; }
@keyframes dieAir {
  0%   { transform: none; opacity: 1; }
  15%  { transform: translateY(10px) rotate(-3deg) scale(0.98); }
  100% { transform: translateY(-330px) translateX(58px) rotate(52deg) scale(0.42); opacity: 0; filter: blur(3px); }
}

/* EARTH — something lands on it. Crushed flat, then it breaks apart
   (see shatterCard — the shards are cut from the card's own art). */
.bf-card.die-earth { animation: dieEarth 900ms cubic-bezier(0.25, 0, 0.3, 1) forwards; }
@keyframes dieEarth {
  0%   { transform: none; opacity: 1; }
  14%  { transform: translateY(-7px) scale(1.02, 1.03); }
  26%  { transform: translateY(9px) scale(1.14, 0.8); filter: brightness(1.35); }
  34%  { transform: translateY(5px) scale(1.06, 0.9); filter: brightness(0.8); }
  46%  { opacity: 1; }
  100% { transform: translateY(6px) scale(1.02, 0.86); opacity: 0; filter: brightness(0.35); }
}

@media (prefers-reduced-motion: reduce) {
  .bf-card.die-fire, .bf-card.die-water, .bf-card.die-air, .bf-card.die-earth { animation: none; }
}

/* ---------- Rarity metal: dark bronze / silver / gold ----------
   Applied here rather than baked into the art so the tiers can be recoloured
   without regenerating, and so one source serves both asset tiers.

   Common is deliberately the darkest thing on the card. An earlier pass gave
   it a bright bronze sepia, which made the cheapest frame louder than the
   silver above it — the ladder read backwards. It is now barely warmed and
   held well down; rare is the first tier that catches light at all.

   Legendary folds its tint into the breathing keyframes: a second `filter`
   declaration would simply lose to the animation. */
.rar-common .bf-frame,
.tcard.rar-common::after, .pick-card.rar-common::after, .result-card.rar-common::after {
  filter: sepia(0.35) saturate(1.15) hue-rotate(-8deg) brightness(0.62) contrast(1.1);
}
.rar-rare .bf-frame,
.tcard.rar-rare::after, .pick-card.rar-rare::after, .result-card.rar-rare::after {
  filter: grayscale(0.85) brightness(1.18) contrast(1.06);
}
@keyframes legendaryBreathe {
  0%, 100% { filter: sepia(0.3) saturate(1.45) brightness(1.02)
                     drop-shadow(0 0 2px rgba(230,200,132,0.28)); }
  50%      { filter: sepia(0.3) saturate(1.6) brightness(1.26)
                     drop-shadow(0 0 10px rgba(230,200,132,0.72)); }
}

/* =========================================================
   AMBIENT BACKDROP (game screens)
   The hub, Grimoire, Ritual, Arcade and deck picker all used to sit on flat
   #050506 with the content in a narrow centred column — which read as an
   unfinished page rather than a deliberately dark one. One shared, purely
   decorative layer fixes all of them at once. It sits behind .game-stage and
   takes no pointer events, so it cannot affect the board's hit-testing.
   ========================================================= */
.game-ambient { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
/* The lobby plate. 640x356 stretched to fill, so it is blurred and dimmed hard
   on purpose — at that scale it has to read as atmosphere, not as a picture,
   and every screen above it carries body text that has to stay legible.
   Fades in only once a frame has actually decoded (.ready), which avoids the
   black flash `autoplay` gives on a cold cache. */
.amb-video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0; transition: opacity 1.1s var(--ease);
  /* Was blur(3px) brightness(0.42) — so dim the plate barely registered and
     the lobby still read as a flat dark page. The vignette and glow above it
     already carry the contrast that keeps text legible, so the video itself
     does not have to be crushed to do that job. */
  filter: blur(1.5px) saturate(0.95) brightness(0.72);
  transform: scale(1.06);   /* hides the soft edge the blur leaves at the frame */
}
.amb-video.ready { opacity: 1; }
/* The duel has its own board, its own lighting and a lot of moving FX. A second
   moving image underneath it is noise — hold the plate still and drop it back
   while a battle is on screen. */
.game-body.in-battle .amb-video { opacity: 0.22; }
/* Raise ONLY the two in-flow content wrappers above the backdrop. Do NOT write
   a blanket `.game-body > *` rule here: #drawResultModal, #navOverlay,
   #loadingScreen and .toast are all direct children of body and are all
   `position: fixed` by design. A blanket rule outranks their own selectors and
   drops every one of them into the document flow — the draw modal ended up
   1967px tall somewhere below the hub, invisible. (The identical mistake on
   .splash children broke the landing hero; see HANDOFF §7b.) */
.game-nav, .game-stage { position: relative; z-index: 1; }

.amb-glow {
  position: absolute; left: 50%; top: -18%; width: 132vw; height: 92vh;
  transform: translateX(-50%);
  background:
    radial-gradient(ellipse 46% 58% at 50% 40%, rgba(107,63,160,0.20) 0%, rgba(107,63,160,0.07) 46%, transparent 72%),
    radial-gradient(ellipse 30% 34% at 50% 26%, rgba(197,164,96,0.10) 0%, transparent 70%);
  animation: ambDrift 26s ease-in-out infinite;
}
@keyframes ambDrift {
  0%, 100% { transform: translateX(-50%) scale(1); opacity: 0.9; }
  50%      { transform: translateX(-50%) scale(1.09); opacity: 1; }
}
/* The house mark, huge and almost subliminal — gives the empty space a centre. */
.amb-sigil {
  position: absolute; left: 50%; top: 46%; width: min(70vh, 62vw); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: url('../assets/logo/logo-mark.webp') center / contain no-repeat;
  opacity: 0.035; filter: grayscale(0.3);
  animation: ambTurn 120s linear infinite;
}
@keyframes ambTurn { from { transform: translate(-50%, -50%) rotate(0deg); } to { transform: translate(-50%, -50%) rotate(360deg); } }
.amb-vignette {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 82% 72% at 50% 45%, transparent 34%, rgba(5,5,6,0.42) 76%, rgba(5,5,6,0.86) 100%),
    linear-gradient(180deg, rgba(5,5,6,0.62) 0%, transparent 18%, transparent 78%, rgba(5,5,6,0.8) 100%);
}
/* The battle is fully art-directed already and covers the viewport — the
   backdrop behind it is wasted work and one more layer to composite. */
.game-body:has(#screenBattle.active) .game-ambient { display: none; }
@media (prefers-reduced-motion: reduce) { .amb-glow, .amb-sigil { animation: none; } }

/* ---------- Loading screen ---------- */
.loading-video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0.42; filter: saturate(0.75) brightness(0.72);
  transform: scale(1.03);
}
.loading-screen::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse 55% 48% at 50% 50%, rgba(5,5,6,0.3), rgba(5,5,6,0.92) 72%, #050506 100%);
}
.loading-screen > *:not(.loading-video) { position: relative; z-index: 1; }
.loading-bar {
  width: min(220px, 54vw); height: 2px; border-radius: 2px;
  background: rgba(255,255,255,0.07); overflow: hidden;
}
/* Real progress now, not a sweep. The bar used to animate on a timer while the
   screen sat for a fixed 800ms — it looked like loading without measuring
   anything, and the lobby could still pop in half-painted afterwards. */
.loading-bar i {
  display: block; width: 0%; height: 100%;
  background: linear-gradient(90deg, var(--gold-dim), var(--gold-bright));
  box-shadow: 0 0 12px -2px rgba(230,200,132,0.7);
  transition: width 0.28s var(--ease);
}
.loading-pct {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.22em;
  color: var(--gold-dim); margin-top: 10px; font-variant-numeric: tabular-nums;
}

/* =========================================================
   THE DRAW RITE
   The daily draw is the one thing a player does every day, and it used to be a
   190px card in a small box on a black screen. In ritual mode the modal takes
   the whole viewport and plays a sequence: chamber up → card rises → flip →
   the element breaks over it → text settles.
   ========================================================= */
.modal.draw-ritual .modal-card {
  max-width: none !important; width: 100vw; height: 100dvh;
  border: none; border-radius: 0; background: transparent; box-shadow: none;
  display: grid; place-items: center; padding: 0;
}
.modal.draw-ritual #drawResultContent { padding: 0; width: 100%; height: 100%; }
.modal.draw-ritual .modal-close { top: 20px; right: 22px; }

.rite {
  position: relative; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0; overflow: hidden; text-align: center;
}
/* The chamber: reuses the ritual plate already shipped for the loading screen,
   so this costs nothing extra to serve. */
.rite-chamber {
  position: absolute; inset: 0;
  background: url('../assets/video/ritual-poster.webp') center / cover no-repeat, #050506;
  opacity: 0; transform: scale(1.12);
  filter: saturate(0.65) brightness(0.5) blur(3px);
  transition: opacity 1.1s var(--ease), transform 2.4s var(--ease);
}
.rite.rite-on .rite-chamber { opacity: 0.5; transform: scale(1.02); }
.rite::after {                         /* keeps the card and copy readable */
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse 46% 44% at 50% 46%, rgba(5,5,6,0.5) 0%, rgba(5,5,6,0.88) 62%, #050506 100%);
}
/* Name the in-flow children explicitly. A `.rite > *` rule here dropped
   .rite-sigil and .rite-burst out of `position: absolute` into the flex
   column — each is ~78vh tall, so the rite grew to 1967px inside a 792px
   card and the card was never on screen. Third time this exact pattern has
   broken a layout in this project (landing hero, game body, here): do not
   write blanket positional rules over a mixed absolute/static set. */
.rite-eyebrow, .rite-stage, .rite-text { position: relative; z-index: 2; }

.rite-sigil {
  position: absolute; left: 50%; top: 46%; width: min(78vh, 88vw); aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(0.6) rotate(-30deg);
  background-size: contain; background-repeat: no-repeat; background-position: center;
  opacity: 0; z-index: 1;
  transition: opacity 1s var(--ease), transform 2.6s var(--ease);
}
.rite.rite-on .rite-sigil { opacity: 0.5; transform: translate(-50%, -50%) scale(1) rotate(14deg); }
.rite.rite-lit .rite-sigil { opacity: 0.78; }

/* The element breaking over the card, using the same plates the battle uses. */
.rite-burst {
  position: absolute; left: 50%; top: 46%; width: min(62vh, 76vw); aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(0.4);
  background-size: contain; background-repeat: no-repeat; background-position: center;
  opacity: 0; z-index: 3; pointer-events: none;
}
.rite.rite-lit .rite-burst { animation: riteBurst 1.1s var(--ease) forwards; }
@keyframes riteBurst {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.4); }
  18%  { opacity: 0.95; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.22); }
}

.rite-eyebrow {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.32em; text-transform: uppercase;
  color: var(--gold-dim); opacity: 0; transform: translateY(6px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  margin-bottom: clamp(14px, 3vh, 30px);
}
.rite.rite-on .rite-eyebrow { opacity: 1; transform: none; }

/* The card itself — far larger than the old modal allowed, and it rises into
   place rather than simply appearing. */
.rite-stage { width: clamp(210px, 30vh, 300px); margin: 0; }
.rite-stage .flip-card { width: 100%; }
.rite-stage {
  opacity: 0; transform: translateY(46px) scale(0.94);
  transition: opacity 0.8s var(--ease), transform 1s var(--ease);
}
.rite.rite-rising .rite-stage { opacity: 1; transform: none; }
.rite.rite-flipped .rite-stage { filter: drop-shadow(0 24px 50px rgba(0,0,0,0.85)); }
.rite.rite-lit .rite-stage { animation: riteSettle 0.9s var(--ease); }
@keyframes riteSettle {
  0% { transform: scale(1); } 22% { transform: scale(1.06); } 100% { transform: scale(1); }
}

.rite-text {
  max-width: min(460px, 86vw); margin-top: clamp(18px, 3.5vh, 34px);
}
.rite-text .blurb { font-family: var(--serif); font-style: italic; color: var(--gold-bright); font-size: clamp(1rem, 2.2vw, 1.2rem); line-height: 1.5; }
.rite-done { margin-top: 20px; min-width: 160px; }

/* Rarity sets the scale of the whole rite, not just the frame. */
.rite-rare .rite-sigil { filter: brightness(1.15); }
.rite-legendary .rite-sigil { filter: brightness(1.5) saturate(1.2); }
.rite-legendary .rite-burst { width: min(84vh, 96vw); }
.rite-legendary .rite-stage { width: clamp(230px, 34vh, 340px); }
.rite-legendary .rite-eyebrow { color: var(--gold-bright); }

.draw-flash {
  position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; z-index: 9;
}
.draw-flash.on { animation: drawFlash 0.5s ease-out; }
@keyframes drawFlash { 0% { opacity: 0; } 22% { opacity: 0.42; } 100% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .rite-chamber, .rite-sigil, .rite-stage, .rite-eyebrow { transition: none; }
  .rite.rite-lit .rite-burst, .rite.rite-lit .rite-stage, .draw-flash.on { animation: none; }
}

/* ---------- Battle end ----------
   The payoff after a whole fight was three lines of text on black. It now
   reports what happened and offers the replay directly. */
#screenBattleEnd { position: relative; }
.end-seal {
  position: absolute; left: 50%; top: 42%; width: min(52vh, 60vw); aspect-ratio: 1;
  transform: translate(-50%, -50%); pointer-events: none; z-index: -1;
  background: url('../assets/fx/sigil.webp') center / contain no-repeat;
  opacity: 0; animation: endSeal 1.4s var(--ease) forwards;
}
.assets-sm .end-seal { background-image: url('../assets/fx/sm/sigil.webp'); }
@keyframes endSeal {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.7) rotate(-24deg); }
  100% { opacity: 0.3; transform: translate(-50%, -50%) scale(1) rotate(8deg); }
}
#screenBattleEnd.lost .end-seal { filter: grayscale(1) brightness(0.6); }
#screenBattleEnd.lost #battleEndTitle { color: var(--oxblood-bright); }

.end-stats {
  display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin: 26px 0 30px;
}
.end-stat {
  min-width: 118px; padding: 14px 20px; border: 1px solid var(--line); border-radius: 10px;
  background: rgba(255,255,255,0.015);
}
.end-stat .v { display: block; font-family: var(--serif); font-size: 1.6rem; color: var(--text); line-height: 1; }
.end-stat .k { display: block; font-family: var(--mono); font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint); margin-top: 6px; }
.end-stat.gain .v { color: var(--gold-bright); }
.end-stat.loss .v { color: var(--oxblood-bright); }
.end-stat.none .v { color: var(--faint); }
.end-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.end-actions .btn { min-width: 150px; justify-content: center; }
@media (prefers-reduced-motion: reduce) { .end-seal { animation: none; opacity: 0.22; } }

/* =========================================================
   LOBBY — the hub as a game lobby, not a settings page
   One featured action (the daily draw) and a grid of mode tiles. Every tile's
   art is a landing panel banner the site already ships; typography and art do
   the signposting — no icon fonts, no emoji.
   ========================================================= */
/* The lobby is the one screen that wants the full width: eight cards in a row
   at 900px come out barely 100px each, smaller than the six-card grid they
   replaced. Everything else stays narrow for readable line lengths. */
#screenHub { max-width: min(1500px, 96vw); }
.lobby { display: flex; flex-direction: column; gap: clamp(10px, 1.6vh, 16px); text-align: left; }

/* HUD in the nav */
.hud { display: flex; align-items: center; gap: 7px; margin-left: auto; margin-right: 11px; }
.hud-chip {
  display: flex; align-items: baseline; gap: 5px; padding: 4px 10px;
  border: 1px solid var(--line); border-radius: 20px; background: rgba(255,255,255,0.02);
}
.hud-v { font-family: var(--serif); font-size: 0.88rem; color: var(--gold-bright); line-height: 1; }
.hud-k { font-family: var(--mono); font-size: 7.5px; letter-spacing: 0.13em; text-transform: uppercase; color: var(--faint); }
.hud-help {
  width: 25px; height: 25px; border-radius: 50%; border: 1px solid var(--line);
  background: rgba(255,255,255,0.02); color: var(--gold-dim); font-family: var(--serif);
  font-size: 12.5px; cursor: pointer; transition: border-color 0.25s, color 0.25s;
}
.hud-help:hover { border-color: var(--gold); color: var(--gold-bright); }

/* Featured daily draw */
.lobby-hero {
  position: relative; display: flex; align-items: center; gap: 22px;
  min-height: 148px; padding: 24px 28px; border-radius: 14px; overflow: hidden;
  border: 1px solid var(--line-gold); background: var(--panel); cursor: pointer;
  text-align: left; color: var(--text); font: inherit;
  transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}
.lobby-hero:hover:not(:disabled) { transform: translateY(-2px); border-color: var(--gold); box-shadow: 0 18px 44px -18px rgba(0,0,0,0.9), 0 0 30px -12px rgba(197,164,96,0.4); }
.lobby-hero:disabled { cursor: default; }
.lobby-hero .lh-art {
  position: absolute; inset: 0; background: url('../assets/panels/draw.webp') center 30% / cover no-repeat;
  opacity: 0.5; transition: opacity 0.4s, transform 6s var(--ease);
}
.lobby-hero:hover:not(:disabled) .lh-art { opacity: 0.65; transform: scale(1.04); }
.lobby-hero::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(5,5,6,0.88) 26%, rgba(5,5,6,0.45) 60%, rgba(5,5,6,0.15) 100%);
}
.lobby-hero:disabled .lh-art { opacity: 0.22; filter: grayscale(0.6); }
.lh-card {
  position: relative; z-index: 2; flex: none; width: 74px; aspect-ratio: 3/5;
  border-radius: 8px; border: 1px solid var(--line-gold);
  background: url('../assets/cards/card-back.webp') center / cover no-repeat;
  box-shadow: 0 10px 26px -8px rgba(0,0,0,0.9);
  transform: rotate(-4deg);
  transition: transform 0.35s var(--ease);
}
.lobby-hero:hover:not(:disabled) .lh-card { transform: rotate(-1deg) translateY(-3px); }
.lh-body { position: relative; z-index: 2; display: flex; flex-direction: column; gap: 5px; }
.lh-eyebrow { font-family: var(--mono); font-size: 9px; letter-spacing: 0.26em; text-transform: uppercase; color: var(--gold-dim); }
.lh-title { font-family: var(--serif); font-size: clamp(1.3rem, 2.6vw, 1.7rem); color: #fff; }
.lh-sub { font-size: 0.86rem; color: var(--muted); }
.lh-state {
  position: relative; z-index: 2; margin-left: auto; flex: none;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--gold-bright); border: 1px solid var(--line-gold); border-radius: 20px; padding: 8px 16px;
  background: rgba(5,5,6,0.6);
}
.lobby-hero:disabled .lh-state { color: var(--faint); border-color: var(--line); }

/* Mode tiles */
/* ---------- Menu as a spread of cards ----------
   This is a tarot game; the lobby menu is now dealt as six cards rather than
   six web tiles. 3:5 portrait, gold rule inside the edge, roman numeral in the
   corner, name across the foot — the same anatomy as the cards in the Grimoire,
   so the menu belongs to the same object family as everything it opens. */
.mode-tile {
  position: relative; overflow: hidden; border-radius: 10px;
  aspect-ratio: 3 / 5;
  border: 1px solid var(--line); background: var(--card-black); cursor: pointer;
  padding: 0; text-align: center; color: var(--text); font: inherit;
  display: flex; flex-direction: column; justify-content: flex-end;
  transition: border-color 0.35s var(--ease), transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
  box-shadow: 0 10px 26px -14px rgba(0,0,0,0.9);
}
/* Lifts and tilts a touch, the way a card does when you pick it out of a fan. */
.mode-tile:hover:not(:disabled) {
  transform: translateY(-8px) rotate(-0.8deg);
  border-color: var(--gold-dim);
  box-shadow: 0 26px 52px -20px rgba(0,0,0,0.95), 0 0 26px -8px rgba(197,164,96,0.28);
}
.mode-tile:nth-child(even):hover:not(:disabled) { transform: translateY(-8px) rotate(0.8deg); }
/* The inner gold rule — set with an inset border, not a second element, so it
   follows the card's own radius and clips with it. */
.mode-tile::before {
  content: ""; position: absolute; inset: 5px; z-index: 3; border-radius: 7px;
  border: 1px solid rgba(197,164,96,0.22); pointer-events: none;
  transition: border-color 0.35s var(--ease);
}
.mode-tile:hover:not(:disabled)::before { border-color: rgba(230,200,132,0.45); }
.mt-art {
  position: absolute; inset: 0; background-size: cover; background-position: center 38%;
  opacity: 0.62; transition: opacity 0.4s, transform 6s var(--ease);
}
.mode-tile:hover:not(:disabled) .mt-art { opacity: 0.82; transform: scale(1.06); }
.mode-tile::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(5,5,6,0.30) 0%, transparent 26%,
                                      transparent 46%, rgba(5,5,6,0.90) 82%, rgba(5,5,6,0.97) 100%);
}
.mt-name {
  position: relative; z-index: 2; font-family: var(--serif);
  font-size: clamp(0.95rem, 1.5vw, 1.14rem); line-height: 1.15; color: #fff;
  padding: 0 12px;
}
.mt-sub {
  position: relative; z-index: 2; font-family: var(--mono); font-size: 8px;
  letter-spacing: 0.11em; text-transform: uppercase; color: var(--muted);
  padding: 6px 12px 16px; line-height: 1.4;
}
/* Roman numeral in the corner, like the Arcana themselves. */
.mt-num {
  position: absolute; top: 11px; left: 0; right: 0; z-index: 3;
  font-family: var(--serif); font-size: 0.92rem; color: var(--gold-bright);
  opacity: 0.85; text-shadow: 0 1px 8px rgba(0,0,0,0.9);
}
.mt-lock {
  position: absolute; top: 9px; right: 9px; z-index: 4;
  font-family: var(--mono); font-size: 7.5px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--gold-dim); border: 1px solid var(--line-gold); border-radius: 16px; padding: 3px 8px;
  background: rgba(5,5,6,0.78);
}
.mode-tile.locked { cursor: not-allowed; }
.mode-tile.locked .mt-art { opacity: 0.2; filter: grayscale(0.85); }
.mode-tile.locked .mt-name { color: var(--muted); }
.mode-tile.locked .mt-num { opacity: 0.4; }
.mode-tile:disabled:not(.locked) { cursor: default; opacity: 0.55; }

.tile-arcade  .mt-art { background-image: url('../assets/panels/duel-board.webp'); }
.tile-grimoire .mt-art { background-image: url('../assets/panels/grimoire.webp'); }
.tile-ritual  .mt-art { background-image: url('../assets/panels/faq.webp'); }
.tile-reading .mt-art { background-image: url('../assets/panels/reading.webp'); }
.tile-duel    .mt-art { background-image: url('../assets/panels/roadmap.webp'); }
.tile-buy     .mt-art { background-image: url('../assets/panels/token.webp'); }
.assets-sm .lobby-hero .lh-art { background-image: url('../assets/panels/draw-sm.webp'); }
.assets-sm .tile-arcade  .mt-art { background-image: url('../assets/panels/duel-board-sm.webp'); }
.assets-sm .tile-grimoire .mt-art { background-image: url('../assets/panels/grimoire-sm.webp'); }
.assets-sm .tile-ritual  .mt-art { background-image: url('../assets/panels/faq-sm.webp'); }
.assets-sm .tile-reading .mt-art { background-image: url('../assets/panels/reading-sm.webp'); }
.assets-sm .tile-duel    .mt-art { background-image: url('../assets/panels/roadmap-sm.webp'); }
.assets-sm .tile-buy     .mt-art { background-image: url('../assets/panels/token-sm.webp'); }

.screen-title { font-family: var(--serif); font-weight: 500; font-size: clamp(1.8rem, 4vw, 2.6rem); color: #fff; margin: 14px 0 6px; }
.screen-sub { color: var(--muted); }
.screen-sub b { color: var(--gold-bright); }

/* =========================================================
   THE RITUAL — a lab, not a list
   Left: the card being raised, full art, live EXP. Right: numbered steps,
   essence stock, burn buttons with exact EXP preview.
   ========================================================= */
.ritual-lab {
  display: grid; grid-template-columns: minmax(200px, 250px) 1fr; gap: 20px;
  margin-top: 26px; text-align: left;
}
.ritual-focus {
  position: relative; border: 1px solid var(--line); border-radius: 14px;
  background: var(--panel); min-height: 360px; overflow: hidden;
  display: grid; place-items: center;
}
.rf-empty { text-align: center; color: var(--faint); font-family: var(--mono); font-size: 11.5px; line-height: 1.8; padding: 20px; }
.rf-arrow { display: block; font-size: 22px; color: var(--gold-dim); margin-bottom: 10px; }
.rf-card { position: absolute; inset: 0; display: flex; flex-direction: column; }
.rf-art { flex: 1; background-size: cover; background-position: center 25%; position: relative; }
.rf-art::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(5,5,6,0.3) 0%, transparent 30%, transparent 55%, rgba(5,5,6,0.95) 100%); }
.rf-meta { position: relative; padding: 0 16px 16px; margin-top: -54px; z-index: 2; }
.rf-name { font-family: var(--serif); font-size: 1.2rem; color: #fff; }
.rf-variant { font-family: var(--serif); font-style: italic; font-size: 0.8rem; color: var(--gold-bright); }
.rf-lvl { font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin: 7px 0 6px; }
.rf-lvl b { color: var(--gold-bright); }
.rf-lvl .max { color: var(--gold-bright); }
.ritual-panel { display: flex; flex-direction: column; gap: 12px; }
.rp-step { display: flex; align-items: baseline; gap: 10px; color: var(--muted); font-size: 0.9rem; }
.rp-step i {
  flex: none; width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--line-gold);
  color: var(--gold-bright); font-family: var(--serif); font-style: normal; font-size: 11px;
  display: grid; place-items: center;
}
.rp-essence { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.rp-actions { display: flex; flex-direction: column; gap: 8px; }
.burn-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; border: 1px solid var(--line); border-radius: 10px;
  background: rgba(255,255,255,0.015); color: var(--text); font: inherit; cursor: pointer;
  transition: border-color 0.25s, background 0.25s;
}
.burn-btn:hover:not(:disabled) { border-color: var(--gold); background: rgba(197,164,96,0.06); }
.burn-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.burn-btn .bb-l { font-family: var(--serif); font-size: 0.98rem; }
.burn-btn .bb-l small { display: block; font-family: var(--mono); font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint); margin-top: 2px; }
.burn-btn .bb-r { font-family: var(--mono); font-size: 11px; color: var(--gold-bright); white-space: nowrap; }
.rp-note { font-family: var(--mono); font-size: 10px; letter-spacing: 0.04em; color: var(--faint); }

/* =========================================================
   COACH — first-visit explainer per screen
   ========================================================= */
/* `display: flex` on the class would silently defeat the `hidden` attribute —
   author rules beat the UA's `[hidden]{display:none}` no matter the
   specificity. The overlay sat permanently over the lobby, dimming it, with
   the JS convinced it was closed. */
.coach[hidden] { display: none; }
.coach {
  position: fixed; inset: 0; z-index: 800; display: flex; align-items: flex-end; justify-content: center;
  padding: 0 18px calc(30px + env(safe-area-inset-bottom, 0px)); background: rgba(5,5,6,0.62);
  animation: coachIn 0.35s var(--ease);
}
@keyframes coachIn { from { opacity: 0; } to { opacity: 1; } }
.coach-card {
  width: min(480px, 94vw); border: 1px solid var(--line-gold); border-radius: 14px;
  background: linear-gradient(170deg, var(--panel-2), var(--panel));
  box-shadow: 0 30px 70px -20px rgba(0,0,0,0.95); padding: 22px 24px 18px; text-align: left;
}
.coach-eyebrow { font-family: var(--mono); font-size: 9px; letter-spacing: 0.26em; text-transform: uppercase; color: var(--gold-dim); }
.coach-text { font-size: 0.98rem; line-height: 1.65; color: var(--text); margin: 10px 0 16px; min-height: 3.2em; }
.coach-text b { color: var(--gold-bright); }
.coach-foot { display: flex; align-items: center; justify-content: space-between; }
.coach-dots { display: flex; gap: 6px; }
.coach-dots i { width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,0.16); }
.coach-dots i.on { background: var(--gold-bright); }
.coach-next { min-width: 110px; padding: 10px 18px; }

.loading-tip {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.06em; color: var(--muted);
  max-width: min(420px, 84vw); text-align: center; line-height: 1.7; min-height: 2.4em;
}
.loading-tip b { color: var(--gold-bright); }

/* =========================================================
   MOBILE
   ========================================================= */
@media (max-width: 860px) {
  .ritual-lab { grid-template-columns: 1fr; }
  .ritual-focus { min-height: 300px; }
  .hud-chip { padding: 6px 11px; }
  .hud { margin-right: 10px; gap: 7px; }
}
@media (max-width: 520px) {
  .game-nav { padding: 14px 16px; }
  .lobby-hero { flex-wrap: wrap; padding: 18px; gap: 14px; min-height: 0; }
  .lh-state { margin-left: 0; }
  .lh-card { width: 56px; }
  .lobby-grid { gap: 10px; }
  .mode-tile { min-height: 106px; padding: 12px; }
  .hud-k { display: none; }   /* number-only chips; labels don't fit beside the burger */
}


/* ---------- Ritual: the offering slot + role badges ---------- */
.ritual-offer { margin-top: 4px; }
.ro-slot {
  display: flex; align-items: center; gap: 12px; min-height: 64px;
  border: 1px dashed var(--line-gold); border-radius: 10px; padding: 10px 14px;
  background: rgba(197,164,96,0.03);
}
.ro-slot.filled { border-style: solid; border-color: var(--el-color); }
.ro-hint { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.04em; color: var(--faint); line-height: 1.7; }
.ro-hint b { color: var(--gold-bright); }
.ro-art {
  flex: none; width: 38px; aspect-ratio: 3/5; border-radius: 5px;
  background-size: cover; background-position: center 20%;
  border: 1px solid var(--el-color);
}
.ro-meta b { display: block; font-family: var(--serif); font-size: 0.95rem; color: #fff; }
.ro-meta span { font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.06em; color: var(--muted); }
.copy-badge {
  position: absolute; top: 6px; left: 6px; z-index: 3;
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.06em;
  color: var(--gold-bright); background: rgba(5,5,6,0.8); border: 1px solid var(--line-gold);
  border-radius: 14px; padding: 2px 7px;
}
.role-badge {
  position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%); z-index: 3;
  font-family: var(--mono); font-size: 8px; letter-spacing: 0.14em; text-transform: uppercase;
  border-radius: 12px; padding: 3px 9px; white-space: nowrap;
}
.role-t { color: #050506; background: var(--gold-bright); }
.role-s { color: var(--oxblood-bright); background: rgba(5,5,6,0.85); border: 1px solid var(--oxblood); }
/* Offering selection: oxblood ring so it never reads as the gold target. */
.pick-card.picked-source {
  border-color: var(--oxblood-bright);
  box-shadow: 0 0 0 2px var(--oxblood-bright) inset,
              inset 0 0 44px rgba(143,48,64,0.3), 0 0 18px -4px var(--oxblood-bright);
}


/* ---------- Deck pick: sticky action bar ---------- */
.pick-bar {
  position: sticky; bottom: 14px; z-index: 30;
  display: flex; align-items: center; justify-content: center; gap: 14px;
  padding: 12px 16px; margin-top: 18px;
  border: 1px solid var(--line); border-radius: 14px;
  background: rgba(10,10,13,0.92); backdrop-filter: blur(6px);
  box-shadow: 0 18px 44px -16px rgba(0,0,0,0.9);
  transition: border-color 0.3s, box-shadow 0.3s;
}
.pick-bar.up { border-color: var(--line-gold); }
.pick-bar-count { font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em; color: var(--gold-bright); min-width: 46px; text-align: center; }
@media (max-width: 520px) {
  .pick-bar { bottom: 8px; gap: 10px; padding: 10px 12px; }
  .pick-bar .btn { padding: 11px 14px; font-size: 10.5px; }
}


/* Mute: same chip as the "?" — a thin slash crosses the note when off. */
.hud-mute { position: relative; }
.hud-mute.muted { color: var(--faint); }
.hud-mute.muted::after {
  content: ""; position: absolute; left: 6px; right: 6px; top: 50%;
  height: 1px; background: var(--oxblood-bright); transform: rotate(-38deg);
}

/* ---------- Fixed back chip (top-left, under the nav) ---------- */
.back-chip {
  position: fixed; top: 78px; left: 22px; z-index: 40;
  display: flex; align-items: center; gap: 6px;
  padding: 9px 16px; border: 1px solid var(--line); border-radius: 22px;
  background: rgba(10,10,13,0.88); backdrop-filter: blur(5px);
  color: var(--muted); font-family: var(--mono); font-size: 10.5px;
  letter-spacing: 0.14em; text-transform: uppercase; cursor: pointer;
  transition: border-color 0.25s, color 0.25s;
}
.back-chip:hover { border-color: var(--gold); color: var(--gold-bright); }
.back-chip[hidden] { display: none; }
@media (max-width: 520px) { .back-chip { top: 66px; left: 12px; padding: 8px 13px; } }

/* ---------- Loose essence: compact legacy row ---------- */
.loose-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 9px 12px; border: 1px dashed var(--line); border-radius: 10px;
}
.loose-k { font-family: var(--mono); font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); margin-right: 2px; }
.loose-btn {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.06em;
  color: var(--muted); background: rgba(255,255,255,0.02);
  border: 1px solid var(--line); border-radius: 16px; padding: 6px 11px; cursor: pointer;
  transition: border-color 0.25s, color 0.25s;
}
.loose-btn:hover { border-color: var(--gold); color: var(--gold-bright); }

/* ---------- Altar slots: tags, clear buttons, step states ---------- */
.slot-tag {
  position: absolute; top: 10px; left: 10px; z-index: 4;
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--gold-bright); background: rgba(5,5,6,0.78);
  border: 1px solid var(--line-gold); border-radius: 14px; padding: 4px 10px;
}
.slot-tag-offer { position: static; flex: none; color: var(--oxblood-bright); border-color: var(--oxblood); }
.slot-clear {
  position: absolute; top: 8px; right: 8px; z-index: 5;
  width: 28px; height: 28px; border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.25); background: rgba(5,5,6,0.75);
  color: #fff; font-size: 16px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: border-color 0.2s, color 0.2s;
}
.slot-clear:hover { border-color: var(--oxblood-bright); color: var(--oxblood-bright); }
.slot-clear[hidden] { display: none; }
.ro-slot { position: relative; }
.ro-slot .slot-clear { position: static; margin-left: auto; flex: none; }

.rp-step { transition: opacity 0.3s; }
.rp-step.done { opacity: 0.42; }
.rp-step.done i { background: var(--gold-dim); color: #050506; border-color: var(--gold-dim); }
.rp-step.now i { background: var(--gold-bright); color: #050506; border-color: var(--gold-bright); }
.rp-step.now span b { color: var(--gold-bright); }

/* ============================================================================
   AUTH GATE — wallet sign-in + username.
   Sits above every other layer (nav is z-100, coach z-800) because none of the
   game chrome is meaningful before we know who is holding the wallet.
   ========================================================================== */
.auth-gate {
  position: fixed; inset: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  padding: 24px 18px;
  overflow-y: auto;
}
/* Author `display` must not defeat the hidden attribute — this exact trap
   already cost hours on .coach, which sat over the whole lobby while "closed". */
.auth-gate[hidden] { display: none; }

.auth-bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0;
}
.auth-veil {
  position: absolute; inset: 0; z-index: 1;
  background:
    radial-gradient(ellipse at 50% 40%, rgba(5,5,6,0.42) 0%, rgba(5,5,6,0.88) 62%, #050506 100%),
    linear-gradient(180deg, rgba(5,5,6,0.55), rgba(5,5,6,0.75));
}

.auth-panel {
  position: relative; z-index: 2;
  width: 100%; max-width: 460px; text-align: center;
  margin: auto;
}
.auth-mark {
  width: 62px; height: 62px; object-fit: contain;
  /* A global rule makes images display:block, so the panel's text-align has no
     effect on this one — it needs auto margins to centre. */
  display: block; opacity: 0.9; margin: 0 auto 22px;
  filter: drop-shadow(0 0 22px rgba(197,164,96,0.28));
}

.auth-step { display: none; padding: 0; }   /* see .set-group — global section padding */
.auth-step.active { display: block; animation: fade 0.5s var(--ease); }

.auth-title {
  font-family: var(--serif); font-weight: 500;
  font-size: clamp(1.75rem, 5.2vw, 2.4rem); line-height: 1.16;
  color: #fff; margin: 14px 0 12px;
}
.auth-sub {
  color: var(--muted); font-size: 0.94rem; line-height: 1.6;
  max-width: 380px; margin: 0 auto 26px;
}

/* ---------- wallet choices ---------- */
.wallet-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }

.wallet-btn {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 13px 16px; cursor: pointer; text-align: left;
  background: linear-gradient(180deg, rgba(22,22,28,0.92), rgba(16,16,20,0.92));
  border: 1px solid var(--line); border-radius: 12px;
  color: var(--text); font-family: var(--sans); font-size: 0.95rem;
  transition: border-color 0.25s var(--ease), transform 0.25s var(--ease), background 0.25s var(--ease);
  backdrop-filter: blur(4px);
}
.wallet-btn:hover:not(:disabled) {
  border-color: var(--line-gold); transform: translateY(-1px);
  background: linear-gradient(180deg, rgba(30,30,38,0.94), rgba(20,20,26,0.94));
}
.wallet-btn:disabled { opacity: 0.55; cursor: default; }
.wallet-btn.busy { opacity: 0.7; cursor: progress; }

/* Monogram tile instead of a fetched brand logo: the CSP-free, offline-safe
   way to show a wallet's identity without shipping someone else's trademark
   or making an external request. --accent carries each wallet's brand hue. */
.wallet-ico {
  flex: 0 0 34px; width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center;
  font-family: var(--serif); font-size: 1.05rem; font-weight: 600; color: #0b0b0e;
  background: linear-gradient(150deg, var(--accent, var(--gold-bright)), color-mix(in srgb, var(--accent, var(--gold)) 62%, #000));
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.28);
}
.wallet-name { flex: 1; font-weight: 500; letter-spacing: 0.01em; }
.wallet-tag {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--faint);
}
.wallet-btn.ready .wallet-tag { color: var(--gold); }

.auth-error {
  color: var(--oxblood-bright); font-size: 0.88rem; line-height: 1.5;
  margin: 0 auto 14px; max-width: 380px;
}
.auth-error[hidden] { display: none; }

.auth-fine {
  color: var(--faint); font-size: 0.76rem; line-height: 1.6;
  max-width: 350px; margin: 0 auto;
}

/* ---------- username ---------- */
.name-field { position: relative; max-width: 320px; margin: 0 auto 10px; }
.name-input {
  width: 100%; padding: 14px 62px 14px 16px;
  background: rgba(10,10,13,0.85); border: 1px solid var(--line);
  border-radius: 11px; color: var(--text);
  font-family: var(--mono); font-size: 1rem; letter-spacing: 0.04em;
  transition: border-color 0.25s var(--ease);
}
.name-input::placeholder { color: #3d3b38; }
.name-input:focus { outline: none; border-color: var(--gold-dim); }
.name-field.ok .name-input { border-color: var(--earth); }
.name-field.bad .name-input { border-color: var(--oxblood-bright); }
.name-count {
  position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
  font-family: var(--mono); font-size: 11px; color: var(--faint); pointer-events: none;
}

.name-status {
  font-size: 0.8rem; color: var(--faint); min-height: 20px;
  margin: 0 auto 22px; max-width: 320px; line-height: 1.5;
}
.name-status.ok  { color: var(--earth); }
.name-status.bad { color: var(--oxblood-bright); }
.name-status.checking { color: var(--muted); }

.auth-go { width: 100%; max-width: 320px; }
.auth-link {
  display: block; margin: 16px auto 0; padding: 6px;
  background: none; border: none; cursor: pointer;
  color: var(--faint); font-family: var(--sans); font-size: 0.82rem;
  text-decoration: underline; text-underline-offset: 3px;
  transition: color 0.25s var(--ease);
}
.auth-link:hover { color: var(--muted); }

/* Local-mode banner. Deliberately visible: if this is showing in production,
   progress is browser-local and usernames are not globally unique. */
.auth-mode {
  position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%);
  z-index: 3; margin: 0; padding: 5px 12px; border-radius: 99px;
  background: rgba(10,10,13,0.8); border: 1px solid var(--line);
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--faint); white-space: nowrap;
}
.auth-mode[hidden] { display: none; }

@media (max-width: 520px) {
  .auth-mark { width: 50px; height: 50px; margin-bottom: 16px; }
  .auth-sub { font-size: 0.88rem; margin-bottom: 20px; }
  .wallet-btn { padding: 12px 14px; }
}
@media (prefers-reduced-motion: reduce) {
  .auth-step.active { animation: none; }
}

/* Signed-in identity chip. Sits with Ash/Arcana so "who am I playing as" is
   answerable from every screen, not just the lobby. */
.hud-who { max-width: 150px; }
.hud-who .hud-v { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 80px; }
.hud-who .hud-k { font-family: var(--mono); font-size: 9px; letter-spacing: 0.06em; }
.hud-who[hidden] { display: none; }
@media (max-width: 640px) { .hud-who { display: none; } }

/* ============================================================================
   SETTINGS — one panel, opened from the nav menu.
   Range inputs are restyled from scratch: a default <input type=range> renders
   as the operating system's own widget, which is the single fastest way to
   make a game look like a web form.
   ========================================================================== */
.settings {
  position: fixed; inset: 0; z-index: 950;
  display: flex; align-items: center; justify-content: center; padding: 22px 16px;
  background: rgba(4,4,6,0.82); backdrop-filter: blur(7px);
  overflow-y: auto;
}
.settings[hidden] { display: none; }   /* author display must not beat [hidden] */

.settings-card {
  width: 100%; max-width: 430px; margin: auto;
  background: linear-gradient(180deg, rgba(18,18,23,0.98), rgba(11,11,14,0.98));
  border: 1px solid var(--line-gold); border-radius: 14px;
  padding: 26px 24px 18px;
  box-shadow: 0 30px 80px -30px #000, inset 0 1px 0 rgba(230,200,132,0.08);
  animation: setIn 0.36s var(--ease);
}
@keyframes setIn { from { opacity: 0; transform: translateY(14px) scale(0.985); } }

.set-head { position: relative; text-align: center; margin-bottom: 22px; }
.set-head h2 {
  font-family: var(--serif); font-weight: 500; font-size: 1.72rem;
  color: #fff; margin: 6px 0 0; letter-spacing: 0.01em;
}
.set-close {
  position: absolute; top: -6px; right: -4px;
  width: 34px; height: 34px; border-radius: 50%;
  background: none; border: 1px solid var(--line); color: var(--muted);
  font-size: 20px; line-height: 1; cursor: pointer;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.set-close:hover { color: var(--gold-bright); border-color: var(--line-gold); }

/* styles.css sets a global `section { padding: 120px 0 }` for landing-page
   bands. These are <section> too, so every group inherited 240px of vertical
   padding and Account fell off the bottom of the phone. Reset explicitly. */
.set-group { padding: 0; margin-bottom: 22px; }
.set-group-title {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--gold-dim);
  padding-bottom: 8px; margin-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.set-row {
  display: grid; grid-template-columns: 78px 1fr 34px;
  align-items: center; gap: 12px; margin-bottom: 13px;
}
.set-label { font-size: 0.9rem; color: var(--text); }
.set-val {
  font-family: var(--mono); font-size: 11px; color: var(--faint);
  text-align: right; font-variant-numeric: tabular-nums;
}

/* ---- slider ---- */
.set-slider {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 20px; background: none; cursor: pointer;
}
.set-slider::-webkit-slider-runnable-track {
  height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, var(--gold) var(--fill, 50%), var(--line) var(--fill, 50%));
}
.set-slider::-moz-range-track { height: 2px; border-radius: 2px; background: var(--line); }
.set-slider::-moz-range-progress { height: 2px; border-radius: 2px; background: var(--gold); }
/* A diamond, not a circle — it echoes the ◆ divider used across the site. */
.set-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; margin-top: -5.5px;
  background: var(--gold-bright); transform: rotate(45deg);
  box-shadow: 0 0 10px rgba(230,200,132,0.45);
  transition: box-shadow 0.2s var(--ease);
}
.set-slider::-moz-range-thumb {
  width: 13px; height: 13px; border: none;
  background: var(--gold-bright); transform: rotate(45deg);
}
.set-slider:hover::-webkit-slider-thumb { box-shadow: 0 0 16px rgba(230,200,132,0.75); }
.set-slider:focus-visible { outline: none; }
.set-slider:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px rgba(197,164,96,0.25); }

/* ---- toggle ---- */
.set-toggle {
  grid-column: 2 / span 2; justify-self: start;
  display: inline-flex; align-items: center; gap: 9px;
  padding: 5px 12px 5px 6px; cursor: pointer;
  background: rgba(10,10,13,0.7); border: 1px solid var(--line); border-radius: 99px;
  color: var(--muted); font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.1em; text-transform: uppercase;
  transition: border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.set-toggle i {
  width: 26px; height: 15px; border-radius: 99px; position: relative;
  background: var(--line); transition: background 0.25s var(--ease);
}
.set-toggle i::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 11px; height: 11px; border-radius: 50%; background: var(--faint);
  transition: transform 0.25s var(--ease), background 0.25s var(--ease);
}
.set-toggle[aria-checked="true"] { border-color: var(--line-gold); color: var(--gold-bright); }
.set-toggle[aria-checked="true"] i { background: var(--gold-dim); }
.set-toggle[aria-checked="true"] i::after { transform: translateX(11px); background: var(--gold-bright); }

/* ---- segmented ---- */
.set-seg {
  grid-column: 2 / span 2; justify-self: start;
  display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
}
.set-seg button {
  padding: 6px 15px; background: none; border: none; cursor: pointer;
  color: var(--muted); font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.1em; text-transform: uppercase;
  transition: background 0.25s var(--ease), color 0.25s var(--ease);
}
.set-seg button + button { border-left: 1px solid var(--line); }
.set-seg button[aria-checked="true"] { background: rgba(197,164,96,0.14); color: var(--gold-bright); }

.set-note { font-size: 0.76rem; color: var(--faint); line-height: 1.55; margin: 2px 0 0; }

/* ---- account ---- */
.set-account {
  padding: 12px 14px; margin-bottom: 11px;
  background: rgba(10,10,13,0.6); border: 1px solid var(--line); border-radius: 10px;
}
.set-acc-name { font-family: var(--serif); font-size: 1.14rem; color: var(--text); }
.set-acc-wallet {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.06em;
  color: var(--faint); margin-top: 3px;
}
.set-signout { width: 100%; }

.set-foot {
  text-align: center; padding-top: 12px; border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.11em;
  text-transform: uppercase; color: #4a4844;
}

@media (max-width: 420px) {
  .settings-card { padding: 22px 18px 16px; }
  .set-row { grid-template-columns: 68px 1fr 30px; gap: 9px; }
}

/* Motion: Reduced. Mirrors what prefers-reduced-motion already switches off,
   but as a deliberate choice — some phones are slow without the OS flag set. */
.motion-reduced .amb-glow,
.motion-reduced .amb-sigil,
.motion-reduced .loading-bar i,
.motion-reduced .end-seal { animation: none !important; }
.motion-reduced .auth-step.active,
.motion-reduced .settings-card,
.motion-reduced .game-screen.active { animation: none !important; }
.motion-reduced .bf-card { transition-duration: 0.12s !important; }

/* The hover lift is a 2D gesture; inside the ring it would fight the placement
   transform and fling the card out of the circle. Light the rim instead. */

/* ---------- Lobby menu ----------
   Five mode cards in one row, then a thin utility row. Eight equal cards read
   as a wall — nothing tells the eye where to start, and Settings carried the
   same weight as the Arcade. The free draw leads and is lit; the three
   utilities drop to chips, which is what they are. */
/* The row fills the screen the way the landing hero does. It used to stop at
   940px on a 1440px display — 63% of the width — which left the lobby reading
   as a small panel floating in a lot of background while the landing next door
   went nearly edge to edge. Same brand, two different scales. */
/* width:100% is load-bearing. .lobby is a column flex container, so without it
   the grid is shrink-to-fit and sizes itself from the tiles' own content — the
   max-width below is then never reached and every step here does nothing but
   cap a width the row was never going to hit. */
.lobby-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: clamp(9px, 1vw, 16px);
  width: 100%; margin: 0 auto;
  /* Three caps, smallest wins:
       1340px — the row stops growing on a very large monitor
       90vw   — never touches the edges
       135vh  — the one that does the real work. Five 3:5 tiles are exactly a
                third of the row tall, so the row's WIDTH is what decides how
                much HEIGHT it eats. Tying it to vh makes the fit continuous;
                stepped max-widths fitted the top of each band and scrolled at
                the bottom of it (900 fine, 850 scrolled; 808 fine, 768
                scrolled). */
  max-width: min(1340px, 90vw, 140vh);
}
/* Card size is driven by the ROW width: five 3:5 cards means card height is
   almost exactly a third of the row, so every 100px trimmed here buys back 33px
   of page height. These steps are measured against real viewport heights (a
   "900px" laptop is 816px once Chrome's chrome is subtracted, and plenty of
   machines are 768px), tuned so the lobby never scrolls while the mark above
   stays large. The vw cap rides along at every step so a narrow-but-tall window
   never pushes the row past the screen. */
@media (max-width: 1000px) { .lobby-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 620px)  { .lobby-grid { grid-template-columns: repeat(2, 1fr); gap: 9px; } }

/* The one card that changes on its own, and the reason to come back daily. */
.mode-tile.is-feature { border-color: var(--gold-dim); }
.mode-tile.is-feature::before { border-color: rgba(230,200,132,0.42); }
.mode-tile.is-feature .mt-art { opacity: 0.78; }
.mode-tile.is-feature:not(:disabled) {
  box-shadow: 0 10px 26px -14px rgba(0,0,0,0.9), 0 0 22px -10px rgba(197,164,96,0.45);
}
.mode-tile.is-feature:disabled { border-color: var(--line); }
.mt-state {
  position: absolute; top: 9px; right: 9px; z-index: 4;
  font-family: var(--mono); font-size: 7.5px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--gold-bright); border: 1px solid var(--line-gold); border-radius: 16px;
  padding: 3px 8px; background: rgba(5,5,6,0.78);
}
.mode-tile:disabled .mt-state { color: var(--faint); border-color: var(--line); }

/* ---------- Utilities ---------- */
.lobby-utils {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: clamp(8px, 1vw, 14px); margin-top: 0;
}
/* Kotak gold di belakang teks.
   Sebelumnya background-nya rgba(255,255,255,0.02) — praktis transparan, jadi
   art latar yang ramai tembus ke belakang tulisan dan labelnya nyaris tidak
   terbaca. Sekarang tiap chip punya alas gelap sendiri + garis emas, supaya
   kontrasnya tidak bergantung pada gambar apa yang kebetulan ada di belakang. */
.util-chip {
  display: flex; align-items: baseline; gap: 10px;
  padding: 10px 18px; cursor: pointer;
  background:
    linear-gradient(180deg, rgba(38,30,16,0.94) 0%, rgba(14,11,8,0.96) 100%);
  border: 1px solid rgba(197,164,96,0.42); border-radius: 10px;
  box-shadow:
    inset 0 1px 0 rgba(230,200,132,0.14),
    0 6px 20px rgba(0,0,0,0.5);
  backdrop-filter: blur(6px);
  color: var(--text); font: inherit; text-align: left;
  transition: border-color 0.28s var(--ease), background 0.28s var(--ease),
              box-shadow 0.28s var(--ease);
}
.util-chip:hover:not(:disabled) {
  border-color: rgba(230,200,132,0.72);
  background: linear-gradient(180deg, rgba(52,41,20,0.95) 0%, rgba(20,16,10,0.96) 100%);
  box-shadow:
    inset 0 1px 0 rgba(230,200,132,0.22),
    0 8px 26px rgba(0,0,0,0.55);
}
.uc-name { font-family: var(--serif); font-size: 1rem; color: var(--gold-bright); }
.uc-meta {
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.12em;
  text-transform: uppercase; color: rgba(230,200,132,0.62);
}
/* locked: tetap harus terbaca, jadi yang diredupkan warnanya — bukan seluruh
   kotaknya lewat opacity (opacity 0.5 juga melunturkan alasnya). */
.util-chip.locked {
  cursor: not-allowed;
  border-color: rgba(197,164,96,0.18);
  box-shadow: inset 0 1px 0 rgba(230,200,132,0.06), 0 4px 14px rgba(0,0,0,0.4);
}
.util-chip.locked .uc-name { color: rgba(230,200,132,0.5); }
.util-chip.locked .uc-meta { color: rgba(230,200,132,0.34); }
.tile-draw    .mt-art { background-image: url('../assets/panels/draw.webp'); }
.assets-sm .tile-draw    .mt-art { background-image: url('../assets/panels/draw-sm.webp'); }

/* ---------- Lobby header ----------
   Small, on purpose. This is a working screen, not a second landing hero — the
   mark just has to say "you are still inside XXII" while the row of cards
   below does the actual work. */
.lobby-head {
  text-align: center;
  /* The mark sits high; the CARDS stay where they were. That is why the bottom
     margin grew as the top one shrank — moving the title up must not drag the
     row of tiles up with it. Both are proportional (vh) so the balance holds at
     any height instead of only at one. */
  margin: clamp(12px, 4.5vh, 80px) 0 clamp(10px, 3.5vh, 58px);
}
/* One line under the mark, so the screen says what it is instead of opening on
   a monogram alone. */
.lobby-desc {
  max-width: 46ch; margin: 14px auto 0;
  font-family: var(--serif); font-size: clamp(0.92rem, 1.25vw, 1.06rem);
  line-height: 1.6; letter-spacing: 0.015em;
  /* Not var(--muted): the lobby film runs bright behind this line, and muted
     grey disappeared into it. Near-white over a hard shadow reads on both the
     dark and the lit frames of the loop. */
  color: rgba(244, 237, 223, 0.9);
  text-shadow: 0 1px 3px rgba(0,0,0,0.95), 0 4px 20px rgba(0,0,0,0.85);
}
.lobby-mark {
  display: block; font-family: var(--display); font-weight: 600;
  font-size: clamp(3.4rem, 7vw, 5.4rem); letter-spacing: 0.06em; line-height: 1;
  background: linear-gradient(174deg, #f4e3b6 0%, var(--gold-bright) 38%, var(--gold) 66%, #8a6f37 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 3px 22px rgba(197,164,96,0.34));
}
.lobby-sub {
  display: block; font-family: var(--serif); font-style: italic;
  font-size: clamp(1.15rem, 2.2vw, 1.6rem); color: var(--gold-bright);
  letter-spacing: 0.14em; margin-top: 8px; opacity: 0.92;
}
/* Short laptops keep the mark large — the CARDS give up height instead, since
   the mark is what identifies the screen. */
/* The margins above are proportional and need no per-band override — only the
   TYPE steps down, because a 5.4rem mark on a short laptop eats the row.

   This steps at 900, not 820. The type drop is a 26px cliff in the head's
   height, and the row's vh cap cannot see it: every height just ABOVE the
   boundary got the tall mark with almost the room of the height just below it,
   so 830 and 860 scrolled while 808 had 44px to spare. The cliff has to land
   where there is still slack to absorb it. Same reason .lobby-desc now hides at
   740 rather than 700. */
@media (max-height: 900px) {
  .lobby-mark { font-size: clamp(2.8rem, 5.4vw, 3.8rem); }
  .lobby-sub { font-size: 1.05rem; margin-top: 5px; }
  .lobby-desc { margin-top: 9px; font-size: 0.9rem; line-height: 1.5; }
}
@media (max-height: 740px) {
  .lobby-desc { display: none; }   /* the tiles need the height more */
}
