/*
 * Sigma Ups: Flight Recorder components and layout, in a chat-app shell on the sigmashake brand.
 *
 * Link order: ./brand/tokens.css (vendored, byte copy of shared/design-tokens/tokens.css: radii and type),
 * then ./su-tokens.css (the local layer: --sd-* shell colors, --su-* roles, spacing, type, motion,
 * stacking), then this file. This file holds no hex colors: every color is a var() from one of the two
 * token files.
 *
 * Red is the accent: the primary action on a surface, "you", the depth bar and the depth target. Text on
 * red is black. The shell (rail, sidebar, header, cards) is in the "App shell" section.
 */

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  height: 100%;
  background: var(--su-bg);
  color-scheme: dark;
}

body {
  min-height: 100dvh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--su-space-2);
  padding:
    max(var(--su-space-2), env(safe-area-inset-top))
    max(var(--su-space-2), env(safe-area-inset-right))
    max(var(--su-space-2), env(safe-area-inset-bottom))
    max(var(--su-space-2), env(safe-area-inset-left));
  background: var(--sd-bg);
  color: var(--sd-text);
  font-family: var(--su-font);
  font-size: var(--su-text-md);
  line-height: 1.4;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}

h1,
h2,
h3 {
  color: var(--sd-header);
  font-family: var(--su-font-display);
  font-weight: 900;
  line-height: 1.1;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--su-space-2);
  top: var(--su-space-2);
  z-index: var(--su-z-toast);
  padding: var(--su-space-3) var(--su-space-4);
  border-radius: var(--ss-radius-xl);
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
  font-weight: 800;
  text-decoration: none;
  transform: translateY(-300%);
}

.skip-link:focus-visible {
  transform: none;
  outline: 2px solid var(--su-fg);
  outline-offset: 2px;
}

a {
  color: var(--su-fg);
}

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

/* -- Icons: Hugeicons (MIT), inline SVG sprite at the top of <body>, 1.5 stroke on a 24 px glyph ---- */

.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.icon {
  width: 24px;
  height: 24px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

/* A glyph set beside a button whose label the game rewrites at run time. It sits in the button's
   padding, so the label text can change and the glyph stays. The glyph takes the button's colour
   through the wrapper (`.has-icon--primary` is the red button). */
.has-icon {
  position: relative;
  display: flex;
  color: var(--su-fg);
}

.has-icon > .icon {
  position: absolute;
  z-index: 1;
}

.has-icon--primary {
  color: var(--su-on-accent);
}

/* The glyph stands in for the label: the label stays for assistive technology, off screen. */
.has-icon--only {
  width: var(--su-hit);
  height: var(--su-hit);
  flex: none;
}

.has-icon--only > .icon {
  left: 10px;
  top: 10px;
}

.has-icon--only > button {
  width: 100%;
  height: 100%;
  padding: 0;
  overflow: hidden;
  text-indent: -999px;
  white-space: nowrap;
}

/* A toggle whose label stays fixed ("Music") and whose state is `aria-pressed`: off dims the glyph and
   strikes it through, so the two states differ by shape and not only by tone. */
.has-icon--only:has(> button[aria-pressed='false']) > .icon {
  opacity: 0.5;
}

.has-icon--only:has(> button[aria-pressed='false'])::after {
  content: '';
  position: absolute;
  left: 8px;
  top: 21px;
  width: 28px;
  height: 2px;
  border-radius: var(--ss-radius-pill);
  background: currentColor;
  transform: rotate(45deg);
  pointer-events: none;
  z-index: 1;
}

/* A tile: the glyph above its label. */
.has-icon--tile {
  flex-direction: column;
}

.has-icon--tile > .icon {
  left: 50%;
  top: var(--su-space-3);
  margin-left: -12px;
}

.has-icon--tile > button,
.has-icon--tile > a {
  flex: 1;
  padding-top: 40px;
}

/* -- Page shell ------------------------------------------------------------------------------- */

header,
footer {
  max-width: 100%;
  flex-shrink: 0;
}

/* The header is the 48 px bar of the shell: the title and the presence chip, and the blurb on a line under them. */
header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  gap: var(--su-space-1) var(--su-space-3);
  width: 100%;
  min-height: var(--sd-header-h);
  padding: 0 var(--su-space-3);
  border-bottom: 1px solid var(--sd-border);
}

.lockup {
  display: flex;
  align-items: center;
  gap: var(--su-space-3);
}

.lockup-mark {
  display: block;
  width: 32px;
  height: 32px;
  border-radius: var(--ss-radius-pill);
}

.lockup h1 {
  font-size: var(--su-text-xl);
  letter-spacing: 0.02em;
}

header p,
footer p {
  color: var(--sd-muted);
  font-size: var(--su-text-sm);
}

header p {
  flex: 1 1 100%;
  max-width: 52rem;
  padding-bottom: var(--su-space-2);
  text-align: left;
}

footer {
  display: flex;
  align-items: center;
  gap: var(--su-space-4);
}

footer a {
  display: inline-flex;
  align-items: center;
  min-height: var(--su-hit);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* A native shell shows the app, not a web page around it: no title block, blurb or footer. */
html[data-edition='store'] header,
html[data-edition='store'] footer {
  display: none;
}

/* The AI-governance house ad is a web-edition card: the store apps never show it (game.js
   showHouseAd() declines too; this keeps the card out of the tree if anything un-hides it). */
html[data-edition='store'] #house-ad {
  display: none;
}

main {
  flex: 1;
  width: 100%;
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: stretch;
  justify-content: center;
  gap: var(--su-space-4);
  min-height: 0;
  /* The size of main is the box that the stage must fit in. The stage reads it as cqw and cqh. */
  container-type: size;
}

/* The stage always has the 3:4 shape of the 480 x 640 canvas. Its width is the smaller of two limits:
   the width that is free (--stage-max-w) and the width that the free height gives at 3:4
   (--stage-max-h * 3 / 4). The height then follows from aspect-ratio. A box that is too big for the
   window gets smaller and stays 3:4. It is centered, so the free space is a letterbox or a pillarbox.
   A mode of the page changes the two limits only. It does not set width, height or max-* of the stage. */
#stage {
  --stage-max-w: calc(100cqw - 28rem);
  --stage-max-h: 100cqh;
  position: relative;
  aspect-ratio: 3 / 4;
  width: min(var(--stage-max-w), calc(var(--stage-max-h) * 3 / 4));
  height: auto;
  max-width: 100%;
  max-height: 100%;
  /* Not stretch: a stretched box would lose the 3:4 shape. */
  align-self: center;
  margin-inline: 0;
  min-height: 0;
  min-width: 0;
  border-radius: var(--ss-radius-2xl);
  overflow: hidden;
  border: 1px solid var(--su-line);
  /* The sky that the transparent top of the painted far plate shows (art/skyline-dragon/README.md). */
  background: var(--su-sky);
  background-clip: padding-box;
  box-shadow: 0 18px 48px rgb(0 0 0 / 0.45);
  flex: 0 0 auto;
}

/* The canvas and what is drawn on it (hit flash, toasts, the depth ruler). Over the whole stage on most layouts, where
   the HUD and the camera bar float over it; a tall phone sets it apart, between the HUD and the camera bar
   (see "A tall phone" below), so the playfield itself is unobstructed. */
#playfield {
  position: absolute;
  inset: 0;
}

/* The stage as the full screen element: Chrome's own :fullscreen rule gives it the width and height of the whole screen
   with !important, which an author rule cannot beat, so the stage is no longer 3:4. The playfield (the canvas and
   the depth ruler, which are placed by percent of it) keeps the shape: the largest 3:4 box on the screen, centred.
   The free space is a letterbox or a pillarbox of the stage's own sky. */
#stage:fullscreen #playfield {
  inset: 0;
  margin: auto;
  width: min(100%, calc(100vh * 3 / 4));
  height: min(100%, calc(100vw * 4 / 3));
}

/* -- Controls: one button, one segmented control, one switch, one slider, one list cell ---------- */

.menu-btn,
#restart,
#btn-overlay-menu,
#workout-panel button,
#camera-btn,
.set-btn,
.level-pick,
.workout-mode-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--su-space-2);
  min-height: var(--su-hit);
  min-width: var(--su-hit);
  padding: 0 var(--su-space-4);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-selected);
  color: var(--su-fg);
  font-family: var(--su-font);
  font-size: var(--su-text-md);
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  pointer-events: auto;
  transition:
    background-color var(--su-duration-fast) var(--su-ease),
    border-color var(--su-duration-fast) var(--su-ease),
    box-shadow var(--su-duration-fast) var(--su-ease),
    transform var(--su-duration-fast) var(--su-ease);
}

.menu-btn:hover,
#btn-overlay-menu:hover,
#workout-panel button:hover,
#camera-btn:hover,
.set-btn:hover,
.level-pick:hover,
.workout-mode-btn:hover {
  background: var(--sd-active);
}

.menu-btn:active,
#restart:active,
#btn-overlay-menu:active,
#workout-panel button:active,
#camera-btn:active,
.set-btn:active,
.level-pick:active {
  transform: scale(0.98);
}

.menu-btn:focus-visible,
#restart:focus-visible,
#btn-overlay-menu:focus-visible,
#workout-panel button:focus-visible,
#camera-btn:focus-visible,
.set-btn:focus-visible,
.level-pick:focus-visible,
.workout-mode-btn:focus-visible,
.skin-tile:focus-visible,
.su-rail-btn:focus-visible,
.su-channel:focus-visible,
.su-boon-card:focus-visible,
#btn-hud-music:focus-visible,
#btn-hud-menu:focus-visible,
footer a:focus-visible,
.cell-link:focus-visible,
#autoplay-summary a:focus-visible,
.back-link:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: 2px solid var(--su-fg);
  outline-offset: 3px;
}

/* The last action of a long card stays reachable. A screen that has one (Settings) is a column: its content scrolls
   in .menu-scroll and the action is a dock under it (below), so nothing scrolls behind the button. Where the card
   is not docked (the camera setup's Continue) the button sticks to the bottom of the scrolling menu on an opaque
   backing, so text scrolling under it is hidden, not ghosted. */
.sticky-action {
  position: sticky;
  bottom: var(--su-space-3);
  background-color: var(--sd-floating);
  box-shadow: 0 0 0 var(--su-space-3) var(--sd-floating);
}

/* Primary: the one red action on a surface. Its text is black. */
.menu-btn.primary,
#restart {
  border-color: var(--su-accent);
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
  font-weight: 800;
}

/* The hover state of the red button is a light edge. */
.menu-btn.primary:hover,
#restart:hover {
  background: var(--su-accent-fill);
  box-shadow: inset 0 0 0 2px var(--su-fg);
}

#workout-panel button.primary {
  border-color: var(--su-accent);
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
  font-weight: 800;
}

#workout-panel button.primary:hover {
  background: var(--su-accent-fill);
  box-shadow: inset 0 0 0 2px var(--su-fg);
}

/* Quiet: text only. */
.menu-btn.quiet {
  border-color: transparent;
  background: transparent;
  color: var(--su-fg-2);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.menu-btn.quiet:hover {
  background: var(--sd-hover);
}

/* Destructive confirm: a white outline with a dashed edge; it never borrows the red. */
.menu-btn.danger {
  border-style: dashed;
  border-color: var(--su-fg);
  background: var(--sd-sidebar);
}

button:disabled,
.menu-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Segmented control: workout mode and set length. */
.workout-mode-cluster,
.set-picker {
  display: inline-flex;
  padding: var(--su-space-1);
  gap: var(--su-space-1);
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-sidebar);
}

.workout-mode-btn,
.set-btn {
  border-color: transparent;
  border-radius: var(--ss-radius-lg);
  background: transparent;
  color: var(--su-fg-2);
  font-size: var(--su-text-sm);
}

.workout-mode-btn:hover,
.set-btn:hover {
  background: var(--sd-hover);
}

.workout-mode-btn.active,
.set-btn[aria-pressed='true'] {
  border-color: var(--su-fg);
  background: var(--sd-active);
  color: var(--su-fg);
}

.set-btn {
  min-width: var(--su-hit);
  font-family: var(--su-font-mono);
  font-variant-numeric: tabular-nums;
}

/* Switch: the one checkbox. The input is the 52 x 44 hit area; its two pseudo-elements draw the track
   and the knob. */
input[type='checkbox'] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: 52px;
  height: var(--su-hit);
  border: 0;
  background: transparent;
  cursor: pointer;
}

input[type='checkbox']::before {
  content: '';
  position: absolute;
  left: 0;
  top: var(--su-space-1);
  width: 52px;
  height: 36px;
  border: 1px solid var(--su-fg);
  border-radius: var(--ss-radius-pill);
  background: var(--sd-sidebar);
  transition: background-color var(--su-duration-fast) var(--su-ease);
}

input[type='checkbox']::after {
  content: '';
  position: absolute;
  left: var(--su-space-1);
  top: var(--su-space-2);
  width: 28px;
  height: 28px;
  border-radius: var(--ss-radius-pill);
  background: var(--su-fg);
  transition: transform var(--su-duration-fast) var(--su-ease);
}

/* On is a white track with a black knob. Red is not a state colour here: it stays for the one primary
   action, you, and the live depth target. */
input[type='checkbox']:checked::before {
  border-color: var(--su-fg);
  background: var(--su-fg);
}

input[type='checkbox']:checked::after {
  background: var(--su-bg);
  transform: translateX(16px);
}

/* Slider: the one range input. */
input[type='range'] {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: var(--su-hit);
  background: transparent;
  cursor: pointer;
}

input[type='range']::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: var(--ss-radius-pill);
  background: var(--su-fg-3);
}

input[type='range']::-moz-range-track {
  height: 8px;
  border-radius: var(--ss-radius-pill);
  background: var(--su-fg-3);
}

input[type='range']::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 24px;
  height: 24px;
  margin-top: -8px;
  border: 2px solid var(--su-bg);
  border-radius: var(--ss-radius-pill);
  background: var(--su-fg);
}

input[type='range']::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border: 2px solid var(--su-bg);
  border-radius: var(--ss-radius-pill);
  background: var(--su-fg);
}

/* Text, time and select fields. */
input[type='text'],
input[type='time'],
select {
  min-height: var(--su-hit);
  padding: 0 var(--su-space-3);
  border: 1px solid var(--su-fg-3);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-sidebar);
  color: var(--su-fg);
  font-family: var(--su-font);
  font-size: var(--su-text-md);
  font-weight: 600;
}

select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--su-space-8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--su-space-2) center;
  background-size: 20px;
  cursor: pointer;
}

/* List cell: a labelled row with its control on the right. */
.setting-row,
.vol-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--su-space-3);
  width: 100%;
  min-height: var(--su-hit);
  padding: 0 var(--su-space-2);
  border-radius: var(--ss-radius-md);
  font-size: var(--su-text-md);
  font-weight: 600;
  color: var(--su-fg);
  transition: background-color var(--su-duration-fast) var(--su-ease);
}

.setting-row:hover,
.vol-row:hover {
  background: var(--sd-hover);
}

.vol-row {
  display: grid;
  grid-template-columns: 5.5rem 1fr 3rem;
  gap: var(--su-space-2);
}

.vol-row > span:last-child {
  font-family: var(--su-font-mono);
  font-size: var(--su-text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--su-fg-2);
  text-align: right;
}

.setting-row select {
  max-width: 58%;
}

.setting-row input[type='time'] {
  color-scheme: dark;
}

/* Chips and pills: status text only, never a control. */
.stat-pill,
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--su-space-2);
  padding: var(--su-space-1) var(--su-space-3);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-pill);
  background: var(--su-scrim-strong);
  color: var(--su-fg);
  font-size: var(--su-text-sm);
  font-weight: 700;
  line-height: 1.3;
}

.menu-tag,
.menu-hint {
  color: var(--su-fg-2);
  font-size: var(--su-text-sm);
  font-weight: 500;
  line-height: 1.4;
}

.menu-error {
  color: var(--su-fault);
  font-size: var(--su-text-sm);
  font-weight: 700;
}

/* One uppercase kicker per surface. */
.menu-kicker {
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--su-fg-2);
}

/* The one switch every surface uses to disappear. It is `!important` on purpose: a plain
   `.hidden { display: none }` is outranked by any element rule that sets its own `display`
   (`.code-row`, `.overlay-dragons-row`, `#autoplay-panel`, ...) whether that rule comes before or
   after it, so a class toggle could pass while the element still painted (the empty "Challenge
   code" row, the blank You / Them dragon boxes and the autoplay debug HUD all shipped that way).
   Nothing else in this file may use `display: ... !important` (test/unit/mirror-flight.test.ts
   fails if one does), so `hidden` always wins; a surface that must show removes the class. */
.hidden {
  display: none !important;
}

/* -- Stage: canvas, HUD, camera bar ------------------------------------------------------------- */

#game {
  display: block;
  width: 100%;
  height: 100%;
  /* The stage is 3:4, so this changes nothing. If a box is ever not 3:4, the picture keeps its shape and
     the free space is a letterbox or a pillarbox, centered. It is never stretched. */
  object-fit: contain;
  object-position: center;
  cursor: pointer;
  /* Practice is a held press (the simulated chest): a touch that drifts must not turn into a pan or
     zoom gesture and cancel the hold mid-dive. */
  touch-action: none;
}

/* The HUD is one top row: the hero rep count, the status pills and the coach line side by side. It has to be a
   single row: the lockout depth (10 %) is only 57-89 css px below the top of the stage on a phone, and a HUD
   that stacks three rows (score, pills, coach) ended below it and covered the top half of every rep. A line
   that does not fit (a long coach line, a form fault, the hint before a set) wraps to a second row; none of
   those is shown while a rep is flown, when the depth target is drawn. */
#hud {
  /* The depth ruler's column on the right edge (a mark is about 2.75 rem wide, 0.5 rem off the edge): no pill ever
     reaches it, so the lockout and full-depth labels never sit under a pill. Both sides, so the score stays centred. */
  --su-ruler-col: 3.75rem;
  position: absolute;
  inset: 0;
  z-index: var(--su-z-hud);
  pointer-events: none;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  align-content: flex-start;
  justify-content: center;
  /* No safe-area inset here: the stage already sits inside body's safe-area padding, and adding the inset again
     pushed the whole HUD down by the notch (59 px on an iPhone 17) into the dragon's resting place. */
  padding: var(--su-space-3) calc(var(--su-space-3) + var(--su-ruler-col)) 0;
  gap: var(--su-space-1) var(--su-space-2);
}

/* The scrim is a plate behind that row (at least 0.72 alpha where the score sits) and is gone by 56 px: the
   lockout gate, the depth target line and the trail stay readable below it. */
#hud::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 3.5rem;
  z-index: -1;
  background: linear-gradient(180deg, var(--su-scrim) 0%, var(--su-scrim-strong) 55%, transparent 100%);
}

#hud .stat-pill {
  padding: var(--su-space-1) var(--su-space-2);
}

/* A status pill shows its whole text on one line: it wraps to the next row instead of squeezing ("Free hour: 60 min
   lef…"). The coach line and a form fault are sentences, so they may wrap inside their own pill. */
#hud .stat-pill:not(.coach):not(.posture-fault) {
  white-space: nowrap;
  flex: 0 0 auto;
}

/* The headline: the rep count ("3/10") over a small "reps" label in a workout run, the spire
   distance ("12m", no label) only in a challenge run or the autoplay demo. Tabular numerals keep
   the figure from shifting the HUD as it counts up. */
#score {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  font-family: var(--su-font-display);
  font-size: var(--su-display-sm);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: var(--su-fg);
}

#score-unit {
  margin-top: var(--su-space-1);
  font-family: var(--su-font);
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--su-fg-2);
}

#score-unit:empty {
  display: none;
}

/* The depth ruler: the two depths the course prescribes (10 % lockout, 90 % full depth) as plates on the right
   edge of the stage, above the HUD and its scrim and over the canvas, so they read wherever the gates and the
   dragon are. Each plate is solid field, never translucent: the red text and the resting grey
   stay readable whatever biome is behind. game.js sets --mark-top (a depth as a share of the stage height) and which
   plate is the live target (the one the next gate asks for), which is the red one. */
#depth-targets {
  position: absolute;
  inset: 0;
  z-index: var(--su-z-target);
  pointer-events: none;
}

.depth-mark {
  position: absolute;
  right: var(--su-space-2);
  top: var(--mark-top, 50%);
  transform: translateY(-50%);
  padding: var(--su-space-1) var(--su-space-2);
  border: 1px solid var(--su-fg-3);
  border-radius: var(--ss-radius-sm);
  background: var(--su-bg);
  color: var(--su-fg-2);
  font-family: var(--su-font-mono);
  font-size: var(--su-text-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.depth-mark[data-active='true'] {
  border-color: var(--su-accent);
  color: var(--su-accent);
}

.hud-pills {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--su-space-2);
  max-width: 100%;
}

.stat-pill.shields {
  border-color: var(--su-shield);
  transition: border-color var(--su-duration-base) var(--su-ease);
}

.stat-pill.shields .icon {
  width: 16px;
  height: 16px;
  color: var(--su-shield);
}

.stat-pill.shields[data-state='low'] {
  border-color: var(--su-fault);
}

.stat-pill.shields[data-state='low'] .icon {
  color: var(--su-fault);
}

.stat-pill.shields[data-state='empty'] {
  border-style: dashed;
  border-color: var(--su-fg-2);
}

.stat-pill.shields[data-state='empty'] .icon {
  color: var(--su-fg-2);
}

.stat-pill.shields.pulse-recharge {
  animation: shieldRechargeAnim var(--su-duration-slow) var(--su-ease);
}

.stat-pill.shields.pulse-hit {
  animation: shieldHitAnim var(--su-duration-slow) var(--su-ease);
}

@keyframes shieldRechargeAnim {
  0% { transform: scale(1); }
  40% {
    transform: scale(1.12);
    border-color: var(--su-shield);
  }
  100% { transform: scale(1); }
}

@keyframes shieldHitAnim {
  0% { transform: scale(1); }
  40% {
    transform: scale(1.1);
    border-color: var(--su-fg);
  }
  100% { transform: scale(1); }
}

/* Reserve width via tabular numerals so a jump from "1.0x" to a large autoplay multiplier (see
   window.SigmaUpsHud in game.js) never reflows the HUD row. The readout is for the autoplay
   spectator only: a player flying a set sees the rep count, not a speed multiplier. */
.stat-pill.speed {
  display: none;
  min-width: 3.6rem;
  justify-content: center;
  font-family: var(--su-font-mono);
  font-variant-numeric: tabular-nums;
}

body.autoplay-mode .stat-pill.speed {
  display: inline-flex;
}

/* The coach line has a red edge. Its text stays white: it sits on the translucent scrim over the sky. */
.stat-pill.coach {
  max-width: 100%;
  text-align: center;
  border-color: var(--su-pill-coach);
}

.stat-pill.posture-fault {
  max-width: 100%;
  text-align: center;
  border-color: var(--su-pill-fault);
  background: var(--su-fault-tint);
  animation: postureFaultPulse var(--su-duration-slow) var(--su-ease);
}

@keyframes postureFaultPulse {
  0% { transform: scale(1); }
  40% { transform: scale(1.04); }
  100% { transform: scale(1); }
}

#posture-offline-chip {
  flex-shrink: 0;
  color: var(--su-fg-2);
  font-size: var(--su-text-xs);
}

#hint {
  padding: var(--su-space-1) var(--su-space-3);
  border-radius: var(--ss-radius-pill);
  background: var(--su-scrim-strong);
  color: var(--su-fg);
  font-size: var(--su-text-sm);
  font-weight: 600;
  text-align: center;
}

#hint:empty {
  display: none;
}

/* Serpent Flight: the hint is a small note of one line, under the HUD row and above the playfield (SERPENT_FIELD_TOP in
   game.js). The side margins use the free ruler columns, so the line does not wrap in a narrow stage. It does not take
   the pointer, so a press under it still flies the dragon. It stays the same live text for a screen reader. */
#hint.hint-serpent {
  margin-inline: calc(-1 * var(--su-ruler-col));
  padding: 0 var(--su-space-2);
  font-size: var(--su-text-xs);
  font-weight: 500;
  white-space: nowrap;
  opacity: 0.85;
  pointer-events: none;
}

/* The camera bar: status text on its own line, then the three controls. */
#camera-bar {
  position: absolute;
  left: var(--su-space-3);
  right: var(--su-space-3);
  bottom: max(var(--su-space-3), env(safe-area-inset-bottom));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--su-space-2);
  padding: var(--su-space-2);
  border: 1px solid var(--su-line);
  border-radius: var(--ss-radius-xl);
  background: var(--su-scrim);
  pointer-events: auto;
  z-index: var(--su-z-hud);
}

#camera-btn {
  flex: 1 1 auto;
  padding-left: var(--su-space-8);
  position: relative;
}

/* State dot: the label says "Camera on / off"; the dot repeats it without colour alone. */
#camera-btn::before {
  content: '';
  position: absolute;
  left: var(--su-space-3);
  width: 10px;
  height: 10px;
  border: 2px solid var(--su-fg);
  border-radius: var(--ss-radius-pill);
}

#camera-btn.active::before {
  background: var(--su-clean);
  border-color: var(--su-clean);
}

#camera-status {
  order: -1;
  flex: 1 1 100%;
  color: var(--su-fg-2);
  font-size: var(--su-text-sm);
  font-weight: 500;
  line-height: 1.35;
}

#btn-hud-music,
#btn-hud-menu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--su-hit);
  height: var(--su-hit);
  min-width: var(--su-hit);
  min-height: var(--su-hit);
  border: 1px solid var(--su-fg);
  border-radius: var(--ss-radius-xl);
  background: transparent;
  color: var(--su-fg);
  cursor: pointer;
  pointer-events: auto;
}

#btn-hud-menu .icon {
  pointer-events: none;
}

#btn-hud-music:hover,
#btn-hud-menu:hover {
  background: var(--su-n3);
}

/* A hit is a short red wash over the stage. */
#hurt-flash {
  position: absolute;
  inset: 0;
  z-index: var(--su-z-flash);
  pointer-events: none;
  opacity: 0;
  background: var(--su-hurt);
}

#hurt-flash.flash {
  animation: hurtFlashAnim var(--su-flash) ease-out;
}

@keyframes hurtFlashAnim {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

/* Toasts sit in a bottom slot, above the camera bar, so they never cover the rep count. */
#skin-unlock-toast,
#mode-toast {
  position: absolute;
  left: 50%;
  bottom: calc(max(var(--su-space-3), env(safe-area-inset-bottom)) + 5.5rem);
  transform: translateX(-50%);
  z-index: var(--su-z-toast);
  max-width: calc(100% - var(--su-space-6));
  padding: var(--su-space-2) var(--su-space-4);
  border: 1px solid var(--su-fg);
  border-radius: var(--ss-radius-xl);
  background: var(--su-scrim);
  color: var(--su-fg);
  font-size: var(--su-text-sm);
  font-weight: 700;
  text-align: center;
  pointer-events: none;
}

/* -- Overlays: end of run, house ad, menu ----------------------------------------------------- */

#overlay,
#house-ad {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--su-space-3);
  padding: max(var(--su-space-6), env(safe-area-inset-top)) var(--su-space-4) max(var(--su-space-6), env(safe-area-inset-bottom));
  overflow-y: auto;
  background: var(--su-scrim-menu);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  text-align: center;
}

#overlay {
  z-index: var(--su-z-overlay);
  /* The stage already sits inside the page's safe-area padding, so the shared rule's inset above would count the
     notch twice: on an iPhone that pushed Restart and Main menu below the fold of a set summary. */
  padding-block: var(--su-space-4);
  /* Not `justify-content: center`: when the summary is taller than the stage that would clip the title
     out of reach. Two growing spacers centre it and let a tall card start at the top. */
  justify-content: flex-start;
}

#overlay::before,
#overlay::after {
  content: '';
  flex: 1 1 0;
}

#house-ad {
  z-index: var(--su-z-ad);
}

#overlay.hidden,
#house-ad.hidden {
  display: none;
}

#overlay h2 {
  font-size: var(--su-display-sm);
}

.overlay-actions {
  display: flex;
  gap: var(--su-space-2);
  flex-wrap: wrap;
  justify-content: center;
  width: 100%;
  max-width: 22rem;
}

.overlay-actions > button {
  flex: 1 1 8rem;
}

#overlay > .menu-btn {
  width: 100%;
  max-width: 22rem;
}

/* The recording of the run, replayed as a chart above the figures. */
.recorder {
  width: 100%;
  max-width: 22rem;
  margin: 0;
  padding: var(--su-space-2);
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-floating);
  box-shadow: var(--ss-shadow-low);
}

.recorder canvas {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 1;
  border: 1px solid var(--su-line);
  border-radius: var(--ss-radius-lg);
  background: var(--su-bg);
}

.recorder figcaption {
  margin-top: var(--su-space-1);
  color: var(--su-fg-2);
  font-size: var(--su-text-xs);
  text-align: center;
}

/* Hero figures: the label above, the value in mono. */
.set-summary {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0;
  width: 100%;
  max-width: 22rem;
  padding: var(--su-space-1) var(--su-space-4);
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-floating);
  box-shadow: var(--ss-shadow-low);
  font-size: var(--su-text-sm);
}

.set-summary dt,
.set-summary dd {
  padding: var(--su-space-1) 0;
  border-bottom: 1px solid var(--sd-border);
}

.set-summary dt:nth-last-of-type(1),
.set-summary dd:nth-last-of-type(1) {
  border-bottom: 0;
}

.set-summary:empty {
  display: none;
}

.set-summary dt {
  color: var(--su-fg-2);
  font-weight: 500;
  text-align: left;
}

.set-summary dd {
  margin: 0;
  font-family: var(--su-font-mono);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.overlay-dragons-row {
  display: flex;
  gap: var(--su-space-6);
  justify-content: center;
}

.overlay-dragon {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--su-space-1);
  font-size: var(--su-text-sm);
  font-weight: 600;
  color: var(--su-fg-2);
}

.overlay-dragon canvas,
.challenge-preview-row canvas,
.skin-tile canvas.skin-tile-canvas {
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-lg);
  background: var(--sd-sidebar);
}

.challenge-preview-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--su-space-3);
  font-size: var(--su-text-sm);
  font-weight: 600;
  color: var(--su-fg-2);
}

.code-row {
  display: grid;
  grid-template-columns: 4rem 1fr;
  align-items: center;
  gap: var(--su-space-2);
  width: 100%;
  text-align: left;
}

.code-row input[type='text'] {
  width: 100%;
  font-family: var(--su-font-mono);
  letter-spacing: 0.04em;
}

#overlay-share-row.code-row {
  grid-template-columns: 1fr auto auto;
}

/* House ad: first-party card, labelled as an ad, never confusable with game chrome. */
#house-ad h2 {
  max-width: 22rem;
  font-size: var(--su-text-2xl);
}

#house-ad p {
  max-width: 22rem;
  color: var(--su-fg-2);
  font-size: var(--su-text-md);
}

.ad-chip {
  align-self: center;
  padding: var(--su-space-1) var(--su-space-3);
  border: 1px solid var(--su-fg);
  border-radius: var(--ss-radius-pill);
  color: var(--su-fg);
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

#btn-house-ad-close:disabled {
  opacity: 0.5;
  cursor: default;
}

#arcade-menu {
  position: absolute;
  inset: 0;
  z-index: var(--su-z-menu);
  display: flex;
  overflow-y: auto;
  /* Near-opaque, so the HUD and camera bar behind the menu do not ghost through the text. */
  background: var(--su-scrim-menu);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

#arcade-menu.hidden {
  display: none;
}

/* margin: auto centres a short screen and lets a tall one scroll from its top edge. A screen is a floating card. */
.menu-screen {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--su-space-3);
  width: min(100%, 24rem);
  margin: auto;
  padding: max(var(--su-space-6), env(safe-area-inset-top)) var(--su-space-4) max(var(--su-space-6), env(safe-area-inset-bottom));
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-2xl);
  background: var(--sd-floating);
  box-shadow: var(--ss-shadow-high);
  text-align: left;
}

/* Glass where the browser can blur. Where it cannot, the solid card above stays. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .menu-screen {
    background: color-mix(in srgb, var(--sd-floating) 84%, transparent);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
  }
}

/* A docked screen: it is exactly as tall as the menu (never taller), the content scrolls above the action and the
   action sits on the stage's own safe-area padding. Its bottom padding is the dock itself, so the last row of the
   content ends above the bar by construction, not by a magic number. */
.menu-screen.menu-screen--docked {
  height: 100%;
  margin: 0 auto;
  /* The stage is already inside the page's safe-area padding: no second inset on a screen that scrolls. */
  padding-top: var(--su-space-6);
  padding-bottom: var(--su-space-3);
  overflow: hidden;
}

.menu-scroll {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--su-space-3);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* A scroll box clips on both axes: the room beside the content keeps a switch's edge and a focus ring whole. */
  padding: 0 var(--su-space-1) var(--su-space-3);
  margin: 0 calc(-1 * var(--su-space-1));
}

.menu-screen--docked > .sticky-action {
  flex: 0 0 auto;
  position: static;
  box-shadow: none;
}

.menu-screen.hidden {
  display: none;
}

.menu-screen h2 {
  font-size: var(--su-display-sm);
  letter-spacing: 0.01em;
}

.menu-screen--title,
#menu-cooldown,
#menu-pause {
  text-align: center;
}

.menu-screen--title .lockup {
  justify-content: center;
}

.lockup--hero .lockup-mark {
  width: 48px;
  height: 48px;
}

.lockup--hero h2 {
  font-size: var(--su-display-sm);
}

.menu-screen--title .set-row,
.menu-screen--title .workout-mode-cluster {
  align-self: center;
}

.menu-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(4.25rem, 1fr));
  gap: var(--su-space-2);
}

/* The music switch is a corner utility of the title: an icon, its state in the label for assistive
   technology ("Music on" / "Music off"). */
.menu-music {
  position: absolute;
  top: var(--su-space-2);
  right: var(--su-space-2);
}

.menu-tiles .menu-btn {
  flex-direction: column;
  width: 100%;
  min-height: 4.75rem;
  padding: 40px var(--su-space-1) var(--su-space-2);
  font-size: var(--su-text-xs);
  font-weight: 700;
}

.menu-tiles .has-icon {
  min-width: 0;
}

.menu-tiles > .has-icon > .menu-btn,
.menu-tiles > .menu-btn {
  flex: 1;
}

.set-row {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--su-space-2);
  width: 100%;
}

/* .set-row sets display; the shared `.hidden { display: none !important }` already wins over it, and
   this pairing just keeps the row's own rule readable next to the one that hides it. */
.set-row.hidden {
  display: none;
}

.back-link,
.cell-link {
  display: inline-flex;
  align-items: center;
  gap: var(--su-space-2);
  min-height: var(--su-hit);
  color: var(--su-fg);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.group-title {
  margin-top: var(--su-space-2);
  padding-bottom: var(--su-space-1);
  border-bottom: 1px solid var(--su-line);
  font-family: var(--su-font);
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--su-fg-2);
}

.settings-store-row,
.reminder-row,
.health-row {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-2);
  width: 100%;
}

.settings-store-row {
  flex-direction: row;
  flex-wrap: wrap;
}

.settings-store-row > .menu-btn {
  flex: 1 1 8rem;
}

.reset-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--su-space-2);
  width: 100%;
}

.reset-row > .menu-btn {
  flex: 1 1 8rem;
}

.level-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--su-space-2);
  width: 100%;
}

.level-pick {
  min-height: 4rem;
  padding: 0 var(--su-space-1);
  font-size: var(--su-text-sm);
}

.level-pick.active {
  border-color: var(--su-fg);
  background: var(--su-n3);
  box-shadow: inset 0 0 0 1px var(--su-fg);
}

.skins-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: var(--su-space-2);
  width: 100%;
  max-height: 20rem;
  overflow-y: auto;
}

/* A skin is an avatar tile. The one you wear is red-edged and has an online dot; a locked one has a dashed edge. */
.skin-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--su-space-1);
  min-height: var(--su-hit);
  padding: var(--su-space-2);
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-sidebar);
  color: var(--su-fg);
  font-size: var(--su-text-sm);
  font-weight: 700;
  cursor: pointer;
  transition:
    background-color var(--su-duration-fast) var(--su-ease),
    transform var(--su-duration-fast) var(--su-ease);
}

.skin-tile:hover {
  background: var(--sd-hover);
}

.skin-tile:active {
  transform: scale(0.98);
}

.skin-tile.active {
  border-color: var(--su-accent);
  box-shadow: inset 0 0 0 1px var(--su-accent);
}

.skin-tile.active::after {
  content: '';
  position: absolute;
  top: var(--su-space-2);
  right: var(--su-space-2);
  width: 12px;
  height: 12px;
  border: 2px solid var(--sd-sidebar);
  border-radius: var(--ss-radius-pill);
  background: var(--su-clean);
}

.skin-tile.locked {
  border-style: dashed;
  opacity: 0.7;
}

.skin-tile-name {
  font-weight: 800;
}

.skin-tile-sub {
  color: var(--su-fg-2);
  font-weight: 500;
  font-size: var(--su-text-xs);
  text-align: center;
}

/* Cooldown: the next free set, with the countdown as the hero figure. */
#cooldown-countdown {
  font-family: var(--su-font-mono);
  font-size: var(--su-display-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--su-fg);
}

.pause-context {
  font-family: var(--su-font-mono);
  font-size: var(--su-text-lg);
  font-variant-numeric: tabular-nums;
  color: var(--su-fg);
}

/* -- Workout panel: the camera check (a developer surface on the web edition) -------------------- */

#workout-panel {
  flex: 1 1 28rem;
  max-width: 40rem;
  min-width: 22rem;
  max-height: 100%;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: var(--su-space-4);
  padding: var(--su-space-4);
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-2xl);
  background: var(--sd-floating);
  box-shadow: var(--ss-shadow-low);
}

#workout-panel h2 {
  margin-bottom: var(--su-space-2);
  font-family: var(--su-font);
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--su-fg-2);
}

.panel-section {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-2);
}

.panel-section {
  flex-shrink: 0;
}

.camera-preview-wrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 20rem;
  border-radius: var(--ss-radius-xl);
  overflow: hidden;
  background: var(--su-bg);
  border: 1px solid var(--su-line);
}

#camera-preview {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 20rem;
  object-fit: cover;
  background: var(--su-bg);
  transform: scaleX(-1);
}

#tracking-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  transform: scaleX(-1);
  z-index: 1;
}

.tracking-hud-row {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--su-z-hud);
  display: flex;
  align-items: center;
  gap: var(--su-space-2);
  padding: var(--su-space-2) var(--su-space-3);
  font-family: var(--su-font-mono);
  font-size: var(--su-text-xs);
  font-weight: 700;
  color: var(--su-fg-2);
  background: var(--su-scrim);
}

.tracking-dot {
  width: 10px;
  height: 10px;
  border: 2px solid var(--su-fg-2);
  border-radius: var(--ss-radius-pill);
  flex-shrink: 0;
}

.tracking-dot.active {
  background: var(--su-clean);
  border-color: var(--su-clean);
}

.depth-gauge {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-1);
}

.depth-label {
  display: flex;
  justify-content: space-between;
  font-size: var(--su-text-sm);
  font-weight: 600;
  color: var(--su-fg-2);
}

.depth-label span:last-child {
  font-family: var(--su-font-mono);
  font-variant-numeric: tabular-nums;
}

.depth-track,
.range-track {
  position: relative;
  height: 12px;
  border-radius: var(--ss-radius-pill);
  background: var(--sd-sidebar);
  border: 1px solid var(--sd-border);
  overflow: hidden;
}

.range-track {
  background: var(--su-range-track);
}

/* The depth bar: a grey to red fill. */
#depth-bar {
  height: 100%;
  width: 0%;
  background: var(--su-depth-fill);
  transition: width 0.08s linear;
}

.range-gauge {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-1);
}

/* The ideal band is the target, drawn in green. */
.range-ideal {
  position: absolute;
  left: 25%;
  width: 33%;
  top: 0;
  bottom: 0;
  background: var(--su-ideal-fill);
  border-left: 1px solid var(--su-ideal-line);
  border-right: 1px solid var(--su-ideal-line);
}

#range-marker {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 4px;
  transform: translateX(-50%);
  left: 50%;
  border-radius: var(--ss-radius-xs);
  background: var(--su-marker);
  transition: left 0.12s linear;
}

#range-marker.zone-ideal {
  background: var(--su-clean);
}

#range-marker.zone-far,
#range-marker.zone-near {
  background: var(--su-accent);
}

#range-label {
  margin: 0;
  font-size: var(--su-text-sm);
  font-weight: 600;
  color: var(--su-fg-2);
}

#range-label.zone-ideal {
  color: var(--su-clean);
}

#range-label.zone-far,
#range-label.zone-near {
  color: var(--su-fault);
}

.phase-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--su-text-sm);
  font-weight: 600;
  color: var(--su-fg-2);
}

#phase-display {
  font-family: var(--su-font-mono);
  color: var(--su-fg);
}

.cal-instructions {
  margin: 0 0 var(--su-space-1) var(--su-space-4);
  font-size: var(--su-text-sm);
  color: var(--su-fg-2);
  line-height: 1.45;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--su-space-2);
}

#workout-panel .btn-row button {
  flex: 1 1 8rem;
  font-size: var(--su-text-sm);
}

/* -- Responsive: phone and narrow windows --------------------------------------------------------- */

@media (max-width: 900px) {
  main {
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
  }

  /* One column: the stage has all of the width and up to 70 % of the screen height. */
  #stage {
    --stage-max-w: 100cqw;
    --stage-max-h: min(70dvh, calc(100dvh - 8rem), 100cqh);
  }

  #workout-panel {
    max-width: 100%;
    min-width: 0;
    width: 100%;
    flex: 1 1 auto;
  }

  #camera-preview,
  .camera-preview-wrap {
    min-height: 16rem;
  }

  /* Show the whole camera frame. The video and the overlay share one box and one fit.
     Then the overlay stays 1:1 with the face. Mirror transforms are not changed here. */
  #camera-preview,
  #tracking-canvas {
    object-fit: contain;
  }

  /* The video must not outgrow the box that the overlay fills. */
  #camera-preview {
    min-height: 0;
  }

  /* A web page keeps its title block on a wide window only: on a phone the stage is the page (the
     title menu carries the lockup), except for the autoplay spectator, whose sentence lives there. */
  html:not([data-edition='store']) header {
    display: none;
  }

  html:not([data-edition='store']) body.autoplay-mode header {
    display: flex;
  }

  .menu-hint {
    display: none;
  }
}

/* The camera card (live preview, gauges, calibration) is for a camera that is running. With the camera off it is an
   empty black box that took the lower half of a phone, so it is not shown: on a phone width and in a store app the
   stage has the screen. A running camera is #camera-btn.active (game.js setCameraStatus); the web page on a wide window keeps its
   side panel (a developer surface) in both states. */
@media (max-width: 900px) {
  body:not(:has(#camera-btn.active)) #workout-panel {
    display: none;
  }

  /* A camera running: the card is a strip under the stage, never half the screen. The stage keeps its full width
     (it is width-bound on a phone) and only gives up height on a short screen, to leave the strip 12 rem. */
  body:has(#camera-btn.active) #stage {
    --stage-max-h: calc(100dvh - 13rem);
  }

  body:has(#camera-btn.active) #workout-panel {
    min-height: 0;
    flex: 1 1 0;
  }

  body:has(#camera-btn.active) .camera-preview-wrap {
    min-height: 7rem;
  }
}

html[data-edition='store'] body:not(:has(#camera-btn.active)) #workout-panel {
  display: none;
}

/* No card beside it: the stage is the app, as large as the window allows at 3:4 (the 28 rem it left for the card
   is its own again). On a phone it spans the width and is as tall as the screen less the safe areas. */
html[data-edition='store'] body:not(:has(#camera-btn.active)) #stage {
  --stage-max-w: 100cqw;
  max-width: 100%;
  /* Do not grow past the 3:4 the canvas is drawn for: width follows the height. */
  flex: 0 1 auto;
}

@media (max-width: 900px) {
  html[data-edition='store'] body:not(:has(#camera-btn.active)) #stage {
    --stage-max-h: calc(100dvh - max(var(--su-space-2), env(safe-area-inset-top)) - max(var(--su-space-2), env(safe-area-inset-bottom)) - var(--su-space-2));
  }
}

/* A tall phone (an iPhone is about 1 : 2.2): a 3:4 stage on a screen that tall left a black band above and below it.
   The HUD (rep count, status pills, coach line) takes the band above, the camera bar the band below, and the
   playfield, still the engine's 480 x 640 canvas at 3:4, sits between them with nothing over it. So the depth
   ruler and the rails never run under the score. It needs real slack (1 : 2 or taller): a phone or a tablet that
   is shorter keeps the HUD over the stage, where a 3:4 box already fills the screen. Camera running and autoplay
   keep the layout above: the card strip and the autoplay panel need the height. */
@media (max-width: 900px) and (max-aspect-ratio: 1/2) {
  html[data-edition='store'] body:not(:has(#camera-btn.active)):not(.autoplay-mode) {
    --su-avail-h: calc(100dvh - max(var(--su-space-2), env(safe-area-inset-top)) - max(var(--su-space-2), env(safe-area-inset-bottom)) - 2px);
    --su-hud-zone: 9.5rem;
    --su-bar-zone: 7rem;
  }

  html[data-edition='store'] body:not(:has(#camera-btn.active)):not(.autoplay-mode) #stage {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    width: 100%;
    max-width: 100%;
    height: auto;
    max-height: none;
    aspect-ratio: auto;
  }

  html[data-edition='store'] body:not(:has(#camera-btn.active)):not(.autoplay-mode) #playfield {
    position: relative;
    inset: auto;
    flex: 0 0 auto;
    align-self: center;
    aspect-ratio: 3 / 4;
    width: min(100%, calc((var(--su-avail-h) - var(--su-hud-zone) - var(--su-bar-zone)) * 3 / 4));
  }

  html[data-edition='store'] body:not(:has(#camera-btn.active)):not(.autoplay-mode) #hud {
    --su-ruler-col: 0px;
    position: relative;
    inset: auto;
    /* The band above the playfield is the HUD's, whatever height the screen has to spare: its rows sit in the middle. */
    flex: 1 1 var(--su-hud-zone);
    min-height: var(--su-hud-zone);
    align-content: center;
  }

  html[data-edition='store'] body:not(:has(#camera-btn.active)):not(.autoplay-mode) #hud::before {
    display: none;
  }

  html[data-edition='store'] body:not(:has(#camera-btn.active)):not(.autoplay-mode) #camera-bar {
    position: static;
    flex: 0 0 auto;
    margin: var(--su-space-2) var(--su-space-3) var(--su-space-3);
  }

  html[data-edition='store'] body:not(:has(#camera-btn.active)):not(.autoplay-mode) :is(#skin-unlock-toast, #mode-toast) {
    bottom: var(--su-space-3);
  }
}

/* A narrow stage: the title screen keeps to one screenful, so the explainer line that the camera
   setup card repeats gives way. */
@media (max-width: 480px) {
  #menu-mode-tag {
    display: none;
  }

  .menu-screen--title {
    gap: var(--su-space-2);
    padding-top: var(--su-space-4);
    padding-bottom: var(--su-space-4);
  }
}

/* A device with no keyboard has no Space or Esc to teach. */
@media (hover: none) {
  .menu-hint {
    display: none;
  }
}

/* -- In-game autoplay (?autoplay=1) -------------------------------------------------------------
   #workout-panel and #camera-bar each carry their own ID-selector `display` rule above, and
   neither is ever a `.hidden` surface (it is a layout region that autoplay removes, not a panel the
   game toggles), so hiding them for autoplay is keyed off one body class instead:
   `body.autoplay-mode #stage #camera-bar` / `body.autoplay-mode aside#workout-panel` (descendant
   + element+id beats a bare id). #header-blurb is rewritten at module
   load (not hidden) so the spectator sentence stays visible. Keying every autoplay-only layout
   change off the one body class set at game.js module load also lets #stage reclaim the aside's
   width instead of just hiding the aside inside space still reserved for it. */
body.autoplay-mode #stage {
  --stage-max-w: 100cqw;
  max-width: 100%;
}

body.autoplay-mode #stage #camera-bar,
body.autoplay-mode aside#workout-panel,
body.autoplay-mode .workout-mode-cluster {
  display: none;
}

/* -- In-game autoplay (?autoplay=1) overlay ---------------------------------------------------
   Anchored to the bottom of #stage (not the top, where #hud already lives) so it never competes
   with the score/shields/level/speed pills for space and never sits over the middle of the
   playfield the dragon actually flies through. */
#autoplay-panel {
  position: absolute;
  left: 50%;
  bottom: max(var(--su-space-3), env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: var(--su-z-autoplay);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--su-space-2);
  max-width: 94%;
  pointer-events: none;
}

.autoplay-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--su-space-2);
}

/* Source is never colour-alone: the text itself always reads JEV or CODE FALLBACK (see
   AUTOPLAY_SOURCE_LABEL in game.js); the outline is a secondary reinforcement only. */
.stat-pill.autoplay-source.jev {
  border-color: var(--su-clean);
}

.stat-pill.autoplay-source.fallback {
  border-style: dashed;
  border-color: var(--su-fg-2);
}

.stat-pill.autoplay-cap {
  border-color: var(--su-fault);
}

#autoplay-summary {
  pointer-events: auto;
  background: var(--su-scrim);
  border: 1px solid var(--su-line);
  border-radius: var(--ss-radius-xl);
  padding: var(--su-space-3) var(--su-space-4);
  max-width: 22rem;
  text-align: center;
}

#autoplay-summary p {
  font-size: var(--su-text-sm);
  font-weight: 600;
  margin-bottom: var(--su-space-2);
}

#autoplay-summary a {
  font-weight: 800;
  text-decoration: underline;
}

/* -- App shell: rail, sidebar, presence, panels, trophies, boons, toasts, the Squad Flight lobby ------
   A chat-app shell on the sigmashake brand. On a wide window the 72 px rail and the 240 px sidebar are fixed to
   the left of the page. On a phone in portrait the rail is a tab bar at the bottom of the screen, and the
   sidebar is a sheet above it. Both are outside <main>, and they are fixed, so they never change the size of
   the stage. The tab bar shows while the main menu is open. During a run the stage has the whole screen.
   Colors: the --sd-* shell tokens, the --su-* roles. Red is "you", the selected item and the primary action.
   Text on red is black. A state is always a shape or a word too. New controls take the focus ring above.
   The Squad Flight lobby is at the end of this section. Its base rules are in the Squad Flight section below. */

:root {
  --su-rail-edge: calc(var(--sd-rail-w) + env(safe-area-inset-left));
}

/* A hidden attribute must beat the display of a class: these rules have a higher specificity than the class rules. */
.su-panel[hidden],
.su-draft[hidden],
.su-banner[hidden],
.su-end-line[hidden],
#toast-stack[hidden] {
  display: none;
}

/* The rail. Its buttons are data-nav="play|squad|trophies|run|settings". The one that is on is aria-current. */
.su-rail {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: var(--su-z-rail);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--su-space-2);
  width: var(--su-rail-edge);
  padding: max(var(--su-space-3), env(safe-area-inset-top)) 0 max(var(--su-space-3), env(safe-area-inset-bottom)) env(safe-area-inset-left);
  border-right: 1px solid var(--sd-border);
  background: var(--sd-rail);
}

.su-rail-mark {
  display: block;
  width: 32px;
  height: 32px;
  margin-bottom: var(--su-space-1);
  border-radius: var(--ss-radius-pill);
}

.su-rail-sep {
  width: var(--su-space-8);
  height: 2px;
  border-radius: var(--ss-radius-pill);
  background: var(--sd-border);
}

.su-rail-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--su-space-1);
  width: 56px;
  min-height: 56px;
  padding: var(--su-space-1) 0;
  border: 0;
  border-radius: var(--ss-radius-xl);
  background: transparent;
  color: var(--sd-muted);
  font-family: var(--su-font);
  font-size: var(--su-text-xs);
  font-weight: 700;
  line-height: 1.1;
  cursor: pointer;
  transition:
    background-color var(--su-duration-fast) var(--su-ease),
    color var(--su-duration-fast) var(--su-ease),
    transform var(--su-duration-fast) var(--su-ease);
}

.su-rail-btn:hover {
  background: var(--sd-hover);
  color: var(--su-fg);
}

.su-rail-btn:active {
  transform: scale(0.98);
}

/* The one that is on: a red tile with a black glyph, and a white pill on the edge of the rail. */
.su-rail-btn[aria-current='page'] {
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
}

.su-rail-btn[aria-current='page']::before {
  content: '';
  position: absolute;
  top: 50%;
  left: calc(-1 * var(--su-space-2));
  width: 4px;
  height: 24px;
  margin-top: -12px;
  border-radius: var(--ss-radius-pill);
  background: var(--su-fg);
}

/* An unread dot. */
.su-rail-btn[data-unread='true']::after {
  content: '';
  position: absolute;
  top: var(--su-space-1);
  right: var(--su-space-2);
  width: 12px;
  height: 12px;
  border: 2px solid var(--sd-rail);
  border-radius: var(--ss-radius-pill);
  background: var(--su-accent);
}

.su-rail-more {
  margin-top: auto;
}

/* The sidebar: a header, channel rows, and the user bar. Under 1440 px it is a drawer: the .su-rail-more button
   opens it (aria-expanded, data-open). It sits under the rail, so the rail stays on top while it slides. */
.su-sidebar {
  position: fixed;
  top: 0;
  bottom: 0;
  left: var(--su-rail-edge);
  z-index: var(--su-z-menu);
  display: flex;
  flex-direction: column;
  width: var(--sd-sidebar-w);
  border-right: 1px solid var(--sd-border);
  background: var(--sd-sidebar);
  color: var(--sd-text);
  box-shadow: var(--ss-shadow-high);
  transform: translateX(-100%);
  visibility: hidden;
  transition:
    transform var(--su-duration-base) var(--su-ease),
    visibility 0s linear var(--su-duration-base);
}

.su-sidebar[data-open='true'] {
  transform: none;
  visibility: visible;
  transition:
    transform var(--su-duration-base) var(--su-ease),
    visibility 0s linear;
}

.su-sidebar-head {
  display: flex;
  align-items: center;
  min-height: var(--sd-header-h);
  padding: 0 var(--su-space-4);
  border-bottom: 1px solid var(--sd-border);
}

.su-sidebar-head h2 {
  font-family: var(--su-font);
  font-size: var(--su-text-md);
  font-weight: 800;
}

.su-sidebar-body {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--su-space-2);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.su-section {
  padding: var(--su-space-3) var(--su-space-2) var(--su-space-1);
  color: var(--sd-muted);
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.su-channels {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--su-space-1);
}

/* A channel row: a hash, a name, and an unread dot when data-unread is true. The open one is aria-current. */
.su-channel {
  display: flex;
  align-items: center;
  gap: var(--su-space-2);
  width: 100%;
  min-height: var(--sd-row-h);
  padding: 0 var(--su-space-2);
  border: 0;
  border-radius: var(--ss-radius-md);
  background: transparent;
  color: var(--sd-muted);
  font-family: var(--su-font);
  font-size: var(--su-text-md);
  font-weight: 600;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--su-duration-fast) var(--su-ease),
    color var(--su-duration-fast) var(--su-ease);
}

.su-channel::before {
  content: '#';
  flex: none;
  width: 16px;
  font-family: var(--su-font-mono);
  font-weight: 700;
  text-align: center;
}

.su-channel:hover {
  background: var(--sd-hover);
  color: var(--sd-text);
}

.su-channel:active {
  background: var(--sd-active);
}

.su-channel[aria-current='true'] {
  background: var(--sd-selected);
  color: var(--sd-header);
  font-weight: 800;
}

.su-channel[data-unread='true'] {
  color: var(--sd-header);
  font-weight: 800;
}

.su-channel[data-unread='true']::after {
  content: '';
  flex: none;
  width: 8px;
  height: 8px;
  margin-left: auto;
  border-radius: var(--ss-radius-pill);
  background: var(--su-accent);
}

.su-userbar {
  display: flex;
  align-items: center;
  gap: var(--su-space-3);
  min-height: var(--ss-shell-userpanel-h);
  padding: var(--su-space-2) var(--su-space-3);
  border-top: 1px solid var(--sd-border);
  background: var(--sd-rail);
}

/* An avatar tile: 40 px, round, with a status dot on its corner. A seat avatar sets --seat-color. */
.su-avatar {
  position: relative;
  flex: none;
  width: var(--sd-avatar);
  height: var(--sd-avatar);
  border-radius: var(--ss-radius-pill);
  background: var(--seat-color, var(--sd-selected));
}

.su-avatar img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--ss-radius-pill);
}

.su-avatar .su-dot {
  position: absolute;
  right: -4px;
  bottom: -4px;
  box-shadow: 0 0 0 2px var(--dot-ground, var(--sd-rail));
}

.su-name {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.su-name-line {
  display: flex;
  align-items: center;
  gap: var(--su-space-2);
  color: var(--sd-header);
  font-size: var(--su-text-sm);
  font-weight: 800;
}

.su-name-sub {
  color: var(--sd-muted);
  font-size: var(--su-text-xs);
  font-weight: 600;
}

/* A role chip: the word says the role, the fill repeats it. You is red, a host is white, the rest are grey. */
.su-role {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--su-space-2);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-sm);
  background: var(--sd-selected);
  color: var(--sd-text);
  font-size: var(--su-text-xs);
  font-weight: 800;
  line-height: 1.5;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.su-role[data-role='you'] {
  border-color: var(--su-accent);
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
}

.su-role[data-role='host'] {
  border-color: var(--sd-header);
  background: var(--sd-header);
  color: var(--sd-on-accent);
}

/* Presence: online is a filled dot, idle is half filled, offline is a ring. The color repeats the shape. */
.su-dot,
.su-presence::before,
.su-banner::before {
  content: '';
  display: inline-block;
  flex: none;
  width: 12px;
  height: 12px;
  border: 2px solid var(--dot-ring, var(--su-fg-2));
  border-radius: var(--ss-radius-pill);
  background: var(--dot-fill, transparent);
}

[data-status='online'] {
  --dot-ring: var(--su-clean);
  --dot-fill: var(--su-clean);
}

[data-status='idle'] {
  --dot-ring: var(--su-fg-2);
  --dot-fill: linear-gradient(90deg, var(--su-fg-2) 50%, transparent 50%);
}

[data-status='offline'] {
  --dot-ring: var(--su-fg-2);
  --dot-fill: transparent;
}

.su-presence {
  display: inline-flex;
  align-items: center;
  gap: var(--su-space-2);
  padding: var(--su-space-1) var(--su-space-3);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-pill);
  background: var(--sd-floating);
  color: var(--sd-text);
  font-size: var(--su-text-sm);
  font-weight: 700;
  line-height: 1.3;
}

/* A panel (Trophies, Run) is a floating card over the stage. It has a head, and cards in it. */
.su-panel {
  position: absolute;
  inset: var(--su-space-3);
  z-index: var(--su-z-menu);
  display: flex;
  flex-direction: column;
  gap: var(--su-space-3);
  padding: var(--su-space-4);
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-2xl);
  background: var(--sd-floating);
  box-shadow: var(--ss-shadow-high);
  text-align: left;
}

.su-panel-head {
  display: flex;
  align-items: center;
  gap: var(--su-space-3);
  min-height: var(--sd-header-h);
  padding-bottom: var(--su-space-2);
  border-bottom: 1px solid var(--sd-border);
}

.su-panel-head h2 {
  flex: 1 1 auto;
  font-size: var(--su-text-xl);
}

.su-card {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-2);
  padding: var(--su-space-3);
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-sidebar);
}

.su-card .group-title {
  margin-top: 0;
}

/* A bar for progress: the number beside it says the same. */
progress.su-progress {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 12px;
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-pill);
  background: var(--sd-sidebar);
  overflow: hidden;
}

progress.su-progress::-webkit-progress-bar {
  background: var(--sd-sidebar);
}

progress.su-progress::-webkit-progress-value {
  border-radius: var(--ss-radius-pill);
  background: var(--su-accent);
  transition: width var(--su-duration-base) var(--su-ease);
}

progress.su-progress::-moz-progress-bar {
  border-radius: var(--ss-radius-pill);
  background: var(--su-accent);
}

.su-progress-text {
  color: var(--sd-muted);
  font-family: var(--su-font-mono);
  font-size: var(--su-text-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.su-ember {
  display: flex;
  align-items: center;
  gap: var(--su-space-3);
  padding: var(--su-space-3) var(--su-space-4);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-sidebar);
}

.su-ember .icon {
  width: 32px;
  height: 32px;
  color: var(--su-accent);
}

.su-ember-count {
  color: var(--sd-header);
  font-family: var(--su-font-mono);
  font-size: var(--su-display-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.su-ember-label {
  color: var(--sd-muted);
  font-size: var(--su-text-sm);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.su-seed {
  align-self: flex-start;
  padding: var(--su-space-1) var(--su-space-3);
  border: 1px dashed var(--sd-border-strong);
  border-radius: var(--ss-radius-md);
  background: var(--sd-bg);
  color: var(--su-fg);
  font-size: var(--su-text-md);
  font-weight: 700;
  letter-spacing: 0.08em;
  user-select: text;
  -webkit-user-select: text;
}

/* A list of rows: unlocks, the boons of this run. */
.su-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--su-space-1);
}

.su-list > li {
  display: flex;
  align-items: center;
  gap: var(--su-space-2);
  min-height: var(--sd-row-h);
  padding: 0 var(--su-space-2);
  border-radius: var(--ss-radius-md);
  color: var(--sd-text);
  font-size: var(--su-text-sm);
  font-weight: 600;
}

.su-list > li::before {
  content: '#';
  flex: none;
  width: 16px;
  color: var(--sd-muted);
  font-family: var(--su-font-mono);
  font-weight: 700;
  text-align: center;
}

.su-list > li:hover {
  background: var(--sd-hover);
}

.su-list:empty::after {
  content: 'None yet.';
  padding: 0 var(--su-space-2);
  color: var(--sd-muted);
  font-size: var(--su-text-sm);
}

/* Trophies. A card is li.su-trophy[data-state="locked|unlocked|secret"] with a .su-tier[data-tier="1|2|3"], a
   .su-trophy-name and a .su-trophy-desc. The state is a word (the label under the text), an edge style, and a
   bar. The tier is a shape and a label, not a color: a circle, a diamond, a hexagon. */
.su-trophies {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
  gap: var(--su-space-2);
}

.su-trophy {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-1);
  padding: var(--su-space-3);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-sidebar);
  color: var(--sd-text);
  transition: background-color var(--su-duration-fast) var(--su-ease);
}

.su-trophy:hover {
  background: var(--sd-hover);
}

.su-trophy::after {
  color: var(--sd-muted);
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.su-trophy[data-state='unlocked'] {
  border-left: 4px solid var(--su-accent);
  background: var(--sd-floating);
}

.su-trophy[data-state='unlocked']::after {
  content: 'Unlocked';
  color: var(--su-fg);
}

.su-trophy[data-state='locked'] {
  border-style: dashed;
}

.su-trophy[data-state='locked']::after {
  content: 'Locked';
}

.su-trophy[data-state='secret'] {
  border-style: dotted;
}

.su-trophy[data-state='secret']::after {
  content: 'Secret';
}

.su-trophy-name {
  color: var(--sd-header);
  font-size: var(--su-text-sm);
  font-weight: 800;
}

.su-trophy-desc {
  color: var(--sd-muted);
  font-size: var(--su-text-xs);
  font-weight: 500;
  line-height: 1.35;
}

.su-tier {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: var(--su-space-1);
  padding: 0 var(--su-space-2);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-pill);
  color: var(--sd-text);
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.su-tier::before {
  content: '';
  width: 12px;
  height: 12px;
  background: var(--su-fg);
  border-radius: var(--ss-radius-pill);
}

.su-tier[data-tier='2']::before {
  border-radius: 0;
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}

.su-tier[data-tier='3']::before {
  border-radius: 0;
  clip-path: polygon(25% 3%, 75% 3%, 100% 50%, 75% 97%, 25% 97%, 0 50%);
}

/* The boon draft: a dialog over the stage with a few cards and a Skip. A card is button.su-boon-card
   [data-rarity="common|rare|epic"] with a .su-rarity label. The rarity is the border style and the word:
   common is a thin line, rare is a double line, epic is a thick dotted line. */
.su-draft {
  position: absolute;
  inset: 0;
  z-index: var(--su-z-draft);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--su-space-4);
  background: var(--su-scrim-menu);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.su-draft-card {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-3);
  width: min(100%, 24rem);
  max-height: 100%;
  padding: var(--su-space-4);
  overflow-y: auto;
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-2xl);
  background: var(--sd-floating);
  box-shadow: var(--ss-shadow-high);
}

.su-boons {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--su-space-3);
}

.su-boon-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--su-space-1);
  width: 100%;
  min-height: var(--su-hit);
  padding: var(--su-space-3) var(--su-space-4);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-sidebar);
  color: var(--su-fg);
  font-family: var(--su-font);
  font-size: var(--su-text-md);
  font-weight: 800;
  text-align: left;
  cursor: pointer;
  /* Fill backwards only: a fill that stays after the end would pin the transform and block the pressed state. */
  animation: suPopIn var(--su-duration-base) var(--su-ease) backwards;
  transition:
    background-color var(--su-duration-fast) var(--su-ease),
    transform var(--su-duration-fast) var(--su-ease);
}

.su-boon-card:hover {
  background: var(--sd-hover);
}

.su-boon-card:active {
  transform: scale(0.98);
}

.su-boon-card[data-rarity='rare'] {
  border: 4px double var(--su-fg);
}

.su-boon-card[data-rarity='epic'] {
  border: 4px dotted var(--su-fg);
}

.su-boon-desc {
  color: var(--sd-text);
  font-size: var(--su-text-sm);
  font-weight: 500;
}

.su-rarity {
  color: var(--sd-muted);
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

@keyframes suPopIn {
  0% {
    opacity: 0;
    transform: translateY(8px);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

/* The daily button: the same shape as a menu button, in the red of the primary action. Its text is black. */
.su-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--su-space-2);
  min-height: var(--su-hit);
  min-width: var(--su-hit);
  padding: 0 var(--su-space-4);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-selected);
  color: var(--su-fg);
  font-family: var(--su-font);
  font-size: var(--su-text-md);
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--su-duration-fast) var(--su-ease),
    box-shadow var(--su-duration-fast) var(--su-ease),
    transform var(--su-duration-fast) var(--su-ease);
}

.su-btn:active {
  transform: scale(0.98);
}

.su-btn:focus-visible {
  outline: 2px solid var(--su-fg);
  outline-offset: 3px;
}

#btn-menu-daily {
  border-color: var(--su-accent);
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
  font-weight: 800;
}

#btn-menu-daily:hover {
  background: var(--su-accent-fill);
  box-shadow: inset 0 0 0 2px var(--su-fg);
}

/* The cosmetics list: one row per item. Worn is a filled dot and a thick white edge. Not worn is a hollow dot and a
   thin grey edge. The state is a shape and an edge, never a color alone. */
#equip-list {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.su-equip-row {
  display: flex;
}

.su-equip-btn {
  display: flex;
  align-items: center;
  gap: var(--su-space-3);
  width: 100%;
  min-height: var(--su-hit);
  padding: var(--su-space-2) var(--su-space-4);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-sidebar);
  color: var(--su-fg);
  font-family: var(--su-font);
  font-size: var(--su-text-md);
  font-weight: 700;
  text-align: left;
  cursor: pointer;
  transition:
    background-color var(--su-duration-fast) var(--su-ease),
    transform var(--su-duration-fast) var(--su-ease);
}

.su-equip-btn::before {
  content: '';
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  border: 2px solid var(--su-fg);
  border-radius: var(--ss-radius-pill);
  background: transparent;
}

.su-equip-btn:hover {
  background: var(--sd-hover);
}

.su-equip-btn:active {
  transform: scale(0.98);
}

.su-equip-btn:focus-visible {
  outline: 2px solid var(--su-fg);
  outline-offset: 3px;
}

/* The worn edge is an inset ring, not a wider border. The border box and the content do not move when an item is equipped. */
.su-equip-btn[aria-pressed='true'] {
  box-shadow: inset 0 0 0 2px var(--su-fg);
  border-color: var(--su-fg);
  background: var(--sd-selected);
}

.su-equip-btn[aria-pressed='true']::before {
  background: var(--su-fg);
}

/* A curse card: a dashed edge and a warning mark, so it is not mistaken for a boon. White and red only. */
.su-boon-card[data-kind='curse'] {
  border: 4px dashed var(--su-fg);
}

.su-curse-note {
  display: flex;
  align-items: center;
  gap: var(--su-space-2);
  color: var(--su-fg);
  font-size: var(--su-text-sm);
  font-weight: 800;
}

/* The warning mark is a red triangle with a black "!" in it. */
.su-curse-note::before {
  content: '!';
  display: inline-flex;
  flex: 0 0 auto;
  align-items: flex-end;
  justify-content: center;
  width: 20px;
  height: 18px;
  background: var(--su-accent-fill);
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  color: var(--su-on-accent);
  font-size: var(--su-text-xs);
  font-weight: 900;
  line-height: 1;
}

/* The count-in after a pause. One big digit; each digit scales down and fades in for one second.
   The animation plays once, when the element is created or shown. A new textContent does not restart it.
   The script must replace the element for each digit.
   The rule is absolute with inset 0, so the parent must be positioned. Mount it inside #stage. */
.su-resume-grace {
  position: absolute;
  inset: 0;
  z-index: var(--su-z-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--su-fg);
  font-family: var(--su-font-display);
  font-size: var(--su-display-lg);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  text-shadow: 0 2px 12px var(--su-bg);
  pointer-events: none;
  animation: suResumeDigit var(--su-duration-count) var(--su-ease) both;
}

.su-resume-grace[hidden] {
  display: none;
}

@keyframes suResumeDigit {
  0% {
    opacity: 0;
    transform: scale(1.6);
  }
  25% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0.2;
    transform: scale(0.85);
  }
}

/* Achievement toasts slide in from the right and stack upward, above the camera bar. */
#toast-stack {
  position: absolute;
  left: var(--su-space-3);
  right: var(--su-space-3);
  bottom: calc(max(var(--su-space-3), env(safe-area-inset-bottom)) + 5.5rem);
  z-index: var(--su-z-toast);
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: var(--su-space-2);
  pointer-events: none;
}

.su-toast {
  display: flex;
  align-items: center;
  gap: var(--su-space-3);
  max-width: 100%;
  padding: var(--su-space-2) var(--su-space-4);
  border: 1px solid var(--sd-border-strong);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-floating);
  color: var(--su-fg);
  font-size: var(--su-text-sm);
  font-weight: 700;
  box-shadow: var(--ss-shadow-high);
  animation: suToastIn var(--su-duration-base) var(--su-ease) both;
}

@keyframes suToastIn {
  0% {
    opacity: 0;
    transform: translateX(24px);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

/* The end screen lines for embers and unlocks. */
.su-end-line {
  width: 100%;
  max-width: 22rem;
  padding: var(--su-space-2) var(--su-space-4);
  border: 1px solid var(--sd-border);
  border-left: 4px solid var(--su-accent);
  border-radius: var(--ss-radius-md);
  background: var(--sd-floating);
  color: var(--sd-text);
  font-size: var(--su-text-sm);
  font-weight: 700;
  text-align: left;
}

/* The biome effects canvas (made by biome-fx.js): over the painted plates, under the HUD, and never a target. */
.biome-fx {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Glass where the browser can blur. Where it cannot, the translucent fills above stay. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  #camera-bar,
  .su-toast {
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
  }
}

/* A wide window: the rail is a column at the left, and the stage keeps what is left of the width. */
@media (min-width: 901px) {
  body:not(.autoplay-mode):has(> .su-rail) {
    padding-left: calc(var(--su-rail-edge) + var(--su-space-2));
  }

  body.autoplay-mode .su-rail,
  body.autoplay-mode .su-sidebar {
    display: none;
  }
}

/* A big window: the sidebar stays open beside the rail. */
@media (min-width: 1440px) {
  body:not(.autoplay-mode):has(> .su-rail) {
    padding-left: calc(var(--su-rail-edge) + var(--sd-sidebar-w) + var(--su-space-2));
  }

  .su-sidebar {
    z-index: var(--su-z-rail);
    transform: none;
    visibility: visible;
    box-shadow: none;
  }

  .su-rail-more {
    display: none;
  }
}

/* A touch screen: rows have the 44 px target. */
@media (pointer: coarse) {
  .su-channel,
  .su-list > li {
    min-height: var(--su-hit);
  }
}

/* A phone: the rail and the sidebar show while the main menu is open, and not during a run. */
@media (max-width: 900px) {
  body:not(:has(#arcade-menu:not(.hidden))) .su-rail,
  body:not(:has(#arcade-menu:not(.hidden))) .su-sidebar {
    display: none;
  }
}

/* A phone on its side keeps the 72 px rail at the left. The screen is short, so the rail scrolls and drops its mark. */
@media (max-width: 900px) and (orientation: landscape) {
  /* The rail shows only while the main menu is open. Then the page keeps clear of it, so full-width content is not under it. */
  body:has(> .su-rail):has(#arcade-menu:not(.hidden)) {
    padding-left: calc(var(--su-rail-edge) + var(--su-space-2));
  }

  .su-rail {
    gap: var(--su-space-1);
    overflow-y: auto;
  }

  .su-rail-mark,
  .su-rail-sep {
    display: none;
  }
}

/* A phone in portrait: the rail is a tab bar and the sidebar is a sheet above it. */
@media (max-width: 900px) and (orientation: portrait) {
  .su-rail {
    top: auto;
    right: 0;
    flex-direction: row;
    align-items: stretch;
    justify-content: space-around;
    gap: 0;
    width: auto;
    height: calc(var(--sd-tab-h) + env(safe-area-inset-bottom));
    padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
    border-right: 0;
    border-top: 1px solid var(--sd-border);
  }

  .su-rail-mark,
  .su-rail-sep {
    display: none;
  }

  .su-rail-btn {
    flex: 1 1 0;
    width: auto;
    min-height: var(--sd-tab-h);
    border-radius: 0;
  }

  .su-rail-btn[aria-current='page'] {
    background: var(--sd-selected);
    color: var(--su-fg);
  }

  .su-rail-btn[aria-current='page']::before {
    top: 0;
    left: 50%;
    width: 24px;
    height: 4px;
    margin: 0 0 0 -12px;
  }

  .su-rail-more {
    margin-top: 0;
  }

  .su-sidebar {
    top: auto;
    right: 0;
    bottom: calc(var(--sd-tab-h) + env(safe-area-inset-bottom));
    left: 0;
    width: auto;
    max-height: 70dvh;
    border-right: 0;
    border-top: 1px solid var(--sd-border-strong);
    border-top-left-radius: var(--ss-radius-2xl);
    border-top-right-radius: var(--ss-radius-2xl);
    transform: translateY(110%);
  }

  .su-sidebar[data-open='true'] {
    transform: none;
  }

  /* The tab bar covers the foot of the stage: a menu card ends above it. */
  body:has(> .su-rail) .menu-screen {
    padding-bottom: calc(var(--sd-tab-h) + env(safe-area-inset-bottom) + var(--su-space-3));
  }
}

/* The tall phone puts the camera bar under the playfield: the toasts go to the foot, like the mode toast. */
@media (max-width: 900px) and (max-aspect-ratio: 1/2) {
  html[data-edition='store'] body:not(:has(#camera-btn.active)):not(.autoplay-mode) #toast-stack {
    bottom: var(--su-space-3);
  }
}

/* Squad Flight lobby, in the same shell. These rules add to the base rules of the Squad Flight section, and they
   have a higher specificity, so they win. The ids and classes that live.js reads are not changed. */
body.live-page header.live-header {
  align-items: center;
  gap: var(--su-space-3);
  width: min(100%, 1000px);
  min-height: var(--sd-header-h);
  padding: 0 var(--su-space-3);
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-xl);
  background: var(--sd-floating);
}

body.live-page header.live-header .back-link {
  align-self: center;
}

body.live-page header.live-header .su-presence {
  margin-left: auto;
}

body.live-page .live-card,
body.live-page #live-join-panel,
body.live-page .live-share {
  border: 1px solid var(--sd-border);
  border-radius: var(--ss-radius-2xl);
  background: var(--sd-floating);
  box-shadow: var(--ss-shadow-low);
}

body.live-page #live-join-panel,
body.live-page .live-share {
  padding: var(--su-space-4);
}

body.live-page #live-join-panel input {
  background: var(--sd-sidebar);
}

body.live-page #live-standings li {
  background: var(--sd-selected);
}

body.live-page .live-panel {
  background: var(--sd-sidebar);
}

/* The copy-code chip: the match code as a dashed chip above the Copy and Share buttons. */
body.live-page #live-match-code {
  padding: var(--su-space-2) var(--su-space-4);
  border: 1px dashed var(--sd-border-strong);
  border-radius: var(--ss-radius-md);
  background: var(--sd-sidebar);
  text-align: center;
  overflow-wrap: anywhere;
}

#live-invite {
  width: 100%;
  font-size: var(--su-text-sm);
}

/* The reconnect banner. The dot is a pseudo-element, so live.js can set the text of the banner. */
.su-banner {
  display: flex;
  align-items: center;
  gap: var(--su-space-3);
  width: min(100%, 1000px);
  padding: var(--su-space-2) var(--su-space-4);
  border: 1px solid var(--su-fault);
  border-radius: var(--ss-radius-xl);
  background: var(--su-fault-tint);
  color: var(--su-fg);
  font-size: var(--su-text-sm);
  font-weight: 700;
}

.su-banner::before {
  --dot-ring: var(--su-fault);
  --dot-fill: linear-gradient(90deg, var(--su-fault) 50%, transparent 50%);
}

/* -- Reduced motion: no pulses, flashes, slides or transitions ---------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }

  #hurt-flash.flash,
  .stat-pill.shields.pulse-hit,
  .stat-pill.shields.pulse-recharge,
  .stat-pill.posture-fault,
  .su-toast,
  .su-boon-card,
  .su-resume-grace {
    animation: none;
  }

  .menu-btn:active,
  #restart:active,
  #btn-overlay-menu:active,
  #workout-panel button:active,
  #camera-btn:active,
  .set-btn:active,
  .level-pick:active,
  .skin-tile:active,
  .su-rail-btn:active,
  .su-boon-card:active {
    transform: none;
  }
}

/* -- Mirror Flight: setup card, mode toast ------------------------------------------------------ */

.mirror-steps {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: var(--su-space-2);
  padding-left: var(--su-space-6);
  text-align: left;
  font-size: var(--su-text-md);
  font-weight: 500;
  line-height: 1.4;
  color: var(--su-fg);
}

.placement-diagram {
  display: block;
  width: 100%;
  height: auto;
  max-height: 7rem;
  color: var(--su-fg);
}

/* The "cannot move your device" line on the title, where Practice is chosen: stays out of the way
   (the shared .hidden hides it) on a phone, where it is empty. */
#title-placement-note {
  line-height: 1.35;
}


/* -- Squad Flight (live.html): lobby, room, results ----------------------------------------------
   One page, one grid, laid out per width, so the standings and the whole squad are on screen WHILE you fly
   (the canvas holds the pointer, so nobody can scroll mid-dive). Everything is scoped to body.live-page: the
   solo game's rules above do not see it. Colours are the shared tokens only, with one exception that is
   not in this file: the 20 seat colours the dummies are tinted with come from live-figure.js (the operator's
   game-art exception to the three-colour brand), measured against the brand black. Red is "you" and the one
   primary action: your panel, your outline, your chip, Start match / Rematch, the depth ticks on the rails. */

body.live-page {
  height: auto;
  min-height: 100dvh;
  gap: var(--su-space-3);
  padding:
    max(var(--su-space-4), env(safe-area-inset-top))
    max(var(--su-space-4), env(safe-area-inset-right))
    max(var(--su-space-4), env(safe-area-inset-bottom))
    max(var(--su-space-4), env(safe-area-inset-left));
}

/* The page hides things with the hidden attribute. Its specificity (0,2,1) beats every class rule that sets a
   display below (the only rules that set one are class rules), so it needs no !important: .hidden stays the
   one !important display in this file. */
body.live-page [hidden] {
  display: none;
}

body.live-page input,
body.live-page #live-match-code {
  user-select: text;
  -webkit-user-select: text;
}

/* The shared "web page keeps its title block on a wide window only" rule hides every header on a phone: this page's
   header is its way back to the app and its title, so it stays (specificity beats that rule). */
body.live-page .live-header {
  display: flex;
  width: min(100%, 1000px);
  align-items: stretch;
}

body.live-page .live-header .back-link {
  align-self: flex-start;
}

body.live-page .lockup {
  align-self: center;
}

body.live-page .lockup h1 {
  min-height: 2rem;
  font-size: var(--su-text-2xl);
  letter-spacing: 0.02em;
}

body.live-page h2 {
  font-size: var(--su-text-sm);
  letter-spacing: 0.04em;
  color: var(--su-fg-2);
}

body.live-page p {
  margin: 0;
}

#live-guard-message:empty {
  display: none;
}

#live-guard-message {
  width: min(100%, 20rem);
  min-height: 2.6em;
  color: var(--su-fault);
  font-weight: 650;
  text-align: center;
}

/* The join screen: one red action, one outline action, one field. */
#live-join-panel {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-3);
  align-items: stretch;
  width: min(100%, 280px);
  flex: 0 0 auto;
}

#live-join-panel p {
  min-height: 1.4em;
  text-align: center;
  color: var(--su-fg-2);
}

#live-join-panel .live-lede {
  color: var(--su-fg);
  font-size: var(--su-text-md);
  line-height: 1.4;
}

#live-join-panel .live-note {
  font-size: var(--su-text-xs);
  line-height: 1.35;
  text-align: left;
}

#live-join-panel .menu-btn,
#live-join-panel input {
  width: 100%;
  min-height: 50px;
  border-radius: var(--ss-radius-xl);
  font-family: var(--su-font);
}

#live-join-panel input {
  padding: 0 var(--su-space-4);
  font-size: var(--su-text-lg);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  text-align: center;
  border: 1px solid var(--su-fg-3);
  background: var(--su-n1);
  color: var(--su-fg);
}

#live-code-display:empty {
  display: none;
}

#live-code-display {
  min-height: 1.8em;
  font-family: var(--su-font-mono);
  font-size: var(--su-text-2xl);
  letter-spacing: 0.2em;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--su-fg);
}

/* The room: lobby, your run, standings, the squad rail. */
#live-match-panel {
  display: none;
  flex-direction: column;
  gap: var(--su-space-4);
  align-items: stretch;
  justify-content: center;
  width: min(100%, 1000px);
}

.live-card {
  display: flex;
  flex-direction: column;
  gap: var(--su-space-2);
  padding: var(--su-space-3);
  background: var(--su-n1);
  border: 1px solid var(--su-line);
  border-radius: var(--ss-radius-xl);
}

#live-lobby-count {
  font-family: var(--su-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--su-fg);
}

.live-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--su-space-3);
  align-items: center;
}

.live-actions .menu-btn {
  min-height: 50px;
  padding: 0 var(--su-space-6);
}

#live-wait-host {
  color: var(--su-fg-2);
  font-weight: 650;
}

/* The match view: ONE grid. Phone portrait: standings strip, squad rail, your run, status row, share panel
   (lobby and results only). Wide screens: your run on the left; status row, standings, squad and share on the right. */
#live-stage {
  /* What the page spends outside your run (title row, standings, rail, status row): the run's width is
     whatever height is left, at its 3:4 shape, and never more than 480 px. */
  --live-chrome: 340px;
  --live-canvas-w: min(480px, max(200px, calc((100vh - var(--live-chrome)) * 3 / 4)));
  display: grid;
  gap: var(--su-space-2);
  align-items: start;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "standings" "squad" "play" "info" "share";
}

.live-play { grid-area: play; display: flex; justify-content: center; }
.live-info { grid-area: info; }
#live-standings-card { grid-area: standings; }
#live-squad-card { grid-area: squad; }
.live-share { grid-area: share; display: flex; flex-direction: column; gap: var(--su-space-1); }

.live-canvas-wrap {
  position: relative;
  width: 100%;
  display: flex;
  justify-content: center;
}

#live-canvas {
  display: block;
  /* Never wider than the column, and never taller than the screen leaves room for. */
  width: min(100%, var(--live-canvas-w));
  height: auto;
  background: var(--su-rail);
  border: 1px solid var(--su-line);
  border-radius: var(--ss-radius-xl);
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Your score, over the top of your own run. */
#live-score {
  position: absolute;
  top: var(--su-space-2);
  left: 50%;
  margin-left: -2.5em;
  width: 5em;
  text-align: center;
  pointer-events: none;
  text-shadow: 0 2px 0 var(--su-rail), 0 0 6px var(--su-rail);
}

/* The status row: your own camera preview, what is going on, and the host's Pause. */
.live-info {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr) auto;
  gap: var(--su-space-2);
  align-items: center;
}

/* Your panel is red: it is "you". Its camera preview is yours alone, never sent. */
.live-panel {
  width: 96px;
  aspect-ratio: 4 / 3;
  border: 2px solid var(--su-accent);
  border-radius: var(--ss-radius-md);
  overflow: hidden;
  position: relative;
  background: var(--su-n1);
  --seat-color: var(--su-fg-2);
}

.live-panel-label {
  position: absolute;
  top: var(--su-space-1);
  left: var(--su-space-1);
  z-index: 1;
  max-width: calc(100% - 8px);
  padding: 0 var(--su-space-2);
  border-radius: var(--ss-radius-sm);
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
  font-size: var(--su-text-xs);
  font-weight: 800;
  line-height: 1.4;
  letter-spacing: 0.02em;
}

#own-camera {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleX(-1);
}

#live-match-code-label {
  min-height: 1.2em;
  font-size: var(--su-text-sm);
  color: var(--su-fg-2);
}

#live-match-code,
#live-score {
  min-height: 1.4em;
  font-family: var(--su-font-mono);
  font-variant-numeric: tabular-nums;
}

#live-match-code {
  font-size: var(--su-text-2xl);
  letter-spacing: 0.2em;
  font-weight: 700;
  color: var(--su-fg);
}

#live-btn-copy-link,
#live-btn-share {
  width: 100%;
  font-size: var(--su-text-sm);
}

#live-btn-pause {
  padding: 0 var(--su-space-4);
  font-size: var(--su-text-sm);
}

#live-score {
  font-family: var(--su-font-display);
  font-size: var(--su-display-sm);
  color: var(--su-fg);
}

#live-status {
  min-height: 4.8em;
  font-size: var(--su-text-md);
  color: var(--su-fg-2);
}

/* Standings: a chip per seat, its colour and shape as swatch, you outlined in red. */
#live-standings {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--su-space-2);
  margin: 0;
  padding: var(--su-space-2);
  background: var(--su-rail);
  border-radius: var(--ss-radius-xl);
}

#live-standings li {
  display: flex;
  align-items: center;
  gap: var(--su-space-2);
  min-height: 32px;
  padding: var(--su-space-1) var(--su-space-3) var(--su-space-1) var(--su-space-2);
  border-radius: var(--ss-radius-pill);
  background: var(--su-n2);
  font-size: var(--su-text-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  --seat-color: var(--su-fg-2);
}

#live-standings li.is-you { outline: 2px solid var(--su-accent); }
#live-standings li.is-offline { opacity: 0.5; }

.live-swatch {
  width: 16px;
  height: 16px;
  flex: none;
  background: var(--seat-color);
}

/* "You" is a red tag with black text. */
.live-you-tag {
  padding: 0 var(--su-space-1);
  border-radius: var(--ss-radius-xs);
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
  font-size: var(--su-text-xs);
  font-weight: 800;
  line-height: 1.4;
}

/* The seat shapes: the second cue next to the number and the colour. The same five shapes on the dummy's
   badge and on the standings swatch. */
.dummy-badge[data-shape='circle'],
.live-swatch[data-shape='circle'] { border-radius: var(--ss-radius-pill); }
.dummy-badge[data-shape='square'],
.live-swatch[data-shape='square'] { border-radius: var(--ss-radius-xs); }
.dummy-badge[data-shape='diamond'],
.live-swatch[data-shape='diamond'] { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }
.dummy-badge[data-shape='hexagon'],
.live-swatch[data-shape='hexagon'] { clip-path: polygon(25% 3%, 75% 3%, 100% 50%, 75% 97%, 25% 97%, 0 50%); }
.dummy-badge[data-shape='shield'],
.live-swatch[data-shape='shield'] { clip-path: polygon(0 0, 100% 0, 100% 58%, 50% 100%, 0 58%); }

/* The training dummy: ONE shared design, tinted by --dummy-tint (a seat colour from live-figure.js). */
.live-grid {
  list-style: none;
  margin: 0;
  padding: var(--su-space-3);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: var(--su-space-2);
  background: var(--su-rail);
  border-radius: var(--ss-radius-xl);
}

.dummy-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0;
  border-radius: var(--ss-radius-md);
  --dummy-tint: var(--su-fg-2);
}

.dummy { display: block; width: 100%; max-width: 72px; aspect-ratio: 1; }

.dummy-torso-outline,
.dummy-limbs-outline {
  fill: none;
  stroke: var(--su-rail);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.dummy-torso-outline { stroke-width: 19; }
.dummy-limbs-outline { stroke-width: 13; }
.dummy-torso { fill: none; stroke: var(--dummy-tint); stroke-width: 14; stroke-linecap: round; }
.dummy-limbs { fill: none; stroke: var(--dummy-tint); stroke-width: 8.5; stroke-linecap: round; stroke-linejoin: round; }
.dummy-seam { fill: none; stroke: rgb(0 0 0 / 45%); stroke-width: 1.6; stroke-dasharray: 3 2.4; }
.dummy-joints { fill: none; stroke: var(--su-rail); stroke-width: 5; stroke-linecap: round; }
.dummy-head-skin { fill: var(--dummy-tint); stroke: var(--su-rail); stroke-width: 2.2; }
.dummy-ring-outer { fill: var(--su-fg); stroke: var(--su-rail); stroke-width: 1; }
.dummy-ring-inner { fill: var(--dummy-tint); stroke: var(--su-rail); stroke-width: 1; }
.dummy-bullseye { fill: var(--su-rail); }

/* The seat badge: the number in black on the seat's colour (every seat colour is at least 4.5:1 on black),
   in the seat's shape. It sits in the cell's top-right corner and must never cover the head: the head is a
   circle at 50% of the figure, 9.5% radius, so its right edge is at 59.5% of the cell (the figure is as wide as
   the cell up to 72 px). A two-digit number on a diamond or a shield needs a wider shape (a clip-path clips the
   digits too); on a narrow rail that shape would reach the head, so where container units exist the badge is
   anchored by its LEFT edge at 64% of the cell and grows to the right, into the gap, never toward the head. */
.dummy-cell { container-type: inline-size; }

.dummy-badge {
  position: absolute;
  top: 0;
  right: -4px;
  display: grid;
  place-items: center;
  min-width: 24px;
  height: 24px;
  padding: 0 var(--su-space-1);
  background: var(--dummy-tint);
  color: var(--su-rail);
  font-family: var(--su-font-mono);
  font-size: var(--su-text-xs);
  font-weight: 800;
  line-height: 1;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Two digits on a diamond: the clip-path clips the digits with the shape and a diamond is widest at the middle, so it
   is wider than the other shapes (a shield is widest at the top, where the digits are, and needs nothing). */
.dummy-badge[data-digits='2'][data-shape='diamond'] { min-width: 32px; }

@supports (left: 1cqw) {
  .dummy-badge {
    left: 64cqw;
    right: auto;
  }
}

.dummy-cell.is-offline { opacity: 0.45; filter: grayscale(1); }

.dummy-cell.is-host::after {
  content: "Host";
  position: absolute;
  bottom: 0;
  right: 0;
  font-size: var(--su-text-xs);
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--su-fg-2);
}

/* In the lobby the tags get a row of their own under the figure ("You" at the left, "Host" at the right), so
   neither can sit on the figure or on the other, at any cell width. The rail keeps them over the corner (a rail
   row must not grow: the stage is sized to it). */
#live-seat-grid .dummy-cell.is-you,
#live-seat-grid .dummy-cell.is-host { padding-bottom: var(--su-space-5); }

/* Your own dummy, in the lobby: a red outline and a tag (never color alone). */
.dummy-cell.is-you { outline: 2px solid var(--su-accent); outline-offset: 1px; }

.dummy-cell.is-you::before {
  content: "You";
  position: absolute;
  bottom: 0;
  left: 0;
  z-index: 1;
  padding: 0 var(--su-space-1);
  border-radius: var(--ss-radius-xs);
  background: var(--su-accent-fill);
  color: var(--su-on-accent);
  font-size: var(--su-text-xs);
  font-weight: 800;
  line-height: 1.4;
}

/* Results: the lobby card shrinks to the result line and the Rematch button (the squad rail below already shows
   everyone), and the standings become a ranked list: place, swatch, seat, spires. */
#live-match-panel[data-phase="ended"] #live-seat-grid { display: none; }

#live-match-panel[data-phase="ended"] #live-standings {
  flex-direction: column;
  flex-wrap: nowrap;
  overflow: visible;
  counter-reset: place;
}

#live-match-panel[data-phase="ended"] #live-standings li {
  counter-increment: place;
  width: 100%;
  min-height: var(--su-hit);
}

#live-match-panel[data-phase="ended"] #live-standings li::before {
  content: counter(place);
  min-width: 24px;
  font-family: var(--su-font-mono);
  color: var(--su-fg-2);
}

#live-match-panel[data-phase="ended"] #live-standings li > :last-child {
  margin-left: auto;
}

#live-match-panel[data-phase="ended"] #live-standings li > :last-child::after {
  content: " spires";
  font-weight: 500;
  color: var(--su-fg-2);
}

@supports (height: 100dvh) {
  #live-stage { --live-canvas-w: min(480px, max(200px, calc((100dvh - var(--live-chrome)) * 3 / 4))); }
}

/* Phone portrait and anything narrower than a desktop: the compact in-match layout. A strip of standings and a
   three-row rail sit ABOVE your run, so all 19 dummies and the leaders are on screen without scrolling; the share
   panel and the page title give way while the match runs. */
@media (max-width: 899.98px) {
  body.live-page[data-in-room] { gap: var(--su-space-2); padding-top: max(var(--su-space-2), env(safe-area-inset-top)); }
  body.live-page[data-in-room] .lockup { display: none; }
  body.live-page[data-in-room] #live-guard-message:empty { display: none; }
  body.live-page[data-in-room] #live-guard-message { min-height: 0; }
  #live-match-panel[data-phase="running"] .live-share { display: none; }
  #live-standings-card,
  #live-squad-card { padding: var(--su-space-2); gap: var(--su-space-1); }
  #live-standings-title,
  #live-squad-title {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  #live-standings {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding: var(--su-space-1);
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
  }
  #live-standings li { flex: 0 0 auto; scroll-snap-align: start; white-space: nowrap; }
  #live-rail {
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: var(--su-space-1);
    padding: var(--su-space-1);
  }
  #live-rail .dummy-cell { padding: 0; }
  #live-rail .dummy-badge { min-width: 20px; height: 20px; }
  #live-status { min-height: 3em; font-size: var(--su-text-sm); }
  .live-info { grid-template-columns: 72px minmax(0, 1fr) auto; }
  .live-panel { width: 72px; }
  /* Everything above and below the canvas, so it can be sized to what is left of the screen. */
  #live-stage { --live-chrome: 352px; }
  #live-match-panel { gap: var(--su-space-2); }
}

/* A phone on its side: your run on the left at the full height of the screen, everything else beside it. */
@media (orientation: landscape) and (max-height: 520px) and (max-width: 899.98px) {
  body.live-page[data-in-room] { padding-top: max(var(--su-space-2), env(safe-area-inset-top)); padding-bottom: var(--su-space-2); }
  #live-stage {
    --live-chrome: 62px;
    grid-template-columns: var(--live-canvas-w) minmax(0, 1fr);
    grid-template-areas:
      "play info"
      "play standings"
      "play squad"
      "play share";
    grid-template-rows: auto auto auto 1fr;
    column-gap: var(--su-space-3);
  }
  #live-standings { flex-wrap: wrap; }
  #live-rail { grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); }
}

/* A desktop: your run on the left; the status row, standings, squad and share down the right, using the width a
   phone does not have. */
@media (min-width: 900px) {
  #live-stage {
    --live-chrome: 150px;
    grid-template-columns: minmax(0, 480px) minmax(300px, 1fr);
    grid-template-areas:
      "play info"
      "play standings"
      "play squad"
      "play share";
    grid-template-rows: auto auto auto 1fr;
    gap: var(--su-space-3) var(--su-space-5);
  }
  .live-play { justify-content: flex-start; }
  .live-info { grid-template-columns: 160px minmax(0, 1fr) auto; }
  .live-panel { width: 160px; }
  #live-status { min-height: 3.6em; }
  #live-rail { grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); gap: var(--su-space-1); }
}

/* -- Native body pose (store apps, native-pose.js) ------------------------------------------------------
   The app shows the camera in a native layer UNDER this page, at the rectangle of the page's camera
   preview (#camera-preview in the card, #own-camera in a live match), and sends only landmarks here. While
   that layer is live the script sets html.native-pose, and everything between the page and the layer is
   see-through so the preview shows where the card is. The app's host view behind the page is the field
   black, so nothing else changes on screen. The video elements stay in layout (they carry no stream) as
   the box the layer fills; the tracking canvas, the gauges and the HUD still draw on top. */
html.native-pose,
html.native-pose body {
  background: transparent;
}

html.native-pose #workout-panel,
html.native-pose .camera-preview-wrap,
html.native-pose #camera-preview,
html.native-pose .live-panel,
html.native-pose #own-camera {
  background: transparent;
}
