/* ===== CPC — black & yellow instrument theme ==============================
   An engineering instrument, not a dashboard. Three rules hold the design
   together; break one and it stops being a system:

   1. YELLOW IS THE ONLY ACCENT. Every interactive affordance, focus ring and
      primary readout is --accent. Nothing else in the UI may introduce a new
      hue to mean "important" — if something needs emphasis, it gets weight,
      size or position, not a new colour.
   2. DATA SURFACES ARE OPAQUE. Cards, tables, popups and modal bodies use the
      --canvas/--panel/--panel-2/--raised ramp, so text contrast is a FIXED,
      stated number. Glass (--glass-*) survives only on chrome the eye passes
      over — the sticky header and the modal scrim — where nothing is read
      through it. The previous theme floated translucent panels over a photo,
      which made every contrast ratio a function of the pixels behind it.
   3. HUE ONLY ENCODES CATEGORY. The seven category tokens are generated in
      OKLCH at ONE lightness (.78) and ONE chroma (.085), varying only in hue,
      so no category shouts and none disappears (1.08x luminance spread).
      State (--pass/--warn/--err) carries HIGHER chroma so a failure always
      outranks a category tint. Never hand-pick a category colour by eye —
      regenerate it, or the set loses the property that makes it fair.

   Every colour below clears WCAG AA (4.5:1) against --raised, the LIGHTEST
   surface text ever sits on — not merely against --panel. Checking against the
   panel alone is how the old palette shipped 3.4:1 badges and a 2.1:1 grey.
   Density stays deliberately compact, but the type FLOOR is --t-micro (11px):
   the old 8.5-10px text was the legibility problem, not the density.
   Solid fallbacks live in the @supports block at the end of the file. */
:root {
  /* ---- surfaces: opaque, deterministic contrast ---- */
  --bg:        #090a0e;   /* page canvas, behind everything */
  --canvas:    #090a0e;
  --panel:     #15171b;   /* cards — the main data surface */
  --panel-2:   #202328;   /* nested surface, table stripe, chips */
  --raised:    #2b2e34;   /* inputs, hover, sticky table head */
  --solid:     #202328;   /* legacy name: anything that must stay opaque */
  --line:      #3b3e45;   /* borders */
  --glass-brd: #3b3e45;

  /* ---- ink ramp ---- */
  --ink:       #f6f7f8;   /* 12.69:1 on --raised */
  --ink-2:     #bcbec2;   /*  7.31:1 — secondary text, labels */
  --muted:     #979a9f;   /*  4.82:1 — units, hints (AA at the 11px floor) */
  --faint:     #75777c;   /*  3.04:1 — DISABLED STATE ONLY, never real text */

  /* ---- brand: the only accent ---- */
  --accent:    #facc15;   /* 8.89:1 — primary / confirm / focus */
  --accent-2:  #f9e03f;   /* hover-bright */
  --accent-dim:#a38b23;   /* quiet accent borders, tick rules */
  --accent-ink:#141207;   /* text on a filled accent button — 12.25:1 */
  --neutral:   #a3b3c4;   /* secondary / utility buttons */

  /* ---- categories: OKLCH L.78 C.085, hue only (see rule 3) ---- */
  --hover:     #8ebcec;   /* multicopter / hover   250° */
  --cruise:    #90c79b;   /* forward flight        150° */
  --hybrid:    #bfabe6;   /* tilt / hybrid         300° */
  --battery:   #72c7d5;   /* energy: battery       210° */
  --tank:      #e2a983;   /* energy: fuel tank      55° */
  --electric:  #7acab4;   /* motor: electric       175° */
  --engine:    #e9a29c;   /* motor: engine          25° */

  /* ---- state: higher chroma than any category ---- */
  --pass:      #7bd77f;   --pass-bg: #223d23;
  --warn:      #ffa746;   --warn-bg: #492f14;
  --err:       #fe626c;   --err-bg:  #4b2224;

  /* ---- type: 6 steps, 11px floor. NO ad-hoc px sizes. ---- */
  --t-micro:   11px;      /* labels, units, badges — the floor */
  --t-small:   12px;      /* table cells, secondary text */
  --t-body:    13px;      /* base */
  --t-lead:    15px;      /* card / panel titles */
  --t-head:    18px;      /* section heads */
  --t-hero:    34px;      /* the single big readout (torque) */

  /* ---- font roles: Segoe = the interface speaking, mono = a MEASURED value.
     Both ship with Windows/macOS, so nothing loads from a CDN (the app is
     CSP-restricted — see SECURITY.md). ---- */
  --ui:   "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, Roboto,
          Helvetica, Arial, sans-serif;
  --data: "Cascadia Mono", Consolas, ui-monospace, "SF Mono", Menlo, monospace;

  /* ---- space / radius / motion ---- */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  /* Radius scale by ROLE, not by taste: --r1 small (badges, chips, tiny
     buttons) · --r2 controls (inputs, dropdowns, buttons — they sit next to
     each other, so they must match) · --r3 panels (cards, modals) · --r-pill
     fully round. There were 9 different radii before this. */
  --r1: 5px; --r2: 8px; --r3: 10px; --r-pill: 999px;
  /* ONE height for every control that can share a row. An input at 26px beside
     a 28px dropdown beside a ~29px button reads as sloppy at a glance even when
     nothing is technically broken — and that mismatch is what "the fields look
     misaligned" usually means. Buttons/inputs get min-height (they must grow
     with content); the dropdown's react-select markup needs a fixed height. */
  --ctl-h: 28px;
  --dur: .15s;
  --shadow: 0 10px 30px rgba(0, 0, 0, .5);

  /* ---- glass: CHROME ONLY (header, modal scrim). Not for data surfaces. ---- */
  --glass-bg:  linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,0)),
               rgba(18, 18, 24, .72);
  --glass-blur: blur(18px) saturate(140%);
  --glass-edge: inset 0 1px 0 rgba(255,255,255,.07);
}

/* ===== form controls on this Dash build =====
   dcc.Input renders a BARE `<input class="dash-input">` (no wrapper), the
   Dropdown is react-select v1 (`.Select-*` classes inside `.dash-dropdown`),
   and dcc.Textarea is a classless `<textarea>` — style those directly (rules
   in the forms section below). color-scheme:dark makes the remaining native
   widgets (number spinners, selection, popups) follow the dark theme. */
html:root { color-scheme: dark; }

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { background: var(--bg); }
body {
  /* body itself stays transparent — an opaque background here would paint
     OVER the fixed body::before photo layer (negative z-index paints above
     the root background but below the body's own) */
  background: transparent;
  color: var(--ink);
  font-family: var(--ui);
  font-size: var(--t-body);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}
/* the backdrop: Mejzlik carbon propellers, at full strength.
   Because panels are OPAQUE now, the scrim no longer has any legibility work to
   do — it exists purely for looks. That is the point: the old theme had to keep
   darkening the photo to protect text floating on glass above it, and this
   version doesn't, so the photograph can simply be visible.
   Fixed pseudo-element: cheaper and smoother than background-attachment: fixed. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1;
  background:
    radial-gradient(1100px 640px at 80% 0%, rgba(250, 204, 21, .06), transparent 60%),
    linear-gradient(180deg, rgba(10,10,12,.28), rgba(10,10,13,.40) 55%, rgba(10,10,13,.58)),
    /* `cover` guarantees the photo fills EVERY viewport (a fixed %-zoom left
       the right edge of wide screens bare); the position keeps the
       spinner/blade cluster right of centre, out of the grid card's way */
    url("bg-carbon-props.webp") 70% 32% / cover no-repeat;
}

/* ---- subtle themed scrollbars (lists, tables, modal bodies) ---- */
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb {
  background: var(--faint); border-radius: var(--r2);
  border: 2px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background-color: rgba(255,255,255,.24); }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }

/* ---- header (sticky glass bar) ----
   The ONE place glass is still right: it is chrome the eye passes over, nothing
   is read *through* it, and the blur signals that content scrolls beneath. */
.app-header {
  display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
  position: sticky; top: 0; z-index: 100;
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  color: var(--ink);
  padding: var(--s2) var(--s4);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--glass-edge), 0 8px 24px rgba(0,0,0,.45);
}
/* a single yellow hairline along the top edge — the brand mark on every screen,
   and the only decorative use of accent in the app */
.app-header::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-dim) 30%, transparent 68%);
}
.app-header h1 {
  font-size: var(--t-lead); margin: 0; font-weight: 700; letter-spacing: -.01em;
}
.app-header h1::first-line { color: var(--accent); }
.app-header .sub {
  color: var(--muted); font-size: var(--t-micro); margin-top: 1px;
  letter-spacing: .05em;
}
.spacer { flex: 1; }
.header-actions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }

/* ---- layout ----
   Three real breakpoints instead of one cliff. The old rule collapsed straight
   from three columns to ONE at 1280px, so every 1000-1280px laptop — a normal
   size for this tool — stacked the whole app vertically and pushed the results
   table below two screens of controls. */
.app-body {
  display: grid;
  grid-template-columns: 280px 330px 1fr;   /* controls · editor · grid */
  gap: var(--s3); padding: var(--s3); align-items: start;
}
/* wide laptop: keep the work area beside the controls, narrow the two rails */
@media (max-width: 1440px) {
  .app-body { grid-template-columns: 240px 280px 1fr; gap: 10px; }
}
/* small laptop / large tablet: rails pair up on one row, work area below */
@media (max-width: 1180px) {
  .app-body { grid-template-columns: 1fr 1fr; }
  .app-body > :last-child { grid-column: 1 / -1; }
}
/* tablet / phone: single column */
@media (max-width: 780px) {
  .app-body { grid-template-columns: 1fr; padding: var(--s2); }
  .app-body > :last-child { grid-column: auto; }
}

.col { display: flex; flex-direction: column; gap: 10px; }

/* ---- the data panel: OPAQUE (see rule 2 at the top of this file) ----
   No backdrop-filter here any more. Besides guaranteeing contrast, dropping it
   removes a compositing layer per card — on the calc tab that is a dozen
   blurred surfaces the GPU no longer re-composites on every scroll. */
.card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r3); padding: 11px;
  box-shadow: var(--shadow);
  transition: border-color var(--dur) ease;
}
.card:hover { border-color: var(--faint); }
/* Cards no longer create a stacking context of their own (backdrop-filter did),
   but an OPEN react-select menu still has to clear the next sibling card, so
   keep lifting whichever card holds focus. */
.card:focus-within { z-index: 30; }
.card h2 {
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: .08em;
  color: var(--accent); margin: 0 0 var(--s2); font-weight: 700;
}
.card h3 {
  font-size: var(--t-micro); margin: var(--s3) 0 5px; color: var(--ink);
  font-weight: 650;
}
/* inactive card (e.g. the editor when no propeller is selected) — dimmed but
   still legible, not so faint it reads as broken */
.card.disabled { opacity: .6; pointer-events: none; }

/* ---- collapsible cards (html.Details/Summary) ----
   The <summary> is the title bar and toggles the card natively. */
details.card { padding: 0; }                 /* head + body own the padding now */
.card-head {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; cursor: pointer; user-select: none; list-style: none;
}
.card-head::-webkit-details-marker { display: none; }   /* drop default triangle */
.card-head::marker { content: ""; }
.card-head:hover .card-title { color: var(--accent-2); }
.card-title {
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: .7px;
  color: var(--accent); font-weight: 700;
}
.card-count { font-size: var(--t-micro); color: var(--muted); font-weight: 600; }
.card-chevron {                              /* points down when open, right when closed */
  margin-left: auto; width: 0; height: 0;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-top: 6px solid var(--muted); transition: transform var(--dur) ease;
}
details.card:not([open]) .card-chevron { transform: rotate(-90deg); }
.card-body { padding: 0 12px 12px; }
/* content settles in softly when a card / sub-section unfolds */
@keyframes rise-in { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
details.card[open] > .card-body,
details.subcard[open] > .subcard-body,
details.op-row[open] > .op-row-body { animation: rise-in var(--dur) ease-out; }

/* ---- dcc.Dropdown (react-select v1) — dark glass skin ----
   The translucent trigger lets the glass panel show through; the MENU is
   solid on purpose (a translucent popup floating over arbitrary content
   would be unreadable). */
.dash-dropdown .Select-control {
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r2);
  height: var(--ctl-h);
  transition: border-color var(--dur) ease, box-shadow var(--dur) ease;
}
.dash-dropdown .Select-placeholder,
.dash-dropdown .Select--single > .Select-control .Select-value {
  line-height: calc(var(--ctl-h) - 2px); padding-left: 9px; color: var(--ink);
}
.dash-dropdown .Select-placeholder { color: var(--faint); }
.dash-dropdown .Select-value-label { color: var(--ink) !important; }  /* r-s hardcodes var(--ink) */
.dash-dropdown .Select-input { height: calc(var(--ctl-h) - 2px); padding-left: 9px; }
.dash-dropdown .Select-input > input { color: var(--ink); padding: 5px 0; }
.dash-dropdown .Select-arrow { border-color: var(--muted) transparent transparent; }
.dash-dropdown .Select.is-focused > .Select-control,
.dash-dropdown .Select.is-open > .Select-control {
  border-color: var(--accent); box-shadow: 0 0 0 3px rgba(250,204,21,.16);
  background: var(--raised);
}
.dash-dropdown .Select-menu-outer {
  background: var(--solid); border: 1px solid var(--faint); border-radius: var(--r2);
  box-shadow: 0 14px 34px rgba(0,0,0,.55); margin-top: 3px; color: var(--ink);
}
.dash-dropdown .Select-option { background: transparent; color: var(--ink); font-size: var(--t-small); padding: 6px 9px; }
.dash-dropdown .Select-option.is-focused { background: rgba(250,204,21,.10); }
.dash-dropdown .Select-option.is-selected { color: var(--accent); }
.dash-dropdown .Select-noresults { color: var(--muted); padding: 6px 9px; font-size: var(--t-small); }
.dash-dropdown .Select--multi .Select-value {
  background: rgba(250,204,21,.10); border: 1px solid rgba(250,204,21,.3);
  color: var(--accent); margin-top: 2px;
}
.dash-dropdown .Select--multi .Select-value-icon { border-right: 1px solid rgba(250,204,21,.3); }
.dash-dropdown .Select-clear-zone { color: var(--muted); }
.dash-dropdown .Select-clear-zone:hover { color: var(--err); }

/* ---- add (palette, draggable) ---- */
.add-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; margin-bottom: 8px; }
.add-row-2 { grid-template-columns: 1fr 1fr; }
/* Palette tiles follow the unified button pattern (see CLAUDE.md › UI
   conventions): idle = coloured border+text, hover = filled, :active = pressed
   ring. Their colour is the item's category colour, carried by --btn. */
.add-btn {
  --btn: var(--accent);
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  border: 1.5px solid var(--btn); background: transparent; border-radius: var(--r3);
  padding: 8px 4px 6px; cursor: grab; font-weight: 600; font-size: var(--t-micro);
  color: var(--btn); user-select: none;
  transition: background var(--dur) ease, color var(--dur) ease, transform var(--dur) ease, box-shadow var(--dur) ease;
}
.add-btn img { width: 30px; height: 30px; pointer-events: none; }
.add-btn:hover { background: var(--btn); color: var(--accent-ink); transform: translateY(-1px); box-shadow: 0 6px 16px rgba(0,0,0,.4); }
.add-btn:active { cursor: grabbing; box-shadow: inset 0 0 0 2px rgba(0,0,0,.5); transform: none; }
/* NB: category classes are prefixed `cat-` — a bare `hover` class collides with
   Dash's global `.hover { position:absolute }` tooltip rule and breaks layout. */
.add-btn.cat-hover    { --btn: var(--hover); }
.add-btn.cat-cruise   { --btn: var(--cruise); }
.add-btn.cat-hybrid   { --btn: var(--hybrid); }
.add-btn.cat-battery  { --btn: var(--battery); }
.add-btn.cat-tank     { --btn: var(--tank); }
.add-btn.cat-electric { --btn: var(--electric); }
.add-btn.cat-engine   { --btn: var(--engine); }

/* ---- propeller / energy / motor lists (select + delete per row) ----
   NO height cap: a capped flex column SHRINKS its rows into unreadable,
   unclickable slivers once the list outgrows it. The cards are collapsible,
   so a long list may simply grow. flex:none guards rows against any future
   height constraint. */
.prop-list { display: flex; flex-direction: column; gap: 5px; }
.prop-list > * { flex: none; }
.prop-item {
  display: flex; align-items: stretch; gap: 0; width: 100%;
  border: 1px solid var(--line); background: var(--panel-2); border-radius: var(--r2); overflow: hidden;
  transition: border-color var(--dur) ease, background var(--dur) ease;
}
.prop-item:hover { border-color: var(--faint); }
.prop-item.active { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.prop-sel {
  display: flex; align-items: center; gap: 8px; flex: 1; text-align: left;
  border: 0; background: transparent; padding: 5px 9px; cursor: pointer;
  font: inherit; font-size: var(--t-small); color: var(--ink);
}
.prop-sel:hover { background: rgba(250,204,21,.06); }
/* requirements-tab row: selector button stacked above its drivetrain viz */
.req-prop-cell { display: flex; flex-direction: column; flex: 1; min-width: 0; }
/* drivetrain connection viz — motor/engine → battery/tank wired to this prop */
.dt-viz {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
  padding: 0 9px 5px 29px; font-size: var(--t-micro);
}
.dt-lead, .dt-arrow { color: var(--muted); }
.dt-chip {
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 7px 1px 5px;
  border: 1px solid var(--line); border-radius: var(--r-pill); background: rgba(255,255,255,.05);
  color: var(--ink); white-space: nowrap;
}
.dt-chip .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
/* the pill's text: truncate rather than stretch the chip — component
   labels are free text and can be long */
.dt-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
                 min-width: 0; }
.dt-none { color: var(--muted); font-style: italic; }
/* the drivetrain line is a button — clicking it opens the powerplant popup */
.dt-btn {
  border: 0; background: transparent; font: inherit; color: inherit;
  width: 100%; cursor: pointer; text-align: left; border-radius: var(--r1);
  transition: background var(--dur) ease;
}
.dt-btn:hover { background: rgba(250,204,21,.07); }
.dt-btn:hover .dt-lead, .dt-btn:hover .dt-arrow { color: var(--accent); }

/* ---- powerplant quick-view popup (requirements tab) ---- */
/* .modal-card.dt-card (two classes) so this beats the later, equal-specificity
   .modal-card width rule — a bare .dt-card here is overridden and does nothing. */
.modal-card.dt-card { width: min(520px, 94vw); }
.dt-modal-body { overflow-y: auto; max-height: calc(88vh - 48px); }
/* engine section: compact two-column fields on the left, drivetrain diagram on
   the right (fields kept tight — numeric inputs don't need much width) */
.dt-eng-row { display: flex; gap: 10px; align-items: stretch; }
.dt-eng-fields { flex: 1 1 0; min-width: 0; }
.dt-eng-map-btn { width: 100%; margin-top: 8px; font-weight: 650; }
/* reduction-gearbox addon (engine section): a dashed sub-block with the ratio */
.dt-gearbox { border: 1px dashed var(--line); border-radius: var(--r2);
              padding: 7px 8px; margin-top: 8px; background: var(--panel); }
.dt-gearbox-head { display: flex; align-items: center; gap: 6px; margin-bottom: 5px;
                   font-size: var(--t-micro); font-weight: 700; letter-spacing: .03em;
                   text-transform: uppercase; color: var(--muted); }
.dt-gearbox-ic { width: 16px; height: 16px; flex: none; }
.dt-gearbox-hint { margin-top: 3px; }
/* drivetrain diagram: Propeller → Engine → Fuel tank + readiness badge */
.dt-conn {
  flex: 0 0 175px; display: flex; flex-direction: column; gap: 6px;
  border: 1px solid var(--line); border-radius: var(--r3); background: var(--panel);
  padding: 8px; align-self: stretch;
}
.dt-conn-title { font-size: var(--t-micro); font-weight: 700; letter-spacing: .04em;
                 text-transform: uppercase; color: var(--muted); text-align: center; }
.dt-conn-chain { display: flex; flex-direction: column; align-items: stretch; gap: 2px; }
.dt-conn-node {
  display: flex; align-items: center; gap: 7px; padding: 6px 8px;
  border: 1px solid var(--line); border-radius: var(--r2); background: var(--panel-2);
  border-left: 3px solid var(--muted);
}
.dt-conn-node.cat-prop     { border-left-color: var(--cruise); }
.dt-conn-node.cat-engine   { border-left-color: var(--engine); }
.dt-conn-node.cat-electric { border-left-color: var(--electric); }
.dt-conn-node.cat-tank     { border-left-color: var(--tank); }
.dt-conn-node.cat-battery  { border-left-color: var(--battery); }
.dt-conn-node.muted { opacity: .55; border-style: dashed; }
.dt-conn-ic { width: 26px; height: 26px; flex: none; }
/* gearbox badge overlaid on the engine node's icon (added when ratio != 1) */
.dt-conn-ic-wrap { position: relative; width: 26px; height: 26px; flex: none; }
.dt-conn-badge-ic {
  position: absolute; right: -6px; bottom: -6px; width: 16px; height: 16px;
  padding: 1px; border-radius: 50%; background: var(--solid);
  box-shadow: 0 0 0 1.5px var(--engine);
}
.dt-conn-text { display: flex; flex-direction: column; min-width: 0; }
.dt-conn-kind { font-size: var(--t-micro); text-transform: uppercase; letter-spacing: .03em;
                color: var(--muted); font-weight: 700; }
.dt-conn-label { font-size: var(--t-small); font-weight: 600; overflow: hidden;
                 text-overflow: ellipsis; white-space: nowrap; }
.dt-conn-arrow { text-align: center; color: var(--muted); font-size: var(--t-micro); line-height: 1; }
.dt-conn-badge { margin-top: auto; text-align: center; font-size: var(--t-micro);
                 font-weight: 700; padding: 4px 6px; border-radius: var(--r1); }
.dt-conn-badge.ok { background: color-mix(in srgb, var(--pass) 14%, transparent); color: var(--pass); }
.dt-conn-badge.no { background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); }
/* non-blocking warning under the ready badge (e.g. battery voltage out of range) */
.dt-conn-warn { margin-top: 4px; text-align: center; font-size: var(--t-micro);
                font-weight: 600; line-height: 1.25; color: var(--warn); }
/* engine map + altitude plots modal (two graphs stacked) */
.dt-plot-card { width: min(720px, 95vw); max-height: 92vh; }
.dt-plot-body { display: flex; flex-direction: column; gap: 4px; overflow-y: auto; }
.dt-plot-body .modal-graph { height: 52vh; }
.dt-plot-body .dt-alt-graph { height: 34vh; }
.dt-plot-hint { padding: 2px 16px 0; }
/* on narrow screens stack the diagram beneath the fields */
@media (max-width: 520px) {
  .dt-eng-row { flex-direction: column; }
  .dt-conn { flex-basis: auto; }
}
.dt-sec {
  border: 1px solid var(--line); border-radius: var(--r3); background: var(--panel-2);
  padding: 8px 9px; margin-bottom: 8px;
}
.dt-sec-head { display: flex; align-items: center; gap: 7px; margin-bottom: 6px;
               font-weight: 650; font-size: var(--t-small); }
.dt-sec-head .dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.dt-sec-kind { margin-left: auto; color: var(--muted); font-size: var(--t-micro); font-weight: 600; }
/* compact "✕ Remove" in a section head */
.dt-sec-del { padding: 2px 7px; font-size: var(--t-micro); font-weight: 600; }
/* add-powerplant rows in the popup (motor/engine or battery/tank) */
.dt-add-row { display: flex; gap: 8px; margin-bottom: 8px; align-items: center;
              flex-wrap: wrap; }
.dt-add-row.col { flex-direction: column; align-items: stretch; }
.dt-add-row .empty-note { margin: 0; flex: 1 1 auto; }
.dt-add-btn { font-weight: 650; white-space: nowrap; }
.dt-add-plus { font-weight: 800; }
.dt-add-btn.cat-electric { --btn: var(--electric); }
.dt-add-btn.cat-engine   { --btn: var(--engine); }
.dt-add-btn.cat-battery  { --btn: var(--battery); }
.dt-add-btn.cat-tank     { --btn: var(--tank); }
.prop-item.active .pi-label { color: var(--accent); }
.prop-sel .dot { width: 11px; height: 11px; border-radius: 50%; flex: none; }
.prop-sel .pi-label { font-weight: 650; }
.prop-sel .pi-meta { color: var(--muted); font-size: var(--t-micro); margin-left: auto; }
.prop-sel .coax-badge {
  font-size: var(--t-micro); background: rgba(255,255,255,.08); border: 1px solid var(--line);
  border-radius: var(--r1); padding: 0 4px; color: var(--muted);
}
/* In-row trash icon: red idle icon, fills red on hover, pressed ring (unified). */
.prop-del-btn {
  border: 0; border-left: 1px solid var(--line); background: transparent;
  color: var(--err); cursor: pointer; padding: 0 10px; font-size: var(--t-body);
  transition: background var(--dur) ease, color var(--dur) ease;
}
.prop-del-btn:hover { background: var(--err); color: var(--accent-ink); }
.prop-del-btn:active { box-shadow: inset 0 0 0 2px rgba(0,0,0,.5); }
.empty-note { color: var(--muted); font-size: var(--t-micro); font-style: italic; padding: 5px 2px; }

/* ---- 9x15 placement grid ---- */
.graph-card { padding: 10px; }
.grid-wrap {
  position: relative; width: 100%; aspect-ratio: 15 / 9; max-height: 70vh;
  margin: 0 auto; border: 1px solid rgba(255,255,255,.12); border-radius: var(--r3);
  background: var(--ink); overflow: hidden; touch-action: none;
}
.grid-bg, .grid-lines, .grid-props { position: absolute; inset: 0; pointer-events: none; }
/* drivetrain connection lines — behind the chips, above the background */
.grid-links-layer { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.grid-bg { background-position: center; background-repeat: no-repeat; background-size: contain; }
.grid-lines {
  background-image:
    linear-gradient(to right, var(--ink-2) 1px, transparent 1px),
    linear-gradient(to bottom, var(--ink-2) 1px, transparent 1px);
  background-size: calc(100% / 15) calc(100% / 9);
  background-position: -1px -1px; opacity: .7;
}
.grid-prop {
  position: absolute; transform: translate(-50%, -50%);
  width: 5.3%; aspect-ratio: 1 / 1; min-width: 24px; box-sizing: border-box;
  border-radius: 50%; border: 2.5px solid var(--cat);
  background: color-mix(in srgb, var(--cat) 22%, #ffffff);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; cursor: grab; pointer-events: auto; user-select: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .45); z-index: 2;
}
.grid-prop:active { cursor: grabbing; }
.grid-prop.sel { box-shadow: 0 0 0 3px var(--accent), 0 1px 5px rgba(0, 0, 0, .4); z-index: 3; }
/* faded top-view propeller artwork as the circle backdrop (behind the text) */
.grid-prop::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background-image: var(--prop-img); background-size: 108%;
  background-position: center; background-repeat: no-repeat;
  opacity: .42; z-index: 0; pointer-events: none;
}
.grid-prop.coax::after {
  content: ""; position: absolute; inset: 17%; border-radius: 50%;
  border: 2px dashed var(--cat); z-index: 1;
}
.gp-label { position: relative; z-index: 2; font-size: var(--t-micro); font-weight: 700; color: var(--accent-ink); line-height: 1; }
.gp-glyph { position: relative; z-index: 2; font-size: var(--t-micro); color: var(--line); line-height: 1; }

/* batteries / fuel tanks on the grid — square chips to read apart from rotors */
.grid-energy {
  position: absolute; transform: translate(-50%, -50%);
  width: 5.3%; aspect-ratio: 1 / 1; min-width: 24px; box-sizing: border-box;
  border-radius: var(--r1); border: 2.5px solid var(--cat);
  background: color-mix(in srgb, var(--cat) 22%, #ffffff);
  display: flex; align-items: center; justify-content: center;
  cursor: grab; pointer-events: auto; user-select: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .45); z-index: 2;
}
.grid-energy:active { cursor: grabbing; }
.grid-energy.sel { box-shadow: 0 0 0 3px var(--accent), 0 1px 5px rgba(0, 0, 0, .4); z-index: 3; }
.grid-energy::before {
  content: ""; position: absolute; inset: 0; border-radius: var(--r1);
  background-image: var(--prop-img); background-size: 76%;
  background-position: center; background-repeat: no-repeat;
  opacity: .5; z-index: 0; pointer-events: none;
}

/* motors / engines on the grid — round chips (rotary), distinct colour */
.grid-motor {
  position: absolute; transform: translate(-50%, -50%);
  width: 5.3%; aspect-ratio: 1 / 1; min-width: 24px; box-sizing: border-box;
  border-radius: 50%; border: 2.5px solid var(--cat);
  background: color-mix(in srgb, var(--cat) 22%, #ffffff);
  display: flex; align-items: center; justify-content: center;
  cursor: grab; pointer-events: auto; user-select: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .45); z-index: 2;
}
.grid-motor:active { cursor: grabbing; }
.grid-motor.sel { box-shadow: 0 0 0 3px var(--accent), 0 1px 5px rgba(0, 0, 0, .4); z-index: 3; }
.grid-motor::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background-image: var(--prop-img); background-size: 80%;
  background-position: center; background-repeat: no-repeat;
  opacity: .5; z-index: 0; pointer-events: none;
}

/* ---- forms (compact) ---- */
.field { display: flex; flex-direction: column; gap: 2px; margin-bottom: 6px; min-width: 0; }
.field > label { font-size: var(--t-micro); color: var(--muted); font-weight: 600; letter-spacing: .2px; }
/* align-items:end is LOAD-BEARING, not cosmetic.
   Each .field is its own column of [label, input]. When one label in a row wraps
   to two lines and its neighbour's does not, the taller field pushed ITS input
   down while the neighbour's stayed up — so the two inputs in a row sat at
   different heights, which is the misalignment users notice. Aligning the grid
   items to the row's END makes every input in a row share a bottom edge (and,
   since all inputs are --ctl-h tall, a top edge too); the shorter label simply
   gains a little space above it. Do not remove this to "tidy up" the grid.
   (CSS subgrid would additionally align the LABELS, but it forces one row-gap
   for both label→input and row→row spacing, which is why it is not used here.) */
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 5px 7px; align-items: end; }
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 5px 7px; align-items: end; }
/* grid/flex items refuse to shrink below their content's intrinsic width by
   default (min-width:auto) — a bare number <input> is ~20ch wide, so in the
   narrow panels the SECOND grid column escaped the card (operating-point
   rows: Required thrust / Temperature). Let cells and inputs shrink. */
.grid2 > *, .grid3 > * { min-width: 0; }

/* ---- field groups: caption + hairline ----
   Numeric specs are grouped by what they describe (a motor's continuous ratings,
   an engine's characteristic RPM points, ...) and ordered defining-spec-first.
   The caption is deliberately light: it separates without boxing, so the editors
   stay as dense as before. Group membership lives in constants.FIELD_GROUPS. */
.fgroup { margin: 0 0 var(--s2); }
.fgroup:last-child { margin-bottom: 0; }
.fgroup-cap {
  display: flex; align-items: center; gap: var(--s2);
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted); font-weight: 650; margin: 0 0 5px;
  white-space: nowrap;
}
.fgroup-cap::after {                    /* the hairline, filling the row */
  content: ""; flex: 1 1 auto; height: 1px; background: var(--line);
  min-width: var(--s2);
}
/* the trailing "Advanced" group — a <details> whose summary IS the caption */
details.fgroup-adv > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: var(--s2);
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: .09em;
  color: var(--faint); font-weight: 650; margin: 0 0 5px;
}
details.fgroup-adv > summary::-webkit-details-marker { display: none; }
details.fgroup-adv > summary::marker { content: ""; }
details.fgroup-adv > summary:hover { color: var(--accent); }
details.fgroup-adv > summary .fgroup-rule {
  flex: 1 1 auto; height: 1px; background: var(--line); min-width: var(--s2);
}
details.fgroup-adv > summary .card-chevron { margin-left: 0; }
details.fgroup-adv:not([open]) > summary .card-chevron { transform: rotate(-90deg); }
details.fgroup-adv[open] > .fgroup-adv-body { animation: rise-in .16s ease-out; }
.fgroup-hint {
  font-size: var(--t-micro); color: var(--muted); margin: 0 0 5px; line-height: 1.35;
}

/* "Generate default layout" sits beside the Body-type dropdown; stretch it to
   the dropdown's height so the row reads as one unit */
.gen-layout-btn { width: 100%; min-height: 28px; padding: 4px 6px; font-size: var(--t-micro); }

/* title line inside the merged Selected-item editor (one per group) */
.sel-title { font-weight: 700; margin-bottom: 8px; }
.sel-title:empty { display: none; }

/* ---- prominent drivetrain-link field (icon + tinted frame) ----
   Used for every drives/powers/feeds dropdown in the Selected-item editor.
   The linked component's category colour rides on --link. */
.link-field {
  --link: var(--accent);
  flex-direction: row; align-items: center; gap: 8px;
  border: 1px solid color-mix(in srgb, var(--link) 50%, transparent);
  background: color-mix(in srgb, var(--link) 9%, transparent);
  border-radius: var(--r3); padding: 5px 7px;
}
.link-field .link-ic { width: 26px; height: 26px; flex: none; }
.link-field .link-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.link-field .link-body > label {
  font-size: var(--t-micro); font-weight: 700; letter-spacing: .3px;
  color: color-mix(in srgb, var(--link) 75%, #ffffff);
}
/* the link row in the motor editor: two link fields side by side, top-aligned */
.link-row { align-items: start; }
.link-row .link-field { margin-bottom: 0; height: 100%; }

/* ---- segmented CW/CCW rotation toggle ---- */
.seg-row { display: flex; gap: 0; border: 1px solid var(--line); border-radius: var(--r2); overflow: hidden; }
.seg-row label {
  flex: 1 1 0; text-align: center; margin: 0; padding: 5px 4px;
  font-size: var(--t-small); font-weight: 600; cursor: pointer; color: var(--muted);
  background: var(--raised); transition: background var(--dur) ease, color var(--dur) ease;
  white-space: nowrap;
}
.seg-row label + label { border-left: 1px solid var(--line); }
.seg-row label:hover { color: var(--ink); }
.seg-row input { display: none; }
.seg-row label:has(input:checked) {
  background: rgba(250, 204, 21, .16); color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
/* older engines without :has() keep the plain radio dots */
@supports not selector(:has(*)) {
  .seg-row input { display: inline-block; margin-right: 4px; accent-color: var(--accent); }
}

/* ---- work-in-progress control (coaxial) — visible but clearly inactive ---- */
.wip-control { opacity: .4; pointer-events: none; }
.wip-note { font-size: var(--t-micro); color: var(--warn); margin-top: 1px; }

/* dcc.Input is a BARE `<input class="dash-input">` on this Dash build — style
   it directly. Keep the selector at (0,1,0) specificity and BEFORE the results
   table section so .flt-num / .flt-text can override the width. */
.dash-input {
  display: block; width: 100%; min-width: 0;
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r2);
  padding: 4px 8px; font: inherit; font-size: var(--t-small);
  min-height: var(--ctl-h);
  transition: border-color var(--dur) ease, box-shadow var(--dur) ease;
}
/* Every numeric dcc.Input renders with step="any" (num_input() in this file,
   the results-table min/max filters) — there is no spinner-increment
   granularity to violate, so entering more decimal places than some nominal
   "step" can NEVER flag :invalid. These rules are the belt-and-suspenders
   backup (older Firefox quirks, a future field that forgets step="any"):
   suppress the UA's invalid styling and flag only true range violations
   (inputs that set min/max). Keep the two UA rules separate — an unknown
   pseudo-class would drop a combined selector list entirely. These sit
   BEFORE :focus so a focused field keeps its yellow ring either way. */
.dash-input:invalid { box-shadow: none; outline: none; }
.dash-input:-moz-ui-invalid { box-shadow: none; outline: none; }
.dash-input:out-of-range {
  border-color: var(--err);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--err) 18%, transparent);
}
.dash-input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(250,204,21,.16);
}
.dash-input::placeholder { color: var(--faint); }

/* No up/down spinner on ANY number input, app-wide — every numeric field
   uses step="any" anyway (see above), so the native increment/decrement
   control has nothing meaningful to step by. */
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type=number] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* dcc.Textarea (export JSON) is a classless <textarea> — same glass skin */
textarea {
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r2); padding: 6px 8px; font: inherit;
}
textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(250,204,21,.16); }
.inherit-hint { font-size: var(--t-micro); color: var(--muted); }
.radio-row label, .check-row label { margin-right: 12px; font-size: var(--t-small); cursor: pointer; }
.radio-row input, .check-row input { margin-right: 4px; accent-color: var(--accent); }

/* ---- global limits table ---- */
.limit-block { border: 1px solid var(--line); border-radius: var(--r2); padding: 7px 8px; margin-bottom: 7px; background: var(--panel-2); }
.limit-block .lb-head { display: flex; align-items: center; gap: 6px; font-weight: 650; font-size: var(--t-small); margin-bottom: 5px; }
.limit-block .lb-head .dot { width: 10px; height: 10px; border-radius: 50%; }

/* ---- buttons (unified) ----------------------------------------------------
   ONE pattern for every button (see CLAUDE.md › UI conventions):
     idle    → coloured border + coloured text, transparent fill (identifiable)
     :hover  → fill with the colour, text flips to a contrasting ink
     :active → stays filled + an inner dark ring marks the pressed state
   The colour rides on two custom props so a variant only swaps the colour:
     --btn      the button's colour (border + text idle, fill on hover)
     --btn-ink  text colour once filled
   Default is the accent (yellow); the modifiers below switch it. */
.btn {
  --btn: var(--accent); --btn-ink: var(--accent-ink);
  border: 1px solid var(--btn); background: transparent; color: var(--btn);
  border-radius: var(--r2); padding: 3px 10px; cursor: pointer;
  font: inherit; font-size: var(--t-small); font-weight: 600;
  min-height: var(--ctl-h);
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  transition: background var(--dur) ease, color var(--dur) ease, transform var(--dur) ease, box-shadow var(--dur) ease;
}
.btn:hover {
  background: var(--btn); color: var(--btn-ink); border-color: var(--btn);
  transform: translateY(-1px); box-shadow: 0 5px 14px rgba(0,0,0,.35);
}
.btn:active { box-shadow: inset 0 0 0 2px rgba(0,0,0,.5); transform: none; }   /* pressed ring */
.btn:disabled, .btn[disabled] { opacity: .45; cursor: not-allowed; box-shadow: none; transform: none; }
/* a disabled button keeps its idle look on hover (no fill) so it reads as inert */
.btn:disabled:hover, .btn[disabled]:hover { background: transparent; color: var(--btn); }
/* disabled inputs (e.g. the hover OP airspeed, fixed at 0 m/s) read as greyed */
.field input:disabled, .field input[disabled] {
  opacity: .5; cursor: not-allowed; background: rgba(255,255,255,.05);
}

/* colour variants — set only the two custom props */
.btn-primary { --btn: var(--accent);  --btn-ink: var(--accent-ink); }  /* yellow */
.btn-danger  { --btn: var(--err);     --btn-ink: var(--accent-ink); }  /* red    */
.btn-ghost   { --btn: var(--neutral); --btn-ink: var(--accent-ink); }  /* slate  */

/* (display/align/gap now come from the base .btn rule) */
.btn-danger .bin { font-size: var(--t-lead); line-height: 1; }
.btn-row { display: flex; gap: 6px; flex-wrap: wrap; }

/* ---- summary / validation ---- */
.summary-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; }
.stat { display: flex; justify-content: space-between; border-bottom: 1px dashed var(--line); padding: 2px 0; font-size: var(--t-small); }
.stat .k { color: var(--muted); }
.stat .v { font-weight: 650; color: var(--accent); }
.msg { display: flex; gap: 7px; align-items: flex-start; padding: 5px 8px; border-radius: var(--r2); margin-bottom: 4px; font-size: var(--t-small); }
.msg.ok   { background: color-mix(in srgb, var(--pass) 12%, transparent);  color: var(--pass); }
.msg.warn { background: color-mix(in srgb, var(--warn) 12%, transparent); color: var(--warn); }
.msg.err  { background: color-mix(in srgb, var(--err) 14%, transparent);  color: var(--err); }
.msg .ic { font-weight: 800; }

/* ---- export ---- */
#export-text { width: 100%; height: 140px; font-family: "Cascadia Code", Consolas, monospace; font-size: var(--t-micro); }
.legend { display: flex; gap: 12px; flex-wrap: wrap; font-size: var(--t-micro); color: var(--muted); margin-top: 7px; }
.legend .li { display: flex; align-items: center; gap: 5px; }
.legend .dot { width: 10px; height: 10px; border-radius: 50%; }
.upload-box {
  border: 1.5px dashed var(--faint); border-radius: var(--r2); padding: 12px; text-align: center;
  color: var(--muted); cursor: pointer; font-size: var(--t-small); margin-top: 7px;
  transition: border-color var(--dur) ease, color var(--dur) ease, background var(--dur) ease;
}
.upload-box:hover { border-color: var(--accent); color: var(--accent); background: rgba(250,204,21,.05); }
.hint { font-size: var(--t-micro); color: var(--muted); margin-top: 5px; line-height: 1.4; margin-bottom: 7px; }

#e-eng-group, #e-elec-group { margin-top: 2px; }

/* ---- nested collapsible sub-section inside an editor panel (e.g. the
   Selected-propeller "Local limit overrides"). A plain <details.subcard> — NOT
   details.card — so the global Expand/Collapse-all leaves it alone. ---- */
.subcard { border: 1px solid var(--line); border-radius: var(--r2); background: var(--panel-2); margin: 8px 0 4px; }
.subcard-head {
  display: flex; align-items: center; gap: 8px; padding: 6px 9px;
  cursor: pointer; user-select: none; list-style: none;
}
.subcard-head::-webkit-details-marker { display: none; }
.subcard-head::marker { content: ""; }
.subcard-head:hover .subcard-title { color: var(--accent-2); }
.subcard-title { font-size: var(--t-micro); font-weight: 650; color: var(--ink); }
.subcard-body { padding: 0 9px 9px; }
details.subcard:not([open]) .card-chevron { transform: rotate(-90deg); }

/* ---- read-only (catalog / repo-DB) motor spec ---- */
.spec-wrap.locked-spec { opacity: .6; pointer-events: none; }   /* lock the fields */
.lock-note:empty { display: none; }
.lock-note {
  display: flex; align-items: center; gap: 6px; font-size: var(--t-micro); line-height: 1.35;
  color: var(--accent); background: rgba(250,204,21,.08);
  border: 1px solid rgba(250,204,21,.2); border-radius: var(--r2); padding: 4px 8px; margin: 4px 0 2px;
}

/* ---- bundled motor actions (apply-to-all · duplicate) ----
   Both utility buttons grow to share the full row; Delete lives on its own
   row below, matching the propeller editor's delete button. */
.action-bundle {
  border-top: 1px solid var(--line); margin-top: 8px; padding-top: 8px;
  align-items: center;
}
.action-bundle .btn { flex: 1 1 0; }

/* ---- "Save as project motor" row (name input + button on one line) ---- */
#save-project-group h3 { margin-top: 10px; }
.save-row { align-items: stretch; }
.save-row .dash-input { flex: 1; width: auto; }

/* ===== tab bar (header) ===== */
.tabbar { display: flex; gap: 6px; }
/* Tab buttons share the unified pattern (accent). The selected tab uses the
   pressed look (filled + inner ring) to mark the current view. */
.tab-btn {
  --btn: var(--accent); --btn-ink: var(--accent-ink);
  border: 1px solid var(--btn); background: transparent; color: var(--btn);
  border-radius: var(--r2); padding: 3px 12px; cursor: pointer; font: inherit;
  font-weight: 650; font-size: var(--t-small);
  min-height: var(--ctl-h);
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  transition: background var(--dur) ease, color var(--dur) ease, box-shadow var(--dur) ease;
}
.tab-btn:hover { background: var(--btn); color: var(--btn-ink); }
.tab-btn.active {
  background: var(--btn); color: var(--btn-ink);
  box-shadow: inset 0 0 0 2px rgba(0,0,0,.5);
}
/* locked tab (feature not shipping this release): muted, dashed, no hover */
.tab-btn-locked, .tab-btn-locked:hover {
  --btn: var(--muted);
  border-style: dashed; background: transparent; color: var(--muted);
  cursor: not-allowed; opacity: .6; box-shadow: none;
}
.tab-btn-locked::after { content: " 🔒"; font-size: var(--t-micro); }

/* the tab bar pulses while the welcome guide is open, drawing the eye to it */
.tabbar-highlight {
  border-radius: var(--r3); padding: 4px; margin: -4px;
  animation: tabbar-pulse 1.6s ease-in-out infinite;
}
@keyframes tabbar-pulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(250,204,21,.55), 0 0 14px rgba(250,204,21,.35); }
  50%      { box-shadow: 0 0 0 2px rgba(250,204,21,.9), 0 0 22px rgba(250,204,21,.6); }
}
@media (prefers-reduced-motion: reduce) {
  .tabbar-highlight { animation: none; box-shadow: 0 0 0 2px rgba(250,204,21,.7); }
}
/* header "?" help button — compact circle */
.help-btn {
  width: 26px; padding: 3px 0; text-align: center; font-weight: 700;
  border-radius: 50%;
}

/* ===== TAB 2 — operating points & requirements ===== */
/* middle (OP + requirements) column matches the first column's width */
.req-body { grid-template-columns: 290px 290px 1fr; }
@media (max-width: 1280px) { .req-body { grid-template-columns: 1fr; } }

/* OP/REQ status badges in the propeller list */
.req-badges { margin-left: auto; display: flex; gap: 4px; }
/* rotation-direction pill (requirement) beside the propeller label */
.pi-rot {
  font-size: var(--t-micro); color: var(--muted); border: 1px solid var(--line);
  border-radius: var(--r1); padding: 0 5px; white-space: nowrap;
}
.req-badge {
  font-size: var(--t-micro); font-weight: 800; letter-spacing: .4px; padding: 1px 5px;
  border-radius: var(--r1); border: 1px solid var(--line);
}
.req-badge.on  { background: color-mix(in srgb, var(--pass) 18%, transparent);  color: var(--pass); border-color: color-mix(in srgb, var(--pass) 40%, transparent); }
.req-badge.off { background: rgba(255,255,255,.05); color: var(--faint); }

/* operating-point rows — each is a collapsible <details> (roll up/down) */
.op-row {
  border: 1px solid var(--line); border-radius: var(--r2); background: var(--panel-2);
  margin-bottom: 7px;
}
.op-row-head {
  display: flex; align-items: center; gap: 8px; padding: 7px 8px;
  cursor: pointer; user-select: none; list-style: none;
}
.op-row-head::-webkit-details-marker { display: none; }
.op-row-head::marker { content: ""; }
.op-row-head:hover .op-row-title { color: var(--accent-2); }
.op-row-title { flex: 1; font-weight: 650; font-size: var(--t-small); color: var(--ink); }
/* standalone (not in-row) trash button in the OP header — full red outline */
.op-del { border: 1px solid var(--err); border-radius: var(--r1); color: var(--err); padding: 1px 7px; }
details.op-row:not([open]) .card-chevron { transform: rotate(-90deg); }
.op-row-body { padding: 0 8px 8px; }
.op-calc { font-size: var(--t-micro); color: var(--accent); margin-top: 3px; min-height: 13px; }

/* air-density field: numeric input + "ISA" reset button on one line.
   Colour comes from the unified .btn pattern (accent); only size differs here. */
.density-row { display: flex; gap: 5px; align-items: stretch; }
.density-row .dash-input { flex: 1; width: auto; }
.op-isa-btn { flex: none; padding: 4px 9px; }

/* ---- results panel: candidate table (DB props) ---- */
.calc-status { margin-bottom: 7px; }
.calc-status:empty { display: none; }
/* staleness: config changed since the cached results were computed */
.btn-stale { --btn: var(--warn); }
/* busy: a build callback (compare / plot modal) is running — pulses until done */
.btn-busy { pointer-events: none; animation: busy-pulse 1s ease-in-out infinite; }
@keyframes busy-pulse { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
/* dcc.Loading wrapper around the detail-modal plots: keep room for the spinner
   while the figures build (the plot area starts empty on modal open) */
.plots-loading { min-height: 140px; }
.calc-stale-note { font-size: var(--t-micro); color: var(--warn); line-height: 1.4;
                   margin: 0 0 6px; }
.calc-stale-note:empty { display: none; }
.calc-table-wrap { overflow: auto; max-height: 72vh; border: 1px solid var(--line); border-radius: var(--r2); }
.calc-table {
  width: 100%; border-collapse: collapse; font-size: var(--t-small); white-space: nowrap;
  font-variant-numeric: tabular-nums;   /* engineering table: aligned digits */
}
/* sticky header rows are OPAQUE on purpose — rows scrolling beneath a
   translucent header would bleed through the numbers. --raised (not --panel)
   so the header separates from the body by value alone, without a heavy rule. */
.calc-table thead th {
  position: sticky; top: 0; z-index: 2; background: var(--raised);
  border-bottom: 1px solid var(--line); padding: 0; text-align: center;
}
/* sortable column header button (unified-ish: ghost, fills on hover) */
.th-sort {
  width: 100%; border: 0; background: transparent; color: var(--accent);
  font: inherit; font-weight: 650; font-size: var(--t-micro); line-height: 15px; padding: 5px 7px;
  cursor: pointer; text-align: center; transition: background var(--dur) ease;
}
.th-sort:hover { background: rgba(250,204,21,.10); }
/* non-sortable header label: match the sortable button's box so headers align */
.th-label {
  display: block; padding: 5px 7px; font-weight: 650; font-size: var(--t-micro); line-height: 15px;
  color: var(--muted); text-align: center;
}
/* the propeller-name column (2nd — after the compare column) stays
   left-aligned, header and cells alike */
.calc-table thead th:nth-child(2) .th-sort,
.calc-table thead th:nth-child(2) .th-label { text-align: left; }
.calc-table thead .flt-row th { position: sticky; top: 26px; z-index: 1; padding: 3px 4px; }
.flt-th { background: var(--raised); }
.flt-text { width: 100%; min-width: 88px; }
.flt-sel { min-width: 76px; font-size: var(--t-micro); }
/* the filter row sits at the top of the scroll container, so the dropdown menu
   opens downward into the visible table; lift it above the sticky header rows */
.flt-sel .Select-menu-outer { z-index: 5; }
.flt-range { display: flex; gap: 3px; }
.flt-num { width: 46px; }
.flt-na { color: var(--muted); }

.calc-table tbody td { border-bottom: 1px solid rgba(255,255,255,.06); padding: 3px 7px; text-align: center; }
/* name cells left-aligned by CLASS — the positional rule broke once the
   compare column took the first slot (and sub-rows have no cell there at all,
   their first td being the OP label) */
.calc-table tbody td.rt-name, .calc-table tbody td.rt-op-name { text-align: left; }
/* compare column: one cell per candidate spanning its whole row group,
   checkbox centered both ways; the whole cell is a click target */
.cmp-cell {
  text-align: center; vertical-align: middle; width: 30px; cursor: pointer;
  border-right: 1px solid rgba(255,255,255,.06);
}
/* overall (per-prop) row — bold, clickable */
.rt-overall { cursor: pointer; font-weight: 600; }
.rt-overall:hover td { background: rgba(250,204,21,.07); }
.rt-overall td { border-top: 2px solid rgba(255,255,255,.10); }
.rt-overall.failed { opacity: .65; }
.rt-name { font-weight: 700; }
/* per-OP sub rows — dimmer and indented, but NOT smaller than the floor and no
   longer italic: 11px italic at this density cost more legibility than the
   distinction was worth. Indent + colour already separate them from the
   overall row. */
.rt-op td {
  font-size: var(--t-micro); color: var(--ink-2);
  border-bottom: 1px dashed rgba(255,255,255,.07);
}
.rt-op-name { padding-left: var(--s4) !important; color: var(--muted); }
/* pass / fail cell colouring (+ "peak" = burst-only powerplant match).
   State tints are mixed from the state colour rather than hand-written rgba, so
   they track the token. They stay TRANSLUCENT so the row-hover tint still
   layers over them. NOTE: "peak" uses --warn, not the accent yellow — yellow
   means "interactive" everywhere else in this UI, and a state that borrows it
   reads as something you can click (see rule 1 at the top of this file). */
.c-ok   { color: var(--pass); }
.c-warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.c-bad  { color: var(--err);  background: color-mix(in srgb, var(--err) 14%, transparent); }
.c-na   { color: var(--faint); }

/* ---- compare selection (checkbox in the name column) + compare modal ---- */
.cmp-check {
  accent-color: var(--accent); width: 14px; height: 14px;
  margin: 0; cursor: pointer; display: inline-block; vertical-align: middle;
}
.compare-card { width: min(1100px, 96vw); }
.compare-body { overflow-y: auto; max-height: calc(92vh - 48px); }
.compare-body h3 { font-size: var(--t-micro); margin: 14px 0 7px; color: var(--accent);
                   font-weight: 650; text-transform: uppercase; letter-spacing: .5px; }
.cmp-table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r2); }
.cmp-table { width: 100%; }
.cmp-table th, .cmp-table td { text-align: center; white-space: nowrap; }
.cmp-table th:first-child, .cmp-table td:first-child { text-align: left; }
.cmp-table td:first-child { color: var(--muted); font-weight: 600; }
/* section separators (Overall / per-operating-point blocks) */
.cmp-sec td {
  background: rgba(250, 204, 21, .07); color: var(--accent); font-weight: 700;
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: .5px;
  text-align: left;
}
/* the best value in a metric row */
.cmp-best { color: var(--pass); font-weight: 700; background: color-mix(in srgb, var(--pass) 10%, transparent); }
/* per-operating-point plot block header (stacked blocks under the table) */
.cmp-op-title {
  font-weight: 700; font-size: var(--t-small); color: var(--ink);
  border-top: 1px solid var(--line); margin-top: 12px; padding-top: 10px;
}

/* ---- candidate detail modal ---- */
.prop-detail-card { width: min(900px, 96vw); }
.modal-body { padding: 12px 14px; }
.prop-detail-body { overflow-y: auto; max-height: calc(92vh - 48px); }
.prop-detail-body h3 { font-size: var(--t-micro); margin: 14px 0 7px; color: var(--accent); font-weight: 650; text-transform: uppercase; letter-spacing: .5px; }
.detail-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.detail-actions { display: flex; align-items: center; gap: 8px; margin-left: auto;
                  flex-wrap: wrap; }
.detail-geo { font-size: var(--t-small); font-weight: 600; }
.detail-geo.ok { color: var(--pass); }
.detail-geo.bad { color: var(--err); }
.detail-table { width: 100%; border-collapse: collapse; font-size: var(--t-small); font-variant-numeric: tabular-nums; }
.detail-table th, .detail-table td {
  border: 1px solid rgba(255,255,255,.08); padding: 3px 7px; text-align: right;
}
.detail-table th { background: rgba(255,255,255,.05); color: var(--accent); font-weight: 650; }
.detail-table td:first-child, .detail-table th:first-child { text-align: left; }
.detail-table .warn-row td { background: color-mix(in srgb, var(--warn) 12%, transparent); color: var(--warn); }
.detail-table .bad-row td { background: color-mix(in srgb, var(--err) 12%, transparent); color: var(--err); }
.op-plot-sel-wrap { margin: -4px 0 7px; }
.op-plot-sel { display: flex; flex-wrap: wrap; gap: 5px 14px; font-size: var(--t-small); color: var(--muted); }
.op-plot-sel label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.detail-plots-wrap { margin-top: 4px; }
.detail-plots { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 760px) { .detail-plots { grid-template-columns: 1fr; } }
.detail-graph { width: 100%; height: 320px; }

/* ---- detail-modal plot tabs (dcc.Tabs) ---- */
.detail-tabs .tab-container, .detail-tabs > div:first-child {
  display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--line);
}
.detail-tab {
  background: transparent !important; border: 1px solid var(--line) !important;
  border-bottom: 0 !important; color: var(--muted) !important;
  padding: 5px 11px !important; font-size: var(--t-small); font-weight: 600;
  border-radius: var(--r1) var(--r1) 0 0; cursor: pointer; transition: color var(--dur) ease, background var(--dur) ease;
}
.detail-tab:hover { color: var(--accent) !important; background: rgba(250,204,21,.07) !important; }
.detail-tab--sel {
  background: rgba(255,255,255,.06) !important; border: 1px solid var(--line) !important;
  border-bottom: 0 !important; color: var(--accent) !important;
  box-shadow: inset 0 2px 0 0 var(--accent);
}
.detail-tabs .tab-content { padding-top: 10px; }
/* informational note above the dimensional plots */
.plot-note {
  font-size: var(--t-micro); color: var(--muted); background: color-mix(in srgb, var(--hover) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--hover) 25%, transparent); border-radius: var(--r1);
  padding: 5px 9px; margin-bottom: 8px;
}

/* copy controls */
.copy-row {
  border-top: 1px solid var(--line); margin-top: 10px; padding-top: 8px;
  display: flex; flex-direction: column; gap: 4px;
}
.copy-row > label { font-size: var(--t-micro); color: var(--muted); font-weight: 600; }

/* ---- modal overlays (motor limits plot · candidate detail · version) ---- */
.modal {
  position: fixed; inset: 0; z-index: 1000; background: rgba(0, 0, 0, .5);
  -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
  align-items: center; justify-content: center; padding: 24px;
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
.modal-card {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-brd); border-radius: var(--r3);
  box-shadow: var(--glass-edge), 0 24px 64px rgba(0,0,0,.6);
  width: min(1040px, 96vw); max-height: 92vh;
  display: flex; flex-direction: column; overflow: hidden;
  animation: modal-in .2s ease-out;               /* restarts when re-shown */
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 14px; border-bottom: 1px solid var(--line);
}
.modal-title { color: var(--accent); font-weight: 700; font-size: var(--t-body); }
.modal-close {
  --btn: var(--neutral);
  border: 1px solid var(--btn); background: transparent; color: var(--btn);
  border-radius: var(--r2); width: 27px; height: 27px; cursor: pointer; font-size: var(--t-body);
  flex: none; transition: background var(--dur) ease, color var(--dur) ease;
}
.modal-close:hover { background: var(--btn); color: var(--accent-ink); }
.modal-close:active { box-shadow: inset 0 0 0 2px rgba(0,0,0,.5); }
.modal-graph { width: 100%; height: 70vh; padding: 6px; }

/* ---- engine correction tables (editor) ---- */
.corr-table { display: flex; flex-direction: column; gap: 4px; margin: 2px 0 4px; }
.corr-row { display: flex; gap: 5px; align-items: center; }
.corr-row > input { flex: 1 1 0; min-width: 0; }
.corr-row .prop-del-btn { flex: 0 0 auto; }
.corr-head { margin-bottom: 0; }
.corr-head .corr-h { flex: 1 1 0; font-size: var(--t-micro); color: var(--muted); }
.corr-head .corr-h:last-child { flex: 0 0 28px; }

/* ---- version badge (header) + version info & history modal ---- */
/* the badge is a normal .btn (accent) — only the pill geometry is local */
.version-badge { font-size: var(--t-micro); font-weight: 700; padding: 3px 11px;
                 border-radius: var(--r-pill); letter-spacing: .4px; }
.version-card { width: min(640px, 92vw); max-height: 84vh; }
/* flex column with min-height:0 so .ver-list can shrink and scroll while the
   timeline stays pinned above it */
.version-body { display: flex; flex-direction: column; min-height: 0;
                padding: 6px 16px 14px; }
.ver-timeline { flex: 0 0 auto; margin-bottom: 4px; }
.ver-list { overflow-y: auto; min-height: 0; padding: 4px 10px 4px 4px; }
/* one rail entry per version — continuous left line, dot colored by kind */
.ver-entry { position: relative; margin-left: 6px; padding: 0 4px 14px 18px;
             border-left: 2px solid rgba(255,255,255,.10); }
.ver-entry:last-child { padding-bottom: 4px; }
.ver-dot { position: absolute; left: -7px; top: 3px; width: 12px; height: 12px;
           border-radius: 50%; border: 2px solid var(--panel); }
.ver-k-major { background: var(--accent); }
.ver-k-minor { background: var(--hover); }
.ver-k-patch { background: var(--muted); }
.ver-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.ver-num { font-weight: 700; font-size: var(--t-small); color: var(--accent); }
.ver-title { font-weight: 600; font-size: var(--t-small); flex: 1 1 auto; min-width: 140px; }
.ver-dev { font-size: var(--t-micro); font-weight: 700; color: var(--warn);
           border: 1px solid var(--warn); border-radius: var(--r-pill); padding: 0 6px;
           letter-spacing: .5px; align-self: center; }
.ver-meta { color: var(--muted); font-size: var(--t-micro); white-space: nowrap; }
.ver-notes { margin: 4px 0 0; padding-left: 16px; color: var(--muted);
             font-size: var(--t-micro); line-height: 1.5; }
.ver-notes li { margin: 1px 0; }
.ver-notes li::marker { color: var(--faint); }

/* ---- welcome / tutorial modal — multi-page knowledge base ---- */
.welcome-card { width: min(720px, 96vw); max-height: 90vh; }
/* fill the card so the topic list stays put while the content pane scrolls */
.welcome-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.wl-layout { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: 150px 1fr; gap: 14px; }

/* left: topic navigation (a table-of-contents list, NOT .btn buttons) */
.wl-nav { display: flex; flex-direction: column; gap: 3px; min-height: 0; overflow-y: auto;
          border-right: 1px solid var(--line); padding-right: 10px; }
.wl-nav-btn { display: flex; align-items: center; gap: 8px; text-align: left; width: 100%;
              border: 1px solid transparent; background: transparent; color: var(--muted);
              border-radius: var(--r2); padding: 7px 9px; font-size: var(--t-small); line-height: 1.2;
              cursor: pointer; transition: background var(--dur) ease, color var(--dur) ease, border-color var(--dur) ease; }
.wl-nav-btn:hover { background: var(--panel-2); color: var(--ink); }
.wl-nav-btn.active { background: var(--panel-2); border-color: var(--accent);
                     color: var(--accent); font-weight: 650; }
.wl-nav-ico { font-size: var(--t-lead); flex: none; width: 18px; text-align: center; }

/* right: scrolling content pane */
.wl-content { min-height: 0; overflow-y: auto; padding-right: 4px; }
.wl-page-title { display: flex; align-items: center; gap: 9px; font-size: var(--t-lead);
                 font-weight: 700; color: var(--accent); margin-bottom: 10px; }
.wl-page-ico { font-size: var(--t-head); }
.wl-p { color: var(--muted); font-size: var(--t-small); line-height: 1.55; margin-bottom: 10px; }
.wl-h { color: var(--ink); font-size: var(--t-small); font-weight: 700; letter-spacing: .2px; margin: 12px 0 6px; }
.wl-ul, .wl-ol { margin: 0 0 10px; padding-left: 18px; color: var(--muted);
                 font-size: var(--t-small); line-height: 1.55; }
.wl-ul li, .wl-ol li { margin: 3px 0; }
.wl-ul li::marker, .wl-ol li::marker { color: var(--accent); }
.wl-ul b, .wl-ol b { color: var(--ink); font-weight: 650; }

/* tip callout */
.wl-tip { display: flex; gap: 9px; align-items: flex-start; margin-top: 12px;
          padding: 9px 11px; border: 1px solid var(--line); border-left: 3px solid var(--accent);
          border-radius: var(--r2); background: var(--panel-2); color: var(--muted);
          font-size: var(--t-small); line-height: 1.5; }
.wl-tip-tag { flex: none; font-size: var(--t-micro); font-weight: 700; letter-spacing: .4px;
              text-transform: uppercase; color: var(--accent); border: 1px solid var(--accent);
              border-radius: var(--r-pill); padding: 1px 6px; margin-top: 1px; }

/* motor-match state legend (coloured dots) */
.wl-states { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 10px; }
.wl-state { display: flex; gap: 9px; align-items: flex-start; font-size: var(--t-small);
            line-height: 1.45; color: var(--muted); }
.wl-state b { color: var(--ink); }
.wl-dot { flex: none; width: 9px; height: 9px; border-radius: 50%; margin-top: 3px; }
.wl-dot-ok { background: var(--cruise); }
.wl-dot-warn { background: var(--warn); }
.wl-dot-bad { background: var(--err); }
.wl-dot-none { background: var(--muted); }

/* overview page: the three tool cards */
.wl-steps { display: flex; flex-direction: column; gap: 8px; margin-bottom: 4px; }
.wl-step { display: flex; gap: 10px; align-items: flex-start;
           border: 1px solid var(--line); border-radius: var(--r3); background: var(--panel-2);
           padding: 9px 11px; }
.wl-step-icon { font-size: var(--t-head); line-height: 1.1; flex: none; width: 26px; text-align: center; }
.wl-step-title { font-weight: 650; font-size: var(--t-small); margin-bottom: 2px;
                 display: flex; align-items: center; gap: 7px; }
.wl-step-text { color: var(--muted); font-size: var(--t-small); line-height: 1.5; }
/* the locked (work-in-progress) tool reads as unavailable */
.wl-step-locked { opacity: .55; border-style: dashed; }
.wl-step-locked .wl-step-icon { filter: grayscale(1); }
.wl-step-lock { font-size: var(--t-micro); font-weight: 700; color: var(--muted);
                border: 1px solid var(--line); border-radius: var(--r-pill);
                padding: 0 6px; letter-spacing: .3px; }

/* footer: page counter + Back / Next / Got it */
.wl-footer { flex: none; display: flex; align-items: center; gap: 8px;
             padding-top: 12px; margin-top: 12px; border-top: 1px solid var(--line); }
.wl-progress { flex: 1; text-align: center; color: var(--muted); font-size: var(--t-micro);
               font-variant-numeric: tabular-nums; }

/* narrow: topic list becomes a horizontal strip and the whole body scrolls */
@media (max-width: 620px) {
  .welcome-body { overflow-y: auto; }
  .wl-layout { grid-template-columns: 1fr; }
  .wl-nav { flex-direction: row; overflow-x: auto; border-right: 0; padding-right: 0;
            padding-bottom: 8px; border-bottom: 1px solid var(--line); }
  .wl-nav-btn { flex: none; }
  .wl-content { overflow-y: visible; }
}

/* ===== TAB 3 — tightening torque ===== */
.tt-body { grid-template-columns: 300px 360px 1fr; }
@media (max-width: 1280px) { .tt-body { grid-template-columns: 1fr; } }

/* drilling-pattern drawing (SVG data-URI in an <img>) + legend */
.tt-diagram-wrap { display: flex; justify-content: center; padding: 10px 0 4px; }
.tt-diagram { width: min(78%, 280px); display: block;
              filter: drop-shadow(0 6px 16px rgba(0, 0, 0, .35)); }
.tt-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; justify-content: center;
             color: var(--muted); font-size: var(--t-micro); padding-bottom: 2px; }
.tt-legend .li { display: inline-flex; gap: 5px; align-items: center; }
.tt-legend .dot { width: 9px; height: 9px; border-radius: 50%;
                  border: 1px solid var(--line); flex: 0 0 auto; }

/* ---- hero: recommended torque + buffered min/max with bolt SFs ---- */
.tt-hero { display: flex; gap: 8px; margin: 2px 0 12px; align-items: stretch; }
.tt-rec {
  flex: 1.35; text-align: center; padding: 10px 12px; border-radius: var(--r3);
  border: 1px solid rgba(250, 204, 21, .55);
  background: linear-gradient(165deg, rgba(250, 204, 21, .12), rgba(250, 204, 21, .03));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08);
  display: flex; flex-direction: column; justify-content: center; gap: 1px;
}
.tt-rec .k { font-size: var(--t-micro); letter-spacing: .1em; text-transform: uppercase;
             font-weight: 700; color: var(--accent); }
/* THE readout of the whole app: mono, so it reads as a measured value rather
   than as interface text. See the font-role note in :root — mono is reserved
   for singular readouts; columns of numbers use tabular-nums sans instead,
   because mono digits are ~10-15% wider and the results table cannot spare it. */
.tt-rec .v {
  font-size: var(--t-hero); font-weight: 700; line-height: 1.05;
  font-family: var(--data); font-variant-numeric: tabular-nums;
  color: var(--accent);
}
.tt-rec .v small {
  font-size: var(--t-lead); font-weight: 650; color: var(--ink-2);
  font-family: var(--ui);
}
.tt-rec .sub { font-size: var(--t-micro); color: var(--muted); }
.tt-win-box {
  flex: 1; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--r3); padding: 8px 6px; text-align: center;
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
}
.tt-win-box .k { font-size: var(--t-micro); color: var(--muted); letter-spacing: .08em;
                 text-transform: uppercase; font-weight: 700; }
.tt-win-box .v {
  font-size: var(--t-head); font-weight: 700; line-height: 1.1;
  font-family: var(--data); font-variant-numeric: tabular-nums;
}
.tt-win-box .sf { font-size: var(--t-micro); font-weight: 650; }
.tt-win-box .sf.good { color: var(--pass); }
.tt-win-box .sf.warn { color: var(--warn); }
.tt-win-box .sf.bad { color: var(--err); }
.tt-win-box .sf.na { color: var(--muted); }
.tt-win-box .bind { font-size: var(--t-micro); color: var(--muted); }

/* The min/max boxes carry `lo` / `hi` modifiers from _hero() in
   callbacks/torque.py, but nothing ever styled them, so the two read as
   identical tiles — the user had to read the caption to tell the floor from the
   ceiling. Give each an edge on the side that faces OUTSIDE the safe window,
   matching the gauge directly beneath (which runs 0 on the left to max on the
   right). Accent-dim rather than a state colour: neither box is a warning, they
   are the two ends of the window. */
.tt-win-box.lo { border-left: 2px solid var(--accent-dim); }
.tt-win-box.hi { border-right: 2px solid var(--accent-dim); }
.tt-win-box.lo .k::before { content: "9  "; color: var(--accent-dim); }
.tt-win-box.hi .k::after  { content: "  A"; color: var(--accent-dim); }

/* wrapper around the source note; the note itself is .tt-src */
.tt-src-note { margin: var(--s2) 0 0; }
.tt-src-note:empty { display: none; }

/* the torque-window gauge: red = forbidden, amber = wrench-scatter buffer,
   green = safe at worst-case scatter; min/max/rec ticks below, the custom
   torque as a flag riding on top */
.tt-gauge { margin: 4px 2px 18px; }
.tt-bar {
  position: relative; height: 32px; border-radius: var(--r3);
  background: rgba(0, 0, 0, .38); border: 1px solid var(--line);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, .35);
  margin-top: 24px;  /* headroom for the custom-torque flag */
}
.tt-seg { position: absolute; top: 0; bottom: 0; }
.tt-seg:first-child { border-radius: var(--r2) 0 0 var(--r2); }
.tt-seg.low, .tt-seg.bad {
  background: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--err) 32%, transparent) 0 8px,
                                        color-mix(in srgb, var(--err) 14%, transparent) 8px 16px);
}
.tt-seg.high {
  background: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--err) 42%, transparent) 0 8px,
                                        color-mix(in srgb, var(--err) 22%, transparent) 8px 16px);
  border-radius: 0 var(--r2) var(--r2) 0;
}
.tt-seg.bad { border-radius: var(--r2); }
.tt-seg.buf {
  background: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--warn) 30%, transparent) 0 6px,
                                        color-mix(in srgb, var(--warn) 12%, transparent) 6px 12px);
}
.tt-seg.unk {
  background: repeating-linear-gradient(-45deg, rgba(255, 255, 255, .10) 0 6px,
                                        rgba(255, 255, 255, .03) 6px 12px);
}
.tt-seg.ok {
  background: linear-gradient(180deg, color-mix(in srgb, var(--pass) 70%, transparent), color-mix(in srgb, var(--pass) 34%, transparent));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .30),
              0 0 14px color-mix(in srgb, var(--pass) 30%, transparent);
}
.tt-marker { position: absolute; top: -22px; bottom: -5px; width: 0;
             transform: translateX(-50%); z-index: 2; }
.tt-marker .mt { position: absolute; top: 17px; bottom: 0; left: -1px; width: 2px;
                 background: var(--ink); box-shadow: 0 0 6px rgba(0, 0, 0, .6); }
.tt-marker .mv { position: absolute; top: -1px; left: 50%; transform: translateX(-50%);
                 background: var(--ink); color: var(--accent-ink); font-size: var(--t-micro); font-weight: 750;
                 padding: 1px 6px; border-radius: var(--r1); white-space: nowrap; }
.tt-marker.out .mt { background: var(--err); }
.tt-marker.out .mv { background: var(--err); color: var(--accent-ink); }
.tt-ticks { position: relative; height: 27px; }
.tt-tick { position: absolute; transform: translateX(-50%); text-align: center; }
.tt-tick .tv { font-size: var(--t-micro); font-weight: 700; color: var(--accent); }
.tt-tick .tc { font-size: var(--t-micro); color: var(--muted); letter-spacing: .08em;
               text-transform: uppercase; }
.tt-tick.rec .tri { width: 0; height: 0; margin: 1px auto 0;
                    border-left: 5px solid transparent;
                    border-right: 5px solid transparent;
                    border-bottom: 6px solid var(--accent); }
.tt-axis-ends { display: flex; justify-content: space-between;
                color: var(--muted); font-size: var(--t-micro); margin-top: -3px; }

/* the custom-torque row stays compact even in the wide results column;
   align-items:end bottom-aligns the "Use recommended" button with the input
   even though the input's label wraps to two lines and the button's doesn't */
.tt-choose { max-width: 460px; align-items: end; }
.tt-choose .btn { width: 100%; }

/* custom-torque all-limit check rows */
.tt-checks { display: flex; flex-direction: column; gap: 3px; margin: 4px 0 8px; }
.tt-check { display: flex; gap: 7px; align-items: baseline; font-size: var(--t-small); }
.tt-check .ic { font-weight: 800; width: 12px; flex: 0 0 auto; text-align: center; }
.tt-check.ok .ic { color: var(--pass); }
.tt-check.bad .ic { color: var(--err); }
.tt-check.na .ic { color: var(--muted); }
.tt-check .why { color: var(--muted); font-size: var(--t-micro); }

/* safety & modelling factor chips (transparency for the SF stack-up) */
.tt-factors { margin: 2px 0 10px; }
.tt-fac-lbl { font-size: var(--t-micro); letter-spacing: .08em; text-transform: uppercase;
              color: var(--muted); font-weight: 700; margin-bottom: 4px; }
.tt-fac-row { display: flex; flex-wrap: wrap; gap: 5px; }
.tt-fac { display: inline-flex; align-items: baseline; gap: 5px;
          background: var(--panel-2); border: 1px solid var(--line);
          border-radius: var(--r-pill); padding: 2px 9px; font-size: var(--t-micro); cursor: help; }
.tt-fac .k { color: var(--muted); }
.tt-fac .v { font-weight: 750; }
/* the net multiplier chip carries the verdict colour */
.tt-fac.good { border-color: color-mix(in srgb, var(--pass) 55%, transparent); background: color-mix(in srgb, var(--pass) 12%, transparent); }
.tt-fac.good .v { color: var(--pass); }
.tt-fac.warn { border-color: color-mix(in srgb, var(--warn) 55%, transparent); background: color-mix(in srgb, var(--warn) 13%, transparent); }
.tt-fac.warn .v { color: var(--warn); }
.tt-fac.bad { border-color: color-mix(in srgb, var(--err) 60%, transparent); background: color-mix(in srgb, var(--err) 14%, transparent); }
.tt-fac.bad .v { color: var(--err); }

/* database-propeller dropdown + "From platform…" button on one line */
.tt-prop-row { display: flex; gap: 6px; align-items: stretch; }
.tt-prop-row > :first-child { flex: 1; min-width: 0; }
.tt-prop-row .btn { flex: 0 0 auto; white-space: nowrap; }

/* torque/thrust source note — a prominent pill (icon + bold tag + one-liner)
   so the active source is impossible to miss; the propeller-limit worst case
   gets a loud amber treatment rather than blending into the grey */
.tt-src {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 11px; border-radius: var(--r2); margin: 5px 0 7px;
  border: 1px solid var(--line); background: var(--panel-2);
  box-shadow: var(--glass-edge);
}
.tt-src .ic { font-size: var(--t-head); line-height: 1; flex: 0 0 auto; }
.tt-src .txt { display: flex; flex-direction: column; gap: 0; min-width: 0; }
.tt-src .lbl { font-size: var(--t-micro); letter-spacing: .12em; text-transform: uppercase;
               color: var(--muted); font-weight: 700; }
.tt-src .tag { font-size: var(--t-small); font-weight: 800; line-height: 1.15; }
.tt-src .desc { font-size: var(--t-micro); color: var(--muted); line-height: 1.3; }
/* propeller-limit: loud amber (attention — usually needs reducing) */
.tt-src.prop { background: color-mix(in srgb, var(--tank) 15%, transparent); border-color: color-mix(in srgb, var(--tank) 60%, transparent); }
.tt-src.prop .tag { color: var(--tank); }
/* powerplant-limit: cyan (the realistic, recommended source) */
.tt-src.powerplant { background: color-mix(in srgb, var(--battery) 13%, transparent); border-color: color-mix(in srgb, var(--battery) 55%, transparent); }
.tt-src.powerplant .tag { color: var(--battery); }
/* custom: neutral slate (deliberate user choice, low emphasis) */
.tt-src.custom { background: color-mix(in srgb, var(--neutral) 12%, transparent); border-color: color-mix(in srgb, var(--neutral) 45%, transparent); }
.tt-src.custom .tag { color: var(--neutral); }

/* platform-powerplant picker modal */
.tt-platform-card { width: min(560px, 94vw); max-height: 84vh; }
.tt-platform-msg { padding: 4px 16px 0; }
.tt-plat-hdr { font-size: var(--t-micro); padding: 6px 9px; border-radius: var(--r2);
               line-height: 1.4; }
.tt-plat-hdr.ok { background: var(--panel-2); color: var(--muted); }
.tt-plat-hdr.warn { background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); }
.tt-plat-hdr .v { font-weight: 700; color: var(--ink); }
.tt-platform-body { display: flex; flex-direction: column; gap: 6px;
                    padding-top: 8px; }
.tt-plat-row {
  display: flex; flex-direction: column; gap: 2px; align-items: stretch;
  text-align: left; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--r3); padding: 8px 10px; cursor: pointer; font: inherit;
  color: var(--ink); transition: border-color var(--dur) ease, background var(--dur) ease;
}
.tt-plat-row:hover { border-color: var(--accent); background: rgba(250,204,21,.06); }
.tt-plat-row.disabled { opacity: .5; cursor: not-allowed; pointer-events: none; }
.tt-plat-main { display: flex; align-items: center; gap: 7px; }
.tt-plat-main .dot { width: 9px; height: 9px; border-radius: 50%;
                     display: inline-block; flex: 0 0 auto; }
.tt-plat-name { font-weight: 650; font-size: var(--t-small); }
.tt-plat-rot { font-size: var(--t-micro); color: var(--muted); margin-left: auto; }
.tt-plat-sub { font-size: var(--t-micro); color: var(--muted); }
.tt-plat-badge { font-size: var(--t-micro); font-weight: 700; margin-top: 1px;
                 letter-spacing: .02em; }
.tt-plat-badge.ok { color: var(--pass); }
.tt-plat-badge.no { color: var(--warn); }

/* ---- engine-catalog browser (powerplant popup) ---- */
/* the engine "Preset" field: Browse button + current-selection label */
.eng-cat-pickline { display: flex; align-items: center; gap: 8px; }
.eng-cat-open-btn { flex: 0 0 auto; white-space: nowrap; }
.eng-cat-open-icon { font-size: var(--t-micro); }
.eng-cat-current {
  font-size: var(--t-small); color: var(--ink); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.eng-cat-card { width: min(620px, 95vw); max-height: 86vh; }
/* search box: a bare .dash-input on this build (see UI conventions) */
.eng-cat-search { padding: 4px 16px 0; }
.eng-cat-search .dash-input { width: 100%; }
.eng-cat-body { padding-top: 8px; overflow: auto; }
.eng-cat-table {
  width: 100%; border-collapse: collapse; font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
}
/* sticky header is SOLID (rows scroll beneath a translucent header would bleed) */
.eng-cat-table thead th {
  position: sticky; top: 0; z-index: 2; background: var(--solid);
  border-bottom: 1px solid var(--line);
}
.eng-cat-th {
  padding: 6px 9px; text-align: center; cursor: pointer; user-select: none;
  color: var(--accent); font-weight: 650; font-size: var(--t-micro); white-space: nowrap;
  transition: background var(--dur) ease;
}
.eng-cat-th:first-child { text-align: left; }
.eng-cat-th:hover { background: rgba(250,204,21,.10); }
.eng-cat-th.sorted { color: var(--accent); }
.eng-cat-row { cursor: pointer; transition: background var(--dur) ease; }
.eng-cat-row td {
  border-bottom: 1px solid rgba(255,255,255,.06); padding: 5px 9px;
  text-align: center; white-space: nowrap;
}
.eng-cat-row td:first-child { text-align: left; }
.eng-cat-row td.rt-name { text-align: left; font-weight: 650; }
.eng-cat-row:hover td { background: rgba(250,204,21,.08); }
.eng-cat-row.current td { background: rgba(250,204,21,.14); }
.eng-cat-row.current td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.eng-cat-custom td { color: var(--muted); font-style: italic; }
.eng-cat-custom td.rt-name { color: var(--ink); font-style: normal; }
.eng-cat-empty { color: var(--muted); padding: 14px; font-style: italic; }
/* electric motor browser: wider (6 columns) + project-motor (★) rows */
.elec-cat-card { width: min(720px, 96vw); }
.cat-group {                                  /* Project motors / Catalog subheader */
  text-align: left; padding: 7px 9px 3px; font-size: var(--t-micro); font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
  background: var(--panel-2);
}
/* project rows aren't whole-row clickable (name = pick button, ✕ = delete) */
.cat-proj-row { cursor: default; }
.cat-proj-mfr { display: flex; align-items: center; gap: 6px; }
.cat-proj-star { color: var(--accent); font-size: var(--t-micro); }
.cat-proj-del { padding: 0 6px; font-size: var(--t-micro); line-height: 1.7; }
.cat-proj-pick {
  background: transparent; border: 0; padding: 0; margin: 0; cursor: pointer;
  color: var(--ink); font: inherit; font-weight: 650; text-align: left;
}
.cat-proj-pick:hover { color: var(--accent); text-decoration: underline; }
/* "Save as project motor" button in the electric section */
.dt-save-project-btn { width: 100%; margin-top: 8px; font-weight: 650; }
.dt-save-star { color: var(--accent); }

/* ---- accessibility & fallbacks ---- */
/* honour the OS "reduce motion" preference — kill lifts, fades and slides */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
/* browsers without backdrop-filter get solid panels (still translucent-tinted
   enough to show the backdrop at the edges) */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .card, .modal-card { background: rgba(18, 18, 24, .96); }
  .app-header { background: rgba(14, 14, 19, .97); }
}
