/* The interface face.
 *
 * This shipped on the system stack, which on Windows resolves to Segoe UI - so the whole product
 * wore the operating system's face, and next to Segoe MDL2 toolbar glyphs it read as a Control
 * Panel applet with a chart in it. Replacing that with a webfont fixed the OS tell and introduced
 * a second one: the face was INTER, and by 2026 Inter at 400/600 on a dark ground is the single
 * most recognisable signature of a generated interface. Every tool a model writes wears it. A
 * face that says "somebody typed a prompt" is worse than one that says "this is Windows",
 * because the first also says the product had no author.
 *
 * IBM PLEX SANS, self-hosted, one variable file per unicode range.
 *
 * Chosen for three things this screen actually needs:
 *
 *   FIGURES. A trading terminal is ninety per cent numbers. Plex's lining figures are drawn on a
 *   single width with a flat, level baseline and an unambiguous 1 - and `tnum` locks the column
 *   without forcing the labels into a monospace, which is what makes a chart look unfinished.
 *
 *   A SIBLING FOR THE DIGITS. The rule this stylesheet already states - labels in the UI face,
 *   figures in a tabular one - only looks deliberate when the two faces share a skeleton. Plex
 *   Sans and Plex Mono are the same design at two widths, so mixing them reads as one decision
 *   rather than as two fonts that happened to be lying around. Inter had no such sibling, which
 *   is why `--mono` kept collapsing back onto Inter itself.
 *
 *   VOICE, at 11px. Plex was drawn for engineering documents: open apertures, low stroke
 *   contrast, squared-off curve joins, a straight-tailed y. Those survive rasterisation at the
 *   sizes this chrome is set in, and they are what makes the difference between "clean" and
 *   "default".
 *
 * Self-hosted rather than linked: a webfont CDN is a third party in the critical path of a page
 * whose whole promise is that the tape is live. Two ranges rather than one file, because latin-ext
 * is 30KB nobody who types in English ever needs to fetch.
 */
@font-face {
  font-family: 'Plex Sans';
  src: url('/fonts/ibm-plex-sans-latin-wght.woff2') format('woff2-variations');
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plex Sans';
  src: url('/fonts/ibm-plex-sans-latin-ext-wght.woff2') format('woff2-variations');
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
                 U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                 U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* A tabular-figure face for the CANVAS.
 *
 * The DOM gets tabular numerals from `font-feature-settings` on body, but Canvas2D does not
 * inherit CSS - it takes a font shorthand string and nothing else. So every number the chart
 * draws was set in a proportional face, and the legend's OHLC row reflowed on every tick as
 * digits changed width. A second @font-face over the same file, with the feature baked in, is
 * the only way to hand canvas a tabular font by name. */
@font-face {
  font-family: 'Plex Sans Tabular';
  src: url('/fonts/ibm-plex-sans-latin-wght.woff2') format('woff2-variations');
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
  font-feature-settings: 'tnum' 1;
}

/* The FIGURE face: Plex Mono, the same design on a fixed width.
 *
 * Where it is used is a narrow list on purpose - tick fields, addresses, hex, accelerators, and
 * the footprint's own cells - because mono everywhere reads as a terminal somebody is still
 * building rather than as a product. Static cuts rather than a variable file: three weights is
 * the whole range this interface asks of it, and 45KB of axis nobody travels is not worth the
 * download on a page that has to show a live tape. */
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/ibm-plex-mono-latin-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/ibm-plex-mono-latin-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/ibm-plex-mono-latin-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* Inter stays declared because it is still one of the choices under Interface font, and someone
   who has set it should keep it. It is no longer the default and no longer named by any token, so
   a profile that has never touched that setting never fetches the file. */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/InterVariable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Own visual identity. Deliberately NOT Exocharts' palette: they use blue=buy / red=sell on a
   near-black ground with their bundled condensed face. This is green/red on a cool dark ground.
   The LAYOUT follows theirs; the look does not.

   Two rules do most of the work in making this read as an application rather than a web page:

   1. Chrome is unselectable and takes a default cursor. Being able to drag-select the word "Tick"
      out of a toolbar is the single loudest tell that something is a document pretending to be a
      tool. Data stays selectable, because copying a number is a real thing to want.

   2. Labels are set in the UI face, numbers in tabular monospace. A trading screen is read by
      scanning columns of digits; when the digits are proportional the columns wobble and the eye
      has to re-find the decimal point on every row. Mono everywhere is the other failure mode -
      it reads as a terminal hobby project rather than a product.

   The type scale is three sizes and the radius is 2px everywhere. Restraint is the whole effect:
   more sizes and rounder corners is what "generated" looks like. */

:root {
  /* A NEUTRAL near-black ground, not a blue-tinted one. The cool slate ramp that ships as the
     default in every CSS framework is the single most recognisable "generated" signature; real
     desk tools sit on neutral grey so that the only colour on screen is data. */
  --bg: #0f0f11;
  --panel: #151517;
  --panel-2: #1b1b1e;
  --panel-3: #232327;
  --line: #252529;
  --line-2: #33333a;
  --grid: #1a1a1d;

  --ink: #e6e6e8;
  --ink-dim: #989ba3;
  --ink-faint: #6b6e76;

  /* Buy and sell are a true green and a true red, not mint and coral. Pastel market colours read
     as decoration; these read as a position. */
  --buy: #21924f;
  --buy-hi: #35c46f;
  --sell: #c2382f;
  --sell-hi: #e85449;

  /* Selection and focus are NEUTRAL. There is deliberately no brand accent in the chrome: the
     moment an active tab is brand-blue, the thing reads as a web app rather than an instrument.
     Colour is spent on data only. */
  --sel: #26262b;
  --focus: #9aa0a6;
  /* The marker colour for a selected nav row or tab. It was defined ONLY inside the TradingView
     appearance, so four settings rules resolved to nothing under every other one. Neutral here,
     because this chrome spends colour on data; theme-tv.css still overrides it with its blue. */
  --accent: #6d6d75;

  /* The primary action is an INVERSION of the ground, which is what makes one button on a screen
     unmissable without an accent colour. On a dark ground it is a near-white chip with dark ink;
     on a light one the tokens flip. It was two frozen hexes - a #d8d8dc chip with #111113 ink -
     which read as intended on dark and nearly vanished on white. */
  --primary: #e8e8ec;
  --primary-hover: #ffffff;
  --primary-ink: #101013;

  /* The fallbacks behind Plex are the host's own, in the order that degrades least: on the
     handful of frames before the webfont lands, Segoe UI Variable is closer to Plex's metrics
     than the generic sans-serif is, so the swap moves less. */
  /* THE CLOCK APP'S OWN TYPE, FIRST. -apple-system is San Francisco on an iPhone and that is
     the whole point: the owner wants the picker and the page to read like the Clock app 1:1,
     and TradingView's phone app is system-faced the same way. Apple aliases are unrecognised
     everywhere else, so Windows still lands on Segoe and Android on Plex exactly as before -
     the stack only changes what the iPhone renders. */
  --ui: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Plex Sans', "Segoe UI Variable Text",
        "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Same family for display. Plex is one design across the range; the size steps below carry the
     optical corrections that a second file would otherwise have to. */
  --ui-display: var(--ui);
  /* Plex Mono is Plex Sans on a fixed width - the same skeleton, so the two never read as two
     fonts. The platform monos stay behind it for a profile that fails to fetch. */
  --mono: 'Plex Mono', ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* ---------------------------------------------------------------- THE TYPE SCALE
   *
   * This said "three sizes, no more" and meant it. The stylesheets then grew twenty-one further
   * sizes in hard pixels - 7, 7.5, 8, 9, 9.5, 10.5, 11.5, 13, 14, 15, 16, 17, 18, 20, 22, 27 -
   * because every rung the three tokens did not offer had to be written by hand, and a rung
   * written by hand is written slightly differently the next time. Nothing looks more generated
   * than type that is ALMOST consistent: the eye cannot name the fault, it just reads the screen
   * as unresolved.
   *
   * So: seven rungs, named for the JOB rather than the size, and they are the whole set. The
   * three original names keep their meaning and their values so nothing shifts under the rules
   * that already use them; the four new ones are the sizes those rules were reaching for.
   *
   * The steps are deliberately small at the bottom and open up at the top, because a UI reads
   * 10 against 11 as two different things and 20 against 21 as a mistake.
   */
  --t-2xs: 9px;   /* micro: badge digits, axis sub-labels, the smallest thing that may exist */
  --t-xs: 10px;   /* captions, column heads, status strip */
  --t-sm: 11px;   /* the workhorse: toolbar, menus, most labels */
  --t-md: 12px;   /* emphasis inside chrome, data rows, field values */
  --t-lg: 14px;   /* dialog titles, section heads */
  --t-xl: 17px;   /* big readouts: a KPI value, a total */
  --t-2xl: 22px;  /* dialog headlines, a wallet's own address */
  --t-3xl: 27px;  /* the hero readout, and nothing else */

  /* NOT part of the scale, and deliberately fixed in pixels: iOS Safari zooms the whole page
     into any focused input whose type is smaller than 16px, and "the settings zoomed my chart
     in" is that behaviour rather than a bug of ours. A rung that moves with the interface size
     would re-open it the moment somebody chose Compact, so this one does not move. */
  --t-input-touch: 16px;

  /* WEIGHT.
   *
   * The interface ran on 400 and 600 and nothing between, which is why it read as either washed
   * out or shouting. On a dark ground 400 loses its stems at 10-11px and 600 is a highlighter;
   * 500 is what the whole chrome should have been sitting on, and it is now the default weight
   * for anything that carries a value. Plex is variable, so 500 costs nothing.
   */
  --w-regular: 400;  /* it says a sentence: prose, help text, secondary labels */
  --w-medium: 500;   /* it states a value: prices, sizes, tickers, active states - the workhorse */
  --w-semi: 600;     /* it LABELS other content: titles, section heads, column heads, the one
                        thing on a surface that has to be found first. NOT every name in a list -
                        a row's own name is that row's content, and twelve semibold rows in a
                        column emphasise nothing. Those take medium. */
  --w-bold: 700;     /* drawn marks and display only - a monogram, the boot watermark. Never
                        chrome: 700 at 11px is a highlighter, not an emphasis. */

  /* TRACKING.
   *
   * Sixteen distinct letter-spacing values existed, several of them the same number spelled two
   * ways, and three of them in PIXELS - which means they stop being tracking the moment anyone
   * changes the interface size. Four values, all in em, and each has one job:
   *
   *   - a face's own spacing is correct at text sizes, so most things get NONE
   *   - display sizes are spaced for text and need closing up
   *   - uppercase micro-labels are the only thing that genuinely needs opening up
   *   - a ticker is read as a shape, and one hair of space stops BTCUSDT reading as a word
   */
  --tr-tight: -.012em;  /* --t-lg and above, where the face's own spacing is too open */
  --tr-none: 0;         /* text sizes: the face is already right */
  --tr-ticker: .015em;  /* tickers, chips, values - one hair so capitals do not touch */
  --tr-micro: .04em;    /* badges and drawn marks: LONG, SHORT, the 123 glyph */
  --tr-label: .07em;    /* THE MICRO-LABEL, which is always uppercase - see below */

  /* THE MICRO-LABEL.
   *
   * One role, and it is the second tier this interface did not have. Everything that NAMES a
   * group rather than belonging to one - a column head, a section head, a category head, a menu
   * heading - is set here: small, semibold, uppercase, tracked, and in the faintest ink.
   *
   * It was fifteen near-misses instead. Section heads ran at 10, 11, 12 and 13px, at 500 and 600,
   * tracked at .04, .05, .06, .07, .08, .1em and once at half a pixel, some uppercase and some
   * title case, in three different inks - so a head and the rows beneath it were often the same
   * size and weight and the eye had nothing to hold on to. Type that is almost consistent is what
   * "generated" actually looks like; this is the rule that ends it.
   *
   * Uppercase is not decoration here. At 10px a title-case head and a title-case row differ only
   * in colour, and colour is the first thing a dim panel takes away.
   */

  /* ...and this is the rule, rather than a paragraph describing one.
   *
   * It was written down and never enforced, so within weeks it had drifted back to eleven
   * near-misses: four sizes (9, 10, 11, 12), three inks, and half of them at regular weight
   * because nothing said otherwise. One of them - the Theme panel's two column heads - had left
   * the family entirely and was setting itself in title case with no tracking at all, while the
   * SAME caption on the phone was uppercase and tracked. Stated once here, and the components
   * below no longer restate it; `npm run verify:type` fails if a new one is written by hand. */

  /* LEADING. Three, and the interface one is the default on body. */
  --lh-tight: 1.2;   /* headings, single-line chrome */
  --lh-ui: 1.4;      /* stacked labels, menu rows */
  --lh-text: 1.55;   /* anything that is genuinely a paragraph */

  /* THE FLOATING STACK, named once.
     Windows own 40-900 and renumber themselves inside it (lib/window.js). Everything a window
     can SUMMON has to sit above the whole band, or a dropdown opens behind the dialog that
     asked for it - which is exactly what an unbounded window counter used to cause. */
  --z-menu: 1200;       /* context menus, pick dropdowns, the settings autocomplete */
  --z-submenu: 1210;
  --z-popover: 1250;    /* colour-field popouts hanging off a row */
  --z-picker: 1300;     /* the full colour picker, which a popout can open */
  --z-tooltip: 1400;    /* never covered by anything */

  /* Two radii, like a current native OS: small on controls, larger on floating surfaces.
     2px everywhere was the 2011 read - flat colour and quiet borders stay, the corners soften. */
  /* A nudge, not a change of geometry. The reference terminal's pills sit around 6-8px on a
     24px control and its panels around 12 - the same family these were already in, one step
     softer. Going full pill (999px) would be a different product: a capsule reads as a tag or a
     filter, and these are controls. */
  --r: 7px;
  --r-surface: 12px;
  /* THE CONTROL CORNER OF THE TOUCH LAYER. The phone chips its controls at eight - rounder than
     the desktop's seven because a thumb reads a corner differently than a cursor does - and a
     score of mobile rules spelled that eight by hand. One token, so an appearance can tune the
     touch layer's corner the way --r tunes the desktop's, without hunting literals. */
  --r-ctl: 8px;
  /* THE HAIRLINE. Every divider between sections is this: the ink itself at a tenth, so a line
     separates without shouting and survives both chrome palettes by construction. Dividers that
     need to shout are a design fault, not a token gap. */
  --hairline: color-mix(in srgb, var(--ink) 10%, transparent);

  /* THE SPACING GRID. Four steps and a jump: 4 for hairline gaps inside a component, 8 for the
     gap between controls, 12 for a component's inner padding, 16 for a panel's, 24 for a
     section's. New work spells its margins from these; legacy paddings converge as each is
     touched, because a sweep that moved every padding at once would be a sweep nobody could
     review. The scale is the law; the migration is incremental. */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  /* THE THIRD RADIUS, and the reason there is one. Twenty-one places had hard-coded 3px or 6px
     for a thing too small to take the control radius - a 14px source mark, an inner chip, a
     swatch. Hard-coded is the problem: an appearance that squares the product off to 1px left
     those twenty-one rounded, and the theme that softens it to 7px left them sharp. A small
     radius still has to be the THEME's small radius. */
  --r-sm: 4px;

  /* ------------------------------------------------------------- the button
   *
   * The button is defined in controls.css, which loads directly after this sheet. It used to be
   * defined here as a physical FACE - a gradient body, a lit rim, a thick bottom edge, a contact
   * shadow, a pixel of press travel - and that recipe is gone. Two of the five appearances wore
   * it and the other three each had to un-do it by hand, which is how it leaked back into the
   * shipping look through the welded toolbar groups.
   *
   * What survives here is the one token those rules shared with the rest of the chrome: the quiet
   * ground a segmented group sits on. --btn-* is deliberately not replaced by a flat equivalent;
   * a control's ground now comes from the palette (--panel-2, --sel) like every other surface, so
   * there is nothing left for a button-only colour table to hold.
   */
  --track: rgba(255, 255, 255, .05);

  /* chrome band heights, kept in one place so the chart's share of the screen is a decision
     rather than an accident */
  --h-menu: 24px;
  /* THE TAB STRIP. Raised from 25, which held a 22px pill in a 3px track and left the product's
     primary navigation reading as a caption strip above the real interface. Thirty gives the pill
     26 and the row a presence proportionate to what it does, which is switch what you are looking
     at. theme-familiar keeps its own 23 - that one is Exocharts' measurement, not ours. */
  --h-tabs: 26px;
  --h-bar: 30px;
  /* These three, and the pair in theme-tv.css, paint the FIRST FRAME only: applyUiType in
     appearance.js writes the same names inline on the root element as soon as it loads, and an
     inline property outranks any sheet. Keep them equal to that table's Default row - when they
     drift, a fresh install shows one chrome and then resizes it. */
  /* Control height, so the interface scale can move it with the type rather than leaving 26px
     buttons around 15px text. */
  --h-ctl: 28px;
  --h-info: 22px;
  --h-status: 20px;
}

/* The light chrome. Only two things need saying now that a control is flat: the quiet ground a
   segmented group sits on has to be a light-ground wash rather than a dark one, and the primary
   action inverts - near-black on white, where the dark chrome runs near-white on black. The
   control's own colours come from the palette above and need no second table. */
/* Dark ink on a light ground wants the platform's own rasteriser back. Greyscale antialiasing is
   a dark-ground technique: on white it strips weight rather than adding it, and 11px chrome that
   is already at 500 cannot afford to lose any. This is the one property the light chrome needs
   that is not a colour. */
:root[data-chrome-light] body,
:root[data-chrome-light] {
  -webkit-font-smoothing: auto;
  -moz-osx-font-smoothing: auto;
}

/* THE CONTROLS THE ENGINE DRAWS ITSELF follow the appearance too.
 *
 * Both pages declare `color-scheme: dark` in a meta tag, which was right when there was one
 * appearance and wrong the moment a light one shipped: the date field's calendar button, a
 * select's popup, the autofill sheet and the scrollbar thumbs all stayed in dark dress against a
 * white panel. One control on a screen that came from a different application is the loudest
 * thing on it. `chromeLight` is already measured off the ground's own luminance for the button
 * face and replayed before first paint, so the same flag answers this - no second list of which
 * appearances are light, which is a list that goes stale the first time one is added. */
:root { color-scheme: dark; }
:root[data-chrome-light] { color-scheme: light; }

:root[data-chrome-light] {
  --track: rgba(16, 20, 30, .07);
  --primary: #1b1d22;
  --primary-hover: #2a2d34;
  --primary-ink: #ffffff;
}

/* Interaction feel: one fast linear transition everywhere. Eased or bouncy transitions on a
   control that reports market state make the state look uncertain. */
button,
select,
input {
  transition: background-color var(--t-tap) var(--ease), color var(--t-tap) var(--ease), border-color var(--t-tap) var(--ease);
}
/* THE MICRO-LABEL, applied. Every caption that NAMES a group rather than belonging to one: a
   section head, a column head, a category head. Small, semibold, uppercase, tracked, faintest ink
   - see the note beside --tr-label. Components own where these sit and how much room they take;
   they do not own what they are set in. Badges and drawn marks are a different role and take
   --tr-micro; a caption that is really a tab keeps its own size. */
.ps-group,
.nw-pgroup,
.newtab-mgroup,
.fl-plabel,
.fl-day,
.fl-colhead,
.aw-section,
.th-head,
.keys-group h3,
.ctx-head,
.toolfly-head,
.wl-colhead,
.wl-sec,
.ip-colhead,
.im-cathead,
.set-ac-cap,
.dd-head,
/* The trade column's own four: the stat captions along the account bar, the facts under a
   position, the head over a table of orders, and the word over the ticket's equity. They were
   four near-copies of this rule at 9px where it says 10 - which is the "almost consistent" that
   reads as unfinished from across the room. */
.tp-statlab,
.tp-factlab,
.tp-row-h,
.hdt-equity > span:first-child,
/* The head over each group in the web directory. */
.wd-head,
/* The word that labels a cascade summary in the liquidation feed. */
.fl-cascade-word,
/* The weekday heads over the feed's calendar - a column head, which is exactly this role. */
.dp-dow {
  /* type-exempt: this IS the micro-label rule, so of course it says what a micro-label is set in. */
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  line-height: var(--lh-ui);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-faint);
}

:focus { outline: none; }
/* The focus ring belongs to a control that has no border of its own to change. TEXT ENTRY has
   one, and states focus with it - see "the field, for TEXT" in controls.css - so it is exempted
   here by SHAPE rather than by a list of class names, which drifts. A checkbox keeps the ring:
   its border IS the box, so a border colour alone cannot also mean focused. */
:focus-visible:not(input, select, textarea) { outline: 1px solid var(--focus); outline-offset: -1px; }
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible { outline: 1px solid var(--focus); outline-offset: -1px; }
::selection { background: var(--sel); }

/* thin, quiet scrollbars instead of the OS defaults */
* { scrollbar-width: thin; /* The same token the WebKit thumb uses two rules below, so the two engines agree and both
     flip with the theme. It was a frozen blue-slate. */
  scrollbar-color: var(--line-2) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: var(--r-sm); }
*::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }
*::-webkit-scrollbar-track { background: transparent; }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font: var(--t-md)/var(--lh-text) var(--ui);
  font-synthesis-weight: none;
  /* Tabular figures across the whole interface. Nearly every string here is a quantity, and a
     price whose digits change width as it ticks is the difference between a readout and a jitter.
     Belt and braces: Plex's lining figures are already drawn on one width, so this is asking for
     what the face gives anyway - but the fallback stack behind it is not, and the fallback is
     what paints the first frames.

     'cv05' used to sit here too. That was INTER's feature for giving the l a tail, so that 1/l/I
     did not collapse in a symbol list. Plex draws the tail as its default - measured, not
     assumed: the five alternate features are byte-identical renders in this file - so the flag
     bought nothing and named a face that is no longer here. */
  font-feature-settings: 'tnum' 1;
  /* Plex carries no optical-size axis, so this resolves to nothing today. Left in because it is
     free and correct: it is the browser doing the right thing if the face ever gains one. */
  font-optical-sizing: auto;
  /* Greyscale antialiasing, which is right for light-on-dark: subpixel rendering fringes a pale
     stem on a dark ground and thickens it, and at 11px that reads as blur. It is the WRONG answer
     on a light ground, where it removes weight from stems that were already thin - see the
     override under [data-chrome-light] below. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overscroll-behavior: none;
}

/* A column flex, not a fixed grid template. A template of `auto auto auto auto 1fr auto` assumes
   exactly six visible children forever; hide one and the 1fr row is orphaned, leaving a band of
   background where the chart should be. Flex makes no such assumption. */
body {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  /* EVERY DIGIT THE SAME WIDTH, everywhere in the chrome.
   *
   * A UI face gives 1 a narrower slot than 8 by default, so a clock, a volume readout or a price
   * that updates twice a second reflows by a pixel or two on every tick, and the row beside it
   * shifts to make room. It is small enough that nobody names it and constant enough that
   * everybody feels it - the difference between a readout that sits still and one that fidgets.
   * The canvas has always drawn in a mono face; this is the chrome catching up. */
  font-variant-numeric: tabular-nums;
}
body > .stage { flex: 1 1 auto; min-height: 0; }

/* Chrome does not behave like text. Data does.
 *
 * The line is what the thing IS, not where it sits: a control, a label on a control, or a menu row
 * is furniture, and dragging across a toolbar to leave "Indicators FRVP Alert" highlighted is the
 * clearest possible tell that a desktop-looking app is a web page. Anything that is a VALUE - a
 * price, a symbol, a size, a field's contents, a ticket someone typed - stays selectable, because
 * the whole reason it is on screen is to be taken somewhere else.
 *
 * Audited surface by surface. The additions below were all chrome that predated this rule or
 * arrived after it: the tooltips, the settings search suggestions, the indicator rows, the phone
 * action bar, the blank-tab view buttons and the drawing palette. */
.tabs,
.bar,
.rail,
.statusbar,
.tape-head,
.win-head,
.set-tabs,
.set-nav,
.set-crumb,
.ctxmenu,
.railtip,
.set-ac,
.im-row,
.im-side,
.mobilebar,
.newtab-view,
.newtab-back,
.drawbar,
.th-root,
.al-label,
.al-help,
.wl-head {
  user-select: none;
  -webkit-user-select: none;
  cursor: default;
}
/* And the values, explicitly, because several of these sit INSIDE a container marked above. */
.tape-rows,
.set-page,
.symlist,
.wl-row,
.set-info,
.al-input,
.fld-input,
.sb b,
.stat b { user-select: text; }

/* The chart itself is a canvas: nothing on it can be selected at any setting, which is why the
   right-click menu carries Copy price. That entry is the selection affordance for the plot. */

/* Every figure lines up in its column - through TABULAR FIGURES in the UI face, not by switching
   to a fixed-width one. A monospace price list reads as a code listing; a tabular sans reads as a
   chart, and the columns are just as straight. */
.stat b,
.sb b,
.val,
.tape-row,
.s-days,
.s-range,
.im-abbrev,
.tpl-meta,
.ot-where,
.ip-sym,
.ip-meta,
input[type="number"] {
  font-family: var(--ui);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}

/* The hex field stays monospace: it is a CODE, not a quantity, and the fixed width is what makes
   a mistyped digit visible. */
.cf-hex { font-family: var(--mono); }

/* ---------------------------------------------------- feed readout / stats */

/* The menu bar's rules lived here. There is no menu bar: see the note in index.html. `.conn`
   stayed, because the feed readout moved into the panel toolbar rather than disappearing. */

.conn { display: inline-flex; gap: 10px; align-items: center; flex: 0 0 auto; padding-left: 4px; }
.conn i {
  font-style: normal;
  color: var(--ink-faint);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--t-xs);
}
/* A dot in BOTH states, coloured for the one it is in.
 *
 * The rule here used to be "no dot while everything is fine", on the argument that a permanently
 * lit lamp is decoration. That is true of a lamp with no other state - but this one goes red, and
 * a mark that only ever appears when something is wrong gives the healthy case nothing to be
 * recognised by. A green dot beside "Connected" is read once and then trusted; its absence is
 * what makes a red one register instantly. */
.conn i { color: var(--ink-dim); }
.conn i.up { color: var(--ink-dim); }
.conn i.down { color: var(--sell-hi); }
.conn i::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.conn i.up::before { background: var(--buy-hi); }

.stat-sep { width: 1px; height: 12px; background: var(--line); margin: 0 8px; flex: 0 0 auto; }

/* Frame rate, bar count and latency: instrumentation, not product. Off unless asked for, because
   the first readout a visitor sees should not be one that says "2". */
.diag { display: none; }
:root[data-debug] .diag { display: inline-flex; }
.stat {
  color: var(--ink-faint);
  font-size: var(--t-xs);
  flex: 0 0 auto;
  margin-left: 11px;
}
/* A status-bar readout STATES A VALUE, so it is medium. It was regular, which left the label and
   the number it labels separated by colour alone - and on a 20px strip in the faintest two inks on
   the ramp, colour alone is not a separation. */
.stat b { color: var(--ink-dim); font-weight: var(--w-medium); }

/* ------------------------------------------------------- stage + left rail */

.stage { display: flex; min-height: 0; overflow: hidden; position: relative; }

/* The rail's own geometry, states and strip live in rail.css - one file, because four was
   how it ended up with a 30px button inside a 41px slot inside a 46px column. */


/* The category flyout: the reference's tool list - icon, name, shortcut, star. */
.toolfly {
  position: fixed;
  z-index: 75;
  min-width: 230px;
  padding: 5px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 10px 28px rgba(0, 0, 0, .45);
}
.toolfly-head {
  padding: 6px 9px 7px;
}
.toolfly-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 32px;
  padding: 0 9px;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--ink-dim);
  font: var(--t-md) var(--ui);
  text-align: left;
  cursor: pointer;
}
.toolfly-row:hover { background: var(--sel); color: var(--ink); }
.toolfly-row.on { background: var(--sel); color: var(--ink); }
.toolfly-row.on svg:first-child { color: var(--accent, var(--ink)); }
.toolfly-name { flex: 1 1 auto; }
/* Clean text, the owner's rule: "Alt + F in white text" - not a chip. */
.toolfly-row kbd {
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  color: var(--ink-dim);
  font: var(--t-sm) var(--ui);
}
.toolfly-sep { height: 1px; margin: 4px 6px; background: var(--line); }
.toolfly-tick { display: inline-flex; width: 15px; justify-content: center; color: var(--accent, var(--ink)); }
.toolfly-row.danger:hover { color: var(--sell-hi); }
.toolfly-row.off { color: var(--ink-faint); cursor: default; }
.toolfly-row.off:hover { background: transparent; color: var(--ink-faint); }
.toolfly-why { margin-left: auto; color: var(--ink-faint); font-size: var(--t-xs); }
.toolfly-star { display: inline-flex; color: var(--ink-faint); opacity: 0; transition: opacity var(--t-tap) var(--ease), color var(--t-tap) var(--ease); }
.toolfly-row:hover .toolfly-star, .toolfly-star.on { opacity: 1; }
.toolfly-star.on { color: #d0a05a; }
.toolfly-star:hover { color: var(--ink); }

/* EVERY favourite star in the product is two glyphs, one shown: the set's outline until the
   control carries `.on`, the set's own SOLID fill-weight star after (starGlyphs in icons.js).
   `fill: currentColor` on the outline glyph was a no-op everywhere it was written - Phosphor's
   light weight draws its star as an outline whose path is itself filled, so refilling the path
   repainted the outline - and a starred row read exactly like an unstarred one. */
.star-fill { display: none; }
.on > .star-fill { display: block; }
.on > .star-line { display: none; }
/* Chosen IS a change of state, so the solid glyph lands with the product's own interval - only
   when the control was just clicked (`.pop`), never on a repaint of rows starred long ago. */
@keyframes star-in { from { opacity: 0; transform: scale(.55); } to { opacity: 1; transform: none; } }
.pop.on > .star-fill { animation: star-in var(--t-pop) var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .pop.on > .star-fill { animation: none; }
}

/* Rail tooltip. The native title attribute takes a second to appear and cannot put the shortcut
   on its own line, and a tool palette is exactly where someone is hunting for a shortcut. */
.railtip {
  position: fixed;
  z-index: var(--z-tooltip);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 7px;
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 4px 10px rgba(0, 0, 0, .3);
  color: var(--ink);
  font-size: var(--t-sm);
  white-space: nowrap;
  pointer-events: none;
}
.railtip b {
  color: var(--ink-faint);
  font-weight: var(--w-regular);
  font-family: var(--mono);
  font-size: var(--t-xs);
}

/* With a note the tip becomes two lines: the name and its shortcut on the first, what the thing is
   FOR underneath. A single row cannot carry both, and the note is the half worth reading - the
   toolbar labels are abbreviations chosen to fit, so expanding one is not the same as explaining
   it. Capped in width so a note wraps rather than running off a narrow window. */
.railtip.has-note {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 2px 10px;
  max-width: 300px;
  white-space: normal;
}
.railtip.has-note i {
  grid-column: 1 / -1;
  color: var(--ink-dim);
  font-size: var(--t-xs);
  font-style: normal;
  line-height: var(--lh-ui);
}

/* indicator toggles in the toolbar */
.toggles { display: inline-flex; gap: 0; flex: 0 0 auto; }
.toggles button {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-faint);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 5px 7px 3px;
  cursor: pointer;
}
.toggles button:hover { color: var(--ink); }
.toggles button.on { color: var(--ink); border-bottom-color: var(--line-2); }

/* Nothing starred means no row and no rule. `:empty` rather than a JS class: the row's own
   emptiness is the condition, so there is no second piece of state to keep in step with it. */
.toggles:empty { display: none; }
.toggles:empty + .div { display: none; }

/* ------------------------------------------------------- the toolbar FITS

   A toolbar that scrolls sideways is a toolbar with things hidden in it, and the arrows at each
   end are an apology rather than a fix. So the bar measures itself and COMPRESSES in three
   ordered steps until it fits, and only a genuinely tiny panel - a quarter of a 2x2 grid - ever
   reaches the arrows.

   The order is the order of what can be given up, cheapest first.

     fit-1  Alert and Replay keep their icon and lose their word. The icon still says which
            button it is, the tooltip still says it in full, and nothing moves.
     fit-2  The controls that have a second, equally fast route: FRVP (in the rail, under
            volume-based tools), Now (Data > Jump to now), and the fifth and sixth quick
            timeframes, which the caret beside them opens along with every other period.

            NOT the shortcut card. It was trimmed here on the reasoning that the Settings and Help
            menus both reach it - true, and it still cost the card the only door anyone finds by
            looking. It also bought nothing: the button lives in .bar-end, which is PINNED, so
            hiding it gives its twenty-eight pixels to a strip that already scrolls rather than to
            anything that was being pushed off. A door that leaves the room at exactly the window
            width most people read the card in is not a door.
     fit-3  Indicators loses its word - last, because it is the door to everything the chart can
            plot and a bare glyph is a puzzle - the quick row trims to three, and the tick
            stepper drops its multiplication sign, keeping the word that names it.

   Nothing is ever REMOVED. Every control in every step is still reachable, and the active
   timeframe is exempt from the trim - :not(.on) - because hiding the pill that says where you
   are is the one thing this must not do. */
.bar.fit-1 #alertBtn .lbl,
.bar.fit-1 #replayTopBtn .lbl { display: none; }
.bar.fit-1 #alertBtn,
.bar.fit-1 #replayTopBtn { padding: 0 7px; gap: 0; }

.bar.fit-2 #frvpBtn,
.bar.fit-2 #nowBtn { display: none; }
/* The venue's market type folds into the button's tooltip here - see the note in app.js. It is
   the widest droppable string in the row and the reason the toolbar still scrolled at 900px with
   every other step already spent. */
.bar.fit-2 #venueBtn .mkt { display: none; }
.bar.fit-2 .tf-quick .tfq:nth-child(n+5):not(.on) { display: none; }

.bar.fit-3 #indicatorsBtn .lbl { display: none; }
.bar.fit-3 #indicatorsBtn { padding: 0 7px; gap: 0; }
.bar.fit-3 .stepper .cap .lbl { display: none; }
.bar.fit-3 .tf-quick .tfq:nth-child(n+4):not(.on) { display: none; }

/* ------------------------------------------------------------- status bar */

.statusbar {
  display: flex;
  align-items: center;
  gap: 16px;
  height: var(--h-status);
  padding: 0 9px;
  background: var(--panel);
  border-top: 1px solid var(--line);
  font-size: var(--t-xs);
  color: var(--ink-faint);
  white-space: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}
.statusbar::-webkit-scrollbar { display: none; }
.sb { flex: 0 0 auto; }
/* The status bar's one button: a quiet text control in the bar's own type, not a pill. */
.sb-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 18px;
  padding: 0 6px;
  border: 1px solid transparent;
  border-radius: var(--r);
  background: transparent;
  color: var(--ink-faint);
  font: inherit;
  cursor: pointer;
}
.sb-btn:hover { color: var(--ink); }
.sb-btn svg { width: 11px; height: 11px; }
.sb b { color: var(--ink-dim); font-weight: var(--w-medium); }
/* A TOGGLE IN THE BAR READS AS ON THE WAY THE INDICATOR STRIP DOES: the label brightens to the
   full ink and takes a rule under it. No pill, no dot, no colour - the bar is the quietest row on
   the screen and a lit chip in it would be the loudest thing in the window. */
.sb-toggle { color: var(--ink-faint); border-bottom: 1px solid transparent; padding-bottom: 1px; }
.sb-toggle:hover { color: var(--ink-dim); }
.sb-toggle.on { color: var(--ink); border-bottom-color: var(--line-2); }
.sb.dim { color: #39424e; }
/* The readouts are BUTTONS: same type and colour as the labels they replaced, plus a hover lift
   and a chevron on the two that open dropdowns - the reference's own status bar grammar. */
button.sb {
  height: 18px;
  padding: 0 6px;
  margin: 0 -2px;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
button.sb:hover { color: var(--ink); }
button.sb:hover b { color: var(--ink); }
/* A drawn chevron: two hairline strokes, centred by geometry rather than by a glyph's baseline. */
.sb-chev {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 5px;
  margin-bottom: 1px;
  border-right: 1px solid var(--ink-faint);
  border-bottom: 1px solid var(--ink-faint);
  transform: rotate(45deg);
  vertical-align: middle;
}
button.sb:hover .sb-chev { border-color: var(--ink); }

/* A qualifier that rides with a value rather than competing with it: the row height in money
   beside the tick multiplier. One weight down and one step back in colour, so the readout still
   reads as "Tick 40" at a glance and answers "forty of what" without being hovered. */
.sb-sub {
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  margin-left: 3px;
}
button.sb:hover .sb-sub { color: var(--ink-dim); }

/* ------------------------------------------------------------------- tabs */

.tabs {
  display: flex;
  align-items: stretch;
  gap: 0;
  /* The strip is sized by what it HOLDS, not by a number that has to be kept in step with it.
     It was `height: var(--h-tabs)` - 25px - while the TV appearance sizes its pill at 24px and
     pads the strip 4px top and bottom, which needs 32. The pill did not fit its own strip: its
     rounded top corners were clipped flat against the window edge and the row could be nudged
     4px vertically by a stray trackpad gesture, because an overflowing `overflow-x: auto` box
     scrolls in BOTH directions. min-height keeps the empty strip's presence; auto lets any
     appearance set whatever pill height it wants and have the strip agree. */
  height: auto;
  min-height: var(--h-tabs);
  overflow-y: hidden;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }

/* TABS AS PILLS IN A TRACK.
 *
 * They were classic chrome tabs: square, butted against each other, separated by rules, with the
 * active one joined to the toolbar by a break in its border. That grammar is correct for a window
 * whose tabs sit directly above the thing they switch - and ours sit at the FOOT now, joined to
 * nothing, where a notched rectangle reads as a leftover rather than as a tab.
 *
 * So they take the shape the rest of the application already uses for "one of these is current":
 * a recessed track holding pills, only the active one raised. Same treatment as the timeframe
 * row, which means the two rows of the window that answer "which one" answer it the same way.
 */
.tabs {
  padding: 3px 4px;
  gap: 3px;
  align-items: center;
}
.tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  font: inherit;
  font-size: var(--t-sm);
  /* A TAB AT REST IS QUIET, NOT DISABLED.
   *
   * This said --ink-faint, which is the ink reserved for things that are barely there: a
   * placeholder, a unit beside a number, a day outside the month. Every other quiet control in the
   * product - see controls.css, which this tab is already listed in - rests at --ink-dim, and the
   * override here is the reason an unselected tab read as switched off rather than as somewhere
   * you could go. The comment below already claims the shared system owns everything but the
   * metrics; now it does.
   *
   * Medium, for the same reason a headline is: a tab's name is its own content, not a label for
   * something else, and 400 at 11px on a dark ground loses its stems. */
  font-weight: var(--w-medium);
  color: var(--ink-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r);
  padding: 0 11px;
  /* ONE NUMBER. It was `calc(var(--h-tabs) - 7)` clamped by `min-height: 22px` - 25 minus 7 is 18,
     so the calc never once decided the height and the strip was sized by a floor that had nothing
     to do with the token above it. Two numbers that disagree, one of them dead. */
  height: calc(var(--h-tabs) - 4px);
  cursor: pointer;
  white-space: nowrap;
  position: relative;
  transition: background var(--t-tap) var(--ease), color var(--t-tap) var(--ease), box-shadow var(--t-tap) var(--ease);
}
/* The rest of the tab - hover, the on-ground, the press - is the shared control system in
   controls.css. A tab is a latch, and it latches the same way every other latch in the product
   does: the quiet ground, full-strength ink, no frame. */

/* The period leads, as a chip, the way the reference terminal leads its own tab with one. It is
   the thing that differs between two tabs on the same instrument, which is the common case. */
.tab-tf {
  flex: 0 0 auto;
  padding: 0 4px;
  /* NO WELL. This was a filled, rounded chip sitting inside a filled, rounded tab - a pill inside
     a pill, which is two statements of "this is an object" about one object, and the single
     loudest thing in the strip. The period is a QUALIFIER on the tab's name; it reads as one in
     weight and ink, the way the venue does inside the instrument cell. */
  border-radius: 2px;
  background: transparent;
  color: var(--ink-faint);
  font-size: var(--t-xs);
  /* Semibold and micro-tracked, which is the chip treatment rather than the value one: at 10px
     on a wash a period has to survive being read from across a desk, and medium does not. The
     shipping appearance already set it this way and the base did not, so the same chip wore two
     weights depending on the theme. */
  font-weight: var(--w-semi);
  letter-spacing: var(--tr-micro);
}
.tab.on .tab-tf { color: var(--ink); }

.tab-label { overflow: hidden; text-overflow: ellipsis; max-width: 190px; }
/* The drag image: a chip, positioned off-screen for the one frame the browser snapshots it. */
.tab-ghost {
  position: fixed;
  top: -100px;
  left: -100px;
  padding: 5px 12px;
  background: var(--panel-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  color: var(--ink);
  font: var(--t-sm) var(--ui);
  white-space: nowrap;
}
/* The in-place rename field: the tab's own metrics, no chrome of its own. */
.tab-edit {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink);
  background: transparent;
  border: 0;
  outline: none;
  width: 110px;
  padding: 0;
}
/* The close only appears on the tab under the pointer, and on the active one. A row of crosses is
   a row of invitations to lose work. */
.tab-x {
  color: var(--ink-faint);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity var(--t-tap) var(--ease), color var(--t-tap) var(--ease);
}
.tab:hover .tab-x,
.tab.on .tab-x { opacity: 1; }
.tab-x:hover { color: var(--sell-hi); }
/* The same ink as the tabs beside it: a New tab control resting two shades below them read as
   the one thing on the strip that was unavailable. */
.tab-add {
  color: var(--ink-dim);
  padding: 0 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--t-tap) var(--ease);
}
.tab-add:hover { color: var(--ink); }

/* ---------------------------------------------------------------- toolbar */

/* Overflow arrows: sticky at the scroll container's ends, over a fade so glyphs slide UNDER
   rather than being guillotined at the edge. Hidden entirely when the bar fits. */
/* `hidden` must actually hide: the display rule below would otherwise win over the attribute. */
.bar-scroll[hidden] { display: none; }
.bar-scroll {
  position: sticky;
  z-index: 6;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: var(--h-ctl, 28px);
  border: 0;
  background: var(--bg);
  color: var(--ink-dim);
  cursor: pointer;
}
.bar-scroll:hover { color: var(--ink); }
.bar-scroll.left { left: 0; box-shadow: 8px 0 8px -4px var(--bg); }
.bar-scroll.right { right: 0; box-shadow: -8px 0 8px -4px var(--bg); }

/* ================================================== THE WINDOW FURNITURE IS NOT A PAGE
 *
 * The strip, the toolbar and the status bar are not content. They are the frame around it, and a
 * frame is read by GLANCING - you look for the tick field, you do not read the toolbar. Every
 * other surface in this product is read properly, which is why the control system's default
 * control is 28px of 12px type: that is the right size for a thing a person stops at.
 *
 * The furniture had been built at that same size, and one rung LARGER again in the shipping
 * appearance, because it was ported from a web application's chrome. Measured against the desktop
 * terminal this product is judged next to: their tab strip is 23px, their toolbar 22, their type
 * 11, their controls 20 - four bands of chrome in 96 pixels. Ours spent 68 on two. Per band we
 * were half again as tall and a fifth larger in type, and that difference - not any one control -
 * is the whole of why the top of the window reads as a web page wrapped around a chart while the
 * chart itself reads as a terminal.
 *
 * So the furniture gets its own rung of the SAME system rather than a set of hand-tuned pixels:
 * the three tokens the control system already keys off are re-pointed here, once, and every
 * control inside a band follows without being named. A dialog is still read properly and still
 * gets 28px - the scale changes where the reading changes.
 *
 * The corner comes with it. Seven pixels of radius on a toolbar cell is a web app's rounding; a
 * terminal squares them. Two is ours - not their zero, and not a capsule.
 */
.tabstrip,
.bar,
.statusbar {
  --h-ctl: 22px;
  --c-sm: 22px;
  --c-md: 22px;
  --t-sm: 11px;
  --t-md: 11px;
  --r: 2px;
}

.bar {
  display: flex;
  align-items: center;
  gap: 3px;
  height: var(--h-bar, 30px);
  padding: 0 6px;
  background: var(--panel);
  overflow-x: auto;
  scrollbar-width: none;
  white-space: nowrap;
}
.bar::-webkit-scrollbar { display: none; }

.ctl {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r);
  height: var(--h-ctl, 26px);
  padding: 0 9px;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  flex: 0 0 auto;
}
.ctl:hover { color: var(--ink); }

/* The instrument button. It holds a TICKER, so it takes the ticker's tracking rather than the
   display tightening it had - a name set tighter than the list it was chosen from is the same
   string wearing two designs. */
.ctl.strong {
  font-family: var(--ui-display);
  letter-spacing: var(--tr-ticker);
  font-weight: var(--w-semi);
  color: var(--ink);
  font-size: var(--t-md);
  background: var(--panel-2);
}
/* One step back, not two. This button is the chart's identity: in a list a faint settlement half
   is quiet, but here it would read as half the name having failed to load. */
.ctl.strong .tk-quote { color: var(--ink-dim); }

.ctl.dim { color: var(--ink-faint); }
.ctl.dim:hover { color: var(--ink-dim); }
.ctl.tiny { padding: 0 5px; color: var(--ink-faint); font-family: var(--mono); }
.ctl.tiny:hover { color: var(--ink); }
.ctl.icon { padding: 0 7px; font-size: var(--t-md); color: var(--ink-faint); }
.ctl.icon:hover { color: var(--ink); }
/* The reference's own text-type glyph is literally the string 123 in a small outlined box - a
   designed mark there, so the same mark here rather than an invented pictogram. */
.ctl.modeglyph {
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  letter-spacing: var(--tr-micro);
  font-variant-numeric: tabular-nums;
}

/* One icon host, everywhere. The SVG inherits currentColor, so an icon takes the colour of the
   control it sits in and needs no state rules of its own. */
.ic { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; }
/* The gap goes on the BUTTON, not between the icon and its neighbour. A label is a bare text
   node, and `.ic + *` selects elements only - so the rule matched nothing and every icon sat
   flush against its word. */
.ctl { gap: 6px; }
.ctl.icon { gap: 0; }
.ctl.date {
  color-scheme: dark;
  color: var(--ink-dim);
  font-family: var(--mono);
  font-size: var(--t-sm);
  width: 112px;
  padding: 0 4px;
}
.ctl.date::-webkit-calendar-picker-indicator { opacity: .4; }
.ctl.date:hover { color: var(--ink); }

/* --------------------------------------------------------------- pressables

   Gone from here, on purpose.

   This block used to build a physical FACE - a gradient body, a lit rim along the top, a bottom
   border darker than the sides, a contact shadow, and a pixel of press travel - and hand it to
   four selectors by name. That recipe is the 2008 web button, and naming its wearers by hand is
   why it leaked: a control added to the toolbar later failed RAISED in three appearances and flat
   in two, and the welded toolbar groups kept their rim in every one of them because the theme
   that flattens listed the controls it flattens and the list had drifted.

   The system that replaces it is controls.css: four roles, three heights, one radius, applied by
   role rather than by name. What stays here is only what is genuinely about THIS sheet's
   layout - the sizes, the paddings and the type of each control - and the hover law, which the
   system now states once for every role.

   THE HOVER LAW, kept because it is the product's own and it is right:

   A control that has no face at rest never gains one on hover. Its ink lights to full white and
   NOTHING appears - no plate, no border, no ground. A control that has a face keeps its frame and
   brightens it. The plate-on-hover pattern is the single loudest default-UI tell: a grey square
   materialising behind a small glyph, twenty times across one toolbar. The expensive version of
   hover is luminance. */

/* -------------------------------------------------------- segmented groups

   The pattern the reference terminal uses everywhere - Live/Depth/Historical, Cross/10x,
   Long/Short: a track holding pills, where only the current one is marked. It states the choice
   as a POSITION rather than as a colour, which is why it survives without an accent.

   The track used to be a recessed well: a dark ground with an inset shadow to sink it. A well is
   the same skeuomorphic claim the raised button made, from the other side, and it cost the row
   contrast at exactly the size where contrast is all it has. It is a wash now - a ground one step
   off the toolbar - and the marking of the current pill is the system's, in controls.css.

   Geometry lives here; paint lives there. */
.tf-quick {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  /* CONCENTRIC: an outer corner is the inner corner plus the gap between them, and the gap here
     is the 2px padding. At +3 the wrapper's curve ran ahead of the pills inside it - the kind of
     quarter-millimetre that nobody can name and everybody can see, because two curves that are
     nearly-but-not-quite parallel is what a corner looks like when it was not drawn on purpose. */
  border-radius: calc(var(--r) + 2px);
  background: var(--track);
}
.tfq { padding: 0 7px; }

/* The value cluster: instrument, time and tick are three ADJACENT boxes - the reference's own
   left corner. Each is ONE field with zones inside it, so their children give up their own
   bodies; the field itself, and that giving-up, are stated once in controls.css. */
.sym-group,
.tf-group {
  display: inline-flex;
  align-items: center;
  /* The group and the control it holds take the SAME token - a welded group is one control with
     zones in it, not a box a control sits inside. */
  height: var(--h-ctl, 22px);
  flex: 0 0 auto;
  overflow: hidden;
}

/* The stepper is ONE field with three zones, not three stacked fields. */
.stepper { height: var(--h-ctl, 22px); overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .ctl, .tfq, .toggles button, .th-preset, .newtab-view, .sb-btn { transition: none; }
  .ctl:active, .tfq:active, .toggles button:active { transform: none; }
}


.div { width: 1px; height: 14px; background: var(--line); opacity: .6; flex: 0 0 auto; margin: 0 5px; }
.spacer { flex: 1 1 auto; }

/* The window verbs hold the right edge while the chart controls scroll under them. Opaque, so
   what scrolls past goes behind rather than through. */
.bar-end {
  position: sticky;
  right: 0;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding-left: 8px;
  background: var(--bg);
}
/* When the bar clips, whatever slides under this cluster FADES under it instead of being
   guillotined mid-word - "FPB" reads as broken; a dissolve reads as "there's more". Same soft
   shadow the scroll arrows already wear, wider. */
#bar.clip-r .bar-end { box-shadow: -26px 0 18px -6px var(--bg); }

/* the tick stepper reads as one object, not three loose buttons */
.stepper {
  display: inline-flex;
  align-items: center;
  gap: 0;
  flex: 0 0 auto;
  height: 22px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.cap { color: var(--ink-faint); font-size: var(--t-sm); padding: 0 6px 0 7px; }
.stepper .ctl { border-radius: 0; border: 0; height: 100%; }
.bar.fit-2 .stepper .sub,
.bar.fit-3 .stepper .sub { display: none; }
/* A typable field that does not look like a form field until you are in it: the toolbar reads as
   a strip of values, and a boxed input in the middle of it would read as the odd one out. */
.val {
  width: 44px;
  min-width: 44px;
  height: 100%;
  text-align: center;
  color: var(--ink);
  font-size: var(--t-sm);
  font-family: var(--mono);
  padding: 0 2px;
  background: transparent;
  border: 0;
  outline: none;
}
.val:hover { color: var(--ink); }
.val:focus { background: var(--bg); color: var(--ink); }
/* the native number spinners duplicate the +/- buttons either side of this */
.val::-webkit-outer-spin-button,
.val::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.val { -moz-appearance: textfield; appearance: textfield; }

/* A clamped tick is drawing a coarser row than the number claims, so the number says so. */
.val.clamped { color: #d0a05a; }

.meta { color: var(--ink-faint); font-size: var(--t-sm); flex: 0 0 auto; }
.live.on {
  color: var(--buy-hi);
  border: 1px solid #2c4a38;
  border-radius: var(--r);
  padding: 1px 5px;
  font-size: var(--t-xs);
  
}

/* ------------------------------------------------------- shared value tones */

/* The readout band's rules lived here. It is drawn inside the plot now, as the first rows of the
   chart legend - see legendHeadLines() in app.js. These three tones stayed: they are used by the
   tape, the dialogs and the pickers as well. */

.muted { color: var(--ink-faint); }
.pos { color: var(--buy-hi); }
.neg { color: var(--sell-hi); }

main { position: relative; overflow: hidden; flex: 1 1 auto; min-width: 0; }
main canvas { width: 100%; }

/* The first frame ARRIVES. The canvas is held invisible until the chart has completed one real
   draw, then fades in over one breath - so boot reads as a single arrival instead of bars popping
   and the auto-fit jumping. An invisible EMPTY canvas looks identical to a visible one (both are
   the ground colour), so nothing is lost if scripts never run.

   And a RELOAD breathes the same way: while bars are being fetched the old frame sinks to a dim
   glow - an eased fade the eye rides down through the shades rather than a hard blank - and once
   the new frame has painted underneath it, it rises back. The auto-fit jump happens while dimmed,
   so what fades up is a finished chart. Data itself never animates; this is the PAGE of it. */
main canvas { opacity: 0; }
body.chart-ready main canvas {
  opacity: 1;
  transition: opacity var(--t-pop) var(--ease);
}
/* A RELOAD DIMS; IT DOES NOT BLANK.
 *
 * At 0.12 the chart is gone - the screen goes dark, and since a reload takes the better part of a
 * second the impression is of the application stalling rather than of it fetching. Held at 0.55
 * the old frame is still readable underneath, which is what it is FOR: the prices you were looking
 * at stay on screen while the new grid arrives. The fade is shorter to match, because 220ms each
 * way was itself a third of the wait. */
body.chart-ready.chart-loading main canvas { opacity: 0.55; }
@media (prefers-reduced-motion: reduce) {
  body.chart-ready main canvas { transition: none; }
}

/* ------------------------------------------------------- the book column

   Docked to the right of the chart, its left edge draggable. The grip is absolutely positioned
   inside the aside rather than being a flex child of the stage: a 5px flex item is width the
   layout has to reserve and every other panel then has to know about, and a handle that is not
   ON the border it drags always ends up a pixel off it. */

.dompanel {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  min-width: 200px;
  background: var(--panel);
  border-left: 1px solid var(--line);
  overflow: hidden;
}
.dompanel-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.dompanel-grip {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 5px;
  cursor: ew-resize;
  z-index: 2;
  /* Invisible until it is wanted. A permanent stripe down the edge of a panel is chrome nobody
     asked for; a hairline that appears under the pointer says "this edge moves" at the moment
     the question is being asked. */
  background: transparent;
  transition: background var(--t-tap) var(--ease);
}
.dompanel-grip:hover,
body.resizing-col .dompanel-grip { background: var(--accent); opacity: .7; }
/* While a column is being dragged the pointer belongs to the drag, not to whatever it happens
   to be over - without this the chart's own crosshair fights the resize for the cursor. */
body.resizing-col { cursor: ew-resize; user-select: none; }
body.resizing-col canvas { cursor: ew-resize !important; }

/* ------------------------------------------------- the liquidation column

   The map's summary, docked where the map view used to dock it. Same object as the book column
   next door: an aside that takes its width off the chart, with the left edge draggable. It is a
   column rather than a floating window because the ladder is drawn flush to the right edge of
   the plot - a window there covers the thing it is summarising. */
.liqpanel {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  min-width: 240px;
  background: var(--panel);
  border-left: 1px solid var(--line);
  overflow: hidden;
}
.liqpanel-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.liqpanel-grip {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 5px;
  cursor: ew-resize;
  z-index: 2;
  background: transparent;
  transition: background var(--t-tap) var(--ease);
}
.liqpanel-grip:hover,
body.resizing-col .liqpanel-grip { background: var(--accent); opacity: .7; }
/* Docked, the panel is the surface - it does not need the window's own padding shell. */
.liqpanel .lm-root { padding: 10px 12px 14px; }

/* The ladder itself: one strip of real buttons, then canvas to the floor. */
.ladder-host {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
}
.ladder-bar {
  flex: 0 0 auto;
  display: flex;
  /* WRAPS RATHER THAN CLIPS. The strip carries eight controls and the column it lives in can be
     dragged to two hundred pixels; a second row of buttons is honest, a row that runs off the
     edge is not. */
  flex-wrap: wrap;
  row-gap: 2px;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-bottom: 1px solid var(--line);
  --h-ctl: 22px;
}
.ladder-bar .ctl { padding: 0 8px; font-size: var(--t-sm); }
/* An ON toggle keeps a ground, like every other latch in this application - see the hover law
   above. It is the state that has a face; hover only lights the ink. The inset rim is what makes
   it legible at 22px: a ground alone is four points of luminance against the panel, which is a
   difference you can measure and not one you can see across a room. */
.ladder-bar .ctl.on {
  color: var(--ink);
  background: var(--sel);
  box-shadow: inset 0 0 0 1px var(--line-2);
}
.ladder-sp { flex: 1 1 auto; }

/* THE WAY BACK, and it is only here when you are away. Sized and coloured as a real suggestion
   rather than as one more word in a row of words - it is the single thing the panel wants you to
   press at that moment, so it is allowed to look like it. */
.ladder-bar .ctl.ladder-back {
  color: var(--ink);
  background: var(--sel);
  box-shadow: inset 0 0 0 1px var(--line-2);
  margin-left: 6px;
}
.ladder-bar .ctl.ladder-back:hover { background: var(--panel-3); }
.ladder-bar .ctl.ladder-back[hidden] { display: none; }

/* The row-size stepper is ONE control with three parts, so it wears one track and one rim rather
   than three loose buttons that happen to sit next to each other. The arrows are flush to the
   value, which is what makes it read as a stepper and not as a sentence. */
.ladder-step {
  display: inline-flex;
  align-items: center;
  height: 22px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--panel-2);
  overflow: hidden;
}
.ladder-step .ctl {
  height: 20px;
  padding: 0 7px;
  border-radius: 0;
  color: var(--ink-dim);
}
.ladder-step .ctl:hover { color: var(--ink); background: var(--sel); }
/* The value in the middle of the stepper is a button too - it opens the list of sizes, the way
   the reference books put their grouping dropdown behind the number itself. It carries no ground
   of its own; the track it sits in is the control. */
.ladder-step .ctl.ladder-group { color: var(--ink); }
.ladder-step .ctl.ladder-group:hover { background: var(--sel); }

/* BOOK | TAPE, on a column too narrow to hold both - see SOLO_W in dom-ladder.js. Same welded
   track as the stepper beside it, because it answers the same kind of question: one control with
   parts, not two buttons that happen to be adjacent. */
.ladder-seg {
  display: inline-flex;
  align-items: center;
  height: 22px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--panel-2);
  overflow: hidden;
}
.ladder-seg[hidden], .ladder-step[hidden] { display: none; }
.ladder-segbtn {
  height: 20px;
  padding: 0 10px;
  border-radius: 0;
  color: var(--ink-dim);
}
.ladder-segbtn + .ladder-segbtn { border-left: 1px solid var(--line); }
.ladder-seg .ctl.on { background: var(--sel); color: var(--ink); }
.ladder-grp { display: inline-flex; align-items: center; gap: 2px; }
.ladder-group {
  /* Tabular figures and a fixed box: the value changes as the stepper is held, and a proportional
     face makes the arrows either side of it jump on every press. */
  font-family: var(--ui);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-sm);
  color: var(--ink);
  /* A price, not a sentence: the value used to read "Fit +/-3.9%" and needed the room. Wide
     enough for a five-figure step and no wider, so the arrows sit where the thumb expects. */
  min-width: 52px;
  text-align: center;
  padding: 0 2px;
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  align-self: stretch;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ladder-cv {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: block;
  cursor: crosshair;
}
/* THE LADDER OWNS THE GESTURES ON A TOUCH SCREEN. A drag through the column is a walk through
   price and a pinch stretches the rows; without this the browser reads both as page gestures -
   scroll and zoom - and the panel cannot see either. The view has nothing to scroll behind it,
   so nothing is taken away by claiming them. */
@media (pointer: coarse) {
  .ladder-cv { touch-action: none; }
}
.domwin-body { display: flex; flex-direction: column; height: 100%; min-height: 0; }

/* On a phone the book REPLACES the chart rather than sharing the row with it. A 200px column
   beside a chart on a 390px screen is two unusable panels where there could be one usable one,
   and giving the column 100% width as a flex sibling just pushes the chart off the stage. So it
   lifts out of the flow entirely and covers the stage, which is what a phone terminal does with
   its book too - one surface at a time, and the indicator toggle is how you swap. */
@media (max-width: 640px) {
  .dompanel {
    position: absolute;
    inset: 0;
    width: auto !important;
    border-left: 0;
    z-index: 30;
  }
  .dompanel-grip { display: none; }
  /* A 340px column on a 390pt screen leaves fifty points of chart, so the liquidation summary
     is a sheet over the stage here rather than a sliver beside it. */
  .liqpanel {
    position: absolute;
    inset: 0;
    width: auto !important;
    border-left: 0;
    z-index: 30;
  }
  .liqpanel-grip { display: none; }
}

/* ------------------------------------------------------------------- tape */

/* A class rule beats the user agent's [hidden], so `display: flex` here meant the hidden
   attribute never actually hid this panel. On the desktop it collapsed to nothing because it is a
   flex child with no content, which is why nobody noticed; given a width it is a blank panel that
   cannot be dismissed. Every panel that hides itself needs this line. */
.tape[hidden] { display: none; }

.tape {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border-left: 1px solid var(--line);
  font-size: var(--t-sm);
  overflow: hidden;
}
.tape-head {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 8px;
  padding: 4px 8px;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-xs);
}
.tape-rows { overflow: hidden; }
.tape-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 8px;
  padding: 1px 8px;
  font-size: var(--t-sm);
}
.tape-row.buy { color: var(--buy-hi); }
.tape-row.sell { color: var(--sell-hi); }
.tape-row.big { background: var(--panel-3); font-weight: var(--w-medium); }
.tape-row .t { color: var(--ink-faint); }
canvas { display: block; height: 100%; touch-action: none; cursor: crosshair; }

.axis {
  padding: 4px 9px;
  background: var(--panel);
  border-top: 1px solid var(--line);
  color: var(--ink-faint);
  font-size: var(--t-sm);
  min-height: 22px;
  overflow: hidden;
  white-space: nowrap;
}

/* ------------------------------------------------------------ context menu */

/* Motion, one rule: floating surfaces ARRIVE - a 100ms fade with a 4px rise - and data never
   animates. That is the whole of the reference's flow; anything longer reads as decoration. */
@keyframes menu-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.ctxmenu {
  position: fixed;
  z-index: var(--z-menu);
  min-width: 224px;
  max-height: 84vh;
  overflow-y: auto;
  padding: 5px;
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-surface);
  /* one tight shadow plus a hairline: large soft shadows read as a web page */
  box-shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 6px 20px rgba(0, 0, 0, .45);
  animation: menu-in var(--t-pop) var(--ease);
}
.ctxmenu.compact { min-width: 120px; }

/* A LIST ROW, not a toolbar verb - and the two take opposite hovers on purpose.
 *
 * The hover law in controls.css says a control with no face at rest never gains one: a grey plate
 * materialising behind a small glyph, twenty times across a toolbar, is the loudest default-UI
 * tell there is. That law is about a row of ICONS. A menu is a LIST, and in a list the pointer
 * has to say which of eleven rows a click will commit - the whole row, edge to edge, not a shade
 * of grey text. This sheet already did it in two places: the parent of an open submenu, and the
 * settings search results. The rows in between were the ones left out.
 *
 * And they sat DIM at rest. Eleven grey labels with one white one under the pointer does not read
 * as "this is the hovered row" - it reads as ten disabled rows and one live one. A menu offers
 * things; every one of them is available, so every one of them is set in full ink. */
.ctx-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 9px;
  border-radius: var(--r);
  cursor: pointer;
  color: var(--ink);
  font-size: var(--t-md);
}
.ctx-item:hover { color: var(--ink); background: var(--panel-3); }
.ctx-item:hover .ctx-accel,
.ctx-item:hover .ctx-arrow { color: var(--ink-dim); }
.ctx-item.danger { color: var(--sell-hi); }
.ctx-item .tick { width: 11px; color: var(--ink); flex: 0 0 auto; font-size: var(--t-sm); }
.ctx-label { flex: 1 1 auto; }
.ctx-arrow { color: var(--ink-faint); margin-left: 14px; font-size: var(--t-md); }
/* A keyboard accelerator is right-aligned and quiet: it is a reminder, not a second label. */
.ctx-accel { color: var(--ink-faint); margin-left: 20px; font-family: var(--mono); font-size: var(--t-xs); }
.ctx-item.has-sub.open { background: var(--panel-3); }
.ctx-item.has-sub.open .ctx-arrow { color: var(--ink); }

/* Amber marks an entry that DOES something rather than sets something: nothing checks back to
   confirm it happened, so it is worth distinguishing from the toggles above it. */
.ctx-item.act { color: #d0a05a; }
.ctx-item.act:hover { color: #e8bd76; }

/* A submenu is a sibling of the root on the body, so it needs its own stacking context. */
.ctxsub { z-index: var(--z-submenu); }

.ctx-sep {
  margin: 5px 8px 3px;
  padding-top: 5px;
  border-top: 1px solid var(--line);
  color: var(--ink-faint);
  font-size: var(--t-xs);
}
.ctx-sep:empty { padding-top: 0; margin-bottom: 3px; }

/* ---------------------------------------------------------------- fields */

/* Dialog chrome lives in windows.css: settings and the instrument picker are real in-app windows,
   not modals. The field primitives below are shared by both. */

.fld {
  display: grid;
  /* minmax(0, ...) on BOTH columns. `auto` sizes to content and refuses to shrink below it, so a
     control made of nine chips laid its whole width out and ran off the edge of the dialog
     instead of wrapping - the chips already wrap, they were simply never asked to. */
  grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  align-items: center;
  gap: 6px 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}
.fld:last-child { border-bottom: 0; }
.fld-disabled .fld-label { color: var(--ink-faint); }
.fld-disabled .fld-input { opacity: .35; cursor: not-allowed; }
/* ...and the same look when the CONTROL carries the disabled attribute rather than its row.
   The settings window disables by wrapping a row; a dialog that builds one field at a time
   disables the field, and a field that is dead but looks live is the one state worse than
   either. One rule, so both routes read the same. */
.fld-input:disabled { opacity: .35; cursor: not-allowed; }
.fld-label { color: var(--ink); font-size: var(--t-lg); }
.fld-help { grid-column: 1 / -1; color: var(--ink-faint); font-size: var(--t-md); line-height: var(--lh-text); }

/* A field's VALUE, so it takes the value weight. Label and value both ran at regular and were
   separated by size and colour alone; in a tree of eight hundred rows the one thing you are
   actually scanning for is what each row is SET TO, and it was the quietest thing in the row. */
.fld-input {
  font: inherit;
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 3px 6px;
  height: var(--c-sm);
}
/* The checkbox has to answer one question from across the room: is it on?
 *
 * A grey accent-color left checked and unchecked nearly the same weight - a slightly darker small
 * square either way - which on a panel of thirty of them is unreadable. Checked is now a FILLED
 * accent box with a white tick; unchecked is an empty inset well. Two different shapes and two
 * different weights, not two shades of the same grey. */
input[type="checkbox"].fld-input {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  padding: 0;
  margin: 0;
  cursor: pointer;
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm, 2px);
  display: inline-grid;
  place-content: center;
  flex: none;
}
input[type="checkbox"].fld-input::after {
  content: "";
  width: 9px;
  height: 5px;
  /* Themed, not #fff. On a light ground --sel is a pale tint, so a white tick on it is invisible
     and every checked box read as unchecked - across the whole settings tree. */
  border-left: 2px solid var(--ink);
  border-bottom: 2px solid var(--ink);
  transform: rotate(-45deg) translate(1px, -1px);
  opacity: 0;
}
input[type="checkbox"].fld-input:checked {
  background: var(--sel);
  border-color: var(--sel);
}
input[type="checkbox"].fld-input:checked::after { opacity: 1; }
input[type="checkbox"].fld-input:hover { border-color: var(--sel); }
input[type="checkbox"].fld-input:focus-visible { /* --sel is a near-black on the dark ground - a 2px ring at about 1.4:1, effectively
     invisible. Every other control focuses in --focus. */
  outline: 1px solid var(--focus); outline-offset: 1px; }
input[type="number"].fld-input { width: 92px; text-align: right; }
select.fld-input { min-width: 154px; cursor: pointer; }
/* The pick trigger: the field grammar with the value on the left and the caret at the edge.
   It is an input by role, so its face is its border, and hover brightens the border - the
   caret and value light with it. */
.fld-pick {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 154px;
  cursor: pointer;
}
.fld-pick svg { color: var(--ink-faint); flex: 0 0 auto; }
.fld-pick:hover svg { color: var(--ink); }
/* The same, for a chosen option: it is a value, not the name of one. */
.fld-pick-val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--w-medium); }
/* The keyboard's place in a menu: the same ink language as the pointer's. */
.ctx-item.kb { color: var(--ink); }

/* short free-text fields: marker label prefixes and the like */
input.txt {
  font: var(--t-sm) var(--mono);
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 3px 6px;
  height: var(--c-sm);
  width: 92px;
}

.rowpick { display: flex; flex-wrap: wrap; gap: 3px; justify-content: flex-end; min-width: 0; }
.chip {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-dim);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 2px 8px;
  cursor: pointer;
}
.chip:hover { color: var(--ink); border-color: var(--line-2); }
.chip.on { color: var(--ink); background: var(--sel); border-color: var(--line-2); }

.btn {
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-dim);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 0 12px;
  height: 24px;
  cursor: pointer;
}
.btn:hover { color: var(--ink); border-color: var(--line-2); }
.btn.ghost { border-color: transparent; }
.btn.ghost:hover { border-color: transparent; }
.btn.primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-ink);
  font-weight: var(--w-semi);
}
.btn.primary:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
  color: var(--primary-ink);
}

/* ----------------------------------------------------------- symbol picker */

.search {
  font: inherit;
  font-size: var(--t-md);
  padding: 6px 9px;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.symlist { overflow-y: auto; padding: 3px 7px 12px; }
.symrow {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "sym venue" "days range";
  gap: 2px 10px;
  width: 100%;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-left: 2px solid transparent;
  border-radius: var(--r);
  padding: 6px 9px;
  cursor: pointer;
}
.symrow:hover .s-days, .symrow:hover .s-range { color: var(--ink-dim); }
.symrow:hover .s-venue { color: var(--ink); }
.symrow.on { background: var(--sel); border-left-color: #6d6d75; }
/* THE TICKER, wherever it is said.
 *
 * The asset carries whatever ink its surround gives it; the settlement half sits one step back on
 * the ramp. Two rules, one home - see web/lib/ticker.js for why the split exists at all and for
 * the function every surface fills these with. */
/* A ticker is ONE word in two elements. Its host is usually a flex row with a gap between
   children - a toolbar control, a list row - and that gap would land inside the name. */
.tk { gap: 0; }
.tk-base { color: inherit; }
.tk-quote { color: var(--ink-faint); }

.s-sym { grid-area: sym; font-weight: var(--w-semi); font-size: var(--t-md); }
.s-venue { grid-area: venue; color: var(--ink-dim); font-size: var(--t-sm); }
.s-days { grid-area: days; color: var(--ink-faint); font-size: var(--t-xs); }
.s-range { grid-area: range; color: var(--ink-faint); font-size: var(--t-xs); text-align: right; }
.s-quote { color: var(--ink-faint); font-weight: var(--w-regular); }
.s-range em { font-style: normal; }
.s-range .ok { color: var(--buy-hi); }
.s-range .fetch { color: var(--ink-dim); }
.s-range .none { color: var(--ink-faint); }
.pad { padding: 14px; }

/* --------------------------------------------------------------- phone */

@media (max-width: 640px) {
  /* Chrome that costs a row of pixels has to earn it on a phone, and most of it does not. */
  .cap, .meta, .stat { display: none; }
  .fld { grid-template-columns: 1fr; }
  .fld-input, .rowpick { justify-self: start; }
  .ctl { height: 26px; }
}

/* ------------------------------------------------------------- tab strip */

/* The tab strip sits at the FOOT of the panel.
 *
 * At the top it was the second of four bands stacked above the plot, and it is the one with the
 * ...at the TOP now, at the owner's word, matching the reference he sits in front of all day:
 * "put our tabs at the top so we can do the same as Exo." The strip leads, the toolbar follows,
 * the chart gets everything below.
 *
 * `order` rather than moving the markup: the strip is already first in the document, so tab
 * order and screen readers meet the panel's identity first either way. */
.tabstrip { display: flex; align-items: stretch; min-width: 0; order: 0; }
.tabstrip .tabs { flex: 1 1 auto; min-width: 0; }
.tabstrip[hidden] { display: none; }
body.panel > .bar { order: 1; }
body.panel > .stage { order: 2; }
body.panel > #replayBar { order: 3; }
body.panel > .mobilebar { order: 5; }
/* A strip at the top is bounded by the thing BELOW it. */
body.panel .tabs { border-top: 0; border-bottom: 1px solid var(--line); }

/* The hide-tabs chevron and its reveal handle are gone: they sat beside the viewport controls,
   and one mis-click vanished the whole set. The strip is permanent chrome now. */

body.panel { position: relative; }

/* ------------------------------------------------- TV-style quick timeframes */

.tf-quick { display: inline-flex; gap: 1px; flex: 0 0 auto; }
.tfq { padding: 0 7px; min-width: 30px; justify-content: center; font-variant-numeric: tabular-nums; }
/* the active period is a quiet pill, not a coloured underline - the TV grammar, our palette */
.tfq.on { background: var(--sel); color: var(--ink); }
.tf-caret { padding: 0 5px; color: var(--ink-faint); }
.tf-caret:hover { color: var(--ink); }

@media (max-width: 900px) {
  /* the quick row is a luxury; on a narrow panel the caret carries the whole job */
  .tf-quick { display: none; }
}

/* ------------------------------------------------------- native chrome (Win32)
   Colour and radius come from the theme as custom properties; these are the SHAPE rules a Win32
   control has that a token cannot express. Scoped to the flag so no other theme is touched. */
[data-chrome-native] input,
[data-chrome-native] select,
/* Win32 fields are inset: a dark line on the top and left, light on the bottom and right. */
[data-chrome-native] textarea,
/* and buttons are the inverse - raised, not inset */
[data-chrome-native] button.ctl:active,
/* Selection is the system accent, filled, with white text - never a tint or an underline. */
/* Focus is a 1px dotted rectangle inside the control, which is the Win32 focus rectangle. */

/* A Win32 toolbar toggle is a TINT with a 1px border, not a solid accent fill: the accent is
   reserved for the one selected item in a list. Solid-filling every active plugin turned the rail
   into a column of blue chips, which is the opposite of what a checked toolbar button looks like. */

/* ------------------------------------------------------ drawing properties

   The colour GRID: a neutral ramp across the top, hue columns under it. Scannable in two
   directions - hue by column, strength by row. */
/* A narrow, fixed label column for the wide controls.
 *
 * The shared field grid is `minmax(0,1fr) minmax(0,auto)` - the label takes the spare room, which
 * is right for a checkbox on the far right and wrong for a control that is itself wide: it pushed
 * the colour grid against the dialog edge and clipped it. These rows give the label what it needs
 * and the control everything else. */
.dw-block {
  align-items: start;
  grid-template-columns: 92px minmax(0, 1fr);
}
.dw-block .fld-label { padding-top: 3px; }
.dw-swatch {
  /* flex-shrink OFF. These sit inside the settings field grid, whose control column is
     minmax(0, auto) so that wide controls wrap instead of overflowing - and a flex child with the
     default `shrink: 1` answers that by squeezing forty swatches into a column of stripes. A
     swatch has one job and it is to be a legible area of colour. */
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: 0;
  cursor: pointer;
  transition: transform var(--t-tap) var(--ease), box-shadow var(--t-tap) var(--ease);
}
.dw-swatch:hover { transform: scale(1.12); }
/* The chosen one is marked by a RING outside its own edge, not by a border on it: a border eats
   into the colour and changes what you are judging. */
.dw-swatch.on {
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--ink);
  border-color: transparent;
}
.dw-custom { align-items: center; gap: 7px; margin-top: 5px; }
.dw-customlabel { color: var(--ink-faint); font-size: var(--t-xs); }

.dw-opacity { display: flex; align-items: center; gap: 9px; min-width: 190px; }
.dw-opacity input[type="range"] { flex: 1 1 auto; accent-color: var(--ink-dim); }
.dw-opval { color: var(--ink-dim); font-size: var(--t-sm); width: 34px; text-align: right; }

/* Line style drawn as the line it produces. A dropdown reading "Dashed" makes you imagine the
   result; a preview lets you point at it. */
.dw-dash {
  width: 46px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: transparent;
  cursor: pointer;
}
.dw-dash.on { border-color: var(--focus); background: var(--panel-2); }
.dw-dash i { display: block; width: 30px; height: 0; border-top: 2px solid var(--ink-dim); }
.dw-dash.on i { border-top-color: var(--ink); }
.dw-dash-dashed { border-top-style: dashed !important; }
.dw-dash-dotted { border-top-style: dotted !important; }

/* ============================================================ elevation and motion

   One recipe for every surface that floats, and one for every state that changes.

   Before this, each popover had invented its own shadow and none of them moved: menus, the colour
   picker, tooltips and the drawing bar all appeared fully formed at full opacity, which is the
   single most reliable way to make an interface feel snapped-together rather than made. Motion is
   not decoration here - it is what tells the eye that a thing came FROM somewhere, and an
   interface where nothing arrives is one where everything is a jump cut.

   Two shadows on every floating surface, always: a tight one that reads as the edge lifting off
   the page, and a wide soft one that puts real distance behind it. One alone reads as a border.

   The durations are deliberately short. 90-140ms is under the threshold where a transition starts
   feeling like a wait; anything slower on a trading tool is an interface arguing with the hand. */
/* TWO DURATIONS AND ONE CURVE, and the reason is arithmetic rather than taste.
 *
 * A render-time audit (tools/verify-polish.mjs) counted NINE distinct transition durations in
 * this chrome - 60, 90, 100, 110, 120, 130, 150, 180 and 220ms - and every one of them was
 * `linear`. Each was defensible where it was written: the rail argued for 60 because a column
 * that thinks feels slow, the controls for 90, the list rows for 110. Nobody can name the
 * difference between 90 and 110ms, and everybody can feel a window where the button under the
 * pointer answers at one speed and the row beneath it at another. That is most of what "it feels
 * cheap" is: not one wrong value, but no two agreeing.
 *
 * And `linear` is the tell underneath the tell. A linear fade moves at a constant rate and stops
 * dead; nothing physical does that, so it reads as a value being assigned rather than a thing
 * moving. One decelerating curve on everything is the difference between a chrome that responds
 * and a chrome that just changes.
 *
 * So: --t-tap for anything answering a pointer, --t-pop for a surface arriving or lifting. Both
 * stay under the threshold where a transition starts feeling like a wait - a trading tool must
 * never argue with the hand - and both take the same curve. Data motion is NOT in here: a bar
 * growing to a value is a measurement, keeps its own figure, and is nobody's hover state. */
:root {
  --elev-pop: 0 2px 6px rgba(0, 0, 0, .34), 0 12px 32px rgba(0, 0, 0, .44);
  --elev-win: 0 4px 12px rgba(0, 0, 0, .38), 0 24px 60px rgba(0, 0, 0, .5);
  --ease: cubic-bezier(.2, .7, .3, 1);
  --t-tap: 110ms;
  --t-pop: 160ms;
}
:root[data-chrome-light] {
  --elev-pop: 0 2px 6px rgba(16, 20, 30, .1), 0 12px 32px rgba(16, 20, 30, .14);
  --elev-win: 0 4px 12px rgba(16, 20, 30, .12), 0 24px 60px rgba(16, 20, 30, .18);
}

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

/* Everything that floats over the chart arrives the same way. */
.ctxmenu,
.cp,
.set-ac,
.railtip,
.dw-pop {
  box-shadow: var(--elev-pop);
  animation: pop-in var(--t-pop) var(--ease);
}
.win { box-shadow: var(--elev-win); }

/* And everything that reacts to a pointer does it over the same interval. A control that changes
   instantly and one that fades over 200ms in the same window read as two different products. */
/* Every clickable row in the application, on one interval. Five of these snapped while their
   siblings faded, which is the kind of inconsistency that reads as two different products in one
   window even when nobody can name what is wrong. */
.ctx-item,
.cp-chip,
.im-row,
.wl-row,
.set-navitem,
.ip-row,
.ws-row,
.tpl-row,
.ot-row,
.sb,
.tab {
  transition: background var(--t-tap) var(--ease), color var(--t-tap) var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .ctxmenu, .cp, .set-ac, .railtip, .dw-pop { animation: none; }
  .ctx-item, .cp-chip, .im-row, .wl-row, .set-navitem, .ip-row, .ws-row, .tpl-row,
  .ot-row, .sb, .tab { transition: none; }
}

/* --------------------------------------------------- viewport controls

   Reset scale, auto-scale, lock to last, scroll mode, zoom. They were painted on the chart in the
   bottom-right corner - over the data, with their own hit-testing in canvas coordinates, and they
   landed inside whichever indicator pane was at the bottom. Ordinary buttons at the end of the tab
   strip: same row, off the plot, and they get real tooltips and focus for free. */
/* ...and when the strip moved to the TOP, the nav went with it and the owner sent it back:
   "those should be at the bottom right corner like it was before." It leaves the strip's flow
   and pins to the panel's own corner, over the axis gutter's foot where every charting tool
   keeps these. */
/* Bare glyphs in the corner - no plate under the strip and none under each icon. "The
   background, it's like a rectangle that the tool set sits on... just remove that, add the
   icons white." Tucked to the very corner and a notch smaller, so it stops standing on the
   axis numbers. */
.tabstrip-nav {
  position: absolute;
  /* CLEAR OF WHATEVER IS DOCKED ON THE RIGHT. The cluster is positioned against the panel, not
     against the chart, so with the order book or the tape docked it sat ON them - three
     viewport buttons over the bottom rows of a ladder, which is where the book prints its
     imbalance strip. Each dock reports its own width into its own variable, so the two can be
     open at once and neither has to know about the other. */
  right: calc(3px + var(--dock-book, 0px) + var(--dock-tape, 0px));
  bottom: 1px;
  z-index: 30;
  display: inline-flex;
  align-items: center;
  gap: 0;
  padding: 0;
}
/* SIZED TO THE AXIS GUTTER, which is 24px. "They're like micro" - they were: a 13px glyph in a
   21x18 box, half of it hanging off the axis. A 16px glyph in a 26x22 box sits inside the gutter
   with a pixel to spare, and 22px of height is a hit target a pointer finds on the first go. */
.navbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 22px;
  border: 0;
  background: transparent;
  color: var(--ink-dim);
  cursor: pointer;
  transition: color var(--t-tap) var(--ease);
  position: relative;
}
.navbtn:hover { color: var(--ink); }
/* A latched control - auto-scale, lock-to-last - shows as ink with a hairline underneath;
   no plate, per the owner's corner spec. */
.navbtn.on { color: var(--ink); }
.navbtn.on::after {
  content: '';
  position: absolute;
  left: 5px;
  right: 5px;
  bottom: 1px;
  height: 1.5px;
  background: currentColor;
  border-radius: 1px;
}
/* No travel here either - the corner buttons sit half inside the price axis, where a moving
   glyph reads as the axis itself jumping. The system presses them by ground. */

/* Present to the platform, absent to the eye. Used by the date input, which Go To Date drives
   through showPicker() - and showPicker() throws on a display:none element. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ---- the product's tooltip (lib/tip.js) - every `title` renders as this card, never as the
        operating system's bubble ---- */
.tipcard {
  position: fixed;
  z-index: 90;
  max-width: 300px;
  padding: 5px 9px;
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  /* The pop elevation, from the token rather than by hand: a tooltip floats the way a menu
     floats, and a private second shadow recipe here was one of the six the chrome carried. */
  box-shadow: var(--elev-pop);
  color: var(--ink);
  font: var(--t-sm) var(--ui);
  line-height: var(--lh-ui);
  white-space: pre-line;
  pointer-events: none;
}
.tipcard[hidden] { display: none; }

/* the tool-search flyout: a field on top, the catalogue under it. Box, corner and focus are the
   field role in controls.css; a literal 4px corner here could not follow an appearance. */
.toolfly-searchwrap { display: flex; flex-direction: column; max-height: min(560px, 78vh); }
.toolfly-search {
  margin: 6px; padding: 0 8px; flex: 0 0 auto;
  font: var(--t-md) var(--ui);
}
.toolfly-list { overflow-y: auto; min-height: 0; }
.toolfly-none { padding: 10px 12px; color: var(--ink-faint); font: var(--t-md) var(--ui); }

/* dash-preview rows: the sample IS the label, sized to sit like text in the row */
.ctx-dash { display: block; margin: 4px 2px; color: var(--ink); }
.fld-pick-val .ctx-dash { margin: 0; }

/* ------------------------------------------------------------- bare chart

   The chart embedded inside a view: no rail, no toolbar, no tab strip - the things that belong to
   a chart which OWNS a tab. Timeframes stay, because the one control the owner asked to keep is
   the one that changes what the chart is showing. Everything else the host view provides. */
/* The phone action bar belongs to a chart that owns its tab, exactly like the rail and the tab
   strip do - it carries the symbol, the timeframe, the drawing tools and undo. A view cell in a
   narrow column was showing it under a watchlist, offering to draw on a table. */
body.bare-chart .rail,
body.bare-chart .tabstrip,
body.bare-chart .mobilebar,
body.bare-chart .statusbar { display: none !important; }
body.bare-chart .bar > *:not(.tfq):not(.tfgroup):not(#tfBtn) { display: none; }
body.bare-chart .bar {
  padding: 4px 8px;
  min-height: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
}
body.bare-chart .stage { inset: 0; }

/* EXCEPT WHEN IT IS THE ONLY PANEL, where the strip is the only thing there is to take hold of.
 *
 * The rule above hides it because a view brings its own header and two title bars is one too many.
 * That holds while something else is on screen. Alone, it left the feed with a header that looks
 * like a tab, is not one, and cannot be dragged - so there was no gesture at all for "put this on
 * the left and leave the right dark", even though the grid has known how to do that since
 * splitLonePane landed. See the note beside `lone-panel` in app.js. */
body.bare-chart.lone-panel .tabstrip { display: flex !important; }

/* AND THE VIEW STARTS BELOW IT. `.newtab` is fixed and covers the panel, which is right when the
   strip is hidden and wrong the moment it is not: the view painted straight over the tab, so the
   one handle for dragging this panel anywhere was present and invisible.
 *
 * The height is DERIVED, not guessed, and not measured in script - a bare view mounts at boot,
 * before the layout message that adds `lone-panel` has arrived, so anything that measured the
 * strip on the way past read zero and set nothing. The strip is its own construction: 3px of
 * padding top and bottom, a 1px bottom border, and a tab of --h-tabs minus 4. That is --h-tabs
 * plus 3, exactly, and it cannot fall out of step because the same token drives both. */
body.bare-chart.lone-panel .newtab { top: calc(var(--h-tabs) + 3px); }

/* ONE CLOSE, AND ROOM FOR IT.
 *
 * A view placed in the grid drew three controls into the same corner: the shell's own x for the
 * cell, this page's close - which does the same thing, four pixels away - and, underneath both,
 * whatever the view keeps at the right of its header, which for the liquidation feed is the size
 * filter. The shell owns the cell, so its x is the one that stays; the page's stands down, and the
 * header reserves the width so nothing of the view's own is covered. */
body.bare-chart .newtab-close { display: none; }
body.bare-chart .panel-head { padding-right: 34px; }

/* The corner cluster stands on a ground the same grey as the strip it sits in, so the last
   time label never prints through its glyphs. Invisible by design - it is not a plate. */
.tabstrip-nav { background: var(--panel); padding-left: 6px; }
/* The strip is emptied outright when the tools toolbar is off - see renderNavStrip - and an empty
   inline-flex with a ground and a padding is a box that paints a colour and no pixels. Nothing
   shows it, which is the whole reason the layout audit looks for it. */
.tabstrip-nav:empty { display: none; }

/* THE IN-APP ALERT BANNER - iOS style, top centre under the safe area. An alert firing while the
   app is open lands here rather than only in the OS notification shelf: one dark glass pill, a
   yellow dot for the crossing, gone on its own, tap to dismiss. Toasts are app-wide - a desktop
   chart is exactly the surface an alert is for. */
.alert-toast {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 12px);
  left: 50%;
  transform: translate(-50%, -18px);
  z-index: 260;
  display: flex;
  align-items: center;
  gap: 9px;
  max-width: min(92vw, 420px);
  padding: 11px 18px;
  border-radius: 999px;
  background: #1c1c1e;
  color: #fff;
  font: 500 13px/1.3 var(--ui);
  box-shadow: 0 10px 34px rgb(0 0 0 / .38);
  opacity: 0;
  transition: opacity .26s ease, transform .26s ease;
  cursor: pointer;
}
.alert-toast.in { opacity: 1; transform: translate(-50%, 0); }
.alert-toast.out { opacity: 0; transform: translate(-50%, -14px); }
.alert-toast-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #ffd60a;
  flex: 0 0 auto;
  box-shadow: 0 0 10px rgb(255 214 10 / .8);
}
.alert-toast-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* THE DESIGN-SYSTEM TOKENS. The spec's names, aliased onto the house tokens - one source of
   truth, so a theme swap re-skins the contract instead of fighting it. Radius: two steps and
   the avatar's 50%, nothing else. Depth is a background step (bg-1 -> bg-2 -> bg-3), never a
   shadow on a flat surface. */
:root {
  --ds-bg-1: var(--bg);
  --ds-bg-2: var(--panel);
  --ds-bg-3: var(--sel);
  --ds-text-1: var(--ink);
  --ds-text-2: var(--ink-dim);
  --ds-text-3: var(--ink-faint);
  --ds-up: var(--buy-hi);
  --ds-dn: var(--sell-hi);
  --ds-divider: var(--line);
  --ds-accent: #d0a05a;
  --ds-radius-sm: var(--r-sm, 6px);
  --ds-radius-md: var(--r-surface, 10px);
}
