/* board.css — the LINE-DRAWN playmat.
   No bitmap playmat: every zone is a CSS/SVG line construct, so it stays
   crisp at any resolution, recolours with the palette, and costs no bandwidth.
   Corner brackets are drawn with a repeating conic gradient trick (four
   corners, one element) rather than four pseudo-elements per zone. */

/* the mat owns the whole viewport — the yellow page never shows behind it */
.preview-body { padding: 0; background: #080b11; }
.preview-body .mat { min-height: calc(100vh - 42px); }
/* Explicit colours, not --bg/--on-bg: this bar was a yellow strip with dark
   text on the old yellow page. Now that the page is black those two tokens
   are both dark-on-dark, which would have made this invisible. */
.pv-note {
  font-family: var(--mono); font-size: 11px; color: #E8EAEB;
  background: #12171f; padding: 10px 14px; line-height: 1.5;
  border-bottom: 2px solid var(--sig-yellow, #FFE500);
}
.pv-note b { letter-spacing: .12em; }

/* ---------- the mat ---------- */
.mat {
  --line-ink: rgba(34,211,238,.34);
  --line-hot: rgba(252,238,10,.5);
  background:
    /* faint grid, drawn not painted */
    linear-gradient(rgba(34,211,238,.045) 1px, transparent 1px) 0 0 / 100% 34px,
    linear-gradient(90deg, rgba(34,211,238,.045) 1px, transparent 1px) 0 0 / 34px 100%,
    radial-gradient(ellipse at 50% 50%, rgba(12,22,32,1) 0%, #080b11 78%);
  padding: 14px; display: flex; flex-direction: column; gap: 10px;
}
.mat-half { display: flex; flex-direction: column; gap: 8px; position: relative; }
.mat-half.rival { --half-ink: var(--magenta); }
.mat-half.mine  { --half-ink: var(--cyan); }
.half-tag {
  font-family: var(--mono); font-size: 10px; letter-spacing: .28em;
  color: var(--half-ink); text-transform: uppercase; opacity: .8;
}
.mine-tag { text-align: right; }
.mat-row { display: grid; grid-template-columns: auto auto 1fr auto; gap: 10px; align-items: stretch; }

/* ---------- zone frame: corner brackets, no full box ---------- */
.zone {
  position: relative; padding: 16px 12px 10px;
  background: linear-gradient(180deg, rgba(18,23,31,.5), rgba(10,14,20,.5));
}
.zone::before, .zone::after {
  content: ""; position: absolute; width: 14px; height: 14px; pointer-events: none;
  border-color: var(--line-ink); border-style: solid;
}
.zone::before { top: 0; left: 0; border-width: 1px 0 0 1px; }
.zone::after  { bottom: 0; right: 0; border-width: 0 1px 1px 0; }
/* the opposite pair, via a child-free trick on the label row */
.zone > .zone-label::before, .zone > .zone-label::after {
  content: ""; position: absolute; width: 14px; height: 14px; pointer-events: none;
  border-color: var(--line-ink); border-style: solid;
}
.zone > .zone-label::before { top: 0; right: 0; border-width: 1px 1px 0 0; }
.zone > .zone-label::after  { bottom: 0; left: 0; border-width: 0 0 1px 1px; }

.zone-label {
  position: absolute; inset: 0; pointer-events: none;
  font-family: var(--mono); font-size: var(--ui-xs, 9px); letter-spacing: .24em;
  color: var(--ink-3); text-transform: uppercase;
  padding: 3px 0 0 6px;
}
.zone-label.sub { position: static; padding: 4px 0 0; text-align: center; display: block; }

/* ---------- card slots (outlines, not images) ---------- */
.slot-row, .field-slots { display: flex; gap: 8px; }
.field-slots { flex-wrap: nowrap; }
.cslot, .fslot {
  width: 62px; aspect-ratio: 5/7; flex: none;
  border: 1px dashed rgba(34,211,238,.22);
  display: grid; place-items: center;
  position: relative;
}
.fslot { width: 78px; }
.zone-field .fslot { width: 84px; }
/* an empty slot shows a faint centre tick so the grid reads as a mat */
.fslot:empty::after, .cslot:not(.back):empty::after {
  content: ""; width: 12px; height: 1px; background: rgba(34,211,238,.18);
}
/* face-down card = David's card back (legends token: separate art incoming) */
.cslot.back {
  border: none;
  background: var(--cardback, repeating-linear-gradient(135deg, rgba(252,238,10,.14) 0 5px, transparent 5px 11px)) center / contain no-repeat;
}
.legends-row .cslot.back { background-image: var(--cardback-legend); }
/* a SIDEWAYS face-down card is the same back genuinely rotated 90° — using
   cover to "fill" the landscape slot centre-crops the portrait art into a
   zoomed chunk (the clipped-legends bug). Rotate a portrait layer instead. */
.legends-row .cslot.spent-h.back { background: none; }
.legends-row .cslot.spent-h.back::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: var(--back-w); height: var(--back-h);
  background: var(--cardback-legend) center / contain no-repeat;
  transform: translate(-50%, -50%) rotate(90deg);
}

/* ---------- stacks (deck / trash / eddies) ---------- */
.zone-stacks { display: flex; gap: 12px; align-items: flex-start; }
.stack { text-align: center; }
.stack-visual {
  width: 54px; aspect-ratio: 5/7; display: grid; place-items: center;
  font-family: var(--mono); font-size: var(--ui-lg, 15px); font-weight: 600; color: var(--ink);
  position: relative;
}
/* the "depth" of a stack drawn with offset outlines */
.stack-visual::before, .stack-visual::after {
  content: ""; position: absolute; inset: 0; border: 1px solid rgba(34,211,238,.2);
}
.stack-visual::before { transform: translate(3px, 3px); }
.stack-visual::after  { transform: translate(6px, 6px); }
.stack-visual.back {
  border: none;
  background: var(--cardback, repeating-linear-gradient(135deg, rgba(252,238,10,.12) 0 5px, transparent 5px 11px)) center / contain no-repeat;
  color: var(--cyan);   /* never yellow — it vanishes against the back art */
}
/* the count must survive David's busy back art — give it a scrim chip */
.stack-visual.back span { background: rgba(9,12,18,.88); padding: 1px 8px;
  border: 1px solid var(--cyan-dim); }
.stack-visual.empty { border: 1px dashed rgba(143,163,189,.3); color: var(--ink-2); }
.stack-visual.eddie {
  border: 1px solid rgba(52,211,153,.5); color: var(--green);
  background: repeating-linear-gradient(90deg, rgba(52,211,153,.1) 0 3px, transparent 3px 8px);
}
.stack-visual span { position: relative; z-index: 1; }

/* ---------- gigs ---------- */
.zone-gigs { display: flex; align-items: center; gap: 10px; }
.gig-readout { font-family: var(--display); font-size: clamp(24px, 3.4vh, 52px); line-height: 1; color: var(--yellow); }
.gig-readout span { font-size: var(--ui-md, 15px); color: var(--ink-3); }
.dice-row { display: flex; gap: 5px; flex-wrap: wrap; align-items: center; }
.gig-tray {
  padding: 5px 7px; min-width: 90px; min-height: 40px;
  border: 1px solid rgba(252,238,10,.22);
  background: repeating-linear-gradient(45deg, rgba(252,238,10,.04) 0 6px, transparent 6px 12px);
}
.dice-row .die-wrap .die-svg { width: var(--die, 40px); height: calc(var(--die, 40px) * 1.13); }
.dice-row .die-wrap.gig .die-svg { width: calc(var(--die, 40px) * 1.1); height: calc(var(--die, 40px) * 1.24); }
.cred { font-family: var(--mono); font-size: var(--ui-sm, 10px); color: var(--ink-2); letter-spacing: .1em; }
.cred b { color: var(--yellow); }

/* ---------- divider (legacy, kept for compat) ---------- */
.mat-divider { display: flex; align-items: center; gap: 12px; margin: 2px 0; }
.div-line { flex: 1; height: 1px; background:
  linear-gradient(90deg, transparent, var(--line-ink) 18%, var(--line-ink) 82%, transparent); }
.div-tag { font-family: var(--mono); font-size: 10px; letter-spacing: .26em; color: var(--yellow); }

/* ══════════ OFFICIAL MAT LAYOUT (v2) ══════════
   One half = the printed playmat: fixer dice down one edge, field center,
   deck/trash on the other edge, legends + eddies along the player edge.
   The rival half is the same mat rotated 180° (so their fixer lands on
   screen-right, their legends/eddies at the very top), and both Gig areas
   meet in a shared corridor at the centerline — two mats facing each other
   on a table. Everything is CSS lines; no bitmap anywhere. */

.mat-core { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px; }
.edge-row { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px; margin-top: 8px; }
.edge-row.top { grid-template-columns: minmax(0, 1fr) auto; margin: 0 0 8px; }

/* fixer: vertical dice rail, D20 top -> D4 bottom, taken dice ghosted */
.zone-fixer.side { display: flex; flex-direction: column; align-items: center; }
.dice-col { display: flex; flex-direction: column; gap: 7px; padding-top: 4px; }
.die-wrap.fx .die-svg { width: var(--die, 40px); height: calc(var(--die, 40px) * 1.13); }
.die-wrap.fx.taken { opacity: .28; filter: grayscale(.6); }
.die-wrap.fx.taken .die-shape polygon,
.die-wrap.fx.taken .die-shape rect { stroke-dasharray: 4 3; }

/* deck / trash rail */
.zone-stacks.side { display: flex; flex-direction: column; gap: 16px; justify-content: flex-start;
  padding: 16px 14px 10px; }

/* eddies: a fan of face-down drawn card backs, spent ones turned sideways */
.zone-eddies { min-height: 74px; }
.eddie-fan { display: flex; gap: 6px; align-items: center; padding: 4px 2px; min-height: 52px; }
.eddie-card { width: 34px; aspect-ratio: 5/7; flex: none;
  border: 1px solid rgba(52,211,153,.55);
  background: repeating-linear-gradient(135deg, rgba(52,211,153,.16) 0 4px, transparent 4px 9px),
              rgba(6,18,14,.7); }
.eddie-card.spent { transform: rotate(90deg) scale(.9); opacity: .55; }
.eddie-fan:empty::after { content: "NO EDDIES"; font-family: var(--mono); font-size: 9px;
  color: var(--ink-3); letter-spacing: .2em; }

/* the gig corridor — shared centerline band, hatched like a road median */
.gig-corridor { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 8px; align-items: stretch; margin: 10px 0;
  border-top: 1px solid var(--line-hot); border-bottom: 1px solid var(--line-hot);
  padding: 8px 0;
  background: repeating-linear-gradient(45deg, rgba(252,238,10,.03) 0 10px, transparent 10px 20px); }
.gig-side { display: block; }
.gig-side .gig-inner { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding-top: 2px; }
.gig-side.rival-gigs { --line-ink: rgba(255,42,109,.4); }
.gig-side.rival-gigs .gig-readout { color: var(--magenta); }
.gig-side.friendly-gigs { --line-ink: rgba(34,211,238,.45); }
.corridor-mid { display: flex; flex-direction: column; justify-content: center; align-items: center;
  gap: 4px; padding: 0 14px;
  border-left: 1px solid var(--line-ink); border-right: 1px solid var(--line-ink); }
.corridor-mid .phase { color: var(--cyan); }

@media (max-width: 900px) {
  .dice-col { gap: 4px; }
  .zone-stacks.side { padding: 14px 8px 8px; gap: 10px; }
  .zone-stacks.side .stack-visual { width: 40px; }
  .gig-corridor { grid-template-columns: 1fr; }
  .corridor-mid { flex-direction: row; border: 0; padding: 2px 0; }
  .edge-row, .edge-row.top { grid-template-columns: 1fr; }
}

/* ---------- cards on the mat ---------- */
.pcard {
  position: absolute; inset: 0; cursor: pointer;
  /* no box border/background: the card's shape comes from David's alpha,
     masked onto the art below — state cues are drop-shadows, which follow
     the masked silhouette (box-shadow would draw a naked rectangle) */
  border: none;
  background: transparent;
  transition: transform .14s ease, filter .14s ease;
  /* each card is its own stacking context — without this, a card's z-indexed
     frame/chips paint OVER the card overlapping it in a fan (the "blue
     outline of the card behind showing through" bug) */
  isolation: isolate;
}
/* every face-up card wears the back's alpha silhouette — real rounded/
   chamfered corners identical to the physical card backs */
.pcard > img {
  -webkit-mask-image: var(--cardmask, none);
  mask-image: var(--cardmask, none);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}
.legends-row .cslot, .fan-back, .eddie-card { isolation: isolate; }
.hand-cards .pcard { position: relative; inset: auto; width: 92px; aspect-ratio: 5/7; flex: none; }
.pcard img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* the "chrome" corner cut, drawn */
.pc-frame { position: absolute; inset: 0; pointer-events: none; z-index: 2;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%);
  border: 1px solid rgba(34,211,238,.35); }
/* hover: highlight only — NO transform lift (a lift before the zoom made a
   two-stage stutter). will-change pre-promotes the layer during the 80ms
   hover-intent window so the zoom starts on the compositor, not mid-paint. */
.pcard:hover { z-index: 6;
  filter: drop-shadow(0 8px 18px rgba(255,42,109,.4)); }
/* NO will-change: the layer hint froze the raster at 1x, so the zoomed card
   showed a blurry stretch that popped sharp late ("the swap of low to high
   res"). Without it Chrome rasters the transition at destination scale —
   sharp the whole way. */
/* in-place hover enlargement — ONE smooth eased scale, transform only
   (shadow/border snap; transitioning paint-heavy properties is what janks) */
/* zoomed card angles with the mouse (the reference-TCG tilt) — the tilt
   vars are driven per-mousemove; after the entry transition settles, a short
   linear transition keeps the tilt responsive instead of floaty */
.pv-zoom { transform: perspective(1100px) scale(2.05)
    rotateX(var(--tx, 0deg)) rotateY(var(--ty, 0deg)) !important;
  z-index: 70 !important;
  filter: drop-shadow(0 14px 30px rgba(0,0,0,.8)) drop-shadow(0 0 18px rgba(34,211,238,.35)) !important;
  transition: transform .16s cubic-bezier(.2,.85,.3,1) !important; }
.pv-zoom.settled { transition: transform .07s linear !important; }
.pcard.spent { transform: rotate(90deg) scale(.82); opacity: .7; }
.pcard.spent:hover { transform: rotate(90deg) scale(.88); }
.pcard.legend { border-color: #8a8206; }
.pc-cost, .pc-pwr {
  position: absolute; z-index: 3; font-family: var(--mono); font-weight: 600; font-size: 12px;
  padding: 0 5px; background: rgba(9,12,18,.92);
}
.pc-cost { top: 3px; left: 3px; color: var(--yellow); border: 1px solid #713f12; }
.pc-pwr  { bottom: 3px; right: 3px; color: var(--magenta); border: 1px solid #8a1d44; }
.pc-name {
  position: absolute; z-index: 3; left: 0; right: 0; bottom: 0;
  font-family: var(--mono); font-size: 8px; color: var(--ink);
  background: linear-gradient(transparent, rgba(9,12,18,.94) 45%);
  padding: 10px 3px 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------- hand rail ---------- */
.hand-rail { padding: 10px 12px 4px; position: relative;
  border-top: 1px solid var(--line-ink); }
.hand-label { position: absolute; top: 2px; left: 8px; inset: auto; padding: 0; }
.hand-cards { display: flex; gap: 8px; justify-content: center; padding-top: 6px; }
.hand-cards .pcard:hover { transform: translateY(-14px) scale(1.06); }

/* ---------- deal-in animation ----------
   Cards fly in from the deck side and settle. Staggered by --i. */
.pcard.dealing { opacity: 0; transform: translate(160px, -90px) rotate(14deg) scale(.7); }
.pcard.dealing.dealt {
  opacity: 1; transform: none;
  transition: opacity .28s ease, transform .46s cubic-bezier(.2,.85,.3,1);
  transition-delay: calc(var(--i, 0) * 75ms);
}
.fslot .pcard.dealt, .slot-row .pcard.dealt { animation: land .4s ease-out both;
  animation-delay: calc(var(--i, 0) * 70ms); }
@keyframes land {
  0%   { opacity: 0; transform: translateY(-26px) scale(1.14); }
  70%  { opacity: 1; transform: translateY(2px) scale(.98); }
  100% { opacity: 1; transform: none; }
}
.fslot .pcard.spent.dealt { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .pcard.dealing, .pcard.dealing.dealt { opacity: 1; transform: none; transition: none; }
  .fslot .pcard.dealt, .slot-row .pcard.dealt { animation: none; }
}

/* ══════════ BOARD v3 — viewport-locked, three columns ══════════
   The whole match fits the screen with NO page scroll; card sizes are
   derived from viewport height so a bigger monitor means bigger cards,
   not more empty mat. Columns: player rail / battlefield / log+actions. */

.pv-note.slim { padding: 6px 14px; font-size: 10px; }

.b3 {
  /* ONE card size for everything on desktop — hand, field, legends, eddies,
     deck, trash, rival hand all match (David's rule). Accumulating zones
     shingle-overlap instead of shrinking. Phones alone keep backs slightly
     smaller or the rows can't share 390px of height. */
  /* TYPE SCALE — every board label/number derives from these so a bigger
     monitor means bigger TEXT too. Fixed-px sizing was why a 4K screen
     rendered 9px labels next to 230px cards. */
  --ui-xs: clamp(9px, 0.78vh, 16px);    /* zone labels, badges */
  --ui-sm: clamp(10px, 0.95vh, 19px);   /* cred, log, prompts */
  --ui-md: clamp(12px, 1.2vh, 23px);    /* body copy, keyword defs */
  --ui-lg: clamp(15px, 1.6vh, 30px);    /* counts, chips */
  --card-h: clamp(96px, 17vh, 330px);
  --card-w: calc(var(--card-h) * 5 / 7);
  --back-h: var(--card-h);
  --back-w: calc(var(--back-h) * 5 / 7);
  --hand-h: var(--card-h);
  --die: clamp(34px, 6vh, 92px);   /* reference-sized: the dice are a feature, not a footnote */
  height: calc(100vh - 32px);
  display: grid;
  grid-template-columns: minmax(300px, 24vw) minmax(0, 1fr) minmax(240px, 18vw);
  gap: 8px; padding: 8px;
  overflow: hidden;
}
.b3 .zone-label { position: static; display: block; padding: 2px 0 4px 2px; }

/* generic framed zone reusing the corner-bracket treatment */
.b3-zone {
  position: relative; padding: 6px 8px;
  background: linear-gradient(180deg, rgba(18,23,31,.5), rgba(10,14,20,.5));
}
.b3-zone::before, .b3-zone::after {
  content: ""; position: absolute; width: 12px; height: 12px; pointer-events: none;
  border-color: var(--line-ink); border-style: solid;
}
.b3-zone::before { top: 0; left: 0; border-width: 1px 0 0 1px; }
.b3-zone::after  { bottom: 0; right: 0; border-width: 0 1px 1px 0; }
.b3-zone.rival-tint { --line-ink: rgba(255,42,109,.4); }
.b3-zone.mine-tint  { --line-ink: rgba(34,211,238,.5); }
/* min-width:0 is load-bearing AT EVERY LEVEL of the chain: the zones (flex
   items), AND the edge rows / mid row themselves (grid items). A grid item
   defaults to min-width:auto, so a row of non-wrapping cards blows the row
   out past the column and paints over the side rail — and every fan sees
   phantom room, so nothing shingles. Same minmax trap that bit CHOOMDEX. */
.b3-zone.grow { flex: 1; min-width: 0; }
.b3-edge > .b3-zone { min-width: 0; }
.b3-battle > * { min-width: 0; }

/* ---- left rail ---- */
.b3-rail { display: flex; flex-direction: column; gap: 6px; min-height: 0; }
/* face-down cards use David's card-back art everywhere. The legends back is
   a separate token so his upcoming legends-specific art is a one-line swap. */
/* :root, not .b3 — drag ghosts and zoom overlays live on <body> and must
   keep the alpha mask + backs */
:root {
  --cardback: url("/img/cardback.webp");
  --cardback-legend: url("/img/cardback_legend.webp");
  --cardmask: url("/img/cardmask.webp");   /* pure silhouette for masking face-up art */
}
/* rival hand: card backs at the shared back size, shingled by fanLayout */
.b3-backfan { display: flex; }
.fan-back { width: var(--back-w); aspect-ratio: 5/7; flex: none;
  border: none;
  background: var(--cardback) center / contain no-repeat;
  filter: drop-shadow(-4px 2px 8px rgba(0,0,0,.55)); }
.b3-dice { display: flex; gap: 5px; flex-wrap: wrap; }
.b3-dice .die-svg { width: var(--die); height: calc(var(--die) * 1.13); }
.b3-gigs { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.b3-gigs .die-wrap.gig .die-svg { width: calc(var(--die) * 1.1); height: calc(var(--die) * 1.24); }

.b3-status { flex: 1; display: flex; flex-direction: column; justify-content: center;
  align-items: center; gap: 2px; text-align: center; min-height: 54px;
  border-top: 1px solid var(--line-hot); border-bottom: 1px solid var(--line-hot);
  background: repeating-linear-gradient(45deg, rgba(252,238,10,.03) 0 10px, transparent 10px 20px); }
.st-turn { font-family: var(--display); font-size: clamp(20px, 3vh, 34px); color: var(--yellow); line-height: 1; }
.st-phase { font-family: var(--mono); font-size: var(--ui-sm, 11px); letter-spacing: .26em; color: var(--cyan); }
.st-who { font-family: var(--mono); font-size: var(--ui-xs, 9px); letter-spacing: .18em; color: var(--ink-3); }

/* the hand is an overlapping FAN — every card always visible, no clipping,
   no scrollbar. JS sets each card's negative left margin so N cards exactly
   fill the rail; the hovered card pops forward and enlarges. Extra headroom
   is padded above so the pop-out never clips against the zone frame. */
.b3-hand-zone { min-height: 0; }
.b3-hand { display: flex; padding: 14px 2px 2px; overflow: visible; }
.b3-hand .pcard { position: relative; inset: auto; flex: none;
  height: var(--hand-h); width: calc(var(--hand-h) * 5 / 7);
  transition: transform .14s ease, filter .14s ease;
  filter: drop-shadow(-5px 2px 9px rgba(0,0,0,.55));
  touch-action: none; }   /* a drag must never turn into a page scroll */
.b3-hand .pcard:hover { z-index: 12;   /* no lift — the zoom is the only motion */
  filter: drop-shadow(0 10px 24px rgba(34,211,238,.4)); }

/* ---- battlefield ---- */
.b3-battle { display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 6px; min-height: 0; }
.b3-edge { display: flex; gap: 6px; align-items: stretch; }
/* (edge-row card sizing lives in the v3.1 two-size system further down:
   --card-h for active cards, --back-h for every card back — no per-zone
   size overrides here, that's what made eddies drift smaller than legends) */
/* fields side-by-side with ONE shared deck/trash column spanning both —
   the reference's middle strip: rival deck, rival trash, my deck, my trash,
   all at the shared back size */
.b3-mid { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px;
  min-height: 0; }
.b3-fields { display: grid; grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
  gap: 6px; min-height: 0; }
.b3-field { display: flex; flex-direction: column; min-height: 0; }
.b3-field .field-slots { flex: 1; display: flex; gap: 10px; align-items: center;
  justify-content: center; overflow-x: auto; padding: 2px; min-height: 0; }
.b3-field .fslot { height: var(--card-h); aspect-ratio: 5/7; width: auto; flex: none; }
/* deck+trash as ONE single-card-wide column (David: trash above the rival's
   deck, below yours) — the freed width goes straight to the fields. These
   are counters, not play surfaces, so they run at ~62% card size to fit
   four-high beside the field rows. */
.b3-stackcol { display: flex; flex-direction: column; gap: 6px;
  justify-content: center; padding: 4px 8px 4px 2px; }
.b3-stackcol .stack-visual { width: calc(var(--card-h) * .62 * 5 / 7);
  font-size: var(--ui-lg, 16px); }
.b3-stackcol .zone-label.sub { font-size: var(--ui-xs, 8px); padding-top: 2px; }

/* ---- right rail ---- */
.b3-side { display: flex; flex-direction: column; gap: 6px; min-height: 0; }
.b3-log-zone { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.b3-log { flex: 1; overflow-y: auto; font-family: var(--mono); font-size: var(--ui-sm, 11px);
  line-height: 1.55; color: var(--ink-2); scrollbar-width: thin; }
.b3-log .l-turn { color: var(--yellow); }
.b3-log .l-hit { color: var(--magenta); }
.b3-log .l-ai { color: var(--cyan); }
.b3-log div { padding: 2px 0; border-bottom: 1px solid rgba(42,54,72,.35); }
.b3-prompt { border: 1px solid var(--line-hot); padding: 10px;
  background: rgba(20,18,4,.35); }
.bp-title { font-family: var(--display); font-size: clamp(18px, 2.4vh, 36px); color: var(--yellow); }
.bp-text { font-family: var(--mono); font-size: var(--ui-sm, 11px); color: var(--ink-2); line-height: 1.5;
  margin: 4px 0 10px; }
.bp-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }

/* ══════════ v3.1 — dynamic zones + drag interactions ══════════ */

/* zones hug their content; a spacer absorbs leftover edge width so the
   fields (1fr rows) inherit every pixel the edges give up */
.b3-zone.hug { flex: none; width: fit-content; }
.edge-spacer { flex: 1; }

/* eddies: face-down cards at the SAME back size as deck/legends/rival hand.
   A growing pile shingles via fanLayout — cards overlap, never shrink.
   A spent eddie lies sideways as a landscape box (no transform tricks, so it
   can't poke out of the row). The green $ total rides alongside. */
.eddie-wallet { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }
.eddie-stack { display: flex; align-items: flex-end; flex: 1; min-width: 0;
  min-height: var(--back-h); }
.eddie-stack .eddie-card { width: var(--back-w); height: var(--back-h); flex: none;
  border: none;
  background: var(--cardback, rgba(6,18,14,.7)) center / contain no-repeat;
  /* eddies are money: same back, green cast so the pile reads as the wallet */
  filter: drop-shadow(0 0 6px rgba(52,211,153,.45)) drop-shadow(-4px 2px 8px rgba(0,0,0,.5)); }
/* spent eddie lies sideways too — same rotated-layer treatment, same reason */
.eddie-stack .eddie-card.spent { width: var(--back-h); height: var(--back-w); opacity: .55;
  background: none; position: relative; }
.eddie-stack .eddie-card.spent::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: var(--back-w); height: var(--back-h);
  background: var(--cardback) center / contain no-repeat;
  transform: translate(-50%, -50%) rotate(90deg);
}
.eddie-stack:empty::after { content: "NO EDDIES YET"; font-family: var(--mono); font-size: var(--ui-sm, 10px);
  color: var(--ink-3); letter-spacing: .2em; align-self: center; }
.eddie-total { font-family: var(--display); font-size: clamp(24px, 4vh, 40px);
  color: var(--green); line-height: 1; flex: none; }

/* legends at the shared BACK size; a spent legend is the same card truly
   rotated 90° — a landscape box whose width is the card's height, bottom-
   aligned beside the upright ones (the reference-sim treatment) */
.legends-row { align-items: flex-end; }
.legends-row .cslot { height: var(--back-h); width: var(--back-w); }
.cslot.spent-h { height: var(--back-w); width: var(--back-h);
  overflow: hidden; opacity: .8; }
.cslot.spent-h + .cslot.spent-h { margin-left: -10px; }
.cslot.spent-h img { position: absolute; left: 50%; top: 50%; max-width: none;
  height: var(--back-h); width: var(--back-w);
  transform: translate(-50%, -50%) rotate(90deg); }

/* field slots exist only where cards are (+ one dashed drop hint) —
   the mat grows as the board state grows, empty turns stay clean */
.fslot.hint { border-style: dashed; border-color: rgba(34,211,238,.16); }
.fslot.hint::after { content: "+"; font-family: var(--mono); font-size: 16px;
  color: rgba(34,211,238,.25); }

/* ══════════ in-game board (the live port) ══════════ */
#app.fullbleed { max-width: none; padding: 0; }
.b3.in-game {
  height: 100vh;
  color: var(--ink);
  /* the mat owns its ground — without this the app's yellow page bleeds
     through every translucent zone and the whole board reads olive */
  background:
    linear-gradient(rgba(34,211,238,.045) 1px, transparent 1px) 0 0 / 100% 34px,
    linear-gradient(90deg, rgba(34,211,238,.045) 1px, transparent 1px) 0 0 / 34px 100%,
    radial-gradient(ellipse at 50% 50%, rgb(12,22,32) 0%, #080b11 78%);
}
.b3.in-game .pcard, .b3.in-game .legend-el, .b3.in-game .fan-back,
.b3.in-game [data-take], .b3.in-game [data-trash], .b3.in-game .eddie-card { cursor: pointer; }

/* card state cues — DROP-SHADOWS ONLY: they follow the masked silhouette,
   while borders/outlines/box-shadows would draw rectangles around it */
.pcard.lag { filter: drop-shadow(0 0 7px rgba(255,42,109,.65)); }
.pcard.can-act { filter: drop-shadow(0 0 9px rgba(34,211,238,.65)); }
.pcard.gosolo { filter: drop-shadow(0 0 8px rgba(252,238,10,.6)); }
/* unaffordable: cards keep their FULL normal colour (David's call) — the
   only cue is the cost chip flipping hot magenta. Never opacity, never
   filters on card art. */
.pcard.too-pricey { opacity: 1; }
.pcard.too-pricey .pc-cost { color: var(--magenta); border-color: #8a1d44;
  text-shadow: 0 0 6px rgba(255,42,109,.5); }
.pc-badges { position: absolute; bottom: 3px; left: 3px; z-index: 3; display: flex; gap: 2px; }
.pc-badge { font-family: var(--mono); font-size: var(--ui-xs, 8px); padding: 0 4px;
  background: rgba(9,12,18,.92); border: 1px solid var(--cyan-dim); color: var(--cyan); }
.pc-badge.gear { color: var(--green); border-color: #14532d; }

/* fixer rail: per-size count + pickable glow during your gig phase */
.die-wrap { position: relative; display: inline-block; }
.die-n { position: absolute; right: -6px; top: -5px; font-family: var(--mono);
  font-size: var(--ui-xs, 9px); font-style: normal; color: var(--ink-2); }
.b3-dice.pickable .can-take { cursor: pointer;
  filter: drop-shadow(0 0 8px rgba(252,238,10,.6)); }
.b3-dice.pickable .can-take:hover { transform: scale(1.14); }

/* attack targeting */
.fslot.targetable { border-style: solid; border-color: var(--yellow);
  box-shadow: 0 0 14px rgba(252,238,10,.45); }
[data-gigtray].targetable { --line-ink: var(--yellow); cursor: pointer;
  background: repeating-linear-gradient(45deg, rgba(252,238,10,.07) 0 8px, transparent 8px 16px); }

/* face-up upright legend shows its art, wearing the alpha silhouette */
.legends-row .cslot.faceup { border: none; }
.legends-row .cslot img { width: 100%; height: 100%; object-fit: cover; display: block;
  -webkit-mask-image: var(--cardmask, none); mask-image: var(--cardmask, none);
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
/* overlay previews (zoom modal / side panel) share the silhouette */
#zoom-img, #hoverpv-img {
  -webkit-mask-image: var(--cardmask, none); mask-image: var(--cardmask, none);
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
/* the SIDEWAYS spent legend must keep its explicit rotated sizing — the
   generic rule above (same specificity, later in file) was overriding it,
   centre-cropping the art into the box before rotating = clipped card */
.legends-row .cslot.spent-h img { position: absolute; left: 50%; top: 50%;
  width: var(--back-w); height: var(--back-h); max-width: none; object-fit: cover;
  transform: translate(-50%, -50%) rotate(90deg); }
.legends-row .cslot .pc-name { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  font-family: var(--mono); font-size: 8px; color: var(--ink);
  background: linear-gradient(transparent, rgba(9,12,18,.94) 45%);
  padding: 10px 3px 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* mulligan: the opening hand is dealt in a row OVER the play area, with the
   keep/mulligan decision right there — not a separate first page */
.b3.in-game { position: relative; }
.mull-overlay { position: absolute; inset: 0; z-index: 40; display: grid; place-items: center;
  background: radial-gradient(ellipse at center, rgba(5,8,13,.5), rgba(5,8,13,.84)); }
.mull-panel { text-align: center; padding: 18px 22px 20px; max-width: 94%;
  border: 1px solid var(--line-hot); background: rgba(10,14,20,.92); }
.mull-title { font-family: var(--display); font-size: clamp(24px, 4.4vh, 38px);
  color: var(--yellow); line-height: 1.1; }
.mull-sub { font-family: var(--mono); font-size: var(--ui-sm, 11px); color: var(--ink-2);
  letter-spacing: .14em; margin: 4px 0 16px; }
.mull-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
  margin-bottom: 18px; }
.mull-slot { width: var(--card-w, 140px); height: var(--card-h, 196px);
  position: relative; flex: none; }
.mull-actions { display: flex; gap: 12px; justify-content: center; }
.mull-actions button { min-width: 160px; }
@media (max-width: 1100px) {
  .mull-panel { padding: 10px 12px; }
  .mull-actions button { min-width: 110px; min-height: 36px; }
}

/* keywords panel — rides beside the zoomed card (the reference-TCG touch),
   CHOOMDEX chips + one-line reminders, never ON the card itself */
.kwpanel { position: fixed; z-index: 69; width: clamp(300px, 26vh, 520px); pointer-events: none;
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--cyan-dim); padding: 14px 16px; }
.kw-name { font-family: var(--display); font-size: clamp(22px, 2.8vh, 42px); line-height: 1.1;
  color: var(--ink); margin-bottom: 6px; }
.kw-title { font-family: var(--mono); font-size: var(--ui-sm, 11px); letter-spacing: .26em;
  color: var(--ink-3); margin-bottom: 10px; }
.kw-row { display: flex; gap: 10px; align-items: baseline; padding: 5px 0; }
.kw-chip { font-family: var(--mono); font-size: var(--ui-md, 13px); font-weight: 600; flex: none;
  padding: 2px 9px; background: rgba(8,30,38,.9);
  border: 1px solid var(--cyan-dim); color: var(--cyan); }
.kw-def { font-family: var(--mono); font-size: var(--ui-md, 14px); line-height: 1.5; color: var(--ink); }

/* ---- drag & drop ---- */
/* card images are native-draggable by default; the browser's HTML5 drag
   hijacks the pointer stream (one pointermove, then silence, no pointerup).
   Kill it or pointer-based dragging dies on the first card. */
.pcard, .pcard img { -webkit-user-drag: none; user-select: none; }
.pcard.dragging { opacity: .25; }
.drag-ghost { position: fixed; z-index: 96; pointer-events: none;
  filter: drop-shadow(0 14px 30px rgba(0,0,0,.7));
  transition: none; }
.drag-ghost .pcard { position: relative; inset: auto; width: 100%; height: 100%; }
.b3-zone.drop-ok { --line-ink: var(--yellow); background:
  linear-gradient(180deg, rgba(60,54,4,.45), rgba(30,27,2,.45)); }
.b3-zone.drop-ok .zone-label { color: var(--yellow); }

/* the sell: ghost pulses, flips to a face-down eddie, joins the wallet */
@keyframes sell-pulse {
  0% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(252,238,10,0)); }
  60% { transform: scale(1.18); filter: drop-shadow(0 0 26px rgba(252,238,10,.7)); }
  100% { transform: scale(1); }
}
.drag-ghost.selling { animation: sell-pulse .3s ease-out; }
.flip-inner { position: relative; width: 100%; height: 100%;
  transform-style: preserve-3d; transition: transform .38s ease-in-out; }
.flip-inner .ff, .flip-inner .fb { position: absolute; inset: 0; backface-visibility: hidden; }
.flip-inner .fb { transform: rotateY(180deg);
  background: var(--cardback, rgba(6,18,14,.9)) center / contain no-repeat;
  filter: drop-shadow(0 0 8px rgba(52,211,153,.5)); }
.flip-inner.flipped { transform: rotateY(180deg); }

/* ---- mobile: SAME layout, played in landscape ----
   The board keeps its three columns at every size and simply scales down;
   portrait phones get a rotate prompt instead of a broken squeeze. */
@media (max-width: 1100px) {
  .pv-note { display: none; }   /* preview-only banner steals height on phones */
  .b3 {
    height: 100vh;
    --card-h: clamp(56px, 24vh, 130px);
    --back-h: calc(var(--card-h) * .72);   /* phones: backs step down or rows can't share 390px */
    --hand-h: var(--card-h);
    --die: clamp(24px, 8vh, 38px);
    grid-template-columns: minmax(200px, 25vw) minmax(0, 1fr) minmax(148px, 19vw);
    gap: 5px; padding: 5px;
  }
  .b3 .zone-label { font-size: 8px; letter-spacing: .16em; }
  .b3-zone { padding: 3px 5px; }
  .b3-rail, .b3-side { gap: 4px; }
  .b3-battle { gap: 4px; }
  .st-turn { font-size: clamp(14px, 4vh, 22px); }
  .st-phase { font-size: 8px; letter-spacing: .18em; }
  .st-who { display: none; }
  .b3-status { min-height: 34px; }
  .b3-gigs .gig-readout { font-size: 20px; }
  .b3-log { font-size: 9px; line-height: 1.45; }
  .b3-prompt { padding: 6px; }
  .bp-title { font-size: 15px; }
  .bp-text { display: none; }
  .bp-actions button { min-height: 30px; padding: 4px 6px; font-size: 9px; }
  .b3-stacks .stack-visual { width: clamp(30px, 7vh, 48px);
    font-size: 11px; }
  /* back-size cards scale via --back-h; no per-zone overrides */
  .mini-back { width: calc(var(--die) * 0.95); }
  .b3-hand { padding-top: 10px; }
  .b3-field .field-slots { gap: 5px; }
  .cred { font-size: 8px; }
}

/* portrait phones: the board needs landscape — say so instead of breaking */
.rotate-overlay { display: none; }
@media (orientation: portrait) and (max-width: 940px) {
  .b3 { display: none; }
  .rotate-overlay {
    display: grid; place-items: center; min-height: calc(100vh - 40px);
    text-align: center; padding: 24px;
  }
  .rotate-overlay .ro-icon { font-size: 54px; animation: ro-tip 1.6s ease-in-out infinite alternate; }
  .rotate-overlay .ro-title { font-family: var(--display); font-size: 30px; color: var(--yellow); }
  .rotate-overlay .ro-sub { font-family: var(--mono); font-size: 11px; color: var(--ink-2);
    letter-spacing: .12em; margin-top: 6px; }
  @keyframes ro-tip { from { transform: rotate(0); } to { transform: rotate(-90deg); } }
}

/* ---------- narrow screens ---------- */
@media (max-width: 900px) {
  .mat-row { grid-template-columns: 1fr; gap: 6px; }
  .zone-stacks { justify-content: space-around; }
  .field-slots { overflow-x: auto; }
  .hand-cards { justify-content: flex-start; overflow-x: auto; }
}

/* rival identity: the name is the thing you're playing against — give it
   presence instead of burying it in a zone label (David's note) */
.rival-name { font-family: var(--display); font-size: clamp(18px, 2.6vh, 34px);
  line-height: 1.05; color: var(--magenta); letter-spacing: .02em;
  text-shadow: 0 0 14px rgba(255,42,109,.35); }
.rival-sub { font-family: var(--mono); font-size: var(--ui-xs, 10px);
  letter-spacing: .16em; color: var(--ink-3); margin-top: 2px; }

/* ---------- online table talk ---------- */
.b3-chat-zone { display: flex; flex-direction: column; min-height: 0; max-height: 34vh; }
.b3-chat { flex: 1; overflow-y: auto; font-family: var(--mono);
  font-size: var(--ui-sm, 11px); line-height: 1.5; scrollbar-width: thin; min-height: 42px; }
.b3-chat div { padding: 2px 0; }
.b3-chat b { font-weight: 400; }
.cm-me b { color: var(--cyan); }
.cm-them b { color: var(--magenta); }
.cm-me, .cm-them { color: var(--ink); }
.cm-empty { color: var(--ink-3); font-style: italic; }
.b3-chat-in { display: flex; gap: 5px; margin-top: 6px; }
.b3-chat-in input { flex: 1; min-width: 0; font-family: var(--mono);
  font-size: var(--ui-sm, 11px); background: rgba(9,12,18,.7); color: var(--ink);
  border: 1px solid var(--line); padding: 6px 8px; min-height: 32px; }
.b3-chat-in input:focus { outline: 1px solid var(--cyan-dim); }
.b3-chat-in button { min-height: 32px; padding: 4px 10px; font-size: 10px; }
@media (max-width: 1100px) { .b3-chat-zone { max-height: 22vh; } }
