/* ============================================================
   ece-base.css — the design system's light theme for the portal's
   framework-free course demos. HAND-WRITTEN (unlike ece-tokens.css /
   ece-tokens.js beside it, which tools/demo-theme.mjs generates).

   Load order in a demo's <head>:
     <link rel="stylesheet" href="../_shared/ece-tokens.css" />
     <link rel="stylesheet" href="../_shared/ece-base.css" />
     <link rel="stylesheet" href="style.css" />          (the demo's own)
   and before the demo's scripts:
     <script src="../_shared/ece-tokens.js"></script>
     <script src="../_shared/ece-charts.js"></script>    (Chart.js demos)

   Rules of this file:
   - It reads ONLY --ece-* variables. No color literal lives here: a token
     change reaches every demo through `pnpm gen:demo-theme`.
   - LOW specificity: a primitive is named by ONE class (or an :is() list of
     single classes, which still weighs one class); a part or a state adds
     only what a demo's own rule for the same thing must also name
     (`.status-pill .status-dot`, `.mode-btn.active`, `.toggle-switch
     input:checked + .track`, a :hover). So every selector here weighs no
     more than the demo's equivalent, and the demo's style.css, loaded
     later, wins the tie: a demo maps its existing classes onto this look one
     rule at a time without renaming anything. NOT :where() (zero): every
     demo opens with `*, *::before, *::after { margin: 0; padding: 0 }`, and
     a zero-specificity rule loses to that reset, which flattened the
     header's padding in the first pilot. Never put a compound selector
     inside an :is() list either; :is() takes its HEAVIEST argument's weight.
   - Each primitive answers to a canonical `ece-` class AND to the house class
     names the demos already share for the same thing (`.chart-card`,
     `.reset-btn`, `.toggle-switch`, `.mode-btn`, …), so a demo that keeps its
     markup inherits the look as soon as it deletes its own dark rules.
     Canonical markup: .ece-card (+ .ece-card-header), .ece-btn /
     .ece-btn-primary, .ece-switch > input + .track, .ece-segmented >
     .ece-segment, .ece-tabs > .ece-tab, .ece-metric (.metric-label +
     .metric-val), .ece-metric-pill, .ece-banner + .ece-banner-ok / -warn /
     -alert. A selected segment or tab is `.active` or aria-pressed /
     aria-selected="true".
   - No glows, no gradients, no text-transform on anything that can carry
     course notation (text-transform is paint-only: "pf" would paint as "PF"
     while the DOM still reads "pf").
   ============================================================ */

/* ── Geist, vendored (latin subset, the weights the design system loads) ── */
@font-face {
  font-family: "Geist";
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url("../_vendor/geist-5.2.9/geist-latin-400-normal.woff2") format("woff2");
  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: "Geist";
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url("../_vendor/geist-5.2.9/geist-latin-500-normal.woff2") format("woff2");
  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: "Geist";
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url("../_vendor/geist-5.2.9/geist-latin-600-normal.woff2") format("woff2");
  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: "Geist";
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url("../_vendor/geist-5.2.9/geist-latin-700-normal.woff2") format("woff2");
  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;
}
/* Glyphs outside that range (θ, φ, Ω, √, …) fall through to the next family
   in --ece-font-sans, exactly as they do in the React apps. */

/* ── Page ── */
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ece-bg);
  color: var(--ece-ink);
  font-family: var(--ece-font-sans);
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

:is(button, input, select, textarea) { font-family: inherit; }
:is(code, kbd, samp, pre) { font-family: var(--ece-font-mono); }

::selection { background: var(--ece-brand-lt); color: var(--ece-ink); }

/* Focus: a 2px brand OUTLINE (>= 4.5:1 on paper and bg). Deliberately not
   the design system's box-shadow halo: that halo is ~1.3:1 on bg-2
   (docs/ONBOARDING.md § @ece/design-system), and a ring carried in
   box-shadow is erased by any rule that sets box-shadow (a selected segment's
   lift, a demo's own shadow). The two places this file moves it are the
   switch, whose input is invisible (see TOGGLE SWITCH), and the range
   slider, whose ring sits on the thumb (see RANGE SLIDERS). */
:focus-visible {
  outline: 2px solid var(--ece-brand);
  outline-offset: 2px;
}

/* ── Scrollbars (light, thin) ── */
* { scrollbar-color: var(--ece-rule) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--ece-rule);
  border-radius: 99px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background-color: var(--ece-ink-4); }

/* ════════════════════════════════════════════
   SHARED DEMO HEADER — the markup seven demos already share:
     <header class="app-header">
       <div class="header-left">
         <a class="back-btn" href="../../index.html">…Portal</a>
         <div class="header-title"><h1>…</h1><span class="header-subtitle">…</span></div>
       </div>
       (optional .header-right)
     </header>
   Styled as the portal Masthead: paper, a rule bottom border, ink title,
   ink-3 subtitle, a secondary-button back link.
   ════════════════════════════════════════════ */
.app-header {
  position: relative;
  z-index: 10;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 56px;
  padding: 0 24px;
  background: var(--ece-paper);
  border-bottom: 1px solid var(--ece-rule);
  color: var(--ece-ink);
}

.header-left {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.header-title {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.header-title h1 {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0;
  color: var(--ece-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.header-subtitle {
  display: block;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.3;
  color: var(--ece-ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The design system's secondary Button, size sm. */
.back-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  height: 32px;
  padding: 0 12px 0 8px;
  border: 1px solid var(--ece-rule);
  border-radius: var(--ece-radius-chip);
  background: var(--ece-paper);
  color: var(--ece-ink);
  font-size: 12px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--ece-dur) var(--ece-ease), border-color var(--ece-dur) var(--ece-ease);
}
.back-btn:hover { background: var(--ece-bg-2); }
.back-btn svg { flex-shrink: 0; color: var(--ece-ink-3); }

@media (max-width: 640px) {
  .app-header { padding: 0 16px; gap: 12px; }
  .header-left { gap: 10px; }
}

/* ════════════════════════════════════════════
   PANELS / CARDS — paper on bg, a rule border, the card radius, no shadow
   (the design system's surfaces are quiet).
   ════════════════════════════════════════════ */
:is(.ece-card, .info-card, .controls-section, .ctrl-card, .chart-card,
       .equation-card, .formula-card, .gauge-card, .audio-card) {
  background: var(--ece-paper);
  border: 1px solid var(--ece-rule);
  border-radius: var(--ece-radius-card);
  color: var(--ece-ink);
}

:is(.ece-card-header, .info-card-header, .ctrl-card-header, .audio-card-header) {
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ece-ink);
  background: var(--ece-bg);
  border-bottom: 1px solid var(--ece-rule);
}

/* A sidebar is page, not card: the cards in it are what sit on paper. */
.sidebar {
  background: var(--ece-bg);
  border-right: 1px solid var(--ece-rule);
}

/* Section / control labels. */
.section-header {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ece-ink-3);
}

.slider-group { display: flex; flex-direction: column; gap: 5px; }
.slider-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.slider-header label { font-size: 12px; color: var(--ece-ink-2); cursor: pointer; }
.slider-val { font-family: var(--ece-font-mono); font-size: 12px; font-weight: 500; color: var(--ece-brand-dk); }
.slider-ticks { display: flex; justify-content: space-between; font-size: 10px; color: var(--ece-ink-3); }

/* ════════════════════════════════════════════
   BUTTONS — the design system's Button: primary = brand fill + paper text
   (hover brand-dk); secondary = paper + rule border (hover bg-2).
   ════════════════════════════════════════════ */
:is(.ece-btn, .ece-btn-primary, .reset-btn, .preset-btn) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 32px;
  padding: 6px 12px;
  border: 1px solid var(--ece-rule);
  border-radius: var(--ece-radius-chip);
  background: var(--ece-paper);
  color: var(--ece-ink);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  transition: background-color var(--ece-dur) var(--ece-ease), border-color var(--ece-dur) var(--ece-ease), color var(--ece-dur) var(--ece-ease);
}
:is(.ece-btn, .reset-btn, .preset-btn):hover { background: var(--ece-bg-2); }

.ece-btn-primary {
  background: var(--ece-brand);
  border-color: var(--ece-brand);
  color: var(--ece-paper);
}
.ece-btn-primary:hover { background: var(--ece-brand-dk); border-color: var(--ece-brand-dk); }

:is(.ece-btn, .ece-btn-primary, .reset-btn, .preset-btn):disabled { opacity: 0.5; pointer-events: none; }

/* ════════════════════════════════════════════
   RANGE SLIDERS — a rule-colored track and a brand thumb with a paper ring
   and a soft drop shadow (so a demo can recolor the thumb with `background`
   alone and it still reads). A demo that paints the filled part with an
   inline linear-gradient keeps doing so; this is only the resting look.
   The primitive is the bare `[type="range"]` (one class of weight, no `input`
   type selector), so a demo's `.my-slider` / `.my-slider::-webkit-slider-thumb`
   ties it and wins by load order.
   ════════════════════════════════════════════ */
[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  margin: 0;
  border-radius: 99px;
  background: var(--ece-rule);
  cursor: pointer;
}
[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--ece-paper);
  background: var(--ece-brand);
  box-shadow: 0 1px 3px rgb(var(--ece-ink-rgb) / 0.35);
  cursor: pointer;
  transition: background-color var(--ece-dur) var(--ece-ease);
}
[type="range"]::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--ece-paper);
  background: var(--ece-brand);
  box-shadow: 0 1px 3px rgb(var(--ece-ink-rgb) / 0.35);
  cursor: pointer;
}
[type="range"]::-moz-range-track { height: 4px; border-radius: 99px; background: transparent; }
/* Keyboard focus rides the THUMB. The global :focus-visible outline on the
   4px-tall input wraps the track at a 2px offset and cuts across the 16px
   thumb, so it is suppressed on the input and the same ring (2px brand, 2px
   offset; brand is >= 3:1 on paper, bg and bg-2) is drawn on the thumb. One
   rule per engine: a selector list holding the other engine's pseudo-element
   is dropped whole. */
[type="range"]:focus-visible { outline: none; }
[type="range"]:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--ece-brand); outline-offset: 2px; }
[type="range"]:focus-visible::-moz-range-thumb { outline: 2px solid var(--ece-brand); outline-offset: 2px; }

/* ════════════════════════════════════════════
   TOGGLE SWITCH
     <label class="ece-switch"><input type="checkbox" /><span class="track"></span></label>
   (the same structure as the house `.toggle-switch`)
   ════════════════════════════════════════════ */
:is(.ece-switch, .toggle-switch) {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 18px;
  flex-shrink: 0;
}
:is(.ece-switch, .toggle-switch) input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
:is(.ece-switch, .toggle-switch) .track {
  position: absolute;
  inset: 0;
  border-radius: 99px;
  background: var(--ece-bg-2);
  border: 1px solid var(--ece-ink-4);
  pointer-events: none;
  transition: background-color var(--ece-dur) var(--ece-ease), border-color var(--ece-dur) var(--ece-ease);
}
:is(.ece-switch, .toggle-switch) .track::after {
  content: "";
  position: absolute;
  left: 2px;
  top: 50%;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--ece-ink-4);
  transform: translateY(-50%);
  transition: left var(--ece-dur) var(--ece-ease), background-color var(--ece-dur) var(--ece-ease);
}
:is(.ece-switch, .toggle-switch) input:checked + .track {
  background: var(--ece-brand);
  border-color: var(--ece-brand);
}
:is(.ece-switch, .toggle-switch) input:checked + .track::after {
  left: calc(100% - 14px);
  background: var(--ece-paper);
}
/* The input is invisible (opacity 0), so its own outline would be too: the
   focus outline is drawn on the visible track instead. */
:is(.ece-switch, .toggle-switch) input:focus-visible { outline: none; }
:is(.ece-switch, .toggle-switch) input:focus-visible + .track { outline: 2px solid var(--ece-brand); outline-offset: 2px; }

/* ════════════════════════════════════════════
   SEGMENTED / STEP TOGGLES — the design system's SegmentedControl: a bg-2
   track, the selected option lifted onto paper. Selected = `.active` (the
   demos' convention) or aria-pressed / aria-selected.
   ════════════════════════════════════════════ */
:is(.ece-segmented, .mode-toggle, .step-toggle, .type-toggle) {
  display: flex;
  gap: 2px;
  width: fit-content;
  padding: 3px;
  border-radius: var(--ece-radius-chip);
  background: var(--ece-bg-2);
}
:is(.ece-segment, .mode-btn, .step-btn, .type-btn) {
  border: none;
  border-radius: calc(var(--ece-radius-chip) - 2px);
  background: transparent;
  color: var(--ece-ink-3);
  font-size: 12px;
  font-weight: 500;
  padding: 5px 12px;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--ece-dur) var(--ece-ease), color var(--ece-dur) var(--ece-ease);
}
:is(.ece-segment, .mode-btn, .step-btn, .type-btn):hover { color: var(--ece-ink); }
:is(.ece-segment, .mode-btn, .step-btn, .type-btn):is(.active, [aria-pressed="true"], [aria-selected="true"]) {
  background: var(--ece-paper);
  color: var(--ece-ink);
  box-shadow: var(--ece-shadow-pop);
}

/* Tabs: an underline strip, brand marks the open one. */
:is(.ece-tabs, .tab-bar) {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--ece-rule);
  background: var(--ece-paper);
}
:is(.ece-tab, .tab-btn) {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: -1px;
  padding: 9px 14px;
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--ece-ink-3);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: color var(--ece-dur) var(--ece-ease), border-color var(--ece-dur) var(--ece-ease);
}
:is(.ece-tab, .tab-btn):hover { color: var(--ece-ink); }
:is(.ece-tab, .tab-btn):is(.active, [aria-selected="true"]) {
  color: var(--ece-ink);
  border-bottom-color: var(--ece-brand);
}

/* ════════════════════════════════════════════
   METRICS — a tile (`.metric`: label over value) and an inline pill
   (`.metric-pill`). Values are mono, brand-dk.
   ════════════════════════════════════════════ */
:is(.ece-metric, .metric) {
  min-width: 0;
  padding: 6px 8px;
  border-radius: var(--ece-radius-chip);
  background: var(--ece-bg);
  border: 1px solid var(--ece-rule);
  text-align: center;
}
.metric-label {
  display: block;
  margin-bottom: 2px;
  font-size: 10px;
  color: var(--ece-ink-3);
}
.metric-val {
  font-family: var(--ece-font-mono);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ece-brand-dk);
  white-space: nowrap;
}
:is(.ece-metric-pill, .metric-pill) {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: 99px;
  border: 1px solid var(--ece-rule);
  background: var(--ece-paper);
  font-family: var(--ece-font-mono);
  font-size: 11px;
  color: var(--ece-ink-3);
  white-space: nowrap;
}

/* A status pill: a dot + text, neutral at rest (demos tint it per state). */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 12px 4px 9px;
  border-radius: 99px;
  border: 1px solid var(--ece-rule);
  background: var(--ece-paper);
  font-size: 12px;
  color: var(--ece-ink-2);
}
.status-pill .status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ece-ink-4);
}

/* ════════════════════════════════════════════
   CHART CARDS — header row + a flexible plotting area. `.chart-wrap` hosts a
   Chart.js canvas (forced to fill); `.canvas-wrap` a raw, JS-sized canvas.
   ════════════════════════════════════════════ */
.chart-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  min-height: 0;
  padding: 10px 14px 12px;
  overflow: hidden;
}
.chart-label {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--ece-ink-2);
}
.chart-sublabel {
  margin-left: auto;
  font-size: 11px;
  font-weight: 400;
  color: var(--ece-ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chart-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--ece-ink-4); }
.chart-wrap { position: relative; flex: 1; min-height: 0; }
/* !important assumes a Chart.js-managed canvas (Chart.js sizes its backing
   store from this CSS box, and the pair stops it growing past the card). A
   raw canvas whose JS sets its own size belongs in .canvas-wrap instead. */
.chart-wrap canvas { width: 100% !important; height: 100% !important; }
.canvas-wrap { position: relative; flex: 1; min-width: 0; min-height: 0; overflow: hidden; }
.canvas-wrap canvas { display: block; max-width: 100%; max-height: 100%; }

/* ════════════════════════════════════════════
   BANNERS — the Badge recipe: the -lt fill carries the -dk text (AA at
   these sizes), a faint -dk border. warn is never painted as a line or as
   text: it is ~2.6:1 on paper, so the advisory banner uses warn-dk.
   ════════════════════════════════════════════ */
:is(.ece-banner, .warn-banner) {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 11px;
  border-radius: var(--ece-radius-chip);
  border: 1px solid var(--ece-rule);
  background: var(--ece-bg);
  color: var(--ece-ink-2);
  font-size: 12px;
  line-height: 1.45;
}
:is(.ece-banner, .warn-banner) svg { flex-shrink: 0; margin-top: 1px; }
:is(.ece-banner-ok, .warn-ok) {
  background: var(--ece-ok-lt);
  border-color: rgb(var(--ece-ok-dk-rgb) / 0.25);
  color: var(--ece-ok-dk);
}
:is(.ece-banner-warn, .warn-caution) {
  background: var(--ece-warn-lt);
  border-color: rgb(var(--ece-warn-dk-rgb) / 0.25);
  color: var(--ece-warn-dk);
}
:is(.ece-banner-alert, .warn-bad) {
  background: var(--ece-alert-lt);
  border-color: rgb(var(--ece-alert-dk-rgb) / 0.25);
  color: var(--ece-alert-dk);
}
