/* ==========================================================================
   DAGLINGS — Battle table
   Loads after card.css and site.css and reuses their tokens.

   SCOPE: everything here is under #dg-battle. That scope is not decoration —
   it is what keeps the table's styles off the shared card component, which
   every other page also renders. Two rules follow from it:

     1. NOTHING HERE MAY STYLE A CARD INTERNAL. No .dc-name, no .dc-stats, no
        .dc-foil. The card owns its own inside. This file touches .dc-card as
        a BOX — opacity, transform, a ring around it — and stops at its edge.
        (css/card.css carries the card, including the foils; the battle build's
        placeholder .dg-card and its stand-in foil recipe are gone.)
     2. Selection and targeting rings are drawn on the .tilt-scene wrapper,
        which this page owns, rather than on the card. The card's rounding
        lives on an inner layer, so a ring painted on .dc-card itself would be
        a rectangle around a rounded card.

   COLOUR: red is reserved for red-chain references — GHOSTDAG reds and the
   red/gold finishes — so it is NOT used here for the rival, for targeting or
   for errors, however much a battle UI wants to. Heat orange carries threat;
   teal carries your own side; Surge amber carries a warning.
   ========================================================================== */

/* The orientation line under the Battle heading — the same sentence the home
   page's hero carries. Set below the section's own prose and quieter than it: it
   says what the systems are FOR, which is worth saying once and not worth saying
   loudly. Outside #dg-battle on purpose, because it is page copy rather than part
   of the table. */
.dg-onboard {
  margin: 10px 0 0;
  max-width: 52ch;
  font-size: 14px;
  color: var(--faint);
}

#dg-battle {
  --cw: clamp(62px, 9vw, 104px);      /* one card's width on the table */
  --dg-you: var(--teal);
  --dg-them: var(--t-heat);
  --dg-warn: var(--t-surge);
  --dg-chip: rgba(10, 15, 25, 0.84);
  --dg-ring: calc(var(--cw) * 0.05);  /* ~the card's own corner radius */
  margin-top: 28px;
}

#dg-battle .dg-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 18px;
  align-items: start;
}
@media (max-width: 1080px) {
  #dg-battle .dg-wrap { grid-template-columns: 1fr; }
}

/* ==========================================================================
   The table
   ========================================================================== */

#dg-battle .dg-table { --c: 26px; }
#dg-battle .dg-table__in {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  /* Two faint pools of light, one per side, so the board reads as two
     territories rather than one field. */
  background:
    radial-gradient(ellipse 70% 40% at 50% 0%, rgba(255, 106, 61, 0.05), transparent 70%),
    radial-gradient(ellipse 70% 40% at 50% 100%, rgba(73, 224, 192, 0.06), transparent 70%),
    var(--panel);
}

#dg-battle .dg-field {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 14px;
}

#dg-battle .dg-board {
  display: grid;
  grid-template-columns: repeat(4, calc(var(--cw) * 1.4));
  gap: 8px;
  padding: 4px;
}

/* A cell is square enough to hold a card lying either way up: a 5:7 card
   turned sideways for defense mode is 1.4 cards wide. */
#dg-battle .dg-cell {
  position: relative;
  width: calc(var(--cw) * 1.4);
  height: calc(var(--cw) * 1.4);
  display: grid;
  place-items: center;
  --c: 10px;
  border: 1px dashed rgba(255, 255, 255, 0.12);
}
#dg-battle .dg-cell:has(.dg-slot) { border-color: transparent; }

/* Defense mode = the card turned sideways, Yu-Gi-Oh style. The rotation is on
   the SLOT, not the card, so the lunge and hit animations — which are the
   card's own transform — still play on a defending Dagling. */
#dg-battle .dg-slot {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--cw);
  height: calc(var(--cw) * 1.4);
  transition: transform 0.2s ease;
}
#dg-battle .dg-slot.is-def { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  #dg-battle .dg-slot { transition: none; }
}

/* Cards size from their own container, so the wrapper sets the width. */
#dg-battle .dg-slot .tilt-scene,
#dg-battle .dg-hand .tilt-scene { width: var(--cw); }

/* ==========================================================================
   Card state — drawn on the wrapper, never inside the card
   ========================================================================== */

#dg-battle .tilt-scene { position: relative; }
#dg-battle .tilt-scene::after {
  content: "";
  position: absolute;
  inset: calc(var(--cw) * -0.045);
  z-index: 3;
  pointer-events: none;
  border-radius: var(--dg-ring);
  box-shadow: inset 0 0 0 2px var(--dg-mark, transparent);
  opacity: 0;
  transition: opacity 0.15s ease;
}
#dg-battle .tilt-scene:has(.is-sel)::after,
#dg-battle .tilt-scene:has(.is-target)::after,
#dg-battle .tilt-scene:has(.is-partner)::after,
#dg-battle .tilt-scene:has(.is-playable)::after { opacity: 1; }

#dg-battle .tilt-scene:has(.is-playable) { --dg-mark: rgba(73, 224, 192, 0.55); }
#dg-battle .tilt-scene:has(.is-partner) { --dg-mark: var(--dg-warn); }
#dg-battle .tilt-scene:has(.is-sel) { --dg-mark: var(--dg-you); }
/* A target is the loudest thing on the table, so it wins the cascade and
   pulses. Heat orange, because red belongs to the chain. */
#dg-battle .tilt-scene:has(.is-target) { --dg-mark: var(--dg-them); }
#dg-battle .tilt-scene:has(.is-target)::after { animation: dg-pulse 1.2s ease-in-out infinite; }

#dg-battle .dc-card.is-sel { transform: translateY(-6px); }
#dg-battle .dc-card.is-spent { opacity: 0.6; }
#dg-battle .dc-card.is-dim { opacity: 0.5; }
#dg-battle .dc-card { transition: transform 0.15s ease, opacity 0.15s ease; }
@media (hover: hover) {
  #dg-battle .dg-hand .dc-card:hover { transform: translateY(-5px); }
}

/* The name tag on YOUR face-down cards — you know what you set, the rival
   does not. A rival's face-down card is built with no name in the document
   at all, so there is nothing here to hide. */
#dg-battle .dg-tag,
#dg-battle .dg-flag {
  position: absolute;
  z-index: 4;
  pointer-events: none;
  padding: 3px 7px;
  font-family: "Chakra Petch", system-ui, sans-serif;
  font-size: 10px;
  font-weight: 600;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
#dg-battle .dg-tag {
  left: 50%;
  bottom: 6%;
  transform: translateX(-50%);
  background: var(--dg-chip);
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: var(--muted);
}
#dg-battle .dg-flag {
  left: 50%;
  top: 52%;
  transform: translate(-50%, -50%);
  background: var(--t-coolant);
  color: #032431;
}
/* Both ride the slot, so both need the rotation taken back out. */
#dg-battle .dg-slot.is-def .dg-flag { transform: translate(-50%, -50%) rotate(-90deg); }
#dg-battle .dg-slot.is-def .dg-tag { transform: translateX(-50%) rotate(-90deg); }

/* ==========================================================================
   The viewer seam
   A card's tap is a move, so the enlarged view gets its own control. It sits
   over the card's top corner, appears on hover or focus, and is always
   reachable on touch (where there is no hover) via a long press.
   ========================================================================== */

#dg-battle .dg-view {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 5;
  padding: 2px 7px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(6, 10, 18, 0.88);
  color: var(--ink);
  font-family: "Chakra Petch", system-ui, sans-serif;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s ease;
}
#dg-battle .tilt-scene:hover .dg-view,
#dg-battle .dg-view:focus-visible { opacity: 1; }
#dg-battle .dg-view:hover { background: rgba(73, 224, 192, 0.2); }
/* No hover to reveal it: the long press is the affordance, so the button
   stays out of the way rather than covering the art. */
@media (hover: none) {
  #dg-battle .dg-view { display: none; }
}
#dg-battle .dg-slot.is-def .dg-view { transform: rotate(-90deg); }
/* The showcase moves the whole .tilt-scene into its overlay, and the View
   button rides along inside it. Nothing to offer there — you are already
   looking at the card. */
.dc-showcase .dg-view { display: none; }

/* ==========================================================================
   Piles and trap slots
   ========================================================================== */

#dg-battle .dg-piles {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 4px 4px 20px;
}
#dg-battle .dg-piles-spacer {
  width: calc(var(--cw) * 2 + 24px);
  flex: 0 0 auto;
}

#dg-battle .dg-pile {
  position: relative;
  width: var(--cw);
  aspect-ratio: 5 / 7;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: default;
  --c: 8px;
}
#dg-battle .dg-pile--grave,
#dg-battle .dg-pile.is-draw { cursor: pointer; }
#dg-battle .dg-pile--grave { border: 1px dashed rgba(255, 255, 255, 0.14); }
#dg-battle .dg-pile .dc-card,
#dg-battle .dg-pile .dg-stack { position: absolute; inset: 0; }
/* The deck is the RENDERED stack (js/deck-stack.js), fitted to the slot's
   WIDTH with its own height following — it is not a card's 5:7, so it sits
   shorter than the 5:7 slot and is centred in it rather than stretched to it.
   It replaced three card backs offset behind each other; the render carries
   the stock's edge and thickness, which is what says fifty cards.
   The `.deckstack` sizing itself lives in css/site.css, shared with the shop's
   product shot — a deck is the same object on both pages. */
#dg-battle .dg-pile .dg-stack { display: grid; place-items: center; }
#dg-battle .dg-pile--grave .dc-card { filter: saturate(0.55) brightness(0.8); }

#dg-battle .dg-pile.is-draw { animation: dg-pulse-warm 1.2s ease-in-out infinite; }
#dg-battle .dg-pile.is-empty .dg-stack { opacity: 0.25; }

/* INSIDE the pile, never hanging off it: the pile is .cut, so its clip-path
   cut the badge's overhang away along with its corner (check-mobile's corner
   rule caught "45" there). 5px + 5px clears the 8px chamfer. */
#dg-battle .dg-pile-count {
  position: absolute;
  right: 5px;
  top: 5px;
  z-index: 3;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  display: grid;
  place-items: center;
  background: var(--panel-2);
  border: 1px solid var(--line);
  font: 700 12px "Chakra Petch", system-ui, sans-serif;
}
#dg-battle .dg-pile-label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -18px;
  text-align: center;
  font: 600 11px "Chakra Petch", system-ui, sans-serif;
  color: var(--faint);
  white-space: nowrap;
}
#dg-battle .dg-pile.is-draw .dg-pile-label { color: var(--dg-warn); }

#dg-battle .dg-traps {
  display: grid;
  grid-template-columns: repeat(4, calc(var(--cw) * 1.4));
  gap: 8px;
  padding: 0 4px;
}
#dg-battle .dg-trap {
  height: calc(var(--cw) * 0.4);
  display: grid;
  place-items: center;
  border: 1px dashed rgba(255, 255, 255, 0.1);
  color: var(--faint);
  font: 600 10px "Chakra Petch", system-ui, sans-serif;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
/* A SET TRAP — the dev-only trap experiment (?traps=1). One look for every
   set trap on both sides: a neutral hatch that says a trap is there and
   nothing about which. Nothing here may vary by trap; your own trap's name is
   a separate line of text, and a rival's cell has none. */
#dg-battle .dg-trap.is-set {
  grid-auto-flow: row;
  align-content: center;
  gap: 2px;
  border: 1px solid color-mix(in srgb, var(--teal) 40%, transparent);
  color: var(--ink);
  background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--teal) 10%, transparent) 0 6px, transparent 6px 12px), var(--panel-2);
}
#dg-battle .dg-trap__name {
  font: 600 9px "IBM Plex Sans", system-ui, sans-serif;
  letter-spacing: 0;
  text-transform: none;
  color: var(--faint);
}
#dg-battle .dg-traphand {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  gap: 4px;
}
#dg-battle .dg-traphand__count {
  width: 100%;
  font: 600 10px "Chakra Petch", system-ui, sans-serif;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}
#dg-battle .dg-traphand__btn {
  display: grid;
  text-align: left;
  font-size: 11px;
}
#dg-battle .dg-traphand__btn small {
  font-size: 9px;
  color: var(--faint);
}

/* RIG GEAR (beta) — a TEXT-ONLY placeholder card, because no gear art exists
   yet: name and BETA tag, "Rig · type · rarity", its trigger, its rules, and
   what setting it costs. The type colour is the edge, never a fill. */
#dg-battle .dg-gear {
  --gear: var(--teal);
  display: grid;
  gap: 3px;
  width: min(100%, 190px);
  padding: 8px 10px;
  border: 1px solid color-mix(in srgb, var(--gear) 55%, transparent);
  border-left-width: 3px;
  background: var(--panel-2);
  color: var(--ink);
  text-align: left;
  font: 400 11px/1.35 "IBM Plex Sans", system-ui, sans-serif;
  cursor: pointer;
}
#dg-battle .dg-gear:disabled { cursor: default; opacity: 0.72; }
#dg-battle .dg-gear:not(:disabled):hover { border-color: var(--gear); }
#dg-battle .dg-gear--heat { --gear: #ff6a3d; }
#dg-battle .dg-gear--root { --gear: #6fe06a; }
#dg-battle .dg-gear--ground { --gear: #d9a441; }
#dg-battle .dg-gear--surge { --gear: #ffd23f; }
#dg-battle .dg-gear--coolant { --gear: #3fd9ff; }
#dg-battle .dg-gear__head { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
#dg-battle .dg-gear__name { font: 600 13px "Chakra Petch", system-ui, sans-serif; }
#dg-battle .dg-gear__beta {
  font: 600 9px "Chakra Petch", system-ui, sans-serif;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 1px 5px;
  border: 1px solid color-mix(in srgb, var(--dg-warn, #ffd23f) 60%, transparent);
  color: var(--dg-warn, #ffd23f);
}
#dg-battle .dg-gear__meta,
#dg-battle .dg-gear__trigger {
  font: 600 10px "Chakra Petch", system-ui, sans-serif;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}
#dg-battle .dg-gear__trigger { color: var(--gear); }
#dg-battle .dg-gear__cost { font-size: 10px; color: var(--faint); }

/* YOUR GEAR IS A ROW UNDER YOUR TRAP SLOTS, at every width: beside them, in
   the piles' narrow column, two cards of rules text stacked into a tower that
   pushed your whole side of the board apart. The rival's side is one line of
   counts and stays where it was. No chamfer: a card of text has text in its
   corners. */
#dg-battle .dg-field:has(> #dg-traphand-0.dg-traphand) { flex-wrap: wrap; }
/* Wrapped, the trap slots would centre alone on their line and drift out from
   under the board; on a wide table the field becomes a grid that keeps the
   piles' column beside them, empty, as the spacer did. (At 680px and under the
   field already wraps, by the small-screen rule below.) */
@media (min-width: 681px) {
  #dg-battle .dg-field:has(> #dg-traphand-0.dg-traphand) {
    display: grid;
    grid-template-columns: auto calc(var(--cw) * 2 + 24px);
    justify-content: center;
    column-gap: 14px;
    row-gap: 10px;
  }
  #dg-battle .dg-field > #dg-traphand-0.dg-traphand { grid-column: 1 / -1; }
}
#dg-battle #dg-traphand-0.dg-traphand {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 170px), 1fr));
  align-items: stretch;
  gap: 8px;
  padding: 0 4px;
}
#dg-battle #dg-traphand-0 .dg-traphand__count { grid-column: 1 / -1; }
#dg-battle #dg-traphand-0 .dg-gear { width: auto; align-content: start; }

#dg-battle .dg-gear-box__toggle { display: inline-flex; align-items: center; gap: 8px; }
#dg-battle .dg-gear-box__blurb,
#dg-battle .dg-gear-box__rig { margin: 8px 0 0; font-size: 13px; color: var(--faint); }
#dg-battle .dg-gear-box__feedback { display: inline-block; margin-top: 8px; font-size: 13px; color: var(--teal); }

/* ==========================================================================
   HUDs
   ========================================================================== */

#dg-battle .dg-hud {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 9px 13px;
  background: var(--dg-chip);
  border: 1px solid var(--line);
  --c: 12px;
}
#dg-battle .dg-hud[data-p="0"].is-active { border-color: rgba(73, 224, 192, 0.5); }
#dg-battle .dg-hud[data-p="1"].is-active { border-color: rgba(255, 106, 61, 0.45); }
#dg-battle .dg-hud.is-target {
  cursor: pointer;
  border-color: var(--dg-them);
  animation: dg-pulse 1.2s ease-in-out infinite;
}
#dg-battle .dg-who {
  min-width: 92px;
  font: 600 15px "Chakra Petch", system-ui, sans-serif;
}
#dg-battle .dg-who small {
  display: block;
  font: 400 12px "IBM Plex Sans", system-ui, sans-serif;
  color: var(--faint);
}
#dg-battle .dg-life { display: flex; align-items: baseline; gap: 5px; }
#dg-battle .dg-life b { font: 700 26px/1 "Chakra Petch", system-ui, sans-serif; }
#dg-battle .dg-life small,
#dg-battle .dg-meta { color: var(--faint); font-size: 12.5px; }
#dg-battle .dg-bank { display: flex; align-items: center; gap: 3px; flex-wrap: wrap; }
#dg-battle .dg-bank b { margin-left: 4px; font: 700 17px "Chakra Petch", system-ui, sans-serif; }
#dg-battle .dg-meta { margin-left: auto; }

/* The banked-block pip. A block, squared off — the same object the footer's
   width scale and the void field are made of. */
#dg-battle .dg-hex {
  width: 11px;
  height: 11px;
  background: rgba(73, 224, 192, 0.16);
  border: 1px solid rgba(73, 224, 192, 0.75);
}

/* ==========================================================================
   The lane between the two sides
   ========================================================================== */

#dg-battle .dg-lane {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 12px;
  min-height: 58px;
}
#dg-battle .dg-lane::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 6px;
  transform: translateY(-50%);
  z-index: 0;
  opacity: 0.45;
  background: repeating-linear-gradient(
    90deg,
    transparent 0 6px,
    rgba(73, 224, 192, 0.4) 6px 20px,
    transparent 20px 26px
  );
  -webkit-mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
#dg-battle .dg-lane > * { position: relative; z-index: 1; }

#dg-battle .dg-arrow {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  color: var(--dg-warn);
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
#dg-battle .dg-arrow svg { width: 22px; height: 22px; display: block; }

#dg-battle .dg-turn {
  padding: 6px 10px;
  background: var(--dg-chip);
  border: 1px solid var(--line);
  font: 700 13px "Chakra Petch", system-ui, sans-serif;
  white-space: nowrap;
}
#dg-battle .dg-msg {
  flex: 1 1 260px;
  min-height: 34px;
  padding: 7px 12px;
  background: rgba(7, 11, 18, 0.7);
  border: 1px solid var(--line);
  font-size: 14px;
}
/* A refused move is a warning, not a chain red. */
#dg-battle .dg-msg.is-err {
  border-color: rgba(255, 210, 63, 0.55);
  color: #ffe9a8;
}
#dg-battle .dg-msg.is-new { animation: dg-in 0.35s ease-out; }
#dg-battle .dg-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* The site's button, at table scale. */
#dg-battle .btn--small {
  --c: 8px;
  padding: 9px 16px;
  font-size: 11px;
  letter-spacing: 0.14em;
}
#dg-battle .btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ==========================================================================
   Side panel
   ========================================================================== */

#dg-battle .dg-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
#dg-battle .dg-top-bar { display: flex; align-items: center; gap: 10px; }
#dg-battle .dg-top-bar h3 {
  flex: 1;
  font: 700 17px "Chakra Petch", system-ui, sans-serif;
}
#dg-battle .dg-box .framed__in { --pad-y: 12px; --pad-x: 12px; }
#dg-battle .dg-box h4 {
  margin: 0 0 8px;
  font: 600 14px "Chakra Petch", system-ui, sans-serif;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
#dg-battle .dg-log {
  max-height: 340px;
  overflow-y: auto;
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
}
#dg-battle .dg-log li {
  padding: 4px 8px;
  border-left: 2px solid var(--dg-you);
  background: rgba(255, 255, 255, 0.02);
}
#dg-battle .dg-log li[data-p="1"] { border-left-color: var(--dg-them); }
#dg-battle .dg-log li.is-turn {
  border-left-color: transparent;
  color: var(--faint);
  font-size: 12px;
}
#dg-battle .dg-seed { margin: 0; font-size: 12px; color: var(--faint); }
/* Surge amber, not chain red: "this is not your deck" is a notice, not an
   error, and red belongs to the chain. */
#dg-battle .dg-decknote {
  margin: 0 0 4px;
  padding: 8px 10px;
  border-left: 2px solid var(--t-surge);
  background: rgba(255, 210, 63, 0.07);
  font-size: 12.5px;
  color: #ffe9a8;
}
/* A LOANER IS NOT A WARNING. The amber above is for "your deck can't be played:
   …", which is a problem the reader has to go and fix. The Field Guide is a fact
   about the deck on the table and nothing is wrong, so it reads teal.

   Declared after the base rule and at equal weight, so document order carries it
   — the same tie the duelist tiles hit in css/story.css. */
#dg-battle .dg-decknote.is-loaner {
  border-left-color: var(--teal);
  background: rgba(73, 224, 192, 0.07);
  color: rgba(226, 255, 250, 0.82);
}
@media (max-width: 1080px) {
  #dg-battle .dg-log { max-height: 200px; }
}

/* ==========================================================================
   Hand
   ========================================================================== */

#dg-battle .dg-hand {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 8px 4px 12px;
  scrollbar-width: thin;
}
#dg-battle .dg-hand .tilt-scene { flex: 0 0 auto; }

/* ==========================================================================
   Sheets — the site's chamfered panel on the showcase's backdrop
   ========================================================================== */

#dg-battle .dg-sheet-back {
  position: fixed;
  inset: 0;
  /* Under the card showcase (300), which is always the frontmost thing the
     site shows — opening the viewer from a sheet must land on top of it. */
  z-index: 120;
  display: grid;
  place-items: center;
  /* Fixed and full-bleed under `viewport-fit=cover`, so the insets come back
     here or the play sheet's buttons sit under the home indicator. */
  padding:
    max(4vmin, env(safe-area-inset-top, 0px))
    max(4vmin, env(safe-area-inset-right, 0px))
    max(4vmin, env(safe-area-inset-bottom, 0px))
    max(4vmin, env(safe-area-inset-left, 0px));
  background: rgba(3, 5, 9, 0.82);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  animation: dg-in 0.2s ease-out;
}
#dg-battle .dg-sheet {
  width: min(660px, 100%);
  max-height: calc(100vh - 8vmin);
}
#dg-battle .dg-sheet--wide { width: min(880px, 100%); }
#dg-battle .dg-sheet__in {
  display: flex;
  gap: 18px;
  padding: 20px;
  max-height: calc(100vh - 8vmin);
  overflow: auto;
}
/* THE SHEET'S OWN TEXT, BY CLASS — never `.dg-sheet h3` / `.dg-sheet p`. Those
   descendant selectors (1,1,1) out-ranked the card's own rules (0,2,0) and
   reached into the card the play and graveyard sheets show: its name came out
   at 22px and its type line and text at 14px muted grey. Page CSS must never
   style card internals (CLAUDE.md), and the card needs nothing here — it is
   built in cqw, so it prints as the same card at whatever width the sheet
   gives it. */
#dg-battle .dg-sheet__title {
  margin: 0 0 6px;
  font: 700 22px "Chakra Petch", system-ui, sans-serif;
}
#dg-battle .dg-sheet__text { margin: 0 0 10px; font-size: 14px; color: var(--muted); }
#dg-battle .dg-note { color: #c9d3ee; }
#dg-battle .dg-play__card { flex: 0 0 200px; }
#dg-battle .dg-play__body { flex: 1; min-width: 0; }
#dg-battle .dg-over {
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 28px;
}
@media (max-width: 620px) {
  #dg-battle .dg-sheet__in { flex-direction: column; align-items: center; }
  #dg-battle .dg-play__card { flex: 0 0 auto; width: 160px; }
}

#dg-battle .dg-opts { display: flex; flex-direction: column; gap: 7px; margin: 12px 0; }
#dg-battle .dg-opt {
  --c: 9px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 13px;
  text-align: left;
  border: 0;
  background: rgba(255, 255, 255, 0.06);
  color: var(--ink);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.16s;
}
#dg-battle .dg-opt:hover:not(:disabled) { background: rgba(255, 255, 255, 0.12); }
#dg-battle .dg-opt:disabled { opacity: 0.42; cursor: not-allowed; }
#dg-battle .dg-opt small {
  display: block;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--faint);
}
#dg-battle .dg-opt em {
  display: flex;
  align-items: center;
  gap: 5px;
  font: 700 15px "Chakra Petch", system-ui, sans-serif;
  font-style: normal;
  white-space: nowrap;
}
#dg-battle .dg-check {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px 10px;
  margin-bottom: 6px;
  background: rgba(255, 255, 255, 0.03);
  font-size: 14px;
  cursor: pointer;
}
#dg-battle .dg-select {
  --c: 5px;
  padding: 3px 7px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
}
#dg-battle .dg-grave-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  max-height: 52vh;
  overflow-y: auto;
  padding: 4px;
  margin-bottom: 14px;
}
#dg-battle .dg-grave-list .tilt-scene { width: 120px; }

/* ==========================================================================
   Hit feedback
   The splat is fixed to the viewport because it is positioned from a card's
   bounding rect, and that card may be inside a rotated slot and a tilted
   scene — a positioned ancestor would drag it into all of that.
   ========================================================================== */

.dg-splat {
  position: fixed;
  z-index: 310;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  color: #fff;
  font: 700 21px/1 "Chakra Petch", system-ui, sans-serif;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
  background: #d4541f;
  clip-path: polygon(
    50% 0%, 58.8% 17.2%, 75% 6.7%, 74% 26%, 93.3% 25%, 82.8% 41.2%, 100% 50%,
    82.8% 58.8%, 93.3% 75%, 74% 74%, 75% 93.3%, 58.8% 82.8%, 50% 100%,
    41.2% 82.8%, 25% 93.3%, 26% 74%, 6.7% 75%, 17.2% 58.8%, 0% 50%,
    17.2% 41.2%, 6.7% 25%, 26% 26%, 25% 6.7%, 41.2% 17.2%
  );
  transform: translate(-50%, -50%) scale(0.4);
  animation: dg-splat 1s ease-out forwards;
  pointer-events: none;
}
.dg-splat.is-soft { background: #3b4a6b; }

@keyframes dg-splat {
  18% { transform: translate(-50%, -50%) scale(1.15); opacity: 1; }
  32% { transform: translate(-50%, -50%) scale(1); }
  70% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -70%) scale(1); }
}

#dg-battle .dc-card.is-hit { animation: dg-shake 0.42s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; }
@keyframes dg-shake {
  10%, 90% { transform: translate(-2px, 0); }
  20%, 80% { transform: translate(4px, 0); }
  30%, 50%, 70% { transform: translate(-6px, 1px); }
  40%, 60% { transform: translate(6px, -1px); }
}

#dg-battle .dc-card.is-lunge { animation: dg-lunge 0.5s ease-out both; }
@keyframes dg-lunge {
  35% { transform: translateY(var(--ly)) scale(1.04); }
  100% { transform: none; }
}
/* A defending card is already turned 90deg by its slot; lunging it would send
   it sideways, so it does not lunge. */
#dg-battle .dg-slot.is-def .dc-card.is-lunge { animation: none; }

/* The kill flies the whole CELL to its owner's graveyard pile. */
#dg-battle .dg-cell.is-dead { animation: dg-die 0.65s ease-in 0.3s forwards; z-index: 5; }
@keyframes dg-die {
  20% { transform: rotateY(90deg) scale(0.95); opacity: 1; }
  60% { opacity: 0.7; }
  100% { transform: translate(var(--dx), var(--dy)) rotateY(180deg) scale(0.15); opacity: 0; }
}

@keyframes dg-pulse { 50% { box-shadow: inset 0 0 0 4px var(--dg-mark, transparent); } }
@keyframes dg-pulse-warm { 50% { filter: drop-shadow(0 0 7px rgba(255, 210, 63, 0.45)); } }
@keyframes dg-in { from { opacity: 0.2; transform: translateY(4px); } }

@media (prefers-reduced-motion: reduce) {
  #dg-battle *,
  .dg-splat {
    animation: none !important;
    transition: none !important;
  }
}

/* ==========================================================================
   Small screens
   ========================================================================== */

@media (max-width: 680px) {
  #dg-battle { --cw: 46px; }
  #dg-battle .dg-table__in { padding: 8px 4px; }
  #dg-battle .dg-piles { flex-direction: column; gap: 24px; }
  #dg-battle .dg-piles-spacer { width: calc(var(--cw) + 12px); }
  #dg-battle .dg-field { gap: 8px; }
  #dg-battle .dg-board, #dg-battle .dg-traps { gap: 6px; }
}


/* ==========================================================================
   MOBILE PASS — battle table
   Measured at 390px. The board itself already fits without pinch-zoom (no
   horizontal overflow), so this is about what can be READ on it.
   ========================================================================== */

@media (max-width: 768px) {
  /* Label floors. These are all tracked uppercase micro-labels, so they stop
     at 12-14 rather than going to 14-16; see the note in css/site.css. */
  #dg-battle .dg-trap { font-size: 12px; }
  #dg-battle .dg-pile-count { font-size: 13px; }
  #dg-battle .dg-pile-label { font-size: 12px; }
  #dg-battle .dg-turn { font-size: 14px; }
  #dg-battle .dg-msg { font-size: 14px; }
  #dg-battle .dg-who small { font-size: 13px; }
  #dg-battle .dg-life small,
  #dg-battle .dg-meta { font-size: 13px; }
  #dg-battle .dg-log { font-size: 13px; }
  #dg-battle .dg-tag,
  #dg-battle .dg-flag { font-size: 11px; }
  #dg-battle .dg-decknote { font-size: 13px; }
  #dg-battle .dg-seed { font-size: 12px; }
  /* #dg-battle out-specifies the .btn--small floor in css/site.css. */
  #dg-battle .btn--small { font-size: 13px; }

  /* NO --cw OVERRIDE HERE. The existing max-width:680px rule above sets
     46px, and a wider rule declared later in the file beats it at every
     width below 680 - raising it to 50px pushed the board to 406px inside
     a 390px viewport and reintroduced horizontal scroll. A 46px card's print
     is sub-pixel; that is the board's width problem (an open item in HANDOFF
     §16), not something the card should hide its print to solve. */
}

/* A touch device with a hover-capable pointer (a laptop trackpad, a stylus)
   still shows the View button, and 42x18 is not a target. Phones never see
   it — `hover: none` hides it and the long press is the affordance there. */
@media (max-width: 768px), (pointer: coarse) {
  #dg-battle .dg-view {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* ==========================================================================
   Opponent difficulty
   Pills, the same control the sort bars use, because it is the same thing:
   one of a short set, currently on. The blurb under them says what the
   current pick means, so the panel carries one line rather than four.
   ========================================================================== */

#dg-battle .dg-tiers {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* The Sound box: effects and music toggles (the same pills), a volume slider,
   and what is playing. */
#dg-battle .dg-sound__row { display: flex; flex-wrap: wrap; gap: 6px; }
#dg-battle .dg-sound__row .pill[aria-pressed="false"] { opacity: 0.6; }
#dg-battle .dg-sound__volrow {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 12px 0 0;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}
#dg-battle .dg-sound__vol { flex: 1; min-width: 0; accent-color: var(--teal); }
#dg-battle .dg-sound__vol:disabled { opacity: 0.4; }
#dg-battle .dg-sound__now {
  margin: 10px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}

#dg-battle .dg-tiers__blurb {
  margin: 10px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--faint);
}

/* Only rendered while a game in progress is being played against a different
   tier than the one now chosen. Surge amber, not chain red: it is a notice,
   and red belongs to the chain (same rule as .dg-decknote). */
#dg-battle .dg-tiers__pending {
  margin: 10px 0 0;
  padding: 8px 10px;
  border-left: 2px solid var(--t-surge);
  background: rgba(255, 210, 63, 0.07);
  font-size: 12.5px;
  line-height: 1.5;
  color: #ffe9a8;
}

@media (max-width: 768px) {
  /* The label floor for tracked micro-type, as everywhere else on this page. */
  #dg-battle .dg-tiers__blurb,
  #dg-battle .dg-tiers__pending { font-size: 13px; }
}

/* The trap hand (dev-only ?traps=1) has no room in the 58px spacer a phone
   leaves beside the trap row, so on a narrow table it takes its own full-width
   line under the slots. Two classes on the spacer, so this outranks the
   `#dg-battle .dg-piles-spacer` width set above: an at-rule adds no
   specificity (CLAUDE.md). */
@media (max-width: 680px) {
  #dg-battle .dg-field:has(> .dg-traphand) { flex-wrap: wrap; }
  #dg-battle .dg-piles-spacer.dg-traphand { width: 100%; }
  #dg-battle .dg-piles-spacer.dg-traphand .dg-traphand__count { width: auto; }
}
