/* =========================================================
   dylanjayabahu.com — presentation layer

   Rules this file is built on:
     1. Interaction resolves a hidden state; it never merely responds.
     2. No entrance choreography. Nothing fades up on scroll.
        Motion is pointer- or user-initiated, or it does not exist.
     3. One accent. One earned colour — the suit red, only where
        a suit actually is.
   ========================================================= */

/* ---------------------------------------------------------
   Tokens
   --------------------------------------------------------- */
:root {
  /* Cobalt */
  --ground:       #060d18;   /* darker ground, brighter ink: more contrast, sharper */
  --ground-lift:  #0d1726;
  --ink:          #f4f8ff;
  --mid:          #76899f;
  --accent:       #4d9bff;   /* sharper than the muted pass; not Tailwind blue */
  --accent-lift:  #8cc2ff;   /* highlights, hover, the bright end of a ramp */
  --suit:         #ef4a3f;
  --suit-face:    #d3382e;   /* the same red, darkened for the light card face */

  /* Cards. One object, two scales: the doorway reads one at hero size and the
     tables deal them at 60px, off these tokens. A card is a physical thing,
     so it is the one element allowed a radius bigger than --radius. */
  --card-face:    #eef2f8;
  --card-ink:     #10161f;
  --card-radius:  6px;
  --card-back:
    repeating-linear-gradient(38deg,  rgba(255,255,255,.05) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(-38deg, rgba(255,255,255,.05) 0 1px, transparent 1px 9px),
    linear-gradient(160deg, #16233a, #0e1727);

  --line:         rgba(255, 255, 255, .13);
  --line-strong:  rgba(255, 255, 255, .20);

  /* Type. Zodiak ships 400/700 only — there is no 500. */
  --display: 'Zodiak', Georgia, serif;
  --text:    'Satoshi', system-ui, -apple-system, sans-serif;
  --mono:    'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --dls:     -.035em;             /* display tracking — tight reads modern */

  --fs-row:    clamp(1.05rem, 1.05vw + .6rem, 1.6rem);
  --fs-lede:   clamp(1.0625rem, .55vw + .95rem, 1.1875rem);
  --fs-body:   1.0625rem;
  --fs-figure: clamp(1.6rem, 1.3vw + 1.1rem, 2.2rem);
  --fs-meta:   .8125rem;
  --fs-micro:  .71875rem;

  /* Space */
  --pad-x:      3rem;
  --sect-gap:   5.5rem;
  --row-y:      1.6rem;
  --measure:    64ch;

  /* Motion — short, and only ever user-initiated */
  --t-fast: .10s;
  --t-mid:  .15s;
  --t-slow: .26s;
  --ease:   cubic-bezier(.2, .8, .25, 1);
  --radius: 2px;
}

/* ---------------------------------------------------------
   Reset + base
   --------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  background: var(--ground);
  -webkit-text-size-adjust: 100%;
}

body {
  background: var(--ground);
  color: var(--ink);
  font-family: var(--text);
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

a { color: inherit; }
img, canvas, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

.skip {
  position: absolute; top: -100vh; left: 1rem; z-index: 60;
  background: var(--ink); color: var(--ground);
  padding: .5rem .9rem; border-radius: var(--radius); font-size: var(--fs-meta);
  text-decoration: none;
}
.skip:focus { top: 1rem; }

/* ---------------------------------------------------------
   Atmosphere. Grain and a darkening vignette — never a glow.
   --------------------------------------------------------- */
.grain, .vignette {
  position: fixed; inset: 0; pointer-events: none; z-index: 30;
}
.grain {
  opacity: .016;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}
.vignette {
  background: radial-gradient(ellipse 82% 70% at 50% 44%,
    rgba(0,0,0,0) 0%, rgba(0,0,0,.12) 68%, rgba(0,0,0,.30) 100%);
}

/* ---------------------------------------------------------
   Shell
   --------------------------------------------------------- */
.wrap { max-width: 78rem; margin: 0 auto; padding: 0 var(--pad-x); }

/* ---------------------------------------------------------
   Hero — the probe
   --------------------------------------------------------- */
.hero { position: relative; min-height: 94vh; min-height: 94dvh; }

.hero-field {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
}

.hero-ui {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none;
}
.hero-ui a { pointer-events: auto; }

/* Anything a machine produced is set in mono. Anything written is not. */
.readout-val,
.row-num,
.row-meta .venue,
.hero-cred,
.tech li,
.viz-ctl {
  font-family: var(--mono);
  font-feature-settings: "tnum" 1;
}

/* Always a title, even when the field is unaligned. */
.hero-mark {
  position: absolute; top: 2.5rem; left: var(--pad-x);
  display: flex; flex-direction: column; gap: .3rem;
}
.wordmark {
  font-family: var(--display); font-weight: 400; letter-spacing: var(--dls);
  font-size: 1.125rem; line-height: 1; color: var(--ink);
}

/* The credential line, where the lede used to be. It is a slot list that
   grows — "CS @ Waterloo · AI research" today, another affiliation later —
   which is why it is set in the telemetry register and separated by the
   page's own middot rather than written as a sentence. */
.hero-cred {
  position: absolute; left: var(--pad-x); bottom: 3rem;
  font-family: var(--mono); font-size: var(--fs-meta); letter-spacing: .015em;
  color: var(--mid);
}

.hero-links {
  position: absolute; right: var(--pad-x); bottom: 3rem;
  display: flex; gap: 1.75rem; font-size: var(--fs-meta);
}
.hero-links a {
  color: var(--mid); text-decoration: none;
  transition: color var(--t-mid) ease;
}
.hero-links a:hover { color: var(--accent-lift); }

.readout {
  position: absolute; top: 2.5rem; right: var(--pad-x); text-align: right;
  pointer-events: auto;
}
.readout-val {
  font-weight: 400; letter-spacing: -.03em;
  font-size: 2rem; line-height: 1;
  font-variant-numeric: tabular-nums;
}
button.readout-key {
  display: block; margin: .4rem 0 0 auto;
  background: none; border: 0; padding: 0;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .015em;
  color: var(--mid); cursor: default; text-align: right;
  transition: color var(--t-mid) ease;
}
button.readout-key:hover,
button.readout-key:focus-visible { color: var(--accent-lift); }
button.readout-key .q {
  display: inline-block; width: 1em; height: 1em; line-height: 1em;
  border: 1px solid currentColor; border-radius: 50%;
  text-align: center; font-size: .85em; margin-left: .3rem;
}

.readout-help {
  position: absolute; top: calc(100% + .75rem); right: 0;
  width: 25rem; max-width: min(25rem, calc(100vw - 2.5rem));
  max-height: min(26rem, 70vh); overflow-y: auto;
  padding: 1.05rem 1.15rem;
  background: var(--ground-lift);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: 0 18px 44px -18px rgba(0, 0, 0, .9);
  text-align: left; font-size: var(--fs-micro); line-height: 1.55; color: var(--mid);
  opacity: 0; visibility: hidden; transform: translateY(-3px);
  transition: opacity var(--t-mid) ease, transform var(--t-mid) ease, visibility var(--t-mid);
  z-index: 5;
}
.readout-help p + p { margin-top: .6rem; }
.readout-help b { color: var(--ink); font-family: var(--mono); font-weight: 400; }
.readout:hover .readout-help,
.readout:focus-within .readout-help {
  opacity: 1; visibility: visible; transform: none;
}
.readout-note {
  font-size: var(--fs-micro); color: var(--accent); margin-top: .5rem;
  opacity: 0; transition: opacity .5s ease;
}
.readout-note.on { opacity: .9; }

/* ---------------------------------------------------------
   Section head
   --------------------------------------------------------- */
.sect { padding: var(--sect-gap) 0 0; }
/* A real <h2>. It is styled as a label, so it has to opt out of bold and out
   of h2 sizing — but the page needs a heading outline, not three styled divs. */
.sect-head {
  display: flex; align-items: center; gap: .8rem;
  font-family: var(--mono);
  /* Tracking comes down as the size goes up: .1em was set for a 15px label,
     and mono uppercase at 22px with the same tracking reads as a stretched
     banner rather than a heading. */
  /* 600 is a real weight: Geist Mono now loads as one variable file with a
     wght 100-900 axis, so this interpolates rather than being smeared. Worth
     stating because it was briefly the opposite -- while the broken subset was
     in place the mono resolved to Menlo, 600 and 700 measured *identically*
     (950 ink apiece) and the advance width jumped 224.44 -> 239.12, which for
     a monospace is the signature of synthetic bold. On the real face the
     advance holds at 211.2 across every weight and ink climbs evenly,
     721 / 817 / 913 / 999 for 400 / 500 / 600 / 700.

     Size is fluid and deliberately parallel to --fs-row, tracking just
     underneath it: 16px against the rows' 16.8px at 390, 22px against their
     24.7px at 1440. The label is always the second-largest thing in its
     block, never the first. */
  font-size: clamp(1rem, .667vw + .8375rem, 1.375rem); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink);
  padding-bottom: 1rem; border-bottom: 1px solid var(--line-strong);
}
.sect-head::before {
  content: ''; width: 9px; height: 9px; flex: none;
  background: var(--accent);
}


/* ---------------------------------------------------------
   The index. Rows that expand in place.
   Real <details>/<summary>: disclosure with no JS, keyboard
   accessible for free.
   --------------------------------------------------------- */
.entry { border-bottom: 1px solid var(--line); position: relative; }

.entry > summary {
  list-style: none; cursor: pointer; position: relative;
  display: grid; grid-template-columns: 2.75rem 1fr 19.5rem auto; gap: 2rem;
  align-items: baseline; padding: var(--row-y) 0;
}
.entry > summary::-webkit-details-marker { display: none; }
.entry > summary::marker { content: ''; }

/* The disclosure triangle is removed three ways directly above, which left
   nothing at all saying these rows open. cursor:pointer and the sweeping
   underline both need the pointer to already be on the row, and neither
   exists on touch at all — so every metric, figure and link on this page sat
   behind a click nothing invited.

   It is the fourth grid column, which is what puts it on the title's first
   baseline: an inline-block baselines on its own last line box, so the mark,
   the row number and the venue all sit on one line across the row with
   nothing measured by hand.

   It is drawn as a bordered mark rather than a bare glyph. A bare '+' was
   built first and rejected on sight — at the far right of a 78rem row it is
   a thousand pixels from the title the eye is actually reading, and making
   it merely brighter only makes a peripheral thing louder. The border is
   what reads as a control at a glance. Square at --radius, not the readout
   key's circle: that circle means "there is an explanation here", this means
   "this opens".

   Weight 500 because a 400 plus-sign in Geist Mono is thinner than the 1px
   rules around it. Accent at rest, accent-lift on hover and while open,
   alongside .row-num doing the same, so both ends of the row light together
   rather than introducing a third behaviour. */
.entry > summary::after {
  content: '+';
  display: inline-block; width: 1.5em; height: 1.5em; line-height: 1.42em;
  text-align: center;
  font-family: var(--mono); font-weight: 500; font-size: var(--fs-meta);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  color: var(--accent);
  transition: color var(--t-mid) ease, border-color var(--t-mid) ease;
}
.entry[open] > summary::after { content: '\2212'; }   /* U+2212, not a hyphen */
.entry:hover > summary::after,
.entry[open] > summary::after { color: var(--accent-lift); border-color: var(--accent); }

.entry::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: var(--accent);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-mid) var(--ease);
}
.entry:hover::after,
.entry[open]::after { transform: scaleX(1); }

.row-num {
  color: var(--mid); font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  transition: color var(--t-mid) ease;
}
.entry:hover .row-num,
.entry[open] .row-num { color: var(--accent-lift); }
.entry[open] .row-num::after {
  content: ''; display: block; width: 100%; height: 2px;
  background: var(--accent); margin-top: .4rem;
}

.row-title {
  font-family: var(--display); font-weight: 400; letter-spacing: var(--dls);
  font-size: var(--fs-row); line-height: 1.06;
}
.row-title em { font-style: italic; }

.row-meta {
  color: var(--mid); font-size: var(--fs-meta); line-height: 1.5; text-align: right;
}
.row-meta .venue {
  color: var(--accent-lift); display: block;
  font-size: .9em; letter-spacing: -.01em;
}
.row-meta .role { color: var(--ink); font-weight: 500; }

/* ---- the opened panel ---- */
@keyframes panel-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: none; }
}
.panel {
  padding: .5rem 0 3rem 5rem;
  animation: panel-in var(--t-mid) var(--ease);
}
.panel-lede {
  font-size: var(--fs-lede); line-height: 1.55;
  max-width: var(--measure);
}
.panel-lede em { font-style: italic; color: var(--ink); }

.panel-notes {
  margin-top: 1.5rem;
  color: var(--mid); font-size: var(--fs-meta); line-height: 1.62;
  max-width: var(--measure);
}

/* figures: the numbers */
.figures {
  display: flex; flex-wrap: wrap; gap: 3.25rem;
  margin: 2.25rem 0 0;
}
.figure-num {
  font-family: var(--display); font-weight: 400; letter-spacing: -.04em;
  font-size: var(--fs-figure); line-height: .96;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.figure-num .unit { font-size: .52em; margin-left: .12em; color: var(--mid); letter-spacing: 0; }
.figure-label {
  color: var(--mid); font-size: var(--fs-micro);
  margin-top: .5rem; max-width: 17ch; line-height: 1.42;
}

/* ---- figures that argue ---- */
.fig {
  margin: 2.25rem 0 0;
  max-width: 40rem;
}
.fig canvas {
  width: 100%;
  display: block;
}
.fig figcaption {
  margin-top: .85rem;
  color: var(--mid);
  font-size: var(--fs-micro);
  line-height: 1.5;
  max-width: 56ch;
}
.fig figcaption em { font-style: italic; color: var(--ink); }
.fig figcaption b { color: var(--ink); font-weight: 500; }
.fig figcaption sup { font-size: .75em; }
/* if the data cannot be loaded, say nothing rather than showing a broken frame */
.fig[data-failed] { display: none; }

/* ---- project graphics ---- */
.viz-wrap {
  margin: 2rem 0 0;
  max-width: 40rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .012);
}
.viz-wrap canvas { display: block; width: 100%; }

.viz-ctl {
  display: flex; align-items: center; gap: 1rem;
  padding: .55rem .75rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-micro); color: var(--mid);
}
.viz-ctl button {
  margin-left: auto;
  background: none; border: 1px solid var(--line);
  border-radius: var(--radius); padding: .22rem .55rem;
  color: var(--mid); font: inherit; cursor: pointer;
  transition: color var(--t-mid) ease, border-color var(--t-mid) ease;
}
.viz-ctl button:hover { color: var(--accent-lift); border-color: var(--accent); }

/* tech list */
.tech {
  display: flex; flex-wrap: wrap; gap: .5rem;
  margin-top: 2rem; list-style: none;
}
.tech li {
  font-size: calc(var(--fs-micro) * .95); color: var(--mid);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .18rem .45rem; letter-spacing: -.01em;
}
.tech li:hover { color: var(--accent-lift); border-color: var(--accent); }

/* links out */
.panel-links { display: flex; flex-wrap: wrap; gap: 1.5rem; margin-top: 2rem; }
.panel-links a {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: -.01em;
  text-decoration: none;
  border-bottom: 1px solid var(--line); padding-bottom: .12rem;
  transition: border-color var(--t-mid) ease, color var(--t-mid) ease;
}
.panel-links a:hover { border-bottom-color: var(--accent); color: var(--accent-lift); }
.panel-links a .ext { color: var(--mid); }

/* ---------------------------------------------------------
   Doorway — the card detector. Not a heading: a threshold.
   You read two cards, then you sit down at the table.

   The pointer is the camera. Nothing here moves on its own:
   the cards stay face down until you move toward them, which
   is the whole point — the hidden state is only ever resolved
   by the pointer, never on a timer.

   JS drives, per card: --p (read strength, 0..1),
   --d (detector opacity), --tx/--ty (parallax).
   --------------------------------------------------------- */
/* Narrower than the wrap and centred: a door is a gap in a wall, not a
   full-width band. At the wrap's own width the two halves drift 600px
   apart and the composition falls open. */
.doorway {
  display: grid; grid-template-columns: auto auto;
  justify-content: center; align-items: center;
  gap: 4rem;
  padding: 4.5rem 0 4rem;
}
.doorway-copy { max-width: 27rem; }
.doorway-title {
  font-family: var(--display); font-weight: 400; letter-spacing: var(--dls);
  font-size: clamp(1.5rem, 1.7vw + .9rem, 2.4rem); line-height: 1.08;
}

/* The title is an <h3> under the Play <h2>: it names a threshold, not a new
   section. Nothing else here is text — the cards do the explaining. */

/* ---- the felt ---- */
.doorway-felt { perspective: 1400px; }
.doorway-hand {
  display: flex; gap: 1.15rem;
  transform-style: preserve-3d;
  transform: rotateY(calc(var(--tx, 0) * 6deg)) rotateX(calc(var(--ty, 0) * -5deg));
  transition: transform var(--t-slow) var(--ease);
}

.dcard {
  position: relative; flex: none;
  width: min(11.5rem, 20vw); aspect-ratio: 2.5 / 3.5;
  transform-style: preserve-3d;
  border-radius: var(--card-radius);
  box-shadow: 0 22px 52px -20px rgba(0, 0, 0, .82);
}
.dcard:first-child { transform: rotate(-5deg) translateY(.5rem); }
.dcard:last-child  { transform: rotate(4deg); }

.dcard-back {
  position: absolute; inset: 0; overflow: hidden;
  border-radius: var(--card-radius);
  background: var(--card-back);
  border: 1px solid var(--line);
}

.dcard-face {
  position: absolute; inset: 0; overflow: hidden;
  border-radius: var(--card-radius);
  background: var(--card-face); color: var(--card-ink);
  display: flex; align-items: center; justify-content: center;
  opacity: var(--p, 0);
  /* out of focus until the detector has a read — reading through glass */
  filter: blur(calc((1 - var(--p, 0)) * 7px)) saturate(calc(.4 + var(--p, 0) * .6));
  transition: opacity var(--t-slow) linear, filter var(--t-slow) linear;
}
.dcard[data-red="1"] .dcard-face { color: var(--suit-face); }

.dcard-pip {
  font-family: var(--display); font-weight: 400; letter-spacing: -.02em;
  font-size: clamp(3rem, 7vw, 5.25rem); line-height: 1;
}
.dcard-corner {
  position: absolute; top: .6rem; left: .7rem;
  font-family: var(--display); font-weight: 400;
  font-size: 1.15rem; line-height: 1.02; text-align: center;
}
.dcard-corner--br {
  top: auto; left: auto; bottom: .6rem; right: .7rem;
  transform: rotate(180deg);
}

/* Glare: the reason reading through glass is hard. Slides off as the
   read strengthens, and tracks the pointer, because it is a reflection. */
.dcard-glass {
  position: absolute; inset: 0; overflow: hidden; pointer-events: none;
  border-radius: var(--card-radius);
}
.dcard-glass::before {
  content: ''; position: absolute; inset: -40%;
  background: linear-gradient(102deg,
    transparent 34%,
    rgba(255, 255, 255, calc(.04 + (1 - var(--p, 0)) * .15)) 47%,
    rgba(255, 255, 255, calc(.02 + (1 - var(--p, 0)) * .06)) 53%,
    transparent 66%);
  transform: translate(calc(var(--tx, 0) * 22%), calc(var(--ty, 0) * 14%));
  transition: transform var(--t-slow) ease-out;
}

/* Detector: corner brackets, square, 1px — an instrument, not a frame. */
.dcard-det {
  position: absolute; inset: -9px; pointer-events: none;
  opacity: var(--d, 0); transition: opacity var(--t-mid) linear;
}
.dcard-det i { position: absolute; width: 16px; height: 16px; border: 1px solid var(--accent); }
.dcard-det i:nth-child(1) { top: 0; left: 0;  border-right: 0; border-bottom: 0; }
.dcard-det i:nth-child(2) { top: 0; right: 0; border-left: 0;  border-bottom: 0; }
.dcard-det i:nth-child(3) { bottom: 0; left: 0;  border-right: 0; border-top: 0; }
.dcard-det i:nth-child(4) { bottom: 0; right: 0; border-left: 0;  border-top: 0; }

.dcard-tag {
  position: absolute; left: -9px; top: -2.1rem;
  display: flex; align-items: baseline; gap: .5rem;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .015em;
  color: var(--accent); white-space: nowrap;
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
  opacity: var(--d, 0); transition: opacity var(--t-mid) linear;
}
/* When it has no read it says so, in the muted register — a refusal,
   not a result. */
.dcard-tag.lost { color: var(--mid); opacity: 1; }

/* ---------------------------------------------------------
   The games. Blackjack and Hold'em.

   The class contract is fixed: poker.js, blackjack.js and
   games_switcher.js emit these names and know nothing about
   presentation. Every id, class and state class the old
   stylesheet answered is answered here. What changed is the
   surface, not the structure — the old pass was glassmorphism
   (20px backdrop blurs, 999px pills, inset highlights, blue
   glow on hover, an infinite pulse on Deal, mono tracked to
   .22em), and none of that survives contact with this file's
   rules.

   Every fixed height and grid template below is load-bearing:
   both tables are a fixed 580px and every row inside them
   reserves its tallest state, so nothing reflows as cards
   arrive, bets resolve, or hands flip up at showdown.
   --------------------------------------------------------- */

/* ---- switcher: two labels and a rule that slides ---- */
.game-switcher {
  position: relative;
  display: grid; grid-template-columns: 1fr 1fr;
  width: max-content; margin: 0 auto 1.75rem;
  border-bottom: 1px solid var(--line);
}
.game-tab {
  background: none; border: 0; padding: .55rem 1.6rem;
  font-family: var(--mono); font-size: var(--fs-meta); letter-spacing: .015em;
  color: var(--mid); cursor: pointer; text-align: center;
  transition: color var(--t-mid) ease;
}
.game-tab:hover:not(.is-active) { color: var(--accent-lift); }
.game-tab.is-active { color: var(--ink); }
.game-tab-indicator {
  position: absolute; left: 0; bottom: -1px; height: 2px; width: 50%;
  background: var(--accent);
  transition: transform var(--t-slow) var(--ease);
}
.game-switcher[data-active="poker"] .game-tab-indicator { transform: translateX(100%); }

.game-panel[hidden] { display: none; }

/* ---- the rules line: a spec, so it is set as one ---- */
.bj-rules {
  display: flex; justify-content: center; flex-wrap: wrap; gap: .55rem;
  margin: 0 0 1.25rem;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .015em;
  color: var(--mid);
  font-feature-settings: "tnum" 1;
}
.bj-rules strong { color: var(--ink); font-weight: 400; }
.bj-rules-sep { color: var(--mid); opacity: .45; }

/* ---- the table: the same framed-instrument surface the
        project graphics use, at table scale ---- */
.bj-table, .pk-table {
  position: relative; overflow: hidden;
  max-width: 45rem; margin: 0 auto;
  height: 580px;
  padding: 1.6rem 1.5rem;
  display: flex; flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .012);
}
.bj-table { gap: 1.3rem; }
.pk-table { gap: 1.1rem; }

/* The Fourier tracer sits under the content. It runs once, when the shoe
   is prepared, and then stops — see js/blackjack_fourier.js. */
.bj-fourier-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 1; opacity: .8;
}
.bj-table > *:not(.bj-fourier-canvas) { position: relative; z-index: 2; }
.pk-table > * { position: relative; z-index: 2; }

/* =========================================================
   Cards. The same object the doorway reads, dealt small.
   ========================================================= */
.bj-card {
  position: relative; flex-shrink: 0;
  width: 60px; height: 84px;
  border-radius: var(--card-radius);
  user-select: none;
  background: var(--card-face);
  box-shadow: 0 6px 16px -8px rgba(0, 0, 0, .75);
}
.bj-card-rank {
  position: absolute; top: 3px; left: 6px;
  font-family: var(--display); font-weight: 400; letter-spacing: -.01em;
  font-size: 1.05rem; line-height: 1;
}
.bj-card-center {
  position: absolute; bottom: 5px; right: 7px;
  font-family: var(--display); font-weight: 400;
  font-size: 1.55rem; line-height: 1;
}
.bj-card--red  .bj-card-rank, .bj-card--red  .bj-card-center { color: var(--suit-face); }
.bj-card--dark .bj-card-rank, .bj-card--dark .bj-card-center { color: var(--card-ink); }

/* Face-down: the doorway's lattice, same token. */
.bj-card--back {
  background: none; overflow: hidden;
  border: 1px solid var(--line);
}
.bj-card-back-pattern { width: 100%; height: 100%; background: var(--card-back); }

/* An empty community slot: the outline of a card that has not been dealt. */
.bj-card.pk-card--empty {
  background: none; box-shadow: none;
  border: 1px solid var(--line);
}

/* A card arriving. User-initiated — you pressed Deal — so it is allowed to
   move. It slides in; it does not zoom. */
@keyframes bj-card-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.bj-card--enter { animation: bj-card-in var(--t-slow) var(--ease) both; }

/* =========================================================
   Blackjack
   ========================================================= */
.bj-area-label,
.bj-bank-label,
.bj-bet-label,
.bj-hand-label,
.pk-pot-label,
.pk-seat-name {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .015em;
  color: var(--mid);
}

.bj-total,
.bj-bank-value,
.bj-hand-total,
.pk-pot-value,
.pk-seat-stack,
.pk-seat-bet,
.bj-message,
.pk-message {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.bj-side { min-height: 95px; display: flex; flex-direction: column; gap: .45rem; }
.bj-side-header { display: flex; align-items: baseline; gap: .7rem; }
.bj-total { font-size: var(--fs-meta); color: var(--ink); min-height: 1em; }

.bj-cards { display: flex; flex-wrap: wrap; gap: 6px; align-items: flex-start; }

/* Fills the leftover vertical space inside the table's fixed 580px so the
   cards stay centred rather than leaving a hollow strip. */
.bj-player-area {
  display: flex; gap: 1.5rem; flex-wrap: wrap;
  min-height: 95px; flex: 1;
  align-items: center; align-content: center;
}

.bj-hand { display: flex; flex-direction: column; gap: .6rem; }
.bj-hand-label { display: flex; align-items: baseline; gap: .6rem; }
.bj-hand--player.is-active .bj-hand-label { color: var(--accent-lift); }
.bj-hand-total { color: var(--ink); }
.bj-hand-bet { color: var(--mid); }

/* Outcome is typographic. Red is not available here: on this page red means
   a suit is present, and a lost hand is not a suit. Green was never in the
   palette at all. So a win is the accent, a push is muted, and a loss is
   muted with the total struck through — the hand itself is crossed out. */
.bj-hand-tag {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .015em;
  color: var(--mid);
}
.bj-hand-tag--win  { color: var(--accent); }
.bj-hand-tag--bj   { color: var(--accent-lift); }
.bj-hand-tag--push { color: var(--mid); }
.bj-hand-tag--lose,
.bj-hand-tag--bust { color: var(--mid); }
.bj-hand--lose .bj-hand-total,
.bj-hand--bust .bj-hand-total {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  color: var(--mid);
}

.bj-message {
  text-align: center; font-size: var(--fs-meta);
  color: var(--mid); min-height: 1.4em; letter-spacing: .015em;
  transition: color var(--t-slow) ease;
}

.bj-divider { height: 1px; background: var(--line); margin: .25rem 0; }

/* ---- controls ---- */
.bj-controls { display: flex; flex-direction: column; gap: .8rem; }
.bj-bank-row { display: flex; gap: 2.5rem; flex-wrap: wrap; }
.bj-bank-info { display: flex; flex-direction: column; gap: .2rem; }
.bj-bank-value { font-size: var(--fs-meta); color: var(--ink); }
.bj-bet-display { display: flex; align-items: baseline; gap: .6rem; }

.bj-btn-ghost {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--text); font-size: var(--fs-micro);
  color: var(--mid);
  border-bottom: 1px solid var(--line); padding-bottom: .05rem;
  transition: color var(--t-mid) ease, border-color var(--t-mid) ease;
}
.bj-btn-ghost:hover:not(:disabled) { color: var(--ink); border-bottom-color: var(--accent); }
.bj-btn-ghost:disabled { opacity: .35; cursor: not-allowed; }

.bj-bet-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.bj-bet-label { margin-right: .25rem; }

/* Chips carry amounts, so they are mono and tabular. */
.bj-chip {
  background: none;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .24rem .6rem; cursor: pointer;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: -.01em;
  color: var(--mid);
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
  transition: color var(--t-mid) ease, border-color var(--t-mid) ease;
}
.bj-chip:hover:not(:disabled) { color: var(--accent-lift); border-color: var(--accent); }
.bj-chip:disabled { opacity: .3; cursor: not-allowed; }

.bj-actions { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }

/* A button label is a command you issue, not a number a machine produced,
   so the actions are set in the text face — and not tracked out. */
.bj-btn {
  padding: .5rem 1rem; min-width: 5rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: none; cursor: pointer;
  font-family: var(--text); font-size: var(--fs-meta); font-weight: 500;
  color: var(--mid);
  transition: color var(--t-mid) ease, border-color var(--t-mid) ease;
}
.bj-btn:hover:not(:disabled) { color: var(--ink); border-color: var(--accent); }
.bj-btn:disabled { opacity: .3; cursor: not-allowed; }

/* Deal is the primary action and the only filled element on the page. It
   needs no pulse: between hands it is the only button that is not disabled,
   which says "next" more clearly than a glow ever did. */
.bj-btn--deal {
  background: var(--accent); border-color: var(--accent);
  color: var(--ground); font-weight: 700;
  min-width: 6rem;
}
.bj-btn--deal:hover:not(:disabled) {
  background: var(--accent-lift); border-color: var(--accent-lift);
  color: var(--ground);
}
/* A disabled primary must not out-shout an enabled secondary: mid-hand,
   Deal is unavailable while Hit and Stand are not, so it drops the fill
   and joins the other disabled buttons. */
.bj-btn--deal:disabled {
  background: none; border-color: var(--line);
  color: var(--mid); font-weight: 500;
  opacity: .3;
}

/* =========================================================
   Hold'em
   ========================================================= */
/* Opponents across row 1, board filling row 2. minmax(0,1fr) plus the
   fixed card slots below is what stops the board from pushing the hero
   seat down as the hand develops. */
.pk-arena {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  gap: 1.2rem .4rem;
  align-items: start;
  padding: .2rem .2rem 0;
  flex: 1; min-height: 0;
}
.pk-seat[data-seat="1"] { grid-column: 1; grid-row: 1; }
.pk-seat[data-seat="2"] { grid-column: 2; grid-row: 1; }
.pk-seat[data-seat="3"] { grid-column: 3; grid-row: 1; }
.pk-seat[data-seat="4"] { grid-column: 4; grid-row: 1; }
.pk-seat[data-seat="5"] { grid-column: 5; grid-row: 1; }
.pk-board               { grid-column: 1 / -1; grid-row: 2; align-self: stretch; }

.pk-seat {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: .3rem;
  transition: opacity var(--t-slow) ease;
}
.pk-seat:not(.pk-seat--hero).is-folded { opacity: .3; }
/* The hero's own name and stack stay legible after folding — only the
   cards and their column dim. */
.pk-seat--hero.is-folded .pk-hero-center,
.pk-seat--hero.is-folded .pk-hero-empty { opacity: .3; }

.pk-seat-name { transition: color var(--t-mid) ease; }
.pk-seat.is-active .pk-seat-name { color: var(--accent-lift); }
.pk-seat.is-winner .pk-seat-name { color: var(--ink); }

/* Whose turn it is: a static mark, plus the timer ring, which carries the
   urgency honestly because it shows the time actually remaining. */
.pk-seat.is-active .pk-seat-name::before {
  content: ''; display: inline-block;
  width: 4px; height: 4px; margin-right: .4rem; vertical-align: middle;
  background: var(--accent);
}
/* The dealer button is a white disc with a D on it. Drawn as one. */
.pk-seat.is-dealer .pk-seat-name::after {
  content: 'D'; margin-left: .4rem;
  display: inline-block; width: 1.2em; height: 1.2em; line-height: 1.2em;
  text-align: center; font-size: .82em;
  color: var(--ground); background: var(--ink); border-radius: 50%;
}

/* The hero sits below the divider, outside the arena grid, so the board can
   fill the whole middle. Its info reads as one line — You / stack / bet —
   with clearance below for the showdown card lift. */
.pk-seat--hero {
  flex-direction: column; justify-content: center; align-items: center;
  gap: .2rem; padding: 0 .8rem;
}
.pk-seat-info { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.pk-seat--hero .pk-seat-info {
  flex-direction: row; align-items: center; gap: .5rem;
  margin-bottom: .6rem;
}
.pk-seat-stack { font-size: var(--fs-meta); color: var(--ink); }
.pk-seat-sep { font-family: var(--mono); font-size: var(--fs-micro); color: var(--mid); opacity: .4; }
.pk-seat-profile {
  font-family: var(--mono); font-size: calc(var(--fs-micro) * .82);
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent); opacity: .75; line-height: 1;
}
.pk-seat-profile:empty { display: none; }

/* Dollars: an outlined slot, always reserving its space so the seat never
   changes height between no-bet, bet and all-in equity. */
.pk-seat-bet {
  display: inline-flex; align-items: center; justify-content: center;
  height: 18px; min-width: 2.6em; padding: 0 .4rem;
  box-sizing: border-box; line-height: 1; text-align: center;
  font-size: var(--fs-micro); letter-spacing: -.01em;
  color: var(--accent-lift);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.pk-seat-bet.is-empty { visibility: hidden; }
/* All in. The game has toggled this class all along with nothing listening;
   a committed stack should say so. */
.pk-seat.is-allin .pk-seat-stack::after {
  content: ' all in'; color: var(--accent); letter-spacing: .015em;
}
/* Percentages, not dollars — so it inverts, flatly. */
.pk-seat-bet.is-equity {
  background: var(--accent); border-color: var(--accent);
  color: var(--ground);
}

.pk-seat-cards { display: flex; gap: 4px; align-items: center; justify-content: center; }
/* Both slots reserve their tallest state: the hero's full-size cards, and
   the opponents' showdown size (50px) even while showing 32px backs. */
.pk-seat--hero .pk-seat-cards { min-height: 84px; }
.pk-seat:not(.pk-seat--hero) .pk-seat-cards { height: 50px; }

.pk-seat:not(.pk-seat--hero) .pk-seat-cards .bj-card {
  width: 22px; height: 32px; border-radius: 3px;
  transition: width var(--t-slow) ease, height var(--t-slow) ease;
}
.pk-seat:not(.pk-seat--hero) .pk-seat-cards .bj-card-rank   { font-size: .46rem; top: 1px; left: 2px; }
.pk-seat:not(.pk-seat--hero) .pk-seat-cards .bj-card-center { font-size: .62rem; bottom: 1px; right: 2px; }

.pk-seat.is-showing:not(.pk-seat--hero) .pk-seat-cards .bj-card {
  width: 36px; height: 50px; border-radius: 4px;
}
.pk-seat.is-showing:not(.pk-seat--hero) .pk-seat-cards .bj-card-rank   { font-size: .66rem; top: 2px; left: 3px; }
.pk-seat.is-showing:not(.pk-seat--hero) .pk-seat-cards .bj-card-center { font-size: .92rem; bottom: 2px; right: 3px; }

.pk-seat--hero .pk-seat-cards .bj-card { width: 60px; height: 84px; }

/* ---- board ---- */
.pk-board {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .7rem; padding: .3rem .2rem; overflow: hidden; min-height: 0;
}
.pk-pot-display { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.pk-pot-value { font-size: var(--fs-meta); color: var(--accent-lift); }

.pk-community { display: flex; gap: 4px; justify-content: center; flex-wrap: nowrap; }
.pk-community .bj-card { width: 50px; height: 70px; }
.pk-community .bj-card-rank   { font-size: .88rem; top: 3px; left: 5px; }
.pk-community .bj-card-center { font-size: 1.3rem; bottom: 4px; right: 5px; }

/* Locked to one line, with clearance below: at showdown the community cards
   lift ~11px and would otherwise clip into it. */
.pk-message {
  width: 100%; text-align: center;
  font-size: var(--fs-micro); color: var(--mid);
  height: 1.2em; line-height: 1.2; letter-spacing: .015em;
  margin: 0 0 .6rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color var(--t-slow) ease;
}

/* ---- hero row: actions left, cards centre, right column reserved.
        min-height is sized for the raise widget open, so opening it never
        reflows the table. ---- */
.pk-hero-row {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 1.4rem;
  min-height: 124px; width: 100%;
}
.pk-hero-actions {
  display: flex; flex-direction: column; align-items: stretch; justify-content: center;
  gap: 5px; width: 130px; justify-self: start;
}
.pk-hero-center {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .25rem; justify-self: center;
  transition: opacity var(--t-slow) ease;
}
.pk-hero-empty { width: 130px; justify-self: end; }

.pk-hero-actions .bj-btn {
  width: 100%; min-width: 0;
  padding: .42rem .6rem; font-size: var(--fs-micro);
}

/* ---- raise widget ---- */
.pk-raise-chips { display: flex; gap: 4px; }
.pk-raise-chips[hidden], .pk-raise-commit[hidden] { display: none; }
.pk-hero-actions .pk-raise-chip {
  flex: 1; padding: .2rem 0; text-align: center;
  font-size: calc(var(--fs-micro) * .92);
}
.pk-raise-commit { display: flex; gap: 4px; }
.pk-hero-actions .pk-raise-x {
  width: 1.9rem; min-width: 0; flex: none; padding: .42rem 0; text-align: center;
}
.pk-hero-actions .pk-raise-confirm { flex: 1; }
/* The widget needs more lateral room than the plain action stack. */
.pk-hero-actions.is-raising { width: 172px; }

/* The only range input on the page. It inherited these values from the
   figure-control slider, which is now deleted along with the steering dial it
   was built for. */
.pk-raise-slider {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 1.25rem;
  background: transparent; cursor: pointer;
}
.pk-raise-slider::-webkit-slider-runnable-track { height: 1px; background: var(--line-strong); }
.pk-raise-slider::-moz-range-track { height: 1px; background: var(--line-strong); }
.pk-raise-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 11px; height: 11px; margin-top: -5px;
  border: 0; border-radius: 50%; background: var(--accent);
}
.pk-raise-slider::-moz-range-thumb {
  width: 11px; height: 11px; border: 0; border-radius: 50%; background: var(--accent);
}
.pk-raise-slider:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.pk-raise-slider:disabled { opacity: .35; cursor: not-allowed; }

/* ---- showdown ---- */
/* `forwards` holds the lifted state: the render loop repaints continuously
   and would otherwise reset the transform every frame. */
@keyframes pk-card-pop-up {
  from { transform: none; }
  to   { transform: translateY(-11px); }
}
.pk-card--best { animation: pk-card-pop-up var(--t-slow) var(--ease) forwards; }
/* Not part of the best hand. The lift on .pk-card--best already says which
   cards count, so this only needs to recede — at .32 a light card turned
   muddy grey and its red suits went brown, which read as a rendering fault
   rather than de-emphasis. */
.pk-card--dim { opacity: .55; filter: saturate(.25); }
.pk-seat.is-loser .pk-seat-info,
.pk-seat.is-loser .pk-seat-bet { opacity: .45; }

/* ---- timer ring: the honest turn indicator. Depletion is driven by
        stroke-dashoffset from the game's own loop. ---- */
.pk-timer-ring {
  position: absolute; top: -5px; right: 2px;
  width: 26px; height: 26px; pointer-events: none; z-index: 4;
}
/* Not on the hero. The bots act against a real clock (poker.js gives them a
   durationMs); the hero has unlimited time, and for it poker.js falls back to
   an "unbounded sweep at 8s period — pure ambient motion". A ring that
   depletes forever and never expires is a spinner pretending to be a timer,
   which is precisely principle 1. The hero seat also spans the full table
   width, so the ring landed at the far right edge, nowhere near "You". */
.pk-seat--hero .pk-timer-ring { display: none; }
.pk-timer-ring[hidden] { display: none; }
.pk-timer-bg  { fill: none; stroke: var(--line); stroke-width: 2.4; }
.pk-timer-arc {
  fill: none; stroke: var(--accent); stroke-width: 2.4; stroke-linecap: round;
  transition: stroke var(--t-mid) ease;
}

/* ---- games, narrow. Ported from the old stylesheet's 600px block: every
        card size, card slot and grid row here was tuned to keep the tables
        from reflowing on a phone, so the geometry carries over intact and
        only the type and colour tokens change. ---- */
@media (max-width: 600px) {
  .bj-table, .pk-table { max-width: 100%; }
  .bj-table { padding: 1.4rem 1rem; }
  .pk-table { padding: 1.1rem .7rem; }

  .bj-rules { gap: .4rem; }

  .bj-card { width: 54px; height: 76px; }
  .bj-card-rank   { font-size: .95rem; }
  .bj-card-center { font-size: 1.4rem; }

  /* actions share the row evenly rather than each holding a 5rem minimum */
  .bj-btn { flex: 1 1 0; min-width: 0; padding: .5rem .5rem; font-size: var(--fs-micro); }
  .bj-bank-row { gap: 1.25rem; }
  .bj-bet-row { flex-wrap: nowrap; gap: .35rem; }
  .bj-bet-label { display: none; }
  .bj-chip {
    flex: 1 1 0; min-width: 0; padding: .3rem .25rem; text-align: center;
    font-size: calc(var(--fs-micro) * .92);
  }

  .pk-arena { grid-template-rows: auto minmax(110px, 1fr); gap: .9rem .2rem; }
  .pk-seat { padding: .15rem; gap: .2rem; }

  .pk-seat:not(.pk-seat--hero) .pk-seat-cards { height: 42px; }
  .pk-seat:not(.pk-seat--hero) .pk-seat-cards .bj-card { width: 18px; height: 26px; border-radius: 2px; }
  .pk-seat:not(.pk-seat--hero) .pk-seat-cards .bj-card-rank   { font-size: .42rem; top: 1px; left: 2px; }
  .pk-seat:not(.pk-seat--hero) .pk-seat-cards .bj-card-center { font-size: .58rem; bottom: 1px; right: 2px; }

  .pk-seat.is-showing:not(.pk-seat--hero) .pk-seat-cards .bj-card { width: 30px; height: 42px; border-radius: 3px; }
  .pk-seat.is-showing:not(.pk-seat--hero) .pk-seat-cards .bj-card-rank   { font-size: .56rem; top: 1px; left: 2px; }
  .pk-seat.is-showing:not(.pk-seat--hero) .pk-seat-cards .bj-card-center { font-size: .8rem; bottom: 2px; right: 3px; }

  .pk-seat--hero .pk-seat-cards { min-height: 70px; }
  .pk-seat--hero .pk-seat-cards .bj-card { width: 50px; height: 70px; }

  .pk-seat-name  { font-size: calc(var(--fs-micro) * .82); }
  .pk-seat-stack { font-size: var(--fs-micro); }
  .pk-seat-bet   { font-size: calc(var(--fs-micro) * .82); min-width: 2.2em; }

  .pk-community .bj-card { width: 38px; height: 54px; }
  .pk-community .bj-card-rank   { font-size: .72rem; }
  .pk-community .bj-card-center { font-size: 1rem; }

  /* No room for the reserved right column: collapse to actions | cards. */
  .pk-hero-row { grid-template-columns: auto auto; justify-content: center; gap: .6rem; min-height: 130px; }
  .pk-hero-actions, .pk-hero-actions.is-raising { width: 100px; justify-self: start; }
  .pk-hero-empty { display: none; }
  /* The quick-bet presets crowd a 100px column; the slider and the
     cancel/confirm row are what fit. */
  .pk-raise-chips { display: none; }
  .pk-hero-actions .pk-raise-x { width: 24px; }

  /* the ring is a badge on a seat, and a mobile seat is much smaller */
  .pk-timer-ring { width: 20px; height: 20px; top: -4px; right: 0; }

  .game-switcher { width: 100%; max-width: 17rem; }
  .game-tab { padding: .5rem .4rem; font-size: var(--fs-micro); }
}

/* ---------------------------------------------------------
   Footer
   --------------------------------------------------------- */
.footer {
  border-top: 1px solid var(--line);
  margin-top: var(--sect-gap);
  padding: 3rem 0 4rem;
  display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: baseline;
  font-size: var(--fs-meta); color: var(--mid);
}
.footer nav { margin-left: auto; display: flex; flex-wrap: wrap; gap: 1.5rem; }
.footer a { text-decoration: none; transition: color var(--t-mid) ease; }
.footer a:hover { color: var(--ink); }

/* ---------------------------------------------------------
   Narrow
   --------------------------------------------------------- */
@media (max-width: 900px) {
  :root { --pad-x: 1.25rem; --sect-gap: 4.5rem; --row-y: 1.5rem; }

  .hero-mark { top: 1.75rem; }
  .hero-cred { bottom: 6.75rem; }
  .hero-links { bottom: 3.25rem; gap: 1.15rem; left: var(--pad-x); right: auto; }
  .readout { top: 4.5rem; }
  .readout-val { font-size: 1.85rem; }

  /* Two columns here, and .row-meta is explicitly placed into the second of
     them on a second row — which leaves nowhere predictable for an auto-placed
     fourth child to land. So the marker comes out of the grid at this width
     and pins to the row's top-right instead, held clear of the title by the
     summary's own padding. Nothing to baseline-align against here anyway:
     the meta has dropped below the title. */
  .entry > summary { grid-template-columns: 2.25rem 1fr; gap: 1rem; padding-right: 1.5rem; }
  .entry > summary::after { position: absolute; right: 0; top: var(--row-y); }
  .row-meta { grid-column: 2; text-align: left; margin-top: .5rem; }
  .row-meta .venue { display: inline; }
  /* venue is a block on wide screens; inline it needs its own separator.
     The leading space is non-breaking so the separator cannot wrap to the
     start of the next line, orphaned ahead of the role. */
  .row-meta .venue::after { content: '\00a0· '; color: var(--mid); }

  .panel { padding-left: 0; padding-bottom: 2.5rem; }
  .figures { gap: 2rem; }

  /* Doorway stacks: cards first, because they are the thing. */
  .doorway {
    grid-template-columns: 1fr; justify-content: stretch;
    gap: 2.5rem; padding: 3rem 0 2.5rem;
  }
  .doorway-felt { order: -1; }
  .doorway-hand { justify-content: center; gap: .9rem; }
  .dcard { width: min(9.5rem, 36vw); }
  .dcard-tag { font-size: calc(var(--fs-micro) * .95); top: -1.9rem; }
}

/* ---------------------------------------------------------
   Reduced motion. The disclosure still works; it just arrives.
   --------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}

/* ---------------------------------------------------------
   Print
   --------------------------------------------------------- */
@media print {
  /* The doorway and the games are interactive or they are nothing: on paper
     they come out as two hero-scale playing cards and a 580px empty table.
     #play goes too, or its heading prints with nothing under it. */
  .grain, .vignette, .hero-field, .readout, #play { display: none; }
  html, body { background: #fff; color: #000; }
  .entry > summary { grid-template-columns: 2.5rem 1fr; padding-right: 0; }
  /* Nothing opens on paper, so the marker would be an instruction that cannot
     be followed. Every panel is forced open below in any case. */
  .entry > summary::after { content: none; }
  .panel { display: block !important; padding-left: 2.5rem; }
  .hero { min-height: auto; }
}
