/* ============================================================================
   tactical-build.css — Tactical Build stylesheet (spatial SC2 build editor).
   Source prototype: handover/tactical-build.css; scoped under .tb-root and
   restyled to author over the live replay analyst canvas.

   Scoping: the prototype assumed it owned the page (global html,body{overflow}
   + #app{100vw/100vh}). Those global rules are REMOVED. Everything is scoped
   under a single `.tb-root` container so nothing leaks into the rest of the app.
   The prototype's former `.tb` element rules now live on `.tb-root`.

   Tokens: the prototype's :root-style local custom props (--bg, --accent, …)
   are deduped against styles/tokens.css — they are RE-MAPPED to the canonical
   --color-* / --font-* / --space-* tokens on the .tb-root block below, so every
   downstream `var(--bg)` etc. resolves to the shared token. Colors with no clean
   token equivalent are flagged inline with a TODO-token note (CSS comments do
   not nest, so this header must not contain a literal close-comment marker).

   Fonts: Rajdhani / Inter / JetBrains Mono are loaded globally by the app
   (see styles/tokens.css --font-* + index.html). No @import here (self-contained,
   no external font fetch — the prototype's Google-Fonts @import is dropped).

   Responsive: canonical breakpoints 480/768/1024. A @media (max-width:767px)
   block at the bottom gives `.tb-modal` a bottom-sheet treatment (mirrors the
   frontend's `.u-sheet-on-mobile` utility, kept self-contained here).
   ============================================================================ */

.tb-root {
  /* Local prototype custom props re-mapped onto the shared design tokens.
     Downstream .tb-* rules reference these short names unchanged. */
  --bg: var(--color-bg);                 /* was #0a0d14 */
  --raised: var(--color-bg-raised);      /* was #0f131c */
  --elevated: var(--color-bg-elevated);  /* was #141821 */
  --overlay: var(--color-bg-overlay);    /* was #1c212c */
  --overlay2: var(--color-bg-overlay-2); /* was #242b38 */
  --border: var(--color-border);             /* was rgba(255,255,255,.08) */
  --border-strong: var(--color-border-strong); /* was rgba(255,255,255,.16) */
  --text: var(--color-text);             /* was #e6e9ef */
  --muted: var(--color-text-muted);      /* was #aab3c2 */
  --subtle: var(--color-text-subtle);    /* was #6e7687 */
  --accent: var(--color-accent);         /* was #56c2ff */
  --min: var(--color-accent);            /* mineral cyan == accent (#56c2ff) */
  --gas: var(--color-success);           /* was #4ade80 */
  --warn: var(--color-warning);          /* was #fbbf24 */
  --danger: var(--color-danger);         /* was #f87171 */
  --eco: var(--color-accent);            /* economy cyan == accent (#56c2ff) */
  --prod: #f7a23b;                       /* TODO token — production orange, no clean token (close to --color-cta #ff7a00 but distinct) */
  --tech: var(--color-brand-2);          /* was #a78bfa */
  --p1: var(--color-player-1);           /* was #ef5a6f */
  --p2: var(--color-player-2);           /* was #4b9eff */
  --f-display: var(--font-display);
  --f-ui: var(--font-ui);
  --f-mono: var(--font-mono);
  /* Analyst glass language — the defining trait shared with the replay viewer.
     --tb-glass* are the floating-PANEL values (palette / build-order / dock);
     --tb-pill* are the corner-control PILL values (home / action buttons). */
  --tb-glass-bg: rgba(15, 19, 28, 0.85);
  --tb-glass-border: rgba(255, 255, 255, 0.14);
  --tb-glass-radius: 10px;
  --tb-glass-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.6);
  --tb-glass-blur: 16px;
  --tb-pill-bg: rgba(15, 19, 28, 0.92);
  --tb-pill-border: rgba(255, 255, 255, 0.12);
  --tb-pill-blur: 10px;
  position: relative; display: flex; flex-direction: column; width: 100%; height: 100%;
  /* The map stage is the full-bleed backdrop — the root no longer paints a
     solid column-shell background that would hide it. */
  background: transparent; color: var(--text); font-family: var(--f-ui); overflow: hidden;
}
.tb-root *, .tb-root *::before, .tb-root *::after { box-sizing: border-box; }
/* Keyboard focus ring on the editor's interactive controls — the rest of the app
   defines :focus-visible app-wide; the ported editor was missing it (the default
   UA outline is invisible against the dark glass). Scoped to .tb-root. */
.tb-root :is(button, [role="button"], .tb-side-tab, .tb-race, .tb-branch, .tb-filter, .tb-pal-tile, .tb-add-one, .tb-add-rep, .tb-chrono):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── topbar: a transparent FLOATING cluster of glass pills/groups over the map,
   NOT a solid bar. Positioned absolutely so the map stage reads edge-to-edge
   behind it; pointer-events pass through the gaps to the stage for placing. */
.tb-topbar { position: absolute; top: 12px; left: 12px; right: 12px; z-index: 10;
  display: flex; align-items: center; gap: 10px; height: auto; padding: 0;
  background: transparent; border: none; pointer-events: none; flex-wrap: wrap; row-gap: 8px; }
.tb-topbar > * { pointer-events: auto; }
/* Three logical groups separated by flexible spacers:
     left  = identity (Builds) + race switch + map selector
     center= build clock + economy bar
     right = action pills (.tb-actions)
   Each group is itself a flex row so its children stay vertically centered and
   keep consistent gaps; the spacers between groups absorb slack on wide screens
   and collapse first when width gets tight. */
.tb-topbar-cluster { display: inline-flex; align-items: center; gap: 8px; }
.tb-group-left { flex-wrap: wrap; row-gap: 8px; min-width: 0; }
.tb-topbar-center { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; row-gap: 8px; min-width: 0; justify-content: center; }
/* Standalone topbar: give the center group a ZERO flex-basis (flex:1000 1 0) so the
   flex line-break algorithm — which sizes by hypothetical/base width, NOT shrunk
   width — sees it as ~0 and never pushes the action pills (+New / File) onto a
   wrapped, left-aligned 2nd row when the eco-bar is wide. It still GROWS to fill the
   slack, and the eco-bar reflows within it. (The mobile rule below resets to 100%.) */
.tb-root.tb-standalone .tb-topbar-center { flex: 1000 1 0; }
.tb-topbar-center:empty { display: none; }
.tb-home { display: inline-flex; align-items: center; gap: 6px; font-family: var(--f-display); letter-spacing: .04em;
  text-transform: uppercase; font-size: 11px; font-weight: 700; color: var(--accent);
  background: var(--tb-pill-bg); -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--tb-pill-border)); border-radius: 6px; padding: 6px 12px; cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease; }
.tb-home:hover { background: rgba(86,194,255,.18); border-color: rgba(86,194,255,.45); color: #fff; }

/* race switch — reuses the analyst segmented control (.replay-viewmode-group/-btn
   in replay.css). We only add the glass backing + the per-race accent on .active
   (the .tb-race* hooks stay for JS). The .replay-viewmode-btn rule supplies the
   base look (Rajdhani uppercase micro-label). */
.tb-raceswitch.replay-viewmode-group { background: var(--tb-pill-bg);
  -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); }
/* The label markup is `T<span>erran</span>` (the leading letter is a bare text
   node). gap:0 + matching font/size makes the first letter butt straight up
   against the span so it reads as one word ("Terran", not "T erran"). */
.tb-race { display: inline-flex; align-items: center; gap: 0; white-space: nowrap; }
.tb-race span { font-size: inherit; letter-spacing: inherit; }
/* Per-race tint overrides the segmented control's generic cyan .active state. */
.tb-race.replay-viewmode-btn.active { background: color-mix(in srgb, var(--rc) 20%, transparent); color: var(--rc);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--rc) 45%, transparent); }
/* Opponent / matchup selector — mirrors the race switch (compact, single-letter)
   so "<your race>  vs  T Z P" reads as the matchup. Always present (it drives the
   corpus build-timing medians), independent of heatmap-data availability. */
.tb-vs-lbl { align-self: center; font-size: 10px; font-weight: 700; letter-spacing: .06em; opacity: .5; padding: 0 4px; }
.tb-oppswitch.replay-viewmode-group { background: var(--tb-pill-bg);
  -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); }
.tb-opp { white-space: nowrap; min-width: 0; padding: 0 9px; }
.tb-opp.replay-viewmode-btn.active { background: color-mix(in srgb, var(--rc) 20%, transparent); color: var(--rc);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--rc) 45%, transparent); }

/* title cluster — a single glass pill carrying the map selector, build badge
   and the player line, so it floats as one chip rather than bare text. */
.tb-title.tb-topbar-cluster { flex-direction: column; align-items: flex-start; gap: 3px; flex-shrink: 1; min-width: 0;
  background: var(--tb-pill-bg); -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); border-radius: 6px; padding: 5px 10px; }
.tb-title-line { display: inline-flex; align-items: center; gap: 8px; }
.tb-map { font-family: var(--f-display); font-size: 15px; letter-spacing: .04em; font-weight: 600; }
/* The `.tb-mapsel` map picker is DELETED — the map is fixed to the live
   replay's map; there is no editor map to choose. */
.tb-badge { font-family: var(--f-display); font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 4px; white-space: nowrap;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); letter-spacing: .08em; text-transform: uppercase; }
.tb-players { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--muted); }
.tb-pdot { width: 8px; height: 8px; border-radius: 50%; } .tb-pdot-1 { background: var(--p1); } .tb-pdot-2 { background: var(--p2); }
.tb-vs { color: var(--subtle); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; padding: 0 2px; }
.tb-spacer { flex: 1 1 0%; min-width: 0; }
/* The right-side action cluster: a single tidy row of glass pills that wraps as
   a unit (right-aligned) only when the topbar genuinely runs out of width. */
.tb-actions { gap: 6px; flex-wrap: wrap; justify-content: flex-end; align-content: center; row-gap: 6px; }
/* Action pills (Save / My Builds / Import / Export / Reset) — the glass-pill spec. */
.tb-ghostbtn { background: var(--tb-pill-bg); -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); color: var(--text); font-family: var(--f-display); font-size: 11px; font-weight: 700;
  padding: 6px 11px; border-radius: 6px; letter-spacing: .04em; text-transform: uppercase; cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease; }
.tb-ghostbtn:hover { background: rgba(86,194,255,.18); border-color: rgba(86,194,255,.45); color: #fff; }

/* body — neutralized so its children position independently as overlays. */
.tb-body { display: contents; }

/* palette — glassy floating card on the LEFT, clear of the map center. */
.tb-palette { position: absolute; left: 12px; top: 64px; bottom: 96px; width: 200px; z-index: 8;
  background: var(--tb-glass-bg); -webkit-backdrop-filter: blur(var(--tb-glass-blur)); backdrop-filter: blur(var(--tb-glass-blur));
  border: 1px solid var(--tb-glass-border); border-radius: var(--tb-glass-radius); box-shadow: var(--tb-glass-shadow);
  display: flex; flex-direction: column; overflow: hidden; }
.tb-pal-head { padding: 12px 14px 6px; flex-shrink: 0; position: relative; }
.tb-pal-title { font-family: var(--f-display); font-size: 12px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); cursor: help; }
/* The ⓘ cues that hovering the title reveals the (otherwise hidden) hint. */
.tb-pal-title::after { content: '\00a0ⓘ'; font-weight: 400; opacity: .4; }
.tb-pal-titlebar:hover .tb-pal-title::after { opacity: .75; }
/* Palette hint is HIDDEN by default to declutter; it reveals as a popover under the
   header when the BUILD PALETTE title row is hovered. (Mobile keeps it hidden — no
   hover — via the existing ≤767px rule.) */
.tb-pal-hint { display: none; font-size: 11px; color: var(--subtle); line-height: 1.4; }
.tb-pal-hint b { color: var(--accent); }
.tb-pal-titlebar:hover ~ .tb-pal-hint {
  display: block; position: absolute; top: calc(100% - 2px); left: 12px; right: 12px; z-index: 30;
  padding: 9px 11px; background: var(--raised, #131722); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 10px 28px rgba(0,0,0,.55);
}
.tb-pal-scroll { flex: 1; overflow: auto; padding: 6px 10px 14px; }
.tb-pal-group { font-family: var(--f-display); font-size: 10px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; color: var(--subtle); margin: 12px 4px 6px; }
.tb-pal-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.tb-pal-tile { position: relative; display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 4px 6px;
  background: var(--elevated); border: 1px solid var(--border); border-radius: 7px; cursor: pointer; transition: border-color .12s, transform .12s, background .12s;
  touch-action: none; }   /* PointerEvents drag-to-canvas: don't let a finger drag scroll/zoom the palette */
.tb-pal-tile:hover { border-color: var(--accent); transform: translateY(-1px); background: var(--overlay); }
.tb-pal-tile img { width: 30px; height: 30px; border-radius: 5px; border: 1px solid rgba(0,0,0,.4); }
.tb-pal-name { font-size: 10px; font-weight: 600; text-align: center; line-height: 1.1; }
.tb-pal-cost { font-family: var(--f-mono); font-size: 9px; color: var(--min); }
.tb-pal-cost .g { color: var(--gas); }
.tb-pal-tile.locked { opacity: .4; cursor: not-allowed; filter: grayscale(.6); }
.tb-pal-lock { position: absolute; top: 4px; right: 5px; font-size: 11px; }
.tb-pal-req { position: absolute; bottom: -3px; left: 0; right: 0; font-size: 7.5px; font-family: var(--f-mono); color: var(--warn); text-align: center; opacity: 0; }
.tb-pal-tile.locked:hover .tb-pal-req { opacity: 1; }
/* Phase 5: producer tiles carry a tiny ⚑ corner badge hinting at the right-click
   "set type-default rally" affordance. Top-LEFT so it never collides with the
   top-right 🔒 lock badge on a locked-but-producer tile. Brightens on hover. */
.tb-pal-rally { position: absolute; top: 3px; left: 4px; font-size: 9px; line-height: 1; color: var(--accent); opacity: .5; pointer-events: none; }
.tb-pal-tile.tb-can-rally:hover .tb-pal-rally { opacity: 1; }
/* ── Palette: title bar + condensed toggle + tabs + condensed (icons-only) view ── */
.tb-pal-titlebar { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tb-pal-condense { flex-shrink: 0; width: 24px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1; color: var(--subtle); background: var(--elevated); border: 1px solid var(--border);
  border-radius: 6px; cursor: pointer; transition: color .12s ease, border-color .12s ease, background .12s ease; }
.tb-pal-condense:hover { color: var(--text); border-color: var(--border-strong); }
.tb-pal-condense.on { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, var(--elevated)); }
.tb-pal-tabs { display: flex; gap: 2px; margin-top: 8px; border-bottom: 1px solid var(--border); }
.tb-pal-tab { flex: 1; padding: 5px 3px; font-family: var(--f-display); font-size: 9.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--subtle); background: transparent; border: none; border-bottom: 2px solid transparent;
  margin-bottom: -1px; cursor: pointer; transition: color .12s ease, border-color .12s ease; }
.tb-pal-tab:hover { color: var(--text); }
.tb-pal-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.tb-pal-tabpanel { display: none; }
.tb-pal-tabpanel.active { display: block; }
.tb-pal-tabpanel > .tb-pal-group:first-child { margin-top: 4px; }
/* Condensed (icons-only) view: drop the name + cost, tighten to a 3-up icon grid. */
.tb-pal-condensed .tb-pal-name, .tb-pal-condensed .tb-pal-cost { display: none; }
.tb-pal-condensed .tb-pal-grid { grid-template-columns: repeat(3, 1fr); gap: 5px; }
.tb-pal-condensed .tb-pal-tile { padding: 6px 3px; }
.tb-pal-condensed .tb-pal-tile img { width: 30px; height: 30px; }
.tb-pal-condensed .tb-pal-tactics { grid-template-columns: repeat(3, 1fr); }
.tb-pal-condensed .tb-pal-tactics .tb-tactic { flex-direction: column; gap: 2px; padding: 8px 3px; justify-content: center; }

/* ── Drag-drop palette → canvas (PointerEvents) ──────────────────────────────
   The floating ghost chip that tracks the pointer while dragging a palette item
   onto the canvas. position:fixed + pointer-events:none so it follows the cursor
   anywhere (incl. over the canvas) and never intercepts the drop. */
.tb-pal-drag-ghost {
  position: fixed; pointer-events: none; z-index: 9999;
  background: var(--elevated); border: 1px solid var(--accent);
  border-radius: 8px; padding: 6px 10px;
  display: flex; gap: 8px; align-items: center;
  font-size: 11px; font-weight: 600; color: var(--text, #e6edf3);
  box-shadow: 0 8px 24px rgba(0,0,0,.6);
  opacity: .88;
  transform: translate3d(0,0,0);   /* own GPU layer — no jitter on mobile */
}
.tb-pal-drag-ghost img { width: 24px; height: 24px; border-radius: 4px; }

/* Auto-time status pill: confirms the computed start time after a drop, or (warn
   variant) explains why an auto-time failed. Scoped to the editor (.tb-root),
   centered near the bottom of the panel. */
.tb-auto-toast {
  position: absolute; bottom: 60px; left: 50%; transform: translateX(-50%);
  background: color-mix(in srgb, var(--accent) 18%, var(--elevated));
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  color: var(--accent); padding: 5px 14px; border-radius: 20px;
  font-size: 11px; font-weight: 600; white-space: nowrap;
  z-index: 20; pointer-events: none;
  animation: tb-toast-fade 2.2s ease forwards;
}
.tb-auto-toast--warn {
  background: color-mix(in srgb, var(--danger) 16%, var(--elevated));
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
  color: var(--danger);
  animation-duration: 3.5s;
}
@keyframes tb-toast-fade { 0% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }

/* "auto" badge on a build-order row whose time was computed (not hand-set). */
.tb-auto-badge {
  display: inline-block; font-size: 8px; font-family: var(--f-mono);
  font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-radius: 3px; padding: 1px 4px; margin-left: 4px; vertical-align: middle;
}

/* NOTE: the editor's OWN map stage is gone — it now authors over the live
   replay canvas. The former `.tb-stage` / `.tb-mapwrap` / `.tb-map-img`
   full-bleed backdrop + its map-decoration overlays (`.tb-map-vignette`,
   `.tb-fields`, `.tb-power`, `.tb-creep`, `.tb-paths`, `.tb-path`) and the
   building PINS (`.tb-pins` / `.tb-pin*`, `.tb-rally`, `.tb-army`, `.tb-ghost`)
   were the rules that produced the letterboxed-map cover — all DELETED.
   Building markers now draw on the canvas via the renderer's overlay API. */

/* floating filter pills — reuse the analyst segmented control
   (.replay-viewmode-group/-btn). Only the glass backing + float position are
   set here; .tb-filter* hooks stay for the JS click handler. The pills float
   over the map top-center, clear of the topbar clusters (top:56px). */
/* The filter pills live INSIDE the build-order panel header (not floating over the
   map any more), so they must be in normal flow — absolute positioning made them
   overlap the economy HUD. Centered compact segmented group between header + list. */
.tb-filters.replay-viewmode-group { position: static; transform: none; left: auto; top: auto; z-index: auto;
  align-self: center; margin: 6px auto 10px; flex-wrap: wrap; justify-content: center;
  background: var(--tb-pill-bg); -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); box-shadow: 0 6px 20px rgba(0,0,0,.5); }

/* Pulse keyframe — formerly used by the deleted building pins; still referenced
   by the in-panel production strip (`.tb-ps-blk.building`), so it is kept. */
@keyframes tb-pulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent), 0 4px 14px rgba(0,0,0,.6);} 70% { box-shadow: 0 0 0 11px transparent, 0 4px 14px rgba(0,0,0,.6);} 100% { box-shadow: 0 0 0 0 transparent, 0 4px 14px rgba(0,0,0,.6);} }

/* sidebar (Build Order panel) — glassy floating card on the RIGHT. Restyled to
   the same glass-panel treatment as the analyst FloatCard (rather than wrapping
   in a real createFloatCard, which would force re-wiring the tab strip/scroll
   that bind() queries by class). The tab strip becomes the card header. */
.tb-side { position: absolute; right: 12px; top: 64px; bottom: 96px; width: 300px; z-index: 8;
  background: var(--tb-glass-bg); -webkit-backdrop-filter: blur(var(--tb-glass-blur)); backdrop-filter: blur(var(--tb-glass-blur));
  border: 1px solid var(--tb-glass-border); border-radius: var(--tb-glass-radius); box-shadow: var(--tb-glass-shadow);
  display: flex; flex-direction: column; overflow: hidden; }
.tb-side-tabs { display: flex; background: rgba(255,255,255,.03); border-bottom: 1px solid var(--border); flex-shrink: 0; }
.tb-side-tab { flex: 1; padding: 11px 0; text-align: center; font-family: var(--f-display); font-size: 11px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--subtle); cursor: pointer; border-bottom: 2px solid transparent;
  transition: color 120ms ease, border-color 120ms ease; }
.tb-side-tab:hover { color: var(--muted); }
.tb-side-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.tb-side-head { display: flex; flex-direction: column; flex-shrink: 0; }
/* View toggle — declutters the build column by hiding worker rows. Economy is
   unaffected (sim always counts workers); this is purely a display filter. */
.tb-workers-toggle { align-self: flex-end; margin: 6px 8px 2px; display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--f-ui); font-size: 11px; font-weight: 600; color: var(--muted); background: var(--elevated);
  border: 1px solid var(--border); border-radius: 6px; padding: 5px 10px; cursor: pointer; white-space: nowrap;
  transition: color 120ms ease, border-color 120ms ease; }
.tb-workers-toggle:hover { color: var(--text); border-color: var(--border-strong); }
.tb-workers-toggle.on { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.tb-side-scroll { flex: 1; overflow: auto; padding: 6px; }
.tb-row { position: relative; display: flex; align-items: center; gap: 9px; padding: 7px 8px; border-radius: 6px; cursor: pointer; border: 1px solid transparent; }
.tb-row:hover { background: var(--elevated); }
.tb-row-grip { color: var(--subtle); font-size: 11px; cursor: grab; opacity: 0; flex-shrink: 0; margin: 0 -4px 0 -2px; letter-spacing: -2px; }
.tb-row:hover .tb-row-grip { opacity: .7; }
.tb-row.dragging { opacity: .4; }
.tb-row.dropt { box-shadow: inset 0 2px 0 var(--accent); }
.tb-row.now { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
.tb-row.sel { background: var(--overlay); border-color: var(--accent); }
/* invalid step (missing prerequisite / supply-blocked / unaffordable — see
   _stepIssues). Red frame + tint so a broken build reads at a glance; the ⚠ in
   the name carries the reason as a tooltip. */
.tb-row.warn { border-color: color-mix(in srgb, var(--danger) 45%, transparent); background: color-mix(in srgb, var(--danger) 9%, transparent); }
.tb-row.warn .tb-row-name { color: var(--danger); }
.tb-row.upg .tb-row-ic { border-color: var(--tech); }
.tb-row.upg .tb-row-name { color: var(--tech); }
.tb-row-seq { font-family: var(--f-mono); font-size: 10px; color: var(--subtle); width: 16px; text-align: right; flex-shrink: 0; }
.tb-row-ic { width: 26px; height: 26px; border-radius: 5px; border: 1px solid var(--border); flex-shrink: 0; }
.tb-row-main { flex: 1; min-width: 0; }
.tb-row-name { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Chrono-boost marker on a chrono'd building / research row — so chrono is visible
   in the build list, not only in the production strip + modal. */
.tb-row-chrono { color: var(--accent); font-size: 11px; }
/* Auto-worker virtual rows — generated continuous worker production, interleaved by
   time. Dimmed + non-interactive so they read as background macro, distinct from the
   buildings/units you placed. No grip/actions (they're derived, not editable steps). */
.tb-row-auto { opacity: .5; cursor: default; }
.tb-row-auto:hover { background: transparent; opacity: .8; }
.tb-row-auto .tb-row-ic { border-style: dashed; }
/* Auto-worker toggle (Workers palette group) — an iOS-style switch. */
.tb-aw-toggle { display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px 10px; margin-bottom: 6px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; color: var(--muted); text-align: left; transition: border-color .12s ease, color .12s ease; }
.tb-aw-toggle:hover { border-color: var(--border-strong); color: var(--text); }
.tb-aw-toggle.on { border-color: color-mix(in srgb, var(--accent) 55%, transparent); color: var(--text); }
.tb-aw-switch { flex-shrink: 0; width: 30px; height: 17px; border-radius: 9px; background: var(--border); position: relative; transition: background .12s ease; }
.tb-aw-toggle.on .tb-aw-switch { background: var(--accent); }
.tb-aw-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px; border-radius: 50%; background: #fff; transition: transform .12s ease; }
.tb-aw-toggle.on .tb-aw-switch::after { transform: translateX(13px); }
.tb-aw-label { display: flex; flex-direction: column; font-size: 12px; font-weight: 600; line-height: 1.25; }
.tb-aw-label small { font-weight: 400; font-size: 10px; color: var(--subtle); margin-top: 1px; }
.tb-row-cost { font-family: var(--f-mono); font-size: 10px; color: var(--min); }
.tb-row-cost .g { color: var(--gas); }
.tb-row-time { font-family: var(--f-mono); font-size: 11px; color: var(--muted); text-align: right; flex-shrink: 0; }
.tb-row-sup { display: block; font-size: 9px; color: var(--prod); }
/* lifecycle sub-line — morphs after the first (⟳ Orbital Command 1:54), add-on
   swaps (⇄), flights (✈). Was UNSTYLED, so a long entry rendered at the 16px
   browser default and wrapped/overlapped the time + supply column. Match the row's
   mono sub-text scale and let multiple entries wrap as tidy chips, not a block. */
.tb-row-lc { display: flex; flex-wrap: wrap; align-items: center; gap: 1px 8px; margin-top: 2px; }
.tb-row-lc-item { font-family: var(--f-mono); font-size: 9px; line-height: 1.35; color: var(--subtle); white-space: nowrap; }
/* in-place morph rows (⟳ Orbital Command) — a building's own upgrade event,
   sequenced at its morph time. Accent-tinted so it reads as a transform of the
   building, distinct from a fresh placement. */
.tb-row-morph .tb-row-ic { border-color: var(--accent); }
.tb-row-morph .tb-row-name { color: var(--accent); }
.tb-row-morph-tag { font-family: var(--f-display); font-size: 8px; letter-spacing: .12em; text-transform: uppercase; color: var(--subtle); }
/* per-row delete affordance — the clickable × to remove a placed step. Reveals
   on row hover / when selected (the row is the click-to-select target). */
.tb-row-del { flex-shrink: 0; width: 22px; height: 22px; padding: 0; border: 1px solid var(--border); background: transparent; color: var(--subtle); border-radius: 5px; font-size: 14px; line-height: 1; cursor: pointer; opacity: 0; transition: opacity .12s ease, color .12s ease, border-color .12s ease; }
.tb-row:hover .tb-row-del, .tb-row.sel .tb-row-del { opacity: 1; }
.tb-row-del:hover { color: var(--danger); border-color: var(--danger); }
/* per-row edit (✎ step time/qty — Fix #3) + fork (⑂ branch from here — Fix #4)
   affordances. Same reveal-on-hover/select treatment as the delete ×; the fork
   tints to the accent, the edit to text. */
.tb-row-edit, .tb-row-fork, .tb-row-prod { flex-shrink: 0; width: 22px; height: 22px; padding: 0; border: 1px solid var(--border); background: transparent; color: var(--subtle); border-radius: 5px; font-size: 12px; line-height: 1; cursor: pointer; opacity: 0; transition: opacity .12s ease, color .12s ease, border-color .12s ease; }
.tb-row:hover .tb-row-edit, .tb-row.sel .tb-row-edit,
.tb-row:hover .tb-row-fork, .tb-row.sel .tb-row-fork,
.tb-row:hover .tb-row-prod, .tb-row.sel .tb-row-prod { opacity: 1; }
.tb-row-edit:hover { color: var(--text); border-color: var(--border-strong); }
.tb-row-fork:hover { color: var(--accent); border-color: var(--accent); }
.tb-row-prod:hover { color: var(--accent); border-color: var(--accent); }
/* Action cluster (⊕ ⑂ ✎ ×) is ABSOLUTELY positioned over the row's right edge and
   revealed on hover/select, so it occupies ZERO flex width in the resting row. That
   frees the whole row for the name+time+supply — previously these 3-4 fixed-width
   buttons (flex-shrink:0, ~22px each) reserved their space even at opacity:0, which
   collapsed the flex name (.tb-row-main, min-width:0) to ~0px on PRODUCER rows
   (Gateway/Nexus/Cyber got a ⊕ too), rendering "Nexus"→"N.", "Gateway"→blank. The
   per-button opacity rules above still fade them in; this container adds the layout
   fix + a gradient scrim so the buttons stay legible over the time/supply they cover
   on hover. Inherits the resting-row bg at the fade's start so the scrim is seamless. */
.tb-row-actions { position: absolute; right: 4px; top: 0; bottom: 0; display: flex; align-items: center; gap: 4px; padding-left: 20px; opacity: 0; pointer-events: none; transition: opacity .12s ease; background: linear-gradient(to right, transparent, var(--elevated) 45%); border-radius: 0 6px 6px 0; }
.tb-row:hover .tb-row-actions, .tb-row.sel .tb-row-actions { opacity: 1; pointer-events: auto; }
.tb-row.sel .tb-row-actions { background: linear-gradient(to right, transparent, var(--overlay) 45%); }
/* step-editor qty stepper — mirrors the action-modal unit chip buttons. */
.tb-step-qty { display: inline-flex; align-items: center; gap: 8px; }
.tb-step-qty .tb-uc-n { min-width: 20px; text-align: center; font-family: var(--f-mono); }

/* NOTE: the editor's OWN bottom scrubber/dock is gone — the live replay's
   analyst dock/scrubber drives time now (via opts.deps.seekTo/getCurrentTime).
   The former `.tb-scrub` floating dock, its `.tb-play` button, the
   `.tb-scrub-mid` / `.tb-scrub-clock*` clock, the dock-rail `.tb-track*`,
   the `.tb-tick*` step ticks, the `.tb-phead` playhead and the
   `.tb-scrub-hud` wrapper are all DELETED. The resource-chip styling
   (`.tb-hud-res` + `.tb-hud-*`) is KEPT — the economy HUD relocates into the
   build-order panel header and reuses these chip rules. */
.tb-hud-res { display: flex; align-items: center; gap: 6px; background: rgba(255,255,255,.04); border: 1px solid var(--border); border-radius: 7px; padding: 6px 10px; min-width: 78px; }
.tb-hud-res img { width: 18px; height: 18px; border-radius: 3px; }
.tb-hud-ic { font-size: 14px; color: var(--warn); width: 18px; text-align: center; }
.tb-hud-v { font-family: var(--f-mono); font-weight: 700; font-size: 15px; font-variant-numeric: tabular-nums; }
.tb-hud-sub { font-family: var(--f-mono); font-size: 9px; color: var(--subtle); margin-left: auto; font-variant-numeric: tabular-nums; }
.tb-hud-res.min .tb-hud-v { color: var(--min); } .tb-hud-res.gas .tb-hud-v { color: var(--gas); } .tb-hud-res.sup .tb-hud-v { color: var(--warn); } .tb-hud-res.wk .tb-hud-v { color: var(--text); }

/* ── production-queue modal ─────────────────────────────────────────── */
.tb-modal-backdrop { position: absolute; inset: 0; z-index: var(--z-modal); background: color-mix(in srgb, var(--color-bg) 62%, transparent); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center; animation: tb-fade .14s ease; }
@keyframes tb-fade { from { opacity: 0; } to { opacity: 1; } }
.tb-modal { width: 470px; max-width: 92%; max-height: 86%; background: var(--raised); border: 1px solid var(--border-strong);
  border-radius: 12px; box-shadow: 0 24px 64px rgba(0,0,0,.7); display: flex; flex-direction: column; overflow: hidden;
  animation: tb-pop .16s cubic-bezier(.2,.8,.25,1); }
@keyframes tb-pop { from { transform: translateY(8px) scale(.98); opacity: 0; } to { transform: none; opacity: 1; } }
.tb-modal-backdrop.tb-noanim, .tb-modal-backdrop.tb-noanim .tb-modal { animation: none; }
.tb-modal-head { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--border); background: var(--elevated); }
.tb-modal-ic { width: 40px; height: 40px; border-radius: 8px; border: 2px solid var(--accent); }
.tb-modal-title { flex: 1; min-width: 0; }
.tb-modal-name { font-family: var(--f-display); font-size: 18px; font-weight: 700; letter-spacing: .03em; }
.tb-modal-sub { font-family: var(--f-mono); font-size: 11px; color: var(--subtle); margin-top: 2px; }
.tb-reactor { font-size: 11px; color: var(--muted); display: flex; align-items: center; gap: 5px; cursor: pointer; user-select: none; }
.tb-modal-x { width: 30px; height: 30px; border-radius: 6px; border: 1px solid var(--border); background: transparent; color: var(--muted); font-size: 18px; cursor: pointer; line-height: 1; }
.tb-modal-x:hover { color: var(--text); border-color: var(--border-strong); }
.tb-modal-body { padding: 12px 16px; overflow: auto; }
.tb-modal-sect { font-family: var(--f-display); font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--subtle); margin: 4px 0 8px; }
.tb-q-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.tb-q-row { display: flex; align-items: center; gap: 10px; background: var(--elevated); border: 1px solid var(--border); border-radius: 8px; padding: 7px 10px; }
.tb-q-row img { width: 26px; height: 26px; border-radius: 5px; }
.tb-q-main { flex: 1; min-width: 0; }
.tb-q-name { font-size: 13px; font-weight: 600; }
.tb-q-rep { color: var(--accent); font-family: var(--f-mono); font-size: 11px; }
.tb-q-sub { font-family: var(--f-mono); font-size: 10px; color: var(--subtle); margin-top: 1px; }
.tb-q-del { width: 24px; height: 24px; border-radius: 6px; border: 1px solid var(--border); background: transparent; color: var(--muted); cursor: pointer; flex-shrink: 0; }
.tb-q-del:hover { color: var(--danger); border-color: var(--danger); }
.tb-q-empty { font-size: 12px; color: var(--subtle); padding: 12px; text-align: center; border: 1px dashed var(--border); border-radius: 8px; }
.tb-add-list { display: flex; flex-direction: column; gap: 6px; }
.tb-add-row { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border-radius: 8px; border: 1px solid var(--border); }
.tb-add-row img { width: 30px; height: 30px; border-radius: 5px; flex-shrink: 0; }
.tb-add-main { flex: 1; min-width: 0; }
.tb-add-name { font-size: 13px; font-weight: 600; }
.tb-add-cost { font-family: var(--f-mono); font-size: 10px; color: var(--min); }
.tb-add-cost .g { color: var(--gas); }
.tb-add-row.locked { opacity: .55; }
.tb-add-lock { font-family: var(--f-mono); font-size: 10px; color: var(--warn); text-align: right; }
.tb-add-one { background: var(--overlay2); border: 1px solid var(--border-strong); color: var(--text); border-radius: 6px; padding: 6px 11px; font-weight: 700; cursor: pointer; font-family: var(--f-mono); flex-shrink: 0; }
.tb-add-one:hover { border-color: var(--accent); color: var(--accent); }
.tb-add-end { background: var(--elevated); border: 1px solid var(--border); color: var(--text); border-radius: 6px; padding: 6px 6px; font-size: 11px; flex-shrink: 0; }
.tb-add-rep { background: color-mix(in srgb, var(--accent) 16%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); color: var(--accent); border-radius: 6px; padding: 6px 9px; cursor: pointer; font-weight: 700; flex-shrink: 0; }
.tb-add-rep:hover { background: color-mix(in srgb, var(--accent) 28%, transparent); }
.tb-modal-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; border-top: 1px solid var(--border); background: var(--elevated); }
.tb-modal-foot span { font-size: 11px; color: var(--subtle); }
.tb-modal-done { background: var(--accent); color: var(--color-text-on-accent); border: none; border-radius: 6px; padding: 7px 16px; font-weight: 700; cursor: pointer; font-family: var(--f-display); letter-spacing: .06em; text-transform: uppercase; font-size: 12px; }
.tb-row-rep { color: var(--accent); font-family: var(--f-mono); font-size: 10px; }
.tb-modal-status { font-family: var(--f-mono); font-size: 11px; font-weight: 700; color: var(--accent); padding: 4px 9px; border-radius: 6px; background: color-mix(in srgb, var(--accent) 14%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); }
.tb-add-done { font-family: var(--f-mono); font-size: 11px; font-weight: 700; color: var(--gas); white-space: nowrap; }
.tb-x2 { color: var(--accent); font-family: var(--f-mono); font-size: 11px; }
.tb-gas-set { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 4px 0 8px; }
.tb-gas-lbl { font-size: 12px; color: var(--muted); }
.tb-gas-btns { display: flex; gap: 6px; }
.tb-gas-btn { width: 40px; height: 40px; border-radius: 8px; border: 1px solid var(--border-strong); background: var(--elevated); color: var(--text); font-family: var(--f-mono); font-size: 16px; font-weight: 700; cursor: pointer; }
.tb-gas-btn:hover { border-color: var(--gas); color: var(--gas); }
.tb-gas-btn.on { background: color-mix(in srgb, var(--gas) 22%, transparent); border-color: var(--gas); color: var(--gas); }
.tb-gas-hint { font-size: 11px; color: var(--subtle); line-height: 1.5; padding: 4px 0; }
/* queue-count badge on producing pins */
.tb-pin-q { position: absolute; bottom: -7px; right: -7px; min-width: 16px; height: 16px; padding: 0 3px; border-radius: 8px;
  background: var(--accent); color: var(--color-bg); font-family: var(--f-mono); font-size: 9px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; box-shadow: 0 1px 4px rgba(0,0,0,.5); }

/* ── tactics palette tiles ──────────────────────────────────────────── */
.tb-pal-tactics .tb-tactic { flex-direction: row; gap: 7px; justify-content: flex-start; padding: 9px 8px; }
.tb-tactic { border-color: color-mix(in srgb, var(--ac) 40%, var(--border)) !important; }
.tb-tactic:hover { border-color: var(--ac) !important; }
.tb-tactic-glyph { font-size: 16px; color: var(--ac); width: 20px; text-align: center; }

/* NOTE: the action-on-canvas feature (timed unit movements marked on the map) was
   removed — tactical actions are now annotation/branch-point rows in the build
   list, not map markers. The former map-marker rules (`.tb-acts`, `.tb-act`,
   `.tb-act-diamond`, `.tb-act-arrow`, `.tb-act-tag`, `.tb-tick-act`) and the
   placement ghost (`.tb-ghost-action`) rendered nothing and are DELETED. The
   action-ROW rules (`.tb-row-action*`) and the palette-tile rules above are kept. */

/* ── action row in build list ───────────────────────────────────────── */
.tb-row-action { border-left: 3px solid var(--ac); background: color-mix(in srgb, var(--ac) 8%, transparent); }
.tb-row-action .tb-act-glyph { font-size: 16px; color: var(--ac); width: 22px; text-align: center; flex-shrink: 0; }
.tb-row-action.sel { border-color: var(--ac); box-shadow: inset 0 0 0 1px var(--ac); }

/* ── branch bar ───────────────────────────────────────────────────────
   Floats as a glass pill row centered under the topbar (clear of the filter
   pills at top:56px), so it reads as part of the analyst overlay cluster
   rather than a solid sub-bar between the topbar and body. */
.tb-branchbar { position: absolute; top: 92px; left: 224px; right: 324px; z-index: 7;
  display: flex; align-items: center; gap: 6px; padding: 6px 12px; overflow-x: auto;
  background: var(--tb-pill-bg); -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.45); }
.tb-branchbar-lbl { font-family: var(--f-display); font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--subtle); margin-right: 4px; flex-shrink: 0; }
.tb-branch { display: inline-flex; align-items: center; gap: 6px; background: var(--elevated); border: 1px solid var(--border); color: var(--muted); font-size: 12px; font-weight: 600; padding: 5px 11px; border-radius: 6px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }
.tb-branch:hover { color: var(--text); border-color: var(--border-strong); }
.tb-branch.active { color: var(--accent); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.tb-branch-x { color: var(--subtle); font-size: 14px; line-height: 1; }
.tb-branch-x:hover { color: var(--danger); }
/* branch rename pencil (Fix #7) — sits inside the tab next to the × */
.tb-branch-edit { color: var(--subtle); font-size: 11px; line-height: 1; opacity: .6; }
.tb-branch-edit:hover { color: var(--accent); opacity: 1; }
/* "diverges at MM:SS" chip (Fix #6) — the fork time inside a branch tab. */
.tb-branch-div { font-family: var(--f-mono); font-size: 10px; color: var(--subtle); }
.tb-branch.active .tb-branch-div { color: color-mix(in srgb, var(--accent) 80%, var(--text)); }
/* Fork control (Fix #4) — a tab-styled button that's always visible so forking
   is discoverable. Dashed border + accent tint distinguishes it from real tabs. */
.tb-branch-fork { border-style: dashed; color: var(--accent); }
.tb-branch-fork:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: var(--accent); }
/* one line of helper copy after the tabs/fork control */
.tb-branchbar-help { font-size: 11px; color: var(--subtle); margin-left: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex-shrink: 1; min-width: 0; }
@media (max-width: 1100px) { .tb-branchbar-help { display: none; } }

/* ── action modal extras ────────────────────────────────────────────── */
.tb-action-ic { width: 40px; height: 40px; transform: rotate(45deg); border-radius: 7px; background: color-mix(in srgb, var(--ac) 35%, var(--color-bg)); border: 2px solid var(--ac); display: flex; align-items: center; justify-content: center; color: #fff; }
.tb-action-ic > span { transform: rotate(-45deg); font-size: 18px; line-height: 1; }
/* Honest one-liner stating what a tactical action IS (annotation + branch point,
   not a sim-affecting placement). Sits at the top of the action modal body. */
.tb-action-purpose { font-size: 12px; color: var(--muted); line-height: 1.45; background: color-mix(in srgb, var(--ac) 9%, transparent);
  border: 1px solid color-mix(in srgb, var(--ac) 28%, var(--border)); border-left: 3px solid var(--ac); border-radius: 7px; padding: 8px 10px; margin-bottom: 12px; }
/* Inline hint under a modal section (e.g. the "trim to your committed force" note). */
.tb-modal-hint { font-size: 11px; color: var(--subtle); line-height: 1.4; margin: -2px 0 10px; }
.tb-uchips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.tb-uchip { display: flex; align-items: center; gap: 4px; background: var(--elevated); border: 1px solid var(--border); border-radius: 6px; padding: 4px 7px; }
.tb-uchip img { width: 22px; height: 22px; border-radius: 4px; }
.tb-uc-btn { width: 18px; height: 18px; border-radius: 4px; border: 1px solid var(--border-strong); background: var(--overlay2); color: var(--text); font-size: 13px; line-height: 1; cursor: pointer; padding: 0; }
.tb-uc-btn:hover { border-color: var(--accent); color: var(--accent); }
.tb-uc-n { font-family: var(--f-mono); font-size: 12px; font-weight: 700; min-width: 14px; text-align: center; }
.tb-uc-max { font-family: var(--f-mono); font-size: 10px; color: var(--subtle); }
.tb-note { width: 100%; background: var(--elevated); border: 1px solid var(--border); border-radius: 7px; padding: 8px 10px; color: var(--text); font-family: var(--f-ui); font-size: 13px; margin-bottom: 6px; }
.tb-note:focus { outline: none; border-color: var(--accent); }
.tb-out-btns { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.tb-out-btn { background: color-mix(in srgb, var(--color-brand-2) 14%, transparent); border: 1px solid color-mix(in srgb, var(--color-brand-2) 40%, transparent); color: var(--color-brand-2-hover); border-radius: 6px; padding: 7px 11px; font-size: 12px; font-weight: 600; cursor: pointer; }
.tb-out-btn:hover { background: color-mix(in srgb, var(--color-brand-2) 26%, transparent); }
.tb-out-custom { display: flex; gap: 6px; margin-bottom: 6px; }
.tb-out-custom .tb-note { margin-bottom: 0; }
.tb-out-custom .tb-out-btn { flex-shrink: 0; white-space: nowrap; }
.tb-swap-row { display: flex; align-items: center; gap: 8px; padding: 6px 2px 2px; flex-wrap: wrap; }
.tb-swap-lbl { font-family: var(--f-mono); font-size: 10px; color: var(--subtle); text-transform: uppercase; letter-spacing: .08em; }
.tb-swap-btns { display: flex; gap: 6px; flex-wrap: wrap; }
/* Add-on management hint (ⓘ swap physics) + relocate orphan warning (⚠). */
.tb-swap-note { font-family: var(--f-ui); font-size: 10px; line-height: 1.45; color: var(--subtle); margin: 4px 2px 2px; }
.tb-warn { font-family: var(--f-ui); font-size: 10px; line-height: 1.45; color: var(--warn); margin: 4px 2px 2px; }
.tb-chrono { background: color-mix(in srgb, var(--accent) 14%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); border-radius: 6px; padding: 5px 11px; font-size: 11px; font-weight: 700; cursor: pointer; font-family: var(--f-mono); }
.tb-chrono:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); }
.tb-chrono.on { background: var(--accent); color: var(--color-bg); }
.tb-ps-blk.chrono { border-left-color: var(--accent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 60%, transparent), 0 2px 6px rgba(0,0,0,.4); }
.tb-ps-ch { position: absolute; top: -1px; left: 1px; font-size: 10px; color: var(--accent); }

/* ── production timeline strip (Design-A conveyor slice) ────────────── */
.tb-ps { margin-bottom: 12px; }
.tb-ps-scroll { overflow-x: auto; overflow-y: hidden; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 0; }
.tb-ps-inner { position: relative; min-width: 100%; }
.tb-ps-ruler { position: relative; height: 22px; border-bottom: 1px solid var(--border); }
.tb-ps-tick { position: absolute; top: 0; bottom: 0; border-left: 1px solid var(--border); }
.tb-ps-tick span { font-family: var(--f-mono); font-size: 9px; color: var(--subtle); padding-left: 4px; }
.tb-ps-track { position: relative; height: 50px; }
.tb-ps-blk { position: absolute; top: 8px; height: 34px; display: flex; align-items: center; gap: 5px; padding: 0 5px; overflow: hidden;
  background: linear-gradient(180deg, var(--overlay), var(--elevated)); border: 1px solid var(--border-strong); border-left: 3px solid var(--prod);
  border-radius: 5px; box-shadow: 0 2px 6px rgba(0,0,0,.4); }
.tb-ps-blk img { width: 24px; height: 24px; border-radius: 4px; flex-shrink: 0; }
.tb-ps-blk span { font-family: var(--f-display); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tb-ps-blk.planned { opacity: .42; border-left-style: dashed; }
.tb-ps-blk.building { border-left-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 2px 6px rgba(0,0,0,.5); animation: tb-pulse 1.6s ease-out infinite; }
.tb-ps-blk.done { border-left-color: var(--gas); }
.tb-ps-head { position: absolute; top: -22px; bottom: 0; width: 2px; background: #fff; box-shadow: 0 0 8px rgba(255,255,255,.5); z-index: 3; }
.tb-ps-head::after { content: ''; position: absolute; top: -2px; left: 50%; width: 9px; height: 9px; border-radius: 2px; transform: translateX(-50%) rotate(45deg); background: #fff; }

/* ── Zerg parallel larva morphs — stacked concurrent lanes ───────────────
   A Hatchery/Lair/Hive draws its queued larva-units as STACKED lanes (one per
   simultaneous morph) rather than a single sequential conveyor, so three Roaches
   off three larva read as three parallel blocks. The track height is set inline
   from the lane count; blocks carry their own inline top/height. A thin larva
   sparkline under the ruler shows the parallel-morph capacity over time. */
.tb-ps-parallel .tb-ps-blk { top: auto; height: auto; }      /* inline top/height win */
.tb-ps-blk.larva-wait { border-left-color: var(--warn); border-left-style: dashed; }
.tb-ps-larva { position: relative; height: 26px; border-bottom: 1px solid var(--border); display: flex; align-items: stretch; }
.tb-ps-larva-lbl { position: absolute; left: 4px; top: 2px; z-index: 2; font-family: var(--f-mono); font-size: 8px; letter-spacing: .06em; text-transform: uppercase; color: var(--tech); opacity: .85; pointer-events: none; }
.tb-ps-larva-bars { position: relative; flex: 1; }
.tb-ps-larva-seg { position: absolute; bottom: 0; background: color-mix(in srgb, var(--tech) 55%, transparent); border-right: 1px solid color-mix(in srgb, var(--tech) 22%, transparent); }

/* ── Larva forecast (Zerg Hatchery/Lair/Hive modal) ──────────────────────
   Mini-chart of larva-available over the queue window + consumption markers. */
.tb-lf { margin-bottom: 12px; }
.tb-lf-stats { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.tb-lf-stat { font-family: var(--f-mono); font-size: 10px; color: var(--muted); background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 3px 8px; }
.tb-lf-stat b { color: var(--tech); font-size: 12px; }
.tb-lf-stat.warn b { color: var(--warn); }
.tb-lf-stat.warn { border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.tb-lf-stat.ok b { color: var(--gas); }
.tb-lf-stat.ok { color: var(--gas); }
.tb-lf-chartwrap { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; }
.tb-lf-scroll { overflow-x: auto; overflow-y: hidden; }
.tb-lf-inner { position: relative; min-width: 100%; }
.tb-lf-svg { display: block; }
.tb-lf-area { fill: color-mix(in srgb, var(--tech) 26%, transparent); stroke: var(--tech); stroke-width: 1.25; vector-effect: non-scaling-stroke; }
.tb-lf-cap { stroke: var(--subtle); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.tb-lf-mark { position: absolute; top: 0; width: 2px; height: 46px; background: color-mix(in srgb, var(--accent) 75%, transparent); }
.tb-lf-mark::after { content: ''; position: absolute; top: -3px; left: 50%; width: 6px; height: 6px; border-radius: 50%; transform: translateX(-50%); background: var(--accent); }
.tb-lf-mark.stall { background: var(--warn); }
.tb-lf-mark.stall::after { background: var(--warn); }
.tb-lf-now { position: absolute; top: 0; width: 2px; height: 46px; background: #fff; box-shadow: 0 0 6px rgba(255,255,255,.5); z-index: 3; }
.tb-lf-ruler { position: relative; height: 14px; }
.tb-lf-tick { position: absolute; top: 0; }
.tb-lf-tick span { font-family: var(--f-mono); font-size: 9px; color: var(--subtle); padding-left: 3px; }
.tb-lf-note { font-size: 10px; color: var(--subtle); margin-top: 5px; line-height: 1.4; }
.tb-lf-caphint { color: var(--muted); white-space: nowrap; }

/* ============================================================================
   Responsive — phone/tablet (≤767px). Canonical mobile↔desktop line is 768px.
   The production / gas / action modals (.tb-modal) become a bottom sheet:
   full-width, pinned to the bottom, max-height ~85vh, internal scroll. Mirrors
   the frontend's `.u-sheet-on-mobile` pattern but kept self-contained here.
   ========================================================================= */
@media (max-width: 767px) {
  /* Backdrop aligns the sheet to the bottom rather than centering it. */
  .tb-root .tb-modal-backdrop {
    align-items: flex-end;
    justify-content: stretch;
  }
  .tb-root .tb-modal {
    width: 100%;
    max-width: 100%;
    max-height: 85vh;
    border-radius: 14px 14px 0 0;        /* rounded top corners only */
    border-left: none;
    border-right: none;
    border-bottom: none;
    box-shadow: 0 -12px 40px rgba(0,0,0,.6);
    animation: tb-sheet-up .18s cubic-bezier(.2,.8,.25,1);
  }
  /* The body is the scroll region so head/foot stay pinned within the sheet. */
  .tb-root .tb-modal-body { flex: 1; overflow: auto; -webkit-overflow-scrolling: touch; }
  .tb-root .tb-modal-head,
  .tb-root .tb-modal-foot { flex-shrink: 0; }
  /* Suppress the desktop pop-in; the sheet uses its own slide-up. */
  .tb-root .tb-modal-backdrop.tb-noanim .tb-modal { animation: none; }
}
@keyframes tb-sheet-up { from { transform: translateY(100%); } to { transform: none; } }

/* ============================================================================
   Responsive — tablet landscape (768px–1023px).
   Canonical intermediate breakpoint 1024px. The floating-overlay posture is
   kept (panels still float over the full-bleed map), but the side cards are
   narrowed so the map keeps meaningful center space — and the floating dock /
   branch-bar horizontal offsets are re-derived from the narrower panel widths.
   ========================================================================= */
@media (max-width: 1023px) and (min-width: 768px) {
  /* Compress the floating side cards so the map stage keeps meaningful space. */
  .tb-root .tb-palette { width: 160px; }
  .tb-root .tb-side    { width: 220px; }
  /* Re-derive the branch-bar gutters: left = palette 160 + 24, right = side 220 + 24. */
  .tb-root .tb-branchbar { left: 184px; right: 244px; }

  /* Topbar: tighten the cluster gap and let the race-switch shrink slightly.
     At this width the three groups can wrap to two rows; tighten the inter-row
     gap + pill padding so a 2-row topbar still clears the branch bar (which is
     anchored at top:92px) rather than overlapping it. */
  .tb-root .tb-topbar { gap: 8px; row-gap: 4px; }
  .tb-root .tb-topbar-center,
  .tb-root .tb-group-left { row-gap: 4px; }
  .tb-root .tb-actions { row-gap: 4px; }
  .tb-root .tb-ghostbtn { padding: 5px 9px; }
  .tb-root .tb-home { padding: 5px 9px; }
  .tb-root .tb-race   { padding: 5px 8px; font-size: 11px; }
  .tb-root .tb-title  { display: none; }     /* hide map/player names — space is too tight */

  /* Palette tiles: single column at this width reads better than a compressed 2-col grid. */
  .tb-root .tb-pal-grid { grid-template-columns: 1fr; }

  /* Scrubber HUD: compress the resource chips slightly. */
  .tb-root .tb-hud-res { min-width: 64px; padding: 5px 7px; }
}

/* ============================================================================
   Responsive — phone (≤767px). Main EDITOR layout.
   The editor's floating glass rails collapse into a single column stack
   (the editor authors over the live replay canvas, which provides the map +
   playback dock — the editor no longer owns a map stage or scrubber):
     1. Topbar (condensed to race switch only)
     2. Build Palette → horizontal scrolling strip
     3. Build Order panel (limited height, internal scroll)

   All tap targets are min 44px (var(--tap-target-min)).
   ========================================================================= */
@media (max-width: 767px) {

  /* ── Posture reset ──────────────────────────────────────────────────────
     The desktop posture floats every region absolutely over a full-bleed map.
     On phones we revert to a simple static vertical stack: the root scrolls,
     the body is a flex column again, and each formerly-floating region drops
     back into normal flow. Subsequent rules in this block then style the
     stacked layout. */
  .tb-root { overflow-y: auto; background: var(--bg); }
  .tb-root .tb-body { display: flex; flex-direction: column; min-height: 0; overflow: visible; }
  .tb-root .tb-topbar,
  .tb-root .tb-branchbar,
  .tb-root .tb-palette,
  .tb-root .tb-side {
    position: static;
    inset: auto;
    left: auto; right: auto; top: auto; bottom: auto;
    width: auto;
    z-index: auto;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* Stacked panels read as solid bars again (no floating glass over a backdrop). */
  .tb-root .tb-palette,
  .tb-root .tb-side { background: var(--raised); border: none; border-radius: 0; }
  .tb-root .tb-branchbar { background: var(--raised); border: none; border-radius: 0; border-bottom: 1px solid var(--border); }

  /* ── Topbar condensed ───────────────────────────────────────────────── */
  .tb-root .tb-topbar {
    pointer-events: auto;
    background: var(--elevated);
    border-bottom: 1px solid var(--border);
    height: auto;
    min-height: 52px;
    flex-wrap: wrap;
    gap: 6px;
    padding: 8px 10px;
  }
  /* Topbar clusters: drop their pill chrome so they sit flat on the solid bar. */
  .tb-root .tb-topbar-cluster.tb-title {
    background: transparent;
    border: none;
    padding: 0;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* Hide decorative / secondary topbar items on phones. */
  .tb-root .tb-title,
  .tb-root .tb-badge  { display: none; }
  /* Ensure ghost buttons are at least 44px tall. */
  .tb-root .tb-ghostbtn { min-height: 44px; padding: 0 14px; }
  /* Race tabs: min 44px height, slightly compressed. */
  .tb-root .tb-race { min-height: 44px; padding: 0 10px; }

  /* ── Body: vertical stack instead of row ────────────────────────────── */
  .tb-root .tb-body { flex-direction: column; overflow: visible; }

  /* ── Left palette → horizontal scroll strip ─────────────────────────── */
  .tb-root .tb-palette {
    width: 100%;
    height: auto;
    flex-shrink: 0;
    border-right: none;
    border-bottom: 1px solid var(--border);
    /* The scroll region inside will overflow-x */
  }
  /* Collapse the header text and show a single scrollable row of tiles. */
  .tb-root .tb-pal-head { padding: 6px 10px 4px; }
  .tb-root .tb-pal-hint { display: none; }
  .tb-root .tb-pal-scroll {
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-x;
    padding: 4px 8px 8px;
    /* Let all groups flow into one horizontal row. */
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 8px;
  }
  /* Group labels sit above their tiles in mini-column orientation. */
  .tb-root .tb-pal-group {
    writing-mode: horizontal-tb;
    margin: 0;
    font-size: 9px;
    white-space: nowrap;
    flex-shrink: 0;
  }
  /* Tiles in a horizontal strip: single column per group, fixed tile size. */
  .tb-root .tb-pal-grid {
    grid-template-columns: repeat(auto-fill, 56px);
    grid-auto-flow: column;
    grid-auto-columns: 56px;
    gap: 5px;
    flex-shrink: 0;
  }
  /* Make tiles a minimum of 44px for comfortable tapping. */
  .tb-root .tb-pal-tile {
    min-width: 56px;
    min-height: 56px;
    padding: 6px 4px 5px;
  }
  .tb-root .tb-pal-tile img { width: 26px; height: 26px; }

  /* Filter pills: slightly smaller text on phones. (The .tb-filters group is
     position:static here, so it has no offset to set.) */
  .tb-root .tb-filter  { font-size: 11px; padding: 5px 10px; min-height: 44px; }

  /* ── Right Build Order panel → below the map ────────────────────────── */
  .tb-root .tb-side {
    width: 100%;
    height: 220px;              /* fixed max height; internal scroll handles the rest */
    border-left: none;
    border-top: 1px solid var(--border);
    flex-shrink: 0;
  }
  /* Tab buttons: taller tap targets. */
  .tb-root .tb-side-tab { padding: 13px 0; min-height: 44px; }
  /* Row items: ensure 44px min height. */
  .tb-root .tb-row { min-height: 44px; }
  /* Worker show/hide toggle: bigger tap target on phones. */
  .tb-root .tb-workers-toggle { min-height: 40px; padding: 9px 13px; font-size: 12px; }

  /* Economy HUD chips (relocated into the build-order header) wrap nicely. */
  .tb-root .tb-hud-res { flex: 1 1 70px; min-width: 64px; min-height: 40px; }

  /* ── Branch bar: horizontal scroll already handles overflow. ─────────── */
  .tb-root .tb-branchbar { padding: 6px 10px; }
  .tb-root .tb-branch { min-height: 44px; }
}

/* ── Analyst-mode integration glue ──────────────────────────────────────────
   The editor authors OVER the live replay canvas: the canvas + analyst
   dock/scrubber stay VISIBLE, and the editor mounts into #tactical-build-host
   as floating edge rails (palette left, build-order right) that leave the map
   center open for placement clicks. Visibility is driven by the
   .tb-build-active class on #replay-viewer (set in replay.js setReplayMode()).
   These selectors intentionally reference replay-viewer chrome by id/class. */
#tactical-build-host {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 12;                 /* above the canvas + analyst rails/dock */
  /* CLICK-THROUGH: the host spans the canvas but must NOT capture pointer
     events over the map center, or canvas placement clicks never reach the
     renderer. No opaque background — the live map shows through. The actual
     editor panels/modals below re-enable pointer-events on themselves. */
  pointer-events: none;
  background: transparent;
}
.replay-viewer.tb-build-active #tactical-build-host {
  display: block;
}
/* The .tb-root container is full-bleed (width/height 100%); over the live
   canvas it must stay click-through so placement clicks reach the renderer.
   Only the actual visual panels/modals/pill-clusters re-enable pointer-events
   on themselves; the empty map center passes clicks down to #replay-canvas. */
#tactical-build-host .tb-root { pointer-events: none; background: transparent; }
.tb-palette,
.tb-side,
.tb-modal-backdrop,
.tb-cv-overlay,
.tb-qp-overlay,
.tb-branchbar,
.tb-topbar > *,
.tb-topbar-cluster,
.tb-filters {
  pointer-events: auto;
}
/* Keep the corner controls (mode toggle) reachable above the editor; hide the
   playback-only action row (Menu/Settings/Draw) while in Build mode. */
.replay-viewer.tb-build-active .replay-corner-row-actions { display: none; }
/* The editor brings its OWN palette + build-order rails and its own topbar, so in
   Build mode HIDE the native analyst rails + stats topbar — otherwise they collide
   (double build-order panel, stacked topbars). The canvas + bottom dock (the shared
   scrubber/transport) stay visible. */
.replay-viewer.tb-build-active .replay-analyst-rail { display: none !important; }
.replay-viewer.tb-build-active #replay-analyst-stats-topbar { display: none !important; }
/* The Playback/Build toggle otherwise floats mid-left over the palette. Move it to a
   clean slot at top-center, below the editor topbar, clear of both rails. */
.replay-viewer.tb-build-active #replay-corner-controls {
  top: 72px; left: 50%; right: auto; bottom: auto; transform: translateX(-50%);
  width: auto; flex-direction: row; z-index: 21;
}
/* The bottom-left minimap inset collides with the palette and is redundant while
   authoring (the whole canvas IS the map — pan/zoom navigates). The full-screen
   chat layer is a playback feature. Hide both in Build mode. */
.replay-viewer.tb-build-active #replay-overview-frame { display: none; }
.replay-viewer.tb-build-active .analyst-chat-layer { display: none; }
/* Active state for the mode-toggle buttons reuses the cyan accent. */
#replay-mode-toggle-group .replay-viewmode-btn.active {
  background: var(--color-accent-soft, rgba(86,194,255,0.14));
  color: var(--color-accent, #56c2ff);
}

/* ── Mobile: defer Build mode to desktop ─────────────────────────────────────
   The editor authors over the LIVE replay canvas; on the phone/touch layout the
   rails stack over the canvas with no exposed map to tap, so building placement
   can't work. Hide the Playback/Build toggle so small-screen / touch users stay
   in Playback (replay.js also entry-guards setReplayMode on the same condition).
   The breakpoint deliberately matches replay.js MOBILE_REPLAY_MEDIA — not the
   480/768/1024 grid — so the CSS hide and the JS guard agree (no "visible but
   dead" toggle). Full mobile placement is a tracked follow-up. */
@media (max-width: 860px), (hover: none) and (pointer: coarse) {
  #replay-mode-toggle-group { display: none !important; }
}

/* ════════════════════════════════════════════════════════════════════════════
   APPENDED — canvas-interaction backlog (drag-to-move / placement UX / base
   overlays). The drag ghost, placement preview, and base-location rings are all
   drawn on the CANVAS by minimap-renderer.js — these rules only style the small
   DOM affordances around placement. (Appended at file end per edit policy; no
   existing rules modified.)
   ════════════════════════════════════════════════════════════════════════════ */

/* Armed palette tile — the `.arming` class is set on the tile whose building is
   currently waiting for a map click (tactical-build.js render()). Until now it
   had no style, so the only "what's armed" cue was the hint-banner text. Give it
   a clear, accent-glowing selected state so the armed building is obvious at a
   glance, plus a crosshair cursor echoing the canvas placement cursor. */
.tb-root .tb-pal-tile.arming {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, var(--elevated));
  box-shadow: 0 0 0 1px var(--accent), 0 0 10px color-mix(in srgb, var(--accent) 45%, transparent);
  cursor: crosshair;
}
.tb-root .tb-pal-tile.arming img { border-color: color-mix(in srgb, var(--accent) 60%, rgba(0,0,0,.4)); }
/* Gentle pulse so the armed tile reads as "live / awaiting input". Honors
   reduced-motion. */
@keyframes tb-arming-pulse {
  0%, 100% { box-shadow: 0 0 0 1px var(--accent), 0 0 8px color-mix(in srgb, var(--accent) 35%, transparent); }
  50%      { box-shadow: 0 0 0 1px var(--accent), 0 0 14px color-mix(in srgb, var(--accent) 60%, transparent); }
}
@media (prefers-reduced-motion: no-preference) {
  .tb-root .tb-pal-tile.arming { animation: tb-arming-pulse 1.4s ease-in-out infinite; }
}

/* ════════════════════════════════════════════════════════════════════════════
   APPENDED — consolidated economy bar. Replaces the 4 stacked .tb-hud-res boxes
   that were in the build-order panel header with ONE horizontal glass pill in
   the topbar centre. Same data-hud hooks so tick() updates it unchanged.
   ════════════════════════════════════════════════════════════════════════════ */
.tb-root .tb-eco-bar {
  /* flex-wrap so the chips reflow to a 2nd line when the topbar is tight (wide
     4-digit economy + the New button), instead of forcing the whole topbar to wrap
     and dropping the action pills to their own left-aligned row. */
  display: inline-flex; align-items: center; gap: 14px 12px; flex-wrap: wrap; min-width: 0; max-width: 100%;
  padding: 4px 14px; background: var(--tb-pill-bg);
  -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); border-radius: 8px;
}
.tb-root .tb-eco-chip { display: inline-flex; align-items: center; gap: 6px; font-family: var(--f-mono); white-space: nowrap; }
.tb-root .tb-eco-chip img { width: 16px; height: 16px; border-radius: 3px; }
.tb-root .tb-eco-chip .tb-eco-ic { font-size: 13px; line-height: 1; color: var(--warn); }
.tb-root .tb-eco-chip b { font-weight: 700; font-size: 14px; font-variant-numeric: tabular-nums; }
.tb-root .tb-eco-chip i { font-style: normal; font-size: 10px; color: var(--subtle); font-variant-numeric: tabular-nums; }
.tb-root .tb-eco-chip.min b { color: var(--min); }
.tb-root .tb-eco-chip.gas b { color: var(--gas); }
.tb-root .tb-eco-chip.sup b { color: var(--warn); }
.tb-root .tb-eco-chip.wk  b { color: var(--text); }
/* The macro chip (race mechanic) and the mode label are a SEPARATE group from the
   four resource stats: a divider rule sits in the gap before each (margin-left
   pushes it off the previous chip; padding-left re-opens space after it) so they
   never read as "MULE0"/"…PROJECTED" jammed against the stats or the pill edge. */
.tb-root .tb-eco-mode { font-family: var(--f-display); font-size: 9px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  margin-left: 2px; padding-left: 12px; border-left: 1px solid var(--border-strong); color: var(--accent); }
/* Supply-block chip (F1) — a clickable <button> that drops the race's supply
   structure in at the block. Strip native button chrome so it sits flush in the
   glass pill like the other chips, plus a hover affordance (it's actionable). */
.tb-root .tb-eco-chip.tb-eco-blocked { appearance: none; -webkit-appearance: none; background: transparent;
  border: 1px solid var(--warn); border-radius: 6px; padding: 2px 8px; margin-left: 2px;
  font-family: var(--f-display); font-size: 9px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.tb-root .tb-eco-chip.tb-eco-blocked:hover { background: color-mix(in srgb, var(--warn) 16%, transparent); }
/* Race macro-mechanic chip (Zerg Larva / Terran MULE / Protoss Chrono). The tag is
   a small uppercase caption sitting just left of the bold value with its own gap so
   "MULE 0" reads as label + number, not a run-on "MULE0". */
.tb-root .tb-eco-chip.macro { margin-left: 2px; padding-left: 12px; border-left: 1px solid var(--border-strong); }
.tb-root .tb-eco-chip.macro .tb-eco-tag { font-family: var(--f-display); font-size: 9px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--subtle); }
.tb-root .tb-eco-chip.macro b { color: var(--tech); }
/* Zerg queen-inject badge next to the Larva count: green = injecting (≥1 Queen/hatch),
   amber = under-injecting / no Queen. The at-a-glance "are you injecting?" signal. */
.tb-root .tb-eco-inj { font-family: var(--f-mono); font-size: 10px; font-weight: 700; color: var(--gas); display: inline-flex; align-items: center; gap: 1px; }
.tb-root .tb-eco-inj.warn { color: var(--warn); }
/* Narrow-desktop / large-tablet (Build only shows above the 860px toggle line):
   drop the per-resource income sub-values so the topbar centre doesn't crowd. */
@media (max-width: 1023px) {
  .tb-root .tb-eco-chip i { display: none; }
  .tb-root .tb-eco-bar { gap: 11px; padding: 4px 11px; }
  .tb-root .tb-eco-mode,
  .tb-root .tb-eco-chip.macro { padding-left: 9px; }
}

/* ════════════════════════════════════════════════════════════════════════════
   APPENDED — STANDALONE / from-scratch editor (no replay). Mounted into the Builds
   page host (#tb-standalone-host) instead of the replay viewer's
   #tactical-build-host. Unlike the replay-overlay path (transparent .tb-root over
   the live canvas), the standalone editor owns its OWN backdrop + placement canvas
   (.tb-stage), so it must be OPAQUE and re-enable pointer events. The MUST-fix
   topbar additions (map selector, time control) + the disabled-tile feedback live
   here too. (Appended at file end per edit policy; no existing rules modified.)
   ════════════════════════════════════════════════════════════════════════════ */

/* Page host: a sized, positioned container so the editor's edge-anchored
   (position:absolute) palette/side rails + floating topbar resolve against it.
   The Builds page section is normal-flow; this gives the editor a viewport-tall
   stage. */
#tb-standalone-host {
  position: relative;
  width: 100%;
  /* Fill the space under the global nav. 100svh-ish minus the app chrome; the
     min keeps it usable on short windows. */
  height: calc(100vh - 64px);
  min-height: 560px;
  border-radius: var(--tb-glass-radius);
  overflow: hidden;
  background: var(--color-bg, #0a0d14);
}
#tb-standalone-host:empty { display: none; }   /* before mount / after teardown */

/* The Build tab opens the Tactical Build editor DIRECTLY (main.js navigateTo
   'builds' → openStandaloneBuild). The legacy saved-build EXPLORER — the "Build
   Explorer" header, the matchup/race filters, the "+ New Build" button, and the
   builds-list — is scrapped (hidden) so the editor is the entire page. */
#page-builds > .page-header,
#page-builds #builds-list { display: none !important; }

/* Standalone root: OPAQUE (it has no live canvas behind it) + interactive (the
   replay-overlay rules made .tb-root click-through; here the stage IS the editor). */
.tb-root.tb-standalone {
  position: absolute; inset: 0;
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(86,194,255,0.05), transparent 60%),
    var(--color-bg, #0a0d14);
  pointer-events: auto;
}
.tb-root.tb-standalone .tb-topbar { pointer-events: none; }   /* clusters re-enable */
.tb-root.tb-standalone .tb-topbar > * { pointer-events: auto; }

/* OVERLAP FIX: the floating topbar wraps to 2+ rows when full of controls, and the
   rails had a FIXED top:64 — so the topbar's 2nd row (scrubber on the left, Save/
   Import/Export on the right) landed ON TOP of the palette + build-order rails.
   _fitChrome() (tactical-build.js) measures the topbar's real bottom edge each
   render/resize and publishes it as --tb-chrome-top; the rails + branchbar start
   there so they always clear the topbar, whatever its height. */
.tb-root.tb-standalone .tb-palette,
.tb-root.tb-standalone .tb-side { top: var(--tb-chrome-top, 64px); }
.tb-root.tb-standalone .tb-branchbar { top: var(--tb-chrome-top, 92px); }

/* DECLUTTER: topbar dropdowns — collapse the Pro-Heat cluster and the file actions
   behind a trigger button + a popover panel so the toolbar reads as one clean row. */
.tb-dd { position: relative; display: inline-flex; }
.tb-dd-btn { display: inline-flex; align-items: center; gap: 5px; font: 600 11px var(--f-ui);
  color: var(--text); background: var(--elevated); border: 1px solid var(--border);
  border-radius: 7px; padding: 6px 10px; cursor: pointer; white-space: nowrap; line-height: 1; }
.tb-dd-btn:hover:not(:disabled), .tb-dd.open > .tb-dd-btn { border-color: var(--accent); color: var(--text); }
.tb-dd-btn:disabled { opacity: .5; cursor: default; }
/* prominent "＋ New build" topbar button — discoverable without opening File ▾ */
.tb-newbtn { display: inline-flex; align-items: center; gap: 5px; font: 700 11px var(--f-ui); letter-spacing: .03em;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 15%, var(--elevated));
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 7px; padding: 6px 11px; cursor: pointer; white-space: nowrap; line-height: 1;
  transition: background .12s ease, border-color .12s ease; }
.tb-newbtn:hover { background: color-mix(in srgb, var(--accent) 28%, var(--elevated)); border-color: var(--accent); }
/* Undo/redo icon buttons (UX) — compact, sit left of "＋ New" in the actions cluster.
   Disabled (greyed, no hover) when the corresponding history stack is empty. */
.tb-undobtn { display: inline-flex; align-items: center; justify-content: center; font-size: 15px; line-height: 1;
  color: var(--text, #cdd6e4); background: var(--elevated); border: 1px solid var(--border);
  border-radius: 7px; width: 30px; height: 28px; padding: 0; cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease; }
.tb-undobtn:hover:not(:disabled) { background: color-mix(in srgb, var(--accent) 18%, var(--elevated)); border-color: var(--accent); color: var(--accent); }
.tb-undobtn:disabled { opacity: .35; cursor: default; }
.tb-dd-btn.has-active { border-color: var(--accent); }
.tb-dd-ic { opacity: .85; font-size: 12px; }
.tb-dd-caret { font-style: normal; font-size: 9px; opacity: .7; margin-left: 1px; }
.tb-dd.open > .tb-dd-btn .tb-dd-caret { transform: rotate(180deg); }
.tb-dd-badge { background: var(--accent); color: #04121f; font-weight: 700; font-size: 9px;
  border-radius: 8px; min-width: 15px; height: 15px; line-height: 15px; text-align: center; padding: 0 3px; }
.tb-dd > .tb-dd-panel { position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  background: var(--raised, #131722); border: 1px solid var(--border); border-radius: 9px;
  padding: 8px; box-shadow: 0 12px 34px rgba(0,0,0,.55); display: none; }
.tb-dd.open > .tb-dd-panel { display: flex; }
/* Heat panel: the toggles + opp select + CB wrap into a tidy popover grid. */
.tb-dd-panel.tb-heatctrl { flex-wrap: wrap; gap: 6px; width: 264px; align-items: center; }
/* File menu: a right-anchored vertical list (it lives in the right-edge cluster). */
.tb-dd-panel.tb-file-menu { flex-direction: column; gap: 3px; left: auto; right: 0; min-width: 150px; padding: 6px; }
.tb-dd-panel.tb-file-menu .tb-ghostbtn { text-align: left; width: 100%; }

/* Placement stage — the map canvas surface. Sits centered behind the floating
   palette/side rails (which keep their edge anchors + higher z-index). Leaves a
   gutter for the rails so the map center is reachable. */
.tb-root.tb-standalone .tb-stage {
  position: absolute;
  /* FULL-BLEED map surface — fills the editor area edge-to-edge below the
     topbar + branch bar, exactly like the SC2 replay viewer's canvas. The
     palette (left) and side rail (right) FLOAT over its edges at a higher
     z-index, so the map is as large as possible instead of being boxed into a
     narrow center gutter. The map center stays clear (rails hug the edges). */
  left: 0; right: 0;
  top: 108px; bottom: 0;
  z-index: 4;
  overflow: hidden;
  background: #070a11;
  pointer-events: auto;
}
.tb-root.tb-standalone .tb-sa-canvas { width: 100%; height: 100%; display: block; touch-action: none; }
/* Empty-state copy shown on the stage when there's no map/canvas (e.g. a map that
   404'd) — centered hint so the surface never reads as broken. */
.tb-root.tb-standalone .tb-stage-empty {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; text-align: center; padding: 24px; color: var(--muted);
  font-family: var(--f-display); font-size: 13px; letter-spacing: .04em; pointer-events: none;
}
.tb-root.tb-standalone .tb-stage-empty span { font-family: var(--f-ui); font-size: 11px; color: var(--subtle); max-width: 260px; line-height: 1.5; }

/* ── F3 box-select marquee + selection mini-toolbar (standalone canvas) ──────
   The marquee box is a pure-DOM rectangle positioned in CSS px from the two
   drag corners (DPR never enters — the canvas DPR contract is the renderer's).
   pointer-events:none so it never swallows the drag it's tracking. The toolbar
   floats above the canvas and carries the six tactic tiles (click → pick a map
   target → issue a timed group order that re-routes the selected units). */
.tb-root.tb-standalone .tb-marquee {
  position: absolute;
  pointer-events: none;
  z-index: 6;                 /* above .tb-stage canvas (z 4), below rails */
  border: 1px dashed var(--color-accent, #56c2ff);
  background: var(--color-accent-soft, rgba(86, 194, 255, 0.12));
  border-radius: 2px;
}
.tb-root.tb-standalone .tb-sel-toolbar {
  position: absolute;
  z-index: 7;                 /* above the marquee box */
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  background: var(--tb-pill-bg, rgba(12, 16, 24, 0.92));
  -webkit-backdrop-filter: blur(var(--tb-pill-blur, 6px));
  backdrop-filter: blur(var(--tb-pill-blur, 6px));
  border: 1px solid var(--tb-pill-border, rgba(255, 255, 255, 0.12));
  border-radius: 7px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
  font-family: var(--f-ui);
  font-size: 12px;
  color: var(--text);
}
.tb-root.tb-standalone .tb-sel-toolbar .tb-sel-count {
  color: var(--color-accent, #56c2ff);
  font-weight: 600;
  letter-spacing: .02em;
  white-space: nowrap;
}
.tb-root.tb-standalone .tb-sel-toolbar .tb-sel-clear {
  appearance: none;
  border: 1px solid var(--border, rgba(255, 255, 255, 0.14));
  background: var(--elevated, rgba(255, 255, 255, 0.06));
  color: var(--muted);
  border-radius: 5px;
  width: 20px; height: 20px;
  line-height: 1;
  font-size: 12px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.tb-root.tb-standalone .tb-sel-toolbar .tb-sel-clear:hover { color: var(--text); border-color: var(--color-accent, #56c2ff); }
/* Tactic tiles inside the selection mini-toolbar — one per TB_ACTIONS entry.
   Each tile is colored from its tactic (--tb-act-color, set inline). Click arms
   "pick destination" placement; the click on the canvas commits the group order. */
.tb-root.tb-standalone .tb-sel-toolbar .tb-sel-tiles {
  display: inline-flex; align-items: center; gap: 3px;
}
.tb-root.tb-standalone .tb-sel-toolbar .tb-sel-tile {
  appearance: none;
  border: 1px solid var(--border, rgba(255, 255, 255, 0.14));
  background: var(--elevated, rgba(255, 255, 255, 0.06));
  color: var(--tb-act-color, var(--text));
  border-radius: 5px;
  width: 24px; height: 22px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: border-color .12s, background .12s;
}
.tb-root.tb-standalone .tb-sel-toolbar .tb-sel-tile .tb-sel-glyph { font-size: 13px; }
.tb-root.tb-standalone .tb-sel-toolbar .tb-sel-tile:hover {
  border-color: var(--tb-act-color, var(--color-accent, #56c2ff));
  background: color-mix(in srgb, var(--tb-act-color, #56c2ff) 18%, transparent);
}

/* ── F3 order edit/delete popover (canvas order marker → click) ──────────────
   Reuses the .tb-sel-toolbar pill chrome; the marker class lifts it above the
   selection toolbar and tints the delete tile red on hover. The count chip
   doubles as the tactic label (glyph + name), colored from --tb-act-color. */
.tb-root.tb-standalone .tb-order-popover { z-index: 8; }
.tb-root.tb-standalone .tb-order-popover .tb-sel-count {
  color: var(--tb-act-color, var(--color-accent, #56c2ff));
}
.tb-root.tb-standalone .tb-order-popover .tb-order-del:hover {
  border-color: #f87171;
  background: color-mix(in srgb, #f87171 20%, transparent);
}

/* Map selector + time control pills in the topbar (standalone only). */
.tb-mapsel-wrap { display: inline-flex; align-items: center; gap: 6px; background: var(--tb-pill-bg);
  -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); border-radius: 6px; padding: 4px 8px; }
.tb-mapsel-ic { color: var(--muted); font-size: 12px; }
.tb-mapsel { background: var(--elevated); color: var(--text); border: 1px solid var(--border); border-radius: 5px;
  font-family: var(--f-ui); font-size: 12px; padding: 3px 6px; max-width: 180px; cursor: pointer; }
/* Patch selector reuses .tb-mapsel-wrap + .tb-mapsel verbatim; its dropdown text
   ("Live 5.0.15" / "PTR 5.0.16") is shorter than map names, so narrow it. */
.tb-patchsel { max-width: 120px; }
/* Aggregate-heatmap controls (standalone topbar): opponent-race select + per-layer toggles */
.tb-heatctrl { display: inline-flex; align-items: center; gap: 6px; background: var(--tb-pill-bg);
  -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); border-radius: 6px; padding: 4px 8px; flex-wrap: wrap; }
.tb-heatctrl.tb-heat-empty { opacity: .55; }
.tb-heat-ic { color: var(--muted); font-size: 13px; line-height: 1; }
.tb-heat-lead { font-family: var(--f-display); font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent); white-space: nowrap; }
.tb-heat-hint { color: var(--subtle); font-size: 11px; }
.tb-heat-opp { background: var(--elevated); color: var(--text); border: 1px solid var(--border);
  border-radius: 5px; font-family: var(--f-ui); font-size: 12px; padding: 3px 5px; cursor: pointer; }
.tb-heat-toggle { display: inline-flex; align-items: center; gap: 5px;
  background: var(--elevated); color: var(--subtle); border: 1px solid var(--border);
  border-radius: 5px; font-family: var(--f-ui); font-size: 11px; font-weight: 600; padding: 3px 8px;
  cursor: pointer; white-space: nowrap; transition: background .12s, color .12s, border-color .12s; }
.tb-heat-toggle:hover:not([disabled]) { color: #fff; border-color: rgba(86,194,255,.45); }
.tb-heat-toggle[disabled] { opacity: .4; cursor: not-allowed; }
/* Pills-as-legend: a permanent colour chip on every toggle, hue from the single-
   source palette injected inline as --hc (tb-heat-palette.js → _heatControlMarkup).
   The CSS no longer hardcodes the four layer colours — they live in ONE place. */
.tb-heat-chip { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto;
  background: var(--hc, #888); box-shadow: 0 0 0 1px rgba(0,0,0,.35) inset; opacity: .85; }
.tb-heat-toggle.active .tb-heat-chip { opacity: 1; }
/* Active accent derives from --hc (no per-layer selectors). A translucent tint of
   the layer hue for fill/border + a NON-COLOUR cue (leading ✓ + inset ring) so the
   active state is legible without relying on colour alone (a11y). */
.tb-heat-toggle.active { background: color-mix(in srgb, var(--hc, #888) 22%, transparent);
  border-color: color-mix(in srgb, var(--hc, #888) 78%, transparent); color: #fff;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--hc, #888) 55%, transparent) inset; }
.tb-heat-toggle.active::before { content: "\2713"; font-weight: 700; font-size: 10px; line-height: 1; margin-right: -1px; }
/* Sample-size honesty: a layer that HAS data but n<20 reads dashed + dimmed, with a
   small superscript n, so thinness shows before the user clicks it. */
.tb-heat-toggle.tb-heat-thin { border-style: dashed; }
.tb-heat-toggle.tb-heat-thin:not(.active) { opacity: .82; }
.tb-heat-n { font-size: 8px; font-weight: 700; opacity: .8; margin-left: 1px; vertical-align: super; }
/* Colour-blind-safe palette pill — same look language as the layer toggles. */
.tb-heat-cb { background: var(--elevated); color: var(--subtle); border: 1px solid var(--border);
  border-radius: 5px; font-family: var(--f-display); font-size: 10px; font-weight: 700; letter-spacing: .06em;
  padding: 3px 7px; cursor: pointer; white-space: nowrap; transition: background .12s, color .12s, border-color .12s; }
.tb-heat-cb:hover { color: #fff; border-color: rgba(86,194,255,.45); }
.tb-heat-cb.active { background: rgba(86,194,255,.2); border-color: rgba(86,194,255,.7); color: #cde6ff; }
/* Tablet band: the 6-control heat cluster (lead + select + 4 toggles) is the
   widest topbar item — compress it so the topbar doesn't wrap to a 3rd row that
   overlaps the branch bar / map stage (hardcoded top offsets). */
@media (max-width: 1023px) {
  .tb-heat-lead { display: none; }
  .tb-heatctrl { gap: 4px; padding: 3px 6px; }
  .tb-heat-toggle { padding: 3px 6px; font-size: 10px; }
  .tb-heat-opp { font-size: 11px; padding: 2px 4px; }
}
/* Heatmap info panel — floats at the bottom-center of the standalone stage,
   clear of the floating palette (left) and side rail (right). pointer-events
   off so it never blocks map interaction. */
.tb-root.tb-standalone .tb-heat-info {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 14px; z-index: 6;
  max-width: min(640px, 64%); pointer-events: none;
  background: rgba(8,10,18,.82); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border: 1px solid var(--tb-pill-border); border-radius: 9px; padding: 8px 12px;
  font-family: var(--f-ui); font-size: 12px; color: var(--text);
  box-shadow: 0 6px 20px rgba(0,0,0,.45); }
.tb-hi-head { font-family: var(--f-display); font-size: 10px; letter-spacing: .07em;
  text-transform: uppercase; color: var(--subtle); margin-bottom: 5px; }
.tb-hi-row { padding: 2px 0 2px 9px; border-left: 3px solid var(--hc, #888); margin: 4px 0; line-height: 1.35; }
.tb-hi-k { font-weight: 700; color: var(--hc, #fff); margin-right: 5px; }
.tb-hi-now { color: var(--subtle); font-size: 11px; white-space: nowrap; margin-left: 4px; }
.tb-hi-low { font-style: italic; opacity: .92; color: #e0a82e; }   /* thin-sample (obs) layers */
/* ── Coaching layer (Batch 4) ── */
/* LIVE row: a small "NOW" chip + a faint accent wash so the active threat reads
   first. PAST rows dim (the window has closed). Both override only when a clock
   is set; at full-game every row renders at full strength. */
.tb-hi-row.tb-hi-live { background: color-mix(in srgb, var(--hc, #888) 13%, transparent); border-radius: 0 6px 6px 0; }
.tb-hi-row.tb-hi-past { opacity: .5; }
.tb-hi-chip { display: inline-block; font-family: var(--f-display); font-size: 8.5px; font-weight: 800;
  letter-spacing: .08em; line-height: 1; padding: 2px 4px; margin-right: 5px; border-radius: 3px;
  color: #0a0d14; background: var(--hc, #fff); vertical-align: 1px; }
/* Scout-by clause — colour-graded vs the user's authored scout. */
.tb-sc { font-size: 11px; white-space: nowrap; font-weight: 600; }
.tb-sc-ok { color: #4ade80; }      /* scout already covers the deadline */
.tb-sc-late { color: #f87171; }    /* scout is later than the deadline (or aim earlier) */
.tb-sc-none { color: #e0a82e; }    /* no scout authored yet */
/* Empty-horizon honesty — a not-yet-on-the-map layer reads as upcoming, not blank. */
.tb-hi-future { font-size: 11px; font-style: italic; color: var(--subtle); white-space: nowrap; }
/* Plain-language takeaway sub-line (muted, derived from THAT layer's numbers). */
.tb-hi-take { font-size: 11px; color: var(--subtle); opacity: .92; margin-top: 1px; line-height: 1.3; }
/* Inline "next: M:SS ›" jump in the panel head — a tiny pill button. */
.tb-hi-next { pointer-events: auto; cursor: pointer; margin-left: 8px; font-family: var(--f-mono);
  font-size: 10px; letter-spacing: 0; text-transform: none; color: var(--text);
  background: var(--tb-pill-bg); border: 1px solid var(--tb-pill-border); border-radius: 5px;
  padding: 1px 6px; vertical-align: 1px; }
.tb-hi-next:hover { border-color: var(--accent); color: var(--accent); }
/* Reading hint only — the colour legend now lives on the topbar pills (chips). */
.tb-hi-foot { margin-top: 6px; padding-top: 5px; border-top: 1px solid rgba(255,255,255,.08);
  font-size: 10px; color: var(--subtle); }
/* Phone typography for the info panel. NOTE: the panel's POSITION on phone is the
   pinned bottom-sheet treatment appended in the Batch-5 section at file end
   (position/inset/max-height there override the desktop float above) — this rule
   only carries the smaller font so the two don't fight over geometry. */
@media (max-width: 767px) { .tb-root.tb-standalone .tb-heat-info { font-size: 11px; } }
/* ── Heat timeline (Batch 4): the promoted build-clock control ──
   A play/pause button + prev/next key-moment steppers around a ticked track,
   an MM:SS field, and a live "through M:SS / FULL GAME" cap. Reuses the scrub
   plumbing of the old .tb-timectrl (the data-time-scrub / data-time-read hooks
   are unchanged) — this is purely a richer shell around the same inputs. */
.tb-heat-timeline { display: inline-flex; align-items: center; gap: 7px; background: var(--tb-pill-bg);
  -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); border-radius: 7px; padding: 4px 9px; }
.tb-htl-play, .tb-htl-step { display: inline-flex; align-items: center; justify-content: center;
  background: var(--elevated); color: var(--text); border: 1px solid var(--border); border-radius: 5px;
  cursor: pointer; font-size: 12px; line-height: 1; padding: 0; }
.tb-htl-play { width: 26px; height: 24px; font-size: 11px; }
.tb-htl-step { width: 22px; height: 24px; font-size: 15px; }
.tb-htl-play:hover, .tb-htl-step:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.tb-htl-play.playing { border-color: var(--accent); color: var(--accent); }
.tb-htl-step:disabled { opacity: .4; cursor: default; }
/* Track wrapper: the range input plus an absolutely-positioned tick rail UNDER
   the thumb. pointer-events off on the rail so it never blocks the scrub drag. */
.tb-htl-track-wrap { position: relative; width: 200px; padding-bottom: 12px; }
.tb-time-scrub.tb-htl-scrub { width: 100%; display: block; }
.tb-htl-ticks { position: absolute; left: 0; right: 0; bottom: 0; height: 12px; pointer-events: none; }
.tb-htl-tick { position: absolute; transform: translateX(-50%); }
.tb-htl-tick > i { position: absolute; bottom: 8px; left: 50%; width: 1.5px; height: 6px; transform: translateX(-50%);
  background: var(--hc, var(--accent)); border-radius: 1px; }
/* Inline tick labels overlapped illegibly — on a ~10-min build squeezed into a
   200px track, "1st cheese / 1st push" and "main push / harass" collide into a
   garbled smear, and (pointer-events:none on the rail) they can't be hovered. Drop
   the inline labels entirely; the tick MARKS stay, and the moment names live in the
   info panel ("first push ~M:SS") + the ‹ › stepper that jumps between them. */
.tb-htl-tick > b { display: none; }
.tb-htl-tick.moment > i { background: var(--accent); }
/* Faint authored-step notches — no label, just a tick on the upper edge. */
.tb-htl-tick.step > i { bottom: 8px; height: 4px; width: 1px; background: var(--subtle); opacity: .5; }
.tb-time-scrub { width: 130px; accent-color: var(--accent); cursor: pointer; }
.tb-time-read { width: 52px; background: var(--elevated); color: var(--text); border: 1px solid var(--border);
  border-radius: 5px; font-family: var(--f-mono); font-size: 12px; padding: 3px 5px; text-align: center; }
.tb-time-read.tb-htl-read { width: 48px; }
.tb-htl-cap { font-family: var(--f-display); font-size: 9.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--subtle); white-space: nowrap; min-width: 64px; text-align: center; }
.tb-htl-cap.tb-htl-full { color: var(--accent); }
/* Phone: drop the labels (room is tight), shrink the track, let it wrap under
   the topbar cluster. The ticks' i-notches stay so position is still legible. */
@media (max-width: 767px) {
  .tb-heat-timeline { gap: 5px; padding: 3px 7px; }
  .tb-htl-track-wrap { width: 140px; padding-bottom: 7px; }
  .tb-htl-ticks { height: 7px; }
  .tb-htl-tick > b { display: none; }
  .tb-htl-tick > i { bottom: 1px; }
  .tb-htl-cap { min-width: 0; font-size: 9px; }
}

/* Disabled-tile feedback (MUST 1e): a building tile that can't be placed (no map).
   Reuses .locked's dim/grayscale; the 🗺 glyph + a not-allowed cursor distinguish
   "needs a map" from "tech-locked" (🔒). */
.tb-root .tb-pal-tile.tb-noplace { cursor: not-allowed; }
.tb-root .tb-pal-tile.tb-noplace .tb-pal-noplace { filter: none; opacity: .9; }

/* Transient toast (used by the no-place tile click + future standalone hints). */
.tb-toast-host { position: absolute; left: 50%; bottom: 28px; transform: translateX(-50%);
  z-index: 40; display: flex; flex-direction: column; gap: 8px; align-items: center; pointer-events: none; }
.tb-toast { background: var(--tb-glass-bg); -webkit-backdrop-filter: blur(var(--tb-glass-blur)); backdrop-filter: blur(var(--tb-glass-blur));
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--tb-glass-border)); color: var(--text);
  font-family: var(--f-ui); font-size: 12.5px; line-height: 1.4; padding: 9px 14px; border-radius: 8px; max-width: 360px;
  box-shadow: var(--tb-glass-shadow); opacity: 0; transform: translateY(8px); transition: opacity .22s ease, transform .22s ease; }
.tb-toast.in { opacity: 1; transform: translateY(0); }

/* ── Standalone responsive: collapse the rails over the stage on tablet/phone.
   Build placement is desktop-first (same rationale as the replay-overlay path),
   but the LIST editor + time/map controls must stay usable, so below 1024px we
   stack the stage above full-width rails instead of edge-floating them.

   Stage top offset: in the 768–1023 band the topbar still FLOATS (position:absolute,
   top:12px), so the absolutely-positioned stage must clear whatever height the
   topbar wraps to. Batch-5 VERIFIED finding (measured at 768–820px): with the
   PRO-HEAT cluster + eco-bar + 5 action pills the topbar reaches ~180px tall, so the
   old top:104px stage was overlapped by ~60px. FIX: raise the stage to 196px
   (12 + ~180 + gap) and re-derive the palette/side offsets to match. The heat
   cluster contributes ~38px (one row) of that height — the rest is pre-existing. ── */
@media (max-width: 1023px) and (min-width: 768px) {
  /* 196px clears the floating topbar at its REALISTIC worst case in this band:
     measured at 768–820px it reaches ~180px tall (identity + eco-bar + 5 action
     pills + heat-timeline + the PRO-HEAT cluster all wrapping; the heat cluster is
     ~38px / one row of that). top:12px + 180px ≈ 192px, so 196px gives the stage a
     small clearance gap. (Was 132px — too low; the topbar overlapped the stage by
     ~60px with heat on. See the Batch-5 finding note in the heat section below.) */
  .tb-root.tb-standalone .tb-stage { left: 12px; right: 12px; top: 196px; bottom: auto; height: 34vh; }
  .tb-root.tb-standalone .tb-palette { left: 12px; right: 12px; width: auto; top: calc(196px + 34vh + 12px); bottom: 12px; }
  .tb-root.tb-standalone .tb-side { left: 12px; right: 12px; width: auto; top: calc(196px + 34vh + 12px); bottom: 12px; }
  /* palette + side would overlap stacked; show the side (list) and let the palette
     scroll under it is fragile — instead make BOTH live in a single scroll column
     by dropping the palette to a slim strip is out of scope. Keep the side panel
     primary; the palette floats above it via z-index already (z:8). */
}
@media (max-width: 767px) {
  /* Phone: the stage is informational-only (placement is desktop). On phone the
     topbar is position:STATIC (normal flow) and — with the home/race/map row, the
     full-width PRO-HEAT toolbar, the heat-timeline, the eco-bar and the action
     pills all stacking — it is very TALL (measured ~600px+). An absolutely-
     positioned stage with any fixed `top` would float over the middle of that
     topbar (the pre-existing overlap). FIX: put the stage in normal FLOW too
     (position:static), so it sits naturally BELOW the topbar + branch bar and the
     palette/side follow it down the column. This removes every fragile top:calc()
     offset on phone and the topbar/stage overlap entirely. */
  .tb-root.tb-standalone .tb-stage {
    position: static;
    left: auto; right: auto; top: auto; bottom: auto;
    width: auto; height: 28vh; min-height: 180px;
    flex-shrink: 0;
    margin: 0 0 0;
  }
  /* Palette/side are already position:static (reset above) and flow after the
     stage; the desktop `top:` offsets don't apply to static elements, so no
     re-derivation is needed here. */
  #tb-standalone-host { height: calc(100vh - 56px); }
}

/* ════════════════════════════════════════════════════════════════════════════
   APPENDED — BATCH 5: mobile/responsive treatment for the PRO-HEAT controls.
   Three concerns, all scoped to the standalone heat feature (the only place
   _heatControlMarkup renders): (1) a phone-only full-width "heat toolbar" row;
   (2) the info readout as a pinned bottom sheet ≤767px; (3) the 768–1023 tablet
   band — keep the floating topbar from wrapping the heat cluster to a 3rd row
   over the stage. DESKTOP (≥1024px) is untouched (every rule is inside a
   max-width media query). Canonical breakpoints only (768 / 1024).
   ════════════════════════════════════════════════════════════════════════════ */

/* ── (3) Tablet band 768–1023: keep the wrapping floating topbar clear of the stage ──
   VERIFIED FINDING (Batch 5, measured in-browser at 768 & 820px): the topbar still
   FLOATS here (position:absolute, top:12px — the static reset is ≤767px only), and
   with the PRO-HEAT cluster + eco-bar + 5 action pills it wraps to ~180px tall. The
   old top:104px stage was therefore OVERLAPPED by ~60px (and ~22px even with the heat
   cluster hidden — i.e. the overlap is mostly pre-existing; the heat cluster adds the
   final ~38px / one row). FIX (applied in the @media(max-width:1023px)and(min-width:768px)
   block above): raise the stage top to 196px and re-derive the palette/side offsets,
   giving the floating topbar clearance even fully-wrapped. No row-forcing here:
   pinning the left group to its own 100% row was tried and REJECTED — it pushed the
   centre/action groups to wrap BELOW it, ADDING rows. Letting the topbar wrap
   naturally under the raised stage is the smaller, safer fix; the pill compression in
   the plain @media(max-width:1023px) block also keeps the wrap shallow. */

/* ── (1) Phone ≤767: dedicated full-width HEAT TOOLBAR row ──
   On phone the topbar is already position:static (normal flow). Promote the heat
   cluster to its OWN full-width row directly under the identity items by widening
   the left group and forcing the cluster to a 100%-basis, last-ordered flex line.
   It therefore sits in normal flow under the topbar with NO overlap of the map
   stage. The opponent <select> + 4 layer toggles + CB pill all wrap within the
   row (no horizontal overflow) and every tap target is ≥44px tall. ── */
@media (max-width: 767px) {
  /* Left group spans the row so the cluster can claim a full line below the
     home/race/map items. */
  .tb-root.tb-standalone .tb-group-left { width: 100%; flex-wrap: wrap; row-gap: 8px; }
  .tb-root.tb-standalone .tb-heatctrl {
    flex: 1 1 100%;
    order: 9;                         /* drops below home/race/map within the group */
    width: 100%;
    /* Read as a flat full-width toolbar bar, not a floating glass pill. */
    background: var(--elevated);
    -webkit-backdrop-filter: none; backdrop-filter: none;
    border: 1px solid var(--border);
    border-radius: 8px;
    gap: 8px; row-gap: 8px;
    padding: 8px;
    justify-content: flex-start;
    align-items: stretch;
    /* No internal horizontal scroll — toggles wrap onto more rows as needed. */
    overflow: visible;
  }
  /* The lead label was hidden at ≤1023; bring it back as a full-width caption so
     the toolbar reads as "PRO HEAT" without eating a tap slot. */
  .tb-root.tb-standalone .tb-heat-lead {
    display: block; flex: 1 1 100%; width: 100%; margin: 0 0 1px; font-size: 11px;
  }
  /* Opponent select: comfortable 44px tap target, takes the full first line. */
  .tb-root.tb-standalone .tb-heat-opp {
    flex: 1 1 100%; min-height: 44px; font-size: 13px; padding: 6px 8px;
    touch-action: manipulation;
  }
  /* The four layer toggles share a 2-up grid-ish flex so they never overflow a
     narrow screen; each is ≥44px tall for the finger. */
  .tb-root.tb-standalone .tb-heat-toggle {
    flex: 1 1 calc(50% - 4px); min-height: 44px; justify-content: center;
    font-size: 12px; padding: 6px 8px; touch-action: manipulation;
  }
  /* Legend swatch on each toggle a touch larger on phone (Part 3 CSS half) so the
     colour cue is readable at arm's length. */
  .tb-root.tb-standalone .tb-heat-toggle .tb-heat-chip { width: 11px; height: 11px; }
  /* CB pill takes a full final line so it doesn't strand a lone half-width toggle. */
  .tb-root.tb-standalone .tb-heat-cb {
    flex: 1 1 100%; min-height: 44px; font-size: 11px; padding: 6px 10px;
    touch-action: manipulation;
  }
  /* Empty-state variant: keep it compact (no full-width promotion needed). */
  .tb-root.tb-standalone .tb-heatctrl.tb-heat-empty { flex: 0 1 auto; width: auto; padding: 6px 10px; }

  /* The standalone heat-timeline is in the center group; let it span its row too
     so play/scrub/step controls get a comfortable full-width line on phone. */
  .tb-root.tb-standalone .tb-topbar-center { width: 100%; flex-wrap: wrap; }
  .tb-root.tb-standalone .tb-heat-timeline { flex: 1 1 100%; }
  /* Bump the timeline's transport buttons to a real tap target on phone. */
  .tb-root.tb-standalone .tb-htl-play,
  .tb-root.tb-standalone .tb-htl-step { min-height: 36px; touch-action: manipulation; }
  .tb-root.tb-standalone .tb-htl-track-wrap { flex: 1 1 120px; }
  .tb-root.tb-standalone .tb-time-read.tb-htl-read { min-height: 36px; }
}

/* ── (2) Phone ≤767: info readout as a pinned BOTTOM SHEET ──
   Desktop floats .tb-heat-info inside the stage (bottom-center). On phone the
   stage is only ~28vh in the upper area, so an in-stage box would be cramped and
   could sit over the (small) map. Lift it to a fixed bottom sheet mirroring the
   .tb-modal sheet language: full-width, pinned to the viewport bottom, rounded
   top, capped height with internal scroll, and PEEKABLE (translucent, pointer
   pass-through except its own interactive bits). It never covers the whole map
   (max-height caps it ~40vh). Desktop (≥768) keeps the in-stage box. ── */
@media (max-width: 767px) {
  .tb-root.tb-standalone .tb-heat-info {
    position: fixed;                   /* pin to the viewport, not the 28vh stage */
    left: 0; right: 0; bottom: 0;
    top: auto; transform: none;
    width: 100%; max-width: 100%;
    max-height: 40vh;                  /* cap so the map below stays visible/peekable */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    z-index: var(--z-modal, 1000);     /* above the rails, like the modal sheet */
    border-radius: 14px 14px 0 0;      /* rounded top corners only (sheet) */
    border-left: none; border-right: none; border-bottom: none;
    box-shadow: 0 -12px 40px rgba(0,0,0,.6);
    /* Slightly more opaque than the desktop float so text stays legible against
       whatever scrolls behind, while still letting the map peek through the gap. */
    background: rgba(8,10,18,.94);
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
    /* Interactive on phone (UNLIKE the desktop float, which is pointer-events:none
       over the large map centre): the sheet is pinned to the bottom ~40vh, so the
       MAP STILL OCCUPIES THE AREA ABOVE IT and stays tappable — and the sheet itself
       must capture touch to scroll its capped overflow + drive its "next ›" jump.
       touch-action:pan-y keeps the vertical scroll snappy (no 300ms tap delay). */
    pointer-events: auto;
    touch-action: pan-y;
    overscroll-behavior: contain;       /* don't chain the scroll to the page */
  }
  /* A grab-handle affordance so the sheet reads as dismissible/peekable (visual
     only — the cap + scroll already keep the map reachable around it). */
  .tb-root.tb-standalone .tb-heat-info::before {
    content: ""; display: block; width: 36px; height: 4px; margin: 0 auto 8px;
    border-radius: 2px; background: rgba(255,255,255,.22);
  }
  /* Legend swatches a touch larger on the bottom sheet (Part 3 CSS half). */
  .tb-root.tb-standalone .tb-heat-info .tb-hi-row { padding-left: 11px; border-left-width: 4px; }
}

/* ════════════════════════════════════════════════════════════════════════════
   ⛓ CONVEYOR / LANES timeline view — a full-bleed Gantt overlay that
   generalizes the production strip (.tb-ps-*) across ALL producing facilities,
   grouped into ECONOMY / PRODUCTION / TECH bands on one shared time axis, with
   a live stats bar + playhead. The CARDS reuse .tb-ps-blk / .tb-ps-ch /
   .tb-ps-stack / its state variants VERBATIM (see ~line 522-542); only the shell,
   header, stats bar, ruler, bands, lane gutters, playhead, and connectors are new.
   ════════════════════════════════════════════════════════════════════════════ */

/* Topbar toggle — cloned from .tb-workers-toggle (.on-class active state). */
.tb-conveyor-toggle { display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--f-ui); font-size: 11px; font-weight: 600; color: var(--muted);
  background: var(--elevated); border: 1px solid var(--border); border-radius: 6px;
  padding: 5px 10px; cursor: pointer; white-space: nowrap;
  transition: color 120ms ease, border-color 120ms ease; }
.tb-conveyor-toggle:hover { color: var(--text); border-color: var(--border-strong); }
.tb-conveyor-toggle.on { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }

/* Overlay shell — same z-layer as the modal backdrop; covers the whole stage. */
/* NO entry animation on the overlay/panel: render() rebuilds this DOM on every edit
   (and scrubbing renders rapidly), so an entry animation re-fires each time → a
   whole-screen flash. The overlay simply appears. */
.tb-cv-overlay { position: absolute; inset: 0; z-index: var(--z-modal);
  display: flex; align-items: center; justify-content: center; padding: 16px; }
.tb-cv-backdrop { position: absolute; inset: 0;
  background: color-mix(in srgb, var(--color-bg) 70%, transparent);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
/* Panel is a CENTERED card that sizes to its content (capped on big displays) so
   short builds don't leave a tall empty gap with a detached horizontal scrollbar. */
.tb-cv-panel { position: relative; flex: 0 1 auto; width: 100%; max-width: 1600px; max-height: 100%;
  display: flex; flex-direction: column; min-width: 0;
  background: var(--tb-glass-bg, var(--raised));
  border: 1px solid var(--tb-glass-border, var(--border-strong));
  border-radius: var(--tb-glass-radius, 12px);
  box-shadow: var(--tb-glass-shadow, 0 24px 64px rgba(0,0,0,.7));
  overflow: hidden; }

/* Header: title block · stats bar · map name · close. */
.tb-cv-header { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 14px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.tb-cv-title { min-width: 0; }
.tb-cv-name { font-family: var(--f-display); font-size: 15px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tb-cv-sub { font-family: var(--f-ui); font-size: 10px; font-weight: 500; color: var(--subtle); }
.tb-cv-map { margin-left: auto; font-family: var(--f-display); font-size: 11px;
  font-weight: 600; color: var(--muted); white-space: nowrap; }
.tb-cv-close { background: none; border: none; color: var(--muted); font-size: 15px;
  cursor: pointer; padding: 4px 8px; line-height: 1; border-radius: 6px; }
.tb-cv-close:hover { color: var(--text); background: var(--elevated); }

/* Stats bar — chips clone the eco-bar look (tabular-nums, mono). */
.tb-cv-stats { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.tb-cv-chip { display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--f-mono); white-space: nowrap; }
.tb-cv-chip img { width: 16px; height: 16px; border-radius: 3px; }
.tb-cv-chip .tb-cv-ic { font-size: 13px; line-height: 1; color: var(--warn); }
.tb-cv-chip b { font-weight: 700; font-size: 13px; font-variant-numeric: tabular-nums; color: var(--text); }
.tb-cv-chip i { font-style: normal; font-size: 9px; color: var(--subtle); font-variant-numeric: tabular-nums; }
.tb-cv-chip.min b { color: var(--min); }
.tb-cv-chip.gas b { color: var(--gas); }
.tb-cv-chip.sup b { color: var(--warn); }
.tb-cv-chip.army b { color: var(--prod); }
.tb-cv-chip.mule b, .tb-cv-chip.larva b { color: var(--tech); }
.tb-cv-mode { font-family: var(--f-mono); font-size: 9px; font-weight: 700; letter-spacing: .08em;
  padding: 2px 6px; border-radius: 4px; background: var(--bg); color: var(--warn); }

/* Shared scroll region — ruler + bands + playhead scroll together horizontally.
   flex:1 1 auto + min-height:0 lets it size to content yet shrink+scroll vertically
   when the build has too many lanes to fit; thin scrollbars keep it tidy. */
.tb-cv-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain; touch-action: pan-x pan-y;
  scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.tb-cv-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.tb-cv-scroll::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 6px;
  border: 2px solid transparent; background-clip: content-box; }
.tb-cv-scroll::-webkit-scrollbar-track { background: transparent; }
.tb-cv-inner { position: relative; }
.tb-cv-axis { position: sticky; top: 0; z-index: 4; background: var(--bg); }
.tb-cv-ruler { position: relative; height: 20px; border-bottom: 1px solid var(--border); }
.tb-cv-tick { position: absolute; top: 0; bottom: 0; border-left: 1px solid var(--border); }
.tb-cv-tick span { font-family: var(--f-mono); font-size: 9px; color: var(--subtle); padding-left: 4px; }
.tb-cv-suprow { position: relative; height: 16px; border-bottom: 1px solid var(--border); }
.tb-cv-supchip { position: absolute; transform: translateX(2px);
  font-family: var(--f-mono); font-size: 9px; color: var(--muted); }

/* Body + category bands. */
.tb-cv-body { position: relative; }
.tb-cv-band { position: relative; z-index: 1; }   /* above the connector SVG (z-index:0) */
.tb-cv-band-label { position: sticky; left: 0; z-index: 3; height: 20px;
  display: flex; align-items: center; padding-left: 8px;
  font-family: var(--f-display); font-size: 9px; font-weight: 700; letter-spacing: .12em;
  color: var(--cv-accent, var(--muted)); border-bottom: 1px solid var(--border);
  background: linear-gradient(90deg, color-mix(in srgb, var(--cv-accent, var(--muted)) 14%, var(--bg)), var(--bg)); }
.tb-cv-band-lanes { position: relative; }

/* Per-facility lane: sticky left gutter + a track of cards. */
.tb-cv-lane { position: relative; border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); }
.tb-cv-lane-label { position: sticky; left: 0; z-index: 2; height: 100%;
  display: inline-flex; align-items: center; gap: 5px; padding: 0 6px;
  font-family: var(--f-display); font-size: 10px; font-weight: 600; color: var(--muted);
  background: var(--elevated); border-right: 1px solid var(--border-strong);
  white-space: nowrap; overflow: hidden; }
.tb-cv-lane-label img { width: 18px; height: 18px; border-radius: 3px; flex-shrink: 0; }
.tb-cv-lane-name { overflow: hidden; text-overflow: ellipsis; }
.tb-cv-addon { display: inline-flex; margin-left: 2px; }
.tb-cv-addon img { width: 13px; height: 13px; border-radius: 2px; }
.tb-cv-lane-track { position: absolute; top: 0; right: 0; bottom: 0; left: 0; }
.tb-cv-morph { position: absolute; top: 0; bottom: 0; width: 0;
  border-left: 1px dashed var(--accent); opacity: .5; pointer-events: none; }

/* MORPH cards (CC→Orbital, Hatch→Lair→Hive, Gateway→Warp Gate) — a teal "transform"
   accent so they read distinctly from trained units on the same lane. */
.tb-cv-morphcard { border-left-color: #2bd1c4 !important;
  background: linear-gradient(180deg, color-mix(in srgb, #2bd1c4 24%, transparent), transparent) !important;
  box-shadow: 0 0 0 1px color-mix(in srgb, #2bd1c4 55%, transparent), 0 2px 6px rgba(0,0,0,.45) !important; }
.tb-cv-up-ic { font-size: 10px; line-height: 1; margin-right: 1px; flex-shrink: 0; opacity: .9; }

/* STRUCTURE cards — the building's own construction (Barracks/Factory/CC/Depot).
   A warm amber "build" accent so it reads as the structure itself, not a unit. */
.tb-cv-structcard { border-left-color: #e0a13a !important;
  background: linear-gradient(180deg, color-mix(in srgb, #e0a13a 20%, transparent), transparent) !important; }

/* UPGRADE cards — cyan research accent + the ▲ glyph (distinct from units). */
.tb-cv-upgrade { border-left-color: #46a0ff !important;
  background: linear-gradient(180deg, color-mix(in srgb, #46a0ff 20%, transparent), transparent) !important; }
.tb-cv-upgrade .tb-cv-up-ic { color: #8cc6ff; }

/* WORKER cards — inline SCV / Probe / Drone cards on the townhall row. Muted so the
   continuous worker stream stays visually quieter than army/morph cards. */
.tb-cv-wkcard { border-left-color: color-mix(in srgb, var(--subtle) 70%, transparent) !important;
  opacity: .82; }
.tb-cv-wkcard.building { opacity: 1; }

/* Affordability / prereq warning on a card (sim-derived). */
.tb-cv-warn { box-shadow: 0 0 0 1px var(--danger) inset, 0 2px 6px rgba(0,0,0,.4); }

/* Playhead — clone of .tb-ps-head with a time label. */
.tb-cv-head { position: absolute; top: 0; bottom: 0; width: 2px; background: #fff;
  box-shadow: 0 0 8px rgba(255,255,255,.5); z-index: 5; pointer-events: none; }
.tb-cv-head-lbl { position: absolute; top: 0; left: 4px; font-family: var(--f-mono);
  font-size: 9px; font-weight: 700; color: #fff; background: rgba(0,0,0,.6);
  padding: 1px 4px; border-radius: 3px; white-space: nowrap; }

/* Connectors (DISABLED in v1 — styled for the future curated pass). */
.tb-cv-conn { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 0; overflow: visible; }
.tb-cv-conn path { stroke: color-mix(in srgb, var(--accent, #7c5cff) 70%, #fff 0%); stroke-width: 1.25;
  stroke-dasharray: 4 4; fill: none; opacity: .55; }

/* Empty state. */
.tb-cv-empty { padding: 40px 16px; text-align: center; font-family: var(--f-ui);
  font-size: 13px; color: var(--subtle); }

/* Lifecycle marks (✈ relocate/flight, ⇄ add-on swap) + the header toggle. */
.tb-cv-lifemark { position: absolute; top: 0; bottom: 0; width: 0; z-index: 3; pointer-events: none; border-left: 1px dashed; }
.tb-cv-lifemark span { position: absolute; top: -1px; left: -7px; font-size: 11px; line-height: 1;
  pointer-events: auto; cursor: help; text-shadow: 0 1px 2px rgba(0,0,0,.85); }
.tb-cv-lifemark.fly { border-left-color: #f0b243; } .tb-cv-lifemark.fly span { color: #f6c869; }
.tb-cv-lifemark.swap { border-left-color: #2bd1c4; } .tb-cv-lifemark.swap span { color: #5fe3d8; }
.tb-cv-lifebtn { background: none; border: 1px solid var(--border); color: var(--muted); font-size: 10px;
  font-family: var(--f-ui); cursor: pointer; padding: 3px 8px; border-radius: 6px; white-space: nowrap; }
.tb-cv-lifebtn:hover { color: var(--text); }
.tb-cv-lifebtn.on { color: var(--text); border-color: color-mix(in srgb, #f0b243 60%, var(--border));
  background: color-mix(in srgb, #f0b243 12%, transparent); }

/* Tech-Lab RESEARCH sub-row — its own labelled line for the add-on's upgrades. */
.tb-cv-research { position: absolute; left: 0; right: 0; display: flex; align-items: center; gap: 8px;
  padding-left: 5px; border-radius: 4px; pointer-events: none; overflow: hidden;
  background: color-mix(in srgb, #46a0ff 8%, transparent);
  border: 1px dashed color-mix(in srgb, #46a0ff 32%, transparent); }
.tb-cv-research-lbl { font-family: var(--f-display); font-size: 8px; font-weight: 700; letter-spacing: .07em;
  color: color-mix(in srgb, #8cc6ff 85%, var(--muted)); text-transform: uppercase; white-space: nowrap; }
.tb-cv-research-hint { font-family: var(--f-ui); font-size: 9px; color: var(--subtle); font-style: italic; white-space: nowrap; }

/* Card edit affordances — drag a card to re-time, right-click to delete. */
.tb-cv-editable { cursor: grab; }
.tb-cv-editable:hover { box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent, #7c5cff) 60%, transparent), 0 2px 6px rgba(0,0,0,.4); }
.tb-cv-dragging { cursor: grabbing !important; opacity: .9; box-shadow: 0 5px 16px rgba(0,0,0,.55); }

/* BOTTOM PALETTE — drag a unit/upgrade/morph/add-on tile onto its lane (or tap to add). */
.tb-cv-pal { flex: 0 0 auto; border-top: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg-elevated, #14141f) 85%, transparent);
  padding: 5px 8px 7px; }
.tb-cv-pal-hint { font-family: var(--f-ui); font-size: 10px; color: var(--subtle); margin: 0 0 4px 2px; }
.tb-cv-pal-scroll { display: flex; gap: 14px; overflow-x: auto; padding-bottom: 2px;
  -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
  scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.tb-cv-pal-scroll::-webkit-scrollbar { height: 7px; }
.tb-cv-pal-scroll::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 5px; }
.tb-cv-pal-scroll::-webkit-scrollbar-track { background: transparent; }
.tb-cv-pal-group { display: flex; flex-direction: column; gap: 4px; flex: 0 0 auto; }
.tb-cv-pal-glabel { font-family: var(--f-display); font-size: 8px; font-weight: 700; letter-spacing: .1em;
  color: var(--muted); text-transform: uppercase; }
.tb-cv-pal-tiles { display: flex; gap: 5px; }
.tb-cv-pal-tile { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; cursor: grab;
  padding: 4px 8px 4px 4px; border-radius: 7px; border: 1px solid var(--border);
  background: color-mix(in srgb, var(--accent, #7c5cff) 7%, var(--bg)); color: var(--text);
  font-family: var(--f-ui); font-size: 11px; white-space: nowrap; touch-action: none; user-select: none; }
.tb-cv-pal-tile:hover { border-color: var(--accent, #7c5cff); background: color-mix(in srgb, var(--accent, #7c5cff) 16%, var(--bg)); }
.tb-cv-pal-tile img { width: 18px; height: 18px; border-radius: 3px; pointer-events: none; }
.tb-cv-pal-tile span { pointer-events: none; }
/* Floating drag ghost (follows the cursor). */
.tb-cv-pal-ghost { position: fixed; z-index: var(--z-tooltip, 4000); transform: translate(-50%, -130%);
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 8px; border-radius: 7px;
  background: color-mix(in srgb, var(--accent, #7c5cff) 30%, #000); color: #fff; font-size: 11px;
  box-shadow: 0 6px 18px rgba(0,0,0,.5); pointer-events: none; opacity: .95; }
.tb-cv-pal-ghost img { width: 18px; height: 18px; border-radius: 3px; }
/* Valid drop-target lane during a drag. */
.tb-cv-droptarget { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent, #7c5cff) 80%, #fff);
  background: color-mix(in srgb, var(--accent, #7c5cff) 10%, transparent); }

/* ≤767px — full-bleed sheet, stacked stats, horizontal scroll preserved. */
@media (max-width: 767px) {
  .tb-cv-overlay { padding: 0; }
  .tb-cv-panel { border-radius: 0; }
  .tb-cv-header { gap: 8px; padding: 8px 10px; }
  .tb-cv-name { font-size: 13px; }
  .tb-cv-map { display: none; }
  .tb-cv-stats { width: 100%; order: 3; gap: 9px; }
  .tb-cv-chip i { display: none; }   /* drop the +rate / breakdown sub-values on phones */
  .tb-cv-lane-label { font-size: 9px; }
}

/* ============================================================================
   Step notes (right-click annotation). Reuses the analyst radial visual language
   (css/replay-radial.css) for the capture card; these rules cover the always-on
   REMINDERS: the build-list row pill + accent, and the LANES card / lane dots.
   The --nc category tokens MIRROR the TB_NOTE_CATS colors in tb-note-radial.js —
   keep them in sync if a color changes.
   ============================================================================ */
.tb-root [data-note-cat="reminder"]  { --nc: #56c2ff; }
.tb-root [data-note-cat="timing"]    { --nc: #4ade80; }
.tb-root [data-note-cat="scout"]     { --nc: #7dd3fc; }
.tb-root [data-note-cat="reaction"]  { --nc: #fbbf24; }
.tb-root [data-note-cat="variation"] { --nc: #a78bfa; }
.tb-root [data-note-cat="warning"]   { --nc: #f87171; }

/* Build-list row pill — always visible, category-colored; click re-opens the editor. */
.tb-row-note {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 15px; margin-left: 5px; padding: 0; vertical-align: -2px;
  border-radius: 4px; cursor: pointer; flex-shrink: 0;
  color: var(--nc, var(--accent));
  background: color-mix(in srgb, var(--nc, var(--accent)) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--nc, var(--accent)) 55%, transparent);
  transition: background-color .12s ease, transform .12s ease;
}
.tb-row-note svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; display: block; }
.tb-row-note:hover { background: color-mix(in srgb, var(--nc, var(--accent)) 32%, transparent); transform: translateY(-1px); }
.tb-row-note:active { transform: translateY(0); }

/* Subtle row marker so an annotated step still reads if a long name ellipsizes the pill. */
.tb-row.has-note { background: color-mix(in srgb, var(--nc, var(--accent)) 6%, transparent); }
.tb-row.has-note::before {
  content: ''; position: absolute; left: 0; top: 4px; bottom: 4px; width: 2px;
  border-radius: 2px; background: var(--nc, var(--accent)); opacity: .8;
}

/* LANES — note dot on a produced card (top-right corner) … */
.tb-cv-note {
  position: absolute; top: 2px; right: 2px; width: 7px; height: 7px; border-radius: 50%;
  background: var(--nc, var(--accent));
  box-shadow: 0 0 0 1.5px rgba(7,16,28,.85), 0 0 6px var(--nc, var(--accent));
  cursor: pointer; z-index: 3;
}
/* … and on a lane gutter (the producing building's own note). */
.tb-cv-lane-note {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-left: 4px; flex-shrink: 0;
  background: var(--nc, var(--accent)); box-shadow: 0 0 6px var(--nc, var(--accent)); cursor: pointer;
}
.tb-cv-lane.tb-cv-hasnote .tb-cv-lane-name {
  text-decoration: underline dotted color-mix(in srgb, var(--nc, var(--accent)) 70%, transparent);
  text-underline-offset: 2px;
}

/* The note radial is body-mounted chrome — float it above the full-screen editor
   (the analyst's z-index:30 is scoped to the canvas wrapper, too low here). */
.replay-radial-overlay.tb-note-radial { z-index: 100000; }
/* Delete affordance in the note-editor footer (shown only when editing an existing
   note); margin-right:auto pushes the hint + Save to the right. */
.tb-note-del {
  margin-right: auto; background: transparent; color: #f87171;
  border: 1px solid color-mix(in srgb, #f87171 45%, transparent); border-radius: 4px;
  padding: 4px 10px; font-size: 10px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; cursor: pointer; font-family: var(--display, sans-serif);
}
.tb-note-del:hover { background: color-mix(in srgb, #f87171 16%, transparent); }

/* ============================================================================
   APPENDED — STACKED STANDALONE TOPBAR (2026-06-10 UI cleanup).
   The standalone editor's topbar previously free-wrapped: the centre group
   (heat timeline + eco bar) floated mid-air between the left controls and the
   action pills, breaking onto ragged extra rows with inconsistent pill heights.
   render() now emits an explicit TWO-ROW structure in standalone mode only
   (.tb-topbar-stacked — replay-mounted keeps the original single-row markup):
     row 1 (.tb-topbar-chrome)      = setup cluster left, action pills right
     row 2 (.tb-topbar-console-row) = ONE centered glass console fusing the
                                      heat timeline + economy bar
   _fitChrome() still measures the real bottom edge, so the rails/branchbar
   keep clearing it.
   ============================================================================ */
.tb-topbar.tb-topbar-stacked { flex-direction: column; align-items: stretch; gap: 8px; }
.tb-topbar-stacked .tb-topbar-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.tb-topbar-chrome { flex-wrap: wrap; row-gap: 6px; }
.tb-topbar-console-row { justify-content: center; }
/* The full-width rows must NOT block the map behind the gaps (the generic
   `.tb-topbar > *` rules re-enable pointer-events on topbar children, which now
   includes these rows). Rows pass through; their content re-enables; the spacer
   stays click-through like the row it sits in. */
.tb-root.tb-standalone .tb-topbar-stacked > .tb-topbar-row { pointer-events: none; }
.tb-root.tb-standalone .tb-topbar-row > * { pointer-events: auto; }
.tb-root.tb-standalone .tb-topbar-row > .tb-spacer { pointer-events: none; }

/* The console: one glass panel for clock + economy, replacing two free-floating
   pills. Internal divider separates the timeline from the eco chips. */
.tb-console { display: inline-flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 6px 14px; min-width: 0; max-width: 100%;
  background: var(--tb-pill-bg);
  -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); border-radius: 9px; padding: 5px 14px; }
.tb-console:empty { display: none; }
/* Nested pills go FLAT inside the console (it carries the glass now). */
.tb-console .tb-heat-timeline,
.tb-console .tb-eco-bar { background: transparent; border: none; border-radius: 0;
  -webkit-backdrop-filter: none; backdrop-filter: none; padding: 0; }
.tb-console .tb-eco-bar { padding-left: 14px; border-left: 1px solid var(--border-strong); }
/* The scrub track earns real width on its dedicated row (base width is 200px,
   sized for the old shared row). */
.tb-console .tb-htl-track-wrap { width: clamp(220px, 26vw, 380px); }

/* Uniform control height across the chrome row — the mixed 24/28/30px pill
   heights were a big part of the "ragged" read. 32px lines everything up. */
.tb-topbar-stacked .tb-home,
.tb-topbar-stacked .tb-mapsel-wrap,
.tb-topbar-stacked .tb-heatctrl,
.tb-topbar-stacked .tb-dd-btn,
.tb-topbar-stacked .tb-newbtn,
.tb-topbar-stacked .tb-conveyor-toggle,
.tb-topbar-stacked .tb-raceswitch.replay-viewmode-group,
.tb-topbar-stacked .tb-oppswitch.replay-viewmode-group { min-height: 32px; box-sizing: border-box; }
.tb-topbar-stacked .tb-undobtn { width: 32px; height: 32px; }
/* When the chrome row DOES wrap (narrow desktop), the action cluster lands on its
   own flex line — margin-left:auto keeps it right-aligned there instead of the
   broken-looking left-packed second row (the spacer only works on a shared line). */
.tb-topbar-stacked .tb-actions { margin-left: auto; }

/* ── Setup chips (matchup + map·patch): the race/opponent/map/patch controls
   collapsed into two tb-dd chips so the chrome row stays short. The chip labels
   are the at-a-glance read ("T v Z", "Winter Madness LE · 5.0.15"); the panels
   hold the original controls, stacked vertically with micro-captions. ── */
.tb-matchup-btn b { color: var(--rc, var(--text)); font-weight: 800; font-size: 12.5px; font-family: var(--f-display); }
.tb-mu-v { opacity: .5; font-size: 10px; margin: 0 2px; }
.tb-map-btn .tb-map-name { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-map-btn .tb-map-patch { opacity: .55; font-size: 10px; margin-left: 2px; }
.tb-dd-panel.tb-setup-panel { flex-direction: column; align-items: stretch; gap: 6px; min-width: 248px; }
.tb-setup-panel .tb-dd-row-lbl { font-family: var(--f-display); font-size: 9.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--subtle); margin: 3px 1px 0; }
.tb-setup-panel .tb-dd-row-lbl:first-child { margin-top: 0; }
/* Panel selects: full-width, comfortable hit target (the topbar pill clamp is off). */
.tb-setup-panel select.tb-mapsel { width: 100%; max-width: none; min-height: 34px; }
/* Race / opponent segmented switches stretch edge-to-edge inside the panel. */
.tb-setup-panel .tb-raceswitch,
.tb-setup-panel .tb-oppswitch { display: flex; width: 100%; }
.tb-setup-panel .replay-viewmode-btn { flex: 1 1 0; justify-content: center; min-height: 34px; }

/* ════════════════════════════════════════════════════════════════════════════
   APPENDED — side-panel NOTES & STATS tabs. Aggregated step notes + guide
   coaching (Notes) and live benchmark/checkpoint/macro numbers (Stats), both
   fed by the Learn-tab guide corpus. Rows carrying data-sn-seek are clickable
   (seek the build clock); data-sn-t rows dim once the clock passes them.
   ════════════════════════════════════════════════════════════════════════════ */
.tb-sn-wrap { padding: 4px 2px 14px; display: flex; flex-direction: column; gap: 12px; }
.tb-sn-sec { display: flex; flex-direction: column; gap: 2px; }
.tb-sn-cap { font-family: var(--f-display); font-size: 9.5px; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--subtle); margin: 0 4px 3px; }
.tb-sn-row { display: flex; align-items: baseline; gap: 7px; padding: 5px 6px; border-radius: 6px;
  font-size: 11.5px; line-height: 1.35; }
.tb-sn-row[data-sn-seek] { cursor: pointer; }
.tb-sn-row[data-sn-seek]:hover { background: var(--overlay, rgba(255,255,255,.05)); }
.tb-sn-time { font-family: var(--f-mono); font-size: 10px; color: var(--accent); flex: 0 0 auto;
  font-variant-numeric: tabular-nums; min-width: 34px; }
.tb-sn-txt { min-width: 0; color: var(--text); }
.tb-sn-txt b { font-weight: 700; }
.tb-sn-ctx { display: block; font-style: normal; font-size: 10px; color: var(--subtle); margin-top: 1px; }
.tb-sn-gap { display: block; font-style: normal; font-size: 10px; color: var(--warn); margin-top: 1px; }
.tb-sn-past { opacity: .45; }
.tb-sn-empty { font-size: 11px; color: var(--subtle); padding: 4px 6px; line-height: 1.45; }
/* User-note rows reuse the [data-note-cat] --nc tokens defined for the list pills. */
.tb-sn-note .tb-sn-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; align-self: center;
  background: var(--nc, var(--accent)); box-shadow: 0 0 5px color-mix(in srgb, var(--nc, var(--accent)) 70%, transparent); }
.tb-sn-note { border-left: 2px solid color-mix(in srgb, var(--nc, var(--accent)) 65%, transparent); }
/* Matched-archetype header card */
.tb-sn-arch { padding: 8px 9px; border: 1px solid var(--border); border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 6%, transparent); display: flex; flex-direction: column; gap: 4px; }
.tb-sn-arch-name { font-family: var(--f-display); font-size: 12px; font-weight: 700; }
.tb-sn-arch-meta { font-size: 10px; color: var(--accent); font-variant-numeric: tabular-nums; }
.tb-sn-p { font-size: 11px; color: var(--muted); line-height: 1.4; }
.tb-sn-p.tb-sn-weak { color: var(--warn); }
/* Stats: timing-delta chip (early / on pace / late / very late) */
.tb-st-d { margin-left: auto; flex: 0 0 auto; font-family: var(--f-mono); font-size: 10px; font-weight: 700;
  padding: 1px 5px; border-radius: 4px; font-variant-numeric: tabular-nums; }
.tb-st-d.ontime { color: #4ade80; background: color-mix(in srgb, #4ade80 14%, transparent); }
.tb-st-d.early  { color: #7dd3fc; background: color-mix(in srgb, #7dd3fc 14%, transparent); }
.tb-st-d.late   { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.tb-st-d.vlate  { color: #f87171; background: color-mix(in srgb, #f87171 16%, transparent); }
/* Stats: checkpoint mini-table (time | workers | supply | army) */
.tb-st-grid { display: grid; grid-template-columns: 38px 1fr 1fr 1fr; gap: 4px 8px; align-items: baseline;
  padding: 4px 6px; border-radius: 6px; font-size: 11px; font-variant-numeric: tabular-nums; }
.tb-st-grid[data-sn-seek] { cursor: pointer; }
.tb-st-grid[data-sn-seek]:hover { background: var(--overlay, rgba(255,255,255,.05)); }
.tb-st-grid.tb-st-head { font-family: var(--f-display); font-size: 9px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--subtle); padding-bottom: 0; }
.tb-st-grid .tb-st-d { margin-left: 4px; }
.tb-st-pro { font-style: normal; color: var(--subtle); font-size: 10px; }
.tb-st-legend { padding-top: 2px; }
/* Stats: macro report flags */
.tb-st-flag { flex: 0 0 auto; width: 15px; height: 15px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; font-size: 9px; font-weight: 800; align-self: center; }
.tb-st-flag.ok   { color: #4ade80; background: color-mix(in srgb, #4ade80 16%, transparent); }
.tb-st-flag.warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 18%, transparent); }

/* ════════════════════════════════════════════════════════════════════════════
   APPENDED — bottom toolbar + collapsible branches bar + New-build dialog
   fields (2026-06-10 user-requested layout pass). The undo/redo + Workers +
   Lanes + Pro Heat controls move from the topbar to a floating bottom-center
   glass bar over the map (standalone only); the branches bar gains a
   horizontal collapse pill; the New-build dialog hosts the map/patch pickers
   and the pro-template dropdown that left the topbar.
   ════════════════════════════════════════════════════════════════════════════ */
.tb-root.tb-standalone .tb-bottombar {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 14px; z-index: 9;
  display: inline-flex; align-items: center; gap: 6px; max-width: min(92vw, 760px);
  padding: 5px 10px; background: var(--tb-pill-bg);
  -webkit-backdrop-filter: blur(var(--tb-pill-blur)); backdrop-filter: blur(var(--tb-pill-blur));
  border: 1px solid var(--tb-pill-border); border-radius: 9px; pointer-events: auto;
}
/* Dropdown panels anchored in the bottom bar open UPWARD (downward would run
   off the viewport bottom). */
.tb-bottombar .tb-dd > .tb-dd-panel { top: auto; bottom: calc(100% + 6px); }
/* The heat-info readout also floats bottom-center — lift it above the toolbar. */
.tb-root.tb-standalone .tb-heat-info { bottom: 64px; }
/* Phone: the editor is a static stack (no floating map surface to anchor to) —
   the bottom bar becomes a normal-flow toolbar row at the end of the stack. */
@media (max-width: 767px) {
  .tb-root.tb-standalone .tb-bottombar { position: static; transform: none; display: flex;
    flex-wrap: wrap; max-width: none; margin: 8px 10px; row-gap: 6px; }
  .tb-root.tb-standalone .tb-bottombar .tb-undobtn,
  .tb-root.tb-standalone .tb-bottombar .tb-conveyor-toggle { min-height: 44px; }
}

/* Branches bar: collapse toggle + collapsed pill state. */
.tb-branchbar-toggle { background: var(--elevated); color: var(--subtle); border: 1px solid var(--border);
  border-radius: 6px; font-size: 12px; line-height: 1; padding: 5px 8px; cursor: pointer; white-space: nowrap;
  transition: color .12s, border-color .12s; }
.tb-branchbar-toggle:hover { color: var(--accent); border-color: var(--accent); }
.tb-branchbar.tb-bb-collapsed { right: auto; width: auto; min-width: 0; padding: 6px; }
.tb-branchbar.tb-bb-collapsed .tb-branchbar-toggle { color: var(--accent); font-weight: 700; }

/* ── Single-row standalone topbar (replaces the two-row stack): [matchup chip]
   · [time/eco console] · [+New | File] on ONE line, floating over the map.
   The stage now reaches the top nav (desktop), so the bar overlays the canvas
   instead of reserving its own band — the gap above the map is gone. ── */
.tb-topbar.tb-topbar-single { flex-wrap: wrap; gap: 8px 10px; align-items: center; }
.tb-root.tb-standalone .tb-topbar-single > .tb-spacer { pointer-events: none; }
/* When width forces a wrap, the right cluster stays right-aligned. */
.tb-topbar-single .tb-actions { margin-left: auto; }
/* Uniform 32px control heights (the stacked-bar normalization, re-targeted). */
.tb-topbar-single .tb-dd-btn,
.tb-topbar-single .tb-newbtn { min-height: 32px; box-sizing: border-box; }
/* The console competes with the chip + actions on one line now — cap the scrub
   track a little tighter than the dedicated-row clamp. */
.tb-topbar-single .tb-console .tb-htl-track-wrap { width: clamp(180px, 20vw, 320px); }
/* Full-bleed canvas: on desktop the stage starts at the very top of the editor
   (under the floating topbar) instead of below a reserved 108px band. Scoped
   ≥1024px — the tablet band (768–1023) keeps its raised-stage posture and the
   phone layout is a static stack. */
@media (min-width: 1024px) {
  .tb-root.tb-standalone .tb-stage { top: 0; }
}

/* ── Build tab fits the viewport exactly (no page scroll). The base host rule
   (calc(100vh - 64px)) only subtracted the nav and ignored the body padding +
   nav gap, so the editor ran ~21px past the fold and the body's bottom padding
   added another 20px — the bottom toolbar needed a scroll to reach. 85px =
   body pad 20 + nav 55 + gap 10 on desktop; main.js refines this with a real
   measurement on mount/resize (the nav can wrap). The negative bottom margin
   swallows the body's own bottom padding. Desktop-only: the phone block keeps
   its scrolling static-stack posture. ── */
@media (min-width: 768px) {
  #tb-standalone-host {
    height: calc(100vh - 85px);
    height: calc(100dvh - 85px);
    margin-bottom: -20px;
  }
}

/* New-build dialog: map/patch field grid. */
.tb-nb-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 4px; }
.tb-nb-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.tb-nb-field > span { font-family: var(--f-display); font-size: 9.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--subtle); }
.tb-nb-field .tb-mapsel { width: 100%; max-width: none; min-height: 34px; }
.tb-nb-pro-row .tb-add-main { min-width: 0; }
.tb-nb-pro { width: 100%; max-width: none; margin-top: 6px; min-height: 32px; }
@media (max-width: 767px) { .tb-nb-grid { grid-template-columns: 1fr; } }

/* Tablet band: tighten the console so the two rows stay shallow. The timeline +
   eco-bar wrap to two console rows through ~768-900px, so the eco-bar's
   border-left divider (a same-row separator) would dangle at the head of the
   wrapped row — drop it here exactly like the phone block does. */
@media (max-width: 1023px) and (min-width: 768px) {
  .tb-root .tb-topbar-stacked { gap: 6px; }
  .tb-root .tb-console { gap: 4px 10px; padding: 4px 10px; }
  .tb-root .tb-console .tb-htl-track-wrap { width: clamp(150px, 24vw, 260px); }
  .tb-root .tb-console .tb-eco-bar { border-left: none; padding-left: 0; }
}

/* Phone: the topbar is a static solid bar (normal flow) — rows wrap and stay
   interactive (the desktop pass-through trick is pointless over a solid bar),
   and the console reads as a flat full-width toolbar like the heat cluster. */
@media (max-width: 767px) {
  .tb-root.tb-standalone .tb-topbar-stacked { gap: 6px; }
  .tb-root.tb-standalone .tb-topbar-stacked > .tb-topbar-row { pointer-events: auto; flex-wrap: wrap; row-gap: 6px; }
  .tb-root.tb-standalone .tb-console { width: 100%; flex: 1 1 100%; justify-content: flex-start;
    background: var(--elevated); border: 1px solid var(--border); border-radius: 8px;
    -webkit-backdrop-filter: none; backdrop-filter: none; padding: 8px; }
  .tb-root.tb-standalone .tb-console .tb-eco-bar { border-left: none; padding-left: 0; }
  /* The ≤767 rule giving .tb-htl-track-wrap a flex basis already applies inside
     the console; release the desktop clamp so it can actually flex. */
  .tb-root.tb-standalone .tb-console .tb-htl-track-wrap { width: auto; flex: 1 1 120px; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   F1: PRODUCTION RADIAL (tb-prod-radial.js) — right-click queue wheel.
   BODY-mounted (survives render()) → it does NOT inherit .tb-root's local var
   remaps, so it reads the global --color-* tokens directly with hard fallbacks
   matching the prototype palette. z-index 10000 > the palette drag ghost (9999),
   the highest fixed layer in this stylesheet.
   Responsive: the JS clamp scales the whole overlay down when the viewport is
   smaller than the box, so nothing can exceed the screen at any width; cells
   are real <button>s (tap-friendly), touch-action:manipulation kills the 300ms
   tap delay. Canonical breakpoints only (≤767px tweak below). */
.tb-prod-radial {
  --pr-text: var(--color-text, #e6e9ef);
  --pr-muted: var(--color-text-muted, #aab3c2);
  --pr-accent: var(--color-accent, #56c2ff);
  --pr-warn: var(--color-warning, #fbbf24);
  --pr-danger: var(--color-danger, #f87171);
  --pr-border: var(--color-border-strong, rgba(255, 255, 255, 0.16));
  --pr-glass: rgba(15, 19, 28, 0.85);
  position: fixed; z-index: 10000;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  color: var(--pr-text); font-family: var(--font-ui, 'Inter', sans-serif);
  user-select: none; -webkit-user-select: none; touch-action: manipulation;
  animation: tb-pr-in .14s ease-out;   /* opacity-only: the root may carry an inline scale() clamp */
}

/* ── ring area ── */
.tb-pr-ring { position: relative; }
.tb-pr-disc {
  position: absolute; inset: 0; border-radius: 50%;
  background: radial-gradient(circle, rgba(10, 13, 20, 0.9) 0%, rgba(10, 13, 20, 0.78) 62%, rgba(10, 13, 20, 0.6) 100%);
  border: 1px solid var(--pr-border);
  box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.6), inset 0 0 60px rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
.tb-pr-hub {
  position: absolute; left: 50%; top: 50%; width: 92px; height: 92px;
  transform: translate(-50%, -50%); border-radius: 50%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  background: rgba(15, 19, 28, 0.94); border: 1px solid var(--pr-border);
  box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.25); text-align: center; pointer-events: none;
}
.tb-pr-hub-icon { width: 28px; height: 28px; border-radius: 6px; object-fit: cover; }
.tb-pr-hub-name {
  max-width: 78px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-display, 'Rajdhani', sans-serif);
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.tb-pr-hub-count { font-family: var(--font-mono, monospace); font-size: 11px; font-weight: 700; color: var(--pr-accent); }
.tb-pr-hub-count.full { color: var(--pr-warn); }
.tb-pr-hub-status {
  max-width: 80px; min-height: 0; font-size: 8.5px; line-height: 1.15; color: var(--pr-warn);
  opacity: 0; transition: opacity .15s;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tb-pr-hub-status.on { opacity: 1; }

/* ── ring cells ── */
.tb-pr-cell {
  position: absolute; width: 52px; height: 52px; padding: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(20, 24, 33, 0.92); border: 1px solid var(--pr-border);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: transform .12s, border-color .12s, box-shadow .12s;
}
.tb-pr-cell:hover:not(:disabled), .tb-pr-cell:focus-visible:not(:disabled) {
  border-color: var(--pr-accent); transform: scale(1.1);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pr-accent) 30%, transparent), 0 0 16px color-mix(in srgb, var(--pr-accent) 35%, transparent);
  z-index: 2;
}
.tb-pr-cell:active:not(:disabled) { transform: scale(0.95); }
.tb-pr-cell-icon { width: 32px; height: 32px; border-radius: 7px; object-fit: cover; pointer-events: none; }
.tb-pr-cell-fallback { font-family: var(--font-display, sans-serif); font-size: 13px; font-weight: 700; pointer-events: none; }
.tb-pr-cell.locked { opacity: .38; filter: grayscale(1); cursor: not-allowed; }
.tb-pr-lock { position: absolute; right: -3px; bottom: -3px; font-size: 11px; line-height: 1; filter: none; }
.tb-pr-badge {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; display: none; align-items: center; justify-content: center;
  background: var(--pr-accent); color: #07101c;
  font-family: var(--font-mono, monospace); font-size: 10px; font-weight: 700;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5); pointer-events: none;
}
.tb-pr-badge.on { display: flex; }
.tb-pr-cell.tb-pr-pulse { animation: tb-pr-pulse .32s ease-out; }
.tb-pr-cell.tb-pr-shake { animation: tb-pr-shake .36s ease-in-out; }

/* ── 8-slot hotbar strip ── */
.tb-pr-strip {
  display: flex; gap: 4px; padding: 6px; border-radius: 9px;
  background: var(--pr-glass); border: 1px solid var(--pr-border);
  box-shadow: 0 12px 28px -8px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
.tb-pr-slot {
  position: relative; width: 34px; height: 34px; border-radius: 6px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.tb-pr-slot.empty { border: 1px dashed rgba(255, 255, 255, 0.16); background: rgba(255, 255, 255, 0.02); }
.tb-pr-slot.filled {
  border: 1px solid var(--pr-border); background: var(--color-bg-elevated, #141821);
  cursor: pointer; transition: border-color .12s;
}
.tb-pr-slot.training { border-color: color-mix(in srgb, var(--pr-accent) 65%, transparent); }
.tb-pr-slot.queued .tb-pr-slot-icon { opacity: .68; }
.tb-pr-slot-fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0%;
  background: color-mix(in srgb, var(--pr-accent) 30%, transparent); pointer-events: none;
}
.tb-pr-slot-icon { position: relative; width: 26px; height: 26px; border-radius: 5px; object-fit: cover; pointer-events: none; }
.tb-pr-slot-qty {
  position: absolute; right: 2px; bottom: 1px; font-family: var(--font-mono, monospace);
  font-size: 9px; font-weight: 700; color: var(--pr-text);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9); pointer-events: none;
}
.tb-pr-slot.filled:hover { border-color: var(--pr-danger); }
.tb-pr-slot.filled:hover::after {
  content: '✕'; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.5); color: var(--pr-danger); font-size: 13px; font-weight: 700;
}

/* ── footer ── */
.tb-pr-footer { display: flex; justify-content: center; }
.tb-pr-open {
  padding: 5px 13px; border-radius: 999px;
  background: rgba(15, 19, 28, 0.92); border: 1px solid var(--pr-border); color: var(--pr-muted);
  font-family: var(--font-display, sans-serif); font-size: 10px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; cursor: pointer;
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  transition: color .12s, border-color .12s;
}
.tb-pr-open:hover { color: var(--pr-text); border-color: var(--pr-accent); }

@keyframes tb-pr-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes tb-pr-pulse {
  0% { transform: scale(1); }
  40% { transform: scale(1.16); border-color: var(--pr-accent);
        box-shadow: 0 0 0 6px color-mix(in srgb, var(--pr-accent) 28%, transparent), 0 0 20px color-mix(in srgb, var(--pr-accent) 55%, transparent); }
  100% { transform: scale(1); }
}
@keyframes tb-pr-shake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-3px); }
  40%, 60% { transform: translateX(3px); }
}

/* ── Terran add-ons + float/swap (second pass) ──────────────────────────────
   Add-on ring cells get a dashed accent ring + ⚙ tag (visually "construction",
   not a unit); the hub docks the ATTACHED add-on as a click-to-swap chip;
   target-pick mode (swap/claim) reuses the cell circle with a name label
   beneath; the actions row sits between ring and hotbar. */
.tb-pr-cell-addon { border-style: dashed; border-color: color-mix(in srgb, var(--pr-warn) 55%, var(--pr-border)); }
.tb-pr-cell-addon:hover:not(:disabled), .tb-pr-cell-addon:focus-visible:not(:disabled) {
  border-color: var(--pr-warn);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pr-warn) 30%, transparent), 0 0 16px color-mix(in srgb, var(--pr-warn) 35%, transparent);
}
.tb-pr-addon-tag {
  position: absolute; right: -3px; bottom: -3px; font-size: 11px; line-height: 1;
  color: var(--pr-warn); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7); pointer-events: none;
}
.tb-pr-hub-addon {
  position: absolute; right: -6px; bottom: 4px; width: 26px; height: 26px; padding: 0;
  border-radius: 7px; display: flex; align-items: center; justify-content: center;
  background: rgba(20, 24, 33, 0.96); border: 1px solid color-mix(in srgb, var(--pr-warn) 55%, var(--pr-border));
  cursor: pointer; pointer-events: auto; -webkit-tap-highlight-color: transparent;
  transition: border-color .12s, transform .12s;
}
.tb-pr-hub-addon:hover { border-color: var(--pr-warn); transform: scale(1.12); }
.tb-pr-hub-addon img { width: 18px; height: 18px; border-radius: 4px; pointer-events: none; }
.tb-pr-cell-target { overflow: visible; }
.tb-pr-cell-lbl {
  position: absolute; top: calc(100% + 3px); left: 50%; transform: translateX(-50%);
  max-width: 86px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-display, sans-serif); font-size: 9px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--pr-muted);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); pointer-events: none;
}
.tb-pr-cell-target:hover .tb-pr-cell-lbl { color: var(--pr-text); }
.tb-pr-none {
  position: absolute; left: 50%; top: 18%; transform: translateX(-50%);
  font-size: 11px; color: var(--pr-muted); white-space: nowrap;
}
.tb-pr-actions { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; }
.tb-pr-act {
  padding: 4px 11px; border-radius: 999px;
  background: rgba(15, 19, 28, 0.92); border: 1px solid var(--pr-border); color: var(--pr-muted);
  font-family: var(--font-display, sans-serif); font-size: 10px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; cursor: pointer;
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  transition: color .12s, border-color .12s;
  touch-action: manipulation;
}
.tb-pr-act:hover { color: var(--pr-text); border-color: var(--pr-accent); }

/* ≤767px: the JS scale-clamp is the hard guarantee (overlay can never exceed
   the viewport); tighten spacing so the unscaled box fits more phones. */
@media (max-width: 767px) {
  .tb-prod-radial { gap: 6px; }
  .tb-pr-act { min-height: 36px; }
}


/* ════════════════════════════════════════════════════════════════════════════
   ⊞ QUEUE — aggregated production panel (tb-queue-panel.js). A bottom-docked
   sheet over the stage: header (title + live summary chips + transient status
   + ✕), a quick-queue strip (one button per producible unit type, click routes
   to the best facility, shift+click ×5), an add-facility strip, and a
   scrollable per-facility list (name → modal · add-on chip · build-state dot ·
   8 queue-slot chips with progress fills · [＋] radial). Dedicated backdrop
   behind the panel closes on pointerdown (the .tb-cv-backdrop convention).
   z = modal − 1 so the building modal opened from a facility row paints ABOVE
   the sheet. ≤767px → full-height scrollable sheet (responsive contract).
   NO entry animation — render() rebuilds this DOM on every edit (see the
   .tb-cv-overlay rationale above).
   ════════════════════════════════════════════════════════════════════════════ */
.tb-qp-overlay { position: absolute; inset: 0; z-index: calc(var(--z-modal) - 1); }
.tb-qp-backdrop { position: absolute; inset: 0;
  background: color-mix(in srgb, var(--color-bg) 40%, transparent); }
.tb-qp-panel { position: absolute; left: 0; right: 0; bottom: 0; height: 300px;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--tb-glass-bg, var(--raised));
  border-top: 2px solid color-mix(in srgb, var(--accent) 55%, var(--border-strong));
  box-shadow: 0 -18px 48px rgba(0, 0, 0, .55);
  -webkit-backdrop-filter: blur(var(--tb-glass-blur, 10px)); backdrop-filter: blur(var(--tb-glass-blur, 10px)); }

/* Header: title · summary chips · transient status (flexes, right-aligned) · ✕. */
.tb-qp-header { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 7px 12px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.tb-qp-title { font-family: var(--f-display); font-size: 12px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--text); white-space: nowrap; }
.tb-qp-chips { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.tb-qp-chip { display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--f-mono); white-space: nowrap; }
.tb-qp-chip b { font-weight: 700; font-size: 12px; font-variant-numeric: tabular-nums; color: var(--accent); }
.tb-qp-chip i { font-style: normal; font-size: 9px; color: var(--subtle); }
.tb-qp-status { flex: 1 1 auto; min-width: 0; text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--f-ui); font-size: 11px; font-weight: 600; color: var(--warn); }
.tb-qp-close { background: none; border: none; color: var(--muted); font-size: 15px;
  cursor: pointer; padding: 4px 8px; line-height: 1; border-radius: 6px; flex-shrink: 0; }
.tb-qp-close:hover { color: var(--text); background: var(--elevated); }

/* Quick-queue + add-facility strips — horizontally scrollable chip rows. */
.tb-qp-quick, .tb-qp-add { display: flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-bottom: 1px solid var(--border); flex-shrink: 0;
  overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.tb-qp-row-lbl { font-family: var(--f-display); font-size: 9px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--subtle); flex-shrink: 0; }
.tb-qp-qbtn, .tb-qp-addbtn { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0;
  font-family: var(--f-ui); font-size: 11px; font-weight: 600; color: var(--text);
  background: var(--elevated); border: 1px solid var(--border); border-radius: 8px;
  padding: 4px 8px; cursor: pointer; touch-action: manipulation; white-space: nowrap;
  transition: border-color 120ms ease, color 120ms ease; }
.tb-qp-qbtn:hover, .tb-qp-addbtn:hover { border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
.tb-qp-qbtn img, .tb-qp-addbtn img { width: 20px; height: 20px; border-radius: 4px; }
.tb-qp-qcount { font-family: var(--f-mono); font-size: 10px; font-weight: 700;
  min-width: 16px; text-align: center; padding: 1px 4px; border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent);
  font-variant-numeric: tabular-nums; }
.tb-qp-qcount:empty { display: none; }
.tb-qp-qlock { font-size: 9px; line-height: 1; }
.tb-qp-qbtn.locked { opacity: .45; }
.tb-qp-qbtn.locked:hover { border-color: var(--border); }
.tb-qp-addbtn i { font-style: normal; font-family: var(--f-mono); font-size: 9px; color: var(--subtle); }

/* Facilities list — one row per placed producer. */
.tb-qp-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 2px 0 6px;
  scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.tb-qp-list::-webkit-scrollbar { width: 10px; }
.tb-qp-list::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 6px;
  border: 2px solid transparent; background-clip: content-box; }
.tb-qp-empty { padding: 18px 14px; font-family: var(--f-ui); font-size: 12px; color: var(--muted); }
.tb-qp-fac { display: flex; align-items: center; gap: 8px; padding: 4px 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 45%, transparent); }
.tb-qp-fac-name { display: inline-flex; align-items: center; gap: 6px;
  width: 190px; min-width: 0; flex-shrink: 0;
  background: none; border: none; padding: 2px 4px; border-radius: 6px; cursor: pointer;
  touch-action: manipulation; text-align: left;
  font-family: var(--f-display); font-size: 11px; font-weight: 600; color: var(--text); }
.tb-qp-fac-name:hover { background: var(--elevated); }
.tb-qp-fac-name img { width: 22px; height: 22px; border-radius: 4px; flex-shrink: 0; }
.tb-qp-fac-lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-qp-addonchip { font-family: var(--f-mono); font-style: normal; font-size: 8px; font-weight: 800;
  padding: 1px 4px; border-radius: 4px; flex-shrink: 0; }
.tb-qp-addonchip.r { background: color-mix(in srgb, var(--prod) 25%, transparent); color: var(--prod); }
.tb-qp-addonchip.tl { background: color-mix(in srgb, var(--tech) 25%, transparent); color: var(--tech); }
.tb-qp-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--subtle); }
.tb-qp-dot.planned { opacity: .45; }
.tb-qp-dot.building { background: var(--warn);
  box-shadow: 0 0 6px color-mix(in srgb, var(--warn) 60%, transparent); }
.tb-qp-dot.done { background: var(--gas); }

/* The 8 queue-slot chips — 26px squares, 2px gap, bottom-up progress fill. */
.tb-qp-slots { display: flex; align-items: center; gap: 2px; min-width: 0; flex-wrap: wrap; }
.tb-qp-slot { position: relative; width: 26px; height: 26px; border-radius: 5px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center; overflow: hidden;
  background: var(--elevated); border: 1px solid var(--border); padding: 0; }
.tb-qp-slot img { width: 20px; height: 20px; border-radius: 3px; position: relative; z-index: 1; }
button.tb-qp-slot { cursor: pointer; touch-action: manipulation; }
button.tb-qp-slot:hover { border-color: var(--danger); }
.tb-qp-slot.empty { border-style: dashed; background: transparent; opacity: .35; }
.tb-qp-slot.queued img { opacity: .55; filter: grayscale(35%); }
.tb-qp-slot.training { border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); }
.tb-qp-slot.done { opacity: .35; }
.tb-qp-fill { position: absolute; left: 0; right: 0; bottom: 0; height: 0%;
  background: color-mix(in srgb, var(--accent) 38%, transparent); z-index: 0; pointer-events: none; }
.tb-qp-x2 { position: absolute; top: -1px; right: 0; z-index: 2; font-family: var(--f-mono);
  font-size: 8px; font-weight: 800; color: #fff; background: var(--prod); border-radius: 3px; padding: 0 2px; }
.tb-qp-over { font-family: var(--f-mono); font-size: 10px; color: var(--subtle); margin-left: 2px; }
.tb-qp-plus { width: 26px; height: 26px; border-radius: 6px; flex-shrink: 0; margin-left: auto;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; line-height: 1; color: var(--muted); cursor: pointer; touch-action: manipulation;
  background: var(--elevated); border: 1px dashed var(--border-strong); }
.tb-qp-plus:hover { color: var(--accent); border-color: var(--accent); }

/* ≤767px — full-height sheet; rows wrap (name+dot+＋ on line 1, slots below)
   with bigger touch targets. Canonical breakpoint per the responsive contract.
   position:fixed pins the sheet to the VIEWPORT: on phones .tb-root is the
   scroll container, and an absolute inset:0 overlay anchors at the scroll
   ORIGIN — opened from the mid-stack bottombar it would render above the
   visible viewport (headerless slice, ✕ unreachable). Same fix as
   .tb-heat-info's ≤767px escalation. */
@media (max-width: 767px) {
  .tb-qp-overlay { position: fixed; }
  .tb-qp-panel { top: 0; height: auto; border-top-width: 1px; }
  .tb-qp-fac { flex-wrap: wrap; row-gap: 4px; }
  .tb-qp-fac-name { width: auto; flex: 1 1 auto; }
  .tb-qp-slots { order: 4; flex-basis: 100%; }
  .tb-qp-slot, .tb-qp-plus { width: 32px; height: 32px; }
  .tb-qp-slot img { width: 24px; height: 24px; }
  .tb-qp-qbtn, .tb-qp-addbtn { min-height: 40px; }
  .tb-qp-close { min-height: 40px; min-width: 40px; }
}


/* ============================================================================
   F3 · Target army-comp solver modal (tb-comp-ui.js). Reuses the .tb-modal
   chrome; these are the .tb-comp-* additions only. The ≤767px bottom-sheet
   escalation for .tb-root .tb-modal (full-width, internal scroll) already
   outranks the 560px width here — only the unit grid needs a mobile tweak.
   ========================================================================= */
.tb-comp-modal { width: 560px; }
.tb-comp-count { color: var(--accent); font-family: var(--f-mono); font-size: 11px; letter-spacing: 0; text-transform: none; margin-left: 8px; }
.tb-comp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; margin-bottom: 12px; }
.tb-comp-tile { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 6px; background: var(--elevated); border: 1px solid var(--border); border-radius: 10px; }
.tb-comp-tile.has { border-color: color-mix(in srgb, var(--accent) 55%, transparent); background: color-mix(in srgb, var(--accent) 10%, var(--elevated)); }
.tb-comp-tile img { width: 34px; height: 34px; border-radius: 6px; border: 1px solid var(--border-strong); }
.tb-comp-lbl { font-size: 10px; color: var(--subtle); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-comp-tile.has .tb-comp-lbl { color: var(--text); }
.tb-comp-step { display: flex; align-items: center; gap: 8px; }
.tb-comp-step button { width: 28px; height: 28px; border-radius: 6px; border: 1px solid var(--border-strong); background: var(--overlay2); color: var(--text); font-size: 15px; font-weight: 700; line-height: 1; cursor: pointer; padding: 0; touch-action: manipulation; }
.tb-comp-step button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.tb-comp-step button:disabled { opacity: .35; cursor: default; }
.tb-comp-step b { font-family: var(--f-mono); font-size: 13px; min-width: 16px; text-align: center; }
.tb-comp-row { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text); margin: 2px 0 10px; }
.tb-comp-row b { font-family: var(--f-mono); }
.tb-comp-dim { color: var(--subtle); font-size: 11px; }
.tb-comp-actions { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.tb-comp-compute:disabled { opacity: .45; cursor: default; }
.tb-comp-status { font-size: 11px; color: var(--subtle); }
.tb-comp-result { border: 1px solid var(--border); border-radius: 10px; background: var(--elevated); padding: 10px 12px; }
.tb-comp-ready { font-family: var(--f-display); font-size: 14px; font-weight: 700; letter-spacing: .04em; color: var(--accent); margin-bottom: 6px; }
.tb-comp-ready--warn { color: #ffb74d; }
.tb-comp-infra { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.tb-comp-chip { font-size: 10px; font-family: var(--f-mono); color: var(--text); background: var(--overlay); border: 1px solid var(--border); border-radius: 12px; padding: 2px 8px; }
.tb-comp-lines { font-family: var(--f-mono); font-size: 11px; line-height: 1.7; color: var(--text); max-height: 220px; overflow: auto; }
.tb-comp-line { display: flex; gap: 8px; }
.tb-comp-t { color: var(--subtle); min-width: 38px; text-align: right; }
.tb-comp-warn { color: #ffb74d; font-size: 11px; margin-top: 6px; line-height: 1.4; }
@media (max-width: 767px) {
  .tb-root .tb-comp-grid { grid-template-columns: repeat(3, 1fr); }
  .tb-comp-lines { max-height: 160px; }
}
