* { box-sizing: border-box; }

:root {
  /* One sans for every Latin word in the game, one sans for every
     Devanagari word: Inter and Noto Sans Devanagari, both listed as a
     fallback in the other's stack so a run of the "wrong" script inside an
     otherwise-English or otherwise-Hindi string still finds real glyphs
     instead of a system default. --font-accent keeps Devanagari first for
     the moments (headline, striker prompt/name) where the Hindi is what a
     reader's eye should land on. */
  --font-display: "Inter", "Noto Sans Devanagari", system-ui, sans-serif;
  --font-body: "Inter", "Noto Sans Devanagari", system-ui, sans-serif;
  --font-accent: "Noto Sans Devanagari", "Inter", system-ui, sans-serif;

  --ground: oklch(16% 0.028 55);
  /* All warm. Every neutral carries a low-chroma yellow-earth undertone
     (hue ~84) so the whole screen reads as one sun-baked surface — aged
     paper, dust, sun-bleached wood — rather than the earlier clay-slate.
     Nothing on the page is cool any more. */
  --panel: oklch(25% 0.03 84 / 0.93);
  --panel-dense: oklch(17% 0.025 82 / 0.96);
  --panel-active: oklch(33% 0.045 84 / 0.96);

  --ink: oklch(96% 0.014 88);
  --ink-mid: oklch(92% 0.016 88 / 0.94);
  --ink-dim: oklch(84% 0.022 88 / 0.78);
  --ink-faint: oklch(84% 0.022 88 / 0.46);

  /* Mud: a brownish-yellow, sun-baked earth rather than gold leaf. Ornament
     linework, score, the round-over text. */
  --gold: oklch(80% 0.14 82);
  --gold-dim: oklch(80% 0.13 82 / 0.9);

  /* State / action accent: the same warm honey-gold as the Start button, run
     a touch brighter and more saturated so a selection or focus ring reads as
     "lit up" against the calmer --gold linework — same hue, more intensity,
     not a second colour. Powers focus rings, the mute-on state and the
     selected-marble ring. (Named --sky from when it was blue; the palette
     went all-warm, the name stayed to avoid churn across files.) */
  --sky: oklch(85% 0.155 85);
  --sky-bright: oklch(91% 0.11 88);
  --sky-deep: oklch(68% 0.16 78);
  --cool: var(--sky);
  --cool-dim: oklch(85% 0.14 85 / 0.82);

  /* Reusable top-lit sheen for glossy chrome (buttons, chips, cards). */
  --sheen: linear-gradient(180deg, oklch(100% 0 0 / 0.16), oklch(100% 0 0 / 0) 46%);

  /* Glass, onboarding only. The onboarding surface sits over #onboardBackdrop
     (a near-opaque panel), never over the live scene, so a blur here has a
     calm backdrop to work against and none of the legibility cost the in-round
     HUD would pay. --glass-edge is a brighter hairline than --line: a lit rim
     is what makes a translucent panel read as a pane of glass rather than a
     faded box. The inset top highlight below each use is the specular streak a
     real marble catches. */
  --glass-bg: oklch(33% 0.035 84 / 0.44);
  --glass-edge: oklch(95% 0.028 88 / 0.26);
  --glass-blur: blur(18px) saturate(1.3);
  --glass-top: inset 0 1px 0 oklch(100% 0 0 / 0.28);

  /* Candy button: a glossy 3D pill. Steep top-lit gradient, a crisp specular
     band (::before on each user), an inner light ring and an outer colored
     halo. One honey-gold now, shared: --candy-* is the Start button, and
     --candy-sky-* (the Continue / invite button) is the same gold a hair
     deeper, so the two primary actions read as one family. */
  --candy-top: oklch(93% 0.08 92);
  --candy-mid: oklch(83% 0.15 86);
  --candy-bot: oklch(70% 0.15 74);
  --candy-ink: oklch(26% 0.06 66);
  --candy-halo: oklch(80% 0.14 82 / 0.4);

  --candy-sky-top: oklch(91% 0.085 88);
  --candy-sky-mid: oklch(80% 0.15 82);
  --candy-sky-bot: oklch(67% 0.15 72);
  --candy-sky-ink: oklch(26% 0.06 64);

  /* Both of the above sit at 80% lightness, and so does the parchment
     texture behind onboarding: as text color there, gold reads as pale
     yellow on pale tan, and no contact shadow closes a gap that small. This
     is the same hue, dropped to something a shadow can actually separate
     from the parchment: a burnished brass rather than pale straw. Use for
     any gold TEXT that sits directly on the parchment (not inside a dark
     panel or card, where --gold/--gold-dim already read fine). */
  --gold-ink: oklch(42% 0.13 80);

  /* Committed second accent, abstracted from the terracotta/pietra-dura
     reference patterns — used on the onboarding surface and as a single
     restrained rule/underline in HUD chrome. Never neon: kept in the same
     earthy-warm register as --gold rather than a pure saturated red. */
  --red: oklch(46% 0.17 28);
  --red-deep: oklch(22% 0.08 24);
  --red-dim: oklch(55% 0.13 28 / 0.75);
  --ruby: oklch(54% 0.19 18);

  /* Warm hairline: a low-chroma yellow-earth line, brightening toward
     marigold on hover. */
  --line: oklch(90% 0.04 86 / 0.2);
  --line-hover: oklch(85% 0.13 84 / 0.5);

  /* Corner radii, stepped. The vibrant pass rounded every panel a notch:
     chips/HUD at --r-sm, tune panel at --r-md, onboarding cards at --r-lg. */
  --r-sm: 13px;
  --r-md: 16px;
  --r-lg: 22px;
  --r-pill: 999px;
}

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--ground);
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

#stage {
  position: relative;
  width: 100vw;
  height: 100vh;
  /* dvh where supported: on a mobile browser the address bar shrinking/growing
     changes the usable height, and the render canvas is sized from
     window.innerHeight — keep the stage box in step so HUD/onboarding overlays
     don't slide under the chrome. */
  height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
}

#scene, #overlay {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}

#overlay {
  pointer-events: none;
}

#vignette {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 4;
  background:
    radial-gradient(ellipse at 50% 48%, rgba(255, 214, 150, 0.05) 0%, rgba(255, 214, 150, 0) 40%),
    radial-gradient(ellipse at 50% 48%, transparent 42%, rgba(30, 16, 6, 0.16) 74%, rgba(18, 9, 3, 0.58) 100%);
}

/* Covers the live WebGL ground while onboarding is up, so mode-select and the
   striker carousel read against a plain surface rather than the actual dirt
   texture rendering underneath (visible edge to edge otherwise: the ground's
   own intro fade finishes within the first second, well before most people
   have read the headline). --panel, the same brown every onboarding control
   (opponent picker, difficulty pill, carousel) already sits on, so the whole
   screen reads as one surface instead of controls floating over gameplay.
   Sibling selector below is what drives its opacity: no JS toggles this, it
   just tracks #onboardTagline's own show/hide state and timing exactly. */
#onboardBackdrop {
  position: absolute;
  inset: 0;
  z-index: 3;
  /* Opaque (not --panel's 0.93) now that the onboarding controls above it are
     glass: the blur wants one calm, consistent surface to sample, not a faint
     view of the settling scene. A soft warm wash, brighter at the top, keeps
     it from being a flat slab. */
  background:
    radial-gradient(ellipse at 50% -10%, oklch(34% 0.08 74 / 0.6), transparent 58%),
    radial-gradient(ellipse at 50% 115%, oklch(26% 0.06 58 / 0.55), transparent 60%),
    oklch(17% 0.028 72);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
#onboardTagline.show ~ #onboardBackdrop {
  opacity: 1;
}

/* Portrait rotate gate — phones only. The play field is a 1280x720 landscape
   rectangle; on a phone held upright it can only be shown tiny or cropped, so
   portrait phones get this "turn sideways" screen instead of the game (main.js
   freezes the sim and render loop behind it, matching this query exactly). The
   media query catches portrait phones and nothing else: tablets are wider than
   560px even in portrait, desktops have a fine pointer. Full-screen opaque
   cover, above every other layer. */
#rotateGate {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 60;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 32px;
  text-align: center;
  background:
    radial-gradient(ellipse at 50% -10%, oklch(34% 0.08 74 / 0.6), transparent 58%),
    radial-gradient(ellipse at 50% 115%, oklch(26% 0.06 58 / 0.55), transparent 60%),
    oklch(17% 0.028 72);
  text-shadow: 0 1px 2px rgba(8, 5, 2, 0.9);
}
#rotateGate .rotateIcon {
  width: 56px;
  height: 56px;
  color: var(--gold-dim);
  animation: rotateHint 2.4s ease-in-out infinite;
}
#rotateGate .rotateHi {
  color: var(--ink);
  font: 500 clamp(24px, 8vw, 32px)/1.25 var(--font-accent);
}
#rotateGate .rotateEn {
  color: var(--ink-dim);
  font: 500 14px/1.5 var(--font-body);
  letter-spacing: 0.2px;
}
@keyframes rotateHint {
  0%, 55%, 100% { transform: rotate(0deg); }
  70%, 90% { transform: rotate(-90deg); }
}
@media (prefers-reduced-motion: reduce) {
  #rotateGate .rotateIcon { animation: none; transform: rotate(-90deg); }
}
@media (hover: none) and (pointer: coarse) and (orientation: portrait) and (max-width: 560px) {
  #rotateGate { display: flex; }
}

#onboardTagline {
  position: absolute;
  top: 45%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  /* One tight contact shadow, not a soft bloom — light text still needs this
     much to hold against the parchment background, but a wide glow behind
     every word read as decoration rather than legibility. */
  text-shadow: 0 1px 2px rgba(8, 5, 2, 0.9);
  pointer-events: none;
  opacity: 0;
  z-index: 5;
  max-width: 640px;
  padding: 0 20px;
  transition: opacity 0.6s ease;
}

#onboardTagline .headlineHi {
  display: block;
  color: var(--ink);
  font: 400 clamp(40px, 7vw, 55px)/1.3 var(--font-accent);
  letter-spacing: 0.3px;
}

.onboardDivider {
  display: block;
  width: 20px;
  height: 20px;
  margin: 10px auto 0;
  color: var(--gold-dim);
  opacity: 0.8;
}

#onboardTagline .headline {
  display: block;
  margin-top: 6px;
  color: var(--ink-mid);
  font: 500 15px/1.5 var(--font-body);
  letter-spacing: 0.2px;
}


#onboardTagline.show {
  opacity: 1;
}

/* The headline is the game's one emotional beat — worth having once, on the
   first (board) step only; the opponent and kancha steps are pure function
   and start right at their own controls. */
#onboardTagline:not([data-step="mode"]) .headlineHi,
#onboardTagline:not([data-step="mode"]) .onboardDivider,
#onboardTagline:not([data-step="mode"]) .headline { display: none; }

#onboardTagline.show[data-step="mode"] .headlineHi,
#onboardTagline.show[data-step="mode"] .onboardDivider,
#onboardTagline.show[data-step="mode"] .headline {
  animation: revealUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) both;
}

/* Cards slide into place but stay fully opaque throughout — no fade. The
   step selector is load-bearing: the cards spend the other steps in
   display:none, so keying the animation to the step replays it on return. */
#onboardTagline.show[data-step="mode"] .modeCards .modeCard {
  animation: revealSlideUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) both;
}

#onboardTagline.show[data-step="opponent"] .pickPanel--opponent {
  animation: revealUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) both;
}

#onboardTagline.show[data-step="opponent"] .continueBtn {
  animation: revealUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) both;
  animation-delay: 0.12s;
}

#onboardTagline.show[data-step="level"] .levelPanel {
  animation: revealUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) both;
}

#onboardTagline.show[data-step="level"] .levelContinueBtn {
  animation: revealUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) both;
  animation-delay: 0.12s;
}

#onboardTagline.show[data-step="pick"] .pickPanel--striker {
  animation: revealUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) both;
}

#onboardTagline.show[data-step="pick"] .startBtn {
  animation: revealUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) both;
  animation-delay: 0.12s;
}

.roomJoin { display: none; }
#onboardTagline.show[data-step="joining"] .roomJoin {
  display: flex;
  align-items: center;
  flex-direction: column;
  gap: 9px;
  color: var(--ink-mid);
  font: 500 0.95rem/1.35 var(--font-body);
  animation: revealUp 0.42s cubic-bezier(0.23, 1, 0.32, 1) both;
}
.roomJoinHi {
  color: var(--ink);
  font: 600 1.35rem/1.25 var(--font-accent);
}
.roomJoinSpinner {
  width: 34px;
  height: 34px;
  margin-bottom: 5px;
  border: 2px solid color-mix(in oklch, var(--sky) 24%, transparent);
  border-top-color: var(--sky-bright);
  border-radius: 50%;
  animation: roomJoinSpin 0.85s linear infinite;
}
@keyframes roomJoinSpin { to { transform: rotate(360deg); } }

#onboardTagline.show[data-step="mode"] .onboardDivider { animation-delay: 0.05s; }
#onboardTagline.show[data-step="mode"] .headline { animation-delay: 0.08s; }
#onboardTagline.show .modeCards .modeCard:nth-child(1) { animation-delay: 0.22s; }
#onboardTagline.show .modeCards .modeCard:nth-child(2) { animation-delay: 0.3s; }

@keyframes revealUp {
  from { opacity: 0; transform: translateY(14px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes revealSlideUp {
  from { opacity: 1; transform: translateY(14px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .kancha, .carouselTrack { transition: none; }

  #onboardTagline.show[data-step="mode"] .headlineHi,
  #onboardTagline.show[data-step="mode"] .onboardDivider,
  #onboardTagline.show[data-step="mode"] .headline,
  #onboardTagline.show[data-step="mode"] .modeCards .modeCard,
  #onboardTagline.show[data-step="opponent"] .pickPanel--opponent,
  #onboardTagline.show[data-step="opponent"] .continueBtn,
  #onboardTagline.show[data-step="level"] .levelPanel,
  #onboardTagline.show[data-step="level"] .levelContinueBtn,
  #onboardTagline.show[data-step="pick"] .pickPanel--striker,
  #onboardTagline.show[data-step="pick"] .startBtn,
  #onboardTagline.show[data-step="joining"] .roomJoin,
  .roomJoinSpinner {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* --- Onboarding step 2: who's playing, and pick a kancha ------------------
   The two decisions used to sit in bordered glass cards; those are gone. Each
   is just a group on the backdrop under one quiet section label — the mode
   cards are the only "objects" on the onboarding surface, everything on step 2
   is a plain control. .pickPanel is an invisible layout wrapper only.
   Hierarchy: section labels sit flat and small; the loud bilingual moment is
   the chosen kancha's NAME under the carousel, not the "pick a kancha" prompt
   above it, which is now the same quiet weight as "Who's playing". */
.pickPanel {
  margin: 22px auto 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: min(92vw, 440px);
}

.panelLabel,
.pickPrompt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin-bottom: 12px;
  color: var(--ink-dim);
  font: 600 12px/1.4 var(--font-body);
  letter-spacing: 0.4px;
}

/* The Devanagari prompt is a quiet caption now, not a headline — one size up
   from its English line, no more. */
.pickPromptHi {
  color: var(--ink-mid);
  font: 500 15px/1.3 var(--font-accent);
  letter-spacing: 0;
}

.pickPromptEn {
  color: var(--ink-dim);
  font: 600 11px/1.4 var(--font-body);
  letter-spacing: 0.4px;
}

/* --- Bot campaign -------------------------------------------------------
   The trail is one continuous sequence, not a wall of equal cards. Numbered
   stones carry progress; the selected challenge opens into one readable
   description below. Classic play stays available without masquerading as a
   ninth level. */
.levelPanel {
  display: none;
  width: min(92vw, 560px);
  margin: 18px auto 0;
}

.levelTrailHeading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  color: var(--ink);
  font: 600 18px/1.3 var(--font-display);
}

#levelTrailProgress {
  color: var(--ink-dim);
  font: 500 12px/1.3 var(--font-body);
}

.levelRail {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 7px;
  margin-top: 16px;
}

.levelRail::before {
  content: "";
  position: absolute;
  left: 18px;
  right: 18px;
  top: 50%;
  height: 1px;
  background: var(--glass-edge);
  pointer-events: none;
}

.levelNode {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  border-radius: 50%;
  border: 1px solid var(--glass-edge);
  background: var(--panel-dense);
  color: var(--ink-mid);
  font: 700 13px/1 var(--font-body);
  cursor: pointer;
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.08), 0 4px 10px oklch(10% 0.02 60 / 0.22);
  transition: transform 160ms ease, color 160ms ease, border-color 160ms ease, background-color 160ms ease;
}

.levelNodeIcn {
  width: 19px;
  height: 19px;
  color: currentColor;
}

/* Stars earned on a cleared level, as three pips under the node. */
.levelNodeStars {
  position: absolute;
  left: 50%;
  bottom: -11px;
  transform: translateX(-50%);
  display: flex;
  gap: 2px;
}
.levelNodeStars i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--gold);
}
.levelNodeStars i.empty { background: color-mix(in oklch, var(--gold) 22%, transparent); }

/* Cleared: gold-tinted and inviting to replay, with a checkmark not a number. */
.levelNode.completed {
  background: color-mix(in oklch, var(--gold) 15%, var(--panel-dense));
  border-color: color-mix(in oklch, var(--gold) 45%, var(--glass-edge));
  color: var(--gold);
}
.levelNode:hover:not(:disabled) { transform: translateY(-2px); border-color: var(--line-hover); }
.levelNode:not(.completed):hover:not(:disabled) { color: var(--ink); }
.levelNode.active {
  background: color-mix(in oklch, var(--sky) 26%, var(--panel-active));
  border-color: var(--sky-bright);
  color: var(--ink);
  transform: translateY(-2px);
}
.levelNode.active.completed { color: var(--gold); }

/* Locked: dim, a padlock instead of the number, not selectable. */
.levelNode:disabled {
  cursor: not-allowed;
  color: var(--ink-faint);
  background: var(--panel);
  opacity: 0.5;
  box-shadow: none;
}
.levelNode:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: 3px; }

.levelRailActions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 14px;
}

.classicLevel {
  min-height: 40px;
  padding: 8px 12px;
  border: 0;
  border-radius: var(--r-pill);
  background: none;
  color: var(--ink-dim);
  font: 500 12px/1.4 var(--font-body);
  cursor: pointer;
}
.classicLevel:hover { color: var(--ink); }
.classicLevel.active { color: var(--sky-bright); background: color-mix(in oklch, var(--sky) 12%, transparent); }
.classicLevel:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: 2px; }

.levelReset {
  min-height: 40px;
  padding: 8px 12px;
  border: 0;
  border-radius: var(--r-pill);
  background: none;
  color: var(--ink-faint);
  font: 500 12px/1.4 var(--font-body);
  cursor: pointer;
}
.levelReset[hidden] { display: none; }
.levelReset:hover { color: var(--ink-dim); }
.levelReset.armed { color: oklch(72% 0.17 35); }
.levelReset:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: 2px; }

@media (hover: none) {
  .classicLevel,
  .levelReset,
  .levelResultActions button { min-height: 44px; }
}

.levelDetail {
  min-height: 116px;
  margin-top: 12px;
  padding-top: 14px;
  border-top: 1px solid var(--glass-edge);
  text-align: center;
}

.levelDetailTitle {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 9px;
}

#levelNameHi { display: none; }
#levelNameEn { color: var(--ink); font: 600 20px/1.3 var(--font-display); }
#levelObjective {
  max-width: 44ch;
  margin: 8px auto 0;
  color: var(--ink-mid);
  font: 400 14px/1.5 var(--font-body);
}
#levelMastery {
  margin-top: 8px;
  color: var(--gold);
  font: 600 11px/1.4 var(--font-body);
  letter-spacing: 0.2px;
}

/* --- The kancha carousel -------------------------------------------------
   An even, gapped row that wraps around: every marble is the same size with
   real space between them, and the nine loop so there is always one to the
   left and to the right whichever is selected. Each is positioned absolutely
   by its circular distance from the centre (--x / opacity / z-index, set from
   JS in layoutCarousel); the one that wraps to the far side does so with its
   transition off so it teleports instead of flying across. Only the gold ring
   marks the current pick — no size or lift change. */
.pickPanel--striker {
  --kancha-size: 84px;
  width: min(94vw, 500px);
}

.carousel {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  margin-top: 6px;
}

.carouselViewport {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: calc(var(--kancha-size) * 1.45);
  overflow: hidden;
  /* The one edge treatment: a wide, smooth alpha fade on both sides so a
     marble dissolves cleanly as it slides toward the edge — no blur on the
     marbles themselves. */
  --edge-fade: linear-gradient(to right,
    transparent 0%, rgba(0, 0, 0, 0.35) 8%, black 20%,
    black 80%, rgba(0, 0, 0, 0.35) 92%, transparent 100%);
  mask-image: var(--edge-fade);
  -webkit-mask-image: var(--edge-fade);
}

.carouselTrack {
  position: absolute;
  inset: 0;
}

.carouselNav {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--glass-edge);
  background: var(--glass-bg);
  color: var(--ink-mid);
  font-size: 20px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.carouselNav:hover { background: var(--panel-active); color: var(--ink); border-color: var(--line-hover); }
.carouselNav:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.kancha {
  --lift: 0px;
  --scale: 1;
  --hscale: 1;
  --x: 0px;
  --ad: 0;
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: calc(20 - var(--ad));
  width: var(--kancha-size);
  height: var(--kancha-size);
  border-radius: 50%;
  /* Kill the UA <button> background — it's a near-white ButtonFace that shows
     through the sprite's feathered edge as a pale ring. */
  background-color: transparent;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  padding: 0;
  margin: 0;
  border: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Fallback face, in play only until (or unless) the rendered sprite lands. */
  background-image:
    radial-gradient(circle at 33% 24%, #fff 0 5%, rgba(255, 255, 255, 0.7) 9%, transparent 21%),
    radial-gradient(circle at 68% 77%, color-mix(in oklab, var(--c) 30%, #fff) 0 5%, transparent 30%),
    radial-gradient(circle at 42% 38%, color-mix(in oklab, var(--c) 74%, #fff) 0 12%, var(--c) 46%, color-mix(in oklab, var(--c) 60%, #000) 84%, color-mix(in oklab, var(--c) 38%, #000) 100%);
  filter: drop-shadow(0 3px 4px rgba(0, 0, 0, 0.5));
  /* Composed from independent sources — JS-driven horizontal slot (--x),
     CSS :hover/:active (--hscale) — so a hover mid-transition never has to
     fight whatever the layout pass set. The leading translate re-centers the
     element on its own left/top:50%. */
  transform:
    translate(-50%, -50%)
    translateX(var(--x))
    translateY(var(--lift))
    scale(calc(var(--scale) * var(--hscale)));
  transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.4s ease, filter 0.3s ease;
}

.kancha:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}

@media (hover: hover) and (pointer: fine) {
  .kancha:hover {
    --hscale: 1.08;
    z-index: 25;
    filter: drop-shadow(0 5px 6px rgba(0, 0, 0, 0.55)) brightness(1.06);
  }
}
.kancha:active { --hscale: 0.94; }

.kancha[aria-checked="true"] {
  z-index: 26;
  /* A drawn ring, not a bloom: box-shadow traces the circle's own edge
     cleanly (this element is genuinely round, border-radius: 50%), where the
     four stacked drop-shadows this replaced painted a soft, per-marble-tinted
     haze that read as smudged rather than deliberate. One gold ring, sized to
     match the thin linework used everywhere else the game marks something as
     selected or ornamental, plus a single grounding shadow for lift. */
  box-shadow: 0 0 0 2.5px var(--gold);
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.4));
}

/* The one loud bilingual beat on step 2: the chosen kancha's name, right
   under the carousel. Devanagari large, the Latin name a small caption. */
.pickName {
  margin-top: 14px;
  min-height: 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.pickNameHi {
  color: var(--ink);
  font: 500 26px/1.15 var(--font-accent);
}

.pickNameEn {
  color: var(--ink-dim);
  font: 600 11px/1.4 var(--font-body);
  letter-spacing: 0.6px;
}

.modeBack {
  display: block;
  margin: 12px auto 0;
  padding: 6px 12px;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--ink-dim);
  font: 500 12px/1.4 var(--font-body);
  letter-spacing: 0.2px;
  cursor: pointer;
  transition: color 0.2s ease;
}

.modeBack:hover { color: var(--ink); }
.modeBack:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.modeBack.hidden { display: none; }

/* The one explicit way a round begins — everything above it (opponent,
   difficulty, kancha) is a setting; this is the action. Filled and the
   biggest touch target on the screen on purpose, so it reads as the answer to
   "what do I do now" without needing a hint label next to it. */
.startBtn {
  position: relative;
  margin-top: 22px;
  width: min(88vw, 340px);
  padding: 14px 24px;
  border: 0;
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, var(--candy-top) 0%, var(--candy-mid) 46%, var(--candy-bot) 100%);
  color: var(--candy-ink);
  font: 700 16px/1.3 var(--font-display);
  letter-spacing: 0.2px;
  cursor: pointer;
  overflow: hidden;
  isolation: isolate;
  display: block;
  margin-left: auto;
  margin-right: auto;
  /* inner top light + inner bottom shade (the 3D bulge) + a plain drop
     shadow. No spread ring, no border — the shape is carried by the gradient
     and the specular band alone. */
  box-shadow:
    inset 0 2px 1px oklch(100% 0 0 / 0.65),
    inset 0 -9px 14px oklch(52% 0.14 66 / 0.35),
    0 8px 18px oklch(40% 0.1 66 / 0.35);
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}

/* The crisp glassy band across the top — what makes it read as candy, not
   just a gradient. */
.startBtn::before {
  content: "";
  position: absolute;
  left: 4%;
  right: 4%;
  top: 3px;
  height: 44%;
  border-radius: 999px;
  background: linear-gradient(180deg, oklch(100% 0 0 / 0.65), oklch(100% 0 0 / 0) 100%);
  pointer-events: none;
  z-index: -1;
}

@media (hover: hover) and (pointer: fine) {
  .startBtn:hover {
    filter: brightness(1.04);
    box-shadow:
      inset 0 2px 1px oklch(100% 0 0 / 0.75),
      inset 0 -9px 14px oklch(52% 0.14 66 / 0.35),
      0 12px 26px oklch(40% 0.1 66 / 0.4);
  }
}

.startBtn:active { transform: scale(0.98) translateY(1px); }
.startBtn:focus-visible { outline: 2px solid oklch(96% 0.08 92); outline-offset: 3px; }

/* --- Onboarding steps ---------------------------------------------------
   Three screens, one decision each: board (Ring/Pit), opponent, kancha.
   Whichever group is not the current step is display:none, not just hidden —
   that is what lets its reveal animation replay on a revisit. #mpPanel keeps
   its own visibility (toggled whenever opponent mode is "vs friend"). A guest
   who arrived on a room link never sees the board or opponent steps — the
   host chose both — so the .guest class hides the opponent controls and the
   back link on the shared kancha screen. */
#onboardTagline[data-step="mode"] .pickPanel--opponent,
#onboardTagline[data-step="mode"] .pickPanel--striker,
#onboardTagline[data-step="mode"] .continueBtn,
#onboardTagline[data-step="mode"] .startBtn,
#onboardTagline[data-step="mode"] .modeBack,
#onboardTagline[data-step="joining"] .headlineHi,
#onboardTagline[data-step="joining"] .onboardDivider,
#onboardTagline[data-step="joining"] .headline,
#onboardTagline[data-step="joining"] .modeCards,
#onboardTagline[data-step="joining"] .pickPanel--opponent,
#onboardTagline[data-step="joining"] .pickPanel--striker,
#onboardTagline[data-step="joining"] .continueBtn,
#onboardTagline[data-step="joining"] .startBtn,
#onboardTagline[data-step="joining"] .modeBack,
#onboardTagline[data-step="opponent"] .modeCards,
#onboardTagline[data-step="opponent"] .pickPanel--striker,
#onboardTagline[data-step="opponent"] .startBtn,
#onboardTagline[data-step="level"] .modeCards,
#onboardTagline[data-step="level"] .pickPanel--opponent,
#onboardTagline[data-step="level"] .pickPanel--striker,
#onboardTagline[data-step="level"] .continueBtn,
#onboardTagline[data-step="level"] .startBtn,
#onboardTagline[data-step="pick"] .modeCards,
#onboardTagline[data-step="pick"] .pickPanel--opponent,
#onboardTagline[data-step="pick"] .continueBtn { display: none; }

#onboardTagline[data-step="level"] .levelPanel,
#onboardTagline[data-step="level"] .levelContinueBtn { display: block; }

/* The host chose board and opponent and is the one who starts the round; a
   guest arriving on a room link only picks a kancha, then waits. Hide
   everything else on that shared last screen. */
#onboardTagline.guest .pickPanel--opponent,
#onboardTagline.guest .continueBtn,
#onboardTagline.guest .startBtn,
#onboardTagline.guest .modeBack { display: none; }

/* #onboardTagline itself is pointer-events:none (needed so its idle pulse
   never blocks clicks), and stays in the DOM at opacity:0 after a round
   starts rather than being removed — every group below must only accept
   clicks while actually shown, or an in-round click near screen center could
   silently re-trigger startRound() through invisible buttons. Easy to miss
   because a synthetic element.click() ignores pointer-events entirely, and
   only a real pointer/mouse event exposes the gap. Granted on the two panel
   wrappers and the standalone controls rather than on every control
   individually — everything else here is a descendant of one of those and
   inherits it. */
#onboardTagline.show .modeCards,
#onboardTagline.show .pickPanel--opponent,
#onboardTagline.show .pickPanel--striker,
#onboardTagline.show .continueBtn,
#onboardTagline.show .levelPanel,
#onboardTagline.show .levelContinueBtn,
#onboardTagline.show .startBtn,
#onboardTagline.show .modeBack { pointer-events: auto; }

.modeCards {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
  margin-top: 22px;
}

/* Two identical glass panes. The only thing that separates Ring from Pit is
   the line-icon and its accent colour (mud gold / marble sky) — the loud
   per-mode colour washes, the stamp-frame inset, the dirt grain and the
   pointer-tracked foil were all doing the job the label already does. */
.modeCard {
  position: relative;
  width: 244px;
  min-height: 250px;
  padding: 30px 26px 28px;
  border-radius: var(--r-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  font: inherit;
  cursor: pointer;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--glass-top), 0 8px 22px oklch(10% 0.02 70 / 0.38);
  transition: border-color 0.2s ease,
    transform 0.2s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.2s ease;
}

/* Opaque fill wherever the blur can't run. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .modeCard { background: var(--panel-dense); }
}

@media (hover: hover) and (pointer: fine) {
  .modeCard:hover {
    border-color: var(--line-hover);
    transform: translateY(-3px);
    box-shadow: var(--glass-top), 0 14px 30px oklch(10% 0.02 70 / 0.46);
  }
  .modeCard:hover .modeIcon { transform: scale(1.06); }
}

.modeCard:active { transform: scale(0.985); }

.modeCard:focus-visible {
  outline: 2px solid var(--sky-bright);
  outline-offset: 3px;
}

.ornGem { fill: var(--ruby); }

/* Each card carries a real kancha — rendered by the game's own renderer
   (paintModeIcons() in main.js), CSS gradient only until it lands — a different
   warm colour per card, turning slowly under a fixed glint so it reads as a
   marble idly rotating rather than a flat disc. It sits in the mark that names
   the mode: the chalk ring for Ring, a sunk pit for Pit. */
.modeIcon {
  position: relative;
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}

.modeIcon::after {
  content: "";
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background:
    var(--marble-img, none) center / cover no-repeat,
    radial-gradient(circle at 36% 30%, oklch(82% 0.09 82), oklch(68% 0.12 70) 42%, oklch(42% 0.09 58) 100%);
  box-shadow: 0 1px 3px oklch(0% 0 0 / 0.4);
  animation: modeMarbleSpin 16s linear infinite;
}

/* A fixed specular glint over the turning marble — the "light source" that
   makes the rotation read as a sphere. */
.modeIcon::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  pointer-events: none;
  background:
    radial-gradient(circle at 33% 28%, oklch(100% 0 0 / 0.42), transparent 30%),
    radial-gradient(circle at 68% 74%, oklch(0% 0 0 / 0.28), transparent 42%);
}

@keyframes modeMarbleSpin {
  to { rotate: 360deg; }
}

@media (prefers-reduced-motion: reduce) {
  .modeIcon::after { animation: none; }
}

/* Same construction on both: a thin circle around the marble with air between.
   Ring's is raised chalk; Pit's is a recessed rim with shadow pooling at its
   base. Equal visual weight, opposite depth. */
.modeCard[data-mode="ring"] .modeIcon {
  border: 1.5px solid oklch(92% 0.012 88 / 0.5);
}

.modeCard[data-mode="pit"] .modeIcon {
  border: 1.5px solid oklch(34% 0.025 60 / 0.85);
  background: radial-gradient(circle at 50% 44%, oklch(22% 0.03 52 / 0.55), transparent 70%);
  box-shadow: inset 0 4px 9px oklch(0% 0 0 / 0.5);
}

.modeCard[data-mode="pit"] .modeIcon::after {
  transform: translateY(1px);
}

.modeText {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 16px;
}

/* The keyboard hint sits in the card's top-right corner, not in the title —
   it's a shortcut cue, not part of the name. */
.modeKbd {
  position: absolute;
  top: 12px;
  right: 12px;
}

.modeCard .modeName {
  color: var(--ink);
  font: 500 25px/1.2 var(--font-display);
  white-space: nowrap;
}

.modeCard .modeNameHi {
  display: block;
  margin-top: 5px;
  color: var(--ink-mid);
  font: 500 16px/1.3 var(--font-accent);
}

.modeCard .rules {
  display: block;
  margin-top: 14px;
  max-width: 30ch;
  color: var(--ink-mid);
  font: 400 14px/1.62 var(--font-body);
}

/* Short viewports: two stacked .pickPanel cards plus Start plus the back link
   is the tallest thing onboarding ever shows. #onboardTagline is absolutely
   positioned and cannot scroll, so trim rather than clip. */
@media (max-height: 720px) {
  .pickPanel { margin-top: 10px; padding: 14px 16px 16px; }
  .pickPanel--striker { --kancha-size: 66px; }
  .pickName { margin-top: 6px; min-height: 30px; }
  .startBtn { margin-top: 14px; }
  .modeBack { margin-top: 6px; }
  .modeCard { min-height: 216px; padding: 22px 22px 22px; }
  #mpPanel { margin-top: 16px; }
}

/* A phone held landscape (the only phone orientation the game renders in — see
   #rotateGate) is very short: ~340-430px tall. #onboardTagline can't scroll, so
   the whole flow has to be squeezed hard. The mode cards already sit side by
   side at this width; the fix is purely vertical — drop the headline sub-line,
   flatten the cards, shrink the carousel. Gated on `hover: none` so a short
   desktop window is left alone. */
@media (hover: none) and (max-height: 500px) {
  #onboardTagline { top: 50%; }
  #onboardTagline .headlineHi { font-size: clamp(28px, 6vw, 36px); }
  #onboardTagline .headline { display: none; }
  .onboardDivider { margin-top: 6px; }
  .modeCards { margin-top: 12px; gap: 10px; }
  .modeCard { min-height: 0; padding: 16px 20px; width: 232px; }
  .modeCard .modeIcon { width: 44px; height: 44px; }
  .modeCard .modeIcon::after,
  .modeCard .modeIcon::before { width: 32px; height: 32px; }
  .modeCard .modeText { margin-top: 10px; }
  .modeCard .modeName { font-size: 21px; }
  .modeCard .modeNameHi { display: none; }
  .modeCard .rules { margin-top: 9px; font-size: 12.5px; line-height: 1.5; }

  .pickPanel { margin-top: 8px; }
  .panelLabel, .pickPrompt { margin-bottom: 8px; }
  .pickPanel--striker { --kancha-size: 52px; }
  .pickName { margin-top: 6px; min-height: 26px; }
  .pickNameHi { font-size: 22px; }
  .startBtn, .continueBtn, .levelContinueBtn { margin-top: 12px; padding: 11px 22px; }
  .levelPanel { margin-top: 8px; }
  .levelRail { margin-top: 10px; }
  .levelNode { width: 44px; height: 44px; flex-basis: 44px; }
  .levelDetail { min-height: 88px; margin-top: 8px; padding-top: 9px; }
  #levelNameEn { font-size: 17px; }
  #levelObjective { margin-top: 4px; font-size: 12.5px; }
  #levelMastery { margin-top: 4px; }
  .modeBack { margin-top: 4px; }
  #mpPanel { margin-top: 10px; gap: 8px; }
}

/* Settings and rules panels sit top-right with no height bound and can be
   taller than a short landscape phone. Cap them to the viewport and let them
   scroll inside. */
@media (max-height: 560px) {
  #tunePanel, #rulesPanel {
    max-height: calc(100dvh - 72px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

#turnHint {
  position: absolute;
  bottom: 22px;
  left: 50%;
  transform: translate(-50%, 0);
  z-index: 10;
  padding: 7px 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background-color: var(--panel);
  background-image: var(--sheen);
  color: var(--ink-dim);
  font: 500 13px/1 var(--font-body);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease;
}

#turnHint.show {
  opacity: 1;
}

#hud {
  position: absolute;
  top: 12px;
  right: 12px;
  display: flex;
  gap: 8px;
  z-index: 10;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

#hud.show {
  opacity: 1;
  pointer-events: auto;
}

/* Same treatment as .jar.occluded (below, in the jars section) — a marble
   resting or flying through the top corners would otherwise disappear
   behind these. Still clickable at a glance if the player really means to
   through the dimness; this is about seeing the marble, not locking anyone
   out of Reset. Comes after .show so it wins when both are set. */
#hud.occluded { opacity: 0.12; }

#hud button {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
  padding: 8px 11px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-hover);
  background-color: var(--panel);
  background-image: var(--sheen);
  color: var(--ink);
  cursor: pointer;
  line-height: 1;
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.08), 0 4px 12px oklch(10% 0.02 40 / 0.4);
  transition: border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease, transform 0.12s ease;
}

#hud button:hover {
  background-color: var(--panel-active);
  border-color: var(--gold-dim);
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.12), 0 6px 18px oklch(85% 0.155 82 / 0.3);
}

#hud button:active {
  transform: scale(0.95);
}

#hud button:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* Touch: no hover to reveal these and no cursor to aim with, so grow every HUD
   button to a comfortable finger target (~44px). The .kbd chips are already
   hidden on touch, so the buttons are icon-only and this just pads them out to
   a square. Same for the scoreboard chips' tap-through feel — left as-is, they
   are display-only. */
@media (hover: none) {
  #hud { gap: 10px; }
  #hud button {
    min-width: 44px;
    min-height: 44px;
    padding: 11px;
    justify-content: center;
  }
}

/* One icon language for the HUD: three inline SVGs on the same 18px grid and
   the same stroke weight, replacing the old mix of a color emoji (🔊) and two
   text glyphs (⟲ / ⚙︎) that never matched each other. */
.hudIc {
  display: block;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.hudIc .slash { display: none; }
#muteBtn.isMuted .hudIc .wave { display: none; }
#muteBtn.isMuted .hudIc .slash { display: inline; }

/* Keyboard-shortcut hint chip. One fixed size and one neutral treatment
   everywhere it appears (HUD buttons, mode-card names, Start) — a translucent
   dark key with a hairline, legible on the dark glass and on the gold Start
   button alike, rather than the earlier em-scaled teal pill that came out a
   different size and clashed on gold. Hidden on touch devices. */
.kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: 19px;
  height: 19px;
  padding: 0 4px;
  border-radius: 5px;
  border: 1px solid oklch(100% 0 0 / 0.3);
  background: oklch(0% 0 0 / 0.32);
  color: oklch(97% 0.01 90);
  font: 700 11px/1 var(--font-body);
  letter-spacing: 0.02em;
  vertical-align: middle;
  flex: none;
}

@media (hover: none) {
  .kbd { display: none; }
}

#scoreboard {
  position: absolute;
  top: 12px;
  left: 12px;
  display: flex;
  gap: 8px;
  z-index: 10;
  opacity: 0;
  transition: opacity 0.5s ease;
}

#scoreboard.show {
  opacity: 1;
}
/* See #hud.occluded's comment. */
#scoreboard.occluded { opacity: 0.12; }

.player-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background-color: var(--panel);
  background-image: var(--sheen);
  color: var(--ink-dim);
  font: 500 14px/1 var(--font-body);
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.06);
  transition: border-color 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease;
}

.player-chip .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
  /* In pit mode this dot wears the player's own kancha, and one of those is
     very nearly black — a hairline keeps it from vanishing into the panel. */
  box-shadow: 0 0 0 1px rgba(255, 240, 214, 0.25);
}

.player-chip .score {
  font-weight: 700;
  color: var(--ink);
}

.player-chip.active {
  border-color: var(--gold-dim);
  background-color: var(--panel-active);
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.1);
}

.player-chip.active .name {
  color: var(--ink);
}

/* --- Winnings jars -------------------------------------------------------- */
#jars {
  position: absolute;
  inset: 0;
  z-index: 8;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s ease;
}
#jars.show { opacity: 1; }

/* The jar is an SVG (see #jarGlass) drawn over the pile of marble divs — a
   clear ribbed PET body with a red screw cap. .jar is the positioning box,
   .jarSvg the plastic, .jarPile the contents clipped to the barrel interior.
   Fills are driven by custom properties (which inherit into the <use>-cloned
   symbol, where a descendant selector would not) so .jar--winner can re-tint
   the body without touching the cap. */
.jar {
  position: absolute;
  bottom: 16px;
  width: 92px;
  height: 136px;
  transition: opacity 0.3s ease,
    transform 0.75s cubic-bezier(0.2, 0.9, 0.25, 1);
}
.jar[data-slot="left"] { left: 18px; }
.jar[data-slot="right"] { right: 18px; }

/* A marble has rolled into this corner — get out of its way. */
.jar.occluded { opacity: 0.12; }

/* Round over: the winner's jar swells into the middle of the screen, the
   loser's recedes. --win-dx/--win-dy are the delta to viewport centre. */
.jar--winner {
  transform: translate(var(--win-dx, 0px), var(--win-dy, 0px)) scale(1.7);
  z-index: 40;
  /* frosted a little so it holds against the dirt at size */
  --jar-wall: oklch(90% 0.015 230 / 0.2);
  --jar-wall-hi: oklch(100% 0 0 / 0.34);
  --jar-edge: oklch(80% 0.02 230 / 0.5);
}
.jar--dimmed { opacity: 0.16; }
.jar--winner .jarSvg {
  filter: drop-shadow(0 16px 34px oklch(6% 0.02 55 / 0.55));
}

.jarSvg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  filter: drop-shadow(0 6px 14px oklch(10% 0.02 70 / 0.38));
}

/* --- the clear PET body: a near-invisible outline with the contents showing
   through, lit by two soft studio streaks and a light-catching edge --- */
.jarGroundShadow { fill: oklch(22% 0.02 60 / 0.26); }
.jarGlassBody {
  fill: var(--jar-wall, oklch(90% 0.012 230 / 0.05));
  stroke: var(--jar-edge, oklch(56% 0.02 230 / 0.2));
  stroke-width: 0.7;
}
.jarGlassEdge {
  fill: none;
  stroke: var(--jar-wall-hi, oklch(100% 0 0 / 0.22));
  stroke-width: 0.9;
}
.jarGlassHi {
  fill: none;
  stroke: var(--jar-wall-hi, oklch(100% 0 0 / 0.4));
  stroke-width: 7;
  stroke-linecap: round;
}
.jarGlassHiThin {
  fill: none;
  stroke: var(--jar-wall-hi, oklch(100% 0 0 / 0.22));
  stroke-width: 3;
  stroke-linecap: round;
}
/* Kept faint: the jarSvg paints OVER the pile, so this ellipse lands on top
   of the marbles' lower halves — any darker and they read as dipped in smoke. */
.jarInnerBase   { fill: oklch(34% 0.02 230 / 0.07); }
.jarInnerBaseHi { fill: oklch(100% 0 0 / 0.10); }
.jarHeel {
  fill: none;
  stroke: oklch(30% 0.02 230 / 0.22);
  stroke-width: 2;
  stroke-linecap: round;
}
.jarBaseRing { fill: none; stroke: oklch(100% 0 0 / 0.12); stroke-width: 1.3; }
.jarGate {
  fill: oklch(40% 0.02 230 / 0.22);
  stroke: oklch(100% 0 0 / 0.1);
  stroke-width: 0.6;
}
/* neck: the clear ring left visible below the cap skirt, with its threads */
.jarNeck {
  fill: oklch(92% 0.01 230 / 0.05);
  stroke: oklch(58% 0.02 230 / 0.22);
  stroke-width: 0.7;
}
.jarThread {
  fill: none;
  stroke: oklch(100% 0 0 / 0.2);
  stroke-width: 1.3;
  stroke-linecap: round;
}

/* --- the fluted screw cap: its own <svg> so it can lift and twist home. The
   colour is the player's (--jar-cap, set per jar in js/main.js); the shading
   is fixed white/black over that so it reads as moulded plastic at any hue. --- */
.jarCapSvg {
  position: absolute;
  left: 50%;
  top: 2px;
  width: 99%;
  height: auto;
  transform: translateX(-50%);
  transition: transform 0.3s cubic-bezier(0.3, 0, 0.25, 1);
  z-index: 3;
  pointer-events: none;
  filter: drop-shadow(0 3px 5px oklch(10% 0.02 40 / 0.4));
}
/* Open: lift the cap and tip it ajar, the way you'd rest a lid on the rim. */
.jar--open .jarCapSvg {
  transform: translateX(-50%) translate(9px, -26px) rotate(6deg);
  transition: transform 0.42s cubic-bezier(0.3, 0.1, 0.3, 1);
}
/* Close: set it back on and twist it home — a short counter-turn, no full spin
   (a photoreal top would strobe through 360deg). */
.jar--closing .jarCapSvg {
  animation: jarCapScrew 0.5s cubic-bezier(0.4, 0, 0.3, 1) both;
}
@keyframes jarCapScrew {
  0%   { transform: translateX(-50%) translate(9px, -26px) rotate(6deg); }
  55%  { transform: translateX(-50%) translate(0, -3px) rotate(-15deg); }
  100% { transform: translateX(-50%) translate(0, 0) rotate(0); }
}

.jarCapSkirt {
  fill: var(--jar-cap, oklch(55% 0.16 28));
  /* soft, close to the fill — a hard dark outline is a drawing's edge, not a
     moulded object's */
  stroke: color-mix(in oklch, var(--jar-cap, oklch(55% 0.16 28)) 72%, #000);
  stroke-width: 0.5;
}
.jarFlutes {
  fill: none;
  stroke: color-mix(in oklch, var(--jar-cap, oklch(55% 0.16 28)) 46%, #000);
  stroke-width: 0.7;
  opacity: 0.5;
}
.jarFlutesHi {
  fill: none;
  stroke: color-mix(in oklch, var(--jar-cap, oklch(55% 0.16 28)) 65%, #fff);
  stroke-width: 0.7;
  opacity: 0.45;
}
.jarCapSkirtShade { fill: url(#jarCapSkirtForm); stroke: none; }
.jarCapSkirtVShade { fill: url(#jarCapSkirtV); stroke: none; }
/* soft matte sheen — matte plastic, so broad and low, not a hard specular */
.jarCapSheen {
  fill: none;
  stroke: oklch(100% 0 0 / 0.16);
  stroke-width: 4;
  stroke-linecap: round;
}
.jarCapTop {
  fill: color-mix(in oklch, var(--jar-cap, oklch(55% 0.16 28)) 90%, #fff);
  stroke: color-mix(in oklch, var(--jar-cap, oklch(55% 0.16 28)) 62%, #000);
  stroke-width: 0.6;
}
.jarCapRing {
  fill: none;
  stroke: color-mix(in oklch, var(--jar-cap, oklch(55% 0.16 28)) 70%, #000);
  stroke-width: 0.7;
  opacity: 0.32;
}
.jarCapPip {
  fill: color-mix(in oklch, var(--jar-cap, oklch(55% 0.16 28)) 58%, #000);
  opacity: 0.5;
}
.jarCapGloss { fill: oklch(100% 0 0 / 0.34); }

@media (prefers-reduced-motion: reduce) {
  .jar--closing .jarCapSvg { animation-duration: 0.2s; }
}

.jarPile {
  position: absolute;
  left: 9%;
  right: 9%;
  top: 26%;
  bottom: 11%;
  /* Bottom radius matches the two corner-blocker discs in buildJarWorld
     (0.23 × pile width) — the physics keeps marbles out of the very zone the
     clip cuts, so nothing visible ever crosses the edge and gets sliced. */
  clip-path: inset(0 round 12% 12% 24% 24%);
}

.jarMarble {
  position: absolute;
  border-radius: 50%;
  background-size: cover;
  background-position: center;
  box-shadow: 0 1px 2px oklch(0% 0 0 / 0.4), inset -2px -3px 4px oklch(0% 0 0 / 0.25);
}

/* the marble in flight from the field to a jar */
.captureFly {
  position: fixed;
  z-index: 30;
  border-radius: 50%;
  background-size: cover;
  background-position: center;
  pointer-events: none;
  box-shadow: 0 3px 10px oklch(0% 0 0 / 0.4);
  transition:
    left 440ms cubic-bezier(0.4, 0, 0.7, 1),
    top 440ms cubic-bezier(0.55, 0, 1, 0.5),
    width 440ms ease,
    height 440ms ease,
    opacity 90ms linear 360ms;
}

@media (max-height: 640px), (max-width: 560px) {
  .jar { width: 72px; height: 106px; bottom: 10px; }
  .jar[data-slot="left"] { left: 12px; }
  .jar[data-slot="right"] { right: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .captureFly { transition-duration: 180ms, 180ms, 180ms, 180ms, 1ms; }
}

#roundOverBanner {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 6;
  text-align: center;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.4s ease;
}

#roundOverBanner.hidden {
  display: none;
}

/* With the winner's jar filling the centre, the text sits below it, and the
   confetti overlay lifts above the jar so the burst is not hidden by it. */
#stage.round-won #roundOverBanner {
  top: auto;
  bottom: 13%;
  transform: translate(-50%, 0);
}
#stage.round-won #overlay {
  z-index: 42;
}

/* One dark surface behind the whole end-of-round message. The text used to
   sit straight on the dirt with only a shadow, which washed out over the
   lighter patches — see the campaign fail state especially. */
#roundOverCard {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  max-width: min(90vw, 446px);
  padding: 16px 26px 18px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background-color: var(--panel-dense);
  background-image: var(--sheen);
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.08), 0 18px 46px oklch(10% 0.02 40 / 0.5);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  pointer-events: none;
}

#roundOverText {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font: 400 clamp(24px, 4vw, 31px)/1.25 var(--font-display);
  color: var(--ink);
  text-shadow: 0 1px 2px oklch(10% 0.02 30 / 0.5);
  text-wrap: balance;
}

.roundOverOrnament {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  color: var(--gold-dim);
  opacity: 0.7;
}

#roundOverHint {
  margin-top: 8px;
  font: 400 15px/1.4 var(--font-body);
  color: var(--ink-dim);
}

/* Campaign stars and the Retry / Next buttons — an inner section of the card
   above, split off by a hairline. Interactive inside the otherwise
   click-through banner. */
#levelResult {
  width: 100%;
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--line);
  pointer-events: auto;
}
#levelResult.hidden { display: none; }
#levelResultMarks {
  color: var(--gold);
  font: 700 24px/1 var(--font-display);
  letter-spacing: 5px;
  text-shadow: 0 2px 8px oklch(80% 0.13 85 / 0.25);
}
#levelResult.failed #levelResultMarks { color: var(--ink-faint); text-shadow: none; }
.levelResultActions {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 13px;
}
.levelResultActions button {
  min-width: 116px;
  min-height: 40px;
  border: 1px solid var(--line-hover);
  border-radius: var(--r-pill);
  padding: 9px 14px;
  background: var(--panel-active);
  color: var(--ink);
  font: 600 13px/1.2 var(--font-body);
  cursor: pointer;
}
.levelResultActions button:last-child {
  border-color: var(--gold-dim);
  color: var(--gold);
}
.levelResultActions button:hover { border-color: var(--gold-dim); }
.levelResultActions button:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.levelResultActions .hidden { display: none; }

#tunePanel {
  position: absolute;
  top: 60px;
  right: 12px;
  width: 260px;
  background: var(--panel-dense);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 14px 16px;
  color: var(--ink);
  font: 400 13px/1.4 var(--font-body);
  z-index: 10;
}

#tunePanel.hidden { display: none; }

/* Same corner, same box, same heading style as #tunePanel — see its comment
   above. Duplicated rather than shared via a class because the two never
   need to change together and this is the only thing #rulesPanel needs. */
#rulesPanel {
  position: absolute;
  top: 60px;
  right: 12px;
  width: 280px;
  background: var(--panel-dense);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 14px 16px;
  color: var(--ink);
  font: 400 13px/1.4 var(--font-body);
  z-index: 10;
}
#rulesPanel.hidden { display: none; }
#rulesPanel h3 {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--red-dim);
  font-size: 14px;
  font-weight: 600;
  opacity: 0.9;
}
#rulesPanel p { margin: 0; opacity: 0.92; }

#tunePanel h3 {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--red-dim);
  font-size: 14px;
  font-weight: 600;
  opacity: 0.9;
}

.tuneMark {
  width: 13px;
  height: 13px;
  color: var(--gold-dim);
  opacity: 0.7;
}

#tunePanel label {
  display: block;
  margin-bottom: 10px;
}

#tunePanel input[type="range"] {
  width: 100%;
  margin-top: 4px;
}

#tunePanel label.checkbox {
  display: flex;
  align-items: center;
  gap: 6px;
}

#tunePanel label.checkbox input {
  width: auto;
  margin: 0;
}

#tunePanel span {
  float: right;
  opacity: 0.65;
}

/* --- Pill selectors --------------------------------------------------------
   Individual glass pills, not a segmented tab group: the selected segment of
   a segmented control read as a faint smudge; a standalone pill that fills
   with gold when picked does not. The opponent picker (onboarding) and the
   bot-difficulty control (in the settings panel) share the same pill. */
.opponentPick,
#difficultyPick {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
}

.opponentOpt,
.diffOpt {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--glass-top);
  color: var(--ink-mid);
  font: 600 0.86rem/1 var(--font-body);
  letter-spacing: 0.1px;
  padding: 9px 16px;
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
}

.opponentOpt:hover,
.diffOpt:hover {
  color: var(--ink);
  border-color: var(--line-hover);
}

.opponentOpt.active,
.diffOpt.active {
  background-color: color-mix(in oklch, var(--sky) 26%, var(--panel-active));
  background-image: var(--sheen);
  border-color: var(--sky);
  color: var(--ink);
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.16);
}

.opponentOpt:focus-visible,
.diffOpt:focus-visible {
  outline: 2px solid var(--sky-bright);
  outline-offset: 2px;
}

/* Bot difficulty lives in the settings panel now, not onboarding. It only
   means anything against the computer, so the whole row is hidden for a
   pass-and-play or online game. */
#difficultyRow {
  margin-bottom: 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
#difficultyRow.hidden { display: none; }

#tunePanel .tuneRowLabel {
  float: none;
  display: block;
  margin-bottom: 8px;
  opacity: 1;
  color: var(--ink-dim);
  font: 600 12px/1.4 var(--font-body);
  letter-spacing: 0.3px;
}

#tunePanel #difficultyPick {
  justify-content: flex-start;
  gap: 5px;
}

#tunePanel .diffOpt {
  font-size: 0.72rem;
  padding: 5px 11px;
}

/* Continue: advances the opponent step to the kancha step. Blue candy — the
   same gloss language as the gold Start button one hue over, so the two
   "advance" actions read as a pair getting closer to play. */
.continueBtn,
.levelContinueBtn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: block;
  margin: 22px auto 0;
  border: 0;
  border-radius: var(--r-pill);
  width: min(88vw, 300px);
  padding: 13px 24px;
  background: linear-gradient(180deg, var(--candy-sky-top) 0%, var(--candy-sky-mid) 46%, var(--candy-sky-bot) 100%);
  color: var(--candy-sky-ink);
  font: 700 16px/1.3 var(--font-display); letter-spacing: 0.2px;
  cursor: pointer;
  box-shadow:
    inset 0 2px 1px oklch(100% 0 0 / 0.6),
    inset 0 -9px 14px oklch(46% 0.15 66 / 0.35),
    0 8px 18px oklch(36% 0.13 62 / 0.4);
  transition: filter 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.continueBtn::before,
.levelContinueBtn::before {
  content: "";
  position: absolute; left: 4%; right: 4%; top: 3px; height: 44%;
  border-radius: 999px;
  background: linear-gradient(180deg, oklch(100% 0 0 / 0.6), oklch(100% 0 0 / 0) 100%);
  pointer-events: none; z-index: -1;
}
.continueBtn:active,
.levelContinueBtn:active { transform: scale(0.98) translateY(1px); }
.continueBtn:focus-visible,
.levelContinueBtn:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: 3px; }
@media (hover: hover) and (pointer: fine) {
  .continueBtn:hover,
  .levelContinueBtn:hover {
    filter: brightness(1.04);
    box-shadow:
      inset 0 2px 1px oklch(100% 0 0 / 0.7),
      inset 0 -9px 14px oklch(46% 0.15 66 / 0.35),
      0 12px 26px oklch(36% 0.13 62 / 0.46);
  }
}

.levelContinueBtn { display: none; }

/* --- Multiplayer -------------------------------------------------------- */
#mpPanel { margin-top: 16px; display: flex; flex-direction: column; align-items: center; gap: 12px; }
#mpPanel.hidden { display: none; }
#mpInvite { display: flex; flex-direction: column; align-items: center; gap: 8px; }
/* --ink-faint (45% opacity) is right for a caption sitting on a dark HUD
   panel, which is where it is used elsewhere. This label floats directly on
   the light parchment with no panel behind it, same as .pickPromptEn above —
   matching that one's --ink-dim (75%) rather than reusing --ink-faint is what
   keeps it readable there. */
.mpLabel { font-size: 0.78rem; letter-spacing: 0.2px; color: var(--ink-dim); }
.mpLinkRow { display: flex; gap: 6px; }
#mpLink {
  width: min(46vw, 320px);
  background: var(--panel-dense); color: var(--ink);
  border: 1px solid var(--line); border-radius: 10px;
  padding: 7px 10px; font-size: 0.88rem; font-family: ui-monospace, Menlo, monospace;
}
#mpCopy {
  background: var(--panel-active); border: 1px solid var(--line-hover);
  color: var(--gold); border-radius: 10px; padding: 7px 14px; cursor: pointer;
  font: 500 0.88rem/1 var(--font-body);
}
/* The status pill mirrors the scoreboard chips so the two read as one HUD. */
#mpStatus {
  position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
  background: var(--panel-dense); border: 1px solid var(--line);
  color: var(--ink-mid);
  padding: 6px 14px; border-radius: 999px;
  font: 500 0.8rem/1 var(--font-body); letter-spacing: 0.1px;
  pointer-events: none; z-index: 6;
}
#mpStatus.hidden, #mpPanel.hidden, #mpInvite.hidden, #mpWait.hidden { display: none; }
#mpStatus.warn { border-color: var(--red-dim); color: var(--red); }

/* The campaign objective sits under the turn pill. Keeping the goal and the
   live count together makes the shot limit legible without another corner
   panel competing with the scoreboard and controls. */
#levelHud {
  position: absolute;
  top: 50px;
  left: 50%;
  z-index: 6;
  display: flex;
  align-items: baseline;
  gap: 9px;
  transform: translateX(-50%);
  padding: 7px 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background-color: var(--panel-dense);
  background-image: var(--sheen);
  color: var(--ink-mid);
  font: 500 12px/1.2 var(--font-body);
  white-space: nowrap;
  pointer-events: none;
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.07);
}
#levelHud.hidden { display: none; }
#levelHudName { color: var(--gold); font-weight: 700; }
#levelHudProgress { color: var(--ink-mid); }

#hud button:disabled {
  cursor: not-allowed;
  opacity: 0.4;
  box-shadow: none;
}
/* Not your turn: the whole field stops inviting a drag. */
#stage.notMyTurn #scene { cursor: not-allowed; }

@media (max-width: 700px), (max-height: 500px) {
  #levelHud {
    top: 48px;
    max-width: calc(100vw - 24px);
    gap: 6px;
    padding: 6px 10px;
    font-size: 10.5px;
  }
}

/* --- How to play -------------------------------------------------------
   A first-run card over a blurred stage. It sits above the onboarding
   tagline (z 40 vs 5) so the menu can already be fading in behind the
   blur while the reader takes this in. Reopened from #howtoOpen or `?`. */
#howto {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: oklch(10% 0.02 55 / 0.62);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
#howto.hidden { display: none; }

.howtoCard {
  width: min(92vw, 440px);
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  padding: 24px 26px 22px;
  text-align: center;
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-lg);
  background-color: var(--panel);
  background-image: var(--sheen);
  box-shadow: var(--glass-top), 0 24px 60px oklch(6% 0.02 40 / 0.62);
  animation: revealUp 0.5s cubic-bezier(0.23, 1, 0.32, 1) both;
}

.howtoCard .onboardDivider { margin-top: 0; }

.howtoCard h2 {
  margin: 8px 0 0;
  color: var(--ink);
  font: 600 19px/1.35 var(--font-display);
}
.howtoCard h2 span {
  display: block;
  color: var(--gold);
  font: 400 22px/1.3 var(--font-accent);
}

.howtoList {
  margin: 16px 0 20px;
  padding: 0;
  list-style: none;
  text-align: left;
}
.howtoList li {
  position: relative;
  margin-top: 12px;
  padding-left: 20px;
  color: var(--ink-dim);
  font: 400 13.5px/1.55 var(--font-body);
}
.howtoList li:first-child { margin-top: 0; }
.howtoList li::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 8px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold-dim);
}
.howtoList b { color: var(--ink-mid); font-weight: 600; }

#howtoClose {
  min-height: 42px;
  padding: 10px 22px;
  border: 1px solid var(--line-hover);
  border-radius: var(--r-pill);
  background-color: var(--panel-active);
  background-image: var(--sheen);
  color: var(--gold);
  font: 600 13px/1.2 var(--font-body);
  cursor: pointer;
}
#howtoClose:hover { border-color: var(--gold-dim); }
#howtoClose:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: 3px; }

/* Level brief — the objective card over the arena at the start of a campaign
   round. Same modal shell as #howto (z 40, blurred scrim), a tighter card. */
#levelBrief {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: oklch(10% 0.02 55 / 0.62);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
#levelBrief.hidden { display: none; }

.levelBriefCard {
  width: min(92vw, 400px);
  padding: 22px 26px 20px;
  text-align: center;
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-lg);
  background-color: var(--panel);
  background-image: var(--sheen);
  box-shadow: var(--glass-top), 0 24px 60px oklch(6% 0.02 40 / 0.62);
  animation: revealUp 0.5s cubic-bezier(0.23, 1, 0.32, 1) both;
}
.levelBriefCard .onboardDivider { margin-top: 0; }

.levelBriefKicker {
  margin-top: 6px;
  color: var(--ink-dim);
  font: 600 11px/1.3 var(--font-body);
  letter-spacing: 1.2px;
  text-transform: uppercase;
}
.levelBriefTitle {
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  gap: 9px;
  margin-top: 7px;
}
#levelBriefHi { display: none; }
#levelBriefName { color: var(--ink); font: 600 20px/1.3 var(--font-display); }
#levelBriefObjective {
  max-width: 40ch;
  margin: 12px auto 0;
  color: var(--ink-dim);
  font: 400 14px/1.55 var(--font-body);
}
#levelBriefMastery {
  margin-top: 10px;
  color: var(--gold);
  font: 600 11px/1.4 var(--font-body);
  letter-spacing: 0.2px;
}
#levelBriefStart {
  margin-top: 18px;
  min-height: 42px;
  padding: 10px 26px;
  border: 1px solid var(--line-hover);
  border-radius: var(--r-pill);
  background-color: var(--panel-active);
  background-image: var(--sheen);
  color: var(--gold);
  font: 600 13px/1.2 var(--font-body);
  cursor: pointer;
}
#levelBriefStart:hover { border-color: var(--gold-dim); }
#levelBriefStart:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: 3px; }

/* Menu-only reopen affordance, shown by the same sibling rule that drives
   #onboardBackdrop so it tracks the tagline with no JS. */
#howtoOpen {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 6;
  display: none;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--panel-dense);
  color: var(--ink-dim);
  font: 500 12px/1 var(--font-body);
  cursor: pointer;
}
#onboardTagline.show ~ #howtoOpen { display: inline-flex; }
#howtoOpen .hudIc { width: 15px; height: 15px; }
#howtoOpen:hover { color: var(--ink); border-color: var(--line-hover); }
#howtoOpen:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: 3px; }

@media (hover: none) { #howtoClose, #howtoOpen, #levelBriefStart { min-height: 44px; } }

@media (prefers-reduced-motion: reduce) {
  .howtoCard,
  .levelBriefCard { animation: none; }
}

@media (max-width: 700px), (max-height: 500px) {
  .howtoCard { padding: 18px 18px 16px; }
  .howtoCard h2 { font-size: 16px; }
  .howtoCard h2 span { font-size: 19px; }
  .howtoList li { font-size: 12.5px; }
  #howtoOpen { bottom: 10px; font-size: 11px; }
}
