:root {
  --bg: #0b0f14;
  --bg-2: #10161e;
  --panel: rgba(20, 27, 36, 0.88);
  --panel-solid: #141b24;
  --stroke: rgba(255, 255, 255, 0.08);
  --text: #e6edf3;
  --muted: #8b98a5;
  --accent: #60a5fa;
  --accent-dim: rgba(96, 165, 250, 0.16);
  --danger: #f87171;
  --radius: 16px;
  --dock-h: 76px;
  --anim-bar-h: 48px;
  --tl-h: 36px;
  --safe-b: env(safe-area-inset-bottom, 0px);
}

* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif;
  overscroll-behavior: none;
  user-select: none;
  -webkit-user-select: none;
}

.hidden { display: none !important; }

/* ---------- Top bar ---------- */
.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 56px;
  padding: 0 12px 0 14px;
  padding-top: env(safe-area-inset-top, 0px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: linear-gradient(to bottom, rgba(11,15,20,.96), rgba(11,15,20,.82));
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--stroke);
  z-index: 40;
}

.brand { display: flex; align-items: center; gap: 9px; }
.brand-icon { width: 26px; height: 26px; color: var(--accent); }
.brand-img {
  height: 28px;
  width: auto;
  max-width: 140px;
  object-fit: contain;
  display: block;
  border-radius: 4px;
  /* The favicon comes pre-tuned for dark backgrounds; no inversion needed. */
}
.brand-text { display: flex; align-items: center; gap: 7px; }
.brand-name { font-weight: 700; font-size: 16.5px; letter-spacing: -0.02em; }
.brand-badge {
  font-size: 9px; font-weight: 800; letter-spacing: 0.09em;
  color: var(--accent);
  background: var(--accent-dim);
  border: 1px solid rgba(96, 165, 250, 0.35);
  padding: 2.5px 6px;
  border-radius: 6px;
}

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

.icon-btn {
  width: 40px; height: 40px;
  border: none;
  border-radius: 12px;
  background: transparent;
  color: var(--text);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background .15s;
}
.icon-btn svg { width: 20px; height: 20px; }
.icon-btn:active { background: rgba(255,255,255,.08); }
.icon-btn.accent { color: var(--accent); }
.icon-btn.on { color: var(--accent); background: var(--accent-dim); }
.icon-btn:disabled { opacity: .32; }
/* View toggle (#btn-view-toggle). The button shows the icon of the TARGET
 * view, not the current one — clicking the cube flips to 3D, clicking the
 * flat-pitch flips to 2D. So in 3D mode (default) the flat-pitch icon shows;
 * in 2D mode the cube icon shows. */
#btn-view-toggle .ico-2d { display: block; }
#btn-view-toggle .ico-3d { display: none; }
body.mode2d #btn-view-toggle .ico-2d { display: none; }
body.mode2d #btn-view-toggle .ico-3d { display: block; }

/* ---------- Menu popover ---------- */
.menu-pop {
  position: fixed;
  top: calc(58px + env(safe-area-inset-top, 0px));
  right: 10px;
  z-index: 60;
  background: var(--panel-solid);
  border: 1px solid var(--stroke);
  border-radius: 14px;
  box-shadow: 0 16px 48px rgba(0,0,0,.55);
  overflow: hidden;
  min-width: 190px;
  animation: pop .14s ease-out;
}
.menu-pop button {
  display: block;
  width: 100%;
  padding: 13px 16px;
  background: none;
  border: none;
  color: var(--text);
  font-size: 14.5px;
  text-align: left;
  cursor: pointer;
}
.menu-pop button:active { background: rgba(255,255,255,.07); }
.menu-pop button + button { border-top: 1px solid var(--stroke); }

@keyframes pop {
  from { opacity: 0; transform: translateY(-6px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Board ---------- */
.board-wrap {
  position: fixed;
  top: calc(56px + env(safe-area-inset-top, 0px));
  bottom: calc(var(--dock-h) + var(--anim-bar-h) + var(--safe-b));
  left: 0; right: 0;
  background:
    radial-gradient(1200px 500px at 50% -10%, rgba(74,222,128,.05), transparent 60%),
    var(--bg);
}

/* Mobile view-toggle FAB: a large, always-tappable button anchored to the
 * top-right of the board on small / coarse-pointer viewports. In 3D mode it
 * reads "2D" (reduce). In 2D mode it reads "3D" (back). The text flips via
 * a body class set by set3D in ui.js. */
/* Close button on the expanded mobile cam-radar: once the user taps the chip
 * to expand the 3x3 grid, the chip itself is hidden, so without this the
 * grid has no visible way to collapse back. The close button sits inside a
 * dedicated header bar at the top of the expanded radar so it never overlaps
 * the 3x3 grid dots. Hidden on desktop (where the radar is always expanded)
 * and on mobile when the radar is still collapsed. */
.cam-radar-header {
  display: none;
  height: 32px;
  align-items: center;
  justify-content: flex-end;
  padding: 0 4px;
  flex: none;
}
.cam-radar-close {
  display: none;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.10);
  color: var(--text);
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
  place-items: center;
  -webkit-tap-highlight-color: transparent;
  padding: 0;
}
.cam-radar-close:active { background: rgba(96, 165, 250, 0.30); }
@media (max-width: 768px), (pointer: coarse) {
  .cam-radar.expanded .cam-radar-header { display: flex; }
  .cam-radar.expanded .cam-radar-close  { display: grid; }
}
/* Both canvases sit absolutely over the board-wrap; only the one matching
 * the current view mode is visible and pointer-active. The view toggle
 * button (#btn-view-toggle) in the topbar flips them. */
#board,
#glboard {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  display: block;
  touch-action: none;
}
#board { z-index: 1; }
#glboard { z-index: 2; }
/* 2D mode: hide the WebGL canvas, show the 2D canvas. The view toggle
 * (set3D in ui.js) flips the .mode2d / .mode3d class on <body>. */
body.mode2d #glboard { display: none; }
body.mode3d #board   { display: none; }

/* ---------- Camera radar widget (3D) ---------- */
/* Small transparent square at top-right with 9 pinpoints in a 3×3 + center grid.
 * Each pinpoint flies the camera to one of the 9 presets. Active pin is highlighted
 * with the accent ring + label band below the grid. */
.cam-radar {
  position: absolute;
  top: 12px; right: 12px;
  z-index: 26;
  width: 144px;
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 22px;
  background: rgba(11, 15, 20, 0.42);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: pop .14s ease-out;
  /* The wrapper only displays the radar UI; clicks on the empty area pass through
   * to the canvas so the user can drop players / drag tokens around the radar
   * without the dots getting in the way. The chip + grid buttons re-enable
   * pointer-events below. */
  pointer-events: none;
}
.cam-radar-chip,
.cam-radar-dot {
  pointer-events: auto;
}
/* collapsed chip on mobile + small viewports: just a small accent dot that
 * expands to the full grid on tap. Tap toggles .expanded on the parent so
 * the user keeps control without losing screen real estate to the radar. */
.cam-radar-chip {
  display: none;
  width: 44px; height: 44px;
  border-radius: 999px;
  background: rgba(96, 165, 250, 0.18);
  border: 1px solid rgba(96, 165, 250, 0.45);
  color: var(--accent);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
@media (max-width: 768px), (pointer: coarse) {
  .cam-radar { width: 44px; height: 44px; border-radius: 999px; padding: 0; }
  .cam-radar > .cam-radar-grid,
  .cam-radar > .cam-radar-label { display: none; }
  .cam-radar-chip { display: flex; }
  .cam-radar.expanded {
    width: 144px; height: auto; border-radius: 22px;
  }
  /* display: revert is wrong here — for a <div> it falls back to the
   * user-agent default (`block`), so the 3x3 grid collapses to a single
   * column of dots on mobile. Explicitly set `grid` / `block` to keep
   * the desktop layout. */
  .cam-radar.expanded > .cam-radar-grid  { display: grid; }
  .cam-radar.expanded > .cam-radar-label { display: block; }
  .cam-radar.expanded > .cam-radar-chip { display: none; }
}
.cam-radar::before {
  /* faint pitch-style outline so spatial intent is visible at a glance */
  content: '';
  position: absolute;
  inset: 14px 18px 30px;
  border: 1px dashed rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  pointer-events: none;
}
.cam-radar-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  width: 144px;
  height: 144px;
  position: relative;
}
.cam-radar-dot {
  position: relative;
  display: grid;
  place-items: center;
  background: transparent;
  border: none;
  cursor: pointer;
  border-radius: 12px;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
  transition: background .15s;
}
.cam-radar-dot::after {
  content: '';
  width: 9px; height: 9px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  border: 1.5px solid rgba(255, 255, 255, 0.30);
  transition: width .16s, height .16s, background .16s, border-color .16s, box-shadow .16s, transform .16s;
}
.cam-radar-dot:active::after {
  background: rgba(96, 165, 250, 0.30);
  transform: scale(1.2);
}
.cam-radar-dot.active::after {
  width: 14px; height: 14px;
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.30);
}
.cam-radar-dot.active { background: rgba(96, 165, 250, 0.10); }
.cam-radar-label {
  flex: none;
  text-align: center;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--accent);
  padding: 6px 4px 9px;
  min-height: 26px;
  background: rgba(96, 165, 250, 0.06);
  border-top: 1px solid rgba(96, 165, 250, 0.16);
  font-variant-numeric: tabular-nums;
}
.cam-radar-label:empty::before {
  content: '3D VIEW';
  color: var(--muted);
  letter-spacing: 0.08em;
  font-weight: 700;
}
/* ---------- Token chip ---------- */
.token-chip {
  position: absolute;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 5px;
  background: var(--panel-solid);
  border: 1px solid var(--stroke);
  border-radius: 16px;
  padding: 6px;
  box-shadow: 0 10px 30px rgba(0,0,0,.5);
  animation: pop .13s ease-out;
}
.chip-row { display: flex; align-items: center; gap: 3px; }

/* Player jersey number — small badge inside the chip-row. Always visible
 * (the chip is the only place the user sees the number badge even when
 * the player isn't selected in 2D labelOpts.num mode), monospaced so a
 * 2-digit swap from "9" to "10" doesn't reflow the chip width. */
.chip-jersey {
  display: inline-grid;
  place-items: center;
  min-width: 28px;
  height: 24px;
  padding: 0 8px;
  margin-left: 4px;
  border-radius: 7px;
  background: rgba(255,255,255,.10);
  color: var(--text);
  font-weight: 800;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  flex: none;
}
/* Player name — ellipsis-clipped so very long names don't push the chip
 * awkwardly wide; showed alongside the jersey number in the collapsed
 * row so the user can confirm who's selected at a glance without having
 * to expand the chip-controls panel. */
.chip-player-name {
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 78px;
  margin: 0 2px 0 1px;
  flex: none;
}
/* POV (1st-person) eye button inside the chip-row. Reads as "view" (not
 * "delete") so we tint it the accent colour and reserve the danger tint
 * for #chip-delete. `.pov.on` mirrors the cam-radar's active dot — a
 * step-up in opacity + accent ring — so the user can see at a glance
 * that the player they're looking at IS the one the camera is locked
 * to. (Active state also exists when the button is clicked; the actual
 * camera lock hides the whole chip while POV is on.) */
.chip-btn.pov {
  color: var(--accent);
  background: rgba(96, 165, 250, .14);
  border: 1px solid rgba(96, 165, 250, .35);
}
.chip-btn.pov:active {
  background: rgba(96, 165, 250, .28);
}
.chip-btn.pov.on {
  background: var(--accent);
  color: #06220f;
  border-color: var(--accent);
}
.chip-name {
  width: 96px;
  padding: 6px 9px;
  margin-left: 3px;
  border: 1px solid var(--stroke);
  border-radius: 9px;
  background: rgba(255,255,255,.05);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 600;
  user-select: text;
  -webkit-user-select: text;
}
.chip-name::placeholder { color: var(--muted); }
#chip-eye.off { opacity: .35; }
.chip-lbl {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .06em;
  color: var(--muted);
  text-transform: uppercase;
  margin: 0 2px 0 5px;
}
.chip-swatches { display: flex; gap: 4px; }
.chip-swatches button {
  width: 19px; height: 19px;
  border-radius: 999px;
  border: 1.6px solid transparent;
  cursor: pointer;
  padding: 0;
}
.chip-swatches button.sel { border-color: #fff; }
.chip-btn {
  width: 32px; height: 32px;
  border: none;
  border-radius: 999px;
  background: rgba(255,255,255,.06);
  color: var(--text);
  font-size: 17px;
  font-weight: 700;
  display: grid;
  place-items: center;
  cursor: pointer;
}
.chip-btn:active { background: rgba(255,255,255,.14); }
.chip-btn.danger { color: var(--danger); }
.chip-num {
  width: 44px;
  min-width: 30px;
  padding: 5px 6px;
  text-align: center;
  font-weight: 800;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid transparent;
  border-radius: 9px;
  color: var(--text);
  outline: none;
  -moz-appearance: textfield;
  appearance: textfield;
  transition: border-color .15s, background .15s;
}
.chip-num::-webkit-outer-spin-button,
.chip-num::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.chip-num:focus {
  border-color: var(--accent);
  background: rgba(255, 255, 255, 0.10);
}
.chip-sep { width: 1px; height: 18px; background: var(--stroke); margin: 0 3px; }
.chip-divider { height: 1px; background: var(--stroke); margin: 4px -2px; }
#chip-summary { gap: 4px; }
#chip-delete { margin-left: auto; }

/* ---------- POV (1st-person) close button ---------- *
 * Sits at the top-left under the topbar — mirrors the cam-radar position
 * on the right. Hidden by default; the .hidden default-display wins
 * because the button has no other place to anchor to. The button is
 * `position: fixed` so it stays glued to screen corners even when the
 * POV camera moves the world beneath it. */
.pov-close {
  position: fixed;
  top: calc(64px + env(safe-area-inset-top, 0px));
  left: 12px;
  z-index: 41;
  display: none;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 14px 0 11px;
  border: 1px solid var(--stroke);
  border-radius: 999px;
  background: var(--panel-solid);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .02em;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0,0,0,.4);
  -webkit-tap-highlight-color: transparent;
  transition: background .15s, color .15s, border-color .15s;
}
.pov-close svg { flex: none; }
.pov-close:active {
  background: rgba(248, 113, 113, .18);
  color: var(--danger);
  border-color: rgba(248, 113, 113, .35);
}
/* body.pov-active toggles the visibility of every POV-only UI:
 *  • the chip-row is hidden because it would appear huge at the centre
 *    of the screen (chip projects onto the player's feet, and the feet
 *    are right under the camera in POV).
 *  • the cam-radar is hidden because the user isn't flying a virtual
 *    camera through orbital presets; POV is a player-locked view.
 *  • the 2D/3D view-toggle is hidden because POV is a 3D-only concept
 *    — leaving it visible invites the user to flip to 2D, which would
 *    silently drop them out of POV with no warning.                  */
body.pov-active .pov-close { display: inline-flex; }
body.pov-active .token-chip { display: none !important; }
body.pov-active .cam-radar { display: none !important; }
body.pov-active #btn-view-toggle { display: none; }

/* ---------- Playback HUD ---------- */
.playback-hud {
  position: absolute;
  bottom: 14px; left: 50%;
  transform: translateX(-50%);
  z-index: 35;
  display: flex;
  gap: 7px;
}
.playback-hud button {
  display: flex; align-items: center; gap: 7px;
  background: var(--panel-solid);
  color: var(--text);
  border: 1px solid var(--stroke);
  border-radius: 999px;
  padding: 10px 16px;
  font-size: 13.5px; font-weight: 700;
  cursor: pointer;
  box-shadow: 0 10px 30px rgba(0,0,0,.5);
  white-space: nowrap;
}
.playback-hud button:active { background: rgba(255,255,255,.1); }
.playback-hud #btn-pause { color: var(--accent); }
.playback-hud #btn-speed { font-variant-numeric: tabular-nums; min-width: 62px; justify-content: center; }
.playback-hud #btn-ballpath.on {
  color: #fde047;
  border-color: rgba(253,224,71,.5);
  background: rgba(253,224,71,.12);
}
.playback-hud #btn-stop { color: var(--danger); }
/* On mobile the playback-hud is the only bottom UI visible during play
 * (the timeline, anim-bar and dock are all hidden by body.anim-playing
 * below), so its buttons can afford to be much smaller. The labels are
 * still present at 11.5px but the padding is reduced and the icons shrink
 * to 14px. */
@media (max-width: 768px), (pointer: coarse) {
  .playback-hud { gap: 4px; bottom: 10px; }
  .playback-hud button { padding: 7px 10px; font-size: 11.5px; gap: 4px; }
  .playback-hud button svg { width: 14px; height: 14px; }
  .playback-hud #btn-speed { min-width: 46px; }
  .playback-hud #btn-ballpath { padding: 7px 9px; }
  .playback-hud #btn-draw { padding: 7px 9px; font-size: 11.5px; }
}
/* Close-toolbar button on the bottom dock: styled like a regular .tool-btn
 * (so it fits the row visually with Move / Draw / Run / Pass / Zone /
 * Erase / Spot / Path + Color + Add), but uses a danger :active tint to
 * signal it's a collapse action rather than a tool selection. The
 * button itself is .tool-btn.close-toolbar, and `body.dock-hidden` is
 * toggled by #btn-tools and #btn-dock-close click handlers in ui.js.
 * Playback-mode: the existing rule below already hides .tool-btn[data-
 * tool='spot'] and .tool-btn[data-tool='path'] under body.anim-playing;
 * .close-toolbar is unaffected (it has no data-tool attribute), so the
 * user can still collapse the dock during playback. */
/* Two-class selector so the close-toolbar's `transition` declaration
 * wins over `.tool-btn`'s later `transition: color .15s, background .15s`
 * rule (`.tool-btn` is specificity (0,1,0); this rule is (0,2,0)).
 * Source-order no longer matters — the fade-in/out declarations stick
 * even if `.tool-btn` is later in the file. */
.tool-btn.close-toolbar {
  color: var(--muted);
  /* Only color + background transitions are meaningful now (no fade/scale),
   * since the visibility rule below uses display:none rather than
   * opacity/transform. The fade baseline that used to live here was
   * killed by the empty-slot fix in From-Edit-To-Play: when off the
   * playback screen the previous opacity:0 left a visible gap in the
   * dock's trailing edge because the flex slot stayed reserved. */
  transition: color .15s, background .15s;
}
.tool-btn.close-toolbar:active {
  background: rgba(248, 113, 113, 0.20);
  color: var(--danger);
}
/* The × Hide close-toolbar button lives on the dock but should only be
 * visible (and clickable) inside the playback state. Outside playback
 * the dock is the regular editing toolbar — the user can pick any tool
 * directly, so the × Hide button has nothing to collapse. animation.js
 * toggles body.anim-playing on play()/stop(), so a `body:not(.anim-playing)`
 * selector keeps the visibility in sync without any extra JS.
 *
 * `display: none` is used (rather than the previous opacity+scale fade)
 * so the close button's flex slot drops out completely when off the
 * play screen — neighbor buttons redistribute the freed width evenly
 * instead of leaving an empty space at the trailing edge of the dock.
 * Source-order caveat: keep this rule AFTER the `.dock { transition: ... }`
 * rule (the dock's own transform fade) so the dock transforms first
 * and the inner button switch is independent. The
 * `body:not(.anim-playing) .tool-btn.close-toolbar` selector carries
 * specificity (0,3,1), comfortably beating every other selector that
 * could match the close-toolbar (max specificity 0,2,1), so this
 * display:none override wins regardless of source order. */
body:not(.anim-playing) .tool-btn.close-toolbar {
  display: none;
}
/* When the toolbar is collapsed, the playback-hud's #btn-tools becomes
 * the only path back to it. The dock state is communicated via the
 * `aria-expanded` attribute (toggled by syncDockHiddenUI in ui.js) and
 * a title-attribute swap, NOT via a color override — the button keeps
 * the same dark style as stop / restart / pause / speed / ballpath so
 * the playback-hud reads as one consistent control row. The previous
 * red tint (color + border + background) made this button look like
 * an error state instead of a peer of the other playback controls. */
.playback-hud #btn-tools.dock-hidden-state {
  /* Intentional no-op: dock state is signalled via aria-expanded + the
   * button's title attribute, not via color. The dark base style from
   * `.playback-hud button` carries through unchanged. */
}
/* Slide the entire .dock panel off-screen when collapsed. .dock already
 * sits at `position: fixed; bottom: 0` so translateY > 100% removes it
 * from the user's viewport. pointer-events:none lets click-through on
 * the freed area reach the board canvas. The transition lives on .dock
 * itself so re-show slides back into place symmetrically. */
body.dock-hidden .dock {
  transform: translateY(calc(100% + var(--safe-b) + 16px));
  opacity: 0;
  pointer-events: none;
}
.dock {
  transition: transform .26s ease-out, opacity .2s ease-out;
}
/* Reclaim the dock's layout slot on the board when hidden. The dock sits
 * at `position: fixed` so transform alone doesn't free up the bottom;
 * board-wrap still anchors its `bottom: calc(var(--dock-h) + ...)` to
 * the dock's eventual location. Extending board-wrap to bottom:0 means
 * the canvas fills the freed space when the toolbar is collapsed.
 *
 * NOTE — source-order dependency: BOTH this rule and the later
 * `body.anim-playing .board-wrap { bottom: 0 }` rule carry specificity
 * (0,1,1). During playback-with-dock-hidden, the playback rule wins
 * (it's later in the file) so the canvas fills the entire screen
 * regardless of dock state. Keep this rule BEFORE the anim-playing
 * rule, or the dock-hidden-in-playback case will fall back to the
 * default 76px+ gap. The reviewer flagged this; do not reorder casually. */
body.dock-hidden .board-wrap {
  bottom: var(--safe-b);
}
@media (max-width: 768px), (pointer: coarse) {
  /* Mobile: hide the "Hide" label on the close-toolbar button so the
   * icon-only button fits the narrower dock width. */
  .tool-btn.close-toolbar span { display: none; }
}

/* When the animation is playing, the dock stays visible as a "quick-draw"
 * picker — only the drawing tools (Move / Draw / Run / Pass / Zone / Erase
 * + Color / Add) remain tappable. The tracker tools (spot + path) are
 * hidden because they only target the edit state, not the running play.
 * The rest of the bottom UI (timeline, anim-bar menu, sheets, toasts,
 * token-chip, cam-radar) goes away so playback reads as focused. */
/* `.color-pop` was previously in this list, which forced the drawing-color
 * palette to be unreachable during playback — the user opens the swatch
 * popover from the Color button in the dock, but the `!important hide`
 * on body.anim-playing kept it pinned closed even when the click handler
 * tried to reveal it. Removed so the user can pick a new brush color
 * (e.g. to add annotations over a running playback) — the popover stays
 * open above the dock and dims the playfield behind its translucent
 * background so the selected swatch remains readable. */
body.anim-playing #anim-timeline,
body.anim-playing #anim-bar,
body.anim-playing .menu-pop,
body.anim-playing .sheet,
body.anim-playing .sheet-backdrop,
body.anim-playing .toast,
body.anim-playing .token-chip,
body.anim-playing .cam-radar { display: none !important; }
/* Quick-draw picker: during playback the dock stays, but the tracker
 * tools (spot / path = "track") are stripped out and the rest reflow to
 * take their place. */
/* Track-only tools (spot, path) and motion are stripped during playback because
 * they only make sense in edit mode (they pin state into the active animation).
 * After the 3D consolidation those three live under a single dock entry —
 * #btn-3d with data-tool="3d" — so the playback-hide rule hides the parent
 * button (which carries the popover-trigger logic). `.close-toolbar` is
 * intentionally NOT in this list so the user can still collapse the dock
 * during playback with the inline × Hide button. */
body.anim-playing .dock .tool-btn[data-tool='3d'] { display: none !important; }
/* Both selectors needed: the second one has specificity (0,3,1) and beats
 * the base `body.has-timeline .board-wrap` rule (0,2,1) so the canvas
 * fills the freed space even when the timeline was active before play. */
body.anim-playing .board-wrap,
body.has-timeline.anim-playing .board-wrap {
  bottom: 0;
}

/* ---------- Frame nav (in anim-bar) ---------- */
.frame-nav {
  display: flex;
  align-items: center;
  gap: 2px;
  background: rgba(255,255,255,.06);
  border: 1px solid var(--stroke);
  border-radius: 999px;
  padding: 3px;
}
.frame-nav button {
  border: none;
  background: transparent;
  color: var(--accent);
  font-size: 13px;
  font-weight: 800;
  padding: 7px 11px;
  border-radius: 999px;
  cursor: pointer;
  line-height: 1;
  min-width: 34px;
  text-align: center;
}
.frame-nav button:active { background: rgba(255,255,255,.1); }
#fn-label { color: var(--text); min-width: 42px; font-variant-numeric: tabular-nums; font-size: 13px; }
#fn-delete { color: var(--danger); padding: 7px 9px; display: flex; align-items: center; }
#fn-next.can-create { color: var(--accent); }
.fn-sep { width: 1px; height: 18px; background: var(--stroke); margin: 0 1px; }

/* ---- Animation bar (frame nav + undo/redo + play, sits above dock) ---- */
#anim-bar {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(var(--dock-h) + var(--safe-b));
  height: var(--anim-bar-h);
  display: flex !important;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  padding: 0 14px;
  background: linear-gradient(to top, rgba(11,15,20,.97), rgba(11,15,20,.86));
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--stroke);
  z-index: 38;
  /* Belt-and-braces: nothing inside the bar should ever lose pointer
   * events, regardless of inherited rules. */
  pointer-events: auto;
}
#anim-bar * { pointer-events: auto; }
/* On large screens the dock is centered 520px wide while the anim-bar is
 * full-width — the play button pushed to the right by margin-left:auto
 * ended up visually disconnected from the dock. Center the bar's content
 * on ≥640px viewports so the play button sits directly above the dock. */
@media (min-width: 640px) {
  #anim-bar { justify-content: center; }
  #anim-bar #btn-play { margin-left: 8px; }
}
.anim-history { display: flex; align-items: center; gap: 2px; }
.anim-history .icon-btn {
  width: 36px; height: 36px;
  border-radius: 10px;
}
.anim-history .icon-btn svg { width: 18px; height: 18px; }
#anim-bar #btn-play { margin-left: auto; }

/* ---- Animation timeline (sits above anim-bar) ---- */
.anim-timeline {
  position: fixed;
  bottom: calc(var(--dock-h) + var(--anim-bar-h) + var(--safe-b));
  left: 0; right: 0;
  height: var(--tl-h);
  background: rgba(11,15,20,.95);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--stroke);
  display: flex;
  align-items: center;
  padding: 0 22px;
  z-index: 37;
}
body.has-timeline .board-wrap {
  bottom: calc(var(--dock-h) + var(--anim-bar-h) + var(--tl-h) + var(--safe-b));
}
.tl-track {
  position: relative;
  flex: 1;
  align-self: stretch;
  cursor: pointer;
  touch-action: none;
}
.tl-track::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 3px;
  background: rgba(255,255,255,.12);
  border-radius: 999px;
  pointer-events: none;
}
.tl-progress {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 3px;
  background: var(--accent);
  border-radius: 999px;
  pointer-events: none;
  width: 0%;
}
.tl-dot {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 11px; height: 11px;
  border-radius: 999px;
  background: var(--panel-solid);
  border: 2px solid rgba(96,165,250,.55);
  cursor: pointer;
  z-index: 1;
  border: none;
  padding: 0;
  transition: transform .1s, background .1s;
}
.tl-dot:active { transform: translate(-50%, -50%) scale(1.4); }
.tl-dot.active {
  background: var(--accent);
  width: 13px; height: 13px;
  box-shadow: 0 0 0 3px rgba(96,165,250,.25);
}
.tl-cursor {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 14px; height: 14px;
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 2px 7px rgba(0,0,0,.7);
  pointer-events: none;
  z-index: 2;
}

/* pinned motion-clip markers (humanoid) above the timeline track */
.tl-moment {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -140%);
  width: 18px; height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  background: rgba(250,204,21,.16);
  border: 1px solid rgba(250,204,21,.65);
  color: #facc15;
  padding: 0;
  cursor: pointer;
  z-index: 3;
}
.tl-moment:active { transform: translate(-50%, -140%) scale(1.25); }

/* ---------- Motion clip menu ---------- */
.motion-menu {
  position: fixed;
  left: 50%;
  bottom: calc(84px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 60;
  min-width: 220px;
  max-height: 46vh;
  overflow-y: auto;
  background: var(--panel-solid, #101827);
  border: 1px solid rgba(255,255,255,.12);
  border-radius: 14px;
  box-shadow: 0 12px 34px rgba(0,0,0,.55);
  padding: 6px;
}
.motion-menu-head {
  padding: 8px 10px 5px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: rgba(255,255,255,.55);
}
.motion-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 12px;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: #e5e7eb;
  font-size: 14px;
  cursor: pointer;
}
.motion-menu-item:hover, .motion-menu-item:active { background: rgba(96,165,250,.16); }
.motion-menu-item.remove { color: #fca5a5; }

/* ---------- Watermark ----------
 * Sits above the pitch canvas but BELOW players + drawings + everything
 * that reacts to input (z-index 3 is the canvas layer, 10 sits over it
 * with disabled pointer-events so the swatch never traps a tap). The
 * watermark is the perma-brand and reads from a CSS custom property so
 * future re-branding only changes one rule, and the opacity is
 * intentionally low (0.10) so the playing field isn't visually walled
 * off — the eye registers the brand without losing the action. */
.watermark {
  position: absolute;
  left: 50%;
  top: 50%;
  /* slightly above the pitch centerline so it doesn't sit on the
   * halfway line itself; keeps it readable without competing with the
   * center circle. */
  transform: translate(-50%, calc(-50% + 22%));
  z-index: 5;
  color: rgba(255, 255, 255, 0.10);
  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Inter, sans-serif;
  font-size: clamp(28px, 6vw, 56px);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  /* Double-text approach: a faint dark shadow behind the bright fill
   * gives the watermark a card-board feel over both light grass
   * (away team side) and dark forest-green grass (home team side),
   * without needing a colored box. The shadow is at half the fill
   * alpha so it never fights the fill. */
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  white-space: nowrap;
}
/* POV mode already hides the rest of the chip row, but the watermark
 * would otherwise bleed through at every camera angle. Hide it during
 * POV so the first-person view stays clean (the brand is still present
 * in the topbar logo). */
body.pov-active .watermark { display: none; }

@media (prefers-reduced-motion: reduce) {
  /* The watermark is static — no keyframe needed — but the reduced-
   * motion query helps future contributors avoid accidentally adding
   * one. Acts as a soft no-op today. */
  .watermark { text-shadow: 0 1px 4px rgba(0, 0, 0, 0.18); }
}

/* ---------- Color popover ---------- */
.color-pop {
  position: fixed;
  bottom: calc(var(--dock-h) + var(--safe-b) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 55;
  display: flex;
  gap: 10px;
  background: var(--panel-solid);
  border: 1px solid var(--stroke);
  border-radius: 999px;
  padding: 10px 14px;
  box-shadow: 0 14px 40px rgba(0,0,0,.55);
  animation: pop .13s ease-out;
}
.color-pop .swatch {
  width: 30px; height: 30px;
  border-radius: 999px;
  border: 2.5px solid transparent;
  cursor: pointer;
}
.color-pop .swatch.sel { border-color: #fff; box-shadow: 0 0 0 2px rgba(255,255,255,.25); }

/* ---------- Pass / Aerial popover ---------- *
 * Opened from a Pass button click in the dock. Mirrors the
 * color-popover's center-anchored pill design (translucent panel,
 * round-cornered, pop-in animation) so the dropdown reads as part of
 * the same button-design family. Each button pairs a stroked arrow
 * (solid for Pass, dashed for Aerial) with a label, and the currently
 * selected sub-type carries an accent-tinted .sel treatment so the
 * user can see at a glance which one is active (same pattern as the
 * color swatches). Buttons keep their flex slot when the pop is
 * already open / closed via .hidden */
.pass-pop {
  position: fixed;
  bottom: calc(var(--dock-h) + var(--safe-b) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 55;
  display: flex;
  gap: 4px;
  background: var(--panel-solid);
  border: 1px solid var(--stroke);
  border-radius: 999px;
  padding: 5px;
  box-shadow: 0 14px 40px rgba(0,0,0,.55);
  animation: pop .13s ease-out;
}
.pass-pop-btn {
  display: flex; align-items: center; gap: 7px;
  padding: 9px 13px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.pass-pop-btn:active { background: rgba(255,255,255,.10); }
.pass-pop-btn.sel {
  background: var(--accent-dim);
  color: var(--accent);
}
.pass-pop-btn svg { display: block; flex: none; }

/* ---------- Spot / Path / Motion popover (3D button) ---------- *
 * Mirrors the .pass-pop pill so the dock UX stays one family of
 * popovers. Three chips: Spot (star-cross with top dot), Path
 * (S-curve with end dots), Motion (humanoid stick). The currently
 * active sub-type carries the same .sel accent-tinted treatment as
 * the Pass/Aerial picker — same pattern, same visual idiom, so
 * users only have to learn one chip vocabulary across the dock.
 * The chips paint over a slightly translucent panel like the color
 * popover so they read as controls, not as text — the .sel rule
 * makes the currently active sub-type stand out without the user
 * needing to compare icon details at a glance. */
.tracker-pop {
  position: fixed;
  bottom: calc(var(--dock-h) + var(--safe-b) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 55;
  display: flex;
  gap: 4px;
  background: var(--panel-solid);
  border: 1px solid var(--stroke);
  border-radius: 999px;
  padding: 5px;
  box-shadow: 0 14px 40px rgba(0,0,0,.55);
  animation: pop .13s ease-out;
}
.tracker-pop-btn {
  display: flex; align-items: center; gap: 7px;
  padding: 9px 13px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tracker-pop-btn:active { background: rgba(255,255,255,.10); }
.tracker-pop-btn.sel {
  background: var(--accent-dim);
  color: var(--accent);
}
.tracker-pop-btn svg { display: block; flex: none; }

/* ---------- Dock ---------- */
.dock {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: calc(var(--dock-h) + var(--safe-b));
  padding: 8px 8px calc(8px + var(--safe-b));
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: 2px;
  background: linear-gradient(to top, rgba(11,15,20,.97), rgba(11,15,20,.86));
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid var(--stroke);
  z-index: 40;
}

.tool-btn {
  flex: 1;
  max-width: 64px;
  border: none;
  border-radius: 13px;
  background: transparent;
  color: var(--muted);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  cursor: pointer;
  transition: color .15s, background .15s;
}
.tool-btn svg { width: 21px; height: 21px; }
.tool-btn span { font-size: 9.5px; font-weight: 700; letter-spacing: .04em; }
.tool-btn.active {
  color: var(--accent);
  background: var(--accent-dim);
}
.tool-btn:active { background: rgba(255,255,255,.07); }
/* Tracker tools (Spot / Path / Motion) used to have three separate dock
 * buttons, each tinted accent blue so the row read as "tap a player"
 * affordances alongside the muted-grey drawing tools. After the
 * consolidation they live under a single .tool-btn[data-tool='3d']
 * (= #btn-3d) that opens the .tracker-pop picker. The accent tint
 * carries over so the dock still flags the entry as a marker-tools
 * group, distinct from the permanent-pen drawing row. The .active
 * background highlight is added by the regular `.tool-btn.active` rule
 * — both layers combine when a sub-type is active (Spot / Path /
 * Motion). */
.tool-btn[data-tool='3d'] {
  color: var(--accent);
}
.tool-btn.add { color: var(--bg); }
.tool-btn.add svg { background: var(--accent); border-radius: 999px; padding: 4px; width: 26px; height: 26px; box-shadow: 0 4px 14px rgba(74,222,128,.4); }
.tool-btn.add span { color: var(--muted); }

.dock-sep { width: 1px; background: var(--stroke); margin: 10px 4px; }

.color-dot {
  width: 22px; height: 22px;
  border-radius: 999px;
  border: 2px solid rgba(255,255,255,.75);
  background: #ffd60a;
}

/* ---------- Sheet ---------- */
.sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.45);
  z-index: 48;
  animation: fade .18s ease-out;
}
@keyframes fade { from { opacity: 0 } to { opacity: 1 } }

.sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 50;
  background: var(--panel-solid);
  border-radius: 22px 22px 0 0;
  border: 1px solid var(--stroke);
  border-bottom: none;
  box-shadow: 0 -18px 60px rgba(0,0,0,.6);
  padding-bottom: calc(10px + var(--safe-b));
  max-height: 72vh;
  display: flex;
  flex-direction: column;
  animation: slideup .22s cubic-bezier(.3,1.1,.4,1);
}
@keyframes slideup { from { transform: translateY(60%); opacity: .4 } to { transform: none; opacity: 1 } }

.sheet-handle {
  width: 40px; height: 4.5px;
  border-radius: 999px;
  background: rgba(255,255,255,.18);
  margin: 9px auto 5px;
  flex: none;
}
/* Explicit close × button — anchored to the sheet's left edge (not the
 * centered drag handle) so it never lands in the middle of the screen.
 * `.sheet` itself is `position: fixed; left: 0; right: 0` so it forms
 * the containing block for this absolute child. The button shares
 * vertical space with `sheet-handle` — `top: 9px` matches the handle's
 * own 9px top margin so they sit on the same baseline. */
.sheet-close {
  position: absolute;
  left: 12px;
  top: 9px;
  width: 28px; height: 28px;
  border: 1px solid var(--stroke);
  border-radius: 999px;
  background: rgba(255,255,255,.06);
  color: var(--text);
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
  transition: background .15s, color .15s;
}
.sheet-close:active { background: rgba(248, 113, 113, .18); color: var(--danger); border-color: rgba(248, 113, 113, .35); }

.sheet-tabs {
  display: flex;
  gap: 4px;
  padding: 6px 14px 10px;
  flex: none;
}
.sheet-tab {
  flex: 1;
  padding: 9px 4px;
  border: none;
  border-radius: 11px;
  background: transparent;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
}
.sheet-tab.active { background: var(--accent-dim); color: var(--accent); }

.sheet-body {
  padding: 2px 16px 14px;
  overflow-y: auto;
}

.anim-panel-hdr {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 4px 2px 0;
}
.anim-panel-hdr span:first-child {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--text);
}
.anim-frame-count {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

.sheet-section-title {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .1em;
  color: var(--muted);
  text-transform: uppercase;
  margin: 14px 2px 9px;
}
.sheet-section-title:first-child { margin-top: 4px; }

/* object grid */
.obj-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 9px;
}
.obj-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 14px 6px 11px;
  border: 1px solid var(--stroke);
  border-radius: 14px;
  background: rgba(255,255,255,.03);
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.obj-btn:active { background: rgba(255,255,255,.08); }
.obj-ico {
  width: 34px; height: 34px;
  display: grid;
  place-items: center;
}

/* formations */
.form-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 9px;
}
.form-btn {
  padding: 15px 4px;
  border: 1px solid var(--stroke);
  border-radius: 14px;
  background: rgba(255,255,255,.03);
  color: var(--text);
  font-size: 15px;
  font-weight: 800;
  letter-spacing: .02em;
  cursor: pointer;
}
.form-btn:active { background: var(--accent-dim); border-color: var(--accent); }

.seg {
  display: flex;
  gap: 4px;
  background: rgba(255,255,255,.05);
  border-radius: 12px;
  padding: 4px;
}
.seg button {
  flex: 1;
  padding: 9px 6px;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.seg button.active { background: var(--accent); color: #06220f; }

/* team colors */
.team-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 2px;
}
.team-row label { font-size: 14px; font-weight: 600; }
.team-swatches { display: flex; gap: 7px; }
.team-swatches .swatch {
  width: 26px; height: 26px;
  border-radius: 999px;
  border: 2px solid transparent;
  cursor: pointer;
}
.team-swatches .swatch.sel { border-color: #fff; }

.wide-btn {
  display: block;
  width: 100%;
  padding: 13px;
  margin-top: 9px;
  border: 1px solid var(--stroke);
  border-radius: 13px;
  background: rgba(255,255,255,.04);
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.wide-btn:active { background: rgba(255,255,255,.09); }
.wide-btn.primary { background: var(--accent); border-color: var(--accent); color: #06220f; }
.wide-btn.danger-o { color: var(--danger); }

/* ========== Add-sheet Animations section ========== *
 * Animated "Open animation panel" button shown in the bottom-sheet Objects
 * tab (renderAddTab in js/ui.js). The button OPENS the existing Animations
 * sheet (renderAnimTab) via TP.ui.openSheet('anim'), which hides the
 * .sheet-tabs row and renders the full management UI (frames strip +
 * Capture frame + onion toggles). Three layered CSS keyframe animations
 * give the button a distinctly "alive" feel so it reads as the primary
 * creative action in the sheet, matching the dock-tool .add button's
 * accent-tinted visual idiom without copying its flat solid-fill
 * aesthetic.
 *
 * The frame slider ("1/N") lives in the topbar #frame-nav and is visible
 * by default thanks to state.js seeding frame 1 on every fresh board and
 * ui.js updateFrameStepper rendering the stepper whenever frames.length
 * >= 1 (no longer waiting for the user to capture a second frame). */

/* Open animation panel button — three layered CSS keyframe animations:

/* Open animation panel button — three layered CSS keyframe animations:
 *  1. .open-anim-btn itself: breathing border + outer glow ring, 2.4s loop
 *  2. .open-anim-btn-play::before: pulsing halo behind the play disc, 1.6s
 *  3. .open-anim-btn-scrubber: accent-fill wipe across the bottom, 2.4s
 *  4. .open-anim-btn-tape: film-strip pattern sliding horizontally, 3.4s
 * Plus a static film-strip tape pattern behind the scrubber for additional
 * decoration. All animations use the same accent palette so the motion
 * reads as cohesive. Reduced-motion users get a static accent border. */
.open-anim-btn {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 14px 18px;
  margin-top: 9px;
  overflow: hidden;
  border: 1.5px solid rgba(96,165,250,.40);
  border-radius: 14px;
  background:
    linear-gradient(135deg, rgba(96,165,250,.16), rgba(96,165,250,.06));
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  animation: open-anim-btn-breath 2.4s ease-in-out infinite;
  transition: background .15s, border-color .15s, transform .1s;
}
.open-anim-btn:active {
  transform: scale(.985);
  background:
    linear-gradient(135deg, rgba(96,165,250,.28), rgba(96,165,250,.12));
}

@keyframes open-anim-btn-breath {
  0%, 100% {
    border-color: rgba(96,165,250,.38);
    box-shadow: 0 0 0 0 rgba(96,165,250,.18);
  }
  50% {
    border-color: rgba(96,165,250,.68);
    box-shadow: 0 0 0 4px rgba(96,165,250,.10);
  }
}

.open-anim-btn-play {
  flex: none;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 999px;
  background: var(--accent);
  color: #06220f;
  position: relative;
  /* prevent the halo from being clipped by the button's overflow:hidden */
  z-index: 2;
}
.open-anim-btn-play svg {
  width: 16px;
  height: 16px;
  margin-left: 2px; /* optical centering — play triangle has visual mass on the right */
  display: block;
}
/* Pulsing halo — expands + fades behind the play disc. Sits at z above
   the button background but below the icon so it stays visible as a
   ring. position: absolute on the play button is required for inset
   to work (the halo is positioned RELATIVE TO the play disc, not the
   outer button). */
.open-anim-btn-play::before {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 999px;
  border: 2px solid rgba(96,165,250,.70);
  animation: open-anim-btn-halo 1.6s ease-out infinite;
  opacity: 0;
  pointer-events: none;
}
@keyframes open-anim-btn-halo {
  0%   { transform: scale(1);    opacity: .60; }
  100% { transform: scale(1.85); opacity: 0; }
}

.open-anim-btn-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.open-anim-btn-title {
  font-size: 14.5px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: -.005em;
}
.open-anim-btn-sub {
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
}

/* Scrubber — accent fill that wipes L→R across the button bottom. Mirrors
   the playback-hud's #tl-progress bar so the visual language stays
   consistent with timeline scrubbing. Uses transform: scaleX (cheap to
   animate, no layout) with a short opacity envelope at the ends so it
   fades in from zero and out to zero (no abrupt edges). */
.open-anim-btn-scrubber {
  position: absolute;
  left: 6px; right: 6px;
  bottom: 4px;
  height: 2.5px;
  background: var(--accent);
  border-radius: 999px;
  transform-origin: left center;
  z-index: 1;
  animation: open-anim-btn-scrub 2.4s linear infinite;
  opacity: 0;
}
@keyframes open-anim-btn-scrub {
  0%   { transform: scaleX(0);    opacity: 0; }
  15%  { opacity: .9; }
  85%  { opacity: .9; }
  100% { transform: scaleX(1);    opacity: 0; }
}

/* Tape — purely decorative film-strip pattern sliding horizontally. Two
   stacked repeating-gradients give the look of a film-reel sprocket
   pattern. Slides at 3.4s (different period from the scrubber so they
   drift in/out of phase, reinforcing the "moving picture" character). */
.open-anim-btn-tape {
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  height: 6px;
  background-image:
    linear-gradient(to right,
      rgba(96,165,250,.22) 0 6px,
      transparent 6px 12px);
  background-size: 12px 100%;
  background-repeat: repeat-x;
  animation: open-anim-btn-tape 3.4s linear infinite;
  opacity: .75;
  pointer-events: none;
}
@keyframes open-anim-btn-tape {
  from { background-position:   0 0; }
  to   { background-position: 144px 0; }
}

/* On mobile, drop the sub-line to reclaim vertical space; the title
   alone is the primary affordance. */
@media (max-width: 768px), (pointer: coarse) {
  .open-anim-btn-sub { display: none; }
  .open-anim-btn { padding: 11px 12px 14px; }
  .open-anim-btn-title { font-size: 13.5px; }
}

/* Respect prefers-reduced-motion: keep the static accent border + the
   play disc but stop all motion. */
@media (prefers-reduced-motion: reduce) {
  .open-anim-btn,
  .open-anim-btn-play::before,
  .open-anim-btn-scrubber,
  .open-anim-btn-tape {
    animation: none !important;
  }
  .open-anim-btn {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent-dim);
  }
  .open-anim-btn-play::before {
    opacity: 0;
  }
  .open-anim-btn-scrubber {
    opacity: .85;
    transform: scaleX(.65);
  }
}

/* animation tab */
.frame-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 4px 2px 8px;
}
.frame-chip {
  flex: none;
  position: relative;
  width: 58px; height: 58px;
  border-radius: 12px;
  border: 2px solid var(--stroke);
  background: #0e2418;
  color: var(--text);
  font-size: 16px;
  font-weight: 800;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.frame-chip.active { border-color: var(--accent); color: var(--accent); }
.frame-chip .fx {
  position: absolute;
  top: -7px; right: -7px;
  width: 21px; height: 21px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  display: grid;
  place-items: center;
  border: none;
  cursor: pointer;
}
.anim-row { display: flex; gap: 9px; }
.anim-row .wide-btn { flex: 1; margin-top: 0; }
.anim-hint { font-size: 12px; color: var(--muted); line-height: 1.5; margin-top: 10px; }

/* animation board selector strip */
.anim-boards-strip {
  display: flex;
  gap: 7px;
  overflow-x: auto;
  padding: 4px 2px 10px;
  align-items: center;
}
.anim-board-chip {
  flex: none;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 7px 10px 7px 12px;
  border-radius: 12px;
  border: 2px solid var(--stroke);
  background: rgba(255,255,255,.04);
  cursor: pointer;
  transition: border-color .15s, background .15s;
  min-width: 0;
}
.anim-board-chip.active {
  border-color: var(--accent);
  background: var(--accent-dim);
  cursor: default;
}
.anim-board-lbl {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 110px;
}
.anim-board-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  background: transparent;
  border: none;
  outline: none;
  width: 100px;
  min-width: 60px;
  padding: 0;
  caret-color: var(--accent);
}
.anim-board-badge {
  font-size: 10px;
  font-weight: 700;
  color: var(--muted);
  background: rgba(255,255,255,.07);
  border-radius: 999px;
  padding: 2px 6px;
  white-space: nowrap;
  flex-shrink: 0;
}
.anim-board-chip.active .anim-board-badge {
  color: var(--accent);
  background: rgba(96,165,250,.12);
}
.anim-board-del {
  flex-shrink: 0;
  width: 18px; height: 18px;
  border-radius: 999px;
  background: rgba(248, 113, 113, .2);
  color: var(--danger);
  border: none;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 0;
}
.anim-board-del:active { background: rgba(248,113,113,.4); }
.anim-board-add {
  flex-shrink: 0;
  width: 36px; height: 36px;
  border-radius: 12px;
  border: 2px dashed rgba(255,255,255,.2);
  background: transparent;
  color: var(--muted);
  font-size: 20px;
  font-weight: 300;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: border-color .15s, color .15s;
}
.anim-board-add:active { border-color: var(--accent); color: var(--accent); }

/* section title with inline count badge */
.sheet-section-title .anim-frame-count {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  margin-left: 6px;
}

/* toggle chips (onion / templates) */
.tgl-row { display: flex; flex-wrap: wrap; gap: 7px; }
.tgl {
  padding: 8px 13px;
  border-radius: 999px;
  border: 1px solid var(--stroke);
  background: rgba(255,255,255,.04);
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
.tgl.on {
  color: var(--accent);
  border-color: rgba(96,165,250,.5);
  background: var(--accent-dim);
}

/* phase sequence editor */
.phase-seq { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 9px; }
.phase-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 6px 7px 12px;
  border-radius: 10px;
  border: 1px solid var(--stroke);
  background: rgba(255,255,255,.05);
  font-size: 12.5px;
  font-weight: 700;
}
.phase-chip .px {
  border: none;
  background: rgba(255,255,255,.08);
  color: var(--danger);
  width: 20px; height: 20px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 800;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.phase-select {
  flex: 1;
  padding: 11px 12px;
  border-radius: 13px;
  border: 1px solid var(--stroke);
  background: var(--panel-solid);
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
}

/* ---------- Clips tab ---------- */
.clip-list {
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.clip-card {
  border: 1px solid var(--stroke);
  border-radius: 14px;
  background: rgba(255,255,255,.03);
  padding: 11px 12px 10px;
}
.clip-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.clip-card-title { font-size: 14px; font-weight: 800; }
.clip-card-score {
  font-size: 13px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.clip-card-meta {
  font-size: 11.5px;
  color: var(--muted);
  margin: 3px 0 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.clip-tag {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 6px;
  border: 1px solid;
}
.clip-tag-goal { color: var(--accent); border-color: rgba(74,222,128,.5); background: var(--accent-dim); }
.clip-tag-shot { color: #fbbf24; border-color: rgba(251,191,36,.5); background: rgba(251,191,36,.12); }
.clip-tag-approx { color: var(--muted); border-color: var(--stroke); }
.clip-bar-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 5px;
}
.clip-bar-lbl {
  flex: none;
  width: 44px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: .07em;
  color: var(--muted);
  text-transform: uppercase;
}
.clip-bar {
  flex: 1;
  display: flex;
  height: 7px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255,255,255,.06);
}
.clip-bar-seg { height: 100%; }
.clip-bar-val {
  flex: none;
  width: 44px;
  text-align: right;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.clip-card-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}
.clip-btn {
  flex: 1;
  padding: 9px 6px;
  border: 1px solid var(--stroke);
  border-radius: 10px;
  background: rgba(255,255,255,.04);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
}
.clip-btn:active { background: rgba(255,255,255,.09); }
.clip-btn.primary { background: var(--accent); border-color: var(--accent); color: #06220f; }

/* ---------- Toast ---------- */
.toast {
  position: fixed;
  bottom: calc(var(--dock-h) + var(--safe-b) + 16px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 70;
  background: var(--panel-solid);
  border: 1px solid var(--stroke);
  color: var(--text);
  border-radius: 999px;
  padding: 10px 19px;
  font-size: 13.5px;
  font-weight: 600;
  box-shadow: 0 12px 34px rgba(0,0,0,.55);
  white-space: nowrap;
  /* Hard cap so the toast never spills past the viewport on narrow
   * screens — even with white-space:normal + word-wrap below, this
   * keeps the box from being centred at left:50% with half its
   * content off the right edge. 24px = 12px gutter on each side. */
  max-width: calc(100vw - 24px);
  animation: pop .15s ease-out;
  pointer-events: none;
}
/* Mobile / coarse-pointer: the long tracker-tool hint
 * ("Tap a player to apply. Use Erase (or tap the marker again) to
 * remove.") was being clipped on small viewports because the desktop
 * pill uses `white-space: nowrap` and the message is wider than a
 * 360px screen. Switch to wrap + softer radius + centred text so the
 * full sentence stays readable on phones. The max-width above caps
 * the wrapped box; text-align:center keeps the wrapped lines
 * visually balanced. The smaller border-radius is needed because a
 * fully-rounded (999px) pill with multi-line text would have
 * weirdly-oversized end caps relative to the line height. */
@media (max-width: 768px), (pointer: coarse) {
  .toast {
    white-space: normal;
    /* Left-align the wrapped multi-line text — centred wrapping reads
     * worse for a long instruction like the tracker-tool hint because
     * the eye loses the line-start anchor as it scans. The box itself
     * stays centred via `left: 50%` + translateX, so visually the toast
     * is still a centred card; only the text alignment inside changes. */
    text-align: left;
    border-radius: 14px;
    padding: 10px 16px;
    line-height: 1.4;
  }
}

/* Larger screens: center-constrain sheet & dock */
@media (min-width: 640px) {
  .sheet, .dock { max-width: 520px; left: 50%; transform: translateX(-50%); }
  .dock { border-radius: 20px 20px 0 0; border-left: 1px solid var(--stroke); border-right: 1px solid var(--stroke); }
}

/* ========== Tutorial ==========
 * Hand-rolled guided tour of the main UI surfaces. One fixed-position
 * container (.tut-root) owns three siblings:
 *   .tut-spot     — transparent element positioned at the highlighted
 *                   element's rect. Carries the accent pulse ring +
 *                   9999px box-shadow that paints a SOFT navy-tinted
 *                   dim ONLY OUTSIDE the spot's rect, creating the
 *                   cut-out effect without a separate overlay layer.
 *                   pointer-events:none so it never steals the click.
 *   .tut-card     — the floating tooltip. position:fixed, auto-placed by
 *                   the tutorial JS depending on the target's rect.
 *   .tut-bar      — minimal Skip + Step X/N + Back/Next strip pinned
 *                   just under the header so the user never has to hunt
 *                   for it during the tour.
 * z-index: 80 puts the whole tutorial on top of every existing panel
 * (toast is the highest at 70) so the dim + spotlight read as the
 * "front plane". The highlighted target carries .tutorial-pinned which
 * promotes it to z-index 83 so it sits ABOVE the spot's halo and
 * remains fully clickable / visible.
 *
 * The previous design layered a full-screen rgba(0,0,0,0.55) overlay
 * on top of the spot's halo — that double-darkened the surroundings
 * and made the board look dead, which user feedback called "ruining
 * the experience". Removed: the spot's tinted box-shadow alone is
 * enough to draw focus while keeping the board readable. */
.tut-root {
  position: fixed;
  inset: 0;
  z-index: 80;
  pointer-events: none;
  /* Hidden by default; .tut-root.visible flips this on when the tour
   * is mounted. Without .visible, none of the children paint — saves
   * a paint when no tour is active. */
  visibility: hidden;
}
.tut-root.visible { visibility: visible; }

/* Spotlight rectangle. Sits on the body so z-index is not capped by
 * .tut-root's stacking context — the halo's box-shadow paints
 * OUTSIDE the spot's rect at z 82, which beats every parent panel
 * (.dock at 40, .sheet at 50, toast at 70) but stays BELOW the
 * highlighted target (z 83). The 9999px halo paints rgba dim
 * everywhere outside the spot's rect, creating the visual "cutout"
 * without an SVG mask.
 *
 * Color is a deep navy (`rgba(7,11,18, .42)`) rather than pure black
 * so the tinted board still reads as "tactical pad", not "error".
 * The 4px solid accent ring + the 8px breathing halo carry the
 * "look here" weight — the dim is a softener, not a barrier.
 *
 * position:fixed (not absolute) because we're directly under the
 * <body> — the original CSS used absolute INSIDE .tut-root but that
 * capped the effective z to .tut-root's z of 80, allowing promoted
 * parents to paint over the dim halo. As a direct body child we
 * escape that. pointer-events:none means the halo (which is just
 * box-shadow paint — not an event target) doesn't capture clicks;
 * the user can click whatever they want during the tour. */
.tut-spot {
  position: fixed;
  z-index: 82;
  border-radius: 12px;
  pointer-events: none;
  box-shadow:
    0 0 0 9999px rgba(7, 11, 18, 0.42),
    0 0 0 4px rgba(96, 165, 250, 0.95),
    0 0 0 8px rgba(96, 165, 250, 0.18);
  transition: top .22s cubic-bezier(.3,1.1,.4,1),
              left .22s cubic-bezier(.3,1.1,.4,1),
              width .22s cubic-bezier(.3,1.1,.4,1),
              height .22s cubic-bezier(.3,1.1,.4,1),
              border-radius .22s cubic-bezier(.3,1.1,.4,1);
}
/* Accent pulse outline. Stacks a tight 4px solid blue ring with a
 * wider, softer glow that breathes outward — the static halo
 * (9999px tinted dim) stays put, only the soft accent halo animates.
 * On reduced-motion this whole keyframe is dropped (see end of file). */
.tut-spot.pulse {
  animation: tut-pulse 1.8s ease-in-out infinite;
}
@keyframes tut-pulse {
  0%, 100% {
    box-shadow:
      0 0 0 9999px rgba(7, 11, 18, 0.42),
      0 0 0 4px rgba(96, 165, 250, 0.95),
      0 0 0 8px  rgba(96, 165, 250, 0.20);
  }
  50% {
    box-shadow:
      0 0 0 9999px rgba(7, 11, 18, 0.42),
      0 0 0 4px rgba(96, 165, 250, 1),
      0 0 0 20px rgba(96, 165, 250, 0);
  }
}

/* The highlighted element is promoted above the overlay's z-index 80
 * so it visually emerges from the dim layer. We only set z-index here
 * — NEVER position — because overwriting position can break layout
 * for elements that already sit at fixed/absolute (e.g. #board-wrap,
 * .dock, .token-chip, .sheet would collapse to auto height if their
 * `position: fixed` were swapped for `position: relative`).
 *
 * JS conditionally sets an inline `position: relative` on
 * tutorial-pinned elements that are static-positioned (j.tutorial.js
 * `_pinTarget` helper) — that's the case where z-index has no effect
 * on its own and we need to establish a stacking context. */
.tutorial-pinned {
  z-index: 81 !important;
}

/* Tooltip card. position:fixed because the spot's coords are already
 * in viewport space (getBoundingClientRect returns viewport px). The
 * tutorial JS computes left/top/arrow-side per step. */
.tut-card {
  position: fixed;
  z-index: 82;
  pointer-events: auto;
  width: 304px;
  max-width: calc(100vw - 24px);
  background: var(--panel-solid);
  border: 1px solid rgba(96, 165, 250, 0.45);
  border-radius: 16px;
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.6);
  padding: 14px 16px 12px;
  color: var(--text);
  font-size: 13.5px;
  line-height: 1.5;
  animation: tut-card-in .22s ease-out;
}
@keyframes tut-card-in {
  from { opacity: 0; transform: translateY(4px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes tut-bar-in {
  from { opacity: 0; transform: translateX(-50%) translateY(-4px); }
  to   { opacity: 1; transform: translateX(-50%); }
}
.tut-card-title {
  display: block;
  font-size: 14.5px;
  font-weight: 800;
  letter-spacing: -.005em;
  color: var(--accent);
  margin-bottom: 5px;
}
.tut-card-body {
  display: block;
  margin: 0;
}
.tut-card-wait {
  display: block;
  margin-top: 9px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--muted);
  text-transform: uppercase;
}
.tut-card-wait::before {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--accent);
  margin-right: 7px;
  vertical-align: 1px;
  animation: tut-wait-blink 1.4s ease-in-out infinite;
}
.tut-card.ready .tut-card-wait::before { background: #4ade80; animation: none; }
@keyframes tut-wait-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}

/* Arrow on the tooltip card — REMOVED. The little triangle pointing
 * at the spotlight target used to live here; the user asked for it
 * gone entirely. The card now sits next to the target without
 * drawing a pointer. `_positionCard` no longer adds arrow-side
 * classes (`top`, `bottom`, `left`, `right`, `no-arrow`) and the
 * `<span class="tut-card-arrow">` element is omitted from the
 * card's innerHTML. The card's accent border + 4px ring + soft
 * glow carry the visual focus on their own. */

/* Floating bar — pinned at the top, just below the header, so it
 * sits where the user is already looking while interacting with the
 * rest of the screen. Contains Skip (left), step counter (center),
 * Back+Next (right). pointer-events:auto so the buttons stay
 * clickable through the dark overlay. Anchored to the account for
 * the topbar height (56px) + iOS notch safe-area inset. */
.tut-bar {
  position: fixed;
  z-index: 82;
  pointer-events: auto;
  top: calc(56px + env(safe-area-inset-top, 0px) + 8px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--panel-solid);
  border: 1px solid rgba(96, 165, 250, 0.45);
  border-radius: 999px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.6);
  white-space: nowrap;
  animation: tut-bar-in .22s ease-out;
  font-size: 12.5px;
  font-weight: 700;
}
.tut-bar-skip {
  border: none;
  background: transparent;
  color: var(--danger);
  padding: 6px 10px;
  border-radius: 999px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tut-bar-skip:active { background: rgba(248, 113, 113, .18); }
.tut-bar-counter {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: .03em;
  padding: 0 4px;
  min-width: 64px;
  text-align: center;
}
.tut-bar-counter strong {
  color: var(--accent);
  font-weight: 800;
}
.tut-bar-spacer {
  width: 1px; height: 18px;
  background: var(--stroke);
}
.tut-bar-btn {
  border: 1px solid var(--stroke);
  background: rgba(255, 255, 255, .06);
  color: var(--text);
  padding: 6px 14px;
  border-radius: 999px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background .15s, opacity .15s;
}
.tut-bar-btn:active { background: rgba(255, 255, 255, .12); }
.tut-bar-btn:disabled {
  opacity: .35;
  cursor: not-allowed;
}
.tut-bar-btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #06220f;
}
.tut-bar-btn.primary:active { background: #93c5fd; }
.tut-bar-btn.primary:disabled { background: rgba(96, 165, 250, .25); color: rgba(6, 34, 15, .5); }
.tut-bar-end .tut-bar-btn.primary:not(:disabled) {
  /* End step: Next swaps to a slightly more emphatic accent (it's the
   * closing action). Same accent — only the label changes via JS. */
  background: #4ade80;
  border-color: #4ade80;
  color: #06220f;
}

/* Reduced-motion: drop all anims + the pulse ring (just a static accent
 * outline). Spotlight + card snap into place. */
@media (prefers-reduced-motion: reduce) {
  .tut-spot.pulse { animation: none; }
  .tut-card,
  .tut-bar { animation: none; }
  .tut-card { opacity: 1; transform: none; }
  .tut-card-wait::before { animation: none; }
}

/* Highlighted target lives above the spot halo so the user can click
 * it + see it clearly. The spot halo's box-shadow is the dim layer
 * (it paints 9999px tinted-dark outward from the spot's rect); the
 * halo sits at z 82, the target at z 83. Both visualise as a rounded
 * rectangle (matching the target's border-radius) with an accent ring
 * pulsing around the cutout. */
