/* ============================================================================
   system.css — TOKENS + SHARED PRIMITIVES. Loaded FIRST (before duel.css and
   layout.css), because both of those consume what's declared here.

   Three things live in this file and nothing else:
     1. every design token (was split across duel.css's :root and layout.css's
        :root, which left each file consuming the other's tokens — --info-w was
        defined in duel.css and read by layout.css, --sp-2 the other way round);
     2. label primitives the press vocabulary below skins (.pill-solid);
     3. THE PRESS VOCABULARY — the die-cut sticker construction that the
        info-box and the p-stats cluster are both printed with. Four moves
        (sticker shell, dithered ink edge, halftone plate, scissor-cut label),
        parameterised by custom properties so each consumer keeps its own tuned
        silhouette without the rules being written twice.
   ============================================================================ */

/* the printed dot radius, registered so it can be animated: the p-stats
   round-win stamp grows it from nothing to full ink coverage. */
@property --r {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

/* registered so it computes to a real px length: board.js's retuneDeckle() reads
   --die back out with getComputedStyle to keep the deckle filters (displacement
   in absolute px) proportional, and an UNREGISTERED custom property comes back as
   its unevaluated token stream — "calc(0.62 * 80px)", which parseFloat reads as
   NaN and the retune silently skips. Registering is what makes the read work. */
@property --die {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

/* the page graphic's sticker size, registered for exactly the same reason as
   --die above: page-graphic.js's retunePageGraphic() reads it back with
   getComputedStyle, and unregistered it would return the token stream
   "clamp(150px, …)" — which parseFloat reads as NaN. */
@property --pg {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

:root {
  /* Mood accent colors — the only color in the UI */
  --c-White: #959288;
  --c-Blue: #3eace8;
  --c-Black: #9c61b7;
  --c-Red: #dc5d68;
  --c-Green: #3da471;
  /* dither tokens — reused across pills, pile stacks, empty slots, the close
       button and the sticker depth edges. dark = white dots on black, light =
       black dots on white (both Bayer 4×4); hatch = light diagonal. */
  --dither-dark: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='4' height='4'><rect width='1' height='1' fill='white'/><rect x='2' y='1' width='1' height='1' fill='white'/><rect x='1' y='2' width='1' height='1' fill='white'/><rect x='3' y='3' width='1' height='1' fill='white'/></svg>");
  --dither-light: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='4' height='4'><rect width='1' height='1' fill='black'/><rect x='2' y='1' width='1' height='1' fill='black'/><rect x='1' y='2' width='1' height='1' fill='black'/><rect x='3' y='3' width='1' height='1' fill='black'/></svg>");
  --hatch: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='4' height='4'><path d='M0 4L4 0M-1 1L1 -1M3 5L5 3' stroke='%23ccc' stroke-width='1' fill='none'/></svg>");
  /* full-fill shorthands — the dither image always rides with its backing
       color + 4px tile size, so bundle the trio; `background: var(--fill-dark)`
       replaces the three-line repeat everywhere. */
  --fill-dark: #000 var(--dither-dark) 0 0 / 4px 4px;
  --fill-light: #fff var(--dither-light) 0 0 / 4px 4px;
  /* doubled halo keeping white text legible over the dark dither */
  --halo: 0 0 2px #000, 0 0 2px #000;
  --wubrg: conic-gradient(
    from 0deg at 50% 50%,
    var(--c-Blue) 0deg,
    var(--c-Black) 90deg,
    var(--c-Red) 180deg,
    var(--c-Green) 270deg,
    var(--c-Blue) 360deg
  );
  /* --fill-dark's wheel variant: the same Bayer dither over --wubrg instead of
     black. Only ever an --edge-fill — the depth edge is the one surface whose
     fill carries no information, so it can take the whole colour wheel and read
     as a plate printed a hair out of register. It claims no mood precisely
     because it is every hue at once (DESIGN.md › The Color-Means-Something
     Rule). Two consumers now — .pstats .score and the page graphic — which is
     why it is a token and not spelled out twice. */
  --fill-wheel: var(--dither-dark) 0 0 / 4px 4px, var(--wubrg);
  /* the scissor cut, shared by both label strips (the info-box nameplate and
     the p-stats "1st" mark) — slanted ends, nicked long edges, neither cut
     parallel to the other. One token so both labels came off the same pair of
     scissors; each sets its own rotation, because two labels leaning
     identically would read as one template applied twice. */
  --torn: polygon(
    0 11%,
    44% 0,
    78% 5%,
    100% 1%,
    99% 60%,
    100% 91%,
    56% 100%,
    20% 94%,
    1% 100%
  );
  /* the info-box's kiss-cut margin: how far its sticker bleeds outside the
     face. Shared, because #inspector's inset floor (layout.css) has to clear it
     or the sticker overhangs the board edge on a narrow screen. */
  --info-cut: 11px;
  /* edge-spacing between a card column and what sits beside it — currently
     just the discard fan's clearance from the discard and deck piles
     (layout.css › §4). */
  --gap: 16px;
  --u: min(100svh / 12, 100vw / 24, 80px);
  --card-h: calc(3.4 * var(--u));
  --card-ar: 63 / 88; /* poker ratio, w/h — matches the card art assets */
  --card-w: calc(var(--card-h) * 63 / 88);
  --mod: calc(4.75 * var(--u)); /* module depth == side-module width */
  --strip: var(--u); /* visible hand-strip tuck (1u) */
  /* p-stats score die — the sticker's PAPER size. Derived from --strip because
     the cluster lives inside the 1u hand-strip whose bottom edge is the board's
     bottom edge: there is nowhere below for a bleed to go, so the die is capped by
     what the strip can hold. Around the die the sticker still has to fit its
     kiss-cut margin (--cut) and the dithered ink edge (--edge-y) and leave clear
     paper, which is what 0.68 buys: ~7.7% of the strip stays white below the
     sticker at every board size. Upward there IS room — the cluster overflows into
     --mod-gap, which is empty by construction — so only the downward budget binds.
     The two things you actually READ (the numeral, the "1st" cap) are sized off --u
     instead, so they hold their size while the paper around them tightens. */
  --die: calc(0.68 * var(--strip));
  --mod-gap: calc(0.35 * var(--u)); /* mood-row <-> hand breathing space */
  --info-max: calc(
    6.5 * var(--u)
  ); /* info-box height clamp; text scrolls past */
  /* the card's corner. A component token like --btn-radius, deliberately NOT a
       step on the radius scale below (6 would sit between s and m and make the
       ramp 4/6/8/12) and deliberately fixed rather than card-proportional: at
       6px, scaling down with the card stops reading as a rounded corner at all
       on a phone. Everything card-SHAPED shares it — the art's inner radius,
       the pile's stack sheets, the empty slot's hatch — so the whole family
       keeps one corner.
       The floor is that fixed 6px; the ceiling is not ours to pick. Every art
       asset is exported with its OWN corner already baked into the bitmap's
       alpha — ~2.9% of the image width (back.png 14px/500, the mood art
       22px/744) — and CSS only ever clips the image, never un-rounds it. So
       while 6px is the tighter corner CSS wins and the two agree; past ~283px
       of card height the baked corner is rounder than the frame and the
       bitmap's own arc shows sitting inside a sharper black edge. Tracking the
       asset above that point is what keeps frame and art one shape at any size.
       (--u caps at 80px, so a live board tops out at 272px and never reaches
       the crossover — this is the sandbox's full-size range, and insurance if
       that cap ever moves.) */
  --card-radius: max(6px, calc(var(--card-h) * 22 / 1039));
  /* side columns (log/stats + deck/discard rail) track card width so the whole
       frame narrows with the cards on short screens; floor keeps the log
       readable and the rail wide enough for a card + its count badge. */
  --side-w: max(160px, calc(var(--card-w) * 230 / 132));
  /* mood/count badge size — native 32px on a 185px card (~17%). Pulled into a
       token so the mobile query can rescale it off --card-h and keep that ratio. */
  --badge: 32px;
  /* hover info-box width — likewise a token so mobile can track --card-w */
  --info-w: 230px;
  /* the structural gap the #inspector portal keeps from the board edge — a fixed
     feel, not u-scaled. Ported from the locked sandbox, which had a --sp-1 4px
     partner; the info-box's own padding was its last consumer and the sticker
     skin replaced that, so a scale of one is all that's left. */
  --sp-2: 8px;
  /* type scale — everything maps to one of four; xl is the oversized number
     display (score, share code) */
  --fs-s: 13px;
  --fs-m: 15px;
  --fs-l: 18px;
  --fs-xl: 40px;
  /* border-radius scale: s = controls (buttons, chips, dice), m = inputs/boxes,
       full = pills. Cards and everything card-shaped take --card-radius above,
       not a step here — there was an `l: 12px` step and the card family was its
       only consumer, so it was the card's corner under a scale name. Proportional
       radii (25% badge, 50% circles) stay literal — idioms, not scale. */
  --radius-s: 4px;
  --radius-m: 8px;
  --radius-full: 999px;
  /* neutral grays, lightest→darkest — the only grays in the UI */
  --gray-lighter: #eee; /* hairline dividers */
  --gray-light: #ccc; /* disabled / inert borders */
  --gray: #aaa; /* muted text + resting input borders */
  --gray-dark: #666; /* form labels */
  /* CHOICES button caps: buttons scale DOWN with u on small screens but never
     grow past these. Side padding is intentionally larger than top/bottom.
     The type cap is --fs-s, the Label register (DESIGN.md › Hierarchy: "control
     text, nav, stats, badges") — a button is exactly that, so the cap is a ramp
     step rather than a fifth size. It was a literal 14px, which is the button's
     horizontal PADDING in DESIGN.md and read like a number that migrated one
     slot over during the sandbox port. Going through the token also picks up
     the landscape-touch retune below (13→11) for free, which a literal missed
     even though the u-scaling was already landing there on its own. */
  --btn-fs: var(--fs-s);
  --btn-pad-y: 6px;
  --btn-pad-x: 18px;
  /* shared control-corner cap — sharp, and the same for buttons + chips so the
     two read as one family. Fixed (not --radius-s, which the landscape @media
     recomputes off --card-h), so the corner is identical mobile→desktop. */
  --btn-radius: 4px;
  --btn-gap: 10px;
}

/* landscape touch: retune the reading tokens so the whole frame shrinks to
   proportion. Card size is u-driven (the svh/vw shrink handles it), so only a
   wider fixed info box, a proportional badge/radius, and tighter type/spacing
   change here. The rules that ride along with this retune (#fsToggle,
   .menu-acts) stay in duel.css — this is the token half only. */
@media (hover: none) and (pointer: coarse) and (orientation: landscape) {
  :root {
    --badge: max(24px, calc(var(--card-h) * 32 / 185));
    --info-w: min(230px, 80vw);
    /* (--card-radius is absent on purpose: at 6px there is nothing left to
       scale away, and the card's corner should read the same on every device
       — same reasoning as --btn-radius.) */
    --radius-m: calc(var(--card-h) * 8 / 185);
    --radius-s: calc(var(--card-h) * 4 / 185);
    --fs-s: 11px;
    --fs-m: 13px;
    --fs-l: 15px;
    --fs-xl: 30px;
    --gap: 10px;
  }
}

/* solid dither pill: white uppercase label on the Bayer-dark fill, with a
   halo so it stays legible over the dots. Worn by everything in the info-box's
   .sats row — the color mark pills and the "suppressed" badge (board.js
   infoFaceHTML) — each skinned in duel.css. Callers set their own font-size. */
.pill-solid {
  padding: 3px 8px;
  border-radius: var(--radius-full);
  background: var(--fill-dark);
  color: #fff;
  text-shadow: var(--halo);
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-indent: 0.04em;
}

/* ══ THE PRESS ════════════════════════════════════════════════════════════════
   One die-cut sticker construction, two consumers (.info-face and the p-stats
   score die). Every rule below is parameterised — a consumer sets the inputs
   and gets the whole sticker:

     --cut          kiss-cut margin: how far the sticker exceeds its face
     --ink          weight of the hand-cut ink outline
     --cut-corners  the full 8-value border-radius (including the `/`), so each
                    component keeps its own tuned silhouette with zero rule
                    duplication and zero visual drift
     --deckle       a url(#…) token stream, so `filter: var(--deckle)` picks the
                    right displacement filter (each is tuned in absolute px, so
                    a big sticker and a tiny one can't share one)
     --edge-x/-y    how far the dithered ink edge peeks out, and in which
                    direction (p-stats pre-rotates this per seat)
     --edge-fill    what that edge is printed in; defaults to the Bayer-dark ink
     --plate-h      how far the ink plate reaches down the sticker
     --pitch        the halftone screen pitch
     --dot          the ink the plate prints in (mood color / black)

   DESIGN.md forbids shadows: depth is the dithered edge, never blur. Nothing
   that carries text is ever filtered — only the decoration warps.
   ══════════════════════════════════════════════════════════════════════════ */

/* the sticker body + its hand-cut ink outline, inflated past the face by --cut.
   Everything inside is decoration, so the turbulence filter is free here. */
.cut {
  position: absolute;
  inset: calc(-1 * var(--cut));
  z-index: -1;
  overflow: hidden; /* clips the ink plate to the sticker silhouette */
  background: #fff;
  border: var(--ink) solid #000;
  border-radius: var(--cut-corners);
  filter: var(--deckle);
}
/* the dithered ink edge under the sticker — the system's only depth cue */
.cut-edge {
  position: absolute;
  inset: calc(-1 * var(--cut));
  z-index: -2;
  translate: var(--edge-x) var(--edge-y);
  background: var(--edge-fill, var(--fill-dark));
  border-radius: var(--cut-corners);
  filter: var(--deckle);
}

/* the halftone plate: a real dot screen with the dot RADIUS decaying down the
   sticker, not the flat 4px Bayer. Sits inside .cut, so the displacement
   misregisters the dots like an off-press print run. */
.plate {
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--plate-h);
  display: flex;
  flex-direction: column;
}
.plate i {
  flex: 1;
  background-image: radial-gradient(
    circle at 50% 50%,
    var(--dot) var(--r),
    #0000 calc(var(--r) + 0.6px)
  );
  background-size: var(--pitch) var(--pitch);
}
/* eight bands, not four: the radius step between neighbours has to be small
   enough that the seam disappears (or the ramp reads as stripes), and the last
   one has to reach near-nothing or the plate ends on a straight rule across the
   sticker. Radii are FRACTIONS of the pitch, so one ramp holds at every size
   instead of only at the one it was tuned for. */
.plate i:nth-child(1) {
  --r: calc(var(--pitch) * 0.36);
}
.plate i:nth-child(2) {
  --r: calc(var(--pitch) * 0.31);
}
.plate i:nth-child(3) {
  --r: calc(var(--pitch) * 0.26);
}
.plate i:nth-child(4) {
  --r: calc(var(--pitch) * 0.21);
}
.plate i:nth-child(5) {
  --r: calc(var(--pitch) * 0.16);
}
.plate i:nth-child(6) {
  --r: calc(var(--pitch) * 0.115);
}
.plate i:nth-child(7) {
  --r: calc(var(--pitch) * 0.075);
}
.plate i:nth-child(8) {
  --r: calc(var(--pitch) * 0.04);
}
