/* =============================================================================
 * SUPERMINE ADVENTURE — style.css                            THE BASE LAYER
 * -----------------------------------------------------------------------------
 * Fullscreen canvas + a DOM overlay, and the shared vocabulary every screen in
 * the game is built out of. `style-adventure.css` sits on top of this file and
 * re-uses all of it: .sm-panel, .sm-stripe, .sm-btn, .sm-cell, .sm-toast, the
 * .sm-start title overlay and every --sm-* custom property are defined HERE.
 *
 * DESIGN LANGUAGE — "heavy industrial"
 *   * brushed dark metal panels with a hard top bevel and a black bottom edge
 *   * hazard/warning stripes as the accent on anything that means "machinery"
 *   * amber-gold for money, cyan for progress/tech, red for danger
 *   * chunky monospace numerals (tabular) so counters never reflow
 *   * THE CENTRE OF THE SCREEN STAYS EMPTY. Everything is pinned to an edge.
 *     That matters more in portrait than in landscape: the strip of rock you
 *     are aiming at is the middle of the screen.
 *
 * A LARGE PART OF THIS FILE IS A GRAVESTONE, and it is marked as one rather
 * than deleted. The game shipped for a while with a second mode — a 60-second
 * time attack — and the rules for its HUD are still here: the score strip
 * (.sm-topbar), the countdown (.sm-center/.sm-clock), the upgrade rail
 * (.sm-rail), the haul tally (.sm-tally), the steering pads (.sm-pads), the
 * zone banner, the pause card, the end-of-run summary and the high-score
 * table. NOTHING BUILDS THAT DOM ANY MORE — js/ui.js builds the title gate and
 * the fps readout and nothing else — so every one of those rules is inert.
 *
 * They are kept because the panel/button/cell treatment they demonstrate IS
 * the base layer, and because a splash screen is the obvious next thing to
 * build out of it. Delete them together, or repurpose them, but do not pick
 * at them one at a time: the shared pieces are threaded through the dead ones.
 *
 * #ui-root is pointer-events:none so drags pass through to the canvas; only
 * the buttons and the overlays opt back in.
 *
 * Class prefix convention: every UI class starts with `sm-`.
 * Layout switches, all toggled from ui.js on resize, no JS pixel maths:
 *   `.sm-compact`  <900px wide or <520px tall
 *   `.sm-tiny`     <420px wide
 *   `.sm-portrait` taller than it is wide — NOT a synonym for compact: a
 *                  tablet upright is portrait but roomy, a phone on its side
 *                  is compact but has no vertical room at all.
 * ========================================================================== */

:root {
  --sm-bg:        #0b0a0d;

  /* metal */
  --sm-panel:     linear-gradient(180deg, rgba(38,42,50,0.90) 0%, rgba(20,23,28,0.92) 42%, rgba(13,15,19,0.94) 100%);
  --sm-panel-flat: rgba(16, 18, 23, 0.88);
  --sm-edge-hi:   rgba(255, 255, 255, 0.16);
  --sm-edge-lo:   rgba(0, 0, 0, 0.72);
  --sm-border:    rgba(126, 140, 158, 0.26);

  /* type */
  --sm-text:      #eef3f8;
  --sm-dim:       #8e9bab;
  --sm-dimmer:    #5c6674;

  /* accents */
  --sm-gold:      #ffc21f;
  --sm-gold-hot:  #fff0a8;
  --sm-cyan:      #66e2ff;
  --sm-cyan-hot:  #c4f6ff;
  --sm-orange:    #ff8a1f;
  --sm-red:       #ff5a48;
  --sm-green:     #58e08a;

  --sm-radius:    6px;
  --sm-font:      ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --sm-mono:      ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Roboto Mono", monospace;

  --sm-hazard: repeating-linear-gradient(
      135deg,
      #f0b31b 0 8px,
      #171512 8px 16px
  );

  /* --- LAYOUT METRICS ---------------------------------------------------
   * The whole top of the HUD is placed off these two, so a breakpoint that
   * wants a shorter score strip moves the clock, the buttons and the upgrade
   * rail with one declaration instead of five.
   *
   * They are declared on :root rather than on #ui-root deliberately — the
   * layout classes (.sm-compact etc.) live ON #ui-root, and a class selector
   * cannot outrank an #id one. Defaults up here, overrides on the class.
   * ------------------------------------------------------------------- */
  --sm-top-y:      8px;      /* inset of the score strip from the top edge */
  --sm-top-h:     48px;      /* its height — ONE LINE, never two */
  --sm-gutter:    10px;      /* side inset shared by the strip and buttons  */
  --sm-btn-size:  42px;      /* icon button plate, 44px touch box in CSS    */

  /* Score shrink-to-fit. ui.js publishes only --sm-cur-len (the character
     count); the arithmetic is here. --sm-cur-chrome is the width the strip's
     own furniture eats (padding, the ◆, the multiplier chip). */
  --sm-cur-max:    36px;
  --sm-cur-vw:      5vw;
  --sm-cur-chrome: 300px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--sm-bg);
  color: var(--sm-text);
  font-family: var(--sm-font);
  /* Stop mobile browsers hijacking drags / double-tap zoom. */
  touch-action: none;
  overscroll-behavior: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

/* A game, not a document (hub CLAUDE.md §2). The block above already stops
   selection and the tap flash; the callout line stops iOS offering Copy /
   Look Up on a long press, and js/touch-guard.js is the half CSS cannot
   promise. Real text fields get selection back here, generically — the
   company name on a save slot had no rule of its own and inherited
   user-select:none, which Safari honours by refusing to edit it. */
input,
textarea,
[contenteditable="true"] {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

#game {
  display: block;
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: grab;
}
#game:active { cursor: grabbing; }

/* ===========================================================================
 * OVERLAY ROOT
 * ======================================================================== */
#ui-root {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 10;
  padding: env(safe-area-inset-top, 0) env(safe-area-inset-right, 0)
           env(safe-area-inset-bottom, 0) env(safe-area-inset-left, 0);
}

/* --- the shared metal plate ------------------------------------------- */
.sm-panel {
  position: relative;
  background: var(--sm-panel);
  border: 1px solid var(--sm-border);
  border-radius: var(--sm-radius);
  /* hard bevel: light on top, black underneath, plus a drop shadow */
  box-shadow:
    inset 0 1px 0 var(--sm-edge-hi),
    inset 0 -2px 0 var(--sm-edge-lo),
    inset 1px 0 0 rgba(255,255,255,0.05),
    0 6px 18px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(7px) saturate(1.1);
  -webkit-backdrop-filter: blur(7px) saturate(1.1);
  overflow: hidden;
}

/* Warning stripe welded to the top edge of a panel. */
.sm-stripe {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--sm-hazard);
  opacity: 0.85;
}

/* ===========================================================================
 * TOP STRIP — THE SCORE, one line, full width
 * ---------------------------------------------------------------------------
 * This used to be a 208px panel in the top-left corner with the score, three
 * stat chips and the multiplier stacked inside it. On a phone that put the
 * one number the game is about in the smallest box on the screen, and a
 * seven-figure haul broke onto a second line.
 *
 * Now it is a single full-width plate exactly one line tall. The score is
 * absolutely centred inside it so the chips at either end can appear and
 * disappear per breakpoint without ever shifting the number.
 * ======================================================================== */
.sm-topbar {
  position: absolute;
  top: var(--sm-top-y);
  left: var(--sm-gutter);
  right: var(--sm-gutter);
  height: var(--sm-top-h);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  /* No backdrop blur here. The plate is now as wide as the screen, and
     blurring a full-width strip at DPR 3 is real fill rate spent on an effect
     nobody can see against dark rock. The gradient carries the metal alone. */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  /* ABOVE the clock column (6). The "+10 s" float flies UP out of the clock
     and, now that the score sits where it used to fly into, would land on top
     of the one number that must always be readable. Putting the plate over it
     turns that collision into the right gesture: the reward rises and slides
     away UNDER the deck plate. */
  z-index: 7;
}

/* The slot centres; the group inside it is what the payout pop scales. Two
   elements, so the animation's transform can never fight the centring one. */
.sm-currency-slot {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: calc(100% - 24px);
}
.sm-currency {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.28em;
  line-height: 1;
  white-space: nowrap;
  transform-origin: center center;
  /* SHRINK-TO-FIT, NO MEASURING.
     ui.js writes the character count into --sm-cur-len on this element (about
     ten times in a run — once per digit gained). The width available is the
     viewport minus the strip's own furniture; dividing it by the character
     count, plus two characters' worth of slack for the ◆ and its gap, is the
     largest size that is guaranteed to fit on ONE line. The plain declaration
     above it is the fallback for engines that will not divide by a var() —
     its vw terms are already safe past fifteen characters.
     The size lives on the GROUP, not on the digits, so the ◆ can be sized in
     `em` and ride the number instead of being pinned to the root font. */
  font-size: min(var(--sm-cur-max), var(--sm-cur-vw));
  font-size: min(var(--sm-cur-max), var(--sm-cur-vw),
                 calc((100vw - var(--sm-cur-chrome)) /
                      ((var(--sm-cur-len, 9) + 2) * 0.62)));
}
.sm-ico {
  color: var(--sm-gold);
  font-size: 0.58em;                 /* rides the score, whatever size it is */
  text-shadow: 0 0 14px rgba(255, 194, 31, 0.8);
}
.sm-currency-val {
  font-family: var(--sm-mono);
  font-size: 1em;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.5px;
  white-space: nowrap;
  color: var(--sm-gold);
  text-shadow:
    0 0 22px rgba(255, 194, 31, 0.45),
    0 2px 0 rgba(0, 0, 0, 0.7);
  font-variant-numeric: tabular-nums;
}

/* count-up "payout" pop */
@keyframes sm-pop-kf {
  0%   { transform: scale(1); }
  22%  { transform: scale(1.13); }
  100% { transform: scale(1); }
}
.sm-pop { animation: sm-pop-kf 0.22s cubic-bezier(0.2, 1.4, 0.4, 1); }

.sm-statrow {
  display: flex;
  align-items: center;
  gap: 6px;
}
/* Pinned to the right END of the strip, not to the chips: compact hides the
   chips entirely and the multiplier must not slide left when it does. */
.sm-stat-mult { margin-left: auto; }
.sm-stat {
  font-family: var(--sm-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.6px;
  color: var(--sm-dim);
  background: rgba(0, 0, 0, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 3px;
  padding: 4px 7px;
  font-variant-numeric: tabular-nums;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
}
.sm-stat-mult { color: var(--sm-dimmer); }
.sm-stat-hot {
  color: #0e1116;
  background: linear-gradient(180deg, var(--sm-gold-hot), var(--sm-gold));
  border-color: rgba(255, 240, 168, 0.6);
  text-shadow: none;
  box-shadow: 0 0 16px rgba(255, 194, 31, 0.5);
}

@keyframes sm-flash-kf {
  0%   { transform: scale(1);   filter: brightness(1); }
  25%  { transform: scale(1.28); filter: brightness(2.2); }
  100% { transform: scale(1);   filter: brightness(1); }
}
.sm-flash { animation: sm-flash-kf 0.42s ease-out; }

/* ===========================================================================
 * CENTRED COLUMN — countdown, then the zone line
 * ---------------------------------------------------------------------------
 * A flex column rather than two absolutely-placed blocks, because FREESTYLE
 * deletes the clock entirely (.sm-notimer). In a column the zone line simply
 * rises into the gap; with fixed offsets there would be a hole where the
 * countdown used to be, on the one mode that never has a countdown.
 * ======================================================================== */
.sm-center {
  position: absolute;
  top: calc(var(--sm-top-y) + var(--sm-top-h) + 7px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  /* NEVER wide enough to reach the button cluster on either side. The column
     is centred and the buttons are pinned right, so the widest it may ever be
     is the viewport less TWO button clusters — measured off the same
     variables the buttons themselves use, so it cannot drift out of step.
     What makes this necessary is the zone line: "THE MOTHERLODE  CORE
     +1 250 m" is 219px of text, which on a 360px phone ran straight into the
     sound button. Bounded here, it wraps onto a second line instead. */
  max-width: calc(100vw - 2 * (var(--sm-gutter) + var(--sm-btn-size) * 2 + 14px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  z-index: 6;
  pointer-events: none;
}

/* The wrapper is the positioned element so the "+10s" float can escape the
   panel, which clips its overflow. The alarm animation lives on .sm-clock and
   the gain kick on .sm-clock-inner, so the two never fight over `transform`. */
.sm-clockwrap {
  position: relative;
  width: max-content;
  pointer-events: none;
}

.sm-clock {
  padding: 7px 20px 9px;
  /* min-width, not width: the panel must NOT resize as the digits go from
     "1:00" to "9.4" — a clock that twitches sideways is unreadable at speed. */
  min-width: 172px;
  text-align: center;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.sm-clock-label {
  font-family: var(--sm-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 5px;
  color: var(--sm-dimmer);
  text-indent: 5px;            /* balance the trailing letter-spacing */
}
.sm-clock-digits {
  font-family: var(--sm-mono);
  font-size: 40px;
  font-weight: 900;
  /* FIXED box. The urgent state uses bigger digits, and without a locked
     height the whole panel would grow at the ten-second mark and shove the
     progress gauge down mid-run. */
  height: 42px;
  line-height: 42px;
  letter-spacing: -1px;
  color: var(--sm-cyan-hot);
  font-variant-numeric: tabular-nums;
  text-shadow:
    0 0 24px rgba(102, 226, 255, 0.55),
    0 2px 0 rgba(0, 0, 0, 0.75);
}
.sm-clock-bar {
  height: 5px;
  margin-top: 7px;
  background: linear-gradient(180deg, #05070a, #10141a);
  border: 1px solid var(--sm-border);
  border-radius: 2px;
  overflow: hidden;
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.9);
}
.sm-clock-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, #1e93c9, var(--sm-cyan) 55%, #c4f6ff);
  box-shadow: 0 0 12px rgba(102, 226, 255, 0.8);
  transition: width 0.1s linear;
}

/* --- stage 2: running short ------------------------------------------- */
.sm-clock-warn { border-color: rgba(255, 194, 31, 0.55); }
.sm-clock-warn .sm-clock-digits {
  color: var(--sm-gold-hot);
  text-shadow: 0 0 26px rgba(255, 194, 31, 0.65), 0 2px 0 rgba(0,0,0,0.75);
}
.sm-clock-warn .sm-clock-fill {
  background: linear-gradient(90deg, #c4771a, var(--sm-gold) 55%, var(--sm-gold-hot));
  box-shadow: 0 0 14px rgba(255, 194, 31, 0.85);
}
.sm-clock-warn .sm-clock-label { color: var(--sm-gold); }

/* --- stage 3: the last ten seconds ------------------------------------- */
.sm-clock-urgent {
  border-color: rgba(255, 90, 72, 0.75);
  box-shadow:
    inset 0 1px 0 var(--sm-edge-hi),
    inset 0 -2px 0 var(--sm-edge-lo),
    0 0 34px rgba(255, 90, 72, 0.55),
    0 6px 18px rgba(0, 0, 0, 0.55);
  animation: sm-clock-alarm 0.5s ease-in-out infinite;
}
.sm-clock-urgent .sm-clock-digits {
  font-size: 46px;              /* same 42px box — see .sm-clock-digits */
  color: #ffe2dc;
  text-shadow: 0 0 30px rgba(255, 90, 72, 0.95), 0 2px 0 rgba(0,0,0,0.8);
}
.sm-clock-urgent .sm-clock-label { color: var(--sm-red); }
.sm-clock-urgent .sm-clock-fill {
  background: linear-gradient(90deg, #8c1c12, var(--sm-red) 55%, #ffb0a4);
  box-shadow: 0 0 16px rgba(255, 90, 72, 0.95);
}
@keyframes sm-clock-alarm {
  0%, 100% { transform: scale(1);     filter: brightness(1); }
  50%      { transform: scale(1.045); filter: brightness(1.45); }
}

/* --- stage 4: stopped --------------------------------------------------- */
.sm-clock-dead { filter: grayscale(0.7) brightness(0.75); }
.sm-clock-dead .sm-clock-digits { color: var(--sm-dim); text-shadow: 0 2px 0 #000; }

/* --- the gain kick + the "+10s" float ------------------------------------ */
@keyframes sm-clock-kick-kf {
  0%   { transform: scale(1); }
  28%  { transform: scale(1.22); }
  100% { transform: scale(1); }
}
.sm-clock-kick { animation: sm-clock-kick-kf 0.42s cubic-bezier(0.2, 1.5, 0.4, 1); }

.sm-clock-gain {
  position: absolute;
  left: 50%;
  top: 6px;
  transform: translate(-50%, 0);
  font-family: var(--sm-mono);
  font-size: 30px;
  font-weight: 900;
  letter-spacing: 1px;
  color: var(--sm-green);
  text-shadow: 0 0 26px rgba(88, 224, 138, 0.95), 0 2px 4px #000;
  opacity: 0;
  pointer-events: none;
}
.sm-clock-gain-on { animation: sm-clock-gain-kf 1.05s cubic-bezier(0.2, 0.9, 0.3, 1); }
@keyframes sm-clock-gain-kf {
  0%   { opacity: 0; transform: translate(-50%, 8px)   scale(0.7); }
  18%  { opacity: 1; transform: translate(-50%, -6px)  scale(1.25); }
  55%  { opacity: 1; transform: translate(-50%, -34px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -62px) scale(0.95); }
}

/* ===========================================================================
 * ZONE LINE (under the clock) — all that is left of the progress gauge
 * ---------------------------------------------------------------------------
 * The gauge itself is gone: a percentage through the map is not a decision
 * anybody makes at speed, and on a phone it was the widest element on screen.
 * Two things in it were worth keeping and both live on this one 13px line —
 * the zone NAME, which is the only persistent "where am I" the HUD has, and
 * the CORE counter, which past 100% is where the score is actually earned.
 * ======================================================================== */
/* Wraps rather than overflows: on a narrow phone in the core the name and the
   CORE counter are two lines, not one line halfway under the pause button.
   min-height keeps the 13px slot reserved so nothing twitches when a zone
   name arrives. */
.sm-zoneline {
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  gap: 2px 9px;
  min-height: 13px;
  max-width: 100%;
  white-space: nowrap;
}
.sm-zonelabel {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 2.4px;
  color: var(--sm-cyan);
  text-shadow: 0 0 12px rgba(102, 226, 255, 0.55), 0 1px 2px #000;
}

/* --- OVERTIME: past 100%, still digging ---------------------------------
 * Cyan is the colour of "how far through are you", and that question has no
 * answer down here — gold is the colour of "this is worth money", which is
 * the only thing the core is about. Hidden until there IS overtime, so the
 * line is just the zone name for the first three minutes of a run. */
.sm-zonecore {
  display: none;
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.6px;
  color: var(--sm-gold);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 14px rgba(255, 194, 31, 0.7), 0 1px 2px #000;
  animation: sm-core-breathe 1.6s ease-in-out infinite;
}
.sm-zone-core .sm-zonecore { display: block; }
.sm-zone-core .sm-zonelabel {
  color: var(--sm-gold);
  text-shadow: 0 0 12px rgba(255, 194, 31, 0.6), 0 1px 2px #000;
}
@keyframes sm-core-breathe { 50% { filter: brightness(1.35); } }

/* ===========================================================================
 * METERS (bottom centre) — overdrive + speed boost, hidden by default
 * ---------------------------------------------------------------------------
 * These used to hang off the bottom of the progress gauge. They cannot follow
 * the zone line under the clock: on a phone in landscape the top cluster is
 * already a third of the screen, and two more bars would push the upgrade
 * announcement into the middle of it. They are countdowns on a state you can
 * already SEE (effects.js grades the whole screen for overdrive), so the
 * bottom edge is the honest place for them — narrow, centred, between the
 * two steering pads, and invisible unless something is actually running.
 * ======================================================================== */
.sm-meters {
  position: absolute;
  left: 50%;
  bottom: max(12px, 2.4vh);
  transform: translateX(-50%);
  width: min(230px, 52vw);
  pointer-events: none;
}
.sm-od {
  width: 100%;
  margin-top: 3px;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.18s ease, transform 0.18s ease;
  pointer-events: none;
}
.sm-od-on { opacity: 1; transform: translateY(0); }

.sm-od-label {
  font-family: var(--sm-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 3px;
  color: var(--sm-orange);
  text-align: center;
  text-shadow: 0 0 12px rgba(255, 138, 31, 0.9);
  animation: sm-od-blink 0.5s steps(2, end) infinite;
}
@keyframes sm-od-blink { 50% { opacity: 0.45; } }

.sm-od-bar {
  height: 6px;
  background: #0a0705;
  border: 1px solid rgba(255, 138, 31, 0.35);
  border-radius: 2px;
  overflow: hidden;
}
.sm-od-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, #ff4d1a, var(--sm-orange) 60%, #ffd479);
  box-shadow: 0 0 14px rgba(255, 138, 31, 0.85);
}

/* ===========================================================================
 * BUTTONS
 * ---------------------------------------------------------------------------
 * The in-game pair sits on the same row as the countdown, at the right end,
 * and they are ICONS now. Two reasons: a word needs ~74px of plate on a
 * screen that is 360px wide, and RESTART-as-a-word was one mis-tap away from
 * throwing a run in the bin. Restart now lives inside the pause menu, which
 * is a far better place to be asked than a confirm dialog would be.
 * ======================================================================== */
.sm-buttons {
  position: absolute;
  top: calc(var(--sm-top-y) + var(--sm-top-h) + 7px);
  right: var(--sm-gutter);
  display: flex;
  gap: 8px;
  pointer-events: auto;
  /* Above the steering pads, which are full height and pass under here. The
     topmost hit target wins, so a tap on a button is never a steer. */
  z-index: 8;
}
.sm-btn {
  position: relative;
  font-family: var(--sm-mono);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.6px;
  color: var(--sm-text);
  background: var(--sm-panel);
  border: 1px solid var(--sm-border);
  border-radius: 4px;
  /* 44px minimum touch target */
  min-height: 40px;
  min-width: 74px;
  padding: 10px 14px;
  cursor: pointer;
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  box-shadow:
    inset 0 1px 0 var(--sm-edge-hi),
    inset 0 -2px 0 var(--sm-edge-lo),
    0 4px 12px rgba(0,0,0,0.5);
  transition: filter 0.12s, transform 0.06s, box-shadow 0.12s;
}
.sm-btn:hover { filter: brightness(1.35); }
.sm-btn:active {
  transform: translateY(2px);
  box-shadow: inset 0 2px 5px rgba(0,0,0,0.8);
}
.sm-btn-primary {
  color: #06131a;
  background: linear-gradient(180deg, var(--sm-cyan-hot), var(--sm-cyan) 45%, #2597c4 100%);
  border-color: rgba(196, 246, 255, 0.5);
  text-shadow: 0 1px 0 rgba(255,255,255,0.35);
}
.sm-btn-big {
  font-size: 14px;
  letter-spacing: 2.4px;
  min-height: 52px;
  padding: 16px 34px;
  margin-top: 20px;
  width: 100%;
}

/* --- the icon plate ---------------------------------------------------
 * Square, gold-on-dark, with the same hazard sliver the panels wear welded
 * to its top edge — that stripe is what says "machinery" everywhere else in
 * the game, and it is what keeps a 42px button from reading as a generic
 * mobile chrome control. */
.sm-iconbtn {
  width: var(--sm-btn-size);
  height: var(--sm-btn-size);
  min-width: 0;
  min-height: 0;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 5px;
  overflow: hidden;
  color: var(--sm-gold);
}
.sm-iconbtn::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: var(--sm-hazard);
  opacity: 0.8;
}
.sm-btn-svg {
  display: block;
  width: 20px;
  height: 20px;
  filter: drop-shadow(0 0 7px rgba(255, 194, 31, 0.5));
}
/* Muted: the same speaker, greyed and unlit. The glyph itself changes too
   (ui.js swaps in the crossed-out one) — colour alone is not a state. */
.sm-iconbtn.sm-btn-off { color: var(--sm-dimmer); }
.sm-iconbtn.sm-btn-off .sm-btn-svg { filter: none; }
.sm-iconbtn.sm-btn-off::before { opacity: 0.35; }

/* ===========================================================================
 * PAUSE MENU
 * ---------------------------------------------------------------------------
 * Deliberately small and quick. It is not a settings screen; it is the two
 * things you can do with a run that is currently stopped.
 * ======================================================================== */
.sm-pause {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(ellipse at center, rgba(4,6,9,0.55), rgba(2,3,5,0.9));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
  /* Above the summary (15) so a pause can never open behind an end screen,
     below the start overlay (20) which owns the screen outright. */
  z-index: 16;
}
.sm-pause-on { opacity: 1; pointer-events: auto; }

.sm-pause-card {
  width: min(320px, 82vw);
  padding: 22px 22px 20px;
  text-align: center;
  transform: translateY(14px) scale(0.97);
  transition: transform 0.3s cubic-bezier(0.16, 1.5, 0.35, 1);
}
.sm-pause-on .sm-pause-card { transform: none; }

.sm-pause-kicker {
  font-family: var(--sm-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 5px;
  text-indent: 5px;
  color: var(--sm-dimmer);
}
.sm-pause-title {
  font-size: 34px;
  font-weight: 900;
  letter-spacing: 6px;
  text-indent: 6px;
  line-height: 1.1;
  margin-bottom: 16px;
  color: #fff;
  text-shadow: 0 0 26px rgba(255, 194, 31, 0.4), 0 3px 0 rgba(0, 0, 0, 0.6);
}
/* NO `pointer-events: auto` HERE, and this is not an oversight.
   The overlay is inset:0 and only opacity-0 while closed, so a child that
   opts back into hit-testing on its own is a full-size invisible button
   sitting in the dead centre of the screen — which is exactly where a
   desktop player puts the mouse down to drag-steer. Interactivity is
   inherited from .sm-pause-on instead, so it exists only while the menu
   does. (Same trap caught the high-score field below.) */
.sm-pause-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 48px;
  margin-top: 9px;
}
.sm-pause-ico { display: block; width: 17px; height: 17px; }
.sm-pause-ico .sm-btn-svg { width: 100%; height: 100%; filter: none; }
.sm-pause-label { line-height: 1; }

/* A frozen run must LOOK frozen. main.js holds the fixed step but CSS
   animations are not part of it, so without this the clock keeps throbbing
   red and the OVERDRIVE label keeps blinking behind the menu — which reads
   as "the timer is still running", the one thing a pause has to disprove. */
.sm-paused .sm-clock-urgent,
.sm-paused .sm-od-label,
.sm-paused .sm-zonecore,
.sm-paused .sm-banner-final .sm-banner-text { animation: none; }

/* ===========================================================================
 * STEERING PADS (left + right, full height, transparent)
 * ---------------------------------------------------------------------------
 * Drag-steering is a laptop gesture. Held one-handed, a drag puts your thumb
 * in the middle of the screen — over the rock you are steering through — and
 * has no resting position. Two edge pads have both: your thumb is already
 * there, and the middle stays clear.
 *
 * They are TRANSPARENT on purpose. The chevrons are the only paint, they sit
 * low where the thumb is, and they light up while held so the control still
 * confirms itself. A solid button here would cover a fifth of the mine.
 *
 * Enabled by LAYOUT, not by sniffing: any compact viewport, anything taller
 * than it is wide, and anything with a coarse pointer. A mouse desktop never
 * gets them, so full-screen drag-steering there is completely untouched.
 * ======================================================================== */
.sm-pads {
  position: absolute;
  inset: 0;
  display: none;
  pointer-events: none;
}
.sm-compact .sm-pads,
.sm-portrait .sm-pads { display: block; }
@media (hover: none) and (pointer: coarse) {
  .sm-pads { display: block; }
}

.sm-pad {
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(76px, 27vw, 230px);
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  color: rgba(255, 255, 255, 0.17);
  cursor: pointer;
  pointer-events: auto;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.14s ease, color 0.14s ease;
}
.sm-pad-left  { left: 0; }
.sm-pad-right { right: 0; }
.sm-pad-svg {
  display: block;
  width: 30px;
  height: 30px;
  /* Low, in the thumb zone, and clear of the haul tally above it. */
  margin-bottom: max(16px, 3.6vh);
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.9));
}
.sm-pad-on { color: var(--sm-cyan-hot); }
.sm-pad-left.sm-pad-on {
  background: linear-gradient(90deg, rgba(102, 226, 255, 0.17), rgba(102, 226, 255, 0) 78%);
}
.sm-pad-right.sm-pad-on {
  background: linear-gradient(270deg, rgba(102, 226, 255, 0.17), rgba(102, 226, 255, 0) 78%);
}

/* ===========================================================================
 * UPGRADE ANNOUNCEMENT (upper third — never the dead centre)
 * ======================================================================== */
.sm-toast {
  position: absolute;
  /* Below the whole top cluster (strip + clock + zone line ~= 190px). On a
     short desktop window a flat percentage would land it on the clock. */
  top: max(21%, 200px);
  left: 50%;
  width: min(760px, 88vw);
  transform: translate(-50%, -18px) scale(0.9);
  text-align: center;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.34s cubic-bezier(0.16, 1.7, 0.35, 1);
  will-change: opacity, transform;
}
.sm-toast-on {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
}
.sm-toast-kicker {
  font-family: var(--sm-mono);
  font-size: clamp(9px, 1.1vw, 12px);
  font-weight: 800;
  letter-spacing: 6px;
  color: var(--sm-cyan);
  margin-bottom: 6px;
  text-shadow: 0 0 16px rgba(102, 226, 255, 0.9), 0 2px 3px #000;
}
.sm-toast-title {
  font-size: clamp(26px, 5.2vw, 62px);
  font-weight: 900;
  letter-spacing: 2px;
  line-height: 1.02;
  color: #fff;
  text-transform: uppercase;
  text-shadow:
    0 0 26px rgba(102, 226, 255, 0.9),
    0 0 70px rgba(102, 226, 255, 0.5),
    0 4px 0 rgba(0, 0, 0, 0.65),
    0 8px 22px rgba(0, 0, 0, 0.8);
}
.sm-toast-sub {
  margin-top: 8px;
  font-family: var(--sm-mono);
  font-size: clamp(11px, 1.5vw, 16px);
  font-weight: 700;
  letter-spacing: 1.4px;
  color: var(--sm-cyan-hot);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.95);
}

/* ===========================================================================
 * POWER-UP SPLASH (mid screen — the one exception to "keep the centre empty")
 * ---------------------------------------------------------------------------
 * "+5 SEC" when a time cell shatters, "BOOST MODE" when a boost block does.
 * It breaks the centre-stays-empty rule on purpose and pays for it by being
 * brief: the whole thing is on and gone inside 1.1s, matching SPLASH_TIME in
 * ui.js. Everything about it is tuned for a player who is STEERING:
 *   * two words, no sentence — you parse the shape, not the text
 *   * sat at 41% rather than dead centre, so it is above the machine and out
 *     of the strip of rock you are actually aiming at
 *   * the material's own colour, so the splash and the thing you just drove
 *     through are obviously the same event
 * The wash is a deliberately weak echo of the overdrive colour grade in
 * effects.js — same gesture, ~a fifth of the alpha and half a second instead
 * of six, because boost mode has to stay legible as the LESSER of the two.
 * ======================================================================== */
/* The outer element is the whole viewport and is NEVER transformed: a
   transformed ancestor would become the containing block for the wash and
   shrink it to the width of the text. All the motion lives on -inner. */
.sm-splash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* The tint is set by the -time / -boost modifiers below and everything else
     in the block inherits from it, so a colour change is one line. */
  --sm-sp: var(--sm-cyan);
  --sm-sp-hot: var(--sm-cyan-hot);
  --sm-sp-rgb: 102, 226, 255;
}
/* Time keeps the GATE green (rgb 90,255,150) rather than the darker teal the
   time-cell casing was pushed to, because on the HUD green already means "the
   clock just went up" — the arch, the "+10s" float and this all have to agree.
   The casing colour is a WORLD-side readability fix, not a HUD one. */
.sm-splash-time  { --sm-sp: #5aff96; --sm-sp-hot: #ddffea; --sm-sp-rgb: 90, 255, 150; }
.sm-splash-boost { --sm-sp: #ff8a1f; --sm-sp-hot: #ffe3bd; --sm-sp-rgb: 255, 138, 31; }
/* 100% reached. Gold, because it is the same "you banked something big"
   language the currency and the final score already use. */
.sm-splash-core  { --sm-sp: #ffc21f; --sm-sp-hot: #fff0a8; --sm-sp-rgb: 255, 194, 31; }

/* Full-screen wash. Radial and centred on the text, so the edges of the
   screen never actually tint — it reads as the world flaring around the thing
   you just hit, not as a coloured pane dropped over the game. */
.sm-splash-wash {
  position: absolute;
  inset: 0;
  opacity: 0;
  background: radial-gradient(ellipse 70% 55% at 50% 46%,
              rgba(var(--sm-sp-rgb), 0.20) 0%,
              rgba(var(--sm-sp-rgb), 0.07) 42%,
              rgba(var(--sm-sp-rgb), 0) 72%);
}

.sm-splash-inner {
  position: absolute;
  top: 43%;
  left: 50%;
  width: min(720px, 92vw);
  transform: translate(-50%, -50%);
  text-align: center;
  opacity: 0;
}

.sm-splash-big {
  position: relative;
  font-size: clamp(42px, 8.4vw, 104px);
  font-weight: 900;
  letter-spacing: 3px;
  line-height: 0.98;
  color: #fff;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  /* Two tight shadows for legibility over bright rock, two wide ones for the
     glow. The hard black offset is what keeps it readable against a gold seam
     — colour alone loses that fight every time. */
  text-shadow:
    0 0 22px rgba(var(--sm-sp-rgb), 0.95),
    0 0 78px rgba(var(--sm-sp-rgb), 0.65),
    0 4px 0 rgba(0, 0, 0, 0.7),
    0 10px 26px rgba(0, 0, 0, 0.9);
}
.sm-splash-sub {
  position: relative;
  margin-top: 7px;
  font-family: var(--sm-mono);
  font-size: clamp(11px, 1.6vw, 17px);
  font-weight: 800;
  letter-spacing: 5px;
  text-indent: 5px;                 /* letter-spacing pads the right edge only */
  color: var(--sm-sp-hot);
  text-shadow: 0 0 14px rgba(var(--sm-sp-rgb), 0.9), 0 2px 5px rgba(0, 0, 0, 0.95);
}

/* One animation on the wrapper drives the whole thing. Punch in over 0.16s
   with an overshoot, hold for half a second, then leave UPWARD while fading —
   drifting up reads as "banked" and clears the machine's approach at the same
   time. Keyframe times are percentages of SPLASH_TIME (1.1s). */
.sm-splash-on .sm-splash-inner { animation: sm-splash-kf 1.1s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
.sm-splash-on .sm-splash-wash  { animation: sm-splash-wash-kf 1.1s ease-out both; }

@keyframes sm-splash-kf {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.72); }
  14%  { opacity: 1; transform: translate(-50%, -50%) scale(1.06); }
  22%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  62%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, calc(-50% - 46px)) scale(0.97); }
}
@keyframes sm-splash-wash-kf {
  0%   { opacity: 0; }
  12%  { opacity: 1; }
  46%  { opacity: 0.55; }
  100% { opacity: 0; }
}

/* ===========================================================================
 * UPGRADE RAIL (left edge, under the stat panel)
 * A column of tiles, one per owned upgrade. `flex-wrap: column` against a
 * max-height means a 13-upgrade run spills into a SECOND column rather than
 * running off the bottom of a short screen — no JS pixel maths, no scroll.
 * ======================================================================== */
.sm-rail {
  position: absolute;
  left: 10px;
  /* Clears the score strip, and moves with it when a breakpoint shortens it. */
  top: calc(var(--sm-top-y) + var(--sm-top-h) + 14px);
  /* Reserves the bottom of the left edge for the haul tally (11 rows of 18px
     + head + its 46px bottom offset ~= 268px). A long run wraps into a second
     column instead of running down into the tally. */
  max-height: calc(100vh - 400px);
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 7px;
  pointer-events: none;
  z-index: 4;
}

.sm-rail-tile {
  position: relative;
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--sm-cyan);
  background: var(--sm-panel);
  border: 1px solid var(--sm-border);
  border-left: 3px solid rgba(102, 226, 255, 0.75);
  border-radius: 4px;
  box-shadow:
    inset 0 1px 0 var(--sm-edge-hi),
    inset 0 -2px 0 var(--sm-edge-lo),
    0 4px 12px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.sm-rail-ico {
  display: block;
  width: 22px;
  height: 22px;
  filter: drop-shadow(0 0 7px rgba(102, 226, 255, 0.55));
}
.sm-rail-svg { display: block; width: 100%; height: 100%; }

/* Level badge — only present above level 1, welded to the bottom-right. */
.sm-rail-lv {
  position: absolute;
  right: -4px;
  bottom: -4px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 900;
  line-height: 15px;
  text-align: center;
  color: #14100a;
  background: linear-gradient(180deg, var(--sm-gold-hot), var(--sm-gold));
  border: 1px solid rgba(20, 16, 10, 0.85);
  border-radius: 8px;
  box-shadow: 0 0 12px rgba(255, 194, 31, 0.75);
}

@keyframes sm-rail-pop-kf {
  0%   { transform: scale(0.55) translateX(-10px); opacity: 0; filter: brightness(2.4); }
  45%  { transform: scale(1.22) translateX(0);     opacity: 1; filter: brightness(2.0); }
  100% { transform: scale(1)    translateX(0);     opacity: 1; filter: brightness(1); }
}
.sm-rail-pop { animation: sm-rail-pop-kf 0.44s cubic-bezier(0.16, 1.5, 0.35, 1); }

/* ===========================================================================
 * LIVE HAUL TALLY (left edge, below the rail)
 * A running count of every material gathered, most valuable at the top.
 *
 * BOTTOM-ANCHORED ON PURPOSE. Rows are revealed as materials are discovered,
 * so the block GROWS during a run. Anchoring it to the bottom means it grows
 * upward into the gap under the rail; anchoring it to the top would push the
 * last-discovered rows off the bottom of a short screen — exactly the rows
 * that just changed. The rail's max-height reserves the space above it.
 * ======================================================================== */
.sm-tally {
  position: absolute;
  left: 10px;
  bottom: 46px;
  width: 122px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  pointer-events: none;
  z-index: 4;
}
.sm-tally-head {
  font-family: var(--sm-mono);
  font-size: 8px;
  font-weight: 800;
  letter-spacing: 3px;
  color: var(--sm-dimmer);
  padding-left: 2px;
  margin-bottom: 2px;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
}
.sm-tally-row {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 16px;
  padding: 0 5px 0 4px;
  background: rgba(10, 12, 16, 0.52);
  border-left: 2px solid rgba(126, 140, 158, 0.3);
  border-radius: 2px;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
/* An actual payday reads brighter than spoil. */
.sm-tally-ore { background: rgba(14, 17, 22, 0.66); }

.sm-tally-sw {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 2px;
  transform: rotate(45deg);            /* reads as a gem, not a checkbox */
}
.sm-tally-name {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--sm-mono);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--sm-dim);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.sm-tally-count {
  flex: 0 0 auto;
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 800;
  color: var(--sm-text);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
.sm-tally-ore .sm-tally-count { color: var(--sm-gold-hot); }

/* First find of a material only — see updateTally(). */
@keyframes sm-tally-pop-kf {
  0%   { transform: translateX(-14px); opacity: 0; filter: brightness(2.6); }
  55%  { transform: translateX(0);     opacity: 1; filter: brightness(2.2); }
  100% { transform: translateX(0);     opacity: 1; filter: brightness(1); }
}
.sm-tally-pop { animation: sm-tally-pop-kf 0.5s cubic-bezier(0.16, 1.5, 0.35, 1); }

/* ===========================================================================
 * ZONE BANNER (RIGHT edge — announces a new section)
 * It used to slide in from the left. The upgrade rail now owns the left edge
 * for the whole run, and the rail GROWS — on a short screen it wraps into a
 * second column — so no fixed left offset is safe in every layout. The right
 * edge is empty below the button cluster, so the banner moved there and
 * mirrored: same gesture, zero collision risk.
 * ======================================================================== */
.sm-banner {
  position: absolute;
  right: 0;
  top: 46%;
  opacity: 0;
  transform: translateX(60px);
  transition: opacity 0.22s ease, transform 0.42s cubic-bezier(0.16, 1.4, 0.3, 1);
  will-change: opacity, transform;
}
.sm-banner-on { opacity: 1; transform: translateX(0); }

.sm-banner-text {
  font-family: var(--sm-mono);
  font-size: clamp(12px, 1.7vw, 18px);
  font-weight: 800;
  letter-spacing: 4px;
  color: #0d1014;
  background: linear-gradient(180deg, var(--sm-cyan-hot), var(--sm-cyan) 55%, #2a9ac6);
  padding: 9px 16px 9px 20px;
  border-right: 5px solid #0d1014;
  border-radius: 4px 0 0 4px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.7), 0 0 30px rgba(102, 226, 255, 0.35);
  white-space: nowrap;
}
.sm-banner-final .sm-banner-text {
  background:
    var(--sm-hazard) left top / 100% 5px repeat-x,
    var(--sm-hazard) left bottom / 100% 5px repeat-x,
    linear-gradient(180deg, #23262c, #101318);
  color: #ffd257;
  padding-top: 12px;
  padding-bottom: 12px;
  border-right-color: #f0b31b;
  text-shadow: 0 2px 4px #000, 0 0 14px rgba(240,179,27,0.6);
  box-shadow: 0 6px 24px rgba(0,0,0,0.8), 0 0 44px rgba(240, 179, 27, 0.6);
  animation: sm-banner-throb 0.42s ease-in-out infinite alternate;
}
@keyframes sm-banner-throb { to { filter: brightness(1.35); } }

/* ===========================================================================
 * END OF RUN SUMMARY
 * ======================================================================== */
.sm-summary {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(ellipse at center, rgba(4,6,9,0.55), rgba(2,3,5,0.88));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
  /* Above the clock (6) and the upgrade rail (4) so the whole HUD dims when
     the run ends, but below the start overlay (20). */
  z-index: 15;
}
.sm-summary-on { opacity: 1; pointer-events: auto; }

.sm-card {
  position: relative;
  width: min(560px, 92vw);
  max-height: 92vh;
  overflow: auto;
  /* <body> sets touch-action:none so a drag steers instead of scrolling the
     page. That also killed touch scrolling INSIDE this card — which on a
     phone put RUN IT AGAIN below the fold with no way to reach it. Re-arming
     vertical panning here is scoped to the card and cannot leak back out to
     the canvas. */
  touch-action: pan-y;
  padding: 26px 28px 24px;
  background: var(--sm-panel);
  border: 1px solid var(--sm-border);
  border-radius: 8px;
  box-shadow:
    inset 0 1px 0 var(--sm-edge-hi),
    inset 0 -3px 0 var(--sm-edge-lo),
    0 24px 70px rgba(0, 0, 0, 0.85);
  transform: translateY(24px) scale(0.96);
  transition: transform 0.42s cubic-bezier(0.16, 1.5, 0.35, 1);
}
.sm-summary-on .sm-card { transform: translateY(0) scale(1); }

.sm-card-kicker {
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 5px;
  color: var(--sm-gold);
  text-shadow: 0 0 14px rgba(255, 194, 31, 0.6);
  margin-bottom: 4px;
}
.sm-card-title {
  font-size: clamp(26px, 4vw, 40px);
  font-weight: 900;
  letter-spacing: 1.5px;
  line-height: 1.05;
  color: #fff;
  text-shadow: 0 0 28px rgba(255, 194, 31, 0.45), 0 3px 0 rgba(0,0,0,0.6);
  margin-bottom: 18px;
}
.sm-card-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.sm-cell {
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-left: 3px solid rgba(255, 194, 31, 0.55);
  border-radius: 3px;
  padding: 9px 12px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
}
.sm-cell-label {
  font-family: var(--sm-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 2px;
  color: var(--sm-dimmer);
}
.sm-cell-value {
  font-family: var(--sm-mono);
  font-size: 21px;
  font-weight: 800;
  color: var(--sm-text);
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
}

/* The haul IS the score in a time attack, so it gets the hero slot. */
.sm-score {
  margin-bottom: 12px;
  padding: 11px 14px 12px;
  background: linear-gradient(180deg, rgba(255, 194, 31, 0.16), rgba(0, 0, 0, 0.5));
  border: 1px solid rgba(255, 194, 31, 0.38);
  border-left: 4px solid var(--sm-gold);
  border-radius: 4px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.sm-score-label {
  font-family: var(--sm-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 4px;
  color: var(--sm-gold);
}
.sm-score-value {
  font-family: var(--sm-mono);
  font-size: clamp(30px, 6vw, 46px);
  font-weight: 900;
  line-height: 1.05;
  letter-spacing: -1px;
  color: var(--sm-gold-hot);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 28px rgba(255, 194, 31, 0.6), 0 2px 0 rgba(0, 0, 0, 0.7);
}

/* ===========================================================================
 * HAUL BREAKDOWN (summary card) — what the score was made of.
 * Each row is a proportional bar in the material's own colour, so the run
 * reads as a shape before any of the numbers are actually parsed.
 * ======================================================================== */
.sm-haul {
  margin-bottom: 14px;
}
.sm-haul-head {
  font-family: var(--sm-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 3px;
  color: var(--sm-dimmer);
  margin-bottom: 6px;
}
.sm-haul-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 24px;
  padding: 0 9px;
  margin-bottom: 3px;
  background: rgba(0, 0, 0, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 3px;
  overflow: hidden;
}
/* The proportional fill sits behind the text, inside the row's own clip. */
.sm-haul-bar {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  z-index: 0;
  pointer-events: none;
}
.sm-haul-row > .sm-haul-sw,
.sm-haul-row > .sm-haul-name,
.sm-haul-row > .sm-haul-count,
.sm-haul-row > .sm-haul-val { position: relative; z-index: 1; }

.sm-haul-sw {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 2px;
  transform: rotate(45deg);
}
.sm-haul-name {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.6px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
}
.sm-haul-count {
  flex: 0 0 auto;
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 700;
  color: var(--sm-dim);
  font-variant-numeric: tabular-nums;
}
.sm-haul-val {
  flex: 0 0 auto;
  min-width: 62px;
  text-align: right;
  font-family: var(--sm-mono);
  font-size: 13px;
  font-weight: 800;
  color: var(--sm-text);
  font-variant-numeric: tabular-nums;
}
/* Dirt, stone, rubble and the barrier rock, folded into one line. */
.sm-haul-spoil .sm-haul-val { color: var(--sm-dim); }

/* Power-ups collected — a footnote, because they paid in seconds and speed
   rather than currency and are deliberately outside the column that has to
   add up to the score. */
.sm-haul-picks {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-top: 7px;
  padding-top: 7px;
  border-top: 1px dashed rgba(255, 255, 255, 0.10);
}
.sm-haul-pick {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.sm-haul-pick-label {
  font-family: var(--sm-mono);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 1.4px;
}
.sm-haul-empty {
  font-family: var(--sm-mono);
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--sm-dimmer);
  padding: 6px 0 2px;
}

/* Ran out of time rather than reaching the bottom: recolour the header. */
.sm-summary-timeout .sm-card-kicker {
  color: var(--sm-red);
  text-shadow: 0 0 14px rgba(255, 90, 72, 0.7);
}
.sm-summary-timeout .sm-card-title {
  text-shadow: 0 0 28px rgba(255, 90, 72, 0.5), 0 3px 0 rgba(0, 0, 0, 0.6);
}
.sm-summary-timeout .sm-stripe {
  background: repeating-linear-gradient(135deg, var(--sm-red) 0 8px, #171512 8px 16px);
}

/* ===========================================================================
 * HIGH SCORES (inside the summary card)
 * ======================================================================== */
.sm-hs {
  margin-top: 16px;
  padding-top: 13px;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}
.sm-hs-head {
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 4px;
  color: var(--sm-dim);
}
.sm-hs-cta {
  margin-top: 9px;
  font-family: var(--sm-mono);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 2px;
  color: var(--sm-gold);
  text-shadow: 0 0 14px rgba(255, 194, 31, 0.55);
  animation: sm-hs-cta-kf 1.1s ease-in-out infinite;
}
@keyframes sm-hs-cta-kf { 50% { opacity: 0.5; } }

.sm-hs-form {
  display: flex;
  gap: 8px;
  margin-top: 8px;
  align-items: stretch;
}
.sm-hs-input {
  flex: 1 1 auto;
  min-width: 0;
  -webkit-user-select: text;
  user-select: text;
  font-family: var(--sm-mono);
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--sm-gold-hot);
  background: linear-gradient(180deg, #05070a, #0e1218);
  border: 1px solid rgba(255, 194, 31, 0.45);
  border-radius: 4px;
  padding: 10px 12px;
  min-height: 44px;
  outline: none;
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.85);
}
.sm-hs-input::placeholder { color: var(--sm-dimmer); letter-spacing: 2px; }
.sm-hs-input:focus {
  border-color: var(--sm-gold);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.85), 0 0 0 2px rgba(255, 194, 31, 0.28);
}
.sm-hs-save {
  min-width: 88px;
  margin: 0;
}

/* This is the one text field in the whole game and it must never be
   un-clickable — but ONLY while the card it lives on is up. Scoped, because
   .sm-summary is inset:0 and merely opacity-0 when closed: an unscoped
   `pointer-events: auto` in here is an invisible input field parked in the
   middle of the mine, swallowing the mouse-down that starts a drag-steer.
   That was a real bug; it cost desktop drag-steering the centre of the
   screen and nothing about it was visible. */
.sm-summary-on .sm-hs-input,
.sm-summary-on .sm-hs-save { pointer-events: auto; }

.sm-hs-rows {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.sm-hs-row {
  display: grid;
  grid-template-columns: 20px 1fr auto 76px;
  gap: 9px;
  align-items: baseline;
  padding: 5px 9px;
  font-family: var(--sm-mono);
  font-size: 12px;
  font-weight: 700;
  background: rgba(0, 0, 0, 0.38);
  border-left: 2px solid rgba(255, 255, 255, 0.10);
  border-radius: 3px;
}
.sm-hs-rank  { color: var(--sm-dimmer); font-weight: 800; }
.sm-hs-name  {
  color: var(--sm-text);
  font-weight: 800;
  letter-spacing: 1.4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sm-hs-score { color: var(--sm-gold); font-weight: 800; font-variant-numeric: tabular-nums; }
.sm-hs-depth { color: var(--sm-dimmer); text-align: right; font-variant-numeric: tabular-nums; }

.sm-hs-row-new {
  border-left-color: var(--sm-gold);
  background: linear-gradient(90deg, rgba(255, 194, 31, 0.26), rgba(0, 0, 0, 0.38));
  animation: sm-hs-new-kf 0.9s ease-out 2;
}
.sm-hs-row-new .sm-hs-rank { color: var(--sm-gold-hot); }
@keyframes sm-hs-new-kf { 50% { filter: brightness(1.6); } }

.sm-hs-note {
  margin-top: 10px;
  font-family: var(--sm-mono);
  font-size: 10px;
  letter-spacing: 1.6px;
  color: var(--sm-dimmer);
}

/* ===========================================================================
 * START OVERLAY — THE TITLE SCREEN
 * ---------------------------------------------------------------------------
 * The front door: a black shaft, a headlight sweeping across it, ore catching
 * the edge of the light, and the wordmark in the middle of it. js/ui.js's
 * buildTitle() builds the DOM; this section is the whole of the artwork. There
 * are no images anywhere in it — the mark is eight divs, the beam is a
 * clip-path, the rock is a repeating gradient.
 *
 * THE ANIMATION BUDGET IS THE ONE RULE HERE. Every keyframe below animates
 * transform, opacity, filter or (on exactly one small element) box-shadow.
 * Nothing animates a layout property, nothing runs on the main thread, and
 * .sm-start-off pauses the lot — a dismissed overlay is opacity-0, not gone,
 * and a sweeping beam nobody can see is battery burned 600 m underground while
 * the player is trying to get a full hold back to the lift.
 * ======================================================================== */
.sm-start {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Two layers: a warm bloom hanging at the top where the light comes from,
     over the black the rest of the game is painted on.

     THE LOWER LAYER IS NEARLY OPAQUE, and that is a fix, not a preference. The
     canvas keeps RENDERING behind all three simulation gates (see main.js), so
     at boot there is a rig parked at the origin in an empty world, and the old
     0.55-alpha centre left it showing straight through the wordmark as a ghost
     machine. The title screen has its own machine — the mark — and two of them
     on one screen is just muddle. */
  background:
    radial-gradient(115% 78% at 50% -12%, rgba(255, 176, 60, 0.12) 0%, rgba(255, 140, 20, 0) 62%),
    radial-gradient(ellipse at center, rgba(5, 6, 9, 0.93) 0%, rgba(2, 3, 5, 0.985) 74%);
  pointer-events: auto;
  cursor: pointer;
  opacity: 1;
  transition: opacity 0.45s ease;
  z-index: 20;
  overflow: hidden;             /* the sweep is deliberately bigger than the screen */
}
.sm-start-off { opacity: 0; pointer-events: none; }

/* See the section header. This is not a nicety: showTitle()/hideTitle() only
   fade the overlay, so without it every animation on this screen keeps running
   for the whole session behind the mine.

   SELECTED AS `.sm-start.sm-start-off` — both classes on the one element — for
   a reason that cost a debugging round. `animation` is a SHORTHAND and setting
   it resets animation-play-state to `running`. Every rule below sets that
   shorthand, and at equal specificity the one further down the file wins, so a
   plain `.sm-start-off .sm-start-go` was silently overturned by START's own
   `animation:` a few hundred lines later. Measured paused, not assumed. */
.sm-start.sm-start-off .sm-start-sweep,
.sm-start.sm-start-off .sm-ore,
.sm-start.sm-start-off .sm-start-sub,
.sm-start.sm-start-off .sm-mark-beam,
.sm-start.sm-start-off .sm-mark-gem,
.sm-start.sm-start-off .sm-start-go { animation-play-state: paused; }

/* --- the stage: everything behind the panel ---------------------------- */
.sm-start-stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* Decoration only. A tap here has to reach .sm-start's click handler, and
     the whole overlay is one big start button. */
  pointer-events: none;
}

/* THE HEADLIGHT SWEEP. A soft wedge pivoting about a point just above the top
   edge, 19s out and 19s back: something with a lamp is moving around off frame.
   It is one composited transform on one element — the cheapest possible way to
   make a still screen feel like it is underground.

   IT HAS TO STAY ALMOST INVISIBLE. The first pass ran it at 0.17 alpha with a
   3px blur, and a hard-edged wedge that bright over black does not read as a
   beam at all — it reads as a diagonal stripe painted across the title. Very
   low alpha and a blur measured in tens of pixels is the whole difference
   between atmosphere and a graphic. The pivot sits near the top edge on purpose
   too: a wedge whose apex is far off screen arrives as a parallel band. */
.sm-start-sweep {
  position: absolute;
  left: 50%;
  top: -7vmax;
  width: 66vmax;
  height: 150vmax;
  margin-left: -33vmax;
  background: linear-gradient(180deg,
    rgba(255, 226, 170, 0.11) 0%,
    rgba(255, 176, 60, 0.05) 30%,
    rgba(255, 140, 20, 0) 68%);
  clip-path: polygon(46% 0, 54% 0, 100% 100%, 0 100%);
  transform-origin: 50% 4%;
  filter: blur(34px);
  animation: sm-sweep 19s ease-in-out infinite alternate;
}
@keyframes sm-sweep {
  from { transform: rotate(-15deg); }
  to   { transform: rotate(15deg); }
}

/* ORE IN THE DARK. Seven specks near the edges of the frame — never in the
   middle, where the wordmark lives — each twinkling once per cycle on its own
   offset, so no two ever flash together and the field reads as depth rather
   than as decoration. */
.sm-start-field { position: absolute; inset: 0; }
.sm-ore {
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 1px;
  background: var(--sm-gold);
  box-shadow: 0 0 11px 2px rgba(255, 194, 31, 0.5);
  opacity: 0;
  animation: sm-ore-twinkle 9s ease-in-out infinite;
}
@keyframes sm-ore-twinkle {
  0%, 58%, 100% { opacity: 0;    transform: rotate(45deg) scale(0.45); }
  70%           { opacity: 0.92; transform: rotate(45deg) scale(1); }
  82%           { opacity: 0.10; transform: rotate(45deg) scale(0.7); }
}
.sm-ore-0 { left:  9%; top: 63%; animation-delay: 0s; }
.sm-ore-1 { left: 17%; top: 32%; animation-delay: 3.1s; }
.sm-ore-2 { left: 88%; top: 71%; animation-delay: 1.4s; }
.sm-ore-3 { left: 79%; top: 26%; animation-delay: 5.6s; }
.sm-ore-4 { left: 31%; top: 86%; animation-delay: 7.2s; }
.sm-ore-5 { left: 67%; top: 89%; animation-delay: 4.3s; }
/* One cold one. Gold is money in this game and cyan is the rare stuff; a field
   of nothing but gold loses the "and something else is down here" note. */
.sm-ore-6 {
  left: 93%; top: 45%;
  animation-delay: 2.2s;
  background: var(--sm-cyan);
  box-shadow: 0 0 11px 2px rgba(102, 226, 255, 0.5);
}

/* The menu is a real panel, vertically centred with a fixed rhythm, rather
   than a stack of margins that drifted apart as copy was added. `gap` on a
   flex column keeps the spacing honest no matter which children exist.

   --sm-mark IS DECLARED HERE, not on :root. Every breakpoint wants a different
   size for the logo mark, and a custom property set on this element beats
   anything the layout classes on #ui-root declare no matter how the specificity
   falls — which is the trap the LAYOUT METRICS block at the top of this file
   documents from the other direction. One place, one override per breakpoint. */
.sm-start-inner {
  --sm-mark: 144px;
  position: relative;           /* over .sm-start-stage */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  width: min(880px, 92vw);
  max-height: 92vh;
  padding: 30px 26px;
  text-align: center;
  overflow: auto;
  touch-action: pan-y;          /* see .sm-card — same trap, same fix */
}
/* A flex column with a max-height SHRINKS its children when the content is
   too tall — on a 390x740 phone that squashed the mode cards to 30px instead
   of scrolling. Pin every direct child at its natural height and let the
   container's overflow do its job. */
.sm-start-inner > * { flex: 0 0 auto; }

/* Mark over wordmark by default; the short-screen rules turn this into a row so
   a phone in landscape spends its 390px of height on the game's name instead of
   on a stack. */
.sm-start-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 15px;
}

.sm-start-head { display: flex; flex-direction: column; align-items: center; gap: 8px; }

.sm-start-logo {
  font-size: clamp(38px, 8.6vw, 92px);
  font-weight: 900;
  letter-spacing: clamp(4px, 1.3vw, 13px);
  /* The tracking is applied to the right of every glyph including the last,
     which visually shifts a centred word left. Pull it back by half. */
  text-indent: clamp(4px, 1.3vw, 13px);
  line-height: 1;
  color: #fff;
  text-shadow:
    0 0 40px rgba(255, 194, 31, 0.55),
    0 0 110px rgba(255, 138, 31, 0.35),
    0 6px 0 rgba(0,0,0,0.7);
}
/* The gold line breathes, very slowly, and it is an OPACITY animation on one
   element — the compositor handles it and the main thread never hears about it.
   Animating the text-shadow instead would repaint a 90px glow every frame. */
/* SUPERMINE / ADVENTURE IS ONE WORDMARK IN TWO LINES, so this has to scale with
   the line above it. At the old 13px ceiling a 92px SUPERMINE had a caption
   under it instead of a second half. The tracking is set so the two lines end up
   close to the same measure. */
.sm-start-sub {
  font-family: var(--sm-mono);
  font-size: clamp(11px, 2.3vw, 23px);
  font-weight: 800;
  letter-spacing: clamp(4px, 1.2vw, 13px);
  text-indent: clamp(4px, 1.2vw, 13px);
  color: var(--sm-gold);
  text-shadow: 0 0 16px rgba(255, 194, 31, 0.6);
  animation: sm-sub-breathe 6.5s ease-in-out infinite;
}
@keyframes sm-sub-breathe {
  0%, 100% { opacity: 0.78; }
  50%      { opacity: 1; }
}

.sm-start-pick {
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 5px;
  text-indent: 5px;
  color: var(--sm-dim);
}

/* The loop, in one line, under the button. Deliberately the dimmest thing on
   the screen: it is there for the player who stops to read, not for the one
   who already knows what they came for. */
/* No max-width: `ch` does not know about letter-spacing, so 52ch measured 310px
   for a 386px line and wrapped "TO THE LIFT" onto its own row. The panel's own
   width is the constraint, and on a narrow phone wrapping here is fine. */
.sm-start-tag {
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 700;
  line-height: 1.7;
  letter-spacing: 2.4px;
  text-indent: 2.4px;
  color: var(--sm-dimmer);
  /* Progressive enhancement: where it is supported this breaks the line into
     two even halves instead of leaving "THE LIFT" orphaned under the centre.
     Where it is not, the line wraps normally and nothing is lost. */
  text-wrap: balance;
}

/* --- START ------------------------------------------------------------
 * THE GOLD PLATE AND THE HAZARD STRIPE ARE NOT SET HERE. Every button in the
 * campaign already wears them — `.sm-adv .sm-btn:not(.sm-iconbtn)` in
 * style-adventure.css, which is the documented default for the whole mode and
 * outranks anything this file says. START gets it for free by being a .sm-btn,
 * and that is the right answer: the front door should be made of the same metal
 * as every other control in the game. Do not restate the colours here — a
 * second copy of the treatment is a second thing to keep in sync, and it would
 * lose anyway.
 *
 * What is left is this button's own job:
 *   * `width: auto` undoes .sm-btn-big's `width: 100%`, which was written for a
 *     340px mode card and stretches to the full 880px panel in here;
 *   * size and tracking, because START is the one word on the screen that has
 *     to be found without reading;
 *   * the glow — the ONE box-shadow animation on this title screen. It repaints
 *     a 320px button and nothing else, and it is what says "press this" without
 *     spending a line of copy on it.
 *
 * SELECTED AS `.sm-btn.sm-start-go`, AND THE BREAKPOINTS REPEAT THE PAIR.
 * `.sm-compact .sm-btn:not(.sm-iconbtn)` and its `.sm-tiny` twin set
 * `min-width: 62px` / `52px` for the small square-ish controls in the mine, and
 * they are three-class selectors: a bare `.sm-start-go` loses to them and START
 * collapsed to a 52px stub on a phone. Measured, not guessed. */
.sm-btn.sm-start-go {
  width: auto;
  min-width: min(320px, 74vw);
  margin-top: 2px;
  font-size: 15px;
  letter-spacing: 5px;
  text-indent: 5px;
  animation: sm-start-glow 3.6s ease-in-out infinite;
}
@keyframes sm-start-glow {
  0%, 100% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.45),
      inset 0 -2px 0 rgba(0, 0, 0, 0.35),
      0 6px 18px rgba(0, 0, 0, 0.55);
  }
  50% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.45),
      inset 0 -2px 0 rgba(0, 0, 0, 0.35),
      0 6px 18px rgba(0, 0, 0, 0.55),
      0 0 30px rgba(255, 194, 31, 0.42);
  }
}

/* ===========================================================================
 * THE MARK — the app icon, in eight divs
 * ---------------------------------------------------------------------------
 * icons/*.png and this are the same drawing, and tools/make-icons.py is the
 * other half of it: a small tracked rig at the top of a black shaft, its
 * headlight falling away into strata, gold and cyan deposits catching the edge
 * of the beam. EDIT THEM TOGETHER — if the tile on the home screen and the mark
 * on the title screen ever stop being the same object, this is why.
 *
 * Every dimension inside is a PERCENTAGE of the mark, so one custom property
 * (--sm-mark, on .sm-start-inner) resizes the whole assembly and no breakpoint
 * has to know what is inside it.
 * ======================================================================== */
.sm-mark {
  position: relative;
  width: var(--sm-mark);
  height: var(--sm-mark);
  flex: 0 0 auto;
  border-radius: 23%;
  overflow: hidden;
  /* Same gradient as the icon's background, ending on the same --sm-bg the
     canvas is cleared to. */
  background: linear-gradient(180deg, #191c22 0%, #101218 44%, var(--sm-bg) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.07),
    0 14px 32px rgba(0, 0, 0, 0.66);
}

/* The rock face, with strata. The repeating gradient's hairlines are the band
   edges. The TOP layer is a black wash that gets heavier with depth, so the
   bands fade out as they go down instead of ruling the whole panel edge to edge
   — in the PNG the strata are only really visible where the beam reaches them,
   and unmasked hairlines across a dark tile read as scan lines. */
.sm-mark-rock {
  position: absolute;
  left: -8%; right: -8%; top: 50%; bottom: -6%;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.55) 62%, rgba(0, 0, 0, 0.9) 100%),
    repeating-linear-gradient(180deg,
      rgba(255, 255, 255, 0.05) 0 1.5px,
      rgba(255, 255, 255, 0) 1.5px 21%),
    linear-gradient(180deg, #16181e 0%, #0a0b0e 100%);
}

/* The headlight. A trapezoid out of the lamp, brightest at the source and
   dissolving before it reaches the bottom edge — the beam ends by fading, never
   by stopping, which is the whole feeling the game is after. */
.sm-mark-beam {
  position: absolute;
  left: 0; right: 0; top: 45%; bottom: 0;
  clip-path: polygon(37% 0, 63% 0, 94% 100%, 6% 100%);
  background: linear-gradient(180deg,
    rgba(255, 246, 214, 0.95) 0%,
    rgba(255, 212, 132, 0.55) 22%,
    rgba(255, 172, 56, 0.20) 50%,
    rgba(255, 140, 20, 0) 84%);
  animation: sm-mark-pulse 5.5s ease-in-out infinite;
}
/* The bottom of the tile goes black over everything — beam, gems and all. It is
   the same statement the PNG makes with its falloff ramp: the light runs out
   before the rock does. */
.sm-mark::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0) 58%, rgba(0, 0, 0, 0.55) 100%),
    radial-gradient(120% 90% at 50% 34%, rgba(0, 0, 0, 0) 52%, rgba(0, 0, 0, 0.4) 100%);
}
@keyframes sm-mark-pulse {
  0%, 100% { opacity: 0.86; }
  50%      { opacity: 1; }
}

/* Deposits. They sit where the beam reaches them and brighten on their own
   offsets — a filter animation, so nothing moves and nothing reflows. */
.sm-mark-gem {
  position: absolute;
  width: 10%;
  height: 10%;
  border-radius: 12%;
  transform: rotate(45deg);
  background: var(--sm-gold);
  box-shadow: 0 0 9px rgba(255, 194, 31, 0.75);
  animation: sm-gem-glint 6s ease-in-out infinite;
}
.sm-mark-gem-a { left: 24%; top: 67%; }
.sm-mark-gem-b {
  left: 60%; top: 58%;
  animation-delay: 2s;
  background: var(--sm-cyan);
  box-shadow: 0 0 9px rgba(102, 226, 255, 0.75);
}
.sm-mark-gem-c { left: 46%; top: 82%; width: 7%; height: 7%; animation-delay: 4s; }
@keyframes sm-gem-glint {
  0%, 100% { filter: brightness(0.62); }
  50%      { filter: brightness(1.38); }
}

/* The rig. Hull, cyan viewport, amber band, lamp bar — and the two tracks as
   its own pseudo-elements, because they are part of the machine and not two
   objects parked either side of it. */
.sm-mark-rig {
  position: absolute;
  left: 32%; top: 22%; width: 36%; height: 24%;
  border-radius: 15%;
  background: linear-gradient(180deg, #cfd8e3 0%, #9aa4b1 52%, #78828f 100%);
  box-shadow: 0 0 0 2px #07090c;
}
.sm-mark-rig::before,
.sm-mark-rig::after {
  content: '';
  position: absolute;
  top: 3%;
  height: 94%;
  width: 30%;
  border-radius: 22%;
  background: repeating-linear-gradient(180deg,
    #828d9a 0 13%, #16191f 13% 27%);
  box-shadow: 0 0 0 2px #07090c;
}
.sm-mark-rig::before { left: -40%; }
.sm-mark-rig::after  { right: -40%; }
.sm-mark-eye {
  position: absolute;
  left: 27%; top: 12%; width: 46%; height: 27%;
  border-radius: 22%;
  background: #79dcff;
  box-shadow: 0 0 0 2px #07090c;
}
.sm-mark-band {
  position: absolute;
  left: 0; right: 0; top: 50%; height: 20%;
  background: linear-gradient(180deg, #ffc94d 0 26%, #e09a10 26%);
}
/* A SLIT, not a slab. At 15% of the hull it filled the machine's whole lower
   half and the rig read as a face with a mouth. It only has to be bright. */
.sm-mark-lamp {
  position: absolute;
  left: 15%; right: 15%; bottom: 7%; height: 10%;
  border-radius: 2px;
  background: #fff6d2;
  box-shadow: 0 0 13px 3px rgba(255, 240, 200, 0.85);
}

/* --- mode cards ------------------------------------------------------- */
.sm-modes {
  display: flex;
  justify-content: center;
  align-items: stretch;      /* equal heights, whatever the blurb length */
  gap: 16px;
  width: 100%;
}

.sm-mode {
  position: relative;
  flex: 1 1 0;
  max-width: 340px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 7px;
  margin: 0;
  padding: 22px 20px 18px;
  text-align: left;
  font-family: var(--sm-font);
  background: var(--sm-panel);
  border: 1px solid var(--sm-border);
  border-radius: 7px;
  box-shadow:
    inset 0 1px 0 var(--sm-edge-hi),
    inset 0 -3px 0 var(--sm-edge-lo),
    0 14px 34px rgba(0,0,0,0.7);
  cursor: pointer;
  /* Interactivity inherits from .sm-start and stops with .sm-start-off — see
     the note on .sm-pause-btn. A dismissed menu is opacity-0, not gone, so an
     unscoped `pointer-events: auto` here leaves two invisible mode cards
     parked over the middle of the mine for the rest of the session. */
  overflow: hidden;
  transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
}
.sm-mode:hover, .sm-mode:focus-visible {
  transform: translateY(-3px);
  border-color: rgba(255, 194, 31, 0.55);
  box-shadow:
    inset 0 1px 0 var(--sm-edge-hi),
    inset 0 -3px 0 var(--sm-edge-lo),
    0 18px 42px rgba(0,0,0,0.8),
    0 0 0 1px rgba(255,194,31,0.25);
  outline: none;
}
.sm-mode:active { transform: translateY(-1px); }

.sm-mode-badge {
  align-self: flex-end;
  font-family: var(--sm-mono);
  font-size: 8px;
  font-weight: 800;
  letter-spacing: 2.4px;
  color: #0e1116;
  background: linear-gradient(180deg, var(--sm-gold-hot), var(--sm-gold));
  border-radius: 2px;
  padding: 3px 6px;
}
.sm-mode-freestyle .sm-mode-badge {
  color: #06120e;
  background: linear-gradient(180deg, #b6ffe2, var(--sm-green));
}

.sm-mode-title {
  font-size: clamp(19px, 2.4vw, 25px);
  font-weight: 900;
  letter-spacing: 1px;
  color: #fff;
  text-shadow: 0 2px 0 rgba(0,0,0,0.6);
}
.sm-mode-tag {
  font-family: var(--sm-mono);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 2.4px;
  color: var(--sm-gold);
}
.sm-mode-freestyle .sm-mode-tag { color: var(--sm-green); }

.sm-mode-blurb {
  font-family: var(--sm-mono);
  font-size: 10px;
  line-height: 1.65;
  letter-spacing: 0.7px;
  color: var(--sm-dim);
  flex: 1 1 auto;            /* pushes START to the bottom on both cards */
}

.sm-mode-go {
  margin-top: 4px;
  align-self: stretch;
  text-align: center;
  font-family: var(--sm-mono);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 3px;
  text-indent: 3px;
  color: #fff;
  background: linear-gradient(180deg, #1c2027, #0b0d11);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 3px;
  padding: 10px 8px;
}
.sm-mode:hover .sm-mode-go, .sm-mode:focus-visible .sm-mode-go {
  color: #0e1116;
  background: linear-gradient(180deg, var(--sm-gold-hot), var(--sm-gold));
  border-color: rgba(255, 240, 168, 0.6);
}
.sm-mode-freestyle:hover .sm-mode-go,
.sm-mode-freestyle:focus-visible .sm-mode-go {
  color: #06120e;
  background: linear-gradient(180deg, #b6ffe2, var(--sm-green));
  border-color: rgba(182, 255, 226, 0.6);
}

/* Two control lines, one shown at a time: a phone player has no keyboard to
   be told about, and a desktop player has no steering pads to find. The swap
   follows the same rule the pads themselves use. */
.sm-start-keys {
  font-family: var(--sm-mono);
  font-size: 10px;
  line-height: 1.6;
  letter-spacing: 1.6px;
  color: var(--sm-dimmer);
}
.sm-keys-touch { display: none; }
.sm-compact .sm-keys-desk,
.sm-portrait .sm-keys-desk { display: none; }
.sm-compact .sm-keys-touch,
.sm-portrait .sm-keys-touch { display: block; }
@media (hover: none) and (pointer: coarse) {
  .sm-keys-desk  { display: none; }
  .sm-keys-touch { display: block; }
}

/* Build stamp, pinned to the overlay rather than the panel so it cannot be
   pushed around by however tall the copy happens to be — and so a scrolling
   panel on a short phone never scrolls it out of reach of a bug report.
   Written to by the service worker (setVersionTag), so it also has to survive
   whatever the title screen looks like. */
.sm-start-version {
  position: absolute;
  bottom: max(12px, env(safe-area-inset-bottom, 0px));
  left: 0;
  right: 0;
  text-align: center;
  font-family: var(--sm-mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 3px;
  text-indent: 3px;
  color: rgba(255, 255, 255, 0.20);
}

/* THE WAY OUT: the last row of the title's column, as wide as PLAY above
   it (the same min(320px, 74vw), and the breakpoints that resize PLAY are
   matched below), with a little more air above it than the column's own gap.
   It used to be a small plate pinned to the overlay's top-left corner; ui.js's
   buildArcadeExit() says why it moved (hub CLAUDE.md §2, "One button, and it
   looks and behaves the same in every game"). Absent entirely unless
   ../arcade/exit.js answered.

   ONLY THE GEOMETRY IS HERE. Its colour lives in style-adventure.css, next to
   the rule it has to argue with: `.sm-adv .sm-btn:not(.sm-iconbtn)` paints
   every button in this game a gold hazard plate, and that selector is both
   more specific than a bare class AND in the later file, so a colour written
   here would simply never apply. Measured, not assumed. */
.sm-start-quit {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-width: min(320px, 74vw);
  min-height: 44px;
  margin-top: 8px;
  padding: 10px 18px;
  font-size: 12px;
  letter-spacing: 0.4px;
}
.sm-start-quit-ico { display: block; width: 17px; height: 17px; }
.sm-start-quit-ico .sm-btn-svg { width: 100%; height: 100%; filter: none; }
.sm-start-quit-label { line-height: 1; }

/* ===========================================================================
 * THE CORNER — THREE PLATES, PERMANENT, AND FIXED TO THE PAGE
 * ---------------------------------------------------------------------------
 * Menu, sound, fullscreen, left to right (hub CLAUDE.md §2, "The corner: three
 * plates, on every screen, and nothing else up there"). The same `.sm-iconbtn`
 * gold-on-dark plate with the hazard sliver — that stripe is what keeps a 42px
 * button from reading as generic mobile chrome, and it is why these are NOT
 * dressed like the .sm-start-quit door. Until 2026-10-11 there were two here
 * and the run parked two more beside them; see buildCorner() in js/ui.js.
 *
 * THEY NEVER GO ANYWHERE. This was `position: absolute` inside .sm-start,
 * which made it an ornament of the title gate: it existed on one screen out of
 * seven, and fullscreen was unreachable from the slot picker onwards. It is a
 * child of #ui-root now and `position: fixed`, so the same two plates are in
 * the same place on the title, in the slots, on the map, in the workshop, on
 * prep, on results and underground.
 *
 * Z-INDEX 30 IS THE WHOLE POINT. This game's layers run: stick 3, instruments
 * 6, lift panel 7, the ride wipe 9, the meta screens 14, the pause card 17,
 * the toast 19, the title 20, and the menu's settings face 28. These outlive
 * every one of them, so nothing any of them does may cover them — including
 * either face of the menu the first plate opens, which is what lets a second
 * press of it close the menu again.
 *
 * WHERE IT SITS. --sm-top-y down and --sm-gutter in. The in-mine instrument
 * bar on a phone stops short of it by --sm-ah-icons (style-adventure.css),
 * which is sized for exactly these three plates.
 *
 * SAFE AREAS ARE ARITHMETIC NOW, not inheritance: `fixed` positions against
 * the viewport, so #ui-root's padding does not apply and both insets are added
 * back here. pointer-events too — #ui-root is pointer-events:none, and this
 * used to inherit `auto` from .sm-start.
 * ======================================================================== */
.sm-corner {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + var(--sm-top-y));
  right: calc(env(safe-area-inset-right, 0px) + var(--sm-gutter));
  display: flex;
  gap: 8px;
  pointer-events: auto;
  z-index: 30;
}
/* THE `hidden` ATTRIBUTE HAS TO WIN HERE, AND ON ITS OWN IT DOES NOT. The UA
   sheet's `[hidden] { display: none }` loses to any author rule, and
   .sm-iconbtn sets `display: flex`. Without this the screen plate would be
   VISIBLE on an iPhone — the one browser the attribute exists to hide it from
   — and pressing it would do nothing at all. */
.sm-corner .sm-iconbtn[hidden] { display: none; }

/* BOTH STATES ARE DRAWN; aria-pressed PICKS ONE. The speaker carries its waves
   and its slash, the screen plate its expand and its contract, and ui.js
   writes aria-pressed in the same breath as the label — so the picture is
   read off the very attribute a screen reader announces, and cannot drift
   from it. `.sm-g-up` is the unpressed drawing (audible, windowed),
   `.sm-g-down` the pressed one (muted, filling the screen). */
.sm-iconbtn[aria-pressed="true"] .sm-g-up,
.sm-iconbtn:not([aria-pressed="true"]) .sm-g-down { display: none; }

/* ===========================================================================
 * THE MENU, SETTINGS FACE — two levels, the language, Back and the way out
 * ---------------------------------------------------------------------------
 * What the three-bar plate opens anywhere a run is not live (underground it
 * opens the pause card instead). Still classed `.sm-sound*`, because these
 * rules also dress the ENGINE / EFFECTS rows wherever they appear — the pause
 * card's copy included — and renaming them buys nothing. A modal card with
 * its own backdrop, the same shape as the tester panel below; a child of
 * #ui-root, not of .sm-start, because the plate is on every screen.
 *
 * Z 28: over the title (20), the toast (19), the pause card (17), the meta
 * screens (14) and the whole in-mine HUD — while it is up it is the only thing
 * on screen. BELOW .sm-corner (30) on purpose, so the plate that opened it
 * stays lit above its own scrim and a second press closes it.
 *
 * Still `absolute` rather than `fixed`, so the scrim covers #ui-root's padding
 * box exactly as it did inside the overlay — one less thing to re-measure.
 * ======================================================================== */
.sm-sound {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(ellipse at center, rgba(4,6,9,0.62), rgba(2,3,5,0.94));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
  z-index: 28;
}
.sm-sound-on { opacity: 1; pointer-events: auto; }

.sm-sound-card {
  width: min(400px, 90vw);
  padding: 22px 22px 20px;
  text-align: left;
  /* A safety net on a very short viewport, not a plan — the landscape block at
     the end of this section is the plan. .sm-panel sets overflow:hidden, so
     this has to override it or the net is not there at all. */
  max-height: 92vh;
  overflow: auto;
  touch-action: pan-y;          /* see .sm-start-inner — same trap, same fix */
  transform: translateY(14px) scale(0.97);
  transition: transform 0.3s cubic-bezier(0.16, 1.5, 0.35, 1);
}
.sm-sound-on .sm-sound-card { transform: none; }

.sm-sound-kicker {
  font-family: var(--sm-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 5px;
  text-indent: 5px;
  color: var(--sm-dimmer);
}
.sm-sound-title {
  font-size: 30px;
  font-weight: 900;
  letter-spacing: 6px;
  text-indent: 6px;
  line-height: 1.1;
  margin-bottom: 10px;
  color: #fff;
  text-shadow: 0 0 26px rgba(255, 194, 31, 0.4), 0 3px 0 rgba(0, 0, 0, 0.6);
}

.sm-sound-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
}
/* ONE GUTTER FOR BOTH ROWS, so THE RIG and THE ROCK start their track at the
   same x (hub CLAUDE.md §2). 78px is the longer of the two names at this size
   and tracking; the shorter one simply has room. */
.sm-sound-label {
  flex: 0 0 78px;
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 2px;
  color: var(--sm-dim);
}
/* `1 1 0` and not `1 1 auto`: from a zero basis the two tracks come out
   identical whatever their labels say, which is the point — these are two
   equals, and a gauge sized to its own label says one of them matters less. */
.sm-sound-range {
  flex: 1 1 0;
  min-width: 0;
  -webkit-appearance: none;
  appearance: none;
  height: 44px;              /* the touch box; the visible gauge is the track */
  margin: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  /* Vertical pans still scroll the card on a short screen; horizontal is ours,
     which is what makes the thumb draggable with a finger. */
  touch-action: pan-y;
}
.sm-sound-out {
  flex: 0 0 38px;
  text-align: right;
  font-family: var(--sm-mono);
  font-size: 14px;
  font-weight: 800;
  color: var(--sm-gold);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 10px rgba(255, 194, 31, 0.45);
}

/* --- the gauge ---------------------------------------------------------
 * A bare range input is an OS control and belongs to no game. This one is the
 * same cut channel with the same amber fill as the fuel and hold meters, and
 * the thumb is a machined lever plate wearing the panels' own bevel.
 * --sm-fill is written by ui.js on drag and on open; the 100% fallback is what
 * a browser that never runs that code still shows.
 * Both vendor pseudo-elements are spelled out — there is no shared selector,
 * and one rule listing both is dropped by both.
 * ---------------------------------------------------------------------- */
.sm-sound-range::-webkit-slider-runnable-track {
  height: 10px;
  border-radius: 2px;
  background:
    linear-gradient(90deg, var(--sm-gold-hot), var(--sm-gold))
      0 0 / var(--sm-fill, 100%) 100% no-repeat,
    #0a0705;
  /* The amber rim is an inset ring rather than a border, so the track is
     exactly 10px tall whatever the UA thinks box-sizing means on a vendor
     pseudo-element — which is what makes the thumb's margin-top below an exact
     number instead of an approximation. */
  box-shadow:
    inset 0 0 0 1px rgba(255, 194, 31, 0.30),
    inset 0 1px 3px rgba(0, 0, 0, 0.9);
}
.sm-sound-range::-moz-range-track {
  height: 10px;
  border-radius: 2px;
  background: #0a0705;
  box-shadow:
    inset 0 0 0 1px rgba(255, 194, 31, 0.30),
    inset 0 1px 3px rgba(0, 0, 0, 0.9);
}
/* Firefox fills the track itself, so it needs no --sm-fill. */
.sm-sound-range::-moz-range-progress {
  height: 10px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--sm-gold-hot), var(--sm-gold));
}
.sm-sound-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 26px;
  margin-top: -8px;          /* (track 10 - thumb 26) / 2 */
  border-radius: 3px;
  border: 1px solid rgba(255, 255, 255, 0.34);
  background: linear-gradient(180deg, #5a636f 0%, #262a32 46%, #14171c 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    inset 0 -2px 0 rgba(0, 0, 0, 0.75),
    0 3px 8px rgba(0, 0, 0, 0.65);
}
.sm-sound-range::-moz-range-thumb {
  width: 16px;
  height: 26px;
  border-radius: 3px;
  border: 1px solid rgba(255, 255, 255, 0.34);
  background: linear-gradient(180deg, #5a636f 0%, #262a32 46%, #14171c 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    inset 0 -2px 0 rgba(0, 0, 0, 0.75),
    0 3px 8px rgba(0, 0, 0, 0.65);
}
.sm-sound-range:focus-visible {
  outline: 2px solid var(--sm-cyan);
  outline-offset: 2px;
  border-radius: 3px;
}

.sm-sound-hint {
  margin-top: 14px;
  font-family: var(--sm-mono);
  font-size: 9.5px;
  line-height: 1.7;
  letter-spacing: 1.2px;
  color: var(--sm-dimmer);
}
.sm-sound-back {
  width: 100%;
  margin-top: 16px;
  min-height: 46px;
}

/* A MENU MUST NOT SCROLL ON A PHONE ON ITS SIDE (hub CLAUDE.md §2). This card
   is short enough to need no re-columning — buying back the last thirty pixels
   from the furniture is the whole fix. The kicker and the hint are the two
   things here that are flavour rather than control, so they are what goes,
   exactly as .sm-mode-blurb does at this same breakpoint above. Control
   heights stop at 38: this fires on a phone, where a finger is the pointer. */
/* THE WAY OUT, LAST ON THE SETTINGS FACE: the title's door (its look is in
   style-adventure.css beside .sm-start-quit), full column width, with a little
   more air above it than BACK has. #ui-root, because the compact and tiny
   rules for the title's door in style-adventure.css would otherwise win
   on order and give it the title's fixed width. */
#ui-root .sm-sound-card .sm-btn.sm-menu-quit {
  width: 100%;
  min-width: 0;
  margin-top: 18px;
}

@media (max-height: 560px) {
  #ui-root .sm-sound-card .sm-btn.sm-menu-quit { margin-top: 12px; min-height: 40px; }
  .sm-sound-card { padding: 14px 16px 14px; }
  .sm-sound-kicker { display: none; }
  .sm-sound-title { font-size: 22px; letter-spacing: 4px; text-indent: 4px; margin-bottom: 4px; }
  .sm-sound-row { margin-top: 8px; }
  .sm-sound-range { height: 38px; }
  .sm-sound-hint { display: none; }
  .sm-sound-back { margin-top: 12px; min-height: 40px; }
}

/* ---------------------------------------------------------------------------
 * THE LANGUAGE ROW — js/i18n.js langRow(), on the menu's settings face and on
 * the pause card, and nowhere else (hub CLAUDE.md §2, "Two
 * languages, Norwegian first")
 * ---------------------------------------------------------------------------
 * Laid out as a third row of the sound panel on purpose: the caption takes the
 * same 78px gutter as ENGINE and EFFECTS, so all three controls start at one x
 * (§2's "one logical group is laid out as one"). The two buttons are equals —
 * `1 1 0`, not auto, or ENGLISH would come out wider than NORSK and look like
 * the one that matters. The chosen one wears the gold the gauges fill with,
 * read off aria-pressed so what is lit and what is announced cannot drift.
 * The flags are the hub's art at a fixed height, with a hairline so the white
 * of the Norwegian cross does not dissolve into a light plate.
 * ---------------------------------------------------------------------------*/
.sm-lang {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
}
.sm-lang-label {
  flex: 0 0 78px;
  font-family: var(--sm-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 2px;
  color: var(--sm-dim);
  text-align: left;
}
.sm-lang-seg {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  gap: 8px;
}
.sm-btn.sm-lang-btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  padding: 8px 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  letter-spacing: 1.6px;
  color: var(--sm-dim);
}
.sm-btn.sm-lang-btn[aria-pressed="true"] {
  color: var(--sm-gold);
  border-color: rgba(255, 194, 31, 0.6);
  box-shadow:
    inset 0 1px 0 var(--sm-edge-hi),
    inset 0 -2px 0 var(--sm-edge-lo),
    0 0 12px rgba(255, 194, 31, 0.28);
}
.sm-btn.sm-lang-btn:focus-visible {
  outline: 2px solid var(--sm-cyan);
  outline-offset: 2px;
}
.sm-flag {
  flex: 0 0 auto;
  width: auto;
  height: 14px;
  border-radius: 1px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55);
}
.sm-lang-name { white-space: nowrap; }
@media (max-height: 560px) {
  .sm-lang { margin-top: 8px; }
  .sm-btn.sm-lang-btn { min-height: 38px; padding: 6px 8px; }
}
.sm-tiny .sm-lang-label { flex-basis: 66px; letter-spacing: 1.2px; }
.sm-tiny .sm-lang { gap: 9px; }
.sm-tiny .sm-btn.sm-lang-btn { gap: 6px; padding: 6px; letter-spacing: 1px; }

/* ---------------------------------------------------------------------------
 * TESTER MODE — revealed by the title-gate cheat code (see ui.js)
 * ---------------------------------------------------------------------------
 * The button sits in the title's bottom-right corner, quiet until the code
 * lands; the panel is a modal card over the title with its own backdrop.
 * Both live INSIDE .sm-start, so .sm-start-off dismisses them with the title.
 * ------------------------------------------------------------------------ */
.sm-start-tester {
  position: absolute;
  right: max(14px, env(safe-area-inset-right, 0px));
  bottom: max(10px, env(safe-area-inset-bottom, 0px));
  padding: 7px 13px;
  font-size: 9.5px;
  letter-spacing: 2px;
  color: var(--sm-cyan);
  border-color: rgba(102, 226, 255, 0.40);
  background: rgba(10, 14, 20, 0.72);
}
.sm-start-tester.sm-tester-pop { animation: sm-tester-pop 0.45s ease-out; }
@keyframes sm-tester-pop {
  0%   { transform: scale(0.6); opacity: 0; }
  60%  { transform: scale(1.08); }
  100% { transform: none; opacity: 1; }
}
.sm-tester {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(4, 6, 10, 0.72);
}
.sm-tester.sm-tester-on { display: flex; }
.sm-tester-card {
  width: min(440px, calc(100vw - 32px));
  background: var(--sm-panel);
  border: 1px solid var(--sm-border);
  border-radius: var(--sm-radius);
  padding: 16px 18px 18px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.7);
}
.sm-tester-title {
  font-family: var(--sm-mono);
  font-weight: 800;
  font-size: 14px;
  letter-spacing: 4px;
  color: var(--sm-cyan);
}
.sm-tester-note {
  margin-top: 6px;
  font-family: var(--sm-mono);
  font-size: 8.5px;
  letter-spacing: 1.6px;
  line-height: 1.6;
  color: var(--sm-dim);
}
.sm-tester-slot {
  margin-top: 14px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--sm-border);
  border-radius: var(--sm-radius);
  background: rgba(10, 13, 18, 0.6);
}
.sm-tester-info { flex: 1 1 auto; min-width: 0; }
.sm-tester-name {
  font-family: var(--sm-mono);
  font-weight: 800;
  font-size: 12px;
  letter-spacing: 2px;
  color: #fff;
}
.sm-tester-stats {
  margin-top: 3px;
  font-family: var(--sm-mono);
  font-size: 9px;
  letter-spacing: 1.2px;
  color: var(--sm-dim);
  font-variant-numeric: tabular-nums;
}
.sm-tester-cash {
  flex: 0 0 auto;
  white-space: nowrap;
  font-size: 10px;
  letter-spacing: 1px;
  color: var(--sm-gold);
  border-color: rgba(255, 194, 31, 0.45);
}
.sm-tester-cash:disabled { opacity: 0.35; cursor: default; }
.sm-tester-acts {
  margin-top: 14px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.sm-tester-go { flex: 1 1 auto; }
.sm-tester-quiet { opacity: 0.8; }
@media (prefers-reduced-motion: reduce) {
  .sm-start-tester.sm-tester-pop { animation: none; }
}

/* Freestyle: no countdown panel at all. The centred column closes the gap by
   itself — that is the whole reason it is a flex column. */
.sm-notimer .sm-clockwrap { display: none; }

/* While the menu is up it is the only thing on screen. */
.sm-menu .sm-hint { display: none; }
/* ...and the steering pads must not eat the mode cards' taps behind it. The
   overlay is pointer-events:auto and paints above them, so this is belt and
   braces — but a pad that swallowed a START tap would be unplayable. */
.sm-menu .sm-pads { display: none; }

/* --- opt-in update button (menu only) ---------------------------------
   Hidden unless a new build is precached and waiting. Deliberately reads as
   an offer rather than an alarm: nothing is broken, there is simply a newer
   version parked and ready whenever the player wants it. */
.sm-update {
  margin: 0;
  padding: 11px 18px;
  font-family: var(--sm-mono);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 2.4px;
  text-indent: 2.4px;
  color: #06120e;
  background: linear-gradient(180deg, #b6ffe2, var(--sm-green));
  border: 1px solid rgba(182, 255, 226, 0.7);
  border-radius: 4px;
  cursor: pointer;
  /* Inherits from .sm-start; see .sm-mode. */
  box-shadow: 0 0 22px rgba(88, 224, 138, 0.45), inset 0 1px 0 rgba(255,255,255,0.4);
  transition: transform 0.15s ease, filter 0.15s ease;
}
.sm-update:hover { transform: translateY(-2px); filter: brightness(1.08); }
.sm-update:active { transform: translateY(0); }
.sm-update:disabled { filter: grayscale(0.6) brightness(0.8); cursor: default; }

@keyframes sm-update-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.sm-update-on { animation: sm-update-in 0.4s cubic-bezier(0.16, 1.4, 0.3, 1); }

/* Two cards need ~700px to sit side by side without the blurbs going ragged.
   Below that they stack — a width question, not a "compact" one, which is why
   this is a media query and not another .sm-compact rule. */
@media (max-width: 700px) {
  .sm-modes { flex-direction: column; align-items: center; }
  /* `flex: 1 1 0` gives the cards equal WIDTHS in a row, but once the axis
     turns vertical that zero basis becomes a zero HEIGHT — and because the
     card sets overflow:hidden its automatic minimum size is 0 too, so it
     collapsed to just its padding. Height must come from content here. */
  .sm-mode { flex: 0 0 auto; width: 100%; max-width: 380px; }
  .sm-mode-blurb { flex: 0 0 auto; }
}
/* A SHORT WINDOW IS NOT THE SAME QUESTION AS A SMALL ONE. 1200x540 is not
   compact by ui.js's rules (900 wide, 520 tall) and still has no vertical room,
   so the brand turns into a ROW here: mark beside the wordmark, one line
   instead of a stack. The .sm-compact rules below repeat this for phones, which
   need it for the same reason and cannot inherit it (a class on #ui-root
   outranks a bare class in here). */
@media (max-height: 560px) {
  .sm-start-logo { font-size: clamp(30px, 6vw, 52px); }
  .sm-start-inner { gap: 12px; --sm-mark: 74px; }
  .sm-start-brand { flex-direction: row; gap: 16px; }
  .sm-start-tag { display: none; }
  .sm-mode-blurb { display: none; }   /* keep both cards fully on screen */
}

/* ===========================================================================
 * HINT + DEBUG (bottom)
 * ======================================================================== */
.sm-hint {
  position: absolute;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--sm-mono);
  font-size: 10px;
  letter-spacing: 1.4px;
  color: rgba(255, 255, 255, 0.24);
  white-space: nowrap;
  transition: opacity 1.2s ease 3s;
}
.sm-hint-fade { opacity: 0; }

.sm-debug {
  position: absolute;
  bottom: 9px;
  left: 12px;
  font-family: var(--sm-mono);
  font-size: 10px;
  letter-spacing: 0.4px;
  color: rgba(140, 210, 255, 0.40);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 2px #000;
}

/* ===========================================================================
 * COMPACT / TINY / PORTRAIT LAYOUTS — toggled from ui.js, no pixel maths in JS
 * Order matters: compact first, portrait after it (a phone is both, and the
 * portrait rules are the more specific intent), tiny last.
 * ======================================================================== */
.sm-compact {
  --sm-top-y: 6px;
  --sm-top-h: 38px;
  --sm-gutter: 7px;
  --sm-btn-size: 38px;
  --sm-cur-max: 30px;
  --sm-cur-vw: 7.6vw;
  --sm-cur-chrome: 130px;
}
/* THE TOP-STRIP BUDGET IS THE WHOLE ARGUMENT HERE.
   The strip is one line tall and the score owns the middle of it. The
   PWR / CUT / MAG chips are what gives, because the upgrade rail down the
   left edge says what is installed far better than three numbers did. The
   multiplier chip stays — nothing else reports it. */
.sm-compact .sm-stat { display: none; }
.sm-compact .sm-stat-mult {
  display: block;
  font-size: 9px;
  padding: 3px 5px;
  letter-spacing: 0.3px;
}
.sm-compact .sm-topbar { padding: 0 9px; }
.sm-compact .sm-clock { padding: 5px 12px 7px; min-width: 114px; }
.sm-compact .sm-clock-label { font-size: 8px; letter-spacing: 3px; text-indent: 3px; }
.sm-compact .sm-clock-digits { font-size: 27px; height: 29px; line-height: 29px; }
.sm-compact .sm-clock-urgent .sm-clock-digits { font-size: 31px; }
.sm-compact .sm-clock-bar { margin-top: 5px; height: 4px; }
.sm-compact .sm-clock-gain { font-size: 21px; }

/* The rail starts below the score strip and uses smaller tiles. Its ceiling
   has to leave room for the haul tally underneath it — otherwise a long run
   wraps a second column straight down into the tally. */
.sm-compact .sm-rail {
  left: 7px;
  gap: 5px;
  max-height: calc(100vh - 300px);
}

/* THE TALLY IS LIFTED OFF THE BOTTOM EDGE, and this is what "move HAUL up to
   make room for the buttons" means: the steering pads are full height, so the
   bottom-left corner now belongs to the left pad's chevron. The tally sits
   above it — it is pointer-events:none either way, so this is purely about
   not printing the haul on top of a control.
   It also narrows and shows ORES ONLY: on a short screen eleven rows is most
   of the left edge, and dirt/stone/rubble are the lines nobody reads mid-run.
   !important because updateTally() clears the inline display on reveal. */
.sm-compact .sm-tally {
  left: 7px;
  bottom: max(96px, 15vh);
  width: 96px;
  gap: 1px;
}
.sm-compact .sm-tally-row:not(.sm-tally-ore) { display: none !important; }
.sm-compact .sm-tally-row { height: 15px; gap: 5px; }
.sm-compact .sm-tally-name { font-size: 8px; letter-spacing: 0.3px; }
.sm-compact .sm-tally-count { font-size: 9px; }
.sm-compact .sm-haul-row { height: 21px; padding: 0 7px; gap: 6px; }
.sm-compact .sm-haul-val { font-size: 12px; min-width: 54px; }
.sm-compact .sm-rail-tile { width: 30px; height: 30px; border-left-width: 2px; }
.sm-compact .sm-rail-ico { width: 18px; height: 18px; }
.sm-compact .sm-rail-lv {
  min-width: 13px; height: 13px; line-height: 13px; font-size: 9px; right: -3px; bottom: -3px;
}
/* fps and particle counts printed across the middle of a phone screen is a
   development readout on a device with no keyboard to turn it off. */
.sm-compact .sm-debug { display: none; }
.sm-compact .sm-zonelabel,
.sm-compact .sm-zonecore { font-size: 9px; letter-spacing: 1.6px; }
.sm-compact .sm-buttons { gap: 6px; }
/* :not() because an icon plate is sized by --sm-btn-size, and a min-width
   meant for a word would stretch it into a rectangle. */
.sm-compact .sm-btn:not(.sm-iconbtn) { min-width: 62px; padding: 9px 10px; font-size: 10px; letter-spacing: 1px; }
.sm-compact .sm-btn-svg { width: 18px; height: 18px; }
.sm-compact .sm-hint { display: none; }
/* The whole top cluster is ~135px on a phone in landscape, so the percentage
   alone is not enough — the floor is what keeps the announcement off it. */
.sm-compact .sm-toast { top: max(25%, 148px); }
.sm-compact .sm-banner { top: 62%; }
/* The toast drops to 25% in compact, so the splash drops with it to keep the
   same clear gap between the two when both happen to be up. */
.sm-compact .sm-splash-inner { top: 47%; }
.sm-compact .sm-splash-sub { letter-spacing: 3px; text-indent: 3px; }
.sm-compact .sm-card { padding: 18px 18px 16px; }
.sm-compact .sm-cell-value { font-size: 17px; }
.sm-compact .sm-pause-card { padding: 18px 18px 16px; }
.sm-compact .sm-pause-title { font-size: 28px; letter-spacing: 4px; text-indent: 4px; }
/* Compact can be short-and-wide (1280x600) as well as narrow, so this only
   tightens the rhythm — the side-by-side/stacked decision is a width call and
   lives in the media query below. */
.sm-compact .sm-start-inner { gap: 14px; padding: 18px 16px; --sm-mark: 78px; }
/* A PHONE IN LANDSCAPE has ~390px of height and the wordmark is the only thing
   on this screen that has to be big. Mark beside it, tagline gone, START still
   a full-size touch target. :not(.sm-portrait) because a portrait phone is
   compact too and wants the exact opposite — see the portrait block. */
.sm-compact:not(.sm-portrait) .sm-start-brand { flex-direction: row; gap: 16px; }
.sm-compact:not(.sm-portrait) .sm-start-tag { display: none; }
/* Restates padding and font-size because it is overriding the generic compact
   .sm-btn rule a few lines above, which shrank both for the mine's controls. */
.sm-compact .sm-btn.sm-start-go {
  min-width: min(300px, 78vw);
  min-height: 48px;
  padding: 13px 20px;
  font-size: 13px;
  letter-spacing: 4px;
  text-indent: 4px;
}
/* The sweep is sized in vmax, which on a landscape phone is the LONG edge — it
   ended up wider than the screen and stopped reading as a beam. */
.sm-compact .sm-start-sweep { width: 62vmin; margin-left: -31vmin; }
.sm-compact .sm-mode { padding: 15px 16px 13px; gap: 5px; }
.sm-compact .sm-mode-title { font-size: 19px; }
.sm-compact .sm-mode-blurb { font-size: 9.5px; line-height: 1.55; }
.sm-compact .sm-mode-go { padding: 8px; }
.sm-compact .sm-hs { margin-top: 12px; padding-top: 10px; }
.sm-compact .sm-hs-row { font-size: 11px; padding: 4px 7px; gap: 7px; }
.sm-compact .sm-score { padding: 8px 11px 9px; }

/* ---------------------------------------------------------------------------
 * PORTRAIT — the shape the game is designed for.
 * Taller than it is wide, phone or tablet. The screen is narrow and long, the
 * camera frames the entire 1280-unit lane, and the machine is small: the HUD's
 * whole job here is to stay out of the vertical middle and off the thumbs.
 * ------------------------------------------------------------------------ */
.sm-portrait .sm-tally { bottom: max(104px, 15vh); }
/* A tall screen has room to push the announcement further from the clock. */
.sm-portrait .sm-toast { top: max(26%, 190px); }
.sm-portrait .sm-banner { top: 58%; }
/* Portrait tablets are NOT compact, but they are still held in two hands and
   still want the readouts small enough to leave the mine visible. */
.sm-portrait .sm-debug { display: none; }

/* THE TITLE SCREEN IS AT ITS BEST IN PORTRAIT and this is where it gets the
   room to show it: the mark stays stacked above the wordmark, the tagline stays,
   and the shaft runs the full height of the screen the way it does in play. */
.sm-portrait .sm-start-inner { --sm-mark: 104px; gap: 18px; }
.sm-portrait .sm-start-brand { flex-direction: column; gap: 15px; }
.sm-portrait .sm-start-tag { display: block; }

/* At 390px "ADVENTURE" at the vw-scaled size is thin enough to lose the second
   half of the wordmark, so it gets a floor of its own — but a small one: 6px of
   tracking here pushes it onto a second line. */
.sm-tiny .sm-start-sub { font-size: 11px; letter-spacing: 4px; text-indent: 4px; }
/* Same argument, one line down: 5px of tracking on "RUN A MINING COMPANY" wraps
   at 390px, and a two-line positioning statement is not a positioning
   statement. The mark comes down with it to keep the button above the fold. */
.sm-tiny .sm-start-inner { --sm-mark: 88px; }
/* ...except upright, where a phone has 844px of height and the mark is the
   thing worth spending it on. Three classes, so it beats the line above. */
.sm-tiny.sm-portrait .sm-start-inner { --sm-mark: 100px; }
.sm-tiny .sm-start-pick { letter-spacing: 3px; text-indent: 3px; }
.sm-tiny .sm-start-tag { font-size: 9px; letter-spacing: 1.6px; text-indent: 1.6px; }

.sm-tiny {
  --sm-top-h: 34px;
  --sm-btn-size: 36px;
  --sm-cur-max: 26px;
  --sm-cur-vw: 8.2vw;
  --sm-cur-chrome: 108px;
}
.sm-tiny .sm-statrow { gap: 4px; }
.sm-tiny .sm-card-grid { grid-template-columns: 1fr; }
.sm-tiny .sm-btn:not(.sm-iconbtn) { min-width: 52px; padding: 9px 6px; font-size: 9px; letter-spacing: 0.6px; }
/* AFTER the rule above, not before it — same three-class weight, so this only
   wins by coming second. START is the only button on the narrowest screen the
   game supports that has to be unmissable; everything else in here is a control
   in a crowded mine and wants to be small. */
.sm-tiny .sm-btn.sm-start-go {
  min-width: min(280px, 84vw);
  min-height: 48px;
  padding: 13px 16px;
  font-size: 12px;
  letter-spacing: 3.4px;
  text-indent: 3.4px;
}
.sm-tiny .sm-btn-svg { width: 17px; height: 17px; }
.sm-tiny .sm-clock { padding: 4px 9px 6px; min-width: 96px; }
.sm-tiny .sm-clock-label { display: none; }
.sm-tiny .sm-clock-digits { font-size: 23px; height: 25px; line-height: 25px; }
.sm-tiny .sm-clock-urgent .sm-clock-digits { font-size: 26px; }
.sm-tiny .sm-rail-tile { width: 26px; height: 26px; }
.sm-tiny .sm-rail-ico { width: 16px; height: 16px; }
/* Under 420px the swatch has to carry the identification on its own — the
   colours are the same ones the debris was drawn in, and the row keeps its
   title attribute. */
.sm-tiny .sm-tally { width: 52px; }
.sm-tiny .sm-tally-name { display: none; }
.sm-tiny .sm-tally-head { font-size: 7px; letter-spacing: 2px; }
.sm-tiny .sm-tally-count { font-size: 10px; margin-left: auto; }
.sm-tiny .sm-haul-count { display: none; }
.sm-tiny .sm-haul-name { font-size: 9px; letter-spacing: 1px; }
.sm-tiny .sm-hs-depth { display: none; }
.sm-tiny .sm-hs-row { grid-template-columns: 18px 1fr auto; }
.sm-tiny .sm-hs-form { flex-direction: column; }
.sm-tiny .sm-hs-save { width: 100%; }
/* Under 420px the sound panel's gutter is eating the gauge rather than lining
   it up, so it tightens. Both rows still share it, which is the only thing it
   is for. */
.sm-tiny .sm-sound-label { flex-basis: 66px; letter-spacing: 1.2px; }
.sm-tiny .sm-sound-row { gap: 9px; }
.sm-tiny .sm-sound-out { flex-basis: 32px; font-size: 13px; }

/* Users who ask for less motion get the information, not the theatre. */
@media (prefers-reduced-motion: reduce) {
  .sm-pop, .sm-flash, .sm-od-label, .sm-banner-final .sm-banner-text,
  .sm-clock-urgent, .sm-clock-kick, .sm-rail-pop, .sm-hs-cta, .sm-hs-row-new,
  .sm-zonecore,
  /* The title screen is all theatre: the sweep, the twinkling ore, the beam's
     pulse, the breathing subtitle and START's glow. Every one of them is
     removed here and NOTHING is lost — the beam, the ore and the button are all
     still fully visible, they just stop moving. */
  .sm-start-sweep, .sm-start-sub, .sm-mark-beam, .sm-mark-gem, .sm-start-go {
    animation: none !important;
  }
  /* ...except the ore field, which is invisible between twinkles. With the
     animation gone it has to be pinned ON, or seven deposits simply vanish. */
  .sm-ore {
    animation: none !important;
    opacity: 0.8;
    transform: rotate(45deg);
  }
  /* The steering pads still have to CONFIRM a press — that is feedback, not
     theatre — so they keep the state change and lose only the fade. */
  .sm-pad { transition-duration: 0.01ms !important; }
  /* The +10s float still has to be SEEN, so it keeps a short fade instead of
     the flight path. Losing it entirely would lose the information. */
  .sm-clock-gain-on { animation: sm-clock-gain-static 0.9s ease-out !important; }
  @keyframes sm-clock-gain-static {
    0%, 70% { opacity: 1; transform: translate(-50%, -34px); }
    100%    { opacity: 0; transform: translate(-50%, -34px); }
  }
  .sm-toast, .sm-banner, .sm-card, .sm-summary, .sm-start,
  .sm-pause, .sm-pause-card { transition-duration: 0.01ms !important; }
}

/* ===========================================================================
 * THE OPENING STING (js/intro.js) — a canvas the script creates and removes.
 * Over everything, the corner plates at 30 included, because it is a
 * curtain: for its three seconds nothing under it is meant to be reachable,
 * and a tap anywhere on it is the skip.
 * ======================================================================== */
#intro {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 40;
  touch-action: none;
  cursor: pointer;
}
