/* =====================================================================
   THE PRIZE WHEEL — PHYSICS, LIGHT AND THE WIN REVEAL

   Loaded AFTER arcade.css, which is the only position from which this
   can override .wheel-* rules set in wheel-shop-icons.css (sheet 11),
   boosts-paytable-wheel.css (19) and wheel-nospins-boosttimers.css (21).

   Everything here is compositor-only — transform, opacity, filter — and
   every layer is a SIBLING of #wheelDisc rather than a child of it.
   Anything inside the disc turns with the spin, which is correct for the
   slices and wrong for a light: a rim highlight that rotates with the
   wheel stops reading as a reflection and starts reading as paint.

   The JS drives four custom properties from a rAF sampler that measures
   the disc's real matrix, so the FX follow the ACTUAL rotation rather
   than a parallel guess at it:

     --wheel-flap    the ticker's deflection, kicked by each passing peg
     --wheel-tilt-x  \  the off-axis lean, scaled by angular velocity
     --wheel-tilt-y  /
     --wheel-speed   0..1, how fast the disc is turning right now
   ===================================================================== */

/* THE VANISHING POINT LIVES ON THE STAGE, NOT ON THE PANEL.

   `perspective` on .wheel-panel put every child of the panel into one 3D
   rendering context — and inside a 3D context, painting order is decided
   by depth, not by z-index. .wheel-stage carries a 3D transform, so it
   sorted in front of its flat siblings whatever they asked for; the
   claim panel is one of those siblings, at z-index 6, and it was drawn
   BEHIND the wheel. The prize was announced under an opaque disc.

   `perspective()` as a transform FUNCTION on the stage gives the same
   lean without making the panel a 3D context, so the stack around it
   goes back to being ordinary z-index. transform-origin replaces
   perspective-origin as the vanishing point. */
.wheel-stage {
  transform-style: preserve-3d;
  transform-origin: 50% 45%;
  transform:
    perspective(1100px)
    rotateX(var(--wheel-tilt-x, 0deg))
    rotateY(var(--wheel-tilt-y, 0deg));
  /* Only the settle is eased. During the spin the sampler writes a new
     value every frame and a transition would smear them together. */
  transition: transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}
.wheel-stage.is-spinning { transition: none; }

/* --- STACKING ---------------------------------------------------------
   Declared in one place so the order is legible rather than emergent. */
.wheel-motes     { z-index: 0; }
.wheel-ring      { z-index: 1; }
.wheel-disc      { z-index: 2; }
.wheel-dim       { z-index: 3; }
.wheel-winner    { z-index: 4; }
.wheel-rimlights { z-index: 5; }
.wheel-gloss     { z-index: 6; }
.wheel-hub       { z-index: 7; }
.wheel-pointer   { z-index: 8; }
.wheel-burst     { z-index: 9; }

/* =====================================================================
   1. THE TICKER FLAP
   A real ticker is a hinged flap: a peg shoves it aside and a spring
   drags it back. So it pivots from its TOP edge, not its middle — the
   default 50% 50% origin would make it wag like a metronome instead.
   ===================================================================== */
.wheel-pointer {
  transform: translateX(-50%) rotate(var(--wheel-flap, 0deg));
  transform-origin: 50% 2px;
  will-change: transform;
}
/* No transition while spinning: the sampler already shapes the decay
   curve per peg, and easing on top of it turns the flick into a wobble.
   The settle afterwards is a spring, which is where the snap comes from. */
.wheel-stage:not(.is-spinning) .wheel-pointer {
  transition: transform 420ms cubic-bezier(0.2, 1.7, 0.35, 1);
}

/* =====================================================================
   2. CHASING RIM LIGHTS
   Four highlights swept round the bezel while the wheel is live. Masked
   to a ring so the gradient only shows on the metal, never across the
   face — a conic gradient over the whole disc would grey out the slices.
   ===================================================================== */
.wheel-rimlights {
  position: absolute;
  inset: -3.2%;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg,
    transparent 0deg 4deg,   rgba(255, 244, 200, 0.95) 10deg, transparent 18deg 86deg,
    rgba(255, 244, 200, 0.95) 94deg,  transparent 102deg 176deg,
    rgba(255, 244, 200, 0.95) 184deg, transparent 192deg 266deg,
    rgba(255, 244, 200, 0.95) 274deg, transparent 282deg 360deg);
  -webkit-mask: radial-gradient(circle, transparent 0 45.5%, #000 47.5% 50%, transparent 51.5%);
          mask: radial-gradient(circle, transparent 0 45.5%, #000 47.5% 50%, transparent 51.5%);
  filter: blur(1.5px);
  transition: opacity 340ms ease;
}
.wheel-stage.is-live .wheel-rimlights {
  opacity: calc(0.35 + var(--wheel-speed, 0) * 0.65);
  animation: wheelRimChase 1.5s linear infinite;
}
@keyframes wheelRimChase { to { transform: rotate(360deg); } }

/* =====================================================================
   3. SPECULAR SWEEP
   A glossy bar crossing the dome and the spokes every few seconds. Held
   at an angle so it reads as a light source rather than a wipe.
   ===================================================================== */
.wheel-gloss {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  overflow: hidden;
  opacity: 0.9;
}
.wheel-gloss::before {
  content: '';
  position: absolute;
  top: -55%;
  left: -130%;
  width: 55%;
  height: 210%;
  transform: rotate(18deg);
  background: linear-gradient(90deg,
    transparent,
    rgba(255, 255, 255, 0.05) 30%,
    rgba(255, 255, 255, 0.42) 50%,
    rgba(255, 255, 255, 0.05) 70%,
    transparent);
  filter: blur(3px);
  animation: wheelSpecular 6.5s ease-in-out infinite;
}
/* Faster and brighter while it turns — a spinning rim catches the light
   more often, and this is the cheapest way to say so. */
.wheel-stage.is-spinning .wheel-gloss::before { animation-duration: 2.4s; }
@keyframes wheelSpecular {
  0%, 62% { left: -130%; }
  100%    { left: 190%; }
}

/* =====================================================================
   4. AMBIENT MOTES
   Gold dust drifting around the dark panel behind the wheel. Built by
   JS (one span each) so the count and the stagger live in one place;
   the motion is all here.
   ===================================================================== */
.wheel-motes {
  position: absolute;
  inset: -22%;
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity 600ms ease;
}
.wheel-stage.is-live .wheel-motes { opacity: 1; }
.wheel-mote {
  position: absolute;
  bottom: -6%;
  width: var(--mote-size, 3px);
  height: var(--mote-size, 3px);
  border-radius: 50%;
  background: radial-gradient(circle, #fff6d0 0%, rgba(255, 205, 90, 0.85) 45%, transparent 70%);
  opacity: 0;
  animation: wheelMoteRise var(--mote-dur, 9s) linear var(--mote-delay, 0s) infinite;
}
@keyframes wheelMoteRise {
  0%   { transform: translateY(0) translateX(0) scale(0.6); opacity: 0; }
  12%  { opacity: var(--mote-peak, 0.75); }
  80%  { opacity: var(--mote-peak, 0.75); }
  100% { transform: translateY(calc(var(--mote-rise, 130) * -1%))
                    translateX(var(--mote-drift, 14px)) scale(1);
         opacity: 0; }
}

/* =====================================================================
   5. IDLE PRIZE MOTION + BREATHING HUB
   The wheel should never look switched off. Both stop during the spin —
   a floating icon on a wheel doing 400deg/s is noise.
   ===================================================================== */

.wheel-hub {
  animation: wheelHubBreathe 4.2s ease-in-out infinite;
}
.wheel-hub img {
  animation: wheelHubDrift 22s linear infinite;
}
@keyframes wheelHubBreathe {
  0%, 100% { transform: translate(-50%, -50%) scale(1);
             box-shadow: 0 0 14px rgba(255, 205, 90, 0.30); }
  50%      { transform: translate(-50%, -50%) scale(1.045);
             box-shadow: 0 0 26px rgba(255, 205, 90, 0.60); }
}
@keyframes wheelHubDrift { to { transform: rotate(360deg); } }

/* =====================================================================
   6. THE WIN REVEAL
   After the spin the winning wedge is ALWAYS at twelve o'clock, under
   the pointer — so the mask below is a fixed 30deg notch and never has
   to be aimed. `from -15deg` centres that notch on the pointer.
   ===================================================================== */
.wheel-dim {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  /* Transparent across the winning seat, dark everywhere else: the slice
     that won is not repainted brighter, everything else is taken down.
     That keeps its real artwork and colour instead of covering it. */
  background: conic-gradient(from -15deg,
    transparent 0deg 30deg,
    rgba(3, 2, 10, 0.74) 30deg 360deg);
  transition: opacity 420ms ease;
}
.wheel-stage.is-revealing .wheel-dim { opacity: 1; }

.wheel-winner {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  transform: scale(1);
  transform-origin: 50% 50%;
  background: conic-gradient(from -15deg,
    rgba(255, 240, 190, 0.30) 0deg 30deg,
    transparent 30deg 360deg);
  transition: opacity 380ms ease, transform 620ms cubic-bezier(0.22, 1.4, 0.36, 1);
}
.wheel-stage.is-revealing .wheel-winner {
  opacity: 1;
  /* The pop toward the camera. Scaling a sector of a conic gradient
     scales the whole disc-sized layer, but since everything outside the
     notch is transparent, only the winning wedge appears to lift. */
  transform: scale(1.055);
}

/* --- the burst out of the winning slice --- */
.wheel-burst {
  position: absolute;
  top: 13%;
  left: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
}
.wheel-burst.is-firing::before,
.wheel-burst.is-firing::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(0.2);
  opacity: 0;
}
/* Rays. */
.wheel-burst.is-firing::before {
  width: 24rem;
  height: 24rem;
  background: repeating-conic-gradient(from 0deg,
    rgba(255, 232, 160, 0.55) 0deg 3deg,
    transparent 3deg 13deg);
  -webkit-mask: radial-gradient(circle, #000 0 12%, rgba(0,0,0,0.55) 34%, transparent 62%);
          mask: radial-gradient(circle, #000 0 12%, rgba(0,0,0,0.55) 34%, transparent 62%);
  animation: wheelBurstRays 1150ms cubic-bezier(0.16, 0.9, 0.24, 1) forwards;
}
/* Core flash. */
.wheel-burst.is-firing::after {
  width: 9rem;
  height: 9rem;
  background: radial-gradient(circle, #fffdf2 0%, rgba(255, 226, 140, 0.85) 32%, transparent 68%);
  animation: wheelBurstCore 780ms cubic-bezier(0.16, 0.9, 0.24, 1) forwards;
}
@keyframes wheelBurstRays {
  0%   { opacity: 0;    transform: translate(-50%, -50%) scale(0.2) rotate(0deg); }
  18%  { opacity: 0.95; }
  100% { opacity: 0;    transform: translate(-50%, -50%) scale(1.15) rotate(38deg); }
}
@keyframes wheelBurstCore {
  0%   { opacity: 0;   transform: translate(-50%, -50%) scale(0.15); }
  22%  { opacity: 1; }
  100% { opacity: 0;   transform: translate(-50%, -50%) scale(1.5); }
}

/* =====================================================================
   REDUCED MOTION
   The wheel still turns and still resolves — that is the mechanic, not
   decoration. What goes is everything ambient: the lean, the chase, the
   dust, the idle float and the burst's spin.
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
  .wheel-stage { transform: none !important; }
  .wheel-motes, .wheel-rimlights, .wheel-gloss { display: none; }
  .wheel-hub, .wheel-hub img { animation: none; }
  .wheel-pointer { transform: translateX(-50%); }
  .wheel-burst.is-firing::before { animation-duration: 420ms; }
  .wheel-burst.is-firing::after  { animation-duration: 320ms; }
  .wheel-stage.is-revealing .wheel-winner { transform: scale(1.02); }
}

/* =====================================================================
   THE MYSTERY WHEEL — RARITY TIERS, WALLET, DROP LOG
   ---------------------------------------------------------------------
   Five tiers, and the styling escalates with them: slate is flat, teal
   glows faintly, violet glows, amber shimmers, and the legendary pulses
   behind a moving border. The escalation IS the information — a player
   should be able to tell how good a slice is without reading it.

   Appended to wheel-fx.css because that is the LAST sheet in the cascade
   (index.html loads it 26th of 26), so these win over the base wheel
   styling without needing specificity games.
   ===================================================================== */


/* --- THE LEGEND, BY TIER --------------------------------------------
   Twelve rows of "Coin Boost 13.33%" is a table nobody reads. Five rows
   of "COMMON 40%" is the shape of the game; the per-slice figure is one
   hover away on the slice itself. */
.wheel-odd {
  display: grid;
  grid-template-columns: 12px 1fr auto;
  grid-template-areas: "dot name pct" "dot slices pct";
  align-items: center;
  gap: 2px 9px;
  padding: 7px 11px;
  border-radius: var(--radius-sm, 8px);
  border: 1px solid var(--border-dim, rgba(255,255,255,0.14));
  background: rgba(255, 255, 255, 0.04);
}
.wheel-odd i { grid-area: dot; width: 12px; height: 12px; border-radius: 50%; }
.wheel-odd b {
  grid-area: name;
  font-family: "Orbitron", sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
}
.wheel-odd em {
  grid-area: pct;
  font-style: normal;
  font-family: "Orbitron", sans-serif;
  font-size: 0.95rem;
  font-weight: 800;
}
.wheel-odd small { grid-area: slices; font-size: 0.62rem; color: var(--text-dim); }
.wheel-odd[data-tier="legendary"] {
  border-color: rgba(255, 77, 61, 0.7);
  box-shadow: 0 0 16px rgba(255, 77, 61, 0.28);
}
.wheel-odd[data-tier="epic"] { border-color: rgba(251, 191, 36, 0.55); }

/* --- THE TOKEN WALLET -----------------------------------------------
   One token, one spin, and the token has exactly one source in the whole
   game — so the count is the headline of this panel, not a footnote. */
.wheel-wallet {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 10px auto 4px;
  padding: 9px 16px;
  width: fit-content;
  max-width: 100%;
  border-radius: var(--radius-pill, 999px);
  border: 1px solid rgba(255, 217, 77, 0.6);
  background: linear-gradient(180deg, rgba(58, 40, 8, 0.9), rgba(22, 14, 3, 0.95));
  box-shadow: 0 0 22px rgba(255, 217, 77, 0.22);
}
.wheel-wallet.is-empty {
  border-color: var(--border-dim, rgba(255,255,255,0.16));
  box-shadow: none;
  opacity: 0.75;
}
.wheel-token-art { width: 30px; height: 30px; object-fit: contain; flex: none; }
.wheel-wallet-body { display: flex; flex-direction: column; line-height: 1.1; }
.wheel-wallet-body b {
  font-family: "Orbitron", sans-serif;
  font-size: 1.25rem;
  font-weight: 800;
  color: var(--gold-bright, #ffd94d);
}
.wheel-wallet-body small {
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  color: var(--text-dim);
}
.wheel-wallet-note {
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  padding-left: 12px;
  border-left: 1px solid var(--border-dim, rgba(255,255,255,0.14));
}

/* --- AUTO-SPIN ------------------------------------------------------ */
.wheel-auto-btn {
  appearance: none;
  cursor: pointer;
  min-height: 44px;
  padding: 0 16px;
  border-radius: var(--radius-sm, 8px);
  border: 1px solid var(--border-dim, rgba(255,255,255,0.18));
  background: rgba(255, 255, 255, 0.05);
  color: var(--text-dim);
  font-family: "Orbitron", sans-serif;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
}
.wheel-auto-btn:hover:not(:disabled) { color: #fff; background: rgba(255,255,255,0.1); }
.wheel-auto-btn.is-on {
  color: #06212b;
  border-color: var(--cyan-bright, #7bf3ff);
  background: linear-gradient(180deg, var(--cyan-bright, #7bf3ff), var(--cyan, #22e3ff));
}
.wheel-auto-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* --- RECENT DROPS ---------------------------------------------------
   Server-owned (wheel_history), so it survives a device change and
   cannot be edited by the person it is a record of. */
.wheel-history-panel { margin-top: 14px; }
.wheel-history-title {
  margin: 0 0 8px;
  font-family: "Orbitron", sans-serif;
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  color: var(--text-dim);
}
.wheel-history {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 190px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.wheel-history-empty { margin: 0; font-size: 0.72rem; color: var(--text-dim); }
.wheel-drop {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: var(--radius-sm, 8px);
  border: 1px solid var(--border-dim, rgba(255,255,255,0.12));
  background: rgba(0, 0, 0, 0.34);
}
.wheel-drop-tier {
  font-family: "Orbitron", sans-serif;
  font-size: 0.52rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  padding: 3px 7px;
  border-radius: 999px;
  background: rgba(255,255,255,0.08);
  color: var(--text-dim);
}
.wheel-drop-body { display: flex; flex-direction: column; min-width: 0; }
.wheel-drop-body b { font-size: 0.78rem; color: var(--panel-text, #eaf6ff); }
.wheel-drop-body small {
  font-size: 0.64rem;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wheel-drop-time { font-size: 0.6rem; color: var(--text-dim); white-space: nowrap; }

.wheel-drop[data-tier="uncommon"]  .wheel-drop-tier { background: rgba(45,212,191,0.2);   color: #2dd4bf; }
.wheel-drop[data-tier="rare"]      .wheel-drop-tier { background: rgba(167,139,250,0.22); color: #c4b5fd; }
.wheel-drop[data-tier="epic"]      .wheel-drop-tier { background: rgba(251,191,36,0.22);  color: #fbbf24; }
.wheel-drop[data-tier="legendary"] .wheel-drop-tier { background: rgba(255,77,61,0.26);   color: #ff8a7a; }
.wheel-drop[data-tier="legendary"] {
  border-color: rgba(255, 77, 61, 0.55);
  box-shadow: inset 0 0 20px rgba(255, 77, 61, 0.14);
}

/* --- THE LEGENDARY LANDING ------------------------------------------
   A rotating iridescent border behind the whole stage, for the 3% that
   earns it. Behind the disc, so it reads as light coming off the wheel
   rather than as a ring drawn on top of it. */
.wheel-stage.is-legendary::before {
  content: '';
  position: absolute;
  inset: -8%;
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
  background: conic-gradient(from 0deg,
    #ff4d3d, #ffb020, #ffe98a, #ff4d3d, #b537f2, #ff4d3d);
  filter: blur(16px);
  opacity: 0.65;
  animation: wheelLegendarySpin 2400ms linear infinite;
}
@keyframes wheelLegendarySpin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .wheel-stage.is-legendary::before { animation: none; }
}
/* A full-size blurred conic is the most expensive thing on this screen. */
.lean-fx .wheel-stage.is-legendary::before { display: none; }

@media (max-width: 720px) {
  .wheel-wallet-note { display: none; }
  .wheel-history { max-height: 140px; }
}

/* =====================================================================
   THE PANEL HEIGHT BUDGET
   ---------------------------------------------------------------------
   .wheel-panel centres its children and clips its overflow, which is a
   trap: content taller than the panel loses HALF its excess off the TOP,
   and the top is where the pointer lives — the one part of a wheel that
   has to be visible for the result to mean anything.

   The stage was sized `calc(97vh - N)` where N reserved room for the
   chrome underneath it. The wallet, the auto button, the tier legend and
   the drop log all landed below the wheel, so N was short by ~80px and
   the wheel walked off the top of the screen.

   So: start from the top instead of the centre (excess now falls
   downward, where it can be scrolled to), reserve honestly, and keep
   overflow-x hidden — `overflow-y: auto` alone would promote the x-axis
   to auto as well, and the ring and legendary glow both sit on negative
   insets, which would raise a horizontal scrollbar over nothing.
   ===================================================================== */
.wheel-panel {
  justify-content: flex-start;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  /* Headroom for the pointer, the bezel ring (-4.2%) and the legendary
     glow (-8%), all of which paint above the stage's own box. */
  padding-top: max(46px, clamp(14px, 2vmin, 26px));
}
.wheel-panel .wheel-stage {
  flex: none;
  width: min(72vmin, 82vw, calc(97vh - 330px));
}
@media (max-width: 720px) {
  .wheel-panel .wheel-stage { width: min(80vmin, 86vw, calc(97vh - 312px)); }
}

/* =====================================================================
   THE SVG FACE
   ---------------------------------------------------------------------
   The disc used to be a conic-gradient with twelve absolutely
   positioned, rotated <span>s floating over it. A gradient has no
   geometry, so every icon and caption was placed by guessing a
   percentage down a rotated spoke — which is why the narrow slices ended
   up with captions wider than their own wedge, icons squeezed into a
   heap and boundaries that did not line up with anything.

   The face is one <svg> now. Wedges are explicit arc paths, captions run
   along their own radius, and every size is computed from the arc width
   the slice actually has. This sheet is LAST in the cascade, so these
   win without specificity games.

   #wheelDisc is still the rotating div and still owns the transform.
   That is deliberate: the spin animation, the pointer sampler (which
   reads getComputedStyle(#wheelDisc).transform) and the reward-flight
   origin all key off that one element, and moving the rotation onto the
   SVG would break all three without throwing anything.
   ===================================================================== */

.wheel-svg {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  /* Hit-testing a dozen filtered paths every frame of a 900deg/s spin is
     wasted work; the hover tooltips are re-enabled at rest below. */
  pointer-events: none;
  overflow: visible;
}
.wheel-stage:not(.is-spinning) .wheel-svg .wheel-item { pointer-events: auto; }

/* The face is drawn now, so the div underneath it must not paint too.
   A background here would show through every antialiasing seam between
   adjacent wedges and make a geometry bug look like a rendering choice. */
.wheel-disc {
  background: none;
  /* The inset vignette this used to carry is now the #wheelSheen
     gradient inside the SVG — an inset box-shadow paints BELOW replaced
     content, so it would have been hidden behind the wedges. */
  box-shadow: 0 0 34px rgba(255, 217, 77, 0.34);
}

/* Both of these painted slice dividers: the older one on a hardcoded
   30deg grid from when the seats were equal, the newer one from a
   --slice-edges conic the JS no longer builds. The SVG strokes its own
   dividers on real boundaries, so neither may contribute. */
.wheel-disc::after { display: none !important; }

/* --- WEDGES --------------------------------------------------------- */
.wheel-slice {
  stroke: none;
  transition: opacity 220ms ease;
}
/* Filters re-rasterise every frame. Worth it at rest, not at speed. */
.wheel-stage.is-spinning .wheel-slices,
.lean-fx .wheel-slices { filter: none !important; }

/* --- THE TIER BAND ON THE RIM ---------------------------------------
   The rarity badge, moved off the face. A text chip inside a 5.4deg
   wedge would have recreated exactly the crowding this rewrite undoes;
   a band of the tier's own colour says the same thing out at the rim
   where nothing competes for the room. Brightness escalates with tier,
   so the good slices are findable at a glance. */
.wheel-rim-band { opacity: 0.55; }
.wheel-rim-band[data-tier="uncommon"]  { opacity: 0.72; }
.wheel-rim-band[data-tier="rare"]      { opacity: 0.9; }
.wheel-rim-band[data-tier="epic"]      { opacity: 1; }
.wheel-rim-band[data-tier="legendary"] { opacity: 1; }

/* --- DIVIDERS AND EDGES ---------------------------------------------
   Two adjacent slices of the same tier are the same colour, so without
   these the face reads as one double-width wedge instead of two. */
.wheel-divider {
  stroke: rgba(255, 246, 207, 0.85);
  stroke-width: 2.5;
  fill: none;
}
.wheel-edge {
  fill: none;
  stroke: rgba(255, 217, 77, 0.55);
  stroke-width: 3;
}
.wheel-band-edge {
  fill: none;
  stroke: rgba(0, 0, 0, 0.45);
  stroke-width: 2;
}

/* --- PRIZE ICONS -----------------------------------------------------
   The plate is what made every prize read as the same object when the
   art is a dozen unrelated images at a dozen aspect ratios. It was an
   HTML box with a border and layered gradients; as SVG it is a circle
   with a fill and a stroke, which is the same picture by other means. */
.wheel-item-plate {
  fill: rgba(6, 4, 16, 0.62);
  stroke: rgba(255, 246, 207, 0.5);
  stroke-width: 2.5;
}
.wheel-item[data-tier="rare"]      .wheel-item-plate { stroke: rgba(196, 181, 253, 0.85); }
.wheel-item[data-tier="epic"]      .wheel-item-plate { stroke: rgba(251, 191, 36, 0.95); }
.wheel-item[data-tier="legendary"] .wheel-item-plate { stroke: rgba(255, 138, 122, 1); }

.wheel-item-icon { transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1); }
.wheel-item[data-tier="rare"]      .wheel-item-art,
.wheel-item[data-tier="epic"]      .wheel-item-art,
.wheel-item[data-tier="legendary"] .wheel-item-art {
  filter: drop-shadow(0 0 7px var(--glow));
}

/* Holds are the one prize with no artwork in the asset set. */
.wheel-item-glyph {
  fill: #fff;
  font-family: "Orbitron", sans-serif;
  font-weight: 800;
  text-anchor: middle;
  letter-spacing: 0.04em;
  paint-order: stroke;
  stroke: rgba(120, 6, 14, 0.9);
  stroke-width: 3;
}

/* --- CAPTIONS --------------------------------------------------------
   Radial, because that is the only orientation that fits. A 5.4deg slice
   has ~38 units of room ACROSS it at this radius and ~126 ALONG it.

   `paint-order: stroke` draws the dark outline UNDER the fill rather
   than over it, so the letterforms stay crisp instead of being eaten
   from the outside in. That plus the drop shadow is what holds contrast
   across twelve different slice colours — white on slate and white on
   amber cannot both work on fill alone. */
.wheel-item-label {
  fill: #fff;
  font-family: "Orbitron", sans-serif;
  font-weight: 800;
  letter-spacing: 0.06em;
  dominant-baseline: central;
  paint-order: stroke;
  stroke: rgba(4, 2, 12, 0.92);
  stroke-width: 5;
  stroke-linejoin: round;
  filter: url(#wheelTextInk);
  transition: fill 200ms ease;
}
.wheel-stage.is-spinning .wheel-item-label { filter: none; }

/* --- THE WINNER ------------------------------------------------------ */
.wheel-item.is-winner .wheel-item-label { fill: #fff6cf; }
.wheel-item.is-winner .wheel-item-plate {
  stroke: #fff6cf;
  stroke-width: 4;
}
.wheel-item.is-winner .wheel-item-icon { filter: url(#wheelWinGlow); }

/* --- THE REVEAL MASKS, CUT TO THE REAL WEDGE -------------------------
   Both of these hardcoded a 30deg notch from when every seat was a
   twelfth of the circle. Sweeps are odds-proportional, so that notch
   left a third of a 48deg common dimmed and spilled across both
   neighbours of a 5.4deg legendary. revealWheelWinner now publishes the
   winner's actual sweep and these cut exactly it. The 30deg fallback is
   for a server too old to send `sweep`. */
.wheel-dim {
  background: conic-gradient(from var(--win-from, -15deg),
    transparent 0deg var(--win-sweep, 30deg),
    rgba(3, 2, 10, 0.74) var(--win-sweep, 30deg) 360deg);
}
.wheel-winner {
  background: conic-gradient(from var(--win-from, -15deg),
    rgba(255, 240, 190, 0.30) 0deg var(--win-sweep, 30deg),
    transparent var(--win-sweep, 30deg) 360deg);
}

/* --- THE HUB SITS ABOVE EVERYTHING ----------------------------------
   Slice paths stop at r=118 and the hub covers to roughly there, so no
   icon can slide under it. The shadow is what separates the two planes;
   without it the medallion read as painted onto the face. */
.wheel-hub {
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.75));
}

@media (prefers-reduced-motion: reduce) {
  .wheel-item-icon { transition: none; }
}

/* =====================================================================
   THE WHEEL TAKES THE SCREEN ON DESKTOP
   ---------------------------------------------------------------------
   The wheel was small for a structural reason, not a styling one: every
   control was stacked UNDER it in one column, so the face was sized
   `calc(97vh - 330px)` and the wallet, the buttons and the drop log were
   each eating into it. On a 1280x720 laptop that left a 368px wheel in a
   1280px-wide panel — a third of the width doing nothing on either side.

   Wide screens get two columns instead: the face takes the height, and
   the chrome moves into a rail beside it. The wheel is then limited by
   the VIEWPORT rather than by the sum of everything under it, which is
   roughly a 620px face on that same laptop and ~930px at 1080p.

   Below the breakpoint nothing changes — the single column is right when
   there is no width to spend.
   ===================================================================== */
@media (min-width: 1080px) {
  .wheel-panel {
    display: grid;
    grid-template-columns: minmax(0, 1fr) clamp(250px, 24vw, 340px);
    grid-auto-rows: min-content;
    align-content: center;
    justify-items: center;
    column-gap: clamp(16px, 2vw, 40px);
    row-gap: clamp(6px, 1vmin, 14px);
    width: min(96vw, 1560px);
    max-width: 96vw;
    /* The single-column rule reserved headroom for the pointer. In two
       columns the face is vertically centred, so it is not needed and
       would only shrink the wheel. */
    padding-top: clamp(14px, 2vmin, 26px);
  }

  /* Spans every rail row so the face is centred against the whole stack.
     min() against BOTH axes: height keeps it inside the viewport, width
     keeps it from crowding the rail. */
  .wheel-panel .wheel-stage {
    grid-column: 1;
    /* span 6, NOT 1 / -1: -1 counts back from the end of the EXPLICIT
       grid, and the rows here are implicit (grid-auto-rows), so -1
       resolved to a single row. The stage then sat in row 1 alone and
       every rail row stacked BELOW it — 1280px of grid in an 869px
       panel, which is what pushed the top of the wheel off screen. */
    grid-row: 1 / span 6;
    align-self: center;
    width: min(86vh, 54vw);
    max-width: none;
  }

  .wheel-panel .wheel-wallet        { grid-column: 2; grid-row: 1; margin: 0; }
  .wheel-panel .wheel-actions       { grid-column: 2; grid-row: 2; }
  .wheel-panel .wheel-history-panel { grid-column: 2; grid-row: 3; width: 100%; }
  .wheel-panel .wheel-hint          { grid-column: 2; grid-row: 4; }
  .wheel-panel .wheel-result        { grid-column: 2; grid-row: 5; }
  .wheel-panel .wheel-odds          { grid-column: 2; grid-row: 6; width: 100%; }

  /* The rail has real height to spend, so the log shows more of itself. */
  .wheel-panel .wheel-history { max-height: min(42vh, 420px); }

  /* The claim covers the whole panel, not just the column it was born
     in — it is the reveal, and it should not be boxed into a rail. */
  .wheel-panel .wheel-claim { grid-column: 1 / -1; grid-row: 1 / span 6; }
}

/* Very wide and short (an ultrawide, or a laptop with a short viewport):
   height is the binding constraint, so give the rail less and the face
   more. */
@media (min-width: 1600px) {
  .wheel-panel .wheel-stage { width: min(88vh, 52vw); }
}

/* --- THE HOLD CHIP ----------------------------------------------------
   Holds are the one prize with no artwork, and the shop draws that item
   as the word in a red chip. Bare white text on the standard dark plate
   read as a missing icon rather than as a deliberate one, so the plate
   itself becomes the chip. */
.wheel-item-plate.is-glyph {
  fill: #c11320;
  stroke: #ff9aa1;
  stroke-width: 3;
}

/* --- A SHARPER POINTER, EXACTLY AT TOP CENTRE -------------------------
   It was a 28x26 CSS border-triangle, which is soft at its apex because
   the two mitred borders antialias against each other. Drawn as a clipped
   shape instead, it comes to a real point, and the notch aligns with the
   twelve o'clock radius the landing maths actually uses.

   left:50% + translateX(-50%) is what puts the apex on the vertical
   centreline; the transform is re-declared here because the flap
   rotation in this sheet would otherwise drop the centring. */
.wheel-pointer {
  /* Sized against the STAGE, not in fixed pixels. A 34x46 arrow is
     right on a 774px desktop wheel and chunky on a 300px phone — the
     same fixed-size-on-a-fluid-disc mistake the hub had. Clamped so it
     stays a usable target on the smallest screens. */
  width: clamp(19px, 4.4%, 48px);
  height: clamp(26px, 6%, 64px);
  border: 0;
  top: -2.6%;
  background: linear-gradient(180deg, #fff3bd 0%, #ffd94d 42%, #c9971a 100%);
  /* Apex at bottom centre, dead on the 12 o'clock radius. */
  clip-path: polygon(50% 100%, 0% 26%, 12% 0%, 88% 0%, 100% 26%);
  filter: drop-shadow(0 3px 5px rgba(0, 0, 0, 0.85));
  transform: translateX(-50%) rotate(var(--wheel-flap, 0deg));
  transform-origin: 50% 3px;
}
/* The pin the flap pivots on — sells it as a hinged ticker rather than a
   triangle that happens to wobble. */
.wheel-pointer::after {
  content: '';
  position: absolute;
  top: 5px;
  left: 50%;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fffbe8, #b98a12);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}

/* --- THE RAIL EARNS ITS SPACE ----------------------------------------
   Two columns leave the rail with room to spare, and the odds table was
   sitting behind an info button the whole time. On desktop it just
   shows: five rows saying what the tiers are worth is the shape of the
   game, and a player should not have to hunt for it.

   The attribute is dropped in JS, not overridden here. arcade.css closes
   [hidden] globally with !important because four separate author display
   rules had been leaking hidden elements into view, and fighting that
   from a stylesheet would reopen the hole for everything else.
   renderWheelLegend clears the attribute itself when the rail exists. */
@media (min-width: 1080px) {
  .wheel-panel .wheel-odds { display: grid; grid-template-columns: 1fr; gap: 6px; margin-top: 10px; }
  .wheel-panel .wheel-info-btn { display: none; }
}

/* --- THE HUB SCALES WITH THE WHEEL -----------------------------------
   It was `width: 5.6rem` — a fixed pixel size on a fluid disc, which is
   the same mistake this codebase already recorded for the old prize
   placement. At the sizes the two-column layout reaches, an 89px cap on
   a 774px wheel left a wide dead ring between the medallion and the
   slices. A percentage keeps the proportion at every size. */
.wheel-panel .wheel-hub {
  width: 18%;
  height: 18%;
}

/* --- THE MOTES MUST NOT DRIVE THE SCROLLBAR --------------------------
   .wheel-motes is a decorative particle layer on large negative insets,
   so it measures about 1.44x the stage — 1115px against a 774px wheel.
   It is absolutely positioned, and an absolutely positioned descendant
   still contributes to the scrollable overflow of its ancestor scroller,
   so the panel grew a 246px scroll region made entirely of empty space
   and pushed the top of the wheel off screen.

   `overflow: clip` is the fix rather than `hidden`: it clips WITHOUT
   creating a scroll container, so nothing contributes to the panel's
   scroll height. The clip margin keeps the parts that are meant to paint
   outside the stage — the bezel at -22px, the pointer at -20px and the
   legendary glow at -8% — while cutting the motes' outermost drift,
   which is empty air. A length, not a percentage: overflow-clip-margin
   does not accept percentages. */
.wheel-stage {
  overflow: clip;
  overflow-clip-margin: 80px;
}

/* =====================================================================
   THE PRIZE ANIMATIONS, PORTED TO THE SVG
   ---------------------------------------------------------------------
   The idle float, the epic shimmer, the legendary pulse and the winner
   pop all lived on .wheel-slice-token, an HTML box that no longer
   exists. They are behaviour, not scaffolding, so they move rather than
   die with it.

   They all run on .wheel-item-float, which exists precisely because it
   has no transform attribute of its own: orientWheelItems writes one on
   the parent .wheel-item-icon, and a CSS transform would replace that
   attribute rather than compose with it.

   transform-box: fill-box is what makes `50% 50%` mean the prize's own
   centre. Without it the origin is the 1000x1000 viewBox corner and a
   1.09 scale throws the prize halfway across the wheel.
   ===================================================================== */
.wheel-item-float {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: wheelPrizeIdle 3.4s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * -0.27s);
}
@keyframes wheelPrizeIdle {
  0%, 100% { transform: translateY(0) scale(1);          filter: brightness(1); }
  50%      { transform: translateY(-2.5px) scale(1.045); filter: brightness(1.18); }
}
/* Twelve animated groups re-rasterising at speed is the one thing that
   makes this face expensive. Nobody can see them mid-spin anyway. */
.wheel-stage.is-spinning .wheel-item-float,
.lean-fx .wheel-item-float { animation: none; }

.wheel-item[data-tier="epic"] .wheel-item-float {
  animation: wheelPrizeIdle 3.4s ease-in-out infinite,
             wheelEpicShimmer 2600ms ease-in-out infinite;
}
@keyframes wheelEpicShimmer {
  0%, 100% { filter: drop-shadow(0 0 5px var(--glow))  brightness(1); }
  50%      { filter: drop-shadow(0 0 14px var(--glow)) brightness(1.25); }
}
.wheel-item[data-tier="legendary"] .wheel-item-float {
  animation: wheelPrizeIdle 3.4s ease-in-out infinite,
             wheelLegendaryPulse 1500ms ease-in-out infinite;
}
@keyframes wheelLegendaryPulse {
  0%, 100% { filter: drop-shadow(0 0 6px var(--glow))  saturate(1.1); }
  50%      { filter: drop-shadow(0 0 20px var(--glow)) saturate(1.5); }
}

/* The winner comes forward. Scaled on the float group, never on
   .wheel-item — that group's transform is what aims it round the wheel,
   so scaling it would move the winner off its own wedge as well as
   growing it. The old sheet recorded exactly this trap. */
.wheel-item.is-winner .wheel-item-float {
  animation: wheelWinnerPulse 1.5s ease-in-out infinite;
}
@keyframes wheelWinnerPulse {
  0%, 100% { transform: scale(1.14); filter: brightness(1.15); }
  50%      { transform: scale(1.24); filter: brightness(1.5); }
}

@media (prefers-reduced-motion: reduce) {
  .wheel-item-float,
  .wheel-item[data-tier="epic"] .wheel-item-float,
  .wheel-item[data-tier="legendary"] .wheel-item-float,
  .wheel-item.is-winner .wheel-item-float { animation: none; }
}
