/* Alpha trendline chart page — dark theme (default) + a light theme under [data-theme="light"].
   Nearly all chrome reads these vars, so the light override below re-themes the DOM for free; the
   lightweight-charts canvas + a few canvas-drawn chips are re-themed in chart.js (THEME/applyTheme). */
:root {
  --bg: #131722;
  --panel: #1c2030;
  --panel-2: #222737;
  --fg: #d1d4dc;
  --muted: #787b86;
  --border: #2a2e39;
  --accent: #2962ff;
  --pos: #26a69a;
  --neg: #ef5350;
  --hover: rgba(255, 255, 255, 0.06);
}
:root[data-theme="light"] {
  --bg: #ffffff;
  --panel: #f0f3fa;
  --panel-2: #e0e3eb;
  --fg: #131722;
  --muted: #787b86;
  --border: #d1d4dc;
  --accent: #2962ff;
  --pos: #089981;
  --neg: #f23645;
  --hover: rgba(0, 0, 0, 0.05);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  overflow: hidden;
}

.muted { color: var(--muted); }
.hidden { display: none !important; }

/* ---- top bar ---- */
#topbar {
  display: flex;
  align-items: center;
  gap: 12px;            /* 16 until design/41 review P1-7: four gaps were the stats strip's 7th column */
  height: 48px;
  padding: 0 14px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  user-select: none;
  position: relative;   /* anchor + stacking context so tool tooltips paint above the chart canvas */
  z-index: 30;
}
.tb-left { display: flex; align-items: baseline; gap: 8px; min-width: 140px; }
.coin { font-size: 17px; font-weight: 700; letter-spacing: 0.3px; }

/* The instrument name beside the symbol ("US 10Y Treasury Yield"). Muted and small on purpose: it
   is context for a symbol you may not recognise, never a second headline competing with the price.
   `white-space: nowrap` because .tb-left is a flex row — a wrapped name would grow the 48px bar. */
.coin-name { font-size: 12px; color: var(--muted); white-space: nowrap; }

/* Vendor-delay note, in the LIVE badge's slot. It borrows the badge's typography (10px, 700, wide
   tracking) so the two read as the same kind of fact about the price, and the muted colour is what
   distinguishes them: green means it is happening now, grey means it already happened. */
.feed-note {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.6px;
  color: var(--muted);
  white-space: nowrap;
}
#last-price { font-variant-numeric: tabular-nums; transition: color 0.15s ease; }
#last-price.up { color: var(--pos); }
#last-price.down { color: var(--neg); }

/* 24h change. Same green/red vars as the price, but they mean a different thing on each: on
   #last-price .up/.down is a transient TICK flash (up vs the previous tick), here it is the
   standing sign of the day's move. No `transition` for that reason — this colour should not
   fade between states, it should just be the sign. */
#chg24 { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--muted); }
#chg24.up { color: var(--pos); }
#chg24.down { color: var(--neg); }

/* Live-stream badge: a pulsing dot + LIVE, shown once the 1s trades stream is ticking. */
.live-dot {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.6px;
  color: var(--pos);
}
.live-dot::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--pos);
  animation: livepulse 1.8s ease-out infinite;
}
@keyframes livepulse {
  0% { box-shadow: 0 0 0 0 rgba(38, 166, 154, 0.55); }
  70% { box-shadow: 0 0 0 6px rgba(38, 166, 154, 0); }
  100% { box-shadow: 0 0 0 0 rgba(38, 166, 154, 0); }
}

/* Read-only asset (a bond: no HyperLiquid market) — the class chart.js's loadAsset() puts on <body>.
   The three affordances that would spend money or read an order book stop EXISTING rather than going
   grey: a disabled button still says "this is something you do here", which on a market you cannot
   trade is a promise the page cannot keep. Bookmap has no `book_samples` rows for it either way. The
   keyboard is refused in step (chart.js's P/O branches, bookmap.js's D handler), so there is no
   hidden path back to them. Everything else — drawings, setups, alerts, every candle-only overlay —
   is deliberately untouched: read-only is about the ORDER BOOK, not about the chart.

   The `:not(.bookmap)` is an escape hatch, not a nicety: that button is also the way OUT of the
   mode, so hiding it unconditionally would strand a hand-edited `?coin=US10Y&bookmap=1` with no
   exit (bookmap.js's D handler refuses to enter for the same reason, and only to enter). */
body.readonly-asset:not(.bookmap) #tool-bookmap,
body.readonly-asset:not(.vbars) #tool-vbars,
body.readonly-asset #tool-long,
body.readonly-asset #tool-short { display: none; }

/* The TF row sits LEFT, right after the symbol block, at its content width (design/41 review P1-7:
   centred and flex-growing it left ~170 px empty on each side at 1419 px, room the stats strip after
   it needed). The TOOLS take the slack (.tb-tools margin-left:auto). A phone and a /grid pane keep the
   row flex-growing: there it is the bar's one horizontal scroller (the mobile block + embed.css). */
.tb-center { display: flex; gap: 4px; flex: 0 0 auto; justify-content: flex-start; }
body.mobile .tb-center,
html[data-embed="1"] .tb-center { flex: 1 1 auto; }
.tf-btn {
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.tf-btn:hover { color: var(--fg); background: var(--panel-2); }
.tf-btn.active { color: #fff; background: var(--accent); }

/* ---- floating drawing / overlay toolbar ---------------------------------------------------
   One uniform Lucide/Feather icon grammar (inline SVG, currentColor stroke) so .active
   (white-on-accent) and .hidden just work with zero JS change. Every button keeps its id +
   `tool` class; chart.js drives setTool/toggle by id. ---- */
.tb-tools { display: flex; align-items: center; gap: 4px; margin-left: auto; }

/* "Charts by TradingView" (#tv-attr): lightweight-charts' attribution, last in the tool cluster since the
   vendor logo that sat over the bottom pane's data is off (chart.js attributionLogo: false). Two muted
   9px lines — present and clickable, never a competitor for the eye. */
#tv-attr {
  flex: 0 0 auto;
  display: inline-flex; flex-direction: column; justify-content: center;
  margin-left: 6px;
  color: var(--muted);
  font-size: 9px; line-height: 1.2; letter-spacing: .2px; white-space: nowrap;
  text-decoration: none;
  opacity: .75;
}
#tv-attr:hover { color: var(--fg); opacity: 1; text-decoration: underline; }
#tv-attr:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; opacity: 1; }

/* Group divider: subtle 1px vertical rule — isolates the pointer, splits draw vs. toggle groups. */
.tb-div {
  flex: 0 0 auto;
  width: 1px; height: 20px;
  margin: 0 4px;
  background: var(--border);
  border-radius: 1px;
}

/* Hidden toolbar entries (trend flips / snapshots / AI assistant). They stay in the
   DOM on purpose — chart.js and snapshot.js select #tool-flips / #tool-snap / #tool-snap-list
   with the throwing `$`, and the F, N, ⇧N and C keys still drive all three — they are simply
   no longer offered as buttons. Delete a line here to bring one back (the trainer came back this
   way, as the 🎮 #tool-game beside the bell).
   The .tool-split wrapper needs the #topbar prefix to outrank its own `display: inline-flex`
   further down the file (the snapshots section). */
#tool-flips,
#tool-ai,
#topbar .tool-split { display: none; }

.tool {
  position: relative;
  flex: 0 0 auto;
  width: 32px; height: 32px;
  padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;          /* transparent rest border = zero layout shift across states */
  border-radius: 8px;
  cursor: pointer;
  -webkit-appearance: none; appearance: none;
  -webkit-tap-highlight-color: transparent;
  transition: color .12s ease, background-color .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.tool svg { width: 18px; height: 18px; display: block; pointer-events: none; }

/* rest -> hover -> active */
.tool:hover { color: var(--fg); background: var(--panel-2); border-color: var(--border); }
.tool:active { transform: translateY(.5px); }
.tool:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.tool.active,
.tool.active:hover {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 2px 8px rgba(41, 98, 255, .35);
}
.tool.active:hover { filter: brightness(1.08); }

/* Theme toggle: show the sun in dark mode (click → light), the moon in light mode. */
#tool-theme .theme-moon { display: none; }
:root[data-theme="light"] #tool-theme .theme-sun { display: none; }
:root[data-theme="light"] #tool-theme .theme-moon { display: block; }

/* The two phone-only chrome buttons that flank row 1 of the mobile topbar: the iOS app's back chip
   and the tool-cluster disclosure trigger. Both are inert here — the extension and desktop Safari
   never lay them out and never paint them. #tb-back needs the RN bridge on top of that (body.inapp,
   set by chart.js only when window.ReactNativeWebView exists), because a back button with nowhere
   to go is a lie; #tool-more is revealed by the body.mobile block near the foot of this file. */
#tb-back, #tool-more { display: none; }
body.inapp #tb-back { display: inline-flex; }
/* The Notifications button opens /alerts/ in a NEW tab, and the iOS WebView is locked to /chart —
   there is no tab for it to open, so inside the app it is not offered at all (the app has its own
   alert screens). */
body.inapp #tool-alerts { display: none; }
/* The trainer (🎮) opens /chart/?game=1 in a new tab for the same reason, and the mobile build has
   no trainer at all (body.mobile #tool-game, below) — this line holds even for an in-app page
   loaded without ?mobile=1. */
body.inapp #tool-game { display: none; }
/* An emoji glyph in place of an SVG (#tool-game): sized to sit like the 18px stroke icons, and
   pinned to the colour-emoji faces so every OS draws the same glyph. It ignores `color`, so the
   rest/hover states read through the button's own background, as they do for every .tool. */
.tool .tool-emoji {
  font-size: 16px; line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
  pointer-events: none;
}

/* Indicators = popover trigger: auto width to seat a muted chevron affordance beside the glyph. */
.tool.tool-menu { width: auto; padding: 0 7px; gap: 3px; }
.tool.tool-menu .tool-caret { width: 12px; height: 12px; opacity: .6; }
.tool.tool-menu:hover .tool-caret { opacity: 1; }
.tool.tool-menu.active .tool-caret { opacity: 1; }

/* Contextual caption (mid-draw guidance, set by setTool); fades out on the default pointer tool so
   "Select" never floats as a stray word. It takes NO room in the bar (design/41 review P1-7: its old
   reserved 132px slot was the stats strip's): a pill floated under the bar's centre, as the phone and
   the /grid pane already had it (those rules below / in embed.css restyle it; #topbar is
   position:relative). */
.tb-hint {
  position: absolute;
  top: calc(100% + 8px); left: 50%;
  transform: translateX(-50%);
  max-width: 60vw;
  padding: 3px 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  pointer-events: none;
  z-index: 31;
  color: var(--muted);
  font-size: 11px; font-weight: 500; letter-spacing: .3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  opacity: .92;
  transition: opacity .15s ease;
}
/* Hidden on the default pointer tool (setTool toggles `.idle`; the drawing tools now live in the
   #toolrail, so a `:has(#tool-pointer.active)` selector inside .tb-tools would no longer match). */
.tb-hint.idle { opacity: 0; }

/* Custom tooltip: tool name + a mono keycap chip, dropped BELOW the 48px header. */
.tip {
  position: absolute;
  top: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%) translateY(-4px);
  display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap;
  padding: 5px 8px;
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 11px; font-weight: 500; letter-spacing: .2px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .5);
  opacity: 0; pointer-events: none;
  z-index: 60;
  transition: opacity .12s ease, transform .12s ease;
}
.tip::before {   /* arrow, matched to the bubble fill */
  content: ""; position: absolute; bottom: 100%; left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent; border-bottom-color: var(--panel-2);
}
.tip kbd {
  font: 600 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 2px 5px;
  min-width: 11px; text-align: center;
}
.tool:hover .tip { opacity: 1; transform: translateX(-50%) translateY(0); transition-delay: .28s; }

/* Right-anchored tooltips for the far-right toggle group so they can't clip the viewport edge. */
.tool.tip-right .tip { left: auto; right: 0; transform: translateX(0) translateY(-4px); }
.tool.tip-right .tip::before { left: auto; right: 11px; transform: none; }
.tool.tip-right:hover .tip { transform: translateX(0) translateY(0); }

/* ---- vertical drawing rail (floats over the chart's left edge, drag to reposition) ---- */
#toolrail {
  position: absolute;
  left: 12px; top: 50%;
  transform: translateY(-50%);   /* default dock; the drag JS switches to explicit inline left/top */
  z-index: 15;                    /* above #draw-overlay (z5) so buttons stay clickable when armed */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
  user-select: none;
  touch-action: none;
}
#toolrail-grip {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 18px;
  color: var(--muted);
  cursor: grab;
  border-radius: 6px;
  touch-action: none;
}
#toolrail-grip:hover { color: var(--fg); background: var(--panel-2); }
#toolrail-grip:active { cursor: grabbing; }
#toolrail-grip svg { width: 18px; height: 18px; display: block; pointer-events: none; }
/* Horizontal group divider for the vertical rail (the top-bar .tb-div is a vertical rule). */
.tb-div-h {
  flex: 0 0 auto;
  width: 20px; height: 1px;
  margin: 4px 0;
  background: var(--border);
  border-radius: 1px;
}
/* Tooltips open to the RIGHT of the rail (the default .tip drops below the top bar). */
#toolrail .tip {
  top: 50%; left: calc(100% + 9px); right: auto;
  transform: translateY(-50%) translateX(-4px);
}
#toolrail .tip::before {   /* arrow on the tip's LEFT edge, pointing back at the button */
  bottom: auto; left: auto; top: 50%; right: 100%;
  transform: translateY(-50%);
  border: 5px solid transparent; border-right-color: var(--panel-2); border-bottom-color: transparent;
}
#toolrail .tool:hover .tip { transform: translateY(-50%) translateX(0); }

/* DOCKED RAIL (design/41 review P1-6). On a desktop window >= 1280 px the rail leaves the chart and
   lives in its OWN gutter at the stage's left edge, TradingView's layout: floating, it covered the
   oldest bars of the price pane and of every sub-pane below it. #stage moves right by the gutter and
   narrows by it (below), so the chart (LWC autoSize), every overlay and every panel positioned in
   #stage keep their geometry; the rail sits at left:-gutter, outside #stage's box. The flag is
   html[data-rail="dock"], stamped PRE-PAINT by index.html (never on ?mobile=1 / ?embed=1 / a pop-out,
   which have no rail); the width test is this media query, and chart.js's railMode is the same rule
   (RAIL_DOCK_MIN_PX): while docked the grip is gone and a saved float position is ignored, and below
   1280 px the draggable floating rail returns. No overflow scroll on the docked rail: it would clip
   the tooltips, which open to its right. */
:root { --rail-gutter: 0px; }
@media (min-width: 1280px) {
  html[data-rail="dock"] { --rail-gutter: 44px; }
  html[data-rail="dock"] #toolrail {
    left: calc(-1 * var(--rail-gutter)); top: 0; bottom: 0;
    width: var(--rail-gutter);
    transform: none;
    padding: 6px 0;
    border: 0; border-right: 1px solid var(--border); border-radius: 0;
    box-shadow: none;
  }
  html[data-rail="dock"] #toolrail-grip { display: none; }
  /* The left corners stop clearing a rail that no longer covers them (60px -> the 8px edge). */
  html[data-rail="dock"] #msbias-panel.pos-tl,
  html[data-rail="dock"] #msbias-panel.pos-bl,
  html[data-rail="dock"] #onebox-panel.pos-tl,
  html[data-rail="dock"] #onebox-panel.pos-bl { left: 8px; }
}
/* Docked, the "Charts by TradingView" credit (#tv-attr) leaves the bar — whose room is the stats
   strip's — for the gutter's empty foot, read bottom-to-top like a book's spine. `fixed` because it
   stays a #topbar child in the DOM (the bar's z 30 keeps it above the rail); only on a window tall
   enough that the tools above never reach it. */
@media (min-width: 1280px) and (min-height: 600px) {
  html[data-rail="dock"] #tv-attr {
    position: fixed; left: 0; bottom: 10px;
    width: var(--rail-gutter);
    margin: 0;
    flex-direction: row; align-items: center; gap: .35em;
    writing-mode: vertical-rl; transform: rotate(180deg);
  }
}

/* ---- stage (chart + overlay + inspector) ---- */
/* The stage gives up a LEFT gutter to the docked toolrail and a RIGHT one to whatever is docked on the right
   (design/42 §7): the L4 side panel, the trading dock (#rdock), the VWAP σ map, any of them side by side.
   Each adds its own term to --right-gutter (below: the sum is additive), so none fights another over
   #stage's width; LWC's autoSize and every overlay anchored to #stage keep their geometry. The /chart TAB STRIP
   (tabs.css, design/44) sits between #topbar and #stage; --tabs-h is its height, UNSET without tabs, so every
   `var(--tabs-h, 0px)` term here and in rdock.css is 0 and a page without tabs is pixel-identical. */
#stage { position: relative; height: calc(100% - 48px - var(--tabs-h, 0px)); width: calc(100% - var(--rail-gutter) - var(--right-gutter, 0px)); margin-left: var(--rail-gutter); }
#chart { position: absolute; inset: 0; }
#draw-overlay {
  position: absolute; inset: 0;
  z-index: 5;
  pointer-events: none;       /* enabled only while a draw tool is active */
  cursor: crosshair;
}
#draw-overlay.armed { pointer-events: auto; }

/* ---- inspector ---- */
#inspector {
  position: absolute;
  top: 12px; right: 12px;
  width: 248px;
  z-index: 10;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
}
.insp-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
.insp-head h2 { margin: 0; font-size: 13px; font-weight: 700; }
/* Provenance chip: which timeframe this drawing was DRAWN on. Every drawing is now visible and
   editable on every timeframe, so this chip is the only thing that still carries that fact — and
   it is clickable (switch to that timeframe and focus the drawing). Cloned from .sd-tf, one step
   larger because it is a tap target rather than a label.
   The two font declarations are deliberately SEPARATE properties: the `font:` shorthand cannot
   carry `inherit` in its family slot, so `font: 700 10px inherit` is invalid and the browser drops
   the whole declaration — and a <button> without an explicit family falls back to the UA's Arial. */
.insp-tf {
  flex: 0 0 auto;
  padding: 2px 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--muted);
  font-family: inherit;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: 0.2px;
  cursor: pointer;
}
.insp-tf:hover:not(.here):not(:disabled) { color: var(--fg); border-color: var(--fg); }
/* Already on that timeframe: nothing to click through to. */
.insp-tf.here { color: var(--accent); border-color: var(--accent); cursor: default; }
/* A timeframe this page cannot switch to (bookmap's 5s grid) — readable, but inert. */
.insp-tf:disabled:not(.here) { opacity: 0.6; cursor: default; }
/* Lays the header out as [title][chip] ……… [✕]: margin-left:auto absorbs the free space, so the
   space-between above has nothing left to distribute and the chip stays beside the title it
   describes. Wins over the mobile .insp-head .icon-btn margin shorthand on specificity (id). */
#insp-close { margin-left: auto; }
/* The honest caveat under the header — e.g. an anchor that falls outside this timeframe's loaded
   history and is therefore placed on a nominal grid. Written by chart.js; .hidden when empty. */
.insp-note {
  margin: 0;
  padding: 10px 12px 0;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.35;
}
/* Read-only computed value in the position inspector (Qty, R:R, amounts, PnL). */
.insp-readout { font-size: 12px; color: var(--fg); font-variant-numeric: tabular-nums; }

/* ---- indicators modal (two-pane: toggle list + selected-indicator detail) --------------------
   Replaces the old top-right indicators dropdown, which stacked EVERY enabled indicator's settings
   inline with no scroll and ran off the bottom of the viewport past ~2 indicators. This is a
   centered dialog whose body is height-bounded and shows exactly one settings pane at a time. */
.ind-ov {
  position: fixed;
  inset: 0;
  z-index: 55;                 /* above inspector(10)/toolrail(15), below the snapshot overlay(60) */
  display: flex;
  align-items: center;
  justify-content: center;
  /* Deliberately a LIGHT scrim (not the near-opaque .snap-ov): toggling an indicator live should
     stay visible on the chart behind the dialog. */
  background: rgba(0, 0, 0, 0.35);
}
:root[data-theme="light"] .ind-ov { background: rgba(240, 243, 250, 0.45); }
.ind-modal-box {
  width: min(580px, calc(100vw - 32px));
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
  display: flex;
  flex-direction: column;
}
/* The box takes programmatic focus on open (tabindex=-1 + .focus()) so Esc lands in the window
   keydown handler — its own border+shadow already convey the dialog chrome, so the UA focus ring
   just reads as a stray artifact. Suppressed on the BOX only; inner controls keep native focus. */
.ind-modal-box:focus { outline: none; }
.ind-modal-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  cursor: grab;                /* the whole header is the modal's drag handle (chart.js initIndModalDrag) */
  touch-action: none;
}
.ind-modal-head:active { cursor: grabbing; }
.ind-modal-head h2 { margin: 0; font-size: 13px; font-weight: 700; }
.ind-modal-head .icon-btn,
.ind-modal-head .ind-none-btn { cursor: pointer; }   /* buttons don't inherit the grab cursor */
/* "N active" — pushed to the right of the title, before the Disable-all / close controls. */
#ind-active-count {
  margin-left: auto;
  color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums;
}
.ind-none-btn {
  background: transparent; color: var(--muted);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 3px 8px; font: inherit; font-size: 11px; font-weight: 600;
}
.ind-none-btn:hover { color: var(--fg); border-color: var(--accent); }
.ind-modal-body {
  display: flex;
  max-height: min(72vh, 560px);   /* the structural height bound — the whole point of the redesign */
}
#ind-list {
  flex: 0 0 200px;
  overflow-y: auto;
  padding: 6px;
  border-right: 1px solid var(--border);
}
#ind-detail {
  flex: 1 1 auto;
  min-width: 0;
  overflow-y: auto;
  padding: 12px 14px;
}
/* In the detail pane the settings block is the whole content, so undo the inline-nesting indent it
   carried when it hung under its toggle in the old dropdown (the left rule reads as noise here). */
#ind-detail .atr-settings { border-left: none; padding-left: 0; }
.ind-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 8px 7px 9px;
  border-radius: 7px;
  border-left: 2px solid transparent;   /* accent-bar slot filled on .sel — reserved so no width shift */
  cursor: pointer;
}
.ind-row:hover { background: var(--hover); }
.ind-row.sel { background: var(--panel-2); border-left-color: var(--accent); }
.ind-row-name { font-size: 12px; font-weight: 600; }
.ind-pane { display: none; flex-direction: column; gap: 10px; }
.ind-pane.sel { display: flex; }
.ind-pane h3 { margin: 0; font-size: 14px; font-weight: 700; }
.ind-desc { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
/* "Turn it on to configure" — visible only while the indicator's checkbox is off (JS sets .is-off). */
.ind-off-hint { display: none; margin: 0; }
.ind-pane.is-off .ind-off-hint { display: block; color: var(--muted); font-size: 12px; }
/* The HIDDEN twin (JS sets .is-hidden and injects the <p>): the indicator is on but its chip's eye is
   shut, and hiding tears the overlay down through setEnabled(false), which also hides its settings. */
.ind-hidden-hint { display: none; margin: 0; }
.ind-pane.is-hidden .ind-hidden-hint { display: block; color: var(--muted); font-size: 12px; }
.ind-note { margin: 2px 0 0; color: var(--muted); font-size: 11px; padding: 8px 12px 10px; }

/* ---- #ind-chips: the PRICE LEGEND (panes-legend.js), or the quick-remove chips without it -----------
   Top-left over the candles at 4 px, TradingView's place (review P0-2): the 34 px it used to reserve held
   liqmap's and liqheat's canvas badges, which are gone — their text is in their legend rows. When the
   setup ribbon is up (12 px, a STACK of pills whose height renderRibbon publishes as --ribbon-h) it steps
   below it: 4 + h + 14 = 18 + h, and 4 px with no ribbon (min() is 0 then). panes-legend.js caps its
   height at pane 0's. The container is pointer-transparent so the chart stays interactive between rows;
   only the rows catch clicks. */
#ind-chips {
  position: absolute;
  left: 8px; top: calc(4px + var(--ribbon-h, 0px) + min(var(--ribbon-h, 0px), 14px));
  z-index: 6;                  /* above #draw-overlay(5), below #toolrail(15) which is centered anyway */
  display: flex; flex-wrap: wrap; gap: 6px;
  pointer-events: none;
}
.ind-chip {
  display: inline-flex; align-items: center;
  pointer-events: auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  opacity: 0.85;
  transition: opacity 0.12s ease;
}
.ind-chip:hover { opacity: 1; }
/* The eye leads the pill: hide an indicator without removing it (its chip, config and on/off flag
   all survive; chart.js only tears the drawing down). */
.ind-chip-eye {
  display: inline-flex; align-items: center;
  background: transparent; border: none; color: var(--muted);
  padding: 3px 2px 3px 8px;
  cursor: pointer;
  border-radius: 999px 0 0 999px;
}
.ind-chip-eye svg { width: 13px; height: 13px; display: block; }
.ind-chip-eye:hover { color: var(--fg); }
.ind-chip.is-hidden { opacity: 0.6; }
.ind-chip.is-hidden:hover { opacity: 0.9; }
.ind-chip.is-hidden .ind-chip-name { color: var(--muted); text-decoration: line-through; }
.ind-chip.is-hidden .ind-chip-eye { color: var(--fg); }
.ind-chip-name {
  background: transparent; border: none; color: var(--fg);
  padding: 3px 4px 3px 4px;
  font: inherit; font-size: 11px; font-weight: 600;
  cursor: pointer;
  border-radius: 999px 0 0 999px;
}
.ind-chip-name:hover { color: var(--accent); }
.ind-chip-x {
  background: transparent; border: none; color: var(--muted);
  padding: 3px 8px 3px 3px;
  font: inherit; font-size: 12px; line-height: 1;
  cursor: pointer;
  border-radius: 0 999px 999px 0;
}
.ind-chip-x:hover { color: var(--neg); }
/* A toggle an indicator puts on its own chip (chipActions — Order Flow's "Footprint"): a small bordered
   pill between the name and the ✕, muted when off and filled with the accent when pressed, in both themes. */
.ind-chip-act {
  background: transparent; color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px;
  padding: 1px 7px; margin: 0 2px;
  font: inherit; font-size: 10.5px; font-weight: 600; line-height: 1.3;
  cursor: pointer;
}
.ind-chip-act:hover { color: var(--fg); border-color: var(--accent); }
.ind-chip-act[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.ind-chip-act[aria-pressed="true"]:hover { color: #fff; filter: brightness(1.1); }
/* "Clear all" is a single-button pill (no ✕): round it fully + tint it toward the danger color. */
.ind-chip-clear .ind-chip-name {
  padding: 3px 10px; border-radius: 999px; color: var(--muted);
}
.ind-chip-clear .ind-chip-name:hover { color: var(--neg); }
/* The bulk eye ("Hide all" / "Show all", key E) — a single-button pill like "Clear all", eye first,
   sized like .ind-chip-eye. Never .is-hidden: it is an action, not a hidden indicator. */
.ind-chip-bulk .ind-chip-name {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 10px 3px 8px; border-radius: 999px; color: var(--muted);
}
.ind-chip-bulk .ind-chip-name:hover { color: var(--fg); }
.ind-chip-bulk svg { width: 13px; height: 13px; display: block; flex: none; }
/* JeaFx statistics panel collapse. The panel is PAINTED (jeafx.js drawStats), so this is the one DOM
   node on it, positioned by drawStats over the header's right end in #stage coordinates (the pane
   canvas and #stage share a top-left). Same band as the chips so a draw tool's armed overlay (5)
   never swallows it. */
.jfx-stats-toggle {
  position: absolute;
  z-index: 6;
  width: 16px; height: 14px;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  pointer-events: auto;
  background: none; border: none; border-radius: 3px;
  color: var(--muted);
  font: inherit; font-size: 13px; line-height: 1;
  cursor: pointer;
}
.jfx-stats-toggle:hover { color: var(--fg); background: var(--hover); }
/* ---- Liq Heatmap "how to read" help (collapsible; native <details>) ---- */
.ind-help { margin: 4px 0 2px; font-size: 11px; color: var(--muted); }
.ind-help > summary {
  cursor: pointer; color: var(--text); font-size: 11px; list-style: none; user-select: none;
}
.ind-help > summary::-webkit-details-marker { display: none; }
.ind-help > summary::before { content: '▸ '; color: var(--muted); }
.ind-help[open] > summary::before { content: '▾ '; }
.ind-help-body { display: flex; flex-direction: column; gap: 7px; padding: 7px 0 2px; }
.ind-help-body p { margin: 0; line-height: 1.5; }
.ind-help-body b { color: var(--text); font-weight: 600; }
.lh-legend { display: flex; align-items: center; gap: 6px; color: var(--muted); }
.lh-swatch {
  flex: 1; height: 8px; border-radius: 2px; border: 1px solid var(--border);
  background: linear-gradient(to right, #1a1040, #4c1d95, #0e7490, #22a06b, #eab308, #fde047);
}
/* ---- Liq Heatmap hover tooltip (cell detail under the cursor) ---- */
.lh-tip {
  position: fixed; z-index: 40; pointer-events: none;
  background: var(--panel); border: 1px solid var(--border); border-radius: 7px;
  padding: 6px 9px; font-size: 11px; color: var(--muted); line-height: 1.35;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45); max-width: 240px;
}
.lh-tip .lh-tip-px { color: var(--text); font-weight: 600; font-size: 12px; }
.lh-tip .lh-tip-val { margin-top: 2px; }
.lh-tip .lh-tip-meta { margin-top: 2px; opacity: 0.75; }

/* ---- Tape S/R Zones (tapezones.js): hover tip (.lh-tip's clone), the click-pinned card, the phone sheet.
   The card is pointer-live (✕ + Esc close it); the tip never eats the hover that keeps it open. ---- */
.tz-tip, .tz-card {
  position: fixed; z-index: 40;
  background: var(--panel); border: 1px solid var(--border); border-radius: 7px;
  font-size: 11px; color: var(--muted); line-height: 1.4;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
}
.tz-tip { pointer-events: none; padding: 6px 9px; max-width: 280px; }
.tz-tip-title { color: var(--fg); font-weight: 600; font-size: 12px; }
.tz-tip-row { margin-top: 2px; }
.tz-tip-why { margin-top: 4px; color: var(--fg); opacity: 0.85; }
.tz-tip-meta { margin-top: 3px; opacity: 0.7; font-style: italic; }
.tz-card {
  pointer-events: auto; width: 320px; max-width: calc(100vw - 16px); max-height: min(72vh, 620px);
  overflow-y: auto; padding: 10px 12px 10px;
}
.tz-card .tz-x, .tz-sheet .tz-x {
  float: right; margin: -4px -6px 0 8px; width: 24px; height: 24px; border: 0; border-radius: 5px;
  background: transparent; color: var(--muted); font-size: 13px; cursor: pointer;
}
.tz-card .tz-x:hover, .tz-sheet .tz-x:hover { color: var(--fg); background: var(--hover); }
.tz-title { color: var(--fg); font-weight: 700; font-size: 13px; font-variant-numeric: tabular-nums; }
.tz-badges { display: flex; flex-wrap: wrap; gap: 4px; margin: 5px 0 6px; }
.tz-badge, .tz-chip, .tz-prov {
  display: inline-block; padding: 1px 6px; border-radius: 999px; font-size: 10px; font-weight: 600;
  border: 1px solid var(--border); color: var(--fg); white-space: nowrap;
}
.tz-tip-title .tz-badge, .tz-tip-row .tz-badge { margin-left: 4px; vertical-align: 1px; }
.tz-badge.tz-s-strongly_defended, .tz-badge.tz-s-tested { color: var(--pos); border-color: var(--pos); }
.tz-badge.tz-s-weakening { color: #f0b90b; border-color: #f0b90b; }
:root[data-theme="light"] .tz-badge.tz-s-weakening { color: #b7791f; border-color: #b7791f; } /* COLORS.light.absorption */
.tz-badge.tz-s-broken { color: var(--neg); border-color: var(--neg); }
.tz-badge.tz-s-forming { color: var(--muted); border-style: dashed; }
.tz-prov { font-style: italic; border-style: dashed; }
.tz-muted { color: var(--muted); }
.tz-i { font-style: italic; margin: 0 0 6px; }
.tz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 12px; margin: 4px 0; }
.tz-kv { display: flex; justify-content: space-between; gap: 6px; }
.tz-kv b { color: var(--fg); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.tz-strength { display: flex; align-items: center; gap: 8px; margin: 6px 0 4px; }
.tz-strength b { color: var(--fg); }
.tz-bar { flex: 1; height: 5px; border-radius: 3px; background: var(--panel-2); overflow: hidden; }
.tz-bar i { display: block; height: 100%; border-radius: 3px; }
.tz-line { margin-top: 3px; }
.tz-k { font-weight: 600; }
.tz-sub { margin: 8px 0 2px; color: var(--fg); font-weight: 600; font-size: 11px; }
.tz-why, .tz-retests { margin: 2px 0 0; padding-left: 16px; }
.tz-why li { color: var(--fg); margin: 2px 0; }
.tz-retests li { margin: 1px 0; font-variant-numeric: tabular-nums; }
.tz-details { margin-top: 6px; }
.tz-details > summary { cursor: pointer; color: var(--fg); font-weight: 600; }
.tz-members { list-style: none; margin: 2px 0 4px; padding: 0; }
.tz-members li { margin: 1px 0; }
.tz-member { font: inherit; font-variant-numeric: tabular-nums; color: inherit; text-align: left; }
button.tz-member { width: 100%; padding: 1px 4px; border: 0; border-radius: 3px; background: transparent; cursor: pointer; }
button.tz-member:hover { color: var(--fg); background: var(--hover); }
li.tz-member.tz-cur { padding: 1px 4px; color: var(--fg); font-weight: 600; }
.tz-foot { margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--border); font-size: 10.5px; }
.tz-foot > div { margin-top: 2px; }
.tz-caveat { font-style: italic; }
.tz-sheet { display: none; }
/* the live attack meter's "Right now" block (tapezones-attack.js); empty until a meter lands on the zone */
.tz-attack:empty { display: none; }
.tz-attack { margin: 2px 0 6px; padding: 4px 6px; border-left: 2px solid var(--border); }
.tz-attack .tz-sub { margin-top: 0; }
.tz-attack .tz-kv b { white-space: normal; text-align: right; }
/* ◈ = OBSERVED in the order book, beside the tape's ◆ = INFERRED from executed trades. */
.tz-book-k { color: #26c6da; font-weight: 600; }
:root[data-theme="light"] .tz-book-k { color: #00838f; }
/* The cut-down card's fight line, its pressure caveat (COMPLETE — never clamped, never folded) and the retest tally. */
.tz-fight { margin-top: 0; }
.tz-pcav { display: block; margin: 2px 0 6px; overflow: visible; white-space: normal; -webkit-line-clamp: none; }
.tz-strength .tz-rt { margin-left: auto; color: var(--muted); white-space: nowrap; }

/* ---- ◈ WALL HP (tapezones-hp.js markup, tapezones-fx.js motion; design/35 "◈ Wall HP") ----
   The card's .tz-hp host is STABLE: tapezones-fx.js patches it in place, so a class toggled here is never a remount.
   H.html emits a FIXED tree per kind and hides what a read does not show with a bare `hidden` attribute — an author
   `display` beats the UA's [hidden] rule, so the !important below is load-bearing: without it a hidden pip, note or
   segment would show. Book cyan is T.COLORS[theme].book (tapezones-pure.js); every other hue is the theme's. The bar
   never takes a support / resistance colour: red and green mean ROLE on this chart, and the book never names one. */
.tz-hp, .tz-fx { --tz-book: #26c6da; --tz-gain: rgba(255, 255, 255, 0.3); }
:root[data-theme="light"] .tz-hp,
:root[data-theme="light"] .tz-fx { --tz-book: #00838f; --tz-gain: rgba(255, 255, 255, 0.45); }
.tz-hp [hidden] { display: none !important; }
.tz-hp:empty { display: none; }
.tz-hp { margin: 2px 0 6px; padding: 6px 7px 5px; border: 1px solid var(--border); border-radius: 6px; }
.tz-hp .tz-i { margin: 0; } /* .tz-i's paragraph margin is for the card's own notes, not this widget's lines */
/* The head is ONE row that never wraps: noun · ❄ · word · % with the % pinned right on the noun's first line. Only the
   noun gives way: a wide read's " · levels a – b" wraps INSIDE it, under the noun (balanced lines where the browser
   has text-wrap: balance), and a single word too long for what is left ellipsizes — so the big % is never pushed onto
   a line of its own (seen live on BTC before this: "◈ ASK WALL · levels 85,800 – 86,100  PULLED", then "45%"). */
.tz-hp-head { display: flex; flex-wrap: nowrap; align-items: baseline; gap: 2px 6px; }
.tz-hp-noun {
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; text-wrap: balance;
  color: var(--tz-book); font-weight: 700; font-size: 11px; letter-spacing: 0.3px;
}
.tz-hp-frz, .tz-hp-word, .tz-hp-pct { flex: none; white-space: nowrap; }
.tz-hp-frz { color: var(--muted); }
.tz-hp-word { margin-left: auto; color: var(--tz-book); font-weight: 700; font-size: 10.5px; font-style: normal; }
/* (an empty word keeps its auto margin, so the % stays right whatever the server said) */
.tz-hp-pct { color: var(--fg); font-weight: 700; font-size: 15px; line-height: 1; font-variant-numeric: tabular-nums; }
.tz-hp-pct.tz-hp-muted { color: var(--muted); font-weight: 600; } /* the geometry flag: "≈N%" / "—" */
/* The bar: a 0-150% track, the 100% tick two thirds along (H.TICK). Every segment is absolutely placed by H's style. */
.tz-hp-bar {
  position: relative; height: 10px; margin: 5px 0 4px; border-radius: 3px; background: var(--panel-2); overflow: hidden;
}
.tz-hp-bar > i { position: absolute; top: 0; bottom: 0; display: block; }
.tz-hp-bar::after { content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; }
.tz-hp-fill { left: 0; background: var(--tz-book); opacity: 0.9; transition: width 0.6s ease; }
.tz-hp-over { /* overshield: resting size past its 100% (max(start, avg)), a slow shimmer over the fill */
  background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0));
  background-size: 200% 100%; animation: tz-hp-shimmer 2.4s linear infinite;
}
.tz-hp-eat { background: var(--muted); opacity: 0.55; } /* traded into (only when the identity splits the loss) */
.tz-hp-pull { background: repeating-linear-gradient(135deg, var(--muted) 0 2px, transparent 2px 5px); opacity: 0.8; }
.tz-hp-ghost { background: var(--muted); opacity: 0.35; } /* gone, split unknown */
.tz-hp-gain { background: var(--tz-gain); } /* more than at the start of record: the fill, lighter */
.tz-hp-cap { right: 0; width: 3px; background: var(--fg); opacity: 0.85; } /* past 150%: the % still prints the truth */
.tz-hp-tick { width: 2px; margin-left: -1px; background: var(--fg); opacity: 0.8; }
.tz-hp-was { width: 0; border-left: 1px dashed var(--fg); opacity: 0.55; } /* start of record */
/* Unknown is never zero: the fog is a hatched "?", a MEASURED zero a solid frame, a read in flight a skeleton. A coin
   whose book is not recorded gets none of these: its .tz-hp host stays empty (:empty above), favourites only. */
.tz-hp-q { background: repeating-linear-gradient(135deg, var(--border) 0 2px, transparent 2px 5px), var(--panel-2); }
.tz-hp-q::after { content: '?'; color: var(--muted); font: 700 9px/10px sans-serif; text-align: center; }
.tz-hp-skel {
  background: linear-gradient(90deg, var(--panel-2) 25%, var(--hover) 50%, var(--panel-2) 75%), var(--panel-2);
  background-size: 200% 100%; animation: tz-hp-shimmer 1.2s linear infinite;
}
.tz-hp-zero { box-shadow: inset 0 0 0 1px var(--tz-book); }
/* OBSERVED empty (seen, nothing rested on its side): a measured frame like a zero — no fill, no "?", no % */
.tz-hp-empty { box-shadow: inset 0 0 0 1px var(--tz-book); }
/* The ◈ bars hang off the zone labels: with Labels = None there are none to show, so their two rows read as off. */
#tapezones-settings:has(#tz-labels option[value="none"]:checked) .field:has(#tz-book, #tz-fx) { opacity: 0.45; }
.tz-hp-wide { outline: 1px dashed var(--muted); outline-offset: 1px; } /* the book's levels are wider than the band */
.tz-hp-stale { filter: grayscale(1); opacity: 0.6; } /* frozen at the last read (❄ in the head) */
.tz-hp-stale > i { animation: none; transition: none; }
.tz-hp-flag .tz-hp-fill { /* the geometry flag: the % reads the request's geometry, so the fill is hatched */
  background: repeating-linear-gradient(135deg, var(--tz-book) 0 2px, transparent 2px 4px); opacity: 0.6;
}
.tz-hp-low::after { box-shadow: inset 0 0 0 1px var(--fg); animation: tz-hp-pulse 1.6s ease-in-out infinite; }
/* One-shot flashes (tapezones-fx.js adds and removes them, 700 ms): cyan for a refill, neutral for a drain. */
.tz-hp-bar.tz-hp-fx-refill { animation: tz-hp-refill 0.7s ease-out; }
.tz-hp-bar.tz-hp-fx-drain { animation: tz-hp-drain 0.7s ease-out; }
/* The rows under the bar. Spaces between their parts are markup text, so they collapse around a hidden part. */
.tz-hp-hit, .tz-hp-net { margin-top: 2px; color: var(--fg); font-variant-numeric: tabular-nums; }
.tz-hp-usd { font-weight: 700; }
.tz-hp-pips { display: inline-flex; gap: 2px; vertical-align: 1px; }
.tz-hp-pips i {
  display: block; width: 9px; height: 5px; border-radius: 1px;
  background: linear-gradient(90deg, var(--tz-book) calc(var(--p, 0) * 100%), var(--panel-2) 0);
}
.tz-hp-plus { color: var(--tz-book); font-size: 10px; line-height: 5px; }
.tz-hp-mult { color: var(--muted); }
.tz-hp-pend { color: var(--muted); border-bottom: 1px dashed var(--muted); } /* the live tape tail: a client read */
.tz-hp-note { margin-top: 2px; color: var(--muted); font-size: 10.5px; }
.tz-hp .tz-hp-fog { margin: 4px 0 0; color: var(--muted); }
.tz-hp .tz-hp-sub { margin: 2px 0 0; color: var(--muted); font-size: 10.5px; opacity: 0.85; }
.tz-hp-cav { margin-top: 4px; color: var(--muted); font-size: 10px; }
@keyframes tz-hp-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
@keyframes tz-hp-pulse { 0%, 100% { opacity: 0.2; } 50% { opacity: 0.9; } }
@keyframes tz-hp-refill {
  from { box-shadow: 0 0 0 2px var(--tz-book), 0 0 10px var(--tz-book); }
  to { box-shadow: none; }
}
@keyframes tz-hp-drain {
  from { box-shadow: 0 0 0 2px var(--muted); filter: brightness(0.7); }
  to { box-shadow: none; }
}
/* The chart's FX layer over #stage: above the canvas, below the drawing overlay (z5), every panel and the card; it
   never takes a pointer (tapezones-fx.js also sets that inline). Floats rise 18 px and fade over 1.2 s from a bar. */
.tz-fx { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 4; }
.tz-fx > * { position: absolute; pointer-events: none; }
.tz-fx-f {
  white-space: nowrap; font: 600 11px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--fg); text-shadow: 0 0 3px var(--bg), 0 0 1px var(--bg); font-variant-numeric: tabular-nums;
  animation: tz-fx-rise 1.2s ease-out forwards;
}
.tz-fx-f.tz-fx-refill { color: var(--tz-book); }
.tz-fx-g { border-radius: 3px; animation: tz-fx-fade 0.8s ease-out forwards; }
.tz-fx-g.tz-fx-refill { box-shadow: 0 0 0 2px var(--tz-book), 0 0 12px var(--tz-book); }
.tz-fx-g.tz-fx-drain { box-shadow: 0 0 0 2px var(--muted); }
.tz-fx-t { border-radius: 3px; box-shadow: 0 0 0 1px var(--fg); animation: tz-hp-pulse 1.6s ease-in-out infinite; }
@keyframes tz-fx-rise {
  from { opacity: 1; transform: translateY(-100%); }
  to { opacity: 0; transform: translateY(calc(-100% - 18px)); }
}
@keyframes tz-fx-fade { from { opacity: 1; } to { opacity: 0; } }
/* Reduced motion: tapezones-fx.js spawns nothing while it holds and puts .tz-hp-still on the card host (the page's
   "Hit numbers & flashes" setting does the same, which no media query can see); the bar's own motion stops here. A
   low bar keeps a STILL frame, so the state survives the motion. */
.tz-hp-still *, .tz-hp-still *::after { animation: none !important; transition: none !important; }
.tz-hp-still .tz-hp-low::after { opacity: 0.7; }
@media (prefers-reduced-motion: reduce) {
  .tz-hp *, .tz-hp *::after, .tz-fx * { animation: none !important; transition: none !important; }
  .tz-hp .tz-hp-low::after { opacity: 0.7; }
  .tz-fx { display: none; }
}

/* ---- Momentum divergence marker hover tooltip (momentum.js) — a clone of .lh-tip above. ---- */
.mom-tip {
  position: fixed; z-index: 40; pointer-events: none;
  background: var(--panel); border: 1px solid var(--border); border-radius: 7px;
  padding: 6px 9px; font-size: 11px; color: var(--muted); line-height: 1.35;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45); max-width: 240px;
}
.mom-tip .mom-tip-px { color: var(--text); font-weight: 600; font-size: 12px; }
.mom-tip .mom-tip-row { margin-top: 2px; }
/* CVD divergence hover card (delta.js divTip): the fired second + price, wider than momentum's */
.mom-tip.cvd-tip { max-width: 380px; }
.mom-tip.cvd-tip .cvd-tip-fired { color: var(--text); }
.mom-tip.cvd-tip .cvd-tip-local { margin-top: 0; opacity: 0.75; }

/* ---- Bookmap mode (bookmap.js) ----
   ?bookmap=1 empties #tf-row and rebuilds it as this strip: a mode label, range/gain chips (which
   reuse .tf-btn), the recorder-freshness readout and an exit ✕. The hover tooltip is a clone of
   .lh-tip above; .bm-notice is the "not recorded / warming up" card centered over the chart. */
.bm-label {
  color: var(--muted); font-size: 11px; font-weight: 600;
  letter-spacing: 0.6px; margin-right: 6px; white-space: nowrap;
}
.bm-status { color: var(--muted); font-size: 11px; white-space: nowrap; margin-left: 4px; }
.bm-status.stale { color: var(--neg); }
.bm-gain { min-width: 3ch; text-align: center; font-variant-numeric: tabular-nums; color: var(--muted); }
.bm-gain-btn, .bm-exit {
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.bm-gain-btn:hover, .bm-exit:hover { color: var(--fg); background: var(--panel-2); }
.bm-exit:hover { color: var(--neg); }
.bm-tip {
  position: fixed; z-index: 40; pointer-events: none;
  background: var(--panel); border: 1px solid var(--border); border-radius: 7px;
  padding: 6px 9px; font-size: 11px; color: var(--muted); line-height: 1.35;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45); max-width: 240px;
}
.bm-tip .bm-tip-px { color: var(--fg); font-weight: 600; font-size: 12px; }
.bm-tip .bm-tip-row { margin-top: 2px; }
.bm-tip .bm-tip-meta { margin-top: 2px; opacity: 0.75; }
/* Centered over #stage (position:relative), z14 = above #draw-overlay(5), below #toolrail(15). */
.bm-notice {
  position: absolute; left: 50%; top: 40%; transform: translate(-50%, -50%);
  z-index: 14; pointer-events: none;
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 14px; font-size: 12px; color: var(--muted); line-height: 1.45;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45); max-width: 320px; text-align: center;
}

/* ---- Volume candles (vbars.js) ----
   ?vbars=1 empties #tf-row and rebuilds it as this strip, bookmap's shape: the mode label (.bm-label),
   the size box, a USD | COIN segment, the "Auto $20M · 100/day" chip + its candles-per-day box, the
   preset sizes (all .tf-btn), a "Daily reset" switch, the hovered candle's readout and the exit
   (.bm-exit). The "no volume" card reuses .bm-notice. The strip is wider than a TF row, so it may
   scroll sideways inside the bar rather than push the tools off it. */
body.vbars #tf-row { flex: 0 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; align-items: center; }
body.vbars #tf-row::-webkit-scrollbar { display: none; }
.vb-size, .vb-target {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 3px 6px;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.vb-size { width: 9ch; }
.vb-target { width: 6ch; margin-right: 4px; }
.vb-size:focus, .vb-target:focus { outline: none; border-color: var(--accent); }
.vb-seg { display: inline-flex; gap: 2px; margin: 0 4px; }
.vb-presets { display: inline-flex; gap: 2px; }
.vb-auto { white-space: nowrap; }
.vb-reset {
  display: inline-flex; align-items: center; gap: 2px; margin-left: 6px;
  color: var(--muted); font-size: 11px; white-space: nowrap; cursor: pointer;
}
.vb-readout {
  color: var(--muted); font-size: 11px; white-space: nowrap; margin-left: 8px;
  max-width: 30ch; overflow: hidden; text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.vb-label { margin-left: 10px; }
.vb-exit { white-space: nowrap; margin-right: 6px; }
.vb-target { -moz-appearance: textfield; appearance: textfield; text-align: center; }
.vb-target::-webkit-inner-spin-button, .vb-target::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.vb-exit:hover { color: var(--fg); }
/* iOS zooms the page when a < 16px input takes focus (the .field inputs' rule below, same reason). */
body.mobile .vb-size,
body.mobile .vb-target { font-size: 16px; }
.atr-settings {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-left: 10px;
  border-left: 2px solid var(--border);
}
.atr-settings .field input[type="number"],
.atr-settings .field select {
  width: 88px;
}
/* 88px less the padding and the native arrow leaves ~50px of text, which clipped "Top right" to
   "Top righ". This pane's one select sizes to its longest option instead (88px stays the floor, so
   it still lines up with the number inputs above it; align-self stops the column flex of .field
   from stretching an auto-width control across the pane). Scoped on purpose: the same clip hits
   every long option under the rule above (rsirev "Bottom r…", onebox/msbias/tapezones positions,
   ATR Breakout's exit) — widening that rule is a separate, all-pane change. */
#autocorr-settings .field select,
#volume-settings .field select,
#oi-settings .field select {
  width: auto;
  min-width: 88px;
  align-self: flex-start;
}
.atr-settings .atr-subhead {
  color: var(--muted);
  font-size: 11px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  margin-top: 4px;
}
/* ---- Implose candles legend (swatch colors are painted by implose.js) ---- */
.imp-legend {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
  font-size: 11px;
  color: var(--muted);
}
.imp-legend span { display: flex; align-items: center; gap: 6px; }
.imp-legend .imp-sw {
  width: 10px; height: 10px; border-radius: 2px; display: inline-block;
  background: var(--border);
}
/* ---- VWAP overlay settings (multi-line) ---- */
#vwap-lines { display: flex; flex-direction: column; gap: 8px; }
.vwap-line {
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px; border: 1px solid var(--border); border-radius: 7px;
}
.vwap-line-head { display: flex; align-items: center; gap: 6px; }
/* A line row's style sample: a short stroke in the ink, solid / dashed / dotted (vwap.js paintSample). */
.vwap-sample {
  width: 18px; height: 0; flex: 0 0 auto;
  border-top-width: 2px; border-top-style: solid; border-top-color: var(--muted);
}
.vwap-line select {
  flex: 1 1 auto;
  background: var(--panel-2); color: var(--fg);
  border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px; font: inherit;
}
.vwap-color {
  width: 28px; height: 26px; padding: 0; flex: 0 0 auto;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
}
/* The VWAP ink (vwap.js syncInkUi): the current theme's three swatches + the custom picker. The pressed
   swatch (or the picker, `.on`, when the ink is none of them) carries the accent ring. */
.vwap-ink { display: flex; align-items: center; gap: 6px; }
.vwap-ink-sw {
  width: 22px; height: 22px; padding: 0; flex: 0 0 auto; cursor: pointer;
  border: 1px solid var(--border); border-radius: 50%; background: var(--panel-2);
}
.vwap-ink-sw[aria-pressed="true"],
.vwap-ink .vwap-color.on { box-shadow: 0 0 0 2px var(--accent); }
body.mobile .vwap-ink-sw { width: 30px; height: 30px; }
.vwap-del { flex: 0 0 auto; font-size: 13px; }
.vwap-add {
  background: transparent; color: var(--accent);
  border: 1px dashed var(--border); border-radius: 7px;
  padding: 6px; font: inherit; font-weight: 600; cursor: pointer;
}
.vwap-add:hover { border-color: var(--accent); }
/* Time in band (vwap.js): the coverage / note / caveat under the switch. Empty = no box at all. */
.vwap-inband-legend { margin-top: -4px; font-size: 11px; line-height: 1.45; color: var(--muted); }
.vwap-inband-legend:empty { display: none; }
.vwap-inband-legend .vwap-inband-caveat { margin-top: 3px; font-style: italic; }
.icon-btn {
  background: transparent; border: none; color: var(--muted);
  cursor: pointer; font-size: 14px; line-height: 1;
}
.icon-btn:hover { color: var(--fg); }
.insp-body { padding: 12px; display: flex; flex-direction: column; gap: 12px; }

.field { display: flex; flex-direction: column; gap: 5px; }
.field > label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; }
.field input[type="text"], .field input[type="number"], .field select {
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
  font: inherit;
}
.field.row { flex-direction: row; align-items: center; justify-content: space-between; }
.field.row > label { text-transform: none; letter-spacing: 0; font-size: 12px; }

.swatches { display: flex; gap: 6px; }
.swatch {
  width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid transparent; cursor: pointer;
}
.swatch.active { border-color: var(--accent); }

/* iOS-style toggle */
.switch { position: relative; width: 38px; height: 22px; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute; inset: 0; cursor: pointer;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 22px; transition: 0.15s;
}
.slider::before {
  content: ""; position: absolute; height: 16px; width: 16px;
  left: 2px; top: 2px; background: var(--muted); border-radius: 50%; transition: 0.15s;
}
.switch input:checked + .slider { background: var(--accent); border-color: var(--accent); }
.switch input:checked + .slider::before { transform: translateX(16px); background: #fff; }

.btn-danger {
  margin-top: 2px;
  background: transparent;
  color: var(--neg);
  border: 1px solid var(--neg);
  border-radius: 7px;
  padding: 7px;
  font-weight: 600;
  cursor: pointer;
}
.btn-danger:hover { background: rgba(239, 83, 80, 0.12); }

/* ---- status toast + banner ---- */
.status {
  position: absolute; left: 12px; bottom: 12px; z-index: 20;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 7px; padding: 6px 12px; color: var(--fg);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.status.err { border-color: var(--neg); color: var(--neg); }

/* ---- Fast Alerts: a fired price alert (fastalerts.js) ----
   Louder than #status on purpose: top-centre over the chart instead of a bottom-left line, ~12 s or
   until clicked instead of 2.6 s, up to three stacked. Created by fastalerts.js inside #stage. The
   amber pair matches the JS COLORS of the dashed alert line, so the toast reads as that line. */
#pa-toasts {
  --pa-amber: #ffb300;
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  z-index: 22;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  max-width: calc(100% - 24px);
  pointer-events: none;   /* only the cards take clicks; the gaps pass through to the chart */
}
:root[data-theme="light"] #pa-toasts { --pa-amber: #b45309; }
.pa-toast {
  pointer-events: auto; cursor: pointer;
  min-width: 220px; max-width: 100%;
  background: var(--panel); color: var(--fg);
  border: 1px solid var(--pa-amber); border-left-width: 4px; border-radius: 8px;
  padding: 8px 14px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
}
.pa-toast-title { font-weight: 700; font-size: 14px; color: var(--pa-amber); }
.pa-toast-body { font-size: 12px; margin-top: 2px; overflow-wrap: anywhere; }
/* A phone: full width between the gutters, and a card tall enough to be a tap target. */
body.mobile #pa-toasts { left: 12px; right: 12px; transform: none; max-width: none; align-items: stretch; }
body.mobile .pa-toast { min-width: 0; min-height: 44px; }

.banner {
  position: absolute; inset: 0; z-index: 30;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg); color: var(--muted); font-size: 15px; padding: 24px;
  text-align: center;
}

/* ---- setup drawer (Setup CRM; toolbar button / kbd S) ---- */
#setup-drawer {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 280px;
  z-index: 12;
  background: var(--panel);
  border-left: 1px solid var(--border);
  box-shadow: -8px 0 28px rgba(0, 0, 0, 0.35);
  display: flex; flex-direction: column;
}
#setup-drawer .insp-body { flex: 1; overflow-y: auto; }
/* Keep the inspector clear of the open drawer (the Indicators dialog is a centered modal now, so
   the drawer-open shift only applies to the inspector). */
#stage.drawer-open #inspector { right: 292px; }

.setup-active {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 10px 10px 12px;
  background: rgba(41, 98, 255, 0.08);
  border: 1px solid var(--accent);
  border-left-width: 3px;
  border-radius: 10px;
  box-shadow: 0 0 0 1px rgba(41, 98, 255, 0.22), 0 4px 14px rgba(41, 98, 255, 0.12);
}
.setup-active-label {
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.8px; text-transform: uppercase;
  color: var(--accent);
}
.setup-active-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.setup-meta { margin: 0; font-size: 11px; }
.setup-next { margin: 0; font-size: 12px; }
.setup-thesis { margin: 0; font-size: 12px; color: var(--fg); line-height: 1.35; }
.setup-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.setup-btn {
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 6px 9px;
  font: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer;
}
.setup-btn:hover { border-color: var(--accent); color: #fff; }
.setup-btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.setup-btn.primary:hover { filter: brightness(1.08); }
.setup-list { display: flex; flex-direction: column; gap: 6px; }
.setup-list .muted { margin: 0; }
.setup-row {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 9px;
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
}
.setup-row:hover { background: var(--panel-2); }
.setup-row.active {
  border-color: var(--accent);
  border-left: 3px solid var(--accent);
  padding-left: 7px; /* keep text aligned with non-active rows despite the thicker left border */
  background: rgba(41, 98, 255, 0.14);
}
/* Retired setups, below the "Archived" divider — dimmed, but the active one stays lit. */
.setup-list-sep {
  margin-top: 6px; padding: 5px 2px 1px;
  border-top: 1px solid var(--border);
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--muted);
}
.setup-row.archived { opacity: 0.5; }
.setup-row.archived:hover { opacity: 0.8; }
.setup-row.archived.active { opacity: 1; }
.setup-title {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600;
}
.setup-chip {
  flex: 0 0 auto;
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px;
  padding: 2px 6px;
  border-radius: 5px;
  background: var(--panel-2); color: var(--muted);
}
.setup-chip.tracking { color: #3b82f6; background: rgba(59, 130, 246, 0.12); }
.setup-chip.near_trigger { color: #f59e0b; background: rgba(245, 158, 11, 0.12); }
.setup-chip.ready { color: #22c55e; background: rgba(34, 197, 94, 0.14); }
.setup-chip.in_trade { color: #8b5cf6; background: rgba(139, 92, 246, 0.14); }
.setup-chip.monetized { color: #14b8a6; background: rgba(20, 184, 166, 0.14); }
.setup-chip.invalidated, .setup-chip.failed { color: #ef4444; background: rgba(239, 68, 68, 0.12); }
.dir-badge { flex: 0 0 auto; font-size: 10px; font-weight: 700; text-transform: uppercase; }
.dir-badge.long { color: var(--pos); }
.dir-badge.short { color: var(--neg); }
.dir-badge.either { color: var(--muted); }
.due-dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: #f59e0b; }
/* AI analyst (advisory summary + flag pills on the ACTIVE setup card) */
.agent-block { display: flex; flex-direction: column; gap: 5px; }
.agent-summary { margin: 0; font-size: 11.5px; line-height: 1.45; overflow-wrap: anywhere; }
.agent-tag {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 9px; font-weight: 700; letter-spacing: 0.4px;
  color: var(--accent); background: rgba(41, 98, 255, 0.14);
}
.agent-flags { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.agent-flag {
  flex: 0 0 auto;
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px;
  padding: 2px 6px;
  border-radius: 5px;
  background: rgba(148, 163, 184, 0.14); color: var(--muted);
}
.agent-flag.needs_review { color: #3b82f6; background: rgba(59, 130, 246, 0.12); }
.agent-flag.needs_redraw { color: #f59e0b; background: rgba(245, 158, 11, 0.12); }
.agent-flag.stale { color: #94a3b8; background: rgba(148, 163, 184, 0.14); }
.agent-flag.archive_candidate { color: #ef4444; background: rgba(239, 68, 68, 0.12); }
.agent-checked { margin-left: auto; font-size: 10px; color: var(--muted); white-space: nowrap; }
.setup-btn:disabled { opacity: 0.55; cursor: default; }
.setup-btn:disabled:hover { border-color: var(--border); color: var(--fg); }
.setup-form {
  display: flex; flex-direction: column; gap: 10px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
}
.field textarea {
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
  font: inherit;
  resize: vertical;
}
.attach-note { margin: 0; font-size: 11px; }
.setup-hint { margin: 0 0 8px; font-size: 11px; line-height: 1.4; }

/* Attached-drawings list on the ACTIVE setup card ("Drawings · N") */
.setup-drawings { display: flex; flex-direction: column; gap: 4px; }
.sd-head {
  color: var(--muted);
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px;
}
.sd-empty { margin: 0; font-size: 11px; }
.sd-row {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 6px;
  border: 1px solid var(--border);
  border-radius: 7px;
  cursor: pointer;
}
.sd-row:hover { background: var(--panel); border-color: var(--accent); }
.sd-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; }
.sd-main {
  flex: 1; min-width: 0;
  display: flex; align-items: baseline; gap: 5px;
  overflow: hidden; white-space: nowrap;
}
.sd-kind {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px;
}
.sd-role { flex: 0 0 auto; font-size: 11px; font-weight: 600; }
.sd-label {
  flex: 0 1 auto;
  overflow: hidden; text-overflow: ellipsis;
  color: var(--muted); font-size: 11px; font-style: italic;
}
.sd-price {
  flex: 0 1 auto;
  overflow: hidden; text-overflow: ellipsis;
  color: var(--muted); font-size: 10.5px; font-variant-numeric: tabular-nums;
}
.sd-tf {
  flex: 0 0 auto;
  padding: 1px 4px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--muted);
  font-size: 9px; font-weight: 700;
}
.sd-detach {
  flex: 0 0 auto;
  width: 16px; height: 16px;
  padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: none;
  border-radius: 4px;
  color: var(--muted);
  font-size: 13px; line-height: 1;
  cursor: pointer;
}
.sd-detach:hover { color: var(--neg); background: rgba(239, 83, 80, 0.12); }

/* ---- role popover (attach role picker, shown right after drawing) ---- */

/* ---- chart right-click menu (Lock/Unlock + Delete on a drawing, Reset view) ---- */
#chart-menu {
  position: absolute;
  z-index: 40;
  min-width: 152px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
}
.cm-item {
  display: block;
  width: 100%;
  background: transparent;
  color: var(--fg);
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 6px 9px;
  font: inherit; font-size: 12px;
  cursor: pointer;
  text-align: left;
}
.cm-item:hover { background: var(--panel-2); border-color: var(--border); }
/* Divides the right-clicked drawing's actions from the global ones. */
.cm-sep { height: 1px; margin: 4px 2px; background: var(--border); }

/* ---- snapshots (camera tool; snapshot.js) ---- */
/* Fused capture + gallery pair: shared outer radius, one hover state each. */
.tool-split { display: inline-flex; align-items: stretch; }
.tool-split #tool-snap { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.tool-split #tool-snap-list {
  width: 20px;
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  margin-left: -1px;
}
.tool-split #tool-snap-list .tool-caret { width: 12px; height: 12px; }
.snap-count {
  position: absolute; top: -4px; right: -5px;
  min-width: 14px; height: 14px; padding: 0 3px;
  background: var(--accent); color: #fff;
  border-radius: 999px;
  font-size: 9px; font-weight: 700; line-height: 14px; text-align: center;
  pointer-events: none;
}
/* The Indicators badge while every active indicator is hidden (bulk eye / E, or chip by chip):
   still counted, visibly not drawn. chart.js syncIndicatorUI sets the class. */
#ind-count.all-hidden { opacity: 0.45; }

/* Gallery drawer — same dock/chrome as #setup-drawer (mutually exclusive with it). */
#snap-drawer {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 280px;
  z-index: 12;
  background: var(--panel);
  border-left: 1px solid var(--border);
  box-shadow: -8px 0 28px rgba(0, 0, 0, 0.35);
  display: flex; flex-direction: column;
}
#snap-drawer .insp-body { flex: 1; overflow-y: auto; }

.snap-card {
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.snap-thumb {
  width: 100%; border-radius: 6px; display: block;
  border: 1px solid var(--border);
  cursor: pointer;
}
.snap-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11px; }
.snap-tf {
  background: var(--panel); border: 1px solid var(--border); border-radius: 4px;
  padding: 0 5px; font-size: 10px; font-weight: 700;
}
.snap-delta { font-variant-numeric: tabular-nums; font-weight: 600; }
.snap-delta.pos { color: var(--pos); }
.snap-delta.neg { color: var(--neg); }
.snap-note { margin: 0; font-size: 12px; line-height: 1.35; overflow-wrap: anywhere; }
.snap-note.empty { color: var(--muted); cursor: pointer; }
.snap-note.empty:hover { color: var(--fg); }
.snap-note-input {
  background: var(--panel); color: var(--fg);
  border: 1px solid var(--accent); border-radius: 6px;
  padding: 5px 7px; font: inherit; font-size: 12px; width: 100%;
}
.snap-note-input:focus { outline: none; }
.snap-actions { display: flex; gap: 6px; }
.snap-actions .danger { border-color: var(--neg); color: var(--neg); }
.snap-cap-note { margin: 0; font-size: 11px; color: var(--muted); text-align: center; }

/* Shutter flash on capture. */
#snap-flash {
  position: absolute; inset: 0; z-index: 40;
  background: #fff; opacity: 0; pointer-events: none;
}
:root[data-theme="light"] #snap-flash { background: #000; }
#snap-flash.go { animation: snap-flash 0.16s ease-out; }
@keyframes snap-flash {
  0% { opacity: 0; }
  35% { opacity: 0.3; }
  100% { opacity: 0; }
}

/* Save toast (own element — the plain #status toast stays untouched). */
#snap-toast {
  position: absolute; left: 12px; bottom: 12px; z-index: 21;
  display: flex; align-items: center; gap: 9px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 8px; padding: 7px 10px; color: var(--fg);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  font-size: 12px;
}
#snap-toast img { width: 40px; border-radius: 4px; border: 1px solid var(--border); display: block; }
#snap-toast .snap-toast-act {
  background: none; border: none; padding: 0;
  color: var(--accent); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
}
#snap-toast .snap-toast-act:hover { text-decoration: underline; }

/* Compare overlay — near-opaque scrim (hides the live-chart re-framing running underneath). */
.snap-ov {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(9, 11, 16, 0.94);
  display: none;
  color: var(--fg);
}
.snap-ov.open { display: flex; }
:root[data-theme="light"] .snap-ov { background: rgba(240, 243, 250, 0.96); }
.snap-cmp-inner {
  width: 100%; height: 100%;
  display: flex; flex-direction: column;
  padding: 12px 16px 8px;
}
.snap-cmp-hint {
  margin: 6px 0 0; font-size: 11px; text-align: center; flex: 0 0 auto;
}
.snap-cmp-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 10px;
}
.snap-cmp-title { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.snap-cmp-title b { font-size: 16px; }
.snap-cmp-note { flex-basis: 100%; margin: 0; font-size: 13px; color: var(--fg); }
.snap-cmp-actions { display: flex; align-items: center; gap: 7px; margin-left: auto; }
.snap-panes {
  flex: 1; min-height: 0;
  display: flex; gap: 12px;
  align-items: stretch; justify-content: center;
}
.snap-panes.sbs { flex-direction: row; }
.snap-panes.stacked { flex-direction: column; }
.snap-pane {
  position: relative; margin: 0;
  flex: 1 1 0; min-width: 0; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  cursor: zoom-in;
}
/* The img box fills the whole pane; the bitmap letterboxes inside via object-fit (bars painted
   var(--bg), which matches the chart's own background) — every available pixel is used. */
.snap-pane img {
  width: 100%; height: 100%;
  object-fit: contain;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg);
}
/* Solo view: click a pane to give one image the entire area; Space blinks THEN/NOW in place. */
.snap-panes.solo-then .snap-pane.pane-now { display: none; }
.snap-panes.solo-now .snap-pane.pane-then { display: none; }
.snap-panes.solo-then .snap-pane.pane-then,
.snap-panes.solo-now .snap-pane.pane-now { cursor: zoom-out; }
.snap-chip {
  position: absolute; top: 8px; left: 8px; z-index: 1;
  background: var(--panel); border: 1px solid var(--border); border-radius: 5px;
  padding: 2px 8px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.04em;
}
.snap-chip.now { border-color: var(--accent); color: var(--accent); }
.snap-notice {
  position: absolute; bottom: 8px; left: 8px; right: 8px; z-index: 1;
  background: rgba(224, 168, 0, 0.14); border: 1px solid #e0a800; color: #e0a800;
  border-radius: 6px; padding: 4px 8px; font-size: 11px; text-align: center;
}
.snap-shimmer {
  width: 100%; height: 100%; min-height: 120px;
  border: 1px solid var(--border); border-radius: 8px;
  background: linear-gradient(100deg, var(--panel) 40%, var(--panel-2) 50%, var(--panel) 60%);
  background-size: 200% 100%;
  animation: snap-shimmer 1.1s linear infinite;
}
@keyframes snap-shimmer {
  from { background-position: 120% 0; }
  to { background-position: -80% 0; }
}

/* ---- touch mode (?mobile=1 -> body.mobile) ------------------------------------------------------
   The iOS app loads this same page in a WebView. Viewing already worked (pointer events +
   lightweight-charts' native pinch/pan); these rules fix the parts that assumed a mouse. Everything
   is scoped under body.mobile, so the desktop page is byte-for-byte unaffected.

   The long-press that stands in for right-click lives in chart.js (initSelectAndDrag), because it
   needs the same handler the contextmenu binding uses. */

/* The two-pane Indicators modal had NO @media rule at all, so on a phone the 200px list and the
   detail pane shared ~360px and both became unusable. Stack them instead of scaling them: list on
   top, selected pane beneath, each scrolling on its own inside a near-full-height sheet. */
body.mobile .ind-modal-box {
  width: calc(100vw - 16px);
  max-height: calc(100vh - 24px);
}
body.mobile .ind-modal-body {
  flex-direction: column;
  max-height: calc(100vh - 92px);   /* leaves the header (its own drag handle) always visible */
}
body.mobile #ind-list {
  flex: 0 1 auto;
  max-height: 42vh;                 /* the list keeps a predictable share; the pane takes the rest */
  border-right: none;
  border-bottom: 1px solid var(--border);
}
body.mobile #ind-detail { flex: 1 1 auto; padding: 10px 12px; }
/* A 34px row is the minimum comfortable touch target; the desktop 7px padding gives ~28px. */
body.mobile .ind-row { padding: 10px 10px 10px 11px; }

/* The header is the modal's drag handle on desktop. Dragging a sheet that already fills the screen
   only loses it off-canvas, so the grab affordance goes away (the JS drag is harmless either way —
   it is pointer-based and simply has nowhere useful to move). */
body.mobile .ind-modal-head { cursor: default; }

/* Bigger .tool targets. NOTE `.tool` is NOT rail-only — it is shared with the topbar cluster
   (flips, theme, indicators, notifications, snapshot, snapshot-list, setup, AI, game), and since
   #toolrail is display:none on mobile those nine are the only ones this rule still reaches. Do not
   fold it into the rail block. The training game is out of scope for the app, and #tool-game is a
   topbar button, so hiding it removes the mode rather than half-exposing it. */
body.mobile .tool { width: 40px; height: 40px; }
body.mobile .tool svg { width: 20px; height: 20px; }
body.mobile #tool-game { display: none; }

/* Hover tooltips on the rail never dismiss on touch (there is no pointerout after a tap), so they
   would sit over the chart until the next interaction. The label survives for screen readers —
   every .tool carries its own aria-label. */
body.mobile .tool .tip { display: none; }

/* The on-chart indicator chips double as quick-remove buttons; give them a real target. */
body.mobile .ind-chip { padding: 5px 9px; font-size: 11.5px; }
/* The pill already pads 9px on the left, so the eye trades its own left padding for a gap to the name. */
body.mobile .ind-chip-eye { padding: 0 4px 0 0; }
body.mobile .ind-chip-eye svg { width: 15px; height: 15px; }
body.mobile .ind-chip-bulk svg { width: 15px; height: 15px; }
body.mobile .ind-chip-act { padding: 3px 9px; font-size: 11.5px; }
body.mobile .jfx-stats-toggle { width: 26px; height: 22px; font-size: 15px; }

/* ---- phone width -------------------------------------------------------------------------------
   Everything above made touch targets bigger. None of it made the page FIT: #topbar is a
   non-wrapping flex row whose min-content width is ~870-900px, inside html,body{overflow:hidden}.
   At 390px that pushed Theme / Indicators / Snapshots / Setups / AI off the right edge with no
   scroll to reach them — and growing .tool to 40px above made it strictly worse.

   The first fix was a two-row topbar: row 1 = identity + the timeframe strip, row 2 = the whole
   tool cluster, full width, which at 40px targets measures ~289px in 374px of usable width and so
   never scrolls or hides. The cluster that must always be reachable is the one that is always fully
   laid out.

   That was right about reachability and wrong about rent. Row 2 cost ~53px of an 852pt screen
   PERMANENTLY for a cluster touched a few times a session, and on the iOS app it stacked under a
   native back strip for a combined ~180pt (21%) above the first candle.

   So the cluster is now a DISCLOSURE PANEL revealed by #tool-more. It keeps the reachability
   property BY CONSTRUCTION rather than by measurement: the panel is laid out in byte-for-byte the
   box row 2 had — same full width, same 40px targets, same DOM order, same ids — and the only
   thing that changed is whether it is on screen. It also mirrors .tb-hint (below), which has
   floated out of this same flex flow since the first pass.

   Roads not taken: keeping 2-3 tools inline and overflowing the rest (row 1 must still be 40px
   tall, so it saves ~40 of the ~53px, eats ~120px of #tf-row — the page's one horizontal scroller
   — and adds a which-three-win judgment that gets re-litigated on every new tool); and docking the
   cluster to the foot of #stage as a floating pill (better for a thumb, same reclaim, but it
   collides with #sel-bar, the three bottom sheets and #ai-panel — four overlap rules versus zero).

   NOTE .tb-hint is a DOM child of .tb-tools. It is position:absolute against #topbar so it does
   not lay out inside the panel, but `display:none` on the panel still hides it. That is a no-op
   today (#toolrail is display:none on mobile, so the tool is always 'pointer' and .tb-hint.idle
   already hides it) and would need addressing if drawing ever returns to mobile. */

/* Body becomes the flex column that owns the split, which retires #stage's hardcoded
   `calc(100% - 48px)` (line 263) without inventing a new magic number. Safe because every other
   body descendant is out of flow: #status/#banner are absolute, #ai-panel and .snap-ov are fixed,
   and game.js's children are all fixed.

   No `flex-wrap` any more: with .tb-tools out of flow nothing CAN wrap, and a wrap rule that never
   fires is a lie about the layout. */
body.mobile { display: flex; flex-direction: column; -webkit-text-size-adjust: 100%; }
body.mobile #topbar {
  flex: 0 0 auto;
  height: auto;
  align-items: center;
  gap: 8px;
  padding: calc(5px + env(safe-area-inset-top, 0px))
           calc(8px + env(safe-area-inset-right, 0px))
           5px
           calc(8px + env(safe-area-inset-left, 0px));
}
body.mobile #stage { flex: 1 1 auto; height: auto; min-height: 0; }

/* Row 1. The LIVE badge keeps its pulsing dot and loses its 30px caption — the dot is the signal. */
body.mobile .tb-left { flex: 0 0 auto; min-width: 0; gap: 6px; }
body.mobile .live-dot { font-size: 0; gap: 0; }
/* ...and the instrument name goes entirely: row 1 already carries the symbol, the price, the 24h
   move and the feed note across a phone's width, and the name is the one item that is pure context.
   The delay note STAYS — on a market with no live dot it is the only thing saying how old the
   number is, which is exactly the claim a phone user is most likely to act on. */
body.mobile .coin-name { display: none; }
body.mobile #tf-row {
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  justify-content: flex-start;   /* centered content that overflows makes the LEADING chips unreachable */
  scrollbar-width: none;
}
body.mobile #tf-row::-webkit-scrollbar { display: none; }
/* `0 0 auto`, not shrink: a shrunk chip clips its own label instead of scrolling. */
body.mobile .tf-btn { flex: 0 0 auto; padding: 7px 9px; }

/* The disclosure panel — the same flex row .tb-tools already is (line 104 gives it display:flex),
   anchored under the bar instead of inside it. #topbar is position: relative (line 53). */
body.mobile .tb-tools {
  position: absolute;
  top: 100%; left: 0; right: 0;
  z-index: 32;                     /* over .tb-hint (31) and #stage */
  display: none;                   /* .tools-open restores the flex row it already is */
  justify-content: space-around;
  gap: 4px;
  padding: 6px calc(8px + env(safe-area-inset-right, 0px))
           6px calc(8px + env(safe-area-inset-left, 0px));
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.45);
}
body.mobile #topbar.tools-open .tb-tools { display: flex; }

/* The two chrome buttons flanking row 1. 34px keeps the bar at ~45px total; the ::after restores a
   44px hit box without a 44px visual box (.tool is position: relative, line 116). ID specificity
   (1,1,1) beats `body.mobile .tool` (0,2,1), so the 40px rule above does not fight this. */
body.mobile #tb-back,
body.mobile #tool-more { width: 34px; height: 34px; }
body.mobile #tb-back::after,
body.mobile #tool-more::after { content: ''; position: absolute; inset: -5px; }
body.mobile #tool-more { display: inline-flex; }

/* The dividers are decoration and cost 9px each at exactly the width where it matters. */
body.mobile .tb-div { display: none; }
body.mobile .tool-split #tool-snap-list { width: 28px; }   /* 20px is not a tap target */
body.mobile .tool.tool-menu { padding: 0 9px; }

/* The reserved 132px caption slot is the single biggest waste in a 374px bar — but mid-draw
   guidance matters MORE on touch, not less, so float it out of the flex flow rather than dropping
   it. .tb-hint.idle (line 170) still hides it on the pointer tool, and setTool keeps writing
   textContent + toggling .idle against a still-laid-out element, so no JS changes. */
body.mobile .tb-hint {
  position: absolute;            /* #topbar is position: relative (line 53) */
  top: calc(100% + 8px); left: 50%;
  transform: translateX(-50%);
  min-width: 0;
  padding: 4px 11px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  pointer-events: none;
  z-index: 31;
}

/* iOS ZOOMS THE PAGE when a font-size < 16px input takes focus. `.field input` inherits 13px, so
   editing any inspector or setup-form field zoomed the layout viewport — and with overflow:hidden
   there was no way back. 16px is the only reliable prevention that does not need user-scalable=no
   (which we now also set, but belt and braces: the zoom-on-focus path ignores maximum-scale on
   some iOS versions). */
body.mobile .field input[type="text"],
body.mobile .field input[type="number"],
body.mobile .field select,
body.mobile .field textarea,
body.mobile .snap-note-input,
body.mobile #ai-input { font-size: 16px; }

/* ---- bottom sheets ----
   #inspector (248px, top-right), #setup-drawer and #snap-drawer (280px full-height right docks)
   occupy 64-72% of a 390px screen. Dock all three to the bottom edge instead. `.hidden`
   (display:none !important, line 41) still wins over display:flex, so every existing
   classList.toggle('hidden') open/close keeps working untouched. */
body.mobile #inspector,
body.mobile #setup-drawer,
body.mobile #snap-drawer {
  top: auto; left: 0; right: 0;
  bottom: var(--kb, 0px);          /* rides above the software keyboard; 0 when there is none */
  width: auto;
  max-height: 62vh;
  display: flex;
  flex-direction: column;
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -10px 34px rgba(0, 0, 0, 0.5);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* min-height:0 is REQUIRED — flex:1 alone leaves min-height:auto, which refuses to shrink below
   content and so scrolls the sheet off the screen instead of scrolling inside it. */
body.mobile #inspector .insp-body,
body.mobile #setup-drawer .insp-body,
body.mobile #snap-drawer .insp-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
body.mobile .insp-head { flex: 0 0 auto; padding: 12px 10px 12px 14px; }
body.mobile .insp-tf { font-size: 11px; padding: 3px 7px; }
body.mobile .insp-head .icon-btn,
body.mobile .ind-modal-head .icon-btn { padding: 6px 10px; margin: -6px -6px -6px 0; font-size: 18px; }
/* Line 602 exists only to clear a 280px RIGHT dock, which on mobile no longer exists. */
body.mobile #stage.drawer-open #inspector { right: 0; }
/* Both now share the bottom dock. The inspector wins the overlap because you often reach it by
   tapping a .sd-row INSIDE the open drawer; its ✕ reveals the drawer again. */
body.mobile #inspector { z-index: 13; }

/* ---- popover + context menu ----
   Both are already clamped against the #stage rect in JS (showRolePopover / showChartMenu) and
   measure offsetWidth after .hidden comes off, so widening them here needs no JS. */
body.mobile #chart-menu { min-width: 210px; padding: 6px; }
body.mobile .cm-item { padding: 11px 12px; font-size: 13.5px; }

/* ---- AI panel ----
   Wins over assistant.js's injected `#ai-panel` block on specificity (1,1,1) vs (1,0,0), so it
   holds regardless of stylesheet order. The inline width/height/left/top that loadWin() restores
   would still beat it — that is handled in assistant.js, which skips geometry entirely on mobile. */
body.mobile #ai-panel {
  left: 0; right: 0; top: auto; bottom: var(--kb, 0px);
  width: auto; max-width: none; min-width: 0;
  height: 72vh; max-height: 72vh;
  border-left: none; border-right: none; border-bottom: none;
  border-radius: 14px 14px 0 0;
  resize: none;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
body.mobile #ai-panel.min { height: auto !important; }
body.mobile #ai-head { cursor: default; padding: 12px 10px; }
body.mobile .ai-hbtn { padding: 6px 9px; font-size: 17px; }

/* ---- tool rail: DESKTOP ONLY ----
   Drawing is a desktop gesture. The eleven rail tools all want a precise two-point drag, and the rail
   costs a strip of the chart's left edge — over the candles — to offer them. So on a phone it hides
   entirely rather than half-working.

   `display:none`, not visibility/opacity: it has to stop taking layout AND stop swallowing taps
   aimed at the candles underneath.

   Nothing in initToolRail() breaks. place() only READS offsetWidth/offsetHeight (0 while hidden,
   which merely loosens its own clamp), and the sole writer of `alpha.toolrail.pos` is endDrag —
   unreachable with the grip hidden — so a position saved on desktop is never overwritten by a
   phone. Selection survives too: #tool-pointer ships `active` in the markup and the tool is not
   persisted across loads, so a phone always starts in pointer mode and can still tap or long-press
   an existing drawing to inspect it.

   What went away with it: max-height/overflow-y/overscroll-behavior/touch-action:pan-y, which
   existed because at 40px targets the rail is ~452px tall — fine in a 390x844 portrait stage
   (~620px), but it overflowed landscape (~300px) and an SE with no scroll, dropping tool-long and
   tool-short off the bottom. Recover them from git if the rail ever returns to mobile. */
body.mobile #toolrail { display: none; }


/* ---- multi-select bar + the setup composer + the ribbon -----------------------------------------
   The three pieces of the "draw -> select -> one sentence -> tracked setup" gesture. All three are
   absolutely positioned inside #stage, so they ride the chart rather than the page and cannot be
   scrolled away from. */
#sel-bar {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 16px; z-index: 15;
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px 7px 14px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--border); color: var(--fg);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45); font-size: 12.5px; white-space: nowrap;
}
#sel-bar.hidden { display: none; }
.sel-count { font-weight: 600; }
.sel-make {
  background: var(--accent); border: 1px solid var(--accent); color: #fff;
  border-radius: 999px; padding: 5px 12px; font: inherit; font-weight: 600; cursor: pointer;
}
.sel-make:hover { filter: brightness(1.08); }
.sel-kbd {
  margin-left: 5px; opacity: 0.8; font-size: 11px;
  border: 1px solid rgba(255, 255, 255, 0.45); border-radius: 4px; padding: 0 4px;
}
.sel-clear {
  background: none; border: none; color: var(--muted); cursor: pointer; font: inherit;
  font-size: 15px; line-height: 1; padding: 2px 8px 2px 2px; border-radius: 4px;
}
.sel-clear:hover { color: var(--fg); }

#setup-composer {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 16px; z-index: 16;
  width: min(560px, calc(100% - 32px));
  background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.55); padding: 12px 14px; color: var(--fg);
}
#setup-composer.hidden { display: none; }
.cmp-head { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; font-size: 12px; }
.cmp-title { font-weight: 700; letter-spacing: 0.02em; }
.cmp-sub { color: var(--muted); flex: 1; }
.cmp-dir { display: flex; gap: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.cmp-dir button {
  background: var(--panel-2); border: none; color: var(--muted); cursor: pointer;
  font: inherit; font-size: 12px; padding: 4px 11px;
}
.cmp-dir button.active { background: var(--accent); color: #fff; font-weight: 600; }
#cmp-text {
  width: 100%; box-sizing: border-box; min-height: 54px; resize: vertical;
  background: var(--panel-2); color: var(--fg); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 10px; font: inherit; font-size: 13px;
}
#cmp-text:focus { outline: none; border-color: var(--accent); }
.cmp-rows { margin: 9px 0 4px; display: flex; flex-direction: column; gap: 3px; }
.cmp-row {
  display: flex; align-items: center; gap: 8px; font-size: 12px;
  padding: 3px 4px; border-radius: 6px; color: var(--fg);
}
.cmp-row:hover { background: var(--hover); }
.cmp-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; }
.cmp-kind { color: var(--muted); width: 34px; }
.cmp-px { font-variant-numeric: tabular-nums; flex: 1; }
.cmp-tf { color: var(--muted); font-size: 11px; }
.cmp-x {
  background: none; border: none; color: var(--muted); cursor: pointer; font: inherit;
  padding: 0 4px; border-radius: 4px;
}
.cmp-x:hover { color: var(--neg); }
.cmp-actions { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.cmp-hint { color: var(--muted); font-size: 11px; flex: 1; }
.cmp-err {
  margin: 6px 0 0; padding: 7px 9px; border-radius: 7px; font-size: 12px;
  color: var(--neg); background: rgba(239, 83, 80, 0.10); border: 1px solid var(--neg);
}
.cmp-busy { display: flex; align-items: center; gap: 9px; font-size: 13px; color: var(--muted); }
.cmp-busy .ai-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent);
  animation: snap-shimmer 1s ease-in-out infinite; }
.cmp-ok { display: flex; align-items: center; gap: 9px; font-size: 13px; }
.cmp-ok b { color: var(--pos); }

/* The ribbon is a STACK of pills — one per tracked setup — not a single bar, so the container is
   just a transparent column and the pill carries the chrome. `pointer-events` is off on the column
   and back on per pill, or the gaps between them would swallow drags on the chart underneath. */
#setup-ribbon {
  position: absolute; left: 12px; top: 12px; z-index: 14;
  display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
  max-width: min(420px, 60%);
  pointer-events: none;
}
#setup-ribbon.hidden { display: none; }
.rib-pill {
  pointer-events: auto; max-width: 100%;
  display: flex; align-items: center; gap: 4px;
  padding: 4px 6px 4px 10px; border-radius: 999px; font-size: 12px;
  background: var(--panel); border: 1px solid var(--border); color: var(--fg);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
}
.rib-pill.active { border-color: var(--accent); }
.rib-body {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 1px 0; text-align: left;
  background: none; border: none; color: inherit; font: inherit; cursor: pointer;
}
.rib-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; }
.rib-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.rib-status { flex: 0 0 auto; color: var(--muted); }
.rib-alerts { flex: 0 0 auto; color: var(--accent); font-variant-numeric: tabular-nums; }
.rib-more .rib-body { color: var(--muted); padding: 1px 4px; }
.rib-open {
  background: none; border: none; color: var(--muted); cursor: pointer; font: inherit;
  padding: 1px 5px; border-radius: 4px;
}
.rib-open:hover { color: var(--fg); background: var(--hover); }

/* ---- alert chip strip (inspector) ---------------------------------------------------------------
   ONE ROW, seven fixed positions, never a screen. Two states only — on (filled) / off (hollow) —
   because three states at 30px is unreadable. Kinds this drawing can never emit render dimmed and
   disabled rather than being removed, so the strip never reflows between drawings. */
.ak-wrap { margin: 2px 0 4px; }
.ak-strip { display: flex; gap: 4px; align-items: center; }
.ak-chip {
  flex: 1 1 0; min-width: 0; text-align: center;
  background: var(--panel-2); border: 1px solid var(--border); color: var(--muted);
  border-radius: 6px; padding: 4px 0; font: inherit; font-size: 10.5px; cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.ak-chip.on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
/* pointer-events:none is load-bearing, not cosmetic: a disabled button dispatches no pointer events
   of its own, so the tooltip's hit-testing is done by the STRIP (see attachChipTips) and this is
   what guarantees the move over a dimmed chip actually reaches it. */
.ak-chip:disabled { opacity: 0.32; cursor: default; pointer-events: none; }
.ak-reset {
  flex: 0 0 auto; background: none; border: 1px solid var(--border); color: var(--muted);
  border-radius: 6px; padding: 4px 7px; font: inherit; font-size: 12px; cursor: pointer;
}
.ak-reset:hover { color: var(--fg); }
.ak-note { margin-top: 4px; color: var(--muted); font-size: 10.5px; }

/* Chip tooltip: ONE bubble for the whole strip, appended to <body> and placed `fixed` from the
   chip's client rect. Not a child of the strip on purpose — the inspector scrolls, and on mobile it
   is a bottom sheet, so a nested absolute bubble would be clipped by the thing it explains. Same
   fill/border/radius/shadow/font vars as .tip above so it reads as one system; no arrow, because
   the bubble flips above/below and an arrow would have to be re-placed with it for no real gain.
   Theme comes entirely from the custom properties. */
.aktip {
  position: fixed;
  left: 0; top: 0;
  z-index: 70;                 /* above every panel and the modal overlay(60) — it explains them */
  max-width: min(280px, calc(100vw - 24px));
  padding: 7px 9px;
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 11px; font-weight: 500; line-height: 1.4; letter-spacing: .2px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .5);
  opacity: 0;
  pointer-events: none;        /* must never eat the hover that is keeping it open */
  transition: opacity .12s ease;
}
.aktip.show { opacity: 1; }
/* The abbreviation on the chip (App/Rcl/Tgt) is cryptic, so the bubble leads with the real name. */
.aktip-name { font-weight: 700; margin-bottom: 2px; }
.aktip-why { margin-top: 4px; color: var(--muted); }  /* why a disabled chip is disabled */

/* ---- mobile: the three new surfaces ------------------------------------------------------------ */
body.mobile #sel-bar {
  left: 12px; right: 12px; transform: none; bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
  min-height: 44px; justify-content: space-between;
}
body.mobile .sel-make { padding: 9px 14px; }
body.mobile .sel-kbd { display: none; } /* no keyboard to press */
/* A sheet is open: the bar would stack on top of it, so it steps aside and the composer is reached
   from inside the sheet instead. One rule, no arithmetic against the sheet's 62vh. */
body.mobile.sheet-open #sel-bar { display: none; }
body.mobile #setup-composer {
  left: 0; right: 0; bottom: var(--kb, 0px); transform: none; width: auto;
  border-radius: 14px 14px 0 0; border-bottom: none; z-index: 17;
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
}
/* 16px or focusing this zooms a viewport that has no way to zoom back (html,body overflow:hidden).
   The generic rule below only covers `.field` inputs, and this textarea is not inside one. */
body.mobile #cmp-text { font-size: 16px; min-height: 64px; }
body.mobile .cmp-dir button { padding: 9px 14px; font-size: 13px; }
body.mobile .cmp-row { padding: 7px 4px; font-size: 13px; }
body.mobile .ak-chip { padding: 8px 0; font-size: 11px; }
body.mobile .ak-reset { padding: 8px 10px; }
/* Long-pressed rather than hovered, so it is read at arm's length and gets a size to match. */
body.mobile .aktip { font-size: 12.5px; max-width: calc(100vw - 24px); }
body.mobile #setup-ribbon { left: 8px; top: 4px; max-width: calc(100% - 16px); }
body.mobile .setup-btn { padding: 11px 14px; } /* the armed "Confirm delete" needs a real target */


/* ---- MS Bias dashboard (#msbias-panel) ----------------------------------------------------------
   The one indicator readout on this page that is DOM rather than canvas, because its value is the
   per-row evidence tooltip and the multi-line "Why" — neither survives as painted text. Everything
   here reads the theme vars, so light mode needs no second rule and chart.js's applyTheme touches
   nothing. msbias.js owns .hidden, the .pos-* corner class and .collapsed; the CSS owns where each
   corner actually lands. */
#msbias-panel {
  position: absolute;
  z-index: 6;                   /* same band as #ind-chips: above #draw-overlay(5), below #toolrail(15) */
  width: max-content;
  max-width: 260px;
  /* The container is pointer-TRANSPARENT and only the rows catch the pointer — exactly what
     #ind-chips does two hundred lines up, and for the same reason: this panel sits above
     #draw-overlay, so an `auto` rectangle here means a trendline/box/ruler drag cannot even START
     anywhere under the panel, padding and inter-row gaps included. The rows must stay `auto`
     because their title= IS the evidence block. */
  pointer-events: none;
  /* The Pine's transition orange. It is the one colour on the panel that carries a state rather
     than decorating one ("BULLISH → BEARISH SHIFT", and the ⚠), so it cannot stay a literal:
     #ff9800 on the light --panel is ~2:1 at 10.5px/600, i.e. the least readable text on the page
     would be the one thing this indicator exists to announce. */
  --msb-trans: #ff9800;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
  font-size: 11px;
  color: var(--fg);
  user-select: none;
  overflow: hidden;             /* keeps the header's square corners inside the 8px radius */
}

:root[data-theme="light"] #msbias-panel { --msb-trans: #b45309; }

/* Corners — the FALLBACK. With panes-legend.js on the page its corner DOCK (A.dock, design/41 review
   P0-3) adopts #msbias-panel and #onebox-panel: it stacks them (and the canvas panels — the Tape S/R HUD,
   the VWAP Regime ribbon, the MS Bias badge) in the corner their pos-* class names, clear of each other
   and of the price legend, by writing inline left / top that override these rules.
   Every corner of this stage already has a tenant, which is why the position is a SETTING
   and not a constant; these rules only decide who yields where.
     top-left    — #ind-chips (left:8px/top:34px). Step below it with the SAME expression #ind-chips
                   uses (line ~401) plus one ~24px chip row and an 8px gap, rather than a second
                   magic number that would drift from it. msbias.js's canvas badge mirrors the 66.
     both lefts  — #toolrail docks at left:12px, ~40px wide, vertically centred, z-index 15. It wins
                   the z-fight, so the left corners start PAST it instead of underneath it.
     top-right   — #inspector (top:12px/right:12px/width:248px/z10) covers this corner whenever a
                   drawing is selected, and it should: it is the transient EDITING surface and this
                   is a passive readout. The panel is collapsible and movable; that is the answer.
     right side  — deliberately floats over the price axis, as #inspector already does.
   Known and accepted: #ind-chips wraps (flex-wrap, no max-width), so with ~10 indicators active a
   second chip row can slide under the top-left panel. The pointer-events rule above keeps those
   chips clickable everywhere except under a row itself. */
#msbias-panel.pos-tr { right: 8px; top: 34px; }
#msbias-panel.pos-tl { left: 60px; top: max(66px, calc(50px + var(--ribbon-h, 0px)), calc(var(--lg0-bottom, 0px) + 8px)); }
/* 34px clears the time axis, so the bottom corners never sit on the date labels; --bottom-strip-h is
   the height of the indicator panes below the candles + their separators, published by chart.js
   (0 with none) — the name predates the v5 panes (design/41). */
#msbias-panel.pos-br { right: 8px; bottom: calc(34px + var(--bottom-strip-h, 0px)); }
#msbias-panel.pos-bl { left: 60px; bottom: calc(34px + var(--bottom-strip-h, 0px)); }

.msb-head {
  display: flex;
  align-items: center;
  gap: 6px;
  pointer-events: auto;
  padding: 5px 4px 5px 9px;
  background: var(--panel-2);
  border-bottom: 1px solid var(--border);
}
.msb-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--muted);
  white-space: nowrap;
}
/* The analysis timeframe, not the chart's — the whole feature is that those can differ, so it is
   pinned in the header where it cannot be scrolled or collapsed out of sight. */
.msb-tf {
  margin-left: auto;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}
.msb-warn { color: var(--msb-trans); font-size: 11px; cursor: help; }
.msb-collapse {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  line-height: 1;
  padding: 2px 5px;
  border-radius: 4px;
}
.msb-collapse:hover { color: var(--fg); background: var(--hover); }

.msb-rows { display: flex; flex-direction: column; padding: 4px 0; }
.msb-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  pointer-events: auto;         /* the only pointer target in the body — the row IS the tooltip */
  padding: 2px 9px;
}
.msb-row:hover { background: var(--hover); }
.msb-k { flex: 0 0 auto; color: var(--muted); white-space: nowrap; }
.msb-v {
  flex: 1 1 auto;
  min-width: 0;                 /* without this a flex child refuses to shrink and the ellipsis never fires */
  text-align: right;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;   /* a re-rendered price must not shift the row's width */
  font-size: 10.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.msb-v.bull { color: var(--pos); }
.msb-v.bear { color: var(--neg); }
.msb-v.trans { color: var(--msb-trans); }   /* the Pine's C_TRANS, re-themed for light (see :root above) */
.msb-v.range { color: var(--muted); }

/* The last row is "Why" — a sentence, not a number. It is the one value allowed to wrap and to drop
   the monospace face; every row above it is a level or a state and stays on one ellipsized line. */
.msb-rows .msb-row:last-child {
  align-items: flex-start;
  padding-top: 4px;
}
.msb-rows .msb-row:last-child .msb-v {
  font-family: inherit;
  font-weight: 400;
  white-space: normal;
  overflow: visible;
  text-align: right;
  line-height: 1.35;
  color: var(--muted);
}

/* Collapsed = the two rows that answer "who is in control, and where does that end" — Trend and the
   protected level. msbias.js tags those two `.msb-hl`; selecting on the class rather than on
   nth-child matters because collapsed is the DEFAULT on a phone, so a positional rule would make
   reordering the rows silently change what a phone shows. The evidence rows are one tap away. */
#msbias-panel.collapsed .msb-rows .msb-row:not(.msb-hl) { display: none; }

/* ---- mobile: the MS Bias panel ----
   Narrower and smaller, because at 260px it covers a meaningful share of a 390pt stage. msbias.js
   starts it COLLAPSED here (the `mobile` build only) — on a phone the two-row read is the whole
   affordance and the evidence tooltips have no hover to fire on anyway. */
body.mobile #msbias-panel { max-width: 190px; font-size: 10px; }
body.mobile .msb-title { font-size: 9px; }
body.mobile .msb-tf { font-size: 9px; padding: 1px 5px; }
body.mobile .msb-v { font-size: 9.5px; }
body.mobile .msb-row { padding: 2px 7px; }
/* A tap target, not a pointer target — the header is the expand/collapse gesture on a phone. */
body.mobile .msb-collapse { padding: 6px 8px; font-size: 15px; }
/* Keeps --bottom-strip-h, like #onebox-panel's phone rule below: without it this rule (which
   out-specifies the desktop corner) sat the panel over the indicator panes under the candles. */
body.mobile #msbias-panel.pos-br,
body.mobile #msbias-panel.pos-bl { bottom: calc(34px + var(--bottom-strip-h, 0px) + env(safe-area-inset-bottom, 0px)); }
/* #toolrail is display:none on mobile (line ~1253), so the 60px that clears it on desktop is pure
   lost width on a 390pt stage — take the left corners back to the edge. */
body.mobile #msbias-panel.pos-tl,
body.mobile #msbias-panel.pos-bl { left: 8px; }

/* ---- One Box, Two Trades readout (#onebox-panel) ------------------------------------------------
   design/34 §5.3. Same shell as #msbias-panel above, and for the same reasons: theme vars only, the
   container is pointer-TRANSPARENT so a drawing drag can start under it, and onebox.js owns
   .hidden / .pos-* / .collapsed / .obx-compact while this file owns where a corner lands. Unlike
   MS Bias the rows carry no evidence tooltips, so they stay pointer-transparent too — only the
   header (the collapse button) takes a click.
   The bottom corners sit at 52px, not MS Bias's 34px, so the two panels stacked in one corner
   overlap less. The default corner is top-RIGHT (a user request; it was bottom-left). That corner is
   shared: MS Bias defaults there at the same offsets, and so does the JeaFx statistics panel, which
   is painted on the CANVAS. A canvas panel has no box for CSS to flow around, so jeafx.js publishes
   its bottom edge as --jfx-stats-bottom (set only while it is drawn at Top Right) and pos-tr
   stacks beneath it. MS Bias is not accounted for; move one of the two if both are on. */
#onebox-panel {
  position: absolute;
  z-index: 6;                   /* #ind-chips / #msbias-panel band: above #draw-overlay, below #toolrail */
  width: max-content;
  max-width: 270px;             /* the Event / Targets rows wrap past this rather than ellipsize */
  pointer-events: none;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
  font-size: 11px;
  color: var(--fg);
  user-select: none;
  overflow: hidden;
}
#onebox-panel.pos-bl { left: 60px; bottom: calc(52px + var(--bottom-strip-h, 0px)); }
#onebox-panel.pos-br { right: 8px; bottom: calc(52px + var(--bottom-strip-h, 0px)); }
#onebox-panel.pos-tl { left: 60px; top: max(66px, calc(50px + var(--ribbon-h, 0px)), calc(var(--lg0-bottom, 0px) + 8px)); }
#onebox-panel.pos-tr { right: 8px; top: max(34px, calc(var(--jfx-stats-bottom, 0px) + 8px)); }

/* A non-overflowing flex row: the TITLE is the only item allowed to shrink (and ellipsize); the state
   badge and the collapse button never do. In a 180px compact/phone panel the long title used to push
   the badge to "MATUR…" and the collapse button out of the panel entirely. */
.obx-head {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  pointer-events: auto;
  padding: 5px 4px 5px 9px;
  background: var(--panel-2);
  border-bottom: 1px solid var(--border);
}
.obx-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--muted);
  white-space: nowrap;
}
/* The short title "1BOX" replaces the long one wherever the panel is narrow: a grid pane / a pane
   under 560px (.obx-compact) and every phone (body.mobile, where the panel is capped at 180px). */
.obx-title-short { display: none; }
#onebox-panel.obx-compact .obx-title-full,
body.mobile #onebox-panel .obx-title-full { display: none; }
#onebox-panel.obx-compact .obx-title-short,
body.mobile #onebox-panel .obx-title-short { display: inline; }
/* The lifecycle state, pinned in the header so a collapsed panel still says it. Only ARMED and
   MATURE — a frozen box you can trade against — take the accent; everything before arming and the
   overtested LATE state read muted, so the colour itself means "this is a real range". */
.obx-badge {
  flex: 0 0 auto;
  margin-left: auto;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--muted);
  white-space: nowrap;
}
.obx-badge:empty { display: none; }
.obx-badge.obx-b-armed,
.obx-badge.obx-b-mature { color: var(--accent); border-color: var(--accent); }
.obx-badge.obx-b-valid,
.obx-badge.obx-b-forming { color: var(--fg); }
/* The ALERT BELL, between the state badge and the collapse button — a STATUS MARKER and a LINK (a
   click opens /alerts/ on this coin + timeframe; it toggles nothing). Four visible states, all on
   this one element so the header never grows a second control: OFF (grey, desaturated — no alert on
   this coin + timeframe), ON (accent, full colour), PENDING (the silent detection-cfg re-PUT is out —
   pulsing, cursor:progress; still clickable) and UNAVAILABLE (the glyph itself becomes 🔕 and the
   button dims, for an evaluator that is not running — still clickable, since the page it opens is
   where the rule is managed). `hidden` — no coin, a game replay, a bond, an API without the route —
   is not a state: the button simply is not there. `flex: 0 0 auto` for the same reason as the badge
   beside it: in a 180px compact panel a shrinking control loses its glyph before the title loses a
   letter. */
.obx-alert {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 4px;
  opacity: 0.5;
  filter: grayscale(1);
}
.obx-alert:hover { background: var(--hover); opacity: 0.85; }
.obx-alert.on { opacity: 1; filter: none; color: var(--accent); }
.obx-alert.pending { cursor: progress; animation: obx-bell-pulse 1.1s ease-in-out infinite; }
.obx-alert.unavail { opacity: 0.3; }
@keyframes obx-bell-pulse {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 0.85; }
}
/* A reduced-motion reader still needs the fourth state to be distinguishable from the other three. */
@media (prefers-reduced-motion: reduce) {
  .obx-alert.pending { animation: none; opacity: 0.7; }
}
.obx-collapse {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  line-height: 1;
  padding: 2px 5px;
  border-radius: 4px;
}
.obx-collapse:hover { color: var(--fg); background: var(--hover); }

.obx-rows { display: flex; flex-direction: column; padding: 4px 0; }
.obx-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  pointer-events: none;         /* no per-row tooltip here, so nothing in the body catches the pointer */
  padding: 2px 9px;
}
.obx-k { flex: 0 0 auto; color: var(--muted); white-space: nowrap; }
.obx-v {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;   /* a ticking position % must not shift the row's width */
  font-size: 10.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.obx-v.pos { color: var(--pos); }
.obx-v.neg { color: var(--neg); }
.obx-v.muted { color: var(--muted); font-weight: 400; }
/* Event and Targets (onebox.js tags them .obx-wrap) take a second line instead of an ellipsis: the
   clipped tail was exactly the part that said what happened ("…Upper Swe…", "…TP2 $70,2…"). Every
   other row stays single-line. */
.obx-row.obx-wrap .obx-v {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  overflow-wrap: break-word;
  line-height: 1.3;
}

/* Collapsed = the three starred rows onebox.js tags .obx-hl (Quality, Position, Event). A class,
   not nth-child, so reordering the rows never silently changes what a collapsed panel keeps. */
#onebox-panel.collapsed .obx-rows .obx-row:not(.obx-hl) { display: none; }
/* Compact = a grid pane or a pane under 560px. Collapsed there unless expanded this session. */
#onebox-panel.obx-compact { max-width: 180px; font-size: 10px; }
#onebox-panel.obx-compact .obx-title { font-size: 9px; letter-spacing: 0.03em; }
#onebox-panel.obx-compact .obx-v { font-size: 9.5px; }
#onebox-panel.obx-compact .obx-row { padding: 2px 7px; }

/* ---- mobile: the One Box panel ----
   msbias's phone treatment: narrower, smaller, the toolrail's 60px given back, the bottom corners
   above the home indicator. Collapsed by default on a phone (onebox.js). */
body.mobile #onebox-panel { max-width: 180px; font-size: 10px; }
body.mobile .obx-title { font-size: 9px; }
body.mobile .obx-badge { font-size: 9px; padding: 1px 5px; }
body.mobile .obx-v { font-size: 9.5px; }
body.mobile .obx-row { padding: 2px 7px; }
body.mobile .obx-collapse { padding: 6px 8px; font-size: 15px; }
/* A thumb target for the bell, same as the collapse button beside it. */
body.mobile .obx-alert { padding: 6px 7px; font-size: 14px; }
/* Keeps --bottom-strip-h: this rule out-specifies the desktop corner, and dropping the panes' height
   here laid the default (collapsed) panel over the momentum oscillator / RSI pane on a phone. */
body.mobile #onebox-panel.pos-br,
body.mobile #onebox-panel.pos-bl { bottom: calc(52px + var(--bottom-strip-h, 0px) + env(safe-area-inset-bottom, 0px)); }
body.mobile #onebox-panel.pos-tl,
body.mobile #onebox-panel.pos-bl { left: 8px; }

/* ---- mobile: Bookmap mode ----
   The hover tooltip becomes a bottom bar (there is no cursor to anchor it to, and a floating card
   under a thumb covers the price it describes); the gain/exit chips get real tap targets. The
   #tool-bookmap button is deliberately NOT hidden here the way #tool-game is — it lives inside
   #tb-tools, so on a phone it lands in the disclosure panel and stays reachable. */
body.mobile .bm-tip {
  position: fixed; left: 0; right: 0; bottom: var(--kb, 0px);
  border-radius: 0; max-width: none;
}
body.mobile .bm-gain-btn,
body.mobile .bm-exit { width: 40px; height: 40px; }

/* ---- mobile: Tape S/R Zones ----
   A tap opens the zone card as a bottom sheet (no cursor to anchor a card to). Its own
   body.tz-sheet-open steps #sel-bar aside: chart.js recomputes `sheet-open` from its three sheets. */
body.mobile .tz-sheet {
  position: fixed; left: 0; right: 0; bottom: var(--kb, 0px); z-index: 18;
  max-height: 50vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px));
  background: var(--panel); border-top: 1px solid var(--border); border-radius: 14px 14px 0 0;
  box-shadow: 0 -8px 28px rgba(0, 0, 0, 0.45); font-size: 12px; color: var(--muted); line-height: 1.45;
}
body.mobile .tz-sheet .tz-x { width: 40px; height: 40px; margin: -8px -8px 0 8px; font-size: 16px; }
body.mobile .tz-sheet .tz-title, body.mobile .tz-sheet .tz-tip-title { font-size: 14px; }
body.mobile .tz-sheet .tz-details > summary { padding: 6px 0; }
body.mobile .tz-sheet button.tz-member { min-height: 40px; padding: 8px 4px; }
/* ◈ on the sheet: a bar a thumb-glance can read. */
body.mobile .tz-sheet .tz-hp-pct { font-size: 17px; }
body.mobile .tz-sheet .tz-hp-bar { height: 12px; }
body.mobile .tz-sheet .tz-hp-q::after { line-height: 12px; }
body.mobile .tz-sheet .tz-hp-pips i { width: 11px; height: 6px; }
body.mobile .tz-card, body.mobile .tz-tip { display: none !important; }
body.tz-sheet-open #sel-bar { display: none; }

/* Global L4 status stays in the coin row, outside the narrow-screen tool overflow. */
#l4-pill { font: inherit; font-size: 11px; padding: 3px 7px; border: 1px solid #596273;
  border-radius: 5px; background: transparent; color: inherit; cursor: pointer; white-space: nowrap; }
#l4-pill.live { color: #55d6ac; border-color: #55d6ac; }
#l4-menu { position: absolute; top: 42px; left: 12px; z-index: 1000; padding: 8px;
  background: #17202e; border: 1px solid #596273; border-radius: 6px; }
#l4-menu button { cursor: pointer; margin: 2px; }
#l4-note { font-size: 11px; color: #e4ad6d; max-width: 240px; }

/* ---- L4 Order Book (l4book.js, design/38 Part E) ------------------------------------------------------
   The side panel lives OUTSIDE #stage. Docked (desktop, expanded) it narrows the stage, so LWC's autoSize
   shrinks the chart and every overlay anchored to #stage keeps its place; collapsed it is a slim tab. On a
   phone or a grid pane (.overlay) it floats over the chart's right side instead of stealing its width. */
:root { --l4b-w: 320px; --l4b-book: #26c6da; --l4b-owner: #f0b429; --l4b-warn: #f0b429; }
:root[data-theme="light"] { --l4b-book: #00838f; --l4b-owner: #b7791f; --l4b-warn: #b7791f; }
/* The right gutter (#stage's width rule above) is ADDITIVE: every panel docked OUTSIDE #stage on the right
   sets its OWN width var — --l4b-g (the L4 panel), --rdock-g (the trading dock), --vwapnav-g (the VWAP σ map)
   — 0px until its class is on, and --right-gutter is their sum, so any of them side by side just add up (it
   was an n-way selector table, one rule per combination). From the window's edge inward: the trading dock
   (#rdock, rdock.js — design/42 §7) at right 0, the L4 panel left of it while the dock is docked, then the σ
   map (#vwapnav-lane, vwapnav.js) against #stage's right edge, i.e. just right of the price axis — at right:
   l4b + rdock. The sum is declared ON body, where the classes set its terms, and #stage inherits it.
   body.rdock-docked is rdock.js's "docked right now" (open AND the room for it); before it runs,
   index.html's PRE-PAINT html[data-rdock] stamp (an open dock stored in raw alpha.rdock) holds the dock's
   term on a desktop-wide window, so the chart does not reflow once rdock.js takes over — which it signals
   with body.rdock-ready. chart.js drops the stamp when no dock module registered (A.rdock), so a stale one
   never leaves an empty gutter. */
:root { --rdock-w: 300px; }
:root { --l4b-g: 0px; --rdock-g: 0px; --vwapnav-g: 0px; --vwapnav-w: 86px; }
body { --right-gutter: calc(var(--l4b-g) + var(--rdock-g) + var(--vwapnav-g)); }
body.l4b-docked { --l4b-g: var(--l4b-w); }
body.rdock-docked { --rdock-g: var(--rdock-w); }
body.vwapnav-docked { --vwapnav-g: var(--vwapnav-w); }
body.rdock-docked #l4b-panel:not(.overlay) { right: var(--rdock-w); }
@media (min-width: 1280px) {
  html[data-rdock="open"] body:not(.rdock-ready) { --rdock-g: var(--rdock-w); }
  html[data-rdock="open"] body:not(.rdock-ready) #l4b-panel:not(.overlay) { right: var(--rdock-w); }
}

/* ---- the VWAP σ map (vwapnav.js; mock option D) --------------------------------------------------------
   A canvas lane OUTSIDE #stage, so it can never cover a candle: shown only while vwapnav.js sets
   body.vwapnav-docked (VWAP on, its "σ map" switch on; never on a phone, a pop-out or a narrow grid pane),
   whose --vwapnav-g narrows #stage by exactly its width. `top` and `height` are written by vwapnav.js —
   #stage's top and the MAIN price pane's height, so the sub-panes below are never covered — and it paints
   its own ground and border. --vwapnav-w must equal vwapnav-pure.js LANE_W (a test pins them together). */
#vwapnav-lane {
  display: none;
  position: fixed; top: calc(48px + var(--tabs-h, 0px)); right: calc(var(--l4b-g) + var(--rdock-g));
  width: var(--vwapnav-w); height: 0; z-index: 29;
  pointer-events: none;
}
body.vwapnav-docked #vwapnav-lane { display: block; }
body.mobile #vwapnav-lane { display: none; }
#l4b-panel {
  position: fixed; top: calc(48px + var(--tabs-h, 0px)); right: 0; bottom: 0; width: var(--l4b-w); z-index: 30;
  display: flex; flex-direction: column; background: var(--bg); color: var(--fg);
  border-left: 1px solid var(--border); font-size: 12px; line-height: 1.45;
}
#l4b-panel.hidden { display: none; }
#l4b-panel.collapsed { width: auto; bottom: auto; border-bottom: 1px solid var(--border); border-bottom-left-radius: 8px; }
#l4b-panel.collapsed .l4b-body { display: none; }
#l4b-panel.collapsed .l4b-status { display: none; }
#l4b-panel.overlay { top: calc(56px + var(--tabs-h, 0px)); bottom: 8px; right: 8px; width: min(var(--l4b-w), calc(100vw - 32px));
  border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45); }
#l4b-panel.overlay.collapsed { bottom: auto; }
body.mobile #l4b-panel.overlay { top: auto; bottom: 8px; max-height: 60vh; }
.l4b-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px 8px 12px; border-bottom: 1px solid var(--border); }
.l4b-title { font-size: 10.5px; font-weight: 700; letter-spacing: 0.9px; color: var(--muted); white-space: nowrap; }
.l4b-status { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 11.5px; }
.l4b-status.live { color: var(--l4b-book); }
.l4b-status.live::before { content: '● '; }
.l4b-collapse { font: inherit; color: var(--fg); background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 2px 8px; cursor: pointer; min-width: 32px; min-height: 26px; }
.l4b-collapse:hover { border-color: var(--muted); }
.l4b-body { overflow-y: auto; padding: 10px 12px 20px; display: flex; flex-direction: column; gap: 14px; }
.l4b-body section h3 { margin: 0 0 7px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.9px; text-transform: uppercase;
  color: var(--muted); display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.l4b-body section h3 em { font-style: normal; letter-spacing: 0; text-transform: none; font-weight: 500; text-align: right; }
.l4b-body p { margin: 0 0 6px; }
.l4b-body .num, .l4b-players { font-variant-numeric: tabular-nums; }
.l4b-mut { color: var(--muted); }
.l4b-arm { font: inherit; font-weight: 600; color: #fff; background: var(--accent); border: 0; border-radius: 6px; padding: 7px 12px;
  cursor: pointer; margin-top: 4px; }
.l4b-read { border: 1px solid var(--border); border-radius: 8px; padding: 9px 11px; background: var(--panel); }
.l4b-word { font-weight: 800; font-size: 15px; letter-spacing: 0.6px; }
.l4b-where { color: var(--muted); font-size: 11.5px; margin-left: 6px; }
.l4b-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 12px; margin: 8px 0 6px;
  font-variant-numeric: tabular-nums; }
.l4b-grid div span { display: block; color: var(--muted); font-size: 11px; }
.t-up { color: var(--pos); } .t-dn { color: var(--neg); } .t-mut { color: var(--muted); } .t-book { color: var(--l4b-book); }
.t-warn { color: var(--l4b-warn); } .t-own { color: var(--l4b-owner); }
.l4b-list { display: flex; flex-direction: column; gap: 5px; }
.l4b-item { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 2px 8px; align-items: baseline;
  padding: 5px 7px; border-radius: 6px; background: var(--panel); }
.l4b-item.ghost { opacity: 0.6; background: transparent; border: 1px dashed var(--border); }
.l4b-chip { font-size: 10px; font-weight: 800; letter-spacing: 0.4px; text-align: center; border-radius: 4px; padding: 1px 0; }
.l4b-chip.bid { color: var(--pos); background: rgba(38, 166, 154, 0.12); }
.l4b-chip.ask { color: var(--neg); background: rgba(239, 83, 80, 0.12); }
.l4b-subl { grid-column: 2 / 4; color: var(--muted); font-size: 11.5px; }
.l4b-subl b { color: var(--l4b-owner); font-weight: 600; }
.l4b-players { width: 100%; border-collapse: collapse; }
.l4b-players th { text-align: right; color: var(--muted); font-weight: 500; font-size: 11px; padding: 0 0 4px; }
.l4b-players th:first-child, .l4b-players td:first-child { text-align: left; }
.l4b-players td { text-align: right; padding: 3px 0; border-top: 1px solid var(--border); }
.l4b-players td.w { color: var(--l4b-owner); max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.l4b-feed { display: flex; flex-direction: column; gap: 3px; }
.l4b-feed div { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 8px; }
.l4b-feed time { color: var(--muted); }
.l4b-key summary { cursor: pointer; color: var(--muted); font-size: 11.5px; }
.l4b-key p { color: var(--muted); font-size: 11.5px; margin: 6px 0 0; }
.l4b-key .sw { display: inline-block; width: 22px; height: 8px; border-radius: 2px; margin-right: 6px; vertical-align: middle; }
.l4b-key .sw.wall { background: rgba(239, 83, 80, 0.3); border-bottom: 2px solid var(--neg); }
.l4b-key .sw.ghost { border: 1px dashed var(--muted); }
.l4b-key .sw.strip { background: linear-gradient(90deg, rgba(38, 166, 154, 0.6), rgba(239, 83, 80, 0.6)); }
#l4b-card { position: absolute; z-index: 14; max-width: 300px; background: var(--panel); border: 1px solid var(--border);
  border-radius: 8px; padding: 9px 11px; box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45); font-size: 12px; pointer-events: none; }
#l4b-card.hidden { display: none; }
#l4b-card h4 { margin: 0 0 6px; font-size: 12.5px; }
#l4b-card .l4b-row { display: flex; justify-content: space-between; gap: 12px; font-variant-numeric: tabular-nums; }
#l4b-card .l4b-row span:first-child { color: var(--muted); }
#l4b-card .l4b-sub { margin-top: 6px; color: var(--muted); }
@media (max-width: 860px) { :root { --l4b-w: 280px; } }
