/* LawrenceWave UI
   Built on the design tokens in css/tokens.css. No CSS framework: elements are
   styled semantically, so pages carry markup rather than utility-class soup.

   Note the tokens define colours only under :root[data-theme="dark"] and
   :root[data-theme="light"]. With no attribute set, NOTHING resolves — so the
   document must always carry one. theme.js sets it before first paint. */

/* ---------- app-level tokens ---------- */
/* Derived from tokens.css rather than added to it — these are decisions about how
   this app uses the design system, not part of the system itself. */
:root {
  /* Small PROSE text. --lw-micro is monospace, which is right for data and wrong
     for sentences: it made hints, disclaimers and validation messages render in
     IBM Plex Mono. */
  --lw-ui-small: 400 12px/17px var(--lw-font-ui);

  /* Fallback; the theme rules below override it. Declared first on purpose — a
     later :root of equal specificity would win. */
  --lw-input-bg: var(--lw-surface-2);

  /* The top bar's height is load-bearing: the drawer and scrim are fixed and start
     below it, and the workspace sizes itself as 100dvh minus it. Those were four
     separate hard-coded 56px values, so growing the bar silently misaligned all
     three. One token now, sized to clear the brand lockup with room to breathe. */
  --lw-brand-h: 57px;
  --lw-topbar-h: 76px;

  /* Metallic gold. A flat fill reads as plastic; what makes gold look like metal is a
     bright specular band across the middle with a darker edge below it, so the surface
     appears to catch light rather than just be yellow. Every stop is --lw-gold shifted
     in lightness only, so the hue stays the brand's.

     Deliberately a SEPARATE token from --lw-gold rather than a replacement: a gradient
     cannot be used where a single colour is required, and three places still need the
     flat one — border-color, SVG fills, and the chart's price series.

     The darkest stop was checked against --lw-on-gold rather than assumed: worst case
     is 6.73:1 in the light theme, comfortably past the 4.5:1 needed for body text, so
     the shine cannot make a label harder to read than the flat fill did. */
  --lw-gold-shine: linear-gradient(160deg,
      #FFCD38 0%, #FFBF00 34%, #FBE298 50%, #FFBF00 62%, #C79500 100%);
  --lw-gold-shine-hover: linear-gradient(160deg,
      #FFD65C 0%, #FFC824 34%, #FCECBA 50%, #FFC824 62%, #EBB000 100%);

  /* A lit top edge and a shadowed bottom one — the bevel that sells the gradient as a
     physical surface rather than a printed one. */
  --lw-gold-bevel: inset 0 1px 0 rgb(255 255 255 / .50), inset 0 -1px 0 rgb(0 0 0 / .16);
}

/* Input surfaces have to be chosen per theme. On dark, surface-2 sits clearly above
   the background. On light it does NOT — surface-2 (#FBFAF5) against bg (#F7F5EF)
   is nearly the same colour, which made every text box invisible. White reads
   properly there. */
:root[data-theme="dark"]  { --lw-input-bg: var(--lw-surface-2); }
:root[data-theme="light"] { --lw-input-bg: var(--lw-surface-1); }

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--lw-bg);
  color: var(--lw-text-1);
  font: var(--lw-body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0 0 .5rem; color: var(--lw-text-1); }
h1 { font: var(--lw-display-1); }
h2 { font: var(--lw-display-2); }
h3 { font: var(--lw-title); }
p  { margin: 0 0 1rem; }

a { color: var(--lw-gold-text); text-decoration: none; }
a:hover { text-decoration: underline; }

code, .data { font: var(--lw-data); color: var(--lw-text-2); }

/* Visible focus everywhere — the tokens define it, so honour it. */
:focus-visible {
  outline: var(--lw-focus-width) solid var(--lw-focus);
  outline-offset: var(--lw-focus-offset);
}

/* ...but not around the page heading. FocusOnNavigate (Routes.razor, all three sites) moves
   focus to the h1 after every navigation so a screen reader announces the new page, and that
   is worth keeping. The RING it produces is not: it outlines something nobody can interact
   with, and because a page reached from the address bar counts as a keyboard interaction it
   appears on first load, where it reads as a rendering fault rather than as focus.

   Removing the outline does not remove the focus, so the announcement still happens. This is
   the fix the Blazor template ships in each site's own wwwroot/app.css — a file that is not
   linked in any of the three, so it never applied. Here it does, for all of them. */
h1:focus, h1:focus-visible { outline: none; }

/* ---------- shell ---------- */
.shell { display: flex; flex-direction: column; min-height: 100%; }

.topbar {
  display: flex;
  align-items: center;
  gap: .75rem;
  height: var(--lw-topbar-h);
  padding: 0 .75rem;
  background: var(--lw-surface-1);
  border-bottom: 1px solid var(--lw-border-subtle);
  position: sticky;
  top: 0;
  z-index: 30;
}

.topbar .spacer { flex: 1; }

/* The centred call to action, between the brand and the account controls. */
.topbar .header-action { flex: 0 1 auto; min-width: 0; }
.topbar .header-action .btn { white-space: nowrap; }

/* On a phone the bar cannot fit five things across ~414px, so the action takes a row of its
   own, full width — which makes a better call to action than the squeezed one it replaces
   (Doug, 2026-08-18: it was missing on the phone view; it had been hidden here).

   --lw-topbar-h MUST move with it. That token is load-bearing, as the comment on it says:
   the drawer and scrim are fixed and start below the bar, and a taller bar with an unchanged
   token would have the drawer slide out from underneath its second row. 128px is the two
   rows plus the padding the single row already carries.

   Scoped with :has to bars that actually HAVE an action, so the app and the console — which
   pass none — keep their one-row 76px bar and their drawer alignment untouched. */
@media (max-width: 40rem) {
  :root:has(.topbar .header-action) { --lw-topbar-h: 128px; }

  .topbar:has(.header-action) { flex-wrap: wrap; align-content: center; row-gap: .5rem; }
  .topbar .header-action { order: 10; flex: 1 0 100%; }
  .topbar .header-action .btn { display: block; text-align: center; }
}

/* The lockup is the only brand element in the bar. Its viewBox carries about 20%
   empty margin, so the rendered ink is ~0.78 of this height — 57px reads as ~44px,
   which is why the number looks larger than the result. */
.brand { display: flex; align-items: center; }
.brand img.wordmark { height: var(--lw-brand-h); display: block; }

/* Theme-dependent pairs: both are rendered and CSS picks one, so the markup does
   not need to know the active theme (which is only decided in the browser). */
:root[data-theme="dark"]  .wordmark-light,
:root[data-theme="dark"]  .when-light { display: none; }
:root[data-theme="light"] .wordmark-dark,
:root[data-theme="light"] .when-dark  { display: none; }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--lw-text-2);
  cursor: pointer;
}
.icon-btn:hover { background: var(--lw-surface-2); color: var(--lw-text-1); }
.icon-btn svg { width: 20px; height: 20px; }

main { flex: 1; }
.page { max-width: 1100px; margin: 0 auto; padding: 1.5rem 1rem 4rem; }

/* A reading measure, for the sites that are prose. 70ch rather than a pixel width so it
   tracks the font: the limit is a number of CHARACTERS per line, and past roughly 70 the eye
   struggles to find the start of the next one. At 1100px this page ran to about 140. */
.page-narrow { max-width: 70ch; }

/* Site footer — the information site only. Quiet by design: these are links people go
   looking for, not links that should compete with the page. */
.sitefoot {
  border-top: 1px solid var(--lw-border-subtle);
  background: var(--lw-surface-1);
  color: var(--lw-text-2);
  font-size: .875rem;
}
.sitefoot .page { padding-top: 1.25rem; padding-bottom: 1.25rem; }
.sitefoot nav { display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: .5rem; }
.sitefoot a { color: var(--lw-text-2); }
.sitefoot a:hover { color: var(--lw-text-1); }

/* ---------- drawer (CSS only, so it works under static rendering) ---------- */
#nav-toggle { display: none; }

.drawer {
  position: fixed;
  inset: var(--lw-topbar-h) auto 0 0;
  width: 260px;
  transform: translateX(-100%);
  transition: transform .18s ease;
  background: var(--lw-surface-1);
  border-right: 1px solid var(--lw-border-subtle);
  padding: 1rem .75rem;
  z-index: 25;
  overflow-y: auto;
}
#nav-toggle:checked ~ .drawer { transform: translateX(0); }

.scrim {
  position: fixed;
  inset: var(--lw-topbar-h) 0 0 0;
  background: #0006;
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease;
  z-index: 20;
}
#nav-toggle:checked ~ .scrim { opacity: 1; pointer-events: auto; }

.drawer a {
  display: block;
  padding: .5rem .625rem;
  margin-bottom: .125rem;
  border-radius: 8px;
  color: var(--lw-text-2);
  font: var(--lw-ui);
}
.drawer a:hover { background: var(--lw-surface-2); color: var(--lw-text-1); text-decoration: none; }
.drawer a.active { background: var(--lw-surface-3); color: var(--lw-text-1); }
.drawer .group {
  font: var(--lw-micro);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--lw-text-3);
  padding: .75rem .625rem .25rem;
}

/* ---------- account menu ---------- */
.account { position: relative; }
.account > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: .5rem;
}
.account > summary::-webkit-details-marker { display: none; }

.avatar {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--lw-surface-3);
  border: 1px solid var(--lw-border);
  color: var(--lw-text-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font: var(--lw-ui);
}
.avatar svg { width: 22px; height: 22px; }

.account-menu {
  position: absolute;
  right: 0;
  top: calc(100% + .5rem);
  min-width: 220px;
  background: var(--lw-surface-2);
  border: 1px solid var(--lw-border);
  border-radius: 10px;
  padding: .375rem;
  z-index: 40;
}
.account-menu .who {
  padding: .5rem .625rem;
  border-bottom: 1px solid var(--lw-divider);
  margin-bottom: .25rem;
}
.account-menu .who .name { font: var(--lw-ui); color: var(--lw-text-1); }
.account-menu .who .email { font: var(--lw-ui-small); color: var(--lw-text-3); }
.account-menu a, .account-menu button {
  display: block;
  width: 100%;
  text-align: left;
  padding: .5rem .625rem;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--lw-text-2);
  font: var(--lw-ui);
  cursor: pointer;
}
.account-menu a:hover, .account-menu button:hover {
  background: var(--lw-surface-3);
  color: var(--lw-text-1);
  text-decoration: none;
}

/* ---------- analysis workspace ---------- */
/* The workspace fills the viewport rather than sitting in the centred text column.
   Detected with :has() so it needs no second layout — the page keeps the same top
   bar, hamburger and account menu as the rest of the site. */
.page:has(.workspace) {
  max-width: none;
  padding: 0;
  height: calc(100dvh - var(--lw-topbar-h));
}

.workspace { display: flex; flex-direction: column; height: 100%; }

/* The category row heads the left panel; the chart's title row (its open tabs and pills)
   starts level with it, so no band above the chart is spent on two buttons and a dropdown.
   Both rows share the .5rem top padding of their columns. */
.category-tabs { display: flex; align-items: center; gap: .25rem; margin-bottom: .5rem; }
.category-tab {
  padding: .35rem .8rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--lw-text-2);
  font: var(--lw-ui);
}
.category-tab:hover { background: var(--lw-surface-2); color: var(--lw-text-1); }
.category-tab.active {
  background: var(--lw-gold);
  background-image: var(--lw-gold-shine);
  border-color: var(--lw-gold);
  color: var(--lw-on-gold);
  box-shadow: var(--lw-gold-bevel);
}

/* The picker fills the panel, under the category row and above the degree grid; the
   rule under its block is the seam between "open a chart" and "this chart's overlays". */
.open-chart { margin-bottom: .75rem; padding-bottom: .75rem; border-bottom: 1px solid var(--lw-border-subtle); }
.instrument-picker { width: 100%; padding: .3rem .5rem; font: var(--lw-ui); }
.open-chart > p { margin: 0; }

.workspace-body { display: flex; flex: 1; min-height: 0; position: relative; overflow: hidden; }

.workspace-panel {
  width: 232px;
  flex: 0 0 232px;
  padding: .5rem .75rem .75rem;
  overflow-y: auto;
  background: var(--lw-surface-1);
  border-right: 1px solid var(--lw-border-subtle);
}

.workspace-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }

/* The panel slides out to the LEFT — a negative margin the width of the panel, animated — so the
   chart takes the whole width; the handle on the seam goes with it and sits at the edge. Desktop
   only: under 860px the handle is hidden and the collapse is ignored (the panel is stacked around
   the chart there, nothing to slide). */
.workspace-panel { transition: margin-left .2s ease; }
.workspace.panel-collapsed .workspace-panel { margin-left: -232px; }
.panel-handle {
  position: absolute; top: 50%; left: 232px; z-index: 2;
  transform: translate(-50%, -50%);
  width: 18px; height: 44px; padding: 0;
  border: 1px solid var(--lw-border); border-radius: 6px;
  background: var(--lw-surface-2); color: var(--lw-text-2);
  font: var(--lw-ui); line-height: 1; cursor: pointer;
  transition: left .2s ease;
}
.panel-handle:hover { background: var(--lw-surface-3); color: var(--lw-text-1); }
.workspace.panel-collapsed .panel-handle { left: 0; transform: translate(0, -50%); border-left: 0; border-radius: 0 6px 6px 0; }

/* The open tabs, at the head of the meta row. The ACTIVE tab is the chart's title — the
   title font, the title colour — and the others sit beside it in the muted UI font, each
   with its ×. No strip of its own: that row is the chart's now. */
.chart-tabs { display: flex; align-items: baseline; gap: .125rem; overflow-x: auto; }
.chart-tab { display: flex; align-items: baseline; }
.chart-tab > button {
  border: 0;
  background: transparent;
  color: var(--lw-text-2);
  font: var(--lw-ui);
  padding: .2rem .5rem;
  border-radius: 8px;
}
.chart-tab > button:hover { background: var(--lw-surface-2); color: var(--lw-text-1); }
/* :not(.close) — the × is a "> button" too, and must stay small and muted beside the title. */
.chart-tab.active > button:not(.close) { font: var(--lw-title); color: var(--lw-text-1); padding-left: 0; }
.chart-tab.active > button:not(.close):hover { background: transparent; }
.chart-tab > .close {
  border: 0; background: transparent; color: var(--lw-text-3);
  padding: .2rem .4rem; margin-left: -.4rem; border-radius: 6px; font: var(--lw-ui);
}
.chart-tab.active > .close { margin-left: -.2rem; }
.chart-tab > .close:hover { color: var(--lw-down); background: var(--lw-surface-3); }

/* A column: the meta line, the chart, the time-window control. The chart takes every
   pixel the other two leave, so no viewport height is wasted below the control; when
   the viewport is too short for its minimum the area scrolls rather than squashing it. */
.chart-area {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--lw-bg);
  overflow: auto;
  padding: .5rem;
}
.chart-area > .chart-meta,
.chart-area > .time-window { flex: 0 0 auto; }
.chart-placeholder { padding: 1.25rem; }
.chart-status { padding: 1.25rem; }

/* EVERY open tab's chart is mounted at once, stacked in one grid cell, and all but the
   active one hidden. The stack is what makes switching instant: a hidden pane keeps the
   same size as the visible one, so its SciChart surface is built at the right size, its
   last frame is already on its canvas, and showing it needs no resize and no redraw.

   visibility, never display:none — a display:none pane has NO size, so SciChart would
   build into it at its own fallback size and every first show would pay a resize and a
   redraw. Hidden panes also take no pointer events, so their chart modifiers stay inert.

   Positioning lives here on the pane and never on .chart-host: SciChart writes inline
   position/overflow onto a host element whose inline style is empty, which would silently
   undo a stylesheet's positioning of the host itself. */
.chart-panes {
  flex: 1 1 auto;
  min-height: 320px;
  min-width: 0;
  display: grid;
}
.chart-pane {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  position: relative; /* the containing block for .lw-zone-tip, which must not be inside .chart-host */
}

/* A selected wave's chip, with its × (deselect). */
.pill-sel { color: var(--lw-text-1); border-color: var(--lw-text-2); }
.pill-x { background: none; border: 0; color: inherit; cursor: pointer; padding: 0 0 0 .35rem; font: inherit; line-height: 1; }
.pill-x:hover { color: var(--lw-down); }
/* The readout's zone chip (UTC / local): a button in the pill's clothes. */
.tw-zone { background: none; cursor: pointer; margin-left: .25rem; }
.tw-zone:hover { color: var(--lw-text-1); border-color: var(--lw-text-1); }
.chart-pane-hidden { visibility: hidden; pointer-events: none; }

/* Two charts side by side (Doug, 2026-08-26): the active tab and the one next to it, so the
   same instrument can be read at two time-scales at once.

   Only the GRID changes. Every pane still sits in row 1 and every hidden pane still sits in
   column 1 on visibility:hidden, keeping a real size so its surface is never rebuilt at
   SciChart's 600x400 fallback — the rule .chart-pane exists for. The right-hand pane is the
   single exception, moved to column 2.

   The gap is a real divider rather than whitespace: two WebGL surfaces with nothing between
   them read as one chart with a seam. */
.chart-panes.is-split { grid-template-columns: 1fr 1fr; column-gap: 1px; background: var(--lw-border-subtle); }
.chart-panes.is-split > .chart-pane { background: var(--lw-bg); }
.chart-panes.is-split > .chart-pane-right { grid-area: 1 / 2; }

/* On a phone the two stack, for the same reason the degree grid moves below the chart under
   860px: half of a narrow screen is not a chart, it is a sparkline. */
@media (max-width: 860px) {
  .chart-panes.is-split { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; row-gap: 1px; column-gap: 0; }
  .chart-panes.is-split > .chart-pane-right { grid-area: 2 / 1; }
}

/* The tab whose chart is in the second pane. Marked, but never competing with the focused pane's
   tab: that one owns the title font, this one just says "and this one is up too". */
.chart-tab.partner > button { color: var(--lw-text-1); }
.chart-tab.partner { border-bottom: 2px solid var(--lw-text-3); }

/* The FOCUSED pane — the one the degree grid, the window control and the pills are driving.
   Without this the split has two charts and no way to tell which one the panel belongs to, which
   is the thing that made the first version of it unusable (Doug, 2026-08-26). Shown only while
   split: with one chart there is nothing to disambiguate. An inset ring rather than a border, so
   nothing reflows and the two charts keep exactly the same size. */
.chart-panes.is-split > .chart-pane-focused { box-shadow: inset 0 0 0 1px var(--lw-gold); }

/* A second view of the same instrument, in the other pane. */
.chart-tab .dup {
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  padding: 0 .15rem;
  font: inherit;
  line-height: 1;
  opacity: .55;
}
.chart-tab .dup:hover { opacity: 1; color: var(--lw-gold-text); }

/* The JS chart's host element. Needs a definite size: JavaScript owns everything inside
   it, so it has no content of its own to be sized by, and a zero-height div makes the
   surface build successfully and draw nothing — which looks like a broken chart rather
   than a missing style. It grows to fill the column (SciChart follows the element's size)
   and never drops below a usable height. */
.chart-host { width: 100%; flex: 1 1 auto; min-height: 320px; }

/* Time position and width. The slider takes the slack so the controls either side stay
   put as it grows — a scrub control that shifts its own buttons is unusable. */
.time-window {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .375rem .25rem .625rem;
  flex-wrap: wrap;
}
.time-window .tw-span { width: auto; min-width: 7.5rem; padding: .3rem .5rem; font: var(--lw-ui-small); }
.time-window .btn { min-width: 2rem; }
.time-window .tw-pos { flex: 1; min-width: 8rem; accent-color: var(--lw-gold); }
.time-window .tw-readout { color: var(--lw-text-2); white-space: nowrap; font: var(--lw-micro); }
@media (max-width: 860px) {
  .time-window .tw-readout { width: 100%; }
}
.chart-meta {
  display: flex; align-items: baseline; gap: .625rem;
  flex-wrap: wrap; padding: 0 .25rem .5rem;
}
/* The pills and the predictions button sit inside ONE flex child of .chart-meta, so that
   container's gap never applied between them and they ran together (Doug, 2026-08-20). Its own
   flex row gives them the same rhythm as the strip around them. */
.chart-meta-pills {
  display: inline-flex; align-items: center; flex-wrap: wrap; gap: .5rem;
}

/* degree grid */
.degree-head { margin-bottom: .5rem; }
.degree-title { font: var(--lw-ui); color: var(--lw-text-1); display: block; }
/* Hugs its content rather than stretching to the panel: the degree column and the three
   checkbox columns sit together, with no dead space between the label and the boxes. */
.degree-grid { width: auto; border-collapse: collapse; }
.degree-grid th, .degree-grid td {
  padding: .2rem .35rem;
  border-bottom: 1px solid var(--lw-divider);
  font: var(--lw-micro);
}
.degree-grid th:first-child { text-align: left; }
/* 2.2rem, was 2.6: with the headings shortened to Pd / W-if the columns can sit tighter
   (Doug, 2026-08-28: the What-if heading made its column too wide). */
.degree-grid th:not(:first-child), .degree-grid td:not(:first-child) {
  text-align: center; width: 2.2rem;
}
/* .35rem, was .75: the Deg→Wave gap read as dead space (Doug, 2026-08-28). */
.degree-grid td:first-child { color: var(--lw-text-2); white-space: nowrap; padding-right: .35rem; }
.degree-grid tr.on td:first-child { color: var(--lw-text-1); }
/* Half again the browser's default: the boxes were fiddly to hit. */
.degree-grid input[type="checkbox"] { width: 1.2rem; height: 1.2rem; margin: 0; vertical-align: middle; cursor: pointer; }
.degree-swatch {
  display: inline-block; width: 9px; height: 9px;
  border-radius: 2px; margin-right: .35rem; vertical-align: baseline;
}
/* The what-if arrival marker: a small light-blue dot beside an unticked what-if box, saying one
   is waiting. Light blue, not red — a what-if is a reason to be careful, not an alarm. It is the
   only thing that tells a trader a box exists to look at, so it must be findable without being
   loud. Hidden from assistive tech: the checkbox's own title carries the same fact in words. */
.whatif-waiting {
  color: var(--lw-focus); margin-left: .3rem; font-size: .7rem;
  vertical-align: middle; line-height: 1;
}

.degree-actions { display: flex; gap: .375rem; margin-top: .625rem; }
/* The one toggle under the grid: same box size as the grid's, label in the grid's muted text. */
.degree-tail {
  display: flex; align-items: center; gap: .5rem; margin: .625rem 0 0;
  font: var(--lw-ui); color: var(--lw-text-2); cursor: pointer; white-space: nowrap;
}
.degree-tail input[type="checkbox"] { width: 1.2rem; height: 1.2rem; margin: 0; cursor: pointer; }
.degree-tail:has(input:disabled) { opacity: .55; cursor: default; }

/* A phone: one column, the page scrolls. The panel dissolves (display:contents) so its two
   blocks sit around the chart — category row and picker ABOVE, the degree grid BELOW (Doug,
   2026-08-23: "put the chart control below the chart"). The chart fills the first screen:
   the viewport less the topbar and the open block (~6.25rem), so the time-window control is
   at the bottom of the screen and the grid is one scroll down. */
@media (max-width: 860px) {
  .workspace-body { flex-direction: column; overflow-y: auto; }
  .workspace-panel { display: contents; }
  .panel-handle { display: none; }
  .panel-open, .panel-degrees {
    flex: 0 0 auto; padding: .5rem .75rem; background: var(--lw-surface-1);
  }
  .panel-open { order: 0; padding-bottom: 0; }
  .panel-open .open-chart { border-bottom: 0; margin-bottom: 0; padding-bottom: .5rem; }
  .workspace-main { order: 1; flex: 0 0 auto; min-height: calc(100dvh - var(--lw-topbar-h) - 6.25rem); }
  .panel-degrees { order: 2; border-top: 1px solid var(--lw-border-subtle); }
}

/* ---------- forms ---------- */
label { display: block; font: var(--lw-ui); color: var(--lw-text-2); margin-bottom: .3rem; }

/* Match on what it is NOT, rather than listing types: Blazor's <InputText>
   renders a bare <input> with no type attribute, so input[type="text"] never
   matches it and the field renders unstyled. */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="file"]),
select, textarea {
  width: 100%;
  padding: .5rem .625rem;
  background: var(--lw-input-bg);
  color: var(--lw-text-1);
  border: 1px solid var(--lw-border);
  border-radius: 8px;
  font: var(--lw-body);
}

/* Browsers paint autofilled fields with their own pale background and dark text,
   which is unreadable on a dark theme and ignores `background`. The inset shadow is
   the only way to override the fill; the absurd transition delay stops it being
   repainted a moment later. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
select:-webkit-autofill,
textarea:-webkit-autofill {
  -webkit-text-fill-color: var(--lw-text-1);
  -webkit-box-shadow: 0 0 0 1000px var(--lw-input-bg) inset;
  box-shadow: 0 0 0 1000px var(--lw-input-bg) inset;
  caret-color: var(--lw-text-1);
  transition: background-color 100000s ease-in-out 0s;
}

::placeholder { color: var(--lw-text-3); opacity: 1; }
input:not([type="checkbox"]):not([type="radio"]):hover,
select:hover, textarea:hover { border-color: var(--lw-text-3); }
textarea { resize: vertical; min-height: 6rem; }
.hint { font: var(--lw-ui-small); color: var(--lw-text-3); margin-top: .3rem; }

.check { display: flex; align-items: flex-start; gap: .5rem; margin: .5rem 0; }
.check input { width: auto; margin-top: .15rem; }
.check label { margin: 0; color: var(--lw-text-1); }

.validation-message, .field-error { display: block; color: var(--lw-down); font: var(--lw-ui-small); margin-top: .3rem; }
.invalid { border-color: var(--lw-down); }

button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  padding: .5rem .9rem;
  border-radius: 8px;
  border: 1px solid var(--lw-border);
  background: var(--lw-surface-2);
  color: var(--lw-text-1);
  font: var(--lw-ui);
  cursor: pointer;
}
button:hover, .btn:hover { background: var(--lw-surface-3); text-decoration: none; }

/* The flat colour is set first and the gradient layered over it. Not redundant: the
   `background` shorthand resets background-image, so the order matters, and the flat
   value stays as the honest fallback anywhere the gradient does not paint. */
.btn-primary {
  background: var(--lw-gold);
  background-image: var(--lw-gold-shine);
  border-color: var(--lw-gold);
  color: var(--lw-on-gold);
  box-shadow: var(--lw-gold-bevel);
}
.btn-primary:hover {
  background-color: var(--lw-gold-hover);
  background-image: var(--lw-gold-shine-hover);
  border-color: var(--lw-gold-hover);
}
.btn-danger { color: var(--lw-down); border-color: var(--lw-down); background: transparent; }
.btn-danger:hover { background: var(--lw-down-muted); }
.btn-sm { padding: .3rem .6rem; font: var(--lw-ui-small); }

/* ---------- layout helpers ---------- */
.stack > * + * { margin-top: 1rem; }
.grid { display: grid; gap: 1rem; grid-template-columns: repeat(12, 1fr); }
.grid > .col-2  { grid-column: span 2; }
.grid > .col-3  { grid-column: span 3; }
.grid > .col-4  { grid-column: span 4; }
.grid > .col-6  { grid-column: span 6; }
.grid > .col-7  { grid-column: span 7; }
.grid > .col-9  { grid-column: span 9; }
.grid > .col-12 { grid-column: span 12; }
.grid > .col-auto { grid-column: auto; }
@media (max-width: 720px) {
  .grid > [class*="col-"] { grid-column: span 12; }
  .page { padding: 1rem .75rem 3rem; }
}

.muted { color: var(--lw-text-2); }
.lead { font: var(--lw-title); color: var(--lw-text-2); font-weight: 400; }

/* A section break. Deliberately one hairline in --lw-divider rather than the browser's
   default bevel, which is a 3D border from 1996 and reads as a bug next to flat surfaces. */
hr { border: 0; border-top: 1px solid var(--lw-divider); margin: 2.25rem 0; }

/* A diagram with its key beneath. The SVG carries no intrinsic size, so the width is the
   figure's and the height follows the viewBox — otherwise it collapses to zero in flex
   contexts and to 150px in others. */
figure { margin: 0; max-width: 44rem; }
figure svg { width: 100%; height: auto; display: block; }
/* A screenshot carries its own pixel size, which is larger than the column it sits in on
   every screen we care about. Height auto keeps the aspect ratio the width/height attributes
   declared, so reserving the space does not also fix it. */
figure img { max-width: 100%; height: auto; display: block; border-radius: 6px; }
figcaption { margin-top: .5rem; font: var(--lw-ui-small); color: var(--lw-text-2); }
.key { display: inline-flex; align-items: center; gap: .45rem; margin-right: 1.25rem; }
.key i { width: 1.4rem; border-top: 2.5px solid; display: inline-block; }

/* 40% of the figure width — a diagram whose point is the SHAPE of two lines, not the detail
   of either, and which at full width dominated a page it is only illustrating. */
.figure-sm { max-width: 17.6rem; }

/* A deliberately small utility set — enough for spacing and simple rows, not a
   framework. Prefer .stack and .grid over reaching for more of these. */
.small { font: var(--lw-ui-small); }
.mt-1 { margin-top: .25rem; }  .mt-2 { margin-top: .5rem; }
.mt-3 { margin-top: 1rem; }    .mt-4 { margin-top: 1.5rem; }
.mt-5 { margin-top: 2.25rem; }
.mt-6 { margin-top: 3.5rem; }
.mb-0 { margin-bottom: 0; }    .mb-3 { margin-bottom: 1rem; }
.w-100 { width: 100%; }
.d-block { display: block; }
.row-flex { display: flex; gap: .5rem; align-items: center; }
.flex-1 { flex: 1; }

/* ---------- surfaces ---------- */
.card {
  background: var(--lw-surface-1);
  border: 1px solid var(--lw-border-subtle);
  border-radius: 12px;
  padding: 1rem;
}

.notice { border-radius: 10px; padding: .75rem 1rem; border: 1px solid var(--lw-border); background: var(--lw-surface-2); }
.notice-success { border-color: var(--lw-up); background: var(--lw-up-muted); color: var(--lw-text-1); }
.notice-error   { border-color: var(--lw-down); background: var(--lw-down-muted); color: var(--lw-text-1); }
.notice-info    { border-color: var(--lw-border); background: var(--lw-surface-2); }
.notice-warn    { border-color: var(--lw-alert); background: var(--lw-surface-2); }

/* ---------- tables ---------- */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: .5rem .625rem; border-bottom: 1px solid var(--lw-divider); }
th { font: var(--lw-ui); color: var(--lw-text-3); font-weight: 500; white-space: nowrap; }
td { font: var(--lw-body); color: var(--lw-text-1); vertical-align: top; }
tbody tr:hover { background: var(--lw-surface-2); }

/* Figures in a column. Tabular numerals are not decoration: with proportional digits a 1 is
   narrower than an 8, so a column of numbers will not line up on its units and cannot be
   compared down the page — which is the only reason to put numbers in a column. */
th.num, td.num { text-align: right; font-variant-numeric: tabular-nums; }
.stat { font: var(--lw-title); color: var(--lw-text-1); font-variant-numeric: tabular-nums; }

dl.facts { display: grid; grid-template-columns: minmax(9rem, 14rem) 1fr; gap: .4rem 1rem; margin: 0 0 1rem; }
dl.facts dt { font: var(--lw-ui); color: var(--lw-text-3); }
dl.facts dd { margin: 0; font: var(--lw-body); color: var(--lw-text-1); }

.pill { display: inline-block; padding: .1rem .5rem; border-radius: 999px; font: var(--lw-micro); border: 1px solid var(--lw-border); color: var(--lw-text-2); }
.pill-up { color: var(--lw-up); border-color: var(--lw-up); }
.pill-down { color: var(--lw-down); border-color: var(--lw-down); }
/* The prediction zone hover label. Small on purpose (Doug, 2026-08-20): one line naming the
   degree, horizon, band and tier — the boxes nest, and without this there is no way to tell
   whose is whose. pointer-events:none so it never steals the mouse from the chart under it. */
.lw-zone-tip {
  /* Pinned at the origin and moved with a transform, never left/top: a transform is a
     compositor move that does not invalidate layout, and this label tracks the pointer
     across a page where every open tab's chart pane is mounted and laid out. will-change
     keeps it on its own layer so following the pointer does not re-composite the WebGL
     canvas underneath it on every frame. */
  position: absolute; left: 0; top: 0; will-change: transform;
  z-index: 5; pointer-events: none;
  padding: .15rem .4rem; border-radius: 3px;
  font: var(--lw-micro); white-space: nowrap;
  background: var(--lw-surface-1); color: var(--lw-text-1);
  border: 1px solid var(--lw-border);
  box-shadow: 0 1px 4px rgb(0 0 0 / .3);
}

/* The extreme-move chip: gold, because it is the chart telling you something, not an error.
   As a <button> it is also the auto-LR off switch, so it must LOOK pressable. */
.pill-extreme { color: var(--lw-gold-text); border-color: var(--lw-gold-text); }
button.pill-extreme { background: none; cursor: pointer; }
button.pill-extreme:hover { color: var(--lw-gold); border-color: var(--lw-gold); }

/* A wide table scrolls inside its own box, so the PAGE never scrolls sideways. The console's
   tables have grown past what a laptop shows; without this the whole layout shifts. */
.table-scroll { overflow-x: auto; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Blazor's error UI, restyled from the template default. */
#blazor-error-ui {
  position: fixed; bottom: 0; left: 0; right: 0;
  background: var(--lw-alert); color: var(--lw-on-gold);
  padding: .6rem 1.25rem; display: none; z-index: 1000;
}
#blazor-error-ui .dismiss { cursor: pointer; float: right; }
