/* ===========================================================================
 * SMITE ARCADE — "Conquest"
 * ---------------------------------------------------------------------------
 * CHROME IS COLD, GODS ARE HOT.
 *
 * The previous world sampled its palette from 255k pixels of god splash art
 * and then painted the *chrome* that temperature. That is a category error:
 * a UI's colour comes from its frame, not from the pictures inside it. The
 * measured result was a 1.15:1 luminance step between the game board and the
 * page behind it — no hierarchy, only a gradient of browns, and every warm
 * portrait competing with a warm surface instead of landing on it.
 *
 * SMITE 2's own chrome is cool — near-black and navy, cyan and gold —
 * precisely so the warm gods pop off it. This world does the same. Everything
 * the player READS sits on cold steel; everything the player LOOKS AT is
 * splash art, and it is the only warm thing on screen.
 *
 * Panels are cut, not rounded: long shallow shears, notch tabs, rules that
 * overrun their edges. The old chamfer paid the full clip-path cost at 8-14px
 * and bought a rounded corner.
 *
 *   type   Saira Condensed (display, a scoreboard's voice) / Archivo (UI)
 *          / DM Mono (numerals)
 *   colour cyan = exact, gold = close & victory, blood = wrong & heat
 * ======================================================================== */

/* ---- tokens ------------------------------------------------------------
 * The frame carries the pantheon; the art is the room.
 *
 * Conquest separated board from page by TEMPERATURE (cold chrome, hot art).
 * That worked as contrast and failed as identity: strip the portraits out and
 * what remained was a competent dark dashboard that could have belonged to any
 * product. The separation now comes from VALUE plus a lit metal edge, which
 * frees the chrome to be warm without collapsing the 1.15:1 hierarchy failure
 * that Conquest itself existed to fix. Every bevel is a real light source:
 * bronze catching the top edge, black under the bottom.
 *
 * Names are preserved as aliases on purpose. Every --cyan/--ember/--steel
 * reference across this stylesheet keeps resolving; only the values moved.
 * ---------------------------------------------------------------------- */
:root {
  /* stone — warm darks, a real value ladder so a board clears its page */
  --ink:        #0b0806;
  --ink-2:      #120d09;
  --stone:      #1b1410;   /* surface */
  --stone-2:    #271d17;
  --stone-3:    #362820;   /* raised */

  /* lettering — warm white, so type belongs to the carved frame */
  --bone:       #f4ecdf;
  --bone-dim:   #c6b39c;
  /* 7.6:1 flat on --ink. The extra headroom over the 4.5 floor is deliberate:
     this text sits on the backdrop composite, which is lighter than the token,
     and the old value measured 5.8 flat but 3.1 rendered. */
  --bone-faint: #b09d84;

  /* pigments — three verdicts, unchanged in meaning, moved in hue */
  --patina:     #4fc0a0;   /* EXACT — bronze oxidised, native to this world */
  --patina-lo:  #1f7561;
  --patina-hi:  #86dcc2;
  --gold:       #e8b44a;   /* CLOSE, and victory */
  --gold-lo:    #8f6a18;
  --blood:      #cf3d22;   /* WRONG, and heat */
  --blood-lo:   #8f2311;

  /* bronze — the chrome itself. Conquest forbade this; it is the point now. */
  --bronze:     #b4823f;
  --bronze-hi:  #e6c184;   /* the lit top edge of every bevel */
  --bronze-lo:  #5e401d;

  /* The structural voice. Conquest gave this job to its "exact" pigment, so
     the brand, nav, hairlines, focus rings and primary action all wore the
     verdict colour — which is why stripping the art left a cold dashboard.
     Chrome is bronze; a verdict pigment now only ever states a verdict. */
  --chrome:     var(--bronze-hi);
  --chrome-lo:  var(--bronze);

  /* Forged edge. Conquest banned borders outright because clip-path deletes
     them, and replaced them with an inset shadow nobody can see — which is
     why every plate read as a flat cutout instead of a struck object.
     An inset ring is no good either: it follows the rectangle, so it breaks
     along the two chamfered corners. This draws the edge as the bottom
     background layer with the fill inset on top, so both get clipped
     together and the rim survives the diagonal. */
  --edge-w:  3px;
  --edge:    var(--bronze);
  --edge-hi: var(--bronze-hi);
  --inset:   calc(100% - (var(--edge-w) * 2));
  /* The second, darker line inboard of the rim. One bright line reads as a
     stroke; bright-then-dark reads as a machined bevel catching light. */
  --inset-2: calc(100% - (var(--edge-w) * 4));
  --edge-in: rgba(0, 0, 0, 0.55);

  /* Divine light. SMITE's signature is rim-lit gods against dark ground, and
     the predecessor had no light in it at all — every plate sat flat. Glow is
     warm, tight, and always outboard, so it never washes text. */
  /* Our own bolt, as a background mark. Same geometry as ICONS.bolt in
     core.js — blocky and hard-angled, drawn for this project. */
  --bolt: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M15.6 1.5 5.2 13.6h4.9L8.4 22.5 18.9 10.2h-5.1z' fill='%23e8b44a'/%3E%3C/svg%3E");

  --godlight:     0 0 26px -8px rgba(230, 193, 132, 0.55);
  --godlight-lg:  0 0 60px -14px rgba(230, 193, 132, 0.60);
  --godlight-hot: 0 0 34px -6px rgba(232, 180, 74, 0.85);

  /* Per-mode identity. Four ancient-world pigments, not four hues picked off
     a wheel: gold leaf, copper, verdigris, and murex purple. Each is measured
     against --ink as heading text, and none reuses a verdict's meaning.
     The registry in core.js points at these; colour never lives in the JS. */
  --accent-duel:    #e8b44a;   /* gold leaf      — 9.1:1 */
  --accent-classic: #d2703a;   /* copper         — 5.8:1 */
  --accent-lore:    #4fb08a;   /* verdigris      — 7.5:1 */
  --accent-emoji:   #c46da2;   /* murex purple   — 5.8:1 */

  /* text-safe siblings: the fills above are not legible as small text */
  --blood-text: #ff7255;
  --patina-text:#4fc0a0;

  /* aliases — every component rule below still resolves through these */
  --cyan:       var(--patina);
  --cyan-lo:    var(--patina-lo);
  --cyan-hi:    var(--patina-hi);
  --cyan-text:  var(--patina-text);
  --verdigris:  var(--patina);
  --verdigris-lo: var(--patina-lo);
  --ember:      var(--gold);
  --ember-lo:   var(--gold-lo);
  --ember-hi:   var(--bronze-hi);
  --crimson:    var(--blood);
  --crimson-lo: var(--blood-lo);
  --steel:      var(--bronze);
  --on-hit:      #04211a;  /* dark text ON a full-saturation patina fill */
  --on-near:     #201603;  /* dark text ON a full-saturation gold fill */

  /* ---- THE VERDICT SET -------------------------------------------------
     One meaning, one colour, everywhere a verdict is stated. The pigments
     already existed; what did not exist was a name for "wrong", so every
     mode expressed a miss differently — Classic left it bronze (the chrome
     colour), the logs used a bare mark, and only Duel had a real losing
     colour. Bronze on a miss was the worst of it: the structural metal was
     also, accidentally, the most common verdict on the board.

     GREEN exact · YELLOW close · GREY wrong, with red kept for the harder
     losses (a failed duel, a lost round) where the stake is the whole play
     rather than one field. */
  /* GREEN, and actually green. This pointed at --patina (#4fc0a0), which sits
     at hue 163 -- spring green, a third of the way to cyan -- so next to the
     gold "close" chip it read as blue rather than as the opposite of wrong.
     Patina keeps its own jobs as a MATERIAL (the veiled plate's rim, Lore's
     verdigris accent, oxidised bronze generally); it is just no longer asked
     to also mean "correct". Hue 132: unambiguous at a glance, still warm
     enough to belong beside bronze. */
  --exact:       #58c46e;
  --exact-lo:    #2f8046;
  --exact-text:  #6ad07e;
  --close:       var(--gold);
  --close-lo:    var(--gold-lo);
  --close-text:  #f0c76a;   /* gold is a fill; this is its text-safe sibling */
  --wrong:       #6f6459;   /* warm grey — inert next to bronze, not a pigment */
  --wrong-lo:    #3a332c;
  --wrong-text:  var(--bone-dim);

  /* Caption colours for the field name inside a verdict chip. One per state,
     because the caption sits ON the verdict fill and each fill needs its own
     measured step-down. Ratios are against that state's own chip. */
  --key-wrong:   #bdab96;   /* 5.58:1 on --wrong-lo */
  --key-exact:   #0e3a1c;   /* 5.81:1 on --exact   */
  --key-close:   #42300b;   /* 6.66:1 on --close   */
  --lost:        var(--blood);
  --lost-lo:     var(--blood-lo);
  --lost-text:   var(--blood-text);
  --on-verdigris:#04211a;
  --shade-hi:   #16100c;
  --shade-lo:   #0a0705;

  /* rules & light — bronze-tinted, so even the hairlines are metal */
  --rule:       rgba(230, 193, 132, 0.14);
  --rule-soft:  rgba(230, 193, 132, 0.07);
  --rule-hard:  rgba(230, 193, 132, 0.32);
  --bevel:      inset 0 1px 0 rgba(230, 193, 132, 0.26),
                inset 0 -1px 0 rgba(0, 0, 0, 0.72);
  --engrave:    var(--bevel);
  --lift:       0 26px 52px -30px rgba(0, 0, 0, 0.95);

  /* TWO ROLES, not two voices.
   *
   * Grenze ran every job from the 58px title down to 11px labels. It is a
   * display serif — low x-height, incised wedge terminals, narrow bowls — and
   * those are exactly the features that stop working at label size. Contrast
   * was never the problem there (the nav measured 7.6:1); the letterforms
   * were. The One Face Rule was costing legibility on every functional string
   * on the site to buy identity in places identity was already secure.
   *
   * So the split is by ROLE, and the test is one question: is this the
   * product speaking, or the interface? Grenze keeps everything you look AT —
   * wordmark, titles, god and mode names, the Lore clue. Barlow takes
   * everything you read THROUGH — nav, chips, pills, labels, meta, captions,
   * buttons, footer. Barlow is a low-contrast grotesque with a tall x-height
   * that holds at 11px, it carries tabular figures, and it is OFL like
   * Grenze. Both ship in one font request. */
  --display: "Grenze", Georgia, serif;
  --label:   "Barlow", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* THE READING FACE — the Lore clue, and anything else that is a paragraph
     rather than a phrase.
     This pointed at Grenze on the argument that the serif is the content's own
     register. The argument was about identity and the paragraph's job is
     reading: Grenze is an incised display serif with a small x-height and
     narrow bowls, which is exactly the wrong instrument for the only sustained
     prose on the site. Identity is already carried by the god names, the mode
     titles and the wordmark, all of which stay Grenze. This is not a third
     typeface — it is the one already loaded, doing the job it is good at. */
  --ui:      var(--label);
  /* Labels and figures. Kept under the old name so the 19 call sites that
     mean "small functional text" did not all have to churn. */
  --mono:    var(--label);

  /* NOTE: a stray `--edge: 2px` used to live here, 66 lines below the real
     `--edge: var(--bronze)` and inside the same :root — so it won. Every
     `background: var(--edge)` resolved to the length `2px`, which is invalid
     for background, so the declaration was dropped and the rim painted
     nothing: chips, stat pills, buttons, rungs, the duel banner and both
     guess plates were chamfered but bare, with the backdrop showing through
     the gap where bronze should be. That is the Rimmed Plate Rule silently
     off across the whole site. It was almost certainly a typo for --edge-w,
     which is already declared above. */
  --gut:  clamp(18px, 4vw, 34px);

  /* Cut, not rounded. A long shallow shear on two opposite corners, deep
     enough to read as a machined plate rather than a soft rectangle. */
  /* Beveled on ALL FOUR corners, not sheared on two. A two-corner shear reads
     as a slanted tab — generic HUD furniture. An octagonal bevel is what a
     struck metal plate actually looks like, and it is the geometry every game
     panel uses. This is the single biggest change from the predecessor. */
  --cut: 14px;
}

/* --chamfer is declared on EVERY element, not just :root, and that is
   load-bearing.
   A custom property resolves its own var() references against the element it
   is declared on. With --chamfer living only on :root, its var(--cut) was
   substituted there -- against :root's 14px -- and every element inherited a
   polygon with 14 already baked into it. So every `--cut` override on this
   sheet was dead on arrival: the 7px chip, the 8px stat pill, the 10px input,
   the 11px pick, the 18px stage and the 20px modal all rendered an identical
   14px bevel. On the small controls that is a shear taking half the height,
   which is what read as the corners being wrong.
   Redeclaring it here means --cut is resolved per element, against whatever
   that element (or its nearest ancestor) actually set. */
*, *::before, *::after {
  --chamfer: polygon(
    var(--cut) 0, calc(100% - var(--cut)) 0,
    100% var(--cut), 100% calc(100% - var(--cut)),
    calc(100% - var(--cut)) 100%, var(--cut) 100%,
    0 calc(100% - var(--cut)), 0 var(--cut)
  );
}

/* ---- reset ------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

/* ---- no scrollbars, anywhere -------------------------------------------
   Scrolling still works — wheel, trackpad, touch, keyboard, and programmatic
   scrollIntoView are all untouched. Only the painted bar is gone, so the
   page and every inner scroller (nav strip, god list, modal body) keep their
   full width instead of donating 15px to a gutter. */
html, body, * {
  scrollbar-width: none;      /* Firefox */
  -ms-overflow-style: none;   /* legacy Edge/IE */
}
html::-webkit-scrollbar,
body::-webkit-scrollbar,
*::-webkit-scrollbar {        /* Chrome, Safari, Edge */
  width: 0;
  height: 0;
  display: none;
}
body {
  margin: 0;
  /* The interface default. Anything that is content rather than interface
     opts back into Grenze explicitly via --display (or --ui for the clue). */
  font-family: var(--label);
  font-size: 16px;
  line-height: 1.6;
  color: var(--bone);
  background: var(--ink);
  overflow-x: hidden;
  /* Short pages used to stop wherever their content ran out — the hub ended
     781px down a 922px screen, leaving 141px of dead page under the footer
     with the room's art still running behind it. The footer now rides the
     bottom of the viewport when there is slack, and behaves normally when the
     content is taller than the screen. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  /* Torchlight. Warm sources, because the frame is warm now too. */
  background-image:
    radial-gradient(1200px 680px at 82% -14%, rgba(230, 193, 132, 0.10), transparent 62%),
    radial-gradient(900px 560px at 4% 0%, rgba(207, 61, 34, 0.07), transparent 58%),
    linear-gradient(180deg, #150e0a 0%, var(--ink) 54%);
  background-attachment: fixed;
}

/* =========================================================================
 * THE ROOM.
 *
 * This reverses a documented Conquest decision, deliberately. That world
 * removed its ambience layer for three real reasons: a warm blur lifted the
 * page to within 1.15:1 of the board, ember sprites produced 36 of 51
 * detector findings, and the art was being spent as wallpaper.
 *
 * Two of those three were caused by the ember sprites and the film grain, not
 * by the backdrop, and neither returns. The third is the actual finding from
 * studying how a game site reads as its game: the art must be the ROOM, not
 * cargo inside tiles. So the backdrop comes back under a hard scrim, and the
 * value ladder is measured against the composite, never against the token.
 *
 * --bg-art is set per page. It is a FIXED god per mode, never the day's
 * answer and never rotating: a constant backdrop carries zero information
 * about the puzzle, which is exactly why it cannot leak one.
 * ====================================================================== */
body::before,
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
body::before {
  background-image: var(--bg-art, none);
  background-size: cover;
  /* Crop to the face, not the top of the card. A portrait card cropped at 14%
     lands on cloth and helmet; ~24% puts the god's head in the visible band,
     which is the part a player actually recognises. */
  background-position: center 24%;
  /* Card art is portrait and 15-46kB; at viewport width it upscales into
     mush. Blur converts that failure into depth of field, and the scale
     keeps the blur from sampling the page edge and banding it. */
  /* Identity comes from RECOGNITION. The previous values — saturate(0.42)
     brightness(1.38) blur(4px) — processed every card until no god could be
     named on any page, which defeated the whole point of putting the art in
     the room. Blur is now light enough to keep a silhouette and a face
     readable; the warm wash below still does the unifying work that heavy
     desaturation used to do. */
  filter: saturate(0.78) brightness(1.06) blur(1.5px);
  transform: scale(1.04);
}
/* The scrim is what makes the room safe to read on. It is measured against
   the composite, never against the token. */
body::after {
  background:
    linear-gradient(180deg,
      rgba(11, 8, 6, 0.60) 0%,
      rgba(11, 8, 6, 0.86) 55%,
      rgba(11, 8, 6, 0.97) 100%),
    /* One room. Without this, cold art (Anubis, Odin) drags its whole page
       back toward navy and the site stops reading as one place. */
    linear-gradient(0deg, rgba(122, 62, 22, 0.24), rgba(122, 62, 22, 0.24));
}

/* One fixed god per mode, six pantheons, 15-46kB each. Fixed is the whole
   safety argument: a constant backdrop is independent of the answer, so it
   cannot narrow it even on the day it happens to match. */
/* The hub is the exception. A single portrait card cropped to `cover` on a
   landscape viewport shows a zoomed fragment of cloth, not a place. The hub
   gets a roster wall instead: five cards laid side by side, which is what a
   player expects a god-select backdrop to look like. Mode pages keep one god
   each, because there the single figure IS the page's identity. */
.page-home { --bg-art: none; }
/* Three large figures with real negative space between them, not five smeared
   together. Five cards at viewport height left no gap wide enough for any one
   god to be read as a god. */
.page-home::before {
  background-image:
    url("../img/gods/card/zeus.jpg"),
    url("../img/gods/card/thor.jpg"),
    url("../img/gods/card/ra.jpg");
  background-size: auto 118%;
  background-repeat: no-repeat;
  background-position: 2% 22%, 50% 22%, 98% 22%;
}
.page-duel    { --bg-art: url("../img/gods/card/bellona.jpg"); }
.page-classic { --bg-art: url("../img/gods/card/anubis.jpg"); }
.page-lore    { --bg-art: url("../img/gods/card/odin.jpg"); }
.page-emoji   { --bg-art: url("../img/gods/card/loki.jpg"); }
.page-404     { --bg-art: url("../img/gods/card/cthulhu.jpg"); }

img { max-width: 100%; display: block; }
button, input { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

::selection { background: var(--blood); color: var(--bone); }

/* Bronze, not blood. The contract gives blood exactly one job — WRONG — and a
   focus ring wearing it reads as a verdict on whatever you just tabbed to.
   Bronze is the structural voice the contract already assigns to focus. */
:focus-visible {
  outline: 2px solid var(--bronze-hi);
  outline-offset: 2px;
}

/* ---- type ------------------------------------------------------------- */
h1, h2, h3 {
  font-family: var(--display);
  font-weight: 800;
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.015em;
  line-height: 1.04;
}

/* The homepage's title. It was a `div.eyebrow` in 11px --bone-faint: not a
   heading element at all, so the one page everyone lands on first offered a
   screen reader no structure — and a kicker set above nothing is a label
   doing a heading's job. It is an h1 now, and it carries its own weight. */
.rail-title {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(32px, 5vw, 58px);
  font-weight: 800;
  line-height: 0.98;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  color: var(--bone);
}

.accent  { color: var(--blood-text); }
.faint   { color: var(--bone-faint); }
.center  { text-align: center; }
.hidden  { display: none !important; }
.mt8  { margin-top: 8px; }
.mt16 { margin-top: 16px; }
.mt24 { margin-top: 24px; }

.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ---- layout ------------------------------------------------------------ */
.wrap {
  width: min(var(--wrap-max, 1180px), 100% - (var(--gut) * 2));
  margin-inline: auto;
}

/* Takes the slack so the footer sits on the bottom edge of a short page. */
main.wrap, main { flex: 1 0 auto; }

/* =========================================================================
 * NAV
 * ====================================================================== */
#sg-nav {
  position: sticky; top: 0; z-index: 60;
  background: color-mix(in srgb, var(--ink) 86%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom: 1px solid var(--rule);
}
.sg-nav-inner {
  width: min(1180px, 100% - (var(--gut) * 2));
  margin-inline: auto;
  height: 66px;
  display: flex; align-items: center; gap: 28px;
}
.sg-brand { display: flex; align-items: center; gap: 11px; flex-shrink: 0; min-height: 44px; }
/* Was a gold bolt inside a struck plate -- dark ground, bronze bevel, tight
   glow. The plate is gone now that the mark is the real logo: the bolt is cut
   clean through the S, so the mark already carries its own silhouette, and
   framing a framed mark reads as a sticker on a badge. The bevel was also
   bronze around an orange mark, which is two warms arguing at 34px.
   What survives is the glow, retinted to the logo's own colour so the mark
   still reads as charged rather than printed. */
.sg-brand-mark {
  width: 34px; height: 34px;
  display: block;
  filter: drop-shadow(0 0 9px rgba(255, 117, 31, 0.4));
}
.sg-brand-name {
  font-family: var(--display);
  font-size: 21px; font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--bone);
}
/* The wordmark was two-tone -- SMITE in --bone, ARCADE in --chrome -- because
   it was two words and the split named them. "SMITED" is one word, and a
   single letter dropped to a dimmer colour at 0.1em tracking reads as a letter
   falling off the end rather than as a mark with two halves. So it sets in one
   tone, and the `b` rule goes with the second word it was written for. */

.sg-navlinks { display: flex; gap: 4px; margin-left: auto; }
.sg-navlink {
  position: relative;
  display: inline-flex; align-items: center;
  min-height: 44px;
  padding: 0 14px;
  /* Grenze is a display serif — low x-height, wedge terminals — so at 12px
     with 0.14em tracking the nav read as texture rather than words. Bigger,
     heavier and tracked tighter: at small sizes wide tracking destroys the
     word-shape faster than it separates the letters. Idle colour also takes
     one step up the ramp; contrast was never the issue (7.6:1) but weight and
     spacing were doing the damage. */
  font-size: 13px; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--bone-dim);
  transition: color 0.2s;
}
.sg-navlink:hover { color: var(--bone); }
.sg-navlink::after {
  content: "";
  position: absolute; left: 14px; right: 14px; bottom: 2px;
  height: 2px; background: var(--chrome);
  transform: scaleX(0); transform-origin: left;
  transition: transform 0.28s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.sg-navlink:hover::after, .sg-navlink.active::after { transform: scaleX(1); }
.sg-navlink.active { color: var(--bone); }

@media (max-width: 620px) {
  .sg-nav-inner { height: auto; padding: 12px 0; flex-wrap: wrap; gap: 10px; }
  /* horizontally scrollable, with tail padding so the last link never sits
     flush against the edge and read as clipped */
  .sg-navlinks {
    margin-left: 0; width: 100%;
    overflow-x: auto; scrollbar-width: none;
    padding-right: 18px;
  }
  .sg-navlinks::-webkit-scrollbar { display: none; }
  .sg-navlink { padding: 0 10px; white-space: nowrap; }
}

/* =========================================================================
 * PORTRAITS — the load-bearing imagery
 * ====================================================================== */
.portrait {
  position: relative;
  flex-shrink: 0;
  width: var(--p, 56px); height: var(--p, 56px);
  border-radius: 50%;
  overflow: hidden;
  background: var(--stone-2);
  /* bronze ring, chased with an inner shadow so it sits IN the stone */
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--bone) 16%, transparent),
    0 0 0 3px var(--ink),
    0 0 0 4px color-mix(in srgb, var(--patina) 34%, transparent),
    inset 0 2px 6px rgba(0, 0, 0, 0.8);
}
.portrait img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(0.92) contrast(1.06);
}
.portrait.lg { --p: 104px; }
.portrait.sm { --p: 38px; }
.portrait.xs { --p: 28px; }

/* hexagonal variant for hero moments */
.portrait.hex {
  border-radius: 0;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  box-shadow: none;
  background: none;
}

/* ---- the veiled answer plate ------------------------------------------- *
 * The god the player is hunting, on screen from the first frame, blurred and
 * darkened. It resolves one step per miss. This is what stops the daily modes
 * opening on an empty rectangle.
 * ----------------------------------------------------------------------- */
.god-plate {
  position: relative;
  display: grid; place-items: center;
  padding: 4px;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  /* the bronze rim, as a backing hex rather than a ring — box-shadow cannot
     follow a clip-path */
  background: linear-gradient(158deg, color-mix(in srgb, var(--patina) 90%, transparent), color-mix(in srgb, var(--patina) 16%, transparent) 58%, color-mix(in srgb, var(--bronze) 60%, transparent));
}
.portrait.plate {
  --p: clamp(88px, 18vw, 116px);
  border-radius: 0;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  box-shadow: none;
  background: var(--ink-2);
}
/* The reveal is the payoff of every daily mode, so it eases rather than cuts:
   a long, soft settle on the blur, with the scale and the colour coming back
   on the same curve. */
.portrait.plate img {
  transition: filter 900ms cubic-bezier(0.22, 0.7, 0.24, 1),
              transform 900ms cubic-bezier(0.22, 0.7, 0.24, 1);
}
/* Scaled up because a blur samples past the element's edge: at the widest step
   it would otherwise pull the page background in around the hex. The overscale
   is tied to --veil-t rather than pinned at 1.32, so the portrait drifts back
   to its true size as it sharpens -- the god settling into focus instead of
   snapping from one fixed crop to another. */
/* Contrast goes UP with the veil, not down. A 22px blur on a 116px plate has
   already destroyed every edge, so the only thing left to hold a shape is the
   difference between the god's light and dark masses -- push that and you get
   a silhouette behind frosted glass; let it fall and you get a smudge. */
.portrait.plate.veil img {
  filter:
    saturate(calc(1 - 0.12 * var(--veil-t, 1)))
    contrast(calc(1 + 0.34 * var(--veil-t, 1)))
    brightness(calc(1 - 0.14 * var(--veil-t, 1)))
    blur(var(--veil, 0px));
  transform: scale(calc(1.02 + 0.3 * var(--veil-t, 1)));
}
/* An ember glow behind the plate, so the veiled state reads as "still in the
   forge" rather than as a failed image load. */
.god-plate::after {
  content: "";
  position: absolute; inset: -18px;
  z-index: -1;
  background: radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--patina) 42%, transparent), transparent 70%);
  pointer-events: none;
}
/* The veil used to be 2px scan bands repeating down the plate. They were there
   to stop a fully-blurred portrait reading as a broken image, but they read as
   a damaged screen instead: hard horizontal lines that do not belong to the
   artwork, do not move, and survive right up to the reveal.
   What replaces them is depth of field. A soft vignette pulls the edges down
   so the centre carries the eye, and every layer is keyed to --veil-t, so the
   whole treatment lifts in step with the blur instead of switching off. */
.portrait.plate.veil::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 2;
  background: radial-gradient(ellipse 76% 64% at 50% 40%,
    transparent 0%,
    color-mix(in srgb, var(--ink) 34%, transparent) 52%,
    color-mix(in srgb, var(--ink) 88%, transparent) 100%);
  opacity: var(--veil-t, 1);
  transition: opacity 900ms cubic-bezier(0.22, 0.7, 0.24, 1);
  pointer-events: none;
}
/* A cold rim light around the inside of the hex, so the plate reads as a lit
   object with something behind it rather than as a flat fill.
   This was a centre bloom on `screen` at first, which lifted the blurred
   midtones straight into a pale olive wash -- the exact "failed image load"
   the veil exists to avoid. Keeping the light at the EDGE leaves the middle
   dark, which is where the silhouette lives. */
.portrait.plate.veil::before {
  content: "";
  position: absolute; inset: 0;
  z-index: 3;
  background: radial-gradient(ellipse 88% 80% at 50% 42%,
    transparent 62%,
    color-mix(in srgb, var(--patina) 26%, transparent) 100%);
  opacity: var(--veil-t, 1);
  transition: opacity 900ms cubic-bezier(0.22, 0.7, 0.24, 1);
  pointer-events: none;
}
/* The etched rim fades out with everything else rather than vanishing on the
   frame the class is dropped. */
.portrait.plate.veil {
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--patina) calc(50% * var(--veil-t, 1)), transparent),
    inset 0 0 26px color-mix(in srgb, var(--patina) calc(16% * var(--veil-t, 1)), transparent);
  transition: box-shadow 900ms cubic-bezier(0.22, 0.7, 0.24, 1);
}
/* .god-stage's plate now comes from the shared tablet rule further down,
   with the three dailies. Its own tuning lives beside that rule so the
   cascade order is obvious rather than accidental. */
/* Attempts remaining, promoted out of the mode-bar subtitle. In a guessing
   game this is the status that matters most, and it was the faintest text on
   the page. */
.stage-cap {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bone-dim);
}
.stage-cap b { color: var(--bone); font-weight: 600; }
.stage-cap.spent b { color: var(--blood-text); }

/* Screen-reader-only. The veil is a purely visual clue, so its text
   equivalent lives here and is announced at the same steps. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* card art used as atmosphere — blurred hard enough that 180px reads as texture */
.artwash {
  position: absolute; inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.artwash img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 20%;
  /* Was blur(30px) at 0.72 under an 0.86 scrim — 640px of brown smear with a
     112px icon on top, i.e. splash art used as wallpaper, the practice this
     world's own header says it retired. The god IS the panel now. */
  filter: saturate(1.02) contrast(1.02);
  transform: scale(1.02);
  opacity: 1;
}
.artwash::after {
  content: "";
  position: absolute; inset: 0;
  /* Bottom-weighted: it carries the text band and nothing else, so the art
     stays legible in the top two-thirds. */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ink) 12%, transparent) 0%, color-mix(in srgb, var(--ink) 34%, transparent) 38%,
    color-mix(in srgb, var(--ink) 86%, transparent) 74%, color-mix(in srgb, var(--ink) 96%, transparent) 100%);
}

/* =========================================================================
 * HUB — a god-select rail. The modes ARE the portraits; there is no card,
 * no panel, no box. First thing on the page, above the fold.
 * ====================================================================== */
/* The hub earns a wider column than the game pages. Its content is five
   parallel things read at a glance, not a single board to focus on, and at
   1180px every clamp on this page had already hit its ceiling by ~1100px of
   viewport — so a 1920 monitor rendered the rail at exactly laptop size with
   740px of empty margin around it.

   Fluid rather than a flat bump: a flat 1340 left only ~50px of margin at
   1440 and read as cramped. This tracks the viewport instead — ~1190 on a
   laptop, the full 1340 on a 1920 monitor. */
.page-home { --wrap-max: clamp(1180px, 62vw + 300px, 1340px); }

.rail-head {
  display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
  padding: clamp(26px, 5vw, 46px) 0 18px;
}
/* The hub's meta line lands over the roster wall, where the backdrop is at
   its brightest. --bone-faint measured 3.88:1 there, so this one line takes
   the next step up the ramp rather than the whole ramp moving for it. */
.rail-meta {
  display: flex; align-items: center; gap: 12px;
  margin-left: auto;
  font-family: var(--mono); font-size: 13px;
  color: var(--bone-dim);
}
.rail-meta b { color: var(--bone); letter-spacing: 0.02em; }
.rail-meta .sep { width: 1px; height: 12px; background: var(--rule-hard); }

.rail {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(12px, 2vw, 26px);
  /* stretch, so a two-line description in one column can't knock that
     column's status chip out of line with the rest of the rail */
  align-items: stretch;
}

.mode {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  height: 100%;
  transition: transform 0.36s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.mode:hover { transform: translateY(-8px); }

/* the hex plate — god art cropped into a SMITE-style selection tile */
.mode-hex {
  position: relative;
  width: min(100%, 212px);
  aspect-ratio: 1 / 1.1;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  background: var(--stone);
  overflow: hidden;
}
.mode-hex img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 16%;
  /* Was brightness(0.86) under an 0.85-opacity accent wash: the frame was
     painting the art its own temperature — the exact inversion of this
     world's thesis. The art runs at full strength now. */
  filter: saturate(1.04) contrast(1.03);
  transform: scale(1.02);
  transition: filter 0.36s, transform 0.5s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.mode:hover .mode-hex img {
  filter: saturate(1.16) contrast(1.05) brightness(1.08);
  transform: scale(1.09);
}
/* accent wash, plus a floor at the bottom vertex for the icon badge to sit on */
.mode-hex::before {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background:
    /* Only the floor the icon badge sits on. The accent wash that used to
       tint the whole plate is gone — chrome does not colour the gods. */
    linear-gradient(180deg, transparent 54%, color-mix(in srgb, var(--ink) 90%, transparent) 97%);
  opacity: 1;
  transition: opacity 0.36s;
}
.mode:hover .mode-hex::before { opacity: 0.86; }
/* the accent rim, drawn as a clipped inset ring */
.mode-hex::after {
  content: "";
  position: absolute; inset: 0; z-index: 2;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bone) 16%, transparent);
  transition: box-shadow 0.36s;
}
.mode:hover .mode-hex::after {
  box-shadow: inset 0 0 0 2px var(--accent), inset 0 0 34px -6px var(--accent);
}

/* Mode glyph as a small seal riding the hex's bottom vertex — keeps the
   character art clear instead of stamping a UI icon over their face. */
.mode-icon {
  position: relative; z-index: 3;
  width: 46px; height: 46px;
  margin-top: -23px;
  display: grid; place-items: center;
  color: var(--bone);
  background: linear-gradient(160deg,
              color-mix(in srgb, var(--accent) 80%, var(--shade-hi)),
              color-mix(in srgb, var(--accent) 38%, var(--shade-lo)));
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  transition: transform 0.36s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.mode-icon svg { width: 22px; height: 22px; }
.mode:hover .mode-icon { transform: rotate(-10deg) scale(1.12); }

.mode-name {
  font-family: var(--display);
  font-size: clamp(21px, 2.4vw, 32px);
  font-weight: 800;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  margin-top: 16px;
  transition: color 0.3s;
}
.mode:hover .mode-name { color: var(--accent); }
.mode-desc {
  margin: 8px 0 0;
  font-size: 15px;
  line-height: 1.45;
  color: var(--bone-dim);
  text-wrap: balance;
}
.mode-foot { margin-top: auto; padding-top: 16px; }
/* The rail is read from across the room; its status chip is not a
   dense-table label and does not need the 11px floor. */
.mode-foot .chip {
  /* Was 12px/400 — a light weight of a display serif at label size, which is
     the hardest combination this typeface can be asked to do. */
  font-size: 13px; font-weight: 700; letter-spacing: 0.06em;
  padding: 7px 16px 7px 20px;
}

.mode.soon { opacity: 0.42; pointer-events: none; }
.mode.soon .mode-hex img { filter: grayscale(0.8) brightness(0.5); }

@media (max-width: 900px) { .rail { grid-template-columns: repeat(3, 1fr); row-gap: 34px; } }
@media (max-width: 560px) { .rail { grid-template-columns: repeat(2, 1fr); row-gap: 28px; } }


/* ---- chips & badges ---------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: 7px;
  /* Left padding clears the shear: at 12px the cut was slicing the first
     letter ("No score yet" rendering as "\o score yet"). */
  padding: 6px 14px 6px 18px;
  --plate-fill: linear-gradient(180deg, color-mix(in srgb, var(--ink) 62%, transparent),
                            color-mix(in srgb, var(--ink) 80%, transparent));
  --cut: 7px;
  clip-path: var(--chamfer);
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--bone-faint);
  white-space: nowrap;
}
.chip.on {
  color: var(--ember);
  border-color: color-mix(in srgb, var(--patina) 40%, transparent);
  background: color-mix(in srgb, var(--patina) 10%, transparent);
}
.chip.cool {
  color: var(--verdigris);
  border-color: color-mix(in srgb, var(--chrome) 40%, transparent);
  background: color-mix(in srgb, var(--chrome) 9%, transparent);
}
.badge {
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--bone-faint);
}

/* =========================================================================
 * GAME PAGES
 * ====================================================================== */
.game { padding-bottom: 80px; }

/* =========================================================================
 * MODE BAR — the header every game page wears.
 * Same shape as the home rail's header (identity left, live stats right) and
 * the same seal + accent the mode has on the rail, so a game page reads as
 * the same site rather than a separate one with its own hero.
 * ====================================================================== */
.mode-bar {
  --accent: var(--blood);
  display: flex; align-items: center; gap: 16px;
  position: relative;
  padding: clamp(14px, 2.4vw, 20px) 0 12px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--rule);
}
/* The mode bar's own ground. Measured against the rendered composite, the
   Classic heading fell to 2.55:1 because the bar happens to sit over the
   brightest part of Anubis's head — the accent was fine against the token
   and failed against the page. A per-mode accent must never depend on what
   the backdrop is doing behind it, so the bar carries its own plate that
   bleeds past the column edge. */
.mode-bar::before {
  content: "";
  position: absolute;
  inset: 0 calc(var(--gut) * -1) 0;
  z-index: -1;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ink) 92%, transparent) 0%,
    color-mix(in srgb, var(--ink) 74%, transparent) 100%);
}
.mode-seal {
  flex-shrink: 0;
  width: 46px; height: 46px;
  display: grid; place-items: center;
  color: var(--bone);
  background: linear-gradient(160deg,
              color-mix(in srgb, var(--accent) 80%, var(--shade-hi)),
              color-mix(in srgb, var(--accent) 38%, var(--shade-lo)));
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
.mode-seal svg { width: 22px; height: 22px; }

.mode-id { min-width: 0; }
.mode-id h1 {
  font-size: clamp(24px, 3.4vw, 34px);
  line-height: 1.05;
  color: var(--accent);
}
.mode-id p {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--bone-dim);
}
.mode-bar .statbar { margin: 0 0 0 auto; flex-shrink: 0; }

@media (max-width: 700px) {
  .mode-bar { flex-wrap: wrap; gap: 12px; }
  .mode-bar .statbar { margin-left: 0; width: 100%; }
}

/* the accent follows the mode onto its own page */
/* This used to repaint .btn-gold in the per-mode --accent with #14100c text,
   silently overriding the ember remedy: 404.html measured 3.34:1, failing AA
   on the primary action. The accent belongs on the rule above the button, not
   in the button's own fill. */
.game .btn-gold {
  box-shadow: inset 0 2px 0 var(--accent), var(--engrave);
}
/* The per-mode accent on the focused field, moved to the plate for the same
   geometry reason as the base rule. */
.game .guess-row:focus-within {
  background: color-mix(in srgb, var(--accent) 80%, var(--edge));
}

.statbar { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 15px; }
.stat-pill {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 14px;
  --plate-fill: linear-gradient(180deg, var(--stone-2), var(--stone));
  --cut: 8px;
  clip-path: var(--chamfer);
  box-shadow: var(--engrave);
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--bone-faint);
}
.stat-pill b {
  font-family: var(--mono); font-size: 14px;
  letter-spacing: 0;
  color: var(--bone);
}
.stat-pill.hot b { color: var(--ember); }

/* ---- buttons ----------------------------------------------------------- */
/* Also worn by <a> — the modal's primary action is a link to the next mode,
   so this needs the properties a button gets from the UA sheet for free. */
.btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;
  padding: 12px 24px;
  --cut: 10px;
  clip-path: var(--chamfer);
  background: var(--stone-2);
  box-shadow: var(--engrave);
  font-family: inherit;
  font-size: 12px; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase;
  text-align: center; text-decoration: none;
  color: var(--bone);
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, transform 0.12s, color 0.2s;
}
.btn:hover { background: var(--stone-3); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.36; cursor: not-allowed; }
.btn-gold {
  /* Was --blood -> --blood-lo with near-black text: 4.00:1 falling to
     2.21:1, failing AA across the whole button — and it is the primary
     action on every end screen. The ember range keeps the forge heat and
     clears 4.5:1 at both stops. */
  /* A cast plate: bright bronze face inside a dark bronze rim, so the button
     reads as a struck object rather than a coloured rectangle. */
  --plate-fill: linear-gradient(160deg, var(--chrome), var(--chrome-lo));
  color: var(--on-hit);
  box-shadow: 0 12px 30px -16px var(--chrome), var(--engrave);
}
.btn-gold:hover {
  --plate-fill: linear-gradient(160deg, var(--chrome), var(--chrome));
}
.btn-block { display: flex; width: 100%; }

/* "Give up" was the loudest control on a page where nothing had happened yet —
   the first thing a new player saw was an invitation to quit. Demoted to a
   quiet link below the board, but kept at a 44px touch height. */
.linkbtn {
  display: inline-flex; align-items: center;
  min-height: 44px;
  padding: 0 14px;
  background: none; border: 0;
  font-family: var(--mono); font-size: 12px;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--bone-faint);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-color: var(--rule-hard);
}
.linkbtn:hover { color: var(--blood-text); text-decoration-color: currentColor; }
.linkbtn:disabled { opacity: 0.36; cursor: not-allowed; }
/* Armed: the click after this one ends the day.

   It stays a link. An earlier pass gave the armed state a rimmed plate, and
   that was wrong for a reason worth recording: the control changed *category*
   mid-interaction. A quiet link that becomes a button is not one control in
   two states, it is two objects in one slot — the eye reads it as something
   new arriving rather than as the thing under the cursor changing its mind.
   The resting link is also deliberately demoted (see above), and a state that
   promotes it to the loudest control on the page throws that away at the exact
   moment restraint matters most.

   So the change is carried by the two things that cost no geometry: the label
   becomes a question, and the colour goes to --lost-text with a solid rule
   under it. Measured 6.6:1 on the page. Nothing moves, nothing grows. */
.linkbtn.armed {
  color: var(--lost-text);
  text-decoration-color: currentColor;
  text-decoration-thickness: 2px;
}

/* What the next press costs, present only while the control is armed. It sets
   below the question rather than beside it so the line the eye lands on first
   is still the decision, and it carries its own ground because it appears over
   the backdrop art (the Own-Ground Rule). --bone-dim, not the alarm colour:
   the question is the warning, and a second red string beside it would leave
   the sentence with no quiet half to read. */
.arm-cost {
  display: block;
  width: fit-content;
  margin: 6px auto 0;
  padding: 5px 12px;
  --cut: 6px;
  clip-path: var(--chamfer);
  background: var(--ink);
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--bone-dim);
  animation: arm-cost-in 0.18s ease-out both;
}
@keyframes arm-cost-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .arm-cost { animation: none; }
}

/* The already-played-today state. Status, so by the Rim Is Emphasis Rule it
   takes no rim — it reports, it does not ask to be acted on. It carries its
   own ground because it sits over the backdrop art, and the countdown takes
   tabular figures because it ticks in place. */
.done-banner {
  width: fit-content;
  max-width: 100%;
  margin: 22px auto 0;
  padding: 16px 28px;
  --cut: 12px;
  clip-path: var(--chamfer);
  background: linear-gradient(180deg, var(--stone-2), var(--stone));
  box-shadow: var(--engrave);
  text-align: center;
}
.done-outcome {
  margin: 0;
  font-size: 17px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--bone);
}
.done-meta {
  margin: 5px 0 0;
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--bone-faint);
}
.done-meta b {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--bone-dim);
}
/* The modal is still available, but as the quietest thing on the plate --
   it is a second look, not the thing you came back for. */
.done-banner .linkbtn { min-height: 34px; margin-top: 2px; padding: 0; }
.giveup-line { margin-top: 6px; }

/* ---- guess box + autocomplete ------------------------------------------ */
/* The stage's bottom edge and the field's top edge sat 7px apart, so the
   answer plate and the thing you type into read as one welded object. */
.guessbox { position: relative; max-width: 620px; margin: 26px auto 18px; }
.guess-row { display: flex; gap: 10px; }
/* The field itself carries no shape at all now -- no clip, no rim, no fill.
   Its wrapper is the plate (see `.guess-row` under THE FORGED PLATE), which is
   what stopped the chamfer from slicing a rectangular ring into wedges at all
   four corners. Padding accounts for the wrapper's 3px rim. */
.guess-input {
  flex: 1; min-width: 0;
  padding: 11px 16px;
  background: transparent;
  border: 0;
  box-shadow: none;
  font-size: 15px; font-weight: 600;
}
.guess-input::placeholder { color: var(--bone-faint); font-weight: 400; }
.guess-input:focus { outline: none; }
/* Focus is expressed by the plate, so the ring follows the octagon instead of
   being cut by it. Ember, not blood — a full-saturation red ring reads as a
   validation error. */
.guess-row:focus-within { background: color-mix(in srgb, var(--patina) 70%, var(--edge)); }
.guess-row:focus-within::before {
  background: var(--ink-2);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6);
}

/* Same plate as everything else, for the same reason: this carried a
   `inset 0 0 0 1px` rim that was immediately overwritten by the `--lift`
   shadow on the next line, so the list had no edge at all -- and where the
   chamfer cut across the first and last rows it sliced through their hover
   fill and their hairline. The element is the rim; the scroller is inset. */
.ac-list {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px);
  z-index: 40;
  --cut: 12px;
  clip-path: var(--chamfer);
  background: var(--edge);
  padding: var(--edge-w);
  box-shadow: var(--lift);
}
.ac-scroll {
  max-height: 340px; overflow-y: auto;
  clip-path: var(--chamfer);
  background: var(--ink-2);
}
.ac-item {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px;
  cursor: pointer;
  transition: background 0.14s;
}
/* The hairline stops short of the bevels, so it never runs off a cut corner. */
.ac-item + .ac-item { box-shadow: inset 0 1px 0 var(--rule-soft); }
/* First and last rows sit inside the chamfer, so their fill is not sheared. */
.ac-item:first-child { padding-top: 12px; }
.ac-item:last-child { padding-bottom: 12px; }
.ac-item:hover, .ac-item.active { background: color-mix(in srgb, var(--patina) 14%, transparent); }
.ac-item .nm { font-weight: 700; font-size: 14px; }
.ac-item .pan {
  margin-left: auto;
  font-family: var(--mono); font-size: 11px;
  color: var(--bone-faint);
}

/* =========================================================================
 * HIGHER / LOWER — two idols facing off
 * ====================================================================== */
/* The question, called out above the board — it changes every round, so it
   has to be the loudest thing on the page. */
.duel-question {
  position: relative;
  margin: 0 auto 18px;
  padding: 14px 26px 16px;
  max-width: 720px;
  text-align: center;
  color: var(--bone);
  --cut: 12px;
  clip-path: var(--chamfer);
  /* The question is the system speaking, not a verdict — cyan, not blood. */
  --plate-fill: linear-gradient(120deg, var(--chrome-lo), transparent 58%), linear-gradient(0deg, var(--rule-hard), var(--rule-hard));
}
.duel-ask {
  display: block;
  font-family: var(--display);
  font-size: clamp(25px, 3.4vw, 34px);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.012em;
  line-height: 1.1;
}

.duel-tier {
  display: inline-block;
  margin-bottom: 7px;
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--bone-dim);
}
.duel-tier.t3 { color: var(--ember); }
.duel-tier.t4 { color: var(--blood-text); }

/* =========================================================================
 * THE LADDER — 3 Easy, 3 Medium, 3 Hard, 3 Extreme. Gives the run a shape
 * you can aim at instead of an undifferentiated streak.
 * ====================================================================== */
.ladder {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  max-width: 620px;
  margin: 0 auto 10px;
}
/* A rung is PROGRESS, not an action.
 *
 * These were full bronze-rimmed plates sitting directly above the question —
 * 51px of chrome against the question's 67px, so the two competed and the eye
 * could not tell which one was the task. The rim and the plate ground are
 * gone; only the rung you are standing on carries any fill at all. */
.rung {
  --rung: var(--bone-faint);
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 5px 6px 6px;
  --cut: 7px;
  clip-path: var(--chamfer);
  background: transparent;
  transition: background 0.35s, color 0.35s;
}
.rung.t1 { --rung: var(--verdigris); }
.rung.t2 { --rung: var(--steel); }
.rung.t3 { --rung: var(--gold); }
.rung.t4 { --rung: var(--blood-text); }

.rung-name {
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--bone-faint);
  transition: color 0.35s;
}
.rung-pips { display: flex; gap: 5px; }
.rung-pips .pip {
  width: 7px; height: 7px;
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  background: color-mix(in srgb, var(--bone) 18%, transparent);
  transition: background 0.35s, transform 0.35s;
}
.rung-pips .pip.on { background: var(--rung); }

.rung.active {
  background: color-mix(in srgb, var(--rung) 14%, transparent);
}
.rung.active .rung-name { color: var(--rung); }
.rung.active .rung-pips .pip.on { transform: scale(1.15); }
.rung.done .rung-name { color: var(--bone-dim); }
/* Was opacity 0.5, which multiplied the label's contrast by a half and took
   the unplayed rungs to 2.68:1. Dim the plate, never the type. */
.rung.todo { opacity: 1; }
.rung.todo .rung-name { color: var(--bone-faint); }
.rung.todo .rung-pips { opacity: 0.72; }
/* cleared the gauntlet and still going — the last rung keeps burning */
.rung.endless {
  background: color-mix(in srgb, var(--rung) 22%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--rung) 60%, transparent);
}
.rung.endless .rung-name { color: var(--rung); }

@media (max-width: 560px) {
  .ladder { gap: 5px; }
  .rung { padding: 7px 3px 8px; }
  .rung-name { font-size: 11px; letter-spacing: 0.06em; }
  .rung-pips .pip { width: 6px; height: 6px; }
}

/* the god's epithet, under the name */
.hl-side .god-title {
  margin-top: -6px;
  font-size: 11px;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--bone-faint);
}
/* =========================================================================
 * THE FORGED PLATE
 *
 * One rim system for every container and control. The element itself IS the
 * rim — a chamfered plate of solid bronze — and a pseudo-element inset by
 * --edge-w carries the fill, chamfered to the same geometry.
 *
 * Both shapes are octagons, which is the whole point. The previous attempt
 * layered RECTANGLE fills inside an octagon plate, so each of the four bevels
 * sliced through them and the rim swelled into a wedge at every corner. That
 * is what read as "the borders look weird", and it is a geometry bug, not a
 * colour one.
 *
 * <input> cannot carry a pseudo-element, so it used to wear an inset ring --
 * and got the exact wedge-at-every-corner bug described above, because an
 * inset ring is a RECTANGLE that the octagon then slices through. The plate
 * moved out to its `.guess-row` wrapper, which is a div and can do it
 * properly; the input inside is now unclipped and unrimmed. See `.guess-row`.
 * ====================================================================== */
.btn-gold,
.nextup-item,
.stat-pill,
.chip,
.duel-question {
  position: relative;
  isolation: isolate;
  background: var(--edge);
}
.btn-gold::before,
.nextup-item::before,
.stat-pill::before,
.chip::before,
.duel-question::before {
  content: "";
  position: absolute;
  inset: var(--edge-w);
  z-index: -1;
  clip-path: var(--chamfer);
  background: var(--plate-fill, linear-gradient(180deg, var(--stone-2), var(--stone)));
}

/* Primary actions and the question banner catch the light. */
.btn-gold { background: var(--edge-hi); box-shadow: var(--godlight); }
/* Gold rim, dark face. Its fill was mostly transparent, so a bright rim came
   straight through and put white type on gold at 1.58:1. */
.duel-question {
  background: var(--edge-hi);
  --plate-fill:
    linear-gradient(150deg, color-mix(in srgb, var(--gold) 22%, transparent), transparent 62%),
    linear-gradient(0deg, var(--ink-2), var(--ink-2));
  box-shadow: var(--godlight);
}

/* The input's plate, worn by the wrapper so it can be a real octagon. */
.guess-row {
  position: relative;
  isolation: isolate;
  --cut: 10px;
  clip-path: var(--chamfer);
  background: var(--edge);
  transition: background 0.2s;
}
.guess-row::before {
  content: "";
  position: absolute;
  inset: var(--edge-w);
  z-index: -1;
  clip-path: var(--chamfer);
  background: var(--plate-fill, linear-gradient(180deg, var(--ink), var(--ink-2)));
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6);
}

/* =========================================================================
 * THE ROSTER WALL  (Classic's board)
 *
 * Replaced a 7x8 spreadsheet grid. Three reasons, in order of weight:
 *  1. The product's best asset is 134 portraits with per-god crops, and the
 *     grid rendered them at 34px. Recognition is the mechanic these players
 *     actually have; a name in a cell does not use it.
 *  2. The grid needed a 760px minimum, so on a phone it scrolled sideways —
 *     two gestures per guess on the platform this product primarily lives on.
 *     Chips wrap instead.
 *  3. Every chip carries its own key ("PANTHEON GREEK"), so the verdict
 *     survives without a colour legend, and without colour at all.
 * ====================================================================== */
/* The legend explains the roster, so it lines up with the roster — it was
   still spanning the full 1180 column after the rows were bounded to 940,
   which left it hanging off their left edge. */
.legend-line {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px;
  max-width: 1040px;
  margin: 0 auto 12px;
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--bone-dim);
}
.legend-line .sw {
  display: inline-block; width: 13px; height: 13px;
  margin-right: 7px; vertical-align: -2px;
  --cut: 4px; clip-path: var(--chamfer);
}
.legend-line .sw-hit   { background: var(--exact); }
.legend-line .sw-near  { background: var(--close); }
.legend-line .sw-wrong { background: var(--wrong); }

/* Classic's board and the dailies' logs share a width as well as a shape.
   Classic ran the full 1180 column while its six chips ended at 868, so 312px
   of every row was slack — the width was never content-driven, just unbounded,
   and it was the last thing making Classic read as a different page. */
.roster { display: flex; flex-direction: column; gap: 10px; }
/* 940 was set when the chips carried a "YEAR" caption and still wrapped; six
   verdicts measured 821-850px against 835px of room, so a long pantheon tipped
   the row onto a second line and the guess stopped being one object. */
.roster, .guess-log { max-width: 1040px; margin-inline: auto; }

/* ONE guess row, shared by Classic's board and the dailies' logs.
   These were two rules that had drifted to different cuts (13 vs 11) and
   different name sizes (21 vs 19) while being, to the player, the same object:
   a god you guessed, on a plate. Only the column layout differs — the logs add
   a third column for the verdict mark. */
.pick,
.log-row {
  position: relative; isolation: isolate;
  display: grid; grid-template-columns: 64px 1fr; gap: 15px;
  align-items: center;
  padding: 10px 16px 10px 10px;
  background: var(--edge);
  --cut: 11px;
  clip-path: var(--chamfer);
}
.pick::before,
.log-row::before {
  content: ""; position: absolute; inset: var(--edge-w); z-index: -1;
  clip-path: var(--chamfer);
  background: linear-gradient(100deg, var(--stone-2), var(--ink) 78%);
}
/* The wrapper must establish a formatting context: .portrait is an inline
   span sized from --p, so inside another inline span it collapses to nothing. */
.pick-art { display: grid; place-items: center; }
.pick .portrait,
.log-row .portrait { --p: 64px; }

.pick-name {
  font-family: var(--display); font-weight: 800; font-size: 21px;
  text-transform: uppercase; letter-spacing: 0.01em;
  line-height: 1; color: var(--bone);
}
/* One row, one line — above the width where that is actually possible.
   The widest row in the roster is Cthulhu (PANTHEON GREAT OLD ONES · CLASS
   GUARDIAN · DAMAGE MAGIC · RANGE MELEE · DIFFICULTY AVERAGE · ↑YYYY) at 806px,
   so the board needs ~830px of chip room before `nowrap` is safe. Below that it
   must wrap: held at nowrap it pushed the chips straight out of their own plate
   and put 28px of horizontal scroll on the document at 901px. */
.pick-traits { display: flex; flex-wrap: nowrap; gap: 5px; margin-top: 9px; }
@media (max-width: 1040px) { .pick-traits { flex-wrap: wrap; gap: 6px; } }

/* A verdict chip states its own field, so the encoding is never colour-only.
   That is a WCAG 1.4.1 requirement and it also removes the need for a legend. */
/* Sizing note: this was 11px/700 at 0.14em with the field name at 0.62 opacity
   -- the smallest, widest-tracked, faintest text on the site, carrying the one
   thing a player is actually reading. Six of them per row, scanned in a
   fraction of a second. The value now sets at 14px and the field name at 11px,
   with the tracking pulled in far enough that the letters stop drifting apart
   at speed. */
.trait {
  position: relative; isolation: isolate;
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 7px 11px;
  font-size: 14px; font-weight: 800;
  letter-spacing: 0.03em; text-transform: uppercase;
  white-space: nowrap;
  --cut: 7px; clip-path: var(--chamfer);
  /* A miss is GREY. It used to be bronze — the structural metal — which made
     the commonest verdict on the board look like chrome and left "wrong" as
     the only state with no colour of its own. */
  background: var(--wrong);
  color: var(--wrong-text);
}
.trait::before {
  content: ""; position: absolute; inset: 2px; z-index: -1;
  clip-path: var(--chamfer);
  background: linear-gradient(180deg, var(--wrong-lo), var(--ink));
}
/* The field name is a caption for the value, so it sets smaller and quieter --
   but 0.62 opacity on --bone-faint took it under 3:1 against the plate, which
   is where "what does this chip even say" starts. */
/* The field name is a caption for the value, so it steps down — but by COLOUR,
   not by opacity. At `opacity: 0.8` it measured 4.27:1 against the chip's own
   fill, under AA for 11px text, and an opacity on a text container cannot be
   audited from the stylesheet at all (see The Dim The Plate Rule). Each state
   names its own caption colour and each one is measured. */
.trait .k { font-size: 11px; font-weight: 700; letter-spacing: 0.07em; color: var(--key-wrong); }
.trait b { font-weight: 800; }
.trait.hit  { background: var(--exact); color: var(--on-hit); }
/* A shallow gradient, not a full ramp to --exact-lo. Run to the foot colour it
   dropped the fill under the dark type to 3.55:1 against the RENDERED pixels --
   the token passed at 7.7:1 and the composite did not, which is exactly what
   The Measured Composite Rule exists to catch. The fill now stays inside the
   band where dark text clears AA everywhere in the chip. */
.trait.hit::before  { background: linear-gradient(165deg, var(--exact), color-mix(in srgb, var(--exact) 82%, var(--exact-lo))); }
.trait.hit .k { color: var(--key-exact); }
.trait.near { background: var(--close); color: var(--on-near); }
.trait.near::before { background: linear-gradient(165deg, var(--close), color-mix(in srgb, var(--close) 82%, var(--close-lo))); }
.trait.near .k { color: var(--key-close); }

/* A chip that states a FACT, not a verdict. Lore and Emoji print the guessed
   god's pantheon as context — nothing is being judged — but they used the bare
   .trait, whose default IS the grey "wrong". A player who learned the code in
   Classic read every Lore row as "your pantheon was wrong too".
   It cannot take .hit instead: in these modes the answer's pantheon is not
   known to the player, and colouring a match would answer the question the
   panel is asking. So it steps out of the verdict set entirely and sits on
   stone. --bone-faint on --stone-2 measures 6.29:1, clear of AA at 11px. */
.trait.info { background: var(--stone-3); color: var(--bone); }
.trait.info::before { background: linear-gradient(180deg, var(--stone-2), var(--ink)); }
.trait.info .k { color: var(--bone-faint); }

/* No data, which is not the same as a miss. Four gods carry no difficulty and
   no release year, so the chip prints an em-dash — and the value steps back to
   the caption's own colour, because "—" is the absence of a value and should
   not set at the weight of one. Measured 5.58:1, clear of AA at 14px. */
.trait.unknown b { color: var(--key-wrong); }

/* The year arrow says which way the answer lies -- the only directional clue
   in the game -- and it was riding inside the value at 11px. It now sets
   larger than the number it qualifies, and leans into the direction it means. */
.trait .ar {
  font-style: normal;
  /* 700, not 800: Barlow only ships 400/500/600/700, so the old 800 was being
     clamped and the arrow was already as heavy as the weight axis could make
     it. Stroking the outline is the only lever left, and it is the right one —
     this glyph is a symbol, not a letter, so thickening it costs no legibility.
     `paint-order` keeps the fill over the stroke so the stem stays clean. */
  font-size: 21px; font-weight: 700; line-height: 0;
  -webkit-text-stroke: 1px currentColor;
  paint-order: stroke fill;
  position: relative; top: 2px;
  margin-right: 4px;
}
.trait .yv { font-variant-numeric: tabular-nums; }
/* The year chip carries no visible caption, so it sets its figures a little
   larger and pads symmetrically — it is a number and a direction, not a
   label/value pair like the other five. */
.trait.year { padding: 7px 13px; font-size: 15px; }
/* A neutral year is still a miss, so it stays grey; the arrow is the thing
   that has to carry from across the row, and on a coloured chip it reads
   against the dark ink text rather than competing with it. */
.trait.year .ar { opacity: 0.92; }

/* A new pick lands rather than appears. Informative, so reduced-motion keeps
   a short fade in place instead of killing the state change outright. */
@keyframes pick-land {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}

/* The six verdicts resolve left to right instead of all at once.
   Six simultaneous colour changes is a single flash the eye reads as one
   event; staggered, it is six answers -- pantheon, then class, then damage,
   then range, then difficulty, then year -- and the row becomes something you
   watch resolve rather than something you have to re-read after the fact.
   `--i` is the chip's column index, set in classic.js. */
@keyframes trait-turn {
  0%   { opacity: 0; transform: translateY(-6px) scale(0.94); }
  60%  { opacity: 1; transform: translateY(0) scale(1.05); }
  100% { opacity: 1; transform: none; }
}
.pick.fresh .trait {
  animation: trait-turn 0.46s cubic-bezier(0.2, 0.9, 0.3, 1) both;
  /* Offset past the row's own landing so the plate arrives before its verdicts.
     110ms between chips was fast enough that the six still read as one sweep;
     at 220ms you actually get to take them one at a time -- pantheon, then
     class, then damage, then range, then difficulty, then year -- which is the
     order the eye wants them in anyway. Last chip settles at ~1.76s, and
     classic.js holds the result modal back past that. */
  animation-delay: calc(180ms + var(--i, 0) * 220ms);
}
/* The land animation is declared once, with .log-row, where the shared plate
   spec lives. Reduced motion swaps both to a fade in place. */

@media (max-width: 560px) {
  .pick, .log-row { grid-template-columns: 52px 1fr; gap: 12px; padding: 9px 12px 9px 9px; }
  .log-row { grid-template-columns: 52px 1fr auto; }
  .pick .portrait, .log-row .portrait { --p: 52px; }
  .pick-name { font-size: 18px; }
  /* Narrower plate, but the type does not shrink below the desktop value --
     this is the text the whole mode is built around and the phone is where it
     was hardest to read. Only the padding and tracking give way. */
  .trait { padding: 6px 10px; gap: 6px; letter-spacing: 0.03em; }
  .trait .k { letter-spacing: 0.06em; }
}

/* ---- the bolt, recurring -------------------------------------------------
 * A motif earns its place by repeating. It marks the divider between two
 * gods, heads every section rule, and flags the rung you are standing on. */
.vs {
  background-image: var(--bolt);
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: 15px 15px;
  color: transparent;
  filter: drop-shadow(0 0 8px rgba(232, 180, 74, 0.5));
}
.nextup-head { position: relative; padding-left: 22px; }
.nextup-head::before {
  content: "";
  position: absolute; left: 0; top: 50%;
  width: 15px; height: 15px;
  transform: translateY(-50%);
  background: var(--bolt) no-repeat 50% 50% / contain;
}
.rung.active::after {
  content: "";
  position: absolute; top: 5px; right: 6px;
  width: 11px; height: 11px;
  background: var(--bolt) no-repeat 50% 50% / contain;
  filter: drop-shadow(0 0 6px rgba(232, 180, 74, 0.6));
}

.hl-board {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 84px 1fr;
  align-items: stretch;
  --cut: 18px;              /* was 20 — one cut for every stage on the site */
  clip-path: var(--chamfer);
  background: var(--rule);
  gap: 1px;
  box-shadow: var(--lift);
  min-height: 330px;
}
/* Both sides top-align against an identical bottom band, so the two portraits
   sit on the same line whether or not the choice buttons are showing. */
/* Both sides are now the answer buttons — you pick a god, not a direction. */
.hl-side {
  position: relative;
  display: flex; flex-direction: column;
  align-items: center; justify-content: flex-start;
  gap: 4px;
  padding: 30px 26px 34px;
  text-align: center;
  background: var(--ink-2);
  isolation: isolate;
  cursor: pointer;
  /* The one place a real border is possible: this panel is unclipped and
     full of artwork, so a layered-background rim would sit under the art.
     A thick gold frame around key art is the register anyway. */
  border: 3px solid var(--bronze);
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.55), var(--godlight);
  /* Nothing paint-heavy transitions here. This used to run
     `background, border-color, box-shadow` together, which meant every hover
     started a 260ms non-composited transition of a box-shadow, four border
     colours and a background across a panel filled with splash art. Firefox
     rebuilt the display list and repainted the artwork for the whole run:
     profiled at 240 image-paint markers, the same six images repainted ~40
     times each, and pointer latency up to 221ms. The rim and the glow moved to
     ::before, which animates opacity only. */
  transition: background-color 0.26s;
}

/* The active rim and glow, as one compositor-friendly layer.
   `inset: -3px` puts it exactly over the 3px border — inset resolves against
   the padding box, so -3px reaches out across the border box — which is how a
   pseudo-element can recolour a real border without transitioning
   border-color. z-index 2 keeps it above the art and the verdict scrim; the
   inset ring is 3px at the very edge and the glow is an OUTER shadow, so
   neither touches the content sitting at z-index 1. */
.hl-side::before {
  content: "";
  position: absolute;
  inset: -3px;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  box-shadow: inset 0 0 0 3px var(--rim, var(--bronze-hi)),
              var(--rim-glow, var(--godlight-hot));
  transition: opacity 0.26s;
  will-change: opacity;
}
.hl-board:not(.locked) .hl-side:hover::before { opacity: 1; }

.hl-side > *:not(.artwash) { position: relative; z-index: 1; }
.hl-board:not(.locked) .hl-side:hover { background-color: var(--stone-2); }
.hl-board:not(.locked) .hl-side:hover .portrait { transform: scale(1.05); }
/* Hover was --blood: the wrong-answer colour, as the reward for aiming at the
   button. */
.hl-board:not(.locked) .hl-side:hover .pick-cue { color: var(--chrome); }
.hl-board.locked .hl-side { cursor: default; }

.hl-side .portrait {
  --p: 112px;
  margin-bottom: 16px;
  transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1);
}

/* The click affordance. Was a gold-rimmed plate — a second button for an
   action the whole panel already performs, competing with the god's name
   inside its own panel. Now it is a word: no plate, no rim, no fill. It still
   brightens on hover, so the panel stays discoverable. */
.pick-cue {
  margin-top: 22px;
  padding: 11px 6px;
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--bone-faint);
  transition: color 0.26s;
}
.hl-side .pan-tag {
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--verdigris);
}
/* placeholder that holds the tag's space while its value is withheld */
.hl-side .pan-tag.muted { color: var(--bone-faint); letter-spacing: 0.16em; }
.hl-side .god-name {
  font-family: var(--display);
  font-size: clamp(30px, 4.8vw, 50px);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  margin: 6px 0 14px;
  line-height: 1.05;
}
.hl-side .metric {
  font-size: 12px;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--bone-faint);
}
/* The revealed answer value. Sized down from the old year-only display since
   it now holds words ("Physical", "Norse · 10") as often as numbers. */
.hl-side .value {
  margin-top: 18px;
  font-family: var(--mono);
  font-size: clamp(26px, 3.6vw, 38px);
  font-weight: 500;
  line-height: 1.15;
  color: var(--gold);
  text-shadow: 0 0 40px color-mix(in srgb, var(--gold) 30%, transparent);
  animation: rise 0.4s cubic-bezier(0.2, 0.9, 0.3, 1);
}

/* the divider: a hairline with a hexagonal VS seal riding it */
.hl-mid {
  position: relative;
  display: grid; place-items: center;
  background: var(--ink-2);
  z-index: 2;
}
.hl-mid::before {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 50%;
  width: 1px; background: var(--rule-hard);
}
.hl-mid .vs {
  position: relative;
  width: 58px; height: 58px;
  display: grid; place-items: center;
  font-family: var(--display); font-size: 17px;
  letter-spacing: 0.05em;
  color: var(--bone);
  background: var(--stone-2);
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}

.hl-reveal {
  min-height: 46px;
  margin-top: 20px;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 15px;
  letter-spacing: 0.04em;
}
/* Both were pigment fills used as text: --verdigris at 15px, and --crimson,
   which measured 4.01:1 against --ink-2 and worse over the backdrop. The
   text-safe siblings exist precisely for this, and they are also the verdict
   set the rest of the site states its results in. */
.hl-reveal .good { color: var(--exact-text); }
.hl-reveal .bad  { color: var(--lost-text); }

/* the whole board flashes with the verdict */
/* The verdict, on the panel you touched.
 *
 * These two rules used to paint the BOARD's background — which is covered by
 * two opaque panels and an opaque divider, so the flash only ever showed
 * through the 1px grid gaps. Measured at flash time: board rgba(55,159,146,.71)
 * behind an opaque 547px panel. The single loudest moment in the endless mode
 * did not render. */
/* The fill has to be a scrim, not the panel's background: `.artwash` is an
   absolutely-positioned z-index 0 layer holding the god's splash art, so
   anything set on `background` paints *behind* the art and washes out to
   almost nothing. This sits above the art and below the content. */
.hl-side.picked::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 0;
  opacity: 0;
  transition: opacity 0.28s;
}
.hl-board.correct .hl-side.picked::after {
  opacity: 1;
  background: linear-gradient(165deg, color-mix(in srgb, var(--exact) 62%, transparent), color-mix(in srgb, var(--exact-lo) 50%, transparent));
}
/* On a loss the picked panel is the MISTAKE, so its scrim also carries a dark
   ink wash: it stays unmistakably red, and it recedes. It used to be the
   brightest object in the frame while the correct god sat faded beside it —
   the design gave every bit of emphasis to the error and dimmed the lesson,
   in the one mode whose losing frame is supposed to teach. */
.hl-board.wrong .hl-side.picked::after {
  opacity: 1;
  background:
    linear-gradient(165deg, color-mix(in srgb, var(--lost) 44%, transparent), color-mix(in srgb, var(--lost-lo) 36%, transparent)),
    color-mix(in srgb, var(--ink) 66%, transparent);
}
/* The verdict rim rides the same ::before layer as the hover rim, so it is an
   opacity change rather than a second animated box-shadow on the panel. The
   two custom properties just repoint the colours; they flip discretely while
   the opacity fades, which is what you want -- the rim should arrive already
   the right colour. */
.hl-board.correct .hl-side.picked::before {
  opacity: 1;
  --rim: var(--exact);
  --rim-glow: 0 0 34px -6px color-mix(in srgb, var(--exact) 70%, transparent);
}
.hl-board.wrong .hl-side.picked::before {
  opacity: 1;
  --rim: var(--lost);
  --rim-glow: 0 0 34px -6px color-mix(in srgb, var(--lost) 70%, transparent);
}
/* ...and the panel you did NOT pick is the answer, so it takes the exact rim
   and keeps it. Nothing on screen used to say which of the two was right —
   the loser was lit and the winner was faded, and you had to infer it. */
.hl-board.wrong .hl-side:not(.picked)::before {
  opacity: 1;
  --rim: var(--exact);
  --rim-glow: 0 0 34px -6px color-mix(in srgb, var(--exact) 70%, transparent);
}
/* The panel's resting shadow is dropped while a verdict is up, because the
   verdict rim on ::before replaces it rather than sitting on top of it —
   without this the old 2px dark inset line stays under the coloured rim and
   double-lines the picked card. This is a static swap, not a transition: it
   fires once when the round resolves, never on a pointer move, so it costs a
   single repaint and none of what this component was just fixed for. */
.hl-board.correct .hl-side.picked,
.hl-board.wrong .hl-side.picked,
.hl-board.wrong .hl-side:not(.picked) { box-shadow: none; }
.hl-board.wrong .hl-side.picked {
  animation: jolt 0.18s steps(2, end) 2;
}

/* The loser recedes, so the verdict has one subject — and which panel that is
   depends on the verdict. On a win it is the side you passed over; on a loss
   it is the side you chose. It was `:not(.picked)` in both cases, which is
   right exactly half the time and wrong in the half that matters.
 *
 * It was also `opacity: 0.55` on a container full of text, which multiplies
 * contrast invisibly and cannot be audited from the stylesheet — the Dim The
 * Plate Rule exists because of this exact pattern. The plate now dims (an ink
 * scrim over the artwork, above the art and below the content) and the type
 * steps down by named colour instead. */
.hl-board.correct .hl-side:not(.picked)::after {
  opacity: 1;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
}
/* The type is NOT stepped down with it. A first pass dimmed the plate *and*
   recoloured the god name to --bone-dim, and the name then measured 2.17:1
   over bright splash art — under the 3:1 large-text floor, and worse than
   before the fix. The plate carries the whole recession; the letters stay the
   colour they always were, which is the entire point of the rule. */
/* box-shadow is gone from here too: the verdict's rim and glow are on
   ::before now, and this panel is the last thing that should be repainting
   while the result lands. */
.hl-board.correct .hl-side, .hl-board.wrong .hl-side {
  transition: background-color 0.28s, opacity 0.28s;
}
@keyframes jolt {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(-5px); }
}

/* Right and wrong used to print in the same gold at the exact moment the
   difference is the entire point. */
.hl-side .value.won  { color: var(--exact-text); text-shadow: 0 0 40px color-mix(in srgb, var(--exact) 30%, transparent); }
.hl-side .pan-tag { color: var(--exact-text); }
.hl-side .value.lost { color: var(--lost-text); text-shadow: none; }

/* the next pair rises out of the stone */
.hl-board.enter .hl-side { animation: rise 0.5s cubic-bezier(0.2, 0.9, 0.3, 1); }
@keyframes rise {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* A duel is a side-by-side comparison, so the two gods stay side by side even
   on a phone. Stacking them meant you were asked to choose between two things
   you couldn't see at the same time. Everything scales down instead. */
@media (max-width: 860px) {
  .hl-board { grid-template-columns: 1fr 48px 1fr; min-height: 0; }
  .hl-side { padding: 24px 10px 26px; }
  .hl-side .portrait { --p: clamp(64px, 17vw, 96px); margin-bottom: 12px; }
  .hl-side .god-name { font-size: clamp(16px, 4.4vw, 30px); margin: 4px 0 6px; }
  .hl-side .god-title { display: none; }      /* no room, and it's decoration */
  .hl-side .value { font-size: clamp(17px, 4.4vw, 28px); margin-top: 12px; }
  .hl-mid .vs { width: 40px; height: 40px; font-size: 12px; }
  .pick-cue { margin-top: 14px; letter-spacing: 0.12em; }
}
@media (max-width: 420px) {
  .hl-board { grid-template-columns: 1fr 34px 1fr; }
  .hl-side { padding: 20px 6px 22px; }
  .hl-mid .vs { width: 34px; height: 34px; font-size: 11px; }
}

/* the home rail packs tighter on a phone so more modes clear the fold */
@media (max-width: 560px) {
  .mode-hex { width: min(100%, 132px); }
  .mode-name { font-size: 18px; margin-top: 10px; }
  .mode-desc { font-size: 12px; }
  .mode-foot { padding-top: 10px; }
}

/* =========================================================================
 * EMOJI
 * ====================================================================== */
/* ONE tablet, three dailies. Classic used to have no stage at all — a veiled
   hex floating on the backdrop with a caption under it, no plate, no edge —
   which is why it read as a different product from Lore and Emoji. It now
   shares their slab. Duel keeps its own board because its stage IS the two
   god panels, not a container holding a clue. */
.god-stage, .emoji-stage, .lore-stage {
  position: relative;
  max-width: 660px;
  margin: 0 auto;
  padding: 56px 30px;
  display: grid; place-items: center;
  min-height: 250px;
  text-align: center;
  --cut: 18px;              /* cut-hero — the one stage cut, see Duel too */
  clip-path: var(--chamfer);
  background: var(--rule-hard);
  box-shadow: var(--lift);
}
/* Classic's stage holds a portrait and one line, not a paragraph of clue, so
   it sits shorter than Lore's and Emoji's — same slab, less air. */
.god-stage {
  padding: 32px 30px 28px;
  min-height: 0;
  gap: 14px;
  grid-auto-flow: row;
}

/* These belong to the emoji row, not to the stage -- but they were set on the
   stage, so an 88px font size and a 12px letter-spacing inherited into
   everything else inside it. The reveal's name and its pantheon line were
   picking up 12.32px of tracking, which is what set "N E I T H" and
   "E g y p t i a n · H u n t e r" out like that. Scoped to the clue itself. */
.emoji-stage .emoji-clue {
  font-size: clamp(52px, 12vw, 88px);
  letter-spacing: 0.14em;
  line-height: 1.2;
}
/* the fill layer, inset so the edge above reads as a 1px chamfered border */
.god-stage::after, .emoji-stage::after, .lore-stage::after {
  content: "";
  position: absolute; inset: 1px;
  z-index: 0;
  clip-path: var(--chamfer);
  background: linear-gradient(165deg, var(--stone), var(--ink-2) 72%);
}

/* the emoji clue, wrapped so it sits above the tablet's fill layer */
.emoji-clue { display: flex; align-items: center; gap: clamp(8px, 1.6vw, 16px); }

/* One slot per symbol. Revealed slots hold the glyph; unrevealed ones hold
   the space it will take, so the clue never jumps sideways mid-round and the
   player can see how much help is still owed to them. */
/* The clue is set in NOTO EMOJI, the monochrome cut — carved line glyphs that
   take `color`, instead of 180 different vendors' glossy full-colour art.
   Native emoji were the one thing on the site that ignored the palette
   entirely: a red chilli and a blue keycap next to bronze and stone.
   See `stripVS16` in emoji.js — a variation selector requests COLOUR
   presentation and overrides this font, which left 37 of the 180 glyphs
   stranded in full colour while the rest went monochrome. */
.eg {
  display: grid; place-items: center;
  width: 1.16em; height: 1.16em;
  font-family: "Noto Emoji", var(--label);
  font-weight: 500;
  color: var(--bone);
}
/* only the symbol that just landed */
.eg.fresh { animation: eg-land 300ms cubic-bezier(0.2, 0.9, 0.3, 1); }
.eg.locked {
  --cut: 9px;
  clip-path: var(--chamfer);
  background: color-mix(in srgb, var(--bone) 7%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bronze) 26%, transparent);
  animation: none;
}
/* A symbol landing is information, so it is not a decorative entrance —
   reduced-motion keeps a fade rather than nothing. */
@keyframes eg-land {
  from { opacity: 0; transform: translateY(-6px) scale(0.9); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .eg.fresh { animation-name: land-still; animation-duration: 0.24s; }
}

/* The veiled god and the emoji clue, side by side. Two things to look at
   instead of one, which is what closes the ~200px of dead air the tablet used
   to hold around a single glyph. Stacks under 560px. */
.stage-duo {
  display: flex;
  align-items: center; justify-content: center;
  gap: clamp(18px, 5vw, 46px);
  flex-wrap: wrap;
}

/* ---- lore clue --------------------------------------------------------- */
.lore-stage {
  padding: 44px clamp(26px, 5vw, 54px) 48px;
  align-content: center;
  gap: 16px;
}
.lore-text {
  position: relative; z-index: 1;
  display: block;
  /* This paragraph IS the mode — it is the only thing the player has to work
     with — and it has now given way on four counts:

     Face.      Grenze, then Barlow. The serif was there for register, but the
                paragraph's job is reading, and Grenze's small x-height and
                narrow bowls make it the wrong instrument for it. See --ui.
     Alignment. Centred prose makes the eye hunt for the start of every line.
                The block still centres in the stage; the text inside it sets
                ragged-right like anything else you actually read.
     Size.      Barlow's x-height is far taller than Grenze's, so it reads
                LARGER at the same px. Coming down from 19-23 to 18-21 keeps
                the apparent size while taking back some measure.
     Leading.   1.7 — a touch tighter than the serif wanted, because a tall
                x-height closes the gap between lines on its own.

     `balance` is for headlines and gives up past a few lines anyway; `pretty`
     is the one that works on a paragraph. */
  font-family: var(--ui);
  font-size: clamp(18px, 1.95vw, 21px);
  font-weight: 400;
  line-height: 1.7;
  /* Measured, not guessed. `ch` is the width of "0", which is narrower in
     Barlow than in Grenze, so the same number buys a wider line than it did:
     58ch ran the tablet breakpoint out to 74 characters. 54ch holds every
     breakpoint inside the 45-75 reading band. */
  max-width: 54ch;
  margin: 0 auto;
  color: var(--bone);
  text-align: left;
  text-wrap: pretty;
}
/* The reveal lands BENEATH the clue instead of replacing it, so the myth and
   the name it belongs to are readable in the same glance. Divided by the same
   bronze hairline that separates everything else in this world. */
.lore-answer {
  position: relative;
  padding-top: 26px;
  margin-top: 2px;
}
.lore-answer::before {
  content: "";
  position: absolute; top: 0; left: 50%;
  transform: translateX(-50%);
  width: min(190px, 58%); height: 1px;
  background: linear-gradient(90deg, transparent, var(--bronze), transparent);
}
/* The tablet's surface — above the fill layer, below content.
 *
 * This was a two-axis hairline lattice on a fixed 46px cell. The intent was
 * "carved stone", but a perfect square grid is the one thing stone is not:
 * real stone is irregular and tool-marked, and a tiled hairline grid is a
 * recognisable generated-UI signature rather than a material. Replaced with
 * a fractal-noise tooth (the old page grain, kept only here), scaled
 * coarser so it reads as grain in the slab, plus a slow diagonal sheen for
 * the polish. Same job, actually made of the right stuff. */
.emoji-stage::before {
  content: "";
  position: absolute; inset: 1px;
  z-index: 1;
  opacity: 0.5;
  background-image:
    linear-gradient(118deg, transparent 38%, color-mix(in srgb, var(--bone) 5%, transparent) 50%, transparent 62%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='s'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.5' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23s)' opacity='.5'/%3E%3C/svg%3E");
  background-size: 100% 100%, 220px 220px;
  mask-image: radial-gradient(circle at 50% 50%, #000, transparent 78%);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000, transparent 78%);
}
.god-stage > *, .emoji-stage > *, .lore-stage > * { position: relative; z-index: 2; }
.attempts-line { margin: 18px auto 4px; min-height: 22px; }

/* the answer portrait, unveiled on solve */
.emoji-reveal {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  animation: rise 0.6s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.emoji-reveal .nm {
  font-family: var(--display); font-size: 32px; font-weight: 800;
  text-transform: uppercase; line-height: 1.05;
}
.emoji-reveal .meta {
  font-family: var(--mono); font-size: 12px; color: var(--bone-faint);
}

/* The clue, kept on screen after the round instead of being thrown away with
   the rest of the stage. Set larger than it ran during play: while you are
   guessing it is a puzzle you squint at, and afterwards it is the explanation,
   so it should read at a glance. */
.emoji-clue.solved {
  font-size: clamp(30px, 6vw, 44px);
  /* the row is a flex line, so the spacing is the gap -- tracking on top of it
     would just push the last glyph off-centre */
  letter-spacing: 0;
  gap: clamp(10px, 2vw, 18px);
  margin-bottom: 2px;
}
/* Symbols you never needed. Dimmed rather than hidden -- seeing the help you
   left on the table is the whole reward for solving early. */
.emoji-clue.solved .eg.unused {
  opacity: 0.3;
  filter: grayscale(1);
}
.solved-on {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--verdigris);
}

/* ---- guess log ----------------------------------------------------------
 * Classic's roster plate, at the weight this list deserves. This was the one
 * row shape on the site that was chamfered but never rimmed — a flat --stone
 * bar with an engraved inset, while every other plate lays a bronze edge down
 * first and insets the fill on top. It is built from Classic's own classes
 * (.pick-art, .pick-name, .trait) so the two cannot drift apart again.
 *
 * Lore and Emoji return one verdict, not six, so the field chip stays neutral
 * and the ✓/✗ carries the result. The portrait runs smaller than Classic's
 * 76px because this log is what you have tried, not the board itself. */
.guess-log { margin: 26px auto 0; display: flex; flex-direction: column; gap: 10px; }
/* the log's only structural difference: a third column for the verdict */
.log-row { grid-template-columns: 64px 1fr auto; }
/* The one verdict Lore and Emoji return. Same set as Classic's chips: the
   cross is a lost round, not a missed field, so it takes red rather than the
   grey a single wrong attribute gets. */
.log-row .mark { font-family: var(--mono); font-size: 18px; color: var(--lost-text); }

/* A correct guess ends the round, so its row swaps the bronze edge for patina
   — the same pigment the verdict chips use, in the same structural slot. */
.log-row.ok { background: var(--exact); }
.log-row.ok::before {
  background: linear-gradient(100deg, color-mix(in srgb, var(--exact) 24%, var(--ink)), var(--ink) 78%);
}
.log-row.ok .mark { color: var(--exact-text); }

/* Shared with .pick: a new guess lands rather than appears. Replayed rows
   from a saved round pass no `fresh`, so a reload does not re-animate. */
.log-row.fresh,
.pick.fresh { animation: pick-land 320ms cubic-bezier(0.2, 0.9, 0.3, 1) both; }

@media (max-width: 560px) {
}

/* =========================================================================
 * RESULT MODAL
 * ====================================================================== */
.overlay {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center;
  padding: 22px;
  background: color-mix(in srgb, var(--ink) 82%, transparent);
  backdrop-filter: blur(8px);
  opacity: 0;
  transition: opacity 0.3s;
}
.overlay.show { opacity: 1; }
/* Belt and braces: a transparent overlay must never eat clicks, even in the
   window between fade-out starting and the node being removed. */
.overlay:not(.show) { pointer-events: none; }

/* The modal itself no longer scrolls — `.modal-body` does. That is what keeps
   the share button and the countdown pinned to the bottom edge instead of
   sliding below an internal scroll line where nobody could reach them. */
.modal {
  position: relative;
  display: flex; flex-direction: column;
  width: min(430px, 100%);
  /* The old 620px cap was set when the body still scrolled behind a visible
     bar. A full result — headline, relic, six-row board, stat strip and the
     distribution summary — measures ~815px, so 620 forced a scroll on every
     screen, including ones with room to spare. The cap now clears the tallest
     result outright; 92vh still governs anything shorter than the content. */
  max-height: min(92vh, 860px);
  padding: 36px 32px 0;
  text-align: center;
  background: linear-gradient(90deg, var(--blood), var(--gold) 50%, var(--blood)) top / 100% 3px no-repeat,
              var(--rule-hard);
  --cut: 20px;
  clip-path: var(--chamfer);
  box-shadow: var(--lift);
  transform: translateY(14px) scale(0.985);
  transition: transform 0.34s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.overlay.show .modal { transform: translateY(0) scale(1); }
/* the fill, inset to expose the element's own background as the edge */
.modal::before {
  content: "";
  position: absolute; inset: 3px 1px 1px;
  z-index: -1;
  clip-path: var(--chamfer);
  background: linear-gradient(180deg, var(--stone-2), var(--ink-2));
}
.modal > * { position: relative; }
.modal h2 { font-size: 38px; margin: 0 0 20px; }

.modal-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  /* room for the close control so a long god name never runs under it */
  padding-top: 4px;
}
/* On a short viewport the body still has to scroll, and with the bar gone
   there is nothing to say so — a board clipped mid-row reads as the whole
   board. `has-more` is set by ui.js whenever content remains below the fold
   and cleared at the end, so the fade is a live answer to "is there more",
   not a permanent decoration that lies once you reach the bottom. */
.modal-body.has-more {
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 44px), transparent);
          mask-image: linear-gradient(180deg, #000 calc(100% - 44px), transparent);
}
/* The primary action, pinned. It cannot scroll out of reach. */
.modal-foot {
  flex-shrink: 0;
  padding: 14px 0 26px;
  background: linear-gradient(180deg, transparent, var(--ink-2) 34%);
}
.modal-x {
  position: absolute; top: 10px; right: 10px;
  z-index: 3;
  display: grid; place-items: center;
  width: 44px; height: 44px;
  background: none; border: 0;
  font-size: 26px; line-height: 1;
  color: var(--bone-faint);
  cursor: pointer;
}
.modal-x:hover { color: var(--bone); }

/* Shown instead of the stats panel on a first play, where every figure in it
   would read zero. */
.first-run {
  margin: 18px 0 4px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--bone-dim);
  text-wrap: balance;
}

/* The histogram was eight rows dumped into the modal at the worst possible
   moment. Folded away by default; the modal opens at a readable height. */
.stats-more { margin: 4px 0 2px; text-align: center; }
.stats-more summary {
  display: inline-flex; align-items: center;
  min-height: 44px;
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--bone-faint);
  cursor: pointer;
  list-style: none;
}
.stats-more summary::-webkit-details-marker { display: none; }
.stats-more summary::after { content: " ▾"; }
.stats-more[open] summary::after { content: " ▴"; }
.stats-more summary:hover { color: var(--bone-dim); }

/* the answer, presented as a relic */
.modal .relic { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-bottom: 20px; }
.modal .relic .nm {
  font-family: var(--display); font-size: 30px; font-weight: 800;
  text-transform: uppercase; line-height: 1.05;
}
.modal .relic .meta { font-family: var(--mono); font-size: 11px; color: var(--bone-faint); }

/* the share board, redrawn in the codex palette */
.result-emoji { margin: 20px 0; }
.sq-row { display: flex; justify-content: center; gap: 5px; margin-bottom: 5px; }
.sq {
  width: 20px; height: 20px;
  /* a 6px cut on a 20px square eats 30% of it and reads as a slash, not a tile */
  --cut: 4px;
  clip-path: var(--chamfer);
  /* A miss has to stay legible as a cell — without an edge the board reads as
     floating hits with no structure, and looks nothing like the grid the
     player just filled in. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bone) 16%, transparent),
              inset 0 1px 0 color-mix(in srgb, var(--bone) 7%, transparent);
  /* ⬛ is a field that did not match, which is the same verdict the board
     states in grey. It was --stone-3 brown: a surface tone standing in for a
     verdict, so the summary disagreed with the thing it summarised. */
  background: linear-gradient(165deg, var(--wrong), var(--wrong-lo));
}
/* The share board is the SAME verdict set as the board it summarises, drawn
   small. It ran on verdigris/gold/crimson while the board ran on
   exact/close/wrong — two palettes for one meaning, both on screen at once,
   400px apart. And this is the artefact that leaves the site, so a player
   arriving from a friend's card learned a code the site does not use.
   These track the chips in .trait exactly, including the shallow 82% ramp. */
.sq.hit  { background: linear-gradient(165deg, var(--exact), color-mix(in srgb, var(--exact) 82%, var(--exact-lo))); }
.sq.near { background: linear-gradient(165deg, var(--close), color-mix(in srgb, var(--close) 82%, var(--close-lo))); }
/* 🟥 is one spent guess in Lore and Emoji — a lost play, not a failed field —
   so it takes --lost, the same red as those modes' ✗ mark. */
.sq.bad  { background: linear-gradient(165deg, var(--lost), var(--lost-lo)); }

.stats-row {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 18px 0;
  border-top: 1px solid var(--rule-soft);
  border-bottom: 1px solid var(--rule-soft);
}
.stat-big .n { font-family: var(--mono); font-size: 24px; color: var(--bone); }
.stat-big .l {
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--bone-faint);
  margin-top: 3px;
}

.hist { margin: 6px 0 14px; display: flex; flex-direction: column; gap: 5px; }
.hist-row { display: flex; align-items: center; gap: 9px; }
.hist-row .lbl {
  width: 12px;
  font-family: var(--mono); font-size: 12px;
  color: var(--bone-faint);
}
.hist-bar {
  padding: 3px 9px;
  --cut: 6px;
  clip-path: var(--chamfer);
  background: var(--stone-3);
  font-family: var(--mono); font-size: 12px;
  text-align: right;
  color: var(--bone-dim);
}
.hist-bar.cur {
  background: linear-gradient(90deg, var(--verdigris-lo), var(--verdigris));
  color: var(--on-verdigris);
  font-weight: 700;
}

.countdown {
  margin-top: 16px;
  font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--bone-faint);
}
.countdown b {
  font-family: var(--mono); font-size: 15px;
  letter-spacing: 0;
  color: var(--bone);
}

/* ---- toast ------------------------------------------------------------- */
.toast {
  position: fixed; left: 50%; bottom: 34px;
  z-index: 300;
  transform: translate(-50%, 16px);
  padding: 11px 20px;
  background: var(--bone);
  color: var(--ink);
  --cut: 8px;
  clip-path: var(--chamfer);
  font-size: 12px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  box-shadow: var(--lift);
  opacity: 0; pointer-events: none;
  transition: opacity 0.26s, transform 0.26s;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* =========================================================================
 * FOOTER
 * ====================================================================== */
.foot {
  padding: 44px 0 56px;
  margin-top: 60px;
  border-top: 1px solid var(--rule-soft);
  font-size: 13px;
  color: var(--bone-faint);
  line-height: 1.7;
}
/* Was a bare inline link measuring 15px tall — under the 24x24 minimum.
   Padding gives it a real target without changing how it reads. */
.foot a {
  display: inline-block;
  padding: 12px 2px;
  color: var(--bone-dim);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--rule-hard);
}
.foot a:hover { color: var(--chrome); text-decoration-color: currentColor; }

/* ---- staggered page entrance ------------------------------------------- */
.reveal { opacity: 0; animation: rise 0.62s cubic-bezier(0.2, 0.9, 0.3, 1) forwards; }
.reveal.d1 { animation-delay: 0.06s; }
.reveal.d2 { animation-delay: 0.16s; }
.reveal.d3 { animation-delay: 0.26s; }

/* Reduced motion, not no feedback.
 *
 * This used to be the usual blanket `0.01ms !important` on everything, which
 * does not calm the interface so much as strip it: the motion that tells you a
 * guess landed, the veil easing open as the god resolves, and the modal's
 * arrival all became instantaneous pops with nothing signalling that state had
 * changed. A player who asked for less motion still needs to know something
 * happened.
 *
 * So: kill the decorative and the travelling (ambient sway, embers, page
 * entrances, anything that slides), and keep the informative — reduced to a
 * short fade with no movement. */
@media (prefers-reduced-motion: reduce) {
  /* entrances that only decorate: show the end state immediately */
  .reveal, .emoji-reveal, .modal {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  /* Informative feedback, kept — but as a fade in place, not a slide. A guess
     landing is a state change, so it still has to register; it just does not
     travel. Both weights of the plate get the same treatment. */
  .pick.fresh, .log-row.fresh {
    animation-name: land-still;
    animation-duration: 0.24s;
  }

  /* The cascade is informative -- it is what separates six answers from one
     flash -- so it survives, but as a fade in place with the stagger tightened
     to roughly a third. Nothing travels or scales. */
  .pick.fresh .trait {
    animation-name: land-still;
    animation-duration: 0.2s;
    animation-delay: calc(var(--i, 0) * 80ms);
  }

  /* the veil still eases, because the change in blur IS the information */
  .portrait.plate img { transition-duration: 0.2s !important; }

  /* Everything else: quick enough not to read as motion.
     `!important` is load-bearing, not laziness. A universal selector has
     specificity 0, so this was being beaten by every class that sets its own
     transition — `.ac-item` kept its 0.14s, `.hl-side` kept 0.26s, and only
     elements with no class-level transition at all were actually reduced. The
     clause read as site-wide and applied almost nowhere. An accessibility
     override is exactly the case `!important` exists for; the deliberate
     exception above outranks it on specificity and still wins. */
  *, *::before, *::after {
    transition-duration: 0.12s !important;
    animation-iteration-count: 1;
  }
}
@keyframes land-still {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}


/* =========================================================================
 * NEXT UP — the strip that fills the dead half of every game page.
 * Each page used to end 400-900px above its footer with no onward action
 * anywhere outside the result modal, so dismissing the modal stranded the
 * player on a finished board.
 * ====================================================================== */
.nextup { margin: 32px 0 8px; }
.nextup-head {
  margin: 0 0 16px;
  font-family: var(--display);
  font-size: 15px; font-weight: 800;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--bone-faint);
}
.nextup-head::after {
  content: "";
  display: block;
  margin-top: 10px;
  height: 1px;
  background: linear-gradient(90deg, var(--rule-hard), transparent 62%);
}
.nextup-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.nextup-item {
  display: flex; align-items: center; gap: 12px;
  min-height: 64px;
  padding: 12px 16px;
  --plate-fill: linear-gradient(180deg, var(--stone-2), var(--stone));
  --cut: 12px;
  clip-path: var(--chamfer);
  box-shadow: var(--engrave);
  transition: background 0.22s, transform 0.22s;
}
.nextup-item:hover { background: var(--stone-3); transform: translateY(-2px); }
.nextup-item.done { opacity: 0.5; }
.nextup-seal {
  display: grid; place-items: center;
  width: 30px; height: 30px; flex-shrink: 0;
  color: var(--chrome);
}
.nextup-seal svg { width: 20px; height: 20px; }
.nextup-txt { display: flex; flex-direction: column; line-height: 1.25; }
.nextup-txt b {
  font-family: var(--display);
  font-size: 17px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.02em;
  color: var(--bone);
}
.nextup-txt i {
  font-family: var(--mono); font-style: normal;
  font-size: 11px; color: var(--bone-faint);
}
@media (max-width: 860px) { .nextup-row { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 420px) { .nextup-row { grid-template-columns: 1fr; } }
