/* webcore — THE LUCKY WHEEL's own sheet.
 * ===========================================================================
 * Owner-neutral, and TOKEN-ONLY. theme.css is the one sheet in an assembled
 * site allowed to decide a colour; every colour below is a role name, so a
 * palette change is one edit there and none here. `wheel.js` reads the same
 * roles through getComputedStyle, which is what keeps the canvas and the page
 * wearing one palette instead of two.
 *
 * WHY A SHEET OF ITS OWN. The dial is a stage — a bezel, a pawl, a hub, a
 * reveal card — and expressing that in site.css would push wheel-only rules
 * onto every page that loads it.
 *
 * ★ A DECLARATION OUTSIDE A RULE IS SILENT. A dropped selector leaves its
 * declarations at the top level, where a CSS parser discards them without a
 * word (memory law, 2026-08-09 — a background that was never DRAWN, not one
 * that was dimmed). Every block below opens and closes in one place, and
 * tests/frontend/test_wheel_page.py fails on a declaration outside a rule.
 *
 * REDUCED MOTION IS THE FINISHED PICTURE, never a shorter one: the transitions
 * here are removed rather than shortened, and the script lands the dial at its
 * final angle instead of walking to it.
 * ===========================================================================
 */

/* -------------------------------------------------------------------------
   THE REGISTER (2026-09-07, Everfall's whole-site redesign)

   One plate grammar for this page, stated once: a 1px hairline, a translucent
   near-black ground, the house radius, no shadow, and a soft blur so a plate
   sitting over a PHOTOGRAPH reads as glass over the picture rather than as a
   grey box cut into it. Token-only, so a server with no photograph gets the
   same plate on its own ground.

   ★ WHY THE SELECTORS ARE RAISED. terminal.css is served AFTER every page
   sheet - that is what makes it the house look - and it dresses `.iw-console`,
   `.iw-bank` and `.iw-award` with the record's grain face. A bare `.iw-bank`
   here would lose that tie on load order alone and the rule would read as
   correct in the file and do nothing on the page. `.iw-grid .iw-console` is
   (0,2,0) and wins on merit, which is the same move livedossier.css makes for
   the same reason.

   ★ AND THE CONSOLE IS ONE PLATE, NOT THREE. It used to be a plate holding
   two more plates - the bank strip and the reveal card each drew their own
   hairline inside it - which is three nested boxes for one column of readings.
   The console keeps the plate; the two inside it become RULED ROWS.
   ------------------------------------------------------------------------- */

.iw-grid .iw-console {
  border: 1px solid var(--tm-hair);
  border-radius: var(--r);
  background-color: color-mix(in srgb, var(--bg-deep) 72%, transparent);
  background-image: none;
  box-shadow: none;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  padding: clamp(1.1rem, 2.4vw, 1.6rem);
  gap: 1.15rem;
}

.iw-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  align-items: start;
}

@media (max-width: 860px) {
  .iw-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------------------------------------------------------------- the dial */

.iw-stage { min-width: 0; }

.iw-dial {
  position: relative;
  width: 100%;
  max-width: 34rem;
  margin-inline: auto;
  aspect-ratio: 1 / 1;
}

.iw-dial canvas {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--r-round, 50%);
  /* The disc paints its OWN ground: a translucent page background showing
     through the machined cuts turns them into gaps. */
  background: var(--bg-deep);
  box-shadow: none;
}

.iw-dial.is-loading canvas { opacity: .55; }

/* The pawl. Its transform is written by the script every frame, so this rule
   must NOT also transition transform: two animations on one property is not a
   layering, it is a race, and the picture loses. */
.iw-pointer {
  position: absolute;
  top: -1.6%;
  left: 50%;
  width: 8%;
  min-width: 1.75rem;
  margin-left: -4%;
  z-index: 2;
  transform-origin: 50% 12%;
  color: var(--accent);
}

.iw-pointer svg { display: block; width: 100%; height: auto; }

/* The hub: the wheel's own plate, and where the result lands. */
.iw-hub {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 26%;
  height: 26%;
  margin: -13% 0 0 -13%;
  border-radius: var(--r-round, 50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .15rem;
  text-align: center;
  padding: .4rem;
  background: var(--surface-2);
  border: 1px solid var(--tm-hair);
  color: var(--fg);
  z-index: 1;
}

.iw-hub .hk {
  font-size: clamp(.55rem, 1.4vw, .7rem);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--fg-mute);
  line-height: 1.1;
}

/* ★ THE PRIZE HAS TO FIT THE HUB, and the sheet half of that had gone missing.
   wheel.js publishes the length it is about to write (`hub.setAttribute(
   "data-len", ...)` - s / m / l), and the rules that read it were not in this
   file at all: the JS half shipped, the CSS half did not, so a long species
   name was set at the same size as a two-digit payout inside a circle a
   quarter of the dial across. `anywhere` made it worse rather than better -
   it breaks INSIDE a word, which is what split "Tyrannosaurus" across two
   lines mid-word. `break-word` keeps the space as the first cut. Restored
   2026-09-07 with the games page; pinned by
   tests/frontend/test_wheel_species_dial.py::ThePrizeFitsTheHub. */
.iw-hub .hv {
  font-family: var(--font-display);
  font-size: clamp(.9rem, 2.6vw, 1.35rem);
  line-height: 1.05;
  overflow-wrap: break-word;
  hyphens: auto;
}

/* The two steps down, in the script's own buckets: `m` is over 16 characters,
   `l` is over 30. Nothing else about the plate moves - a smaller circle would
   move the dial's centre. */
.iw-hub[data-len="m"] .hv { font-size: clamp(.72rem, 2.05vw, 1.05rem); }
.iw-hub[data-len="l"] .hv {
  font-size: clamp(.6rem, 1.65vw, .85rem);
  line-height: 1.12;
}

.iw-hub.is-won {
  border: 1px solid var(--accent);
}

.iw-hub.is-won .hv { color: var(--accent-hi); }

/* THE HUB'S OWN PULSE. The dial itself now holds still the moment it lands
   (wheel.js's shouldRun()) - which fixed the plate visibly disagreeing with
   its own payout, but a wheel that goes completely motionless the instant it
   pays out reads as inert rather than as landed. Two breaths on the ring
   that is already the "you won" marker, cheap CSS on a small element, and
   then it rests - a landing has a moment, not a pulse that runs until the
   player looks away. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes iw-hub-pulse {
    0%, 100% {
      box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent);
    }
    50% {
      box-shadow: 0 0 22px 4px color-mix(in srgb, var(--accent) 45%, transparent);
    }
  }
  .iw-hub.is-won { animation: iw-hub-pulse 1.7s ease-in-out 2; }
}

/* ------------------------------------------------------------- the console */

.iw-console {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* THE READING AT THE HEAD OF THE CONSOLE: a tracked micro-label, the count in
   the display face, and the mute at the far end. A rule under it, not a box
   round it - see the register block above. */
.iw-console .iw-bank {
  display: flex;
  align-items: baseline;
  gap: .7rem;
  padding: 0 0 .95rem;
  border: 0;
  border-bottom: 1px solid var(--tm-hair);
  border-radius: 0;
  background: none;
  box-shadow: none;
}

.iw-bank-k {
  font-size: .7rem;
  letter-spacing: var(--tm-track, .22em);
  text-transform: uppercase;
  color: var(--fg-mute);
}

/* THE ONE BIG NUMERAL ON THIS PANEL, and it is the accent's - the register
   spends the accent on numerals, the open tab and the committing button. */
.iw-bank-v {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 2.1rem);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--accent-hi);
}

.iw-mute {
  margin-left: auto;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--r-xs);
  cursor: pointer;
  color: var(--fg-mute);
  background: none;
}

.iw-mute:hover { color: var(--fg); }
.iw-mute svg { width: 1.1rem; height: 1.1rem; }
/* One icon, two states: the slash and the waves are the same drawing with one
   of the two hidden, so there is no second SVG to keep in step. */
.iw-mute .iw-mute-off { display: none; }
.iw-mute[data-off="1"] .iw-mute-on { display: none; }
.iw-mute[data-off="1"] .iw-mute-off { display: inline; }
.iw-mute[data-off="1"] { color: var(--fg-mute); }

.iw-state {
  margin: 0;
  min-height: 3.2em;
  line-height: 1.6;
  font-size: .92rem;
  color: var(--fg-dim);
}

.iw-state.calm { color: var(--fg-dim); }
.iw-state.bad { color: var(--bad); }
.iw-cd { font-variant-numeric: tabular-nums; font-weight: 700; }

.iw-actions { display: flex; flex-wrap: wrap; gap: .6rem; }
.iw-spin { min-width: 12rem; font-variant-numeric: tabular-nums; }

/* ★ `hidden` LOSES TO A CLASS THAT SETS display. `.btn` is `inline-flex`, and
   a class selector outranks the `[hidden]` UA rule, so every button this
   console hides was drawn anyway - all three states on screen at once, which
   is exactly the "which one am I looking at" confusion rule 5 of wheel.js
   exists to prevent. Caught in a rendered shot, not by any static gate. */
.iw-actions [hidden],
.iw-award[hidden] { display: none !important; }

/* ------------------------------------------------------------- the reveal */

.iw-console .iw-award {
  border: 0;
  border-top: 1px solid var(--tm-hair);
  border-radius: 0;
  padding: 1rem 0 0;
  background: none;
  box-shadow: none;
  opacity: 0;
  transform: translateY(.4rem);
  transition: opacity .28s ease, transform .28s ease;
}

.iw-award.in { opacity: 1; transform: none; }

.iw-award-k {
  margin: 0 0 .3rem;
  font-size: .7rem;
  letter-spacing: var(--tm-track, .22em);
  text-transform: uppercase;
  color: var(--fg-mute);
}

/* ★ THE PRIZE IS A FIGURE, NOT A PARAGRAPH, and the selector has to say so
   three levels deep. A site theme in this fleet sets `.section p` to 13px body
   copy (correctly - that IS what a paragraph is on that theme), and this
   element is a `<p>` in fragments/wheel.html. At `.iw-award-v` the theme wins
   on load order and the number a player just won reads at the same size as
   the sentence under it, in the same dim ink. Measured on the rendered page,
   not guessed: computed font-size 13px, colour --ink-2. */
.iw-console .iw-award .iw-award-v {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 4vw, 2.1rem);
  line-height: 1.1;
  color: var(--accent-hi);
  overflow-wrap: anywhere;
}

.iw-award-s { margin: .35rem 0 0; color: var(--fg-dim); line-height: 1.5; }

/* -------------------------------------------------------------- the board */

.iw-recent {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .35rem;
}

/* ONE HAIRLINE PER ROW, not one box per row. A stack of filled slabs over a
   photograph reads as a second page pasted on top of the first; a ruled list
   reads as a register, which is what this is. */
.iw-recent li {
  display: grid;
  grid-template-columns: minmax(4.5rem, auto) 1fr auto;
  gap: .75rem;
  align-items: baseline;
  padding: .7rem .2rem;
  border-bottom: 1px solid var(--tm-hair-soft);
}

.iw-recent li:last-child { border-bottom: 0; }

.iw-amt {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-hi);
}

.iw-who { min-width: 0; overflow-wrap: anywhere; }
.iw-when { font-size: .8rem; color: var(--fg-mute); white-space: nowrap; }

@media (max-width: 520px) {
  .iw-recent li { grid-template-columns: minmax(4rem, auto) 1fr; }
  .iw-when { grid-column: 1 / -1; }
}

/* The owner's parked sentences are never drawn; they exist for the script to
   read. `hidden` already does this — the rule is here so a stylesheet reset
   that drops the attribute's default cannot put a wall of copy on the page. */
.iw-copy { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  .iw-award { transition: none; opacity: 1; transform: none; }
}

/* ===================== THE MEMBERS' WHEEL TAB =====================
   (Everfall, 2026-08-29). Rendered only when the server's payload carries a
   `patreon` block - an owner without one ships these rules unused. Every
   colour is a theme token, so the tab wears each owner's own palette. */
/* ★ A STRIP INSIDE A STRIP HAS TO READ AS THE SMALLER ONE (2026-09-07). On the
   merged games page this mode picker sits directly under the page's own
   sub-tab strip, and a site theme that dresses every `[role="tablist"]` the
   same way gave the two identical weight - two rows of tracked caps, one over
   the other, with nothing saying which one was the page and which one was the
   wheel. It cannot win that by TYPE (the theme sets the tab's size and loads
   last, correctly), so it wins it by WIDTH: this one is only as wide as its
   two words, centred under a strip that spans the column. */
.iw-tabs {
  display: flex;
  gap: .6rem;
  justify-content: center;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto 1.4rem;
  flex-wrap: wrap;
}
.iw-tab {
  /* Dressed by terminal.css's SURFACE REGISTER (one hairline, one face, the
     house radius) - this rule only lays the plate out and moves its states. */
  font: inherit;
  font-weight: 600;
  letter-spacing: .04em;
  color: inherit;
  padding: .55rem 1.1rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  transition: transform .18s ease, background .18s ease, color .18s ease,
              box-shadow .18s ease;
}
.iw-tab:hover { color: var(--fg); }
/* ★ THE OPEN TAB IS THE ACCENT, NEVER A FILLED SLAB (2026-09-07). It used to
   be solid accent with black text and an accent glow - which on the merged
   games page put TWO filled tabs on one screen, one inside the other, and made
   the wheel's own mode picker shout louder than the page's sub-tab strip above
   it. The register spends the accent as INK and a hairline; the one filled
   control on this page is the button that commits a spin. */
.iw-tab[aria-selected="true"] {
  background: none;
  color: var(--accent-hi);
  border-color: color-mix(in srgb, var(--accent) 62%, transparent);
  box-shadow: none;
}
.iw-badge {
  font-size: .68em;
  font-weight: 600;
  letter-spacing: var(--tm-track, .22em);
  padding: .16rem .45rem;
  border-radius: var(--r-xs);
  border: 1px solid color-mix(in srgb, currentColor 45%, transparent);
  text-transform: uppercase;
}
@media (prefers-reduced-motion: reduce) {
  .iw-tab { transition: none; }
}
/* The members' dial gets the committing ring; a locked one steps back. */
.iw-dial.iw-members { box-shadow: 0 0 0 2px var(--primary, var(--accent)); }
.iw-dial.iw-locked { opacity: .45; filter: saturate(.4); }
.iw-join { text-decoration: underline; font-weight: 600; }
