/* =============================================================================
 * The Basement at 756 Page
 *
 * Two lights: the default parchment room, and "basement" — the lamp over the
 * table with the rest of the room dark. Every colour is a token defined once
 * on :root and overridden once on [data-theme="basement"].
 *
 * Type does one job each: Zilla Slab speaks, Karla explains, IBM Plex Mono
 * measures. Any number that came out of the physics engine is set in mono.
 * ========================================================================== */

:root {
  /* Ink and ground */
  --ground:      #F2EEE6;
  --ground-2:    #EAE4D8;
  --surface:     #FBF8F2;
  --surface-2:   #F4F0E7;
  --ink:         #16191C;
  --ink-2:       #4A5158;
  --ink-3:       #7C848C;
  --hair:        #DED6C6;
  --hair-2:      #CFC5B1;

  /* Brand */
  --teal:        #0F766E;
  --teal-deep:   #0B554F;
  --teal-soft:   #E2EFEC;
  --ochre:       #C27310;
  --ochre-soft:  #F8EBD8;
  --walnut:      #5A3A21;
  --brass:       #C7912F;

  /* Meaning */
  --good:        #157A52;
  --bad:         #B3402E;
  --warn:        #A8720C;

  /* Geometry */
  --r-sm: 5px;
  --r-md: 9px;
  --r-lg: 14px;
  --shadow-1: 0 1px 2px rgba(20,25,28,.06), 0 2px 8px rgba(20,25,28,.05);
  --shadow-2: 0 2px 6px rgba(20,25,28,.08), 0 12px 34px rgba(20,25,28,.10);
  --panel-w-l: 264px;
  --panel-w-r: 304px;

  --ff-display: "Zilla Slab", ui-serif, Georgia, serif;
  --ff-body: "Karla", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --ff-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

[data-theme="basement"] {
  --ground:      #0E1113;
  --ground-2:    #080A0C;
  --surface:     #171B1E;
  --surface-2:   #1E2327;
  --ink:         #EDE7DA;
  --ink-2:       #A9B0B4;
  --ink-3:       #767E84;
  --hair:        #272D31;
  --hair-2:      #333A3F;
  --teal:        #2FA396;
  --teal-deep:   #14655D;
  --teal-soft:   #123832;
  --ochre:       #E0952F;
  --ochre-soft:  #3A2A12;
  --brass:       #D9A83E;
  --good:        #3FA97A;
  --bad:         #E0705A;
  --warn:        #D9A03A;
  --shadow-1: 0 1px 2px rgba(0,0,0,.4), 0 2px 10px rgba(0,0,0,.35);
  --shadow-2: 0 2px 8px rgba(0,0,0,.5), 0 18px 44px rgba(0,0,0,.5);
}

/* ========================================================== base ========= */

*, *::before, *::after { box-sizing: border-box; }

/* A class that sets `display` outranks the UA rule for [hidden]; several here
 * do. Anything marked hidden must actually be hidden. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--ff-body);
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--ground);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  transition: background .35s ease, color .35s ease;
}

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

button, select, input { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--ochre);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: var(--hair-2); border-radius: 9px; }
::-webkit-scrollbar-track { background: transparent; }

/* ========================================================== shell ======== */

.app {
  height: 100dvh;
  display: grid;
  grid-template-rows: auto 1fr auto;
}

/* ---- top rail ---- */
.topbar {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 9px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--hair);
  box-shadow: var(--shadow-1);
  z-index: 20;
}

.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }

.brand-mark {
  width: 22px; height: 22px; border-radius: 50%;
  background: radial-gradient(circle at 34% 32%, #FFF 0 12%, var(--teal) 13% 100%);
  box-shadow: inset 0 0 0 1.5px rgba(0,0,0,.22), 0 1px 3px rgba(0,0,0,.3);
  position: relative; flex: none;
}
.brand-mark::after {
  content: ""; position: absolute; inset: 32% 34% auto auto;
  width: 5px; height: 5px; border-radius: 50%; background: #C2352B;
}

.brand-text { display: flex; flex-direction: column; line-height: 1.12; min-width: 0; }
.brand-text strong {
  font-family: var(--ff-display); font-weight: 700; font-size: 16.5px;
  letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand-text em {
  font-style: normal; font-size: 11.5px; color: var(--ink-3);
  letter-spacing: .04em; text-transform: lowercase;
}

.modes { display: flex; gap: 2px; margin-left: auto; background: var(--surface-2);
         border: 1px solid var(--hair); border-radius: var(--r-md); padding: 3px; }

.mode-btn {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 6px 14px; border-radius: 6px; font-size: 13.5px; font-weight: 500;
  color: var(--ink-2); transition: background .18s, color .18s;
  white-space: nowrap;
}
.mode-btn:hover { color: var(--ink); background: var(--ground-2); }
.mode-btn.is-on { background: var(--teal); color: #fff; box-shadow: var(--shadow-1); }

.topbar-tools { display: flex; gap: 4px; }

.icon-btn {
  appearance: none; width: 34px; height: 34px; border-radius: var(--r-md);
  border: 1px solid var(--hair); background: var(--surface-2); cursor: pointer;
  color: var(--ink-2); font-size: 15px; line-height: 1;
  display: grid; place-items: center; transition: all .16s;
}
.icon-btn:hover { color: var(--ink); border-color: var(--hair-2); transform: translateY(-1px); }
.icon-btn.is-off { color: var(--ink-3); opacity: .55; }

/* ---- stage -------------------------------------------------------------
   ONE VIEW, and the cloth owns it.

   Until v1.6.0 the stage was three columns: a 196px rail of selects, the
   table, and a 236px rail of instruments. Two rails plus a table meant the
   canvas was HEIGHT-limited on a 16:9 screen, so the balls were small and
   the two things a player actually operates — English and power — were
   competing for attention with a shot log, a ball tray and a ten-row
   numeric readout.

   Now: the table takes the whole stage, the dock floats over it carrying
   English + power + shoot, and everything else is behind the hamburger.
   `.panel` / `.view-full` rail rules are gone rather than overridden — an
   overridden rule is a rule someone re-enables by accident. */

.stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  padding: 6px 8px 8px;
  min-height: 0;
  background:
    radial-gradient(120% 100% at 50% 0%, var(--ground) 0%, var(--ground-2) 100%);
}

/* ---- the level + mode segmented controls ---- */
.seg {
  display: flex; gap: 2px; flex: none;
  background: var(--surface-2);
  border: 1px solid var(--hair);
  border-radius: var(--r-md);
  padding: 3px;
}
.seg-btn {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 6px 13px; border-radius: 6px;
  font-size: 13px; font-weight: 500; line-height: 1.2;
  color: var(--ink-2); white-space: nowrap; text-decoration: none;
  transition: background .18s, color .18s;
}
.seg-btn:hover { color: var(--ink); background: var(--ground-2); }
.seg-btn.is-on { background: var(--teal); color: #fff; box-shadow: var(--shadow-1); }

/* The level is what the app is TEACHING you, so it is the ochre one — it
   reads as a different kind of choice from the mode beside it. */
.seg-levels { margin-left: auto; }
.seg-levels .seg-btn.is-on { background: var(--ochre); color: #fff; }
.seg-modes { margin-left: 10px; }

/* ---- hamburger ---- */
.icon-btn-burger { border-color: var(--hair-2); }
.burger, .burger::before, .burger::after {
  display: block; width: 15px; height: 1.8px; border-radius: 2px;
  background: currentColor; transition: transform .2s ease, opacity .2s ease;
}
.burger { position: relative; }
.burger::before, .burger::after { content: ""; position: absolute; left: 0; }
.burger::before { top: -5px; }
.burger::after  { top: 5px; }
body.more-open .burger { background: transparent; }
body.more-open .burger::before { top: 0; transform: rotate(45deg); }
body.more-open .burger::after  { top: 0; transform: rotate(-45deg); }

/* ---- the felt takes everything ---- */
.felt { position: relative; min-width: 0; min-height: 0; display: flex; }
.felt-inner { width: 100%; height: 100%; }

/* ---- the dock: English, power, shoot ---------------------------------
   Floats over the cloth. Every pixel it takes is a pixel off the table, so
   it is a pill, not a bar, and it carries three controls and one number. */
.stroke.dock {
  position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%);
  z-index: 12; width: max-content; max-width: calc(100% - 20px);
  display: flex; align-items: center; gap: 14px;
  padding: 8px 12px 8px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(10px);
  border: 1px solid var(--hair);
  box-shadow: var(--shadow-2);
}


.dock-power { display: flex; flex-direction: column; gap: 3px; flex: 1 1 340px; min-width: 190px; }
.dock .stroke-label { font-size: 9px; letter-spacing: .11em; }
.dock .stroke-track { height: 14px; border-radius: 7px; }
.dock .stroke-ticks { opacity: .45; }
.dock .stroke-read { min-width: 68px; font-size: 13px; }
.dock .btn-shoot { flex: none; }

/* The dock dims while you are actually pulling the cue back, so it never
   competes with the shot it is about to take. */
body.is-aiming .stroke.dock { opacity: .55; transition: opacity .2s ease; }
body.is-aiming .stroke.dock:hover { opacity: 1; }
.solo-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.solo-stat {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 7px 4px; border-radius: var(--r-sm); background: var(--surface-2);
}
.solo-stat b { font-family: var(--ff-mono); font-size: 16px; color: var(--teal); }
.solo-stat small { font-size: 10px; letter-spacing: .05em; color: var(--ink-3); text-transform: uppercase; }


/* ---- cards ---- */
.card {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: var(--r-lg);
  padding: 12px 13px 13px;
  box-shadow: var(--shadow-1);
}
.card-grow { flex: 1 1 auto; min-height: 96px; display: flex; flex-direction: column; }

.card-title {
  margin: 0 0 9px;
  font-family: var(--ff-mono);
  font-size: 10.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.card-title-note { color: var(--ochre); letter-spacing: .06em; text-transform: none; font-size: 10.5px; }
.card-note { margin: -3px 0 10px; font-size: 12.2px; color: var(--ink-3); line-height: 1.42; }

/* ========================================================== match ======== */

.players { display: flex; flex-direction: column; gap: 5px; }

.player {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px; border-radius: var(--r-md);
  border: 1px solid transparent;
  font-size: 14px;
  transition: background .2s, border-color .2s;
}
.player.is-turn { background: var(--teal-soft); border-color: color-mix(in srgb, var(--teal) 32%, transparent); }
.player-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--hair-2); flex: none;
}
.player.is-turn .player-dot { background: var(--teal); box-shadow: 0 0 0 3px color-mix(in srgb, var(--teal) 22%, transparent); }
.player-name { font-weight: 500; }
.player-group {
  margin-left: auto; font-family: var(--ff-mono); font-size: 11px;
  color: var(--ink-3); letter-spacing: .06em; text-transform: uppercase;
}
.player.is-out .player-name { text-decoration: line-through; color: var(--ink-3); }

.objective {
  margin: 9px 0 0; padding-top: 9px; border-top: 1px dashed var(--hair);
  font-size: 13px; color: var(--ink-2); line-height: 1.44;
}

/* ---- ball tray ---- */
.tray { display: flex; flex-wrap: wrap; gap: 5px; }

.tray-ball {
  width: 25px; height: 25px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--ff-mono); font-size: 10.5px; font-weight: 600; color: #1b1b1b;
  position: relative; overflow: hidden;
  box-shadow: inset -2px -3px 5px rgba(0,0,0,.34), inset 2px 2px 4px rgba(255,255,255,.42), var(--shadow-1);
  transition: opacity .3s, transform .3s, filter .3s;
}
.tray-ball span { position: relative; z-index: 2; }
.tray-ball.is-stripe { background: #F4EFE2; }
.tray-ball.is-stripe::before {
  content: ""; position: absolute; inset: 30% 0; background: var(--bc); z-index: 1;
}
.tray-ball.is-solid { background: var(--bc); }
/* Light text on the dark balls only — yellow and orange need dark text. */
.tray-ball.is-solid span { color: #F7F3E8; }
.tray-ball.is-solid.n1 span, .tray-ball.is-solid.n5 span { color: #1B1B1B; }
.tray-ball.is-stripe span {
  color: #1B1B1B;
  background: #F7F3E8;
  width: 15px; height: 15px; border-radius: 50%;
  display: grid; place-items: center;
  box-shadow: 0 0 0 .5px rgba(0,0,0,.18);
}
.tray-ball.is-gone { opacity: .17; filter: grayscale(1); transform: scale(.84); }
.tray-ball.is-target { outline: 2px solid var(--ochre); outline-offset: 2px; }

/* ---- log ---- */
.log { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.log li {
  font-size: 12.6px; line-height: 1.4; padding: 5px 0 5px 11px;
  border-bottom: 1px solid var(--hair);
  position: relative; color: var(--ink-2);
}
.log li::before {
  content: ""; position: absolute; left: 0; top: 11px;
  width: 4px; height: 4px; border-radius: 50%; background: var(--hair-2);
}
.log li.t-good::before { background: var(--good); }
.log li.t-bad::before  { background: var(--bad); }
.log li.t-great::before{ background: var(--ochre); box-shadow: 0 0 0 3px var(--ochre-soft); }
.log li:last-child { border-bottom: 0; }
.log-drill { max-height: 132px; margin-top: 8px; }

/* ========================================================== fields ======= */

.field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 9px; }
.field label {
  font-family: var(--ff-mono); font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3);
}
.field select, .field input {
  appearance: none; width: 100%;
  background: var(--surface-2); border: 1px solid var(--hair);
  border-radius: var(--r-sm); padding: 7px 9px; font-size: 13.4px;
  transition: border-color .16s, background .16s;
}
.field select {
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%), linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: calc(100% - 15px) 52%, calc(100% - 10px) 52%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
  padding-right: 28px; cursor: pointer;
}
.field select:hover, .field input:hover { border-color: var(--hair-2); }
.field-inline { flex-direction: row; align-items: center; gap: 7px; margin-bottom: 7px; }
.field-inline label { flex: none; width: 46px; }
.field-inline input { text-align: right; padding: 5px 7px; }
.unit { font-family: var(--ff-mono); font-size: 10.5px; color: var(--ink-3); width: 26px; }

/* ========================================================== buttons ====== */

.btn {
  appearance: none; cursor: pointer; border: 1px solid transparent;
  border-radius: var(--r-md); padding: 9px 14px;
  font-size: 13.5px; font-weight: 500; letter-spacing: .01em;
  transition: transform .12s, box-shadow .16s, background .16s, border-color .16s;
}
.btn:active { transform: translateY(1px); }
.btn-block { width: 100%; }
.btn-sm { padding: 6px 10px; font-size: 12.4px; }

.btn-primary { background: var(--teal); color: #fff; box-shadow: var(--shadow-1); }
.btn-primary:hover { background: var(--teal-deep); box-shadow: var(--shadow-2); }

.btn-ghost { background: var(--surface-2); border-color: var(--hair); color: var(--ink-2); }
.btn-ghost:hover { border-color: var(--hair-2); color: var(--ink); }

.btn-shoot {
  background: var(--ochre); color: #fff; font-weight: 700;
  padding: 10px 22px; box-shadow: var(--shadow-1); white-space: nowrap;
}
.btn-shoot:hover { background: #A85F0B; box-shadow: var(--shadow-2); }
.btn-shoot:disabled { background: var(--hair-2); color: var(--ink-3); cursor: not-allowed; box-shadow: none; }

/* ========================================================== felt ========= */

.felt { min-width: 0; min-height: 0; display: flex; }

.felt-inner {
  position: relative; flex: 1; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; gap: 10px;
}

.table-wrap {
  position: relative;
  flex: 1 1 auto; min-height: 0; min-width: 0;
  display: grid; place-items: center;
}

#table {
  /* NO width/height/aspect-ratio here, deliberately. A <canvas> takes its
     intrinsic size from its width/height ATTRIBUTES, and resize() sets those
     from the measured layout box. Let CSS size it from the attributes and the
     two chase each other down to nothing on every ResizeObserver tick — which
     is exactly what happened. The px size is set from JS instead, measured
     from .table-wrap, which the canvas cannot influence. */
  display: block;
  border-radius: var(--r-lg);
  touch-action: none;
  cursor: crosshair;
  box-shadow: var(--shadow-2);
}
#table.is-placing { cursor: grab; }
#table.is-busy { cursor: default; }

.banner {
  position: absolute; top: 12px; left: 50%; transform: translateX(-50%);
  max-width: min(560px, 88%);
  padding: 8px 16px; border-radius: 999px;
  background: rgba(10,16,15,.82); color: #F3EFE3;
  font-size: 13.4px; letter-spacing: .01em;
  backdrop-filter: blur(8px);
  box-shadow: 0 6px 22px rgba(0,0,0,.34);
  opacity: 0; pointer-events: none;
  transition: opacity .28s ease, transform .28s ease;
  text-align: center;
}
.banner.is-on { opacity: 1; transform: translateX(-50%) translateY(0); }
.banner.t-bad   { background: rgba(120,38,24,.88); }
.banner.t-good  { background: rgba(13,86,63,.88); }
.banner.t-great { background: rgba(160,92,10,.92); }

.hint-strip {
  position: absolute; left: 50%; bottom: 12px; max-width: calc(100% - 24px);
  white-space: nowrap;
  font-family: var(--ff-mono); font-size: 10.6px; letter-spacing: .04em;
  color: rgba(240,236,224,.62);
  background: rgba(8,14,13,.42); padding: 4px 9px; border-radius: 999px;
  pointer-events: none; backdrop-filter: blur(5px);
  transition: opacity .3s;
}

/* ---- stroke bar ---- */
.stroke {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface); border: 1px solid var(--hair);
  border-radius: var(--r-lg); padding: 9px 12px;
  box-shadow: var(--shadow-1); flex: none;
}
.stroke-label {
  font-family: var(--ff-mono); font-size: 10px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); flex: none;
}
.stroke-track {
  position: relative; flex: 1 1 auto; height: 26px;
  background: var(--surface-2); border: 1px solid var(--hair);
  border-radius: 6px; overflow: hidden; cursor: ew-resize;
}
.stroke-fill {
  position: absolute; inset: 0 auto 0 0; width: 42%;
  background: linear-gradient(90deg, var(--teal) 0%, var(--teal) 52%, var(--ochre) 88%, #B3402E 100%);
  transition: width .06s linear;
}
.stroke-ticks { position: absolute; inset: 0; display: flex; pointer-events: none; }
.stroke-ticks i {
  flex: 1; border-right: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}
.stroke-ticks i:last-child { border-right: 0; }
.stroke-read { flex: none; min-width: 78px; text-align: right; font-size: 13.6px; font-weight: 500; }

/* ========================================================== spin dial ==== */

.spin-wrap { display: flex; gap: 13px; align-items: center; }

.spin-dial {
  --size: 104px;
  width: var(--size); height: var(--size); flex: none;
  display: grid; place-items: center; cursor: crosshair;
  border-radius: 50%;
}
.spin-ball {
  position: relative; width: 100%; height: 100%; border-radius: 50%;
  background:
    radial-gradient(circle at 34% 30%, #FFFDF7 0%, #F1EADA 42%, #CFC5B0 78%, #A99E88 100%);
  box-shadow: inset -5px -7px 14px rgba(0,0,0,.22), inset 3px 4px 9px rgba(255,255,255,.7), var(--shadow-1);
}
.spin-cross-h, .spin-cross-v {
  position: absolute; background: rgba(30,35,38,.16);
}
.spin-cross-h { left: 8%; right: 8%; top: 50%; height: 1px; }
.spin-cross-v { top: 8%; bottom: 8%; left: 50%; width: 1px; }
.spin-miscue {
  position: absolute; inset: 25%; border-radius: 50%;
  border: 1px dashed rgba(178,64,46,.55);
}
.spin-tip {
  position: absolute; width: 17px; height: 17px; border-radius: 50%;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  background: #2E6E8E;
  box-shadow: 0 0 0 2px rgba(255,255,255,.85), 0 2px 5px rgba(0,0,0,.4);
  transition: background .18s;
}
.spin-tip.is-miscue { background: var(--bad); }

.spin-legend { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.spin-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; font-size: 12.4px; color: var(--ink-2); }
.spin-row b { font-size: 13px; color: var(--ink); font-weight: 500; }
.spin-warn { color: var(--bad); font-size: 11.6px; font-weight: 500; }
.spin-legend .btn { margin-top: 4px; }

/* ========================================================== readout ====== */

.readout { margin: 0; display: flex; flex-direction: column; }
.ro {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 5px 0; border-bottom: 1px solid var(--hair);
}
.ro:last-child { border-bottom: 0; }
.ro dt { font-size: 12.4px; color: var(--ink-2); }
.ro dd { margin: 0; font-size: 13.4px; font-weight: 500; }
.ro-strong dd { color: var(--ochre); font-size: 15px; font-weight: 600; }
.ro.is-dim dd { color: var(--ink-3); font-weight: 400; }

.verdict {
  margin-top: 9px; padding: 8px 10px; border-radius: var(--r-md);
  background: var(--surface-2); border: 1px solid var(--hair);
  font-size: 12.6px; line-height: 1.45; color: var(--ink-2);
  min-height: 34px;
}
.verdict.t-good  { border-color: color-mix(in srgb, var(--good) 40%, transparent); color: var(--good); }
.verdict.t-bad   { border-color: color-mix(in srgb, var(--bad) 40%, transparent); color: var(--bad); }
.verdict.t-great { border-color: color-mix(in srgb, var(--ochre) 50%, transparent); color: var(--ochre); background: var(--ochre-soft); }

/* ========================================================== aids ========= */

.aids { display: flex; flex-direction: column; gap: 1px; }

.aid {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 7px; border-radius: var(--r-sm); cursor: pointer;
  transition: background .15s;
}
.aid:hover { background: var(--surface-2); }
.aid input { position: absolute; opacity: 0; width: 0; height: 0; }
.aid-box {
  width: 15px; height: 15px; border-radius: 4px; flex: none;
  border: 1.5px solid var(--hair-2); background: var(--surface-2);
  display: grid; place-items: center; transition: all .16s;
}
.aid-box::after {
  content: ""; width: 7px; height: 4px; border: 2px solid #fff;
  border-top: 0; border-right: 0; transform: rotate(-45deg) scale(0);
  transition: transform .16s; margin-top: -2px;
}
.aid input:checked + .aid-box { background: var(--teal); border-color: var(--teal); }
.aid input:checked + .aid-box::after { transform: rotate(-45deg) scale(1); }
.aid input:focus-visible + .aid-box { outline: 2px solid var(--ochre); outline-offset: 2px; }
.aid-text { display: flex; flex-direction: column; min-width: 0; }
.aid-text b { font-size: 13px; font-weight: 500; }
.aid-text small { font-size: 11.2px; color: var(--ink-3); line-height: 1.32; }

/* ========================================================== drill ======== */

.drill-lesson {
  margin: 0 0 7px; font-family: var(--ff-display); font-size: 13.4px;
  font-weight: 500; font-style: italic; color: var(--ochre); line-height: 1.4;
}
.drill-brief { margin: 0 0 11px; font-size: 12.6px; color: var(--ink-2); line-height: 1.48; }

.drill-score { display: flex; gap: 7px; margin-bottom: 9px; }
.ds {
  flex: 1; background: var(--surface-2); border: 1px solid var(--hair);
  border-radius: var(--r-md); padding: 7px 8px; text-align: center;
}
.ds span { display: block; font-size: 9.6px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); font-family: var(--ff-mono); }
.ds b { font-size: 19px; font-weight: 600; line-height: 1.25; }

.drill-dots { display: flex; gap: 4px; margin-bottom: 4px; }
.drill-dots i {
  flex: 1; height: 4px; border-radius: 2px; background: var(--hair);
}
.drill-dots i.is-pass { background: var(--good); }
.drill-dots i.is-now  { background: var(--ochre); }

/* ========================================================== quote rail === */

.quote-rail {
  padding: 8px 18px; background: var(--surface); border-top: 1px solid var(--hair);
  transition: opacity .3s;
}
.quote-rail p {
  margin: 0; font-family: var(--ff-display); font-style: italic;
  font-size: 13.6px; color: var(--ink-2); text-align: center; line-height: 1.4;
}
.quote-rail span {
  font-style: normal; font-family: var(--ff-mono); font-size: 10.4px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3);
  margin-left: 12px;
}

/* ========================================================== sheet ======== */

.sheet { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; }
.sheet[hidden] { display: none; }
.sheet-scrim { position: absolute; inset: 0; background: rgba(8,12,14,.62); backdrop-filter: blur(3px); }
.sheet-body {
  position: relative; z-index: 1;
  width: min(760px, 92vw); max-height: 86vh; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--hair);
  border-radius: var(--r-lg); box-shadow: var(--shadow-2);
  padding: 30px 34px 34px;
  animation: rise .3s cubic-bezier(.2,.7,.3,1);
}
@keyframes rise { from { opacity: 0; transform: translateY(14px); } }

.sheet-close {
  position: absolute; top: 12px; right: 14px;
  width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--hair);
  background: var(--surface-2); cursor: pointer; font-size: 19px; line-height: 1;
  color: var(--ink-2);
}
.sheet-close:hover { color: var(--ink); }

.sheet-content h2 {
  font-family: var(--ff-display); font-size: 27px; font-weight: 700;
  margin: 0 0 4px; letter-spacing: -.015em; line-height: 1.16;
}
.sheet-content .kicker {
  font-family: var(--ff-mono); font-size: 10.5px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ochre); margin: 0 0 14px;
}
.sheet-content h3 {
  font-family: var(--ff-mono); font-size: 10.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-3); margin: 24px 0 8px;
  padding-top: 14px; border-top: 1px solid var(--hair);
}
.sheet-content p { margin: 0 0 12px; font-size: 15px; line-height: 1.62; color: var(--ink-2); }
.sheet-content blockquote {
  margin: 18px 0; padding: 14px 18px; border-left: 3px solid var(--ochre);
  background: var(--ochre-soft); border-radius: 0 var(--r-md) var(--r-md) 0;
  font-family: var(--ff-display); font-size: 17px; font-style: italic;
  line-height: 1.5; color: var(--ink);
}
.sheet-content blockquote cite {
  display: block; margin-top: 8px; font-family: var(--ff-mono); font-style: normal;
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3);
}
.sheet-content ol.lessons { margin: 0; padding: 0; list-style: none; counter-reset: L; }
.sheet-content ol.lessons li {
  counter-increment: L; position: relative; padding: 10px 0 10px 42px;
  border-bottom: 1px solid var(--hair); font-size: 14.4px; line-height: 1.55; color: var(--ink-2);
}
.sheet-content ol.lessons li::before {
  content: counter(L, decimal-leading-zero);
  position: absolute; left: 0; top: 11px;
  font-family: var(--ff-mono); font-size: 12px; font-weight: 600; color: var(--ochre);
}
.sheet-content ol.lessons li b { color: var(--ink); font-weight: 600; }

.keys { display: grid; grid-template-columns: auto 1fr; gap: 7px 14px; align-items: baseline; }
.keys kbd {
  font-family: var(--ff-mono); font-size: 11.5px; padding: 3px 7px;
  background: var(--surface-2); border: 1px solid var(--hair-2);
  border-bottom-width: 2px; border-radius: 5px; white-space: nowrap;
}
.keys span { font-size: 13.4px; color: var(--ink-2); }

/* ========================================================== toast ======== */

.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 200; padding: 10px 18px; border-radius: 999px;
  background: var(--ink); color: var(--ground);
  font-size: 13.4px; box-shadow: var(--shadow-2);
  animation: rise .25s ease;
}
.toast[hidden] { display: none; }

/* ========================================================== responsive === */

/* The top rail carries brand + level + mode + four icons. Below ~1180px the
   brand strapline goes, then the brand text, then the rail wraps to two rows
   rather than letting the level control shrink — the level is the single most
   important control on the page and must never be the thing that clips. */
@media (max-width: 1240px) {
  body { font-size: 14.5px; }
  .seg-btn { padding: 6px 11px; font-size: 12.5px; }
}

@media (max-width: 1080px) {
  .brand-text em { display: none; }
  .seg-modes { margin-left: 8px; }
}

@media (max-width: 940px) {
  .brand-text { display: none; }
}

@media (max-width: 820px) {
  .topbar { flex-wrap: wrap; gap: 8px 10px; padding: 8px 10px; }
  .seg-levels { margin-left: auto; }
  .seg-modes { margin-left: 0; }
  .topbar-tools { margin-left: auto; }
}

/* Phone. The page still does not scroll: the table is the app, and a table you
   have to scroll to is not a table you can aim on. The dock leaves the pill and
   becomes a real two-row bar under the cloth. */
@media (max-width: 680px) {
  .stage { padding: 4px 6px 6px; }

  .seg-levels, .seg-modes { width: 100%; margin-left: 0; }
  .seg-levels .seg-btn, .seg-modes .seg-btn { flex: 1 1 0; text-align: center; padding: 7px 4px; }
  .topbar-tools { position: absolute; top: 8px; right: 10px; }
  .topbar { position: relative; padding-right: 150px; }

  .stroke.dock {
    position: static; transform: none; width: auto; max-width: none;
    margin-top: 6px; border-radius: var(--r-lg);
    display: grid; grid-template-columns: auto 1fr; gap: 8px 12px;
    align-items: center; padding: 8px 10px;
  }
  .dock-power { grid-column: 2; min-width: 0; }
  .dock .stroke-read { grid-column: 1 / -1; text-align: left; min-width: 0; }
  .dock .btn-undo { grid-column: 1; }
  .dock .btn-shoot { grid-column: 2; width: 100%; }
  body.is-aiming .stroke.dock { opacity: 1; }

  /* At this width the hint wraps to two lines and lands on the dock. Put it in
     the flow under the table instead of over it. */
  .hint-strip {
    position: static; max-width: none; margin: 6px auto 0;
    text-align: center; white-space: normal;
  }

  /* Portrait. A 1.81:1 table at 390px wide can only ever be ~210px tall, so the
     felt column has ~400px of spare height. Left to grow, .table-wrap spread
     the canvas, the hint and the dock across the whole of it and the two things
     you operate ended up at opposite ends of the phone. Hug the top instead, so
     the dock sits directly under the cloth and both are in one thumb's reach.
     Scoped away from the 3D page, whose canvas is absolutely positioned and
     genuinely does need the height. */
  body:not(.is-3d) .felt { align-items: flex-start; }
  body:not(.is-3d) .felt-inner { height: auto; }
  body:not(.is-3d) .table-wrap { flex: 0 0 auto; }
}

@media (max-width: 560px) {
  .sheet-body { padding: 24px 20px 28px; width: 94vw; }
  .more-inner { padding: 12px; }
  .more-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
  .sheet-body { padding: 24px 20px 28px; width: 94vw; }
  .spin-wrap { flex-direction: column; align-items: stretch; }
  .spin-dial { --size: 128px; margin: 0 auto; }
  .brand-text strong { font-size: 15px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}

/* ========================================================== additions ===== */

/* First paint. A CSS animation, not a JS class: if the script never runs the
   page must still be visible. */
.app { animation: appIn .45s cubic-bezier(.2,.7,.3,1) both; }
@keyframes appIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }

/* Undo sits next to the trigger, quiet until it is available. */
.btn-undo {
  flex: none; padding: 9px 13px;
  font-family: var(--ff-mono); font-size: 12px;
  letter-spacing: .06em; text-transform: uppercase;
}

.btn-shoot.is-waiting {
  background: var(--surface-2); color: var(--ink-3);
  border-color: var(--hair); box-shadow: none; cursor: default;
}

/* Play keeps three aim aids; the analysis toggles belong to Training. */
/* (removed v1.6.0 — every overlay stays reachable from the More sheet) */

.sheet-actions {
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--hair);
}
.result-line {
  font-size: 13px; color: var(--ink-2);
  background: var(--surface-2); border: 1px solid var(--hair);
  border-radius: var(--r-md); padding: 9px 12px; display: inline-block;
}

#table.is-placing { cursor: grab; }
#table.is-placing:active { cursor: grabbing; }

/* The tray marks what you are legally on. Make that read at a glance. */
/* .tray-ball is overflow:hidden for the stripe band, which would clip a
   pseudo-element ring. An outline is not clipped, so pulse that instead. */
.tray-ball.is-target {
  outline: 2px solid var(--ochre); outline-offset: 2px;
  animation: targetPulse 2.4s ease-in-out infinite;
}
@keyframes targetPulse {
  0%, 100% { outline-color: color-mix(in srgb, var(--ochre) 40%, transparent); }
  50%      { outline-color: var(--ochre); }
}

/* Panels get a little air on very tall screens rather than one long column. */
@media (min-height: 1000px) and (min-width: 1081px) {
  .card-grow { min-height: 180px; }
}

/* ---- the 861-1080px drawer -----------------------------------------------
   In this band the left panel is an off-canvas drawer. It had no way to be
   opened, so on a laptop or a tablet in landscape the score, the game selector
   and "Rack them" were simply unreachable. It is a real disclosure now. */
.drawer-btn { display: none; }

.drawer-scrim {
  position: fixed; inset: 0; z-index: 29;
  background: rgba(8, 12, 14, .45);
  opacity: 0; pointer-events: none; transition: opacity .25s ease;
}
body.left-open .drawer-scrim { opacity: 1; pointer-events: auto; }

@media (max-width: 1080px) and (min-width: 861px) {
  #btn-panel { display: grid; }
  .drawer-scrim:not([hidden]) { display: block; }
  body.left-open .panel-left { transform: none; }
  .panel-left { z-index: 30; overflow-y: auto; }
}
@media (min-width: 1081px), (max-width: 860px) {
  .drawer-scrim { display: none !important; }
}

/* ================================================= solve + outcome ======== */

.picker { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; }

.pick {
  appearance: none; cursor: pointer;
  width: 28px; height: 28px; border-radius: 50%; border: 0; padding: 0;
  position: relative; overflow: hidden;
  font-family: var(--ff-mono); font-size: 11px; font-weight: 600;
  display: grid; place-items: center;
  box-shadow: inset -2px -3px 5px rgba(0,0,0,.34), inset 2px 2px 4px rgba(255,255,255,.42), var(--shadow-1);
  transition: transform .14s, box-shadow .14s;
}
.pick:hover { transform: translateY(-2px); }
.pick span { position: relative; z-index: 2; color: #F7F3E8; }
.pick.is-stripe { background: #F4EFE2; }
.pick.is-stripe::before { content: ""; position: absolute; inset: 30% 0; background: var(--bc); z-index: 1; }
.pick.is-stripe span {
  color: #1B1B1B; background: #F7F3E8; width: 16px; height: 16px; border-radius: 50%;
  display: grid; place-items: center;
}
.pick.is-solid { background: var(--bc); }
.pick.n1 span, .pick.n5 span { color: #1B1B1B; }
.pick.is-on { outline: 2.5px solid var(--ochre); outline-offset: 2px; transform: translateY(-2px); }
.pick.is-any {
  width: auto; padding: 0 11px; border-radius: 999px; background: var(--surface-2);
  border: 1px solid var(--hair); box-shadow: none; font-size: 11px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-2);
}
.pick.is-any span { color: inherit; background: none; width: auto; height: auto; }
.pick.is-any.is-on { border-color: var(--ochre); color: var(--ink); }

/* A lone checkbox whose explanation runs to several lines: centring the box
   against a tall text block parks it halfway down the paragraph. */
.aid-solo { margin: 2px 0 10px; padding-left: 0; align-items: flex-start; }
.aid-solo .aid-box { margin-top: 2px; }
.solve-actions { display: flex; gap: 7px; }
.solve-actions .btn { flex: 1; }

.solve-status {
  margin: 9px 0 0; min-height: 17px;
  font-family: var(--ff-mono); font-size: 11.4px; line-height: 1.45; color: var(--ink-3);
}
.solve-status.is-good { color: var(--good); }
.solve-status.is-bad { color: var(--bad); }
.solve-status.is-busy { color: var(--ochre); }

/* ---- what happens --------------------------------------------------------- */

.outcome {
  list-style: none; margin: 0; padding: 0;
  max-height: 244px; overflow-y: auto;
}
.outcome li.k-more { color: var(--ink-3); font-style: italic; }
.outcome li.k-more::before { background: transparent; box-shadow: none; }
.outcome li {
  display: flex; align-items: baseline; gap: 8px;
  padding: 6px 0 6px 16px; position: relative;
  border-bottom: 1px solid var(--hair); font-size: 12.8px; color: var(--ink-2);
}
.outcome li:last-child { border-bottom: 0; }
.outcome li::before {
  content: ""; position: absolute; left: 2px; top: 12px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--hair-2);
}
.outcome li.k-pot::before { background: var(--good); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 18%, transparent); }
.outcome li.k-scratch::before,
.outcome li.k-miss::before { background: var(--bad); box-shadow: 0 0 0 3px color-mix(in srgb, var(--bad) 18%, transparent); }
.outcome li.k-contact::before { background: var(--ochre); }
.outcome li.k-rail::before { background: var(--brass); }
.outcome li b { font-weight: 500; color: var(--ink); }
.outcome li .det {
  margin-left: auto; font-family: var(--ff-mono); font-size: 11px;
  color: var(--ink-3); white-space: nowrap;
}
.outcome li.k-pot b { color: var(--good); }
.outcome li.k-scratch b, .outcome li.k-miss b { color: var(--bad); }
.outcome-empty { font-size: 12.6px; color: var(--ink-3); padding: 6px 0; }

@media (max-width: 560px) {
  .solve-actions { flex-direction: column; }
  .pick { width: 32px; height: 32px; }
}

/* ================================================= levels ================= */

.field-note {
  margin: 5px 0 0; font-size: 11.6px; line-height: 1.44; color: var(--ink-3);
}
.field-note b { color: var(--ochre); font-weight: 600; }

.spin-lock-note {
  margin: 4px 0 0; font-size: 11.2px; line-height: 1.4; color: var(--ink-3);
}

/* A dial you cannot use should look like one. */
.spin-dial.is-locked { cursor: not-allowed; opacity: .55; }
.spin-dial.is-locked .spin-tip { background: var(--hair-2); }
.spin-dial.is-vertical-only .spin-ball::after {
  content: ""; position: absolute; left: 50%; top: 8%; bottom: 8%;
  width: 2px; margin-left: -1px; border-radius: 2px;
  background: color-mix(in srgb, var(--ochre) 55%, transparent);
}
.spin-dial.is-vertical-only { cursor: ns-resize; }

/* Beginners get the whole line; the level selector says so. */
body.lvl-beginner .card-plot { display: none; }

/* ================================================= the More sheet ========
   The hamburger. Everything that used to be a permanent rail lives here now:
   the table setup, every overlay, the numbers, the solver, the drills.

   It is a real panel rather than the old auto-fit card soup, because the
   cards inside it are not peers — "Table" and "What to draw" are what you
   open this for, and they lead. Cards are MARKUP here, not moved at runtime:
   the previous version appendChild'd four cards in on open and put them back
   on close, and a card that lives in two places is a card that goes missing. */

.more-sheet {
  position: fixed; inset: 0; z-index: 60; display: none;
  align-items: flex-start; justify-content: center;
  padding: 4vh 20px;
  background: rgba(8, 12, 14, .55);
  backdrop-filter: blur(4px);
}
.more-sheet.is-open { display: flex; }

.more-inner {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  width: min(1000px, 100%);
  max-height: 92vh;
  overflow: auto;
  padding: 16px 18px 20px;
  animation: moreIn .22s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes moreIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }

.more-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
  position: sticky; top: -16px; z-index: 2;
  background: var(--surface);
  padding: 6px 0 10px;
  border-bottom: 1px solid var(--hair);
}
.more-head h2 { margin: 0; font-family: var(--ff-display); font-size: 18px; }

.more-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  align-items: start;
}
.more-col { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.more-inner .card { background: var(--surface-2); }
/* Inside the sheet nothing needs to grow to fill a rail. */
.more-inner .card-grow { flex: 0 1 auto; min-height: 0; }
.more-inner .log { max-height: 190px; overflow-y: auto; }

input[type=range] { width: 100%; accent-color: var(--teal); }

/* Every overlay is reachable from here at every mode. The old rule hid the
   analysis toggles in Play so the right rail was not a wall of switches —
   but there is no rail any more, and this IS the drawer you opened to find
   them. Hiding a control inside "More options" is the one place it makes no
   sense at all. */

/* ================================== v2.3.0 — gutters and a slim dock =====
 * Three asks, all about the two things that were still sitting ON the cloth:
 *   - English lives on the RIGHT, and only there
 *   - the contact magnifier moves OFF the table, into a gutter on the LEFT,
 *     at a size the player chooses
 *   - the power/shoot dock gets shorter, and can be dragged out of the way
 *
 * These are gutters, not the old rails: one instrument each, no cards, and the
 * left one collapses to nothing when the magnifier is switched off. */

.stage {
  grid-template-columns: var(--zoom-w, 0px) minmax(0, 1fr) var(--eng-w, 116px);
  gap: 8px;
  align-items: stretch;
}

.gutter {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 8px;
  min-width: 0; min-height: 0;
}
.gutter-title, .zoom-title {
  font-family: var(--ff-mono); font-size: 9px; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-3);
}

/* ---- left: the magnifier ---- */
/* Collapsed to a zero-width column when the aid is off, so the table simply
   reclaims the space rather than leaving a hole where an instrument was. */
body:not(.zoom-on) .gutter-left { display: none; }
body.zoom-on { --zoom-w: var(--zoom-size, 200px); }

.gutter-left { justify-content: flex-start; padding-top: 4px; }
.zoom-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px; width: 100%;
}
.zoom-size { display: flex; gap: 1px; background: var(--surface-2);
             border: 1px solid var(--hair); border-radius: 6px; padding: 2px; }
.zoom-size-btn {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  font-family: var(--ff-mono); font-size: 10px; line-height: 1;
  padding: 3px 6px; border-radius: 4px; color: var(--ink-3);
}
.zoom-size-btn:hover { color: var(--ink); background: var(--ground-2); }
.zoom-size-btn.is-on { background: var(--teal); color: #fff; }

/* No width/height here — app.js writes the px box and the backing store, and a
   CSS size would fight the ResizeObserver the same way it would on the table. */
#zoomcanvas { display: block; border-radius: var(--r-md); }

/* ---- right: English, and only English ---- */
.gutter-right .spin-dial { --size: 96px; width: 96px; height: 96px; margin: 0; }
.gutter-right .btn { padding: 5px 10px; font-size: 11.5px; }

/* ---- the dock: one slim row, draggable ---- */
/* Was 94px tall with the dial stacked inside it and its label under that, which
   is a lot of cloth to cover. The dial has its own gutter now, so this is a
   single row of controls ~46px tall. */
.stroke.dock {
  gap: 10px;
  padding: 5px 8px 5px 4px;
  align-items: center;
}
.dock-grip {
  appearance: none; border: 0; background: transparent; cursor: grab;
  color: var(--ink-3); font-size: 15px; line-height: 1; padding: 4px 3px;
  touch-action: none; flex: none; letter-spacing: -2px;
}
.dock-grip:hover { color: var(--ink); }
body.dock-dragging .dock-grip { cursor: grabbing; }
body.dock-dragging .stroke.dock { opacity: 1; transition: none; }
.dock .stroke-label { display: none; }          /* the bar is self-evident */
.dock .stroke-track { height: 12px; border-radius: 6px; }
.dock-power { gap: 0; flex: 1 1 300px; min-width: 150px; }
.dock .btn-shoot { padding: 7px 16px; }
.dock .btn-undo { padding: 6px 10px; }

/* A dock the player has moved is positioned from the top-left, so the centring
   transform has to come off or it sits half a dock to the left of the drop. */
.stroke.dock.is-placed { transform: none; }

@media (max-width: 1240px) {
  .gutter-right .spin-dial { --size: 84px; width: 84px; height: 84px; }
  :root { --eng-w: 104px; }
}

/* Phone: the gutters stop being columns and become one row under the table,
   magnifier beside the dial, with the dock below them. A 200px magnifier column
   on a 390px screen would leave the table 180px wide. */
@media (max-width: 680px) {
  .stage { grid-template-columns: minmax(0, 1fr); grid-auto-rows: min-content; }
  .felt { order: 1; }
  .gutter-left { order: 2; }
  .gutter-right { order: 3; }
  .gutter-left, .gutter-right {
    flex-direction: row; align-items: center; justify-content: center;
    gap: 10px; padding: 0;
  }
  .zoom-head { width: auto; flex-direction: column; gap: 3px; }
  body.zoom-on { --zoom-w: auto; }
  .gutter-right .spin-dial { --size: 76px; width: 76px; height: 76px; }
  /* Dragging is a pointer affordance; on a phone the dock is in the flow. */
  .dock-grip { display: none; }
  .stroke.dock.is-placed { position: static; left: auto; top: auto; }
}

/* The canvas is sized from THIS box, not from .gutter-left: on a phone the
   gutter is a flex row with the S/M/L head beside the canvas, and measuring the
   gutter gave the canvas 62px more than it had. `min-width: 0` is required —
   a flex item defaults to min-width:auto and refuses to shrink below its
   content, which is the canvas's own (JS-written) width. */
.zoom-box { flex: 1 1 auto; min-width: 0; display: flex; justify-content: center; width: 100%; }
@media (max-width: 680px) {
  .zoom-box { max-width: 200px; width: auto; }
}
