/* ============================================================================
 * nergyapp/nergyapp.css — the shell's own rules.
 * ----------------------------------------------------------------------------
 * Every colour, radius, shadow and font here is a token from theme.css, so both
 * themes are satisfied by construction rather than by a second stylesheet that
 * has to be kept in step. A literal in this file is a bug in theme.css.
 *
 * THE RULES THIS FILE IS WRITTEN TO. They are worth stating, because "make it
 * look professional" otherwise decays into taste:
 *
 *  1. ONE ACCENT, SPENT SPARINGLY. Blue means "this is the action" or "this is
 *     selected". A screen with three blue things has none.
 *  2. HIERARCHY BY WEIGHT AND SPACE, NOT BY LINES. Borders are the last resort
 *     for separating things; distance and surface do it first.
 *  3. LABELS ARE MONO AND SMALL-CAPS. Anything that names a field rather than
 *     saying something — a column head, a stage caption, an eyebrow — is set in
 *     the mono face, letterspaced. That is the whole of the instrument look, and
 *     it is what keeps prose reading as prose.
 *  4. EVERY INTERACTIVE THING HAS FOUR STATES, and focus-visible is never the
 *     one that was skipped.
 *  5. MOTION IS UNDER 200ms AND ONLY ON WHAT THE POINTER TOUCHED.
 *
 * Ordered as the screens are: shell → buttons → workspace table → notices →
 * dialogs → tab shell → the Planner → the execution tabs.
 * ==========================================================================*/

/* ── shell ───────────────────────────────────────────────────────────────── */

/* The app OWNS THE WINDOW. Nothing is centred in a 1120px column with the rest
   of the screen left empty — a workbench that uses two thirds of a monitor reads
   as a page about the product rather than the product. Line length is kept
   readable where it matters (prose, a form) by the elements that hold prose,
   never by starving the whole shell of width. */
#nergyapp-root {
  display:flex;
  flex-direction:column;
  min-height:var(--screen-h);
  /* WIDTH IS AUTO, deliberately. `width:100%` plus the rail's margin overflows the
     viewport by exactly the width of the rail, and the right-hand edge of every
     screen goes under it. Auto is "the room that is left", which is the ask. */
  margin:0;
  padding:34px clamp(20px,3.2vw,44px) 64px;
  gap:22px;
}

/* The project window is bounded by the viewport; the workspace is not. A tab
   that scrolls its own content needs a height to scroll inside, and a list of
   projects wants the page to grow instead. `project.js` sets the class, so the
   two behaviours are chosen rather than inferred. `overflow:auto` is the way out
   on a viewport too short for the panel's minimum. */
/* The window owns the viewport BELOW the top bar. 100vh was right when nothing sat
   above it; with a fixed bar it pushes the search box and its Stop off the bottom,
   which is the one place they must never be. */
/* The project window is the full window: no page padding, no scroll of its own
   at this level — the tab panel inside it is what scrolls. */
#nergyapp-root.na-window { height:var(--screen-h); min-height:0; overflow:hidden; padding:0; gap:0 }

.na-title { font-size:27px; font-weight:650; line-height:1.22; letter-spacing:-0.022em }
.na-subtitle { color:var(--muted); font-size:14px; line-height:1.55 }
.na-muted { color:var(--muted) }

/* RULE 3 — anything that NAMES a thing rather than saying something is mono,
   small, and letterspaced. One class, used everywhere, so the instrument look is
   a decision made once. */
.na-eyebrow, .na-table th, .na-stage-cap, .na-frame-cap, .na-card-kind,
.na-detail-h, .na-label {
  font-family:var(--mono); font-size:10.5px; font-weight:500;
  letter-spacing:.11em; text-transform:uppercase; color:var(--muted);
}

.na-sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

.na-workspace-head {
  display:flex; align-items:flex-end; justify-content:space-between; gap:16px;
  padding-bottom:20px; border-bottom:1px solid var(--border);
}

/* THE PROJECT WINDOW HAS NO HEAD OF ITS OWN. It used to carry a band across the
   top holding the way back and the project's name — a second horizontal rule
   directly under the top bar's, and 50px of a window whose whole job is to fit a
   bounded tab panel into the viewport. Both moved into the bar itself
   (`chrome.js`), which was already on screen and already outlives every tab:
   `.na-back` and `.na-project-title` are styled in chrome.css now. The tab strip
   is the first thing in the window. */

/* ── buttons ─────────────────────────────────────────────────────────────── */

/* Four states on every control, and `:focus-visible` is never the missing one.
   The press is a 1px drop rather than a scale — a button that grows on click
   moves the words next to it. */
.na-btn {
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  font:inherit; font-size:13.5px; font-weight:500; line-height:1;
  padding:9px 15px; border-radius:var(--radius);
  border:1px solid var(--border2); background:var(--surface-sunken); color:var(--muted3);
  cursor:pointer; white-space:nowrap;
  transition:background .13s ease, border-color .13s ease, color .13s ease,
             box-shadow .13s ease, transform .08s ease;
}
.na-btn:hover:not(:disabled) { background:var(--bg3); border-color:var(--border3); color:var(--text) }
.na-btn:active:not(:disabled) { transform:translateY(1px) }
.na-btn:disabled { opacity:.45; cursor:default }
.na-btn:focus-visible { outline:2px solid var(--blue); outline-offset:2px }

/* The ONE primary action on a screen. It is the only thing that carries the
   accent as a fill, which is what makes it findable without being loud. */
.na-btn-primary {
  background:var(--accent-fill); border-color:transparent; color:var(--on-accent);
  font-weight:600; box-shadow:0 1px 2px rgba(0,0,0,.18), inset 0 1px 0 rgba(255,255,255,.12);
}
.na-btn-primary:hover:not(:disabled) {
  background:var(--accent-fill); filter:brightness(1.07);
  box-shadow:0 2px 10px -2px var(--blue-glow), inset 0 1px 0 rgba(255,255,255,.14);
  color:var(--on-accent);
}
.na-btn-primary:disabled { box-shadow:none }

.na-btn-danger { background:var(--red); border-color:var(--red); color:var(--on-red) }
.na-btn-danger:hover:not(:disabled) { filter:brightness(1.08); color:var(--on-red) }

.na-btn-quiet { background:transparent; border-color:transparent; color:var(--muted2) }
.na-btn-quiet:hover:not(:disabled) { background:var(--bg3); border-color:var(--border2) }
.na-btn-danger-quiet { color:var(--red) }
.na-btn-danger-quiet:hover:not(:disabled) { background:var(--red-dim); border-color:var(--red); color:var(--red) }

.na-linkish {
  font:inherit; font-size:14.5px; font-weight:550; letter-spacing:-0.005em;
  background:none; border:0; padding:0; margin:0;
  color:var(--text); text-align:left; cursor:pointer;
  transition:color .12s ease;
}
.na-linkish:hover { color:var(--blue) }
.na-linkish:focus-visible { outline:2px solid var(--blue); outline-offset:3px; border-radius:3px }

/* ── the app rail ────────────────────────────────────────────────────────────
   Fixed down the left of every signed-in screen, BELOW the top bar. Icons only,
   with the name revealed on hover and focus — a rail wide enough for labels is a
   sidebar, and this is meant to cost 60px, not 200.

   IT IS A PANEL, NOT AN EDGE. It used to run from the top of the window to the
   bottom with a single hairline down its right side, which made it read as part
   of the window frame rather than as a thing in the app — and left the top bar
   indented by its width to get out of its way. Given its own four edges, a
   radius and the shadow that says it is lifted, it reads as what it is: one of
   the app's surfaces, the same kind of object as the sidebar on the other side
   of the screen. `--rail-gap` is the air around it, and the screens leave
   exactly that much plus its width (chrome.css). */
.na-apprail {
  position: fixed; z-index: 120;
  top: calc(var(--topbar-h) + var(--rail-gap));
  bottom: var(--rail-gap); left: var(--rail-gap);
  width: var(--rail-w);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 12px 0 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--elev-2);
}
.na-apprail[hidden] { display: none }
.na-apprail-group { display: flex; flex-direction: column; align-items: center; gap: 4px }
.na-apprail-group[hidden] { display: none }
/* The contextual group is set apart from the fixed one: these come and go with a
   project, and reading as one list with the others would be a promise they stay. */
.na-apprail-sections {
  position: relative; margin-top: 6px; padding-top: 12px; width: 100%;
}
.na-apprail-sections::before {
  content: ''; position: absolute; top: 0; left: 14px; right: 14px; height: 1px;
  background: var(--border2);
}
.na-apprail-foot { margin-top: auto }

.na-apprail-btn {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0; cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius);
  background: transparent; color: var(--muted);
  transition: color .13s ease, background .13s ease, border-color .13s ease;
}
.na-apprail-btn svg { width: 19px; height: 19px }
.na-apprail-btn:hover { color: var(--text); background: var(--bg3) }
.na-apprail-btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px }
/* Where you ARE: the accent, and a bar beside it. The bar is INSIDE the rail's
   own edge now — the rail is a panel with a radius, and a marker hung off its
   left edge would sit outside the border, in the gap, attached to nothing. */
.na-apprail-btn.is-on { color: var(--blue); background: var(--blue-dim) }
.na-apprail-btn.is-on::before {
  content: ''; position: absolute; left: -7px; top: 10px; bottom: 10px; width: 2px;
  border-radius: 2px; background: var(--blue);
}

/* The name, on hover and on focus. It is `aria-hidden` — the button already has
   the same string as its accessible name, and reading it twice is worse than not
   drawing it at all. */
.na-apprail-name {
  position: absolute; left: calc(100% + 10px); top: 50%; transform: translateY(-50%);
  padding: 5px 10px; border-radius: var(--radius-sm);
  background: var(--surface-raised); border: 1px solid var(--border2);
  box-shadow: var(--elev-2); color: var(--text);
  font-size: 12px; white-space: nowrap; pointer-events: none;
  opacity: 0; transition: opacity .12s ease;
}
.na-apprail-btn:hover .na-apprail-name, .na-apprail-btn:focus-visible .na-apprail-name { opacity: 1 }

/* Narrow: the rail goes to the bottom, as a bar. 60px of the width of a phone is
   a tenth of it, and the screens need every pixel of the rest. Still a panel —
   floated off all three edges, with the screen leaving room under itself for it
   (chrome.css) rather than running behind it. */
@media (max-width: 720px) {
  .na-apprail {
    top: auto; right: var(--rail-gap); left: var(--rail-gap);
    bottom: var(--rail-gap); width: auto; height: 56px;
    flex-direction: row; justify-content: center; gap: 6px; padding: 6px 10px;
  }
  .na-apprail-group { flex-direction: row }
  .na-apprail-sections { width: auto; margin: 0 0 0 6px; padding: 0 0 0 10px }
  .na-apprail-sections::before { top: 8px; bottom: 8px; left: 0; right: auto; width: 1px; height: auto }
  .na-apprail-foot { margin: 0 0 0 auto }
  .na-apprail-btn.is-on::before { left: 9px; right: 9px; top: -4px; bottom: auto; width: auto; height: 2px }
  .na-apprail-name { display: none }
}

/* ── the one-time nudge toward Help (`helpintro.js`) ─────────────────────────
   `.na-introduce` marks the Help rail item AND the Quick help pill at once —
   a glow rather than an outline, so it reads the same on the rail's square
   button and the pill's rounded one without two separate rules. */
@keyframes na-intro-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--blue-glow) }
  50% { box-shadow: 0 0 0 6px var(--blue-glow) }
}
.na-introduce { animation: na-intro-pulse 1.6s ease-in-out infinite }
/* `pointer-events: none` ON THE CARD, `auto` ON ITS OWN BUTTON — this floats
   over whatever screen happens to be underneath for up to a minute, and nothing
   there was laid out expecting a fixed card to be sharing its space. Without
   this a control that happens to sit where the card lands (a follow-up bar's
   button did, once) is unclickable until the nudge times out — a first-visit
   nicety has no business costing a real click. The "Got it" button opts back in
   so it is still the one thing on the card a reader can press. */
.na-intro-note {
  position: fixed; z-index: 310; pointer-events: none;
  left: calc(var(--rail-w) + var(--rail-gap) * 2); bottom: var(--rail-gap);
  max-width: 300px; padding: 12px 14px; display: flex; flex-direction: column; gap: 8px;
  background: var(--surface-raised); border: 1px solid var(--border2);
  border-radius: var(--radius-lg); box-shadow: var(--elev-3);
}
.na-intro-note-text { font-size: 12.5px; line-height: 1.5; color: var(--text); margin: 0 }
.na-intro-note .na-btn { align-self: flex-end; font-size: 12px; padding: 4px 10px; pointer-events: auto }
@media (max-width: 720px) {
  .na-intro-note { left: var(--rail-gap); right: var(--rail-gap); max-width: none; bottom: 76px }
}

/* ── the workspace table ─────────────────────────────────────────────────── */

.na-table-wrap {
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface); box-shadow:var(--elev-1);
  overflow:hidden;
}
.na-table { width:100%; border-collapse:collapse; font-size:14px; table-layout:auto }
.na-table th {
  text-align:left; padding:11px 18px;
  border-bottom:1px solid var(--border); white-space:nowrap;
  background:var(--surface-sunken);
}
.na-table td { padding:15px 18px; border-bottom:1px solid var(--border); vertical-align:middle }
.na-table tbody tr:last-child td { border-bottom:0 }
.na-table tbody tr { transition:background .12s ease }
.na-table tbody tr:hover { background:var(--bg3) }

.na-cell-title { width:30%; min-width:200px }
.na-cell-desc { color:var(--muted2); max-width:52ch; line-height:1.5 }
/* A timestamp is a measurement: mono, so a column of them lines up. */
.na-cell-when {
  color:var(--muted); white-space:nowrap; width:1%;
  font-family:var(--mono); font-size:11.5px;
}
/* NOT `display:flex` on a <td>. A flex cell leaves the table's own row layout and
   lands on top of its neighbours — the same trap the row class fell into. Spacing
   between the two buttons is a margin, which a table cell is happy with. */
.na-col-actions, .na-cell-actions { width:1%; white-space:nowrap; text-align:right }
.na-cell-actions .na-btn + .na-btn { margin-left:6px }
/* Row actions rest at low contrast and come up on hover or focus — present, not
   competing with the three columns that carry the information.
   .8 AND NOT LOWER. This is the resting state of a real control, so what it
   rests at is a contrast ratio, not a taste: at .62 these measured 3.03:1 in
   light and 3.54:1 in dark, under the 4.5:1 a body-sized label needs, and
   "you can hover it" is not an answer for someone reading the row. .8 clears
   AA in both themes and still sits back from the three columns beside it. */
.na-cell-actions .na-btn { opacity:.8; transition:opacity .12s ease }
.na-table tbody tr:hover .na-cell-actions .na-btn,
.na-cell-actions .na-btn:focus-visible { opacity:1 }

/* ── notices, loading, empty ─────────────────────────────────────────────── */

.na-notice {
  display:flex; align-items:center; gap:11px;
  padding:12px 15px; border-radius:var(--radius); font-size:13.5px;
  border:1px solid var(--border2); background:var(--surface); color:var(--muted3);
}
.na-notice-text { flex:1 1 auto }
.na-notice-action { flex:0 0 auto; padding:6px 10px }

.na-notice-refusal { border-color:var(--red); background:var(--red-dim); color:var(--text) }
.na-notice-mark {
  flex:0 0 auto; width:18px; height:18px; border-radius:50%;
  background:var(--red); color:var(--on-red); font-size:12px; font-weight:700;
  display:inline-flex; align-items:center; justify-content:center;
}
/* A statement of fact is not a banner. It is drawn as a quiet pill that takes the
   width of its sentence rather than a dashed rectangle across the whole panel —
   a full-bleed box reads as a warning, and "this is sample data" is not one. */
.na-notice-note {
  align-self:flex-start; width:auto; max-width:100%;
  padding:6px 13px; border-radius:999px; font-size:12.5px;
  border-color:var(--border); color:var(--muted); background:var(--surface-sunken);
}
.na-notice-note .na-notice-action { padding:3px 8px; font-size:12px }

.na-loading { display:flex; align-items:center; gap:10px; color:var(--muted); padding:28px 4px; font-size:14px }
.na-spinner {
  width:14px; height:14px; border-radius:50%;
  border:2px solid var(--border3); border-top-color:var(--blue);
  animation:na-spin .7s linear infinite;
}
@keyframes na-spin { to { transform:rotate(360deg) } }
@media (prefers-reduced-motion:reduce) { .na-spinner { animation-duration:2s } }

.na-empty {
  border:1px dashed var(--border2); border-radius:var(--radius-lg);
  padding:64px 24px; text-align:center; background:var(--surface-sunken);
}
.na-empty-title { font-size:18px; font-weight:600; margin-bottom:6px; letter-spacing:-0.01em }
.na-empty-body { color:var(--muted); max-width:52ch; margin:0 auto 20px }
/* The optional mark above the title (`icon` in `notice.js`'s `empty()`), a circle
   just big enough to hold a 20px glyph — the same stroke-and-currentColor recipe
   `rail.js` draws its own icons with, so a mark here reads as this app's rather
   than a borrowed one. */
.na-empty-icon {
  display:flex; align-items:center; justify-content:center;
  width:40px; height:40px; margin:0 auto 14px;
  border-radius:50%; background:var(--blue-dim); color:var(--blue);
}
.na-empty-icon svg { width:20px; height:20px }

/* ── dialogs ─────────────────────────────────────────────────────────────── */

.na-backdrop {
  position:fixed; inset:0; z-index:600;
  display:flex; align-items:center; justify-content:center; padding:24px;
  background:rgba(3,6,12,.6);
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
}
.na-dialog {
  width:100%; max-width:470px;
  background:var(--surface-raised); color:var(--text);
  border:1px solid var(--border2); border-radius:var(--radius-lg);
  padding:24px; box-shadow:var(--elev-3);
}
.na-dialog-title { font-size:17px; font-weight:600; margin-bottom:12px }
.na-dialog-body { display:flex; flex-direction:column; gap:8px; font-size:14px }
.na-dialog-lead { font-size:15px }
.na-dialog-muted { color:var(--muted); font-size:13px }
.na-dialog-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:20px }
.na-dialog.is-busy { cursor:progress }

.na-label { margin-top:8px }
.na-input {
  font:inherit; font-size:14px; width:100%;
  padding:10px 12px; border-radius:var(--radius);
  border:1px solid var(--border2); background:var(--surface-sunken); color:var(--text);
  transition:border-color .13s ease, box-shadow .13s ease;
}
.na-input::placeholder { color:var(--muted) }
.na-input:focus { outline:none; border-color:var(--blue); box-shadow:var(--ring) }
.na-textarea { resize:vertical; min-height:72px; line-height:1.5 }
.na-form-problem:empty { display:none }
.na-form-problem { margin-top:10px }

/* ── the tab shell ───────────────────────────────────────────────────────── */

/* ── the tab shell ───────────────────────────────────────────────────────────
   Filled chips in a recessed track, not text on a hairline. The strip itself
   sits a rung down the surface ladder (`--bg3`) and the active tab rises to
   the level of the panel it fronts (`--surface`), so it reads as "risen to
   page level" rather than merely underlined — the same ladder the rest of the
   shell already uses for depth, just applied here too. The accent moves from
   a flat underline to a gradient cap along the active tab's top edge. */
.na-tabshell { display:flex; flex-direction:column; flex:1 1 auto; min-height:0 }

/* THE BAR IS THE STRIP PLUS WHAT IS NOT A TAB. The tablist keeps its own
   scrolling — a project with nine tabs scrolls them — and the actions slot sits
   OUTSIDE that scroller, so a control about the whole window stays reachable
   instead of scrolling off the end with the tabs (`tabs.js` on why it is a
   sibling of the tablist rather than a child of it). The strip's background and
   its side padding move up here, so the two halves sit on one surface. */
/* The vertical padding stays on the two CHILDREN rather than moving up here, so
   the strip's own box still starts at the top of the window — it is the first
   thing in the project window and the suite measures that it is. */
.na-tabbar {
  display:flex; align-items:flex-end; gap:8px;
  padding:0 clamp(12px,2.4vw,24px);
  background:var(--bg3); flex:0 0 auto; min-width:0;
}
.na-tabbar-actions { flex:0 0 auto; padding:8px 0 6px; align-self:flex-end }
.na-tabbar-actions:empty { display:none }

.na-tabstrip {
  display:flex; align-items:flex-end; gap:4px;
  padding-top:8px; flex:1 1 auto; min-width:0;
  overflow-x:auto; scrollbar-width:none;
}
.na-tabstrip::-webkit-scrollbar { display:none }
.na-tab {
  position:relative;
  font:inherit; font-size:15px; font-weight:500;
  display:inline-flex; align-items:center; gap:6px;
  padding:9px 15px 11px;
  background:transparent; color:var(--muted); cursor:pointer;
  border:0; border-radius:var(--radius) var(--radius) 0 0;
  white-space:nowrap;
  transition:color .15s ease, background .15s ease, padding .15s ease;
}
.na-tab::after {
  content:''; position:absolute; left:12px; right:12px; top:0; height:2px;
  border-radius:2px 2px 0 0; background:transparent; transition:background .15s ease;
}
/* Pinned to the far right of the strip (`tabs.js`). It is last in the DOM
   already, so this only takes up the slack between it and the work it summarises;
   when the strip overflows and scrolls there is no slack to take, and it simply
   sits at the end, which is still the right place for it. */
.na-tab.is-atend { margin-left:auto }
/* A SECOND PINNED TAB SITS FLUSH AGAINST THE FIRST, not with a gap of its own.
   `margin-left:auto` on both would split the strip's free space between two
   gaps; one gap before the pinned GROUP is the picture "pushed across, not
   merely ordered" (Report/Reports) is meant to draw. */
.na-tab.is-atend + .na-tab.is-atend { margin-left:0 }
.na-tab:hover:not(.is-active) { color:var(--muted3); background:var(--bg4) }
.na-tab:focus-visible { outline:2px solid var(--blue); outline-offset:-3px }
.na-tab.is-active {
  color:var(--text); background:var(--surface); font-weight:600;
  padding-top:11px; padding-bottom:13px;
  box-shadow:0 -8px 16px -12px rgba(0,0,0,.28);
}
.na-tab.is-active::after { background:linear-gradient(90deg, var(--blue), var(--cyan) 130%) }
/* Hidden until the tab is reached, so the strip is not four permanently-lit ×
   marks wide at rest. `:focus-within` keeps it reachable from the keyboard —
   tabbing onto the close button itself reveals it the same way hover does. */
.na-tab-close {
  display:inline-flex; align-items:center; justify-content:center;
  width:20px; height:20px; border-radius:var(--radius-sm); font-size:15px; line-height:1;
  color:var(--muted); cursor:pointer; opacity:0;
  transition:background .12s ease, color .12s ease, opacity .12s ease;
}
.na-tab:hover .na-tab-close, .na-tab:focus-within .na-tab-close { opacity:1 }
.na-tab.is-active .na-tab-close { opacity:.5 }
.na-tab-close:hover { background:var(--bg4); color:var(--text) }

/* ── taking the project out of the window ────────────────────────────────────
   Past the tabs, at the end of the bar. It reads as a CONTROL rather than as a
   tab — a bordered chip on the recessed track, where a tab is a chip that rises
   out of it — because pressing it does not change what is on screen. */
.na-dl { position:relative; flex:0 0 auto }
.na-dl-btn {
  font:inherit; font-size:13px; font-weight:550; line-height:1;
  display:inline-flex; align-items:center; gap:7px;
  padding:7px 11px 7px 12px; border-radius:var(--radius);
  border:1px solid var(--border2); background:var(--surface-sunken); color:var(--muted3);
  box-shadow:var(--elev-1); cursor:pointer; white-space:nowrap;
  transition:color .13s ease, background .13s ease, border-color .13s ease, box-shadow .13s ease;
}
.na-dl-btn:hover:not(:disabled) { color:var(--text); background:var(--bg4); border-color:var(--border3) }
.na-dl-btn:focus-visible { outline:2px solid var(--blue); outline-offset:2px }
.na-dl-btn:disabled { opacity:.6; cursor:progress }
.na-dl.is-open .na-dl-btn { color:var(--text); border-color:var(--blue); box-shadow:var(--ring) }
/* The caret points down at the menu, and turns to point at the button when the
   menu is the thing on screen. */
.na-dl-caret {
  width:6px; height:6px; margin-left:1px;
  border-right:1.5px solid currentColor; border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translate(-1px,-1px); transition:transform .18s ease;
}
.na-dl.is-open .na-dl-caret { transform:rotate(-135deg) translate(-1px,-1px) }

.na-dl-menu {
  display:none; position:absolute; top:calc(100% + 7px); right:0; z-index:400;
  min-width:300px; padding:6px;
  background:var(--surface-raised); border:1px solid var(--border2);
  border-radius:var(--radius-lg); box-shadow:var(--elev-3);
}
.na-dl.is-open .na-dl-menu { display:block }
.na-dl-item {
  font:inherit; display:block; width:100%; text-align:left; cursor:pointer;
  padding:9px 10px; border:0; background:none; border-radius:var(--radius-sm);
  transition:background .12s ease;
}
.na-dl-item:hover { background:var(--bg3) }
.na-dl-item:focus-visible { outline:2px solid var(--blue); outline-offset:-2px }
.na-dl-item-label { display:block; font-size:13px; font-weight:600; color:var(--text); line-height:1.35 }
/* The second line is what stops a reader having to press one to find out what it
   does — see `exportmenu.js`. It is dim, not small enough to be unreadable. */
.na-dl-item-detail { display:block; font-size:11.5px; color:var(--muted); line-height:1.45; margin-top:2px }

/* What happened, under the button. Absolute so a refusal cannot make the tab bar
   taller and push every panel in the window down a line. */
.na-dl-said { position:absolute; top:calc(100% + 7px); right:0; z-index:399; width:340px }
.na-dl-said:empty { display:none }

/* A flex column all the way down, so a panel has a real height to fill rather
   than a percentage of something content-sized. The Planner tab needs it: its
   search box is pinned to the bottom of the panel and the work scrolls above it,
   and neither happens if the panel is only as tall as what is in it. */
.na-panels {
  flex:1 1 auto; min-height:0;
  display:flex; flex-direction:column;
  /* `--page-wash` was already drawn for exactly this — the page is not one flat
     colour — but it lives on `body::before`, fixed to the VIEWPORT, so its pools
     sit off the top edge and never reach a panel that starts below the topbar and
     the rail. Painted here instead, the same three gradients anchor to THIS box,
     so they actually fall across the canvas rather than past it. `var(--bg)` is
     the trailing layer — the flat colour these are a wash over, not a
     replacement for it. */
  background:var(--page-wash), var(--bg);
}
.na-panel {
  display:flex; flex-direction:column; flex:1 1 auto; min-height:0;
  padding:22px clamp(14px,2.4vw,28px) 18px;
}
/* `display` above outranks the UA sheet's [hidden] rule, so it is restored here —
   without this every panel is visible at once. */
.na-panel[hidden] { display:none }
.na-panel:focus-visible { outline:2px solid var(--blue); outline-offset:-2px }

.na-placeholder { padding:48px 8px; text-align:center }
.na-placeholder-title { font-size:16px; font-weight:600; margin-bottom:6px }
.na-placeholder-body { color:var(--muted); max-width:56ch; margin:0 auto }

/* ── the Planner tab ─────────────────────────────────────────────────────── */

/* THE COMPOSER BELONGS TO THE COLUMN IT IS ABOUT, AND ONLY TO THAT COLUMN.
   This was a flex column of work-then-box with the sidebar nested inside the
   work, which meant the box was laid out across the WHOLE panel: it ran under
   the sidebar, and centring it in the panel put it visibly off-centre under the
   thing it composes for. A grid says the real shape in one place —

       ┌───────────────┬────────┐
       │ flow          │  side  │   the work, and what the project was given
       ├───────────────┤        │
       │ search        │        │   the box, the width of the work
       └───────────────┴────────┘

   — with the side column spanning both rows, so it is a full-height panel and
   the box stops where it starts. Two layouts, one DOM: idle centres the pair on
   the panel, every other state pushes the box to the bottom with the work above
   it. The box is never rebuilt between them — what is typed in it survives the
   move. */
.na-plan {
  display:grid; position:relative;
  /* A THIRD, LEFT COLUMN FOR THE SCHEMATIC WINDOW, ahead of the work. Fixed
     within a range rather than `1fr`: it holds one figure at a fairly constant
     aspect, not reading text, so it has no reason to grow with the row the way
     `flow` does. Collapsed to nothing while idle (below) — there is nothing to
     draw before a request exists. */
  /* THE FIGURE COLUMN'S WIDTH IS THE FIGURE'S SIZE. Measured at the old 320px
     cap: the stage came out 294x310 and the drawing 294x265 — bound by WIDTH,
     with 45px of stage height it could not use. Height alone would have added
     letterbox rather than figure, so the cap goes up with it.
     AND IT COULD GO FURTHER ONCE THE SHELL WENT TO 85%. `--zoom` gives the
     layout ~18% more of these coordinates than the window has real pixels, so a
     400px column is 340 on screen — narrower than the 360 this branch first
     asked for, while leaving the work column MORE room than the pre-zoom 320
     did. What the three-column breakpoint protects is a row of ready steps at
     about 600px of work column; measured at the 1520px breakpoint this leaves
     it 846 of these coordinates (719 real pixels). */
  grid-template-columns:minmax(280px,400px) minmax(0,1fr) auto;
  grid-template-rows:minmax(0,1fr) auto;
  grid-template-areas:"fig flow side" "fig search side";
  column-gap:18px; row-gap:16px;
  flex:1 1 auto; min-height:0;
  /* How wide the reading is, and the gutter a loop's arrow lives in — read by the
     map and the run row inside `.na-plan-main`, and by the composer, which is a
     sibling of that column rather than a child of it. */
  --map-w:960px; --map-gut:30px;
}
/* The sample-data note is a fact about the page rather than part of the work, so
   it gets a row of its own across the top — and only when there is one, because
   an empty track still costs its gap. `planner.js` sets the class, so the two
   cannot disagree about whether the note exists. The schematic sits BELOW it,
   not beside it: the note is about the whole page. */
.na-plan.has-note {
  grid-template-rows:auto minmax(0,1fr) auto;
  grid-template-areas:"note note note" "fig flow side" "fig search side";
}
.na-plan > .na-notice-note { grid-area:note; margin:0 }
/* Idle centres the invitation and the box TOGETHER, and the sidebar does NOT
   move with them: two equal empty tracks above and below the pair centre it,
   while the side column still spans every row and stays the full-height panel it
   is in every other state. Centring all three together instead made the sidebar
   a short card floating in the middle of the screen, and made how tall it was a
   function of how long the question was. `row-gap:0` because an empty track
   still costs its gap, and two of them would push the pair off centre. The note
   is pinned to the corner: floating a footnote mid-panel makes it read as part
   of the invitation. */
.na-plan.is-idle, .na-plan.is-idle.has-note {
  grid-template-rows:1fr auto auto 1fr;
  /* The schematic column is hidden outright while idle (below) — nothing has
     been asked for yet, so there is nothing to draw — and its area is folded
     into `flow`'s here rather than named `fig`, which stays valid whether or
     not the (display:none) panel is even in the DOM at this width. */
  grid-template-areas:"top top side" "flow flow side" "search search side" "foot foot side";
  row-gap:0;
}
.na-plan.is-idle .na-plan-figcol { display:none }
.na-plan.is-idle .na-plan-search { margin-top:16px }
.na-plan.is-idle > .na-notice-note { position:absolute; inset:0 auto auto 0; margin:0 }
.na-plan-flow { grid-area:flow; display:flex; flex-direction:column; gap:14px; min-height:0 }
.na-plan:not(.is-idle) .na-plan-flow { overflow:auto; padding-bottom:12px }

/* ONE WIDTH FOR THE WHOLE APPROVAL, and it is narrow. The reading — what was
   understood, the workflow, and the button that starts spending money — is one
   column down the middle of the panel: the objective, the map and the run row all
   cap at `--map-w` and centre in whatever room is left, so their edges line up and
   none of them stretches into a ribbon when the sidebar is folded away.

   THE TWO NUMBERS ARE DECLARED ON THE TAB rather than here — see `.na-plan`
   above. The composer is a SIBLING of this column in that grid, not a child of
   it (it has to be, or it is as wide as the work and the sidebar together), and
   a variable set here would not reach it. Declared one level up, the box reads
   the same pair and lines up with the cards it is about. */
.na-plan-main { flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:16px }
/* Inset by the gutter as well, so the screen lines up with the CARD edges rather
   than with the region the cards sit in — the approval row is the approval of what
   is directly above it, and an approval that does not line up with what it approves
   reads as being about something else. */
.na-plan-main > .na-ps-screen, .na-plan-main > .na-run {
  width:100%; max-width:calc(var(--map-w) - 2 * var(--map-gut)); margin-inline:auto;
}
/* The right column: what the project was given, then what the step under the
   cursor is — what it will do, and the thread about it. It folds away — a reader
   working through the map does not always want a third of the width spent on a
   sidebar — and the control is a sibling of the panel it hides rather than a
   child, so it survives the fold. */
/* THE COLUMN'S WIDTH IS STATED, NOT INFERRED. `flex:0 0 auto` sizes a flex item
   from its CONTENT, and a flex container's intrinsic width is its items' own
   intrinsic contributions — so a `flex-basis` on the panel inside is not a
   promise about the column outside. Given a long enough line in the files panel
   or the step detail, this column measured 1026px of a 1164px row and left the
   map 138px to draw a workflow in. `max-width` is the promise: never wider than
   the fold plus the panel, whatever is written inside it. It is a maximum rather
   than a width because folding must still collapse the column to just the fold. */
.na-plan-sidecol {
  grid-area:side;
  display:flex; align-items:stretch; gap:8px;
  /* 480px is `planpanel.js`'s own MAX_SIDE, plus room for the fold button, the
     resize separator between it and the panel, and the two gaps around them. */
  flex:0 0 auto; min-width:0; max-width:540px; min-height:0;
}
/* A CONTAINER, AND ONLY JUST — glass, not a slab. This carried the rail's full
   panel treatment once (surface fill, border, radius, `--elev-2`) and it was
   taken away: beside a composer that is ITSELF a raised card, a bordered column
   of equal weight read as two surfaces competing for the same attention. But
   nothing is the wrong answer too — with no edge at all, an empty sidebar is a
   sentence and three buttons adrift in white space, and a reader cannot tell
   where the region they can drag the edge of, fold away, and drop files into
   actually STARTS. So it is drawn as the lightest thing that is still a
   container: `--glass` is faint enough that the page wash runs straight through
   it, `--glass-edge` is the whole of the shape, and there is no elevation at
   all — it is a region ON the page, not a card above it. The composer keeps the
   only raised surface on the screen, which is what the removal was protecting.

   THE FILE LIST KEEPS ITS OWN BOUNDARY (`.na-files-listcard`) and the empty card
   does not draw one (`.na-files-aside .na-empty`) — one edge around the region,
   one around the group inside it, and never a third around a single sentence.

   IT IS ALSO THE SCROLLER NOW. It used to live inside `.na-plan-flow` and scroll
   with the work; outside it, a long files list would grow the row it is in and
   push the composer off the bottom. One scrollport, on the column — the lists
   inside it keep their own caps (`.na-files-aside`). */
.na-plan-side {
  /* The default width; dragged or keyed via the separator beside it in
     `planpanel.js`, which sets `flex-basis` inline between MIN_SIDE/MAX_SIDE
     (240–480px) and overrides just that part of this shorthand. */
  flex:0 0 320px; min-width:0; max-width:480px; min-height:0;
  display:flex; flex-direction:column; gap:14px; overflow-y:auto;
  /* THE BOTTOM PADDING IS THE ROOM THE Quick ask PILL NEEDS. This column scrolls
     and the pill is fixed over the corner it scrolls into, so whatever is last in
     it — a file row's own controls now that the step panel is gone — came to rest
     underneath the pill and could not be pressed. Clearance here, not a smaller
     pill: the pill floats over every tab and only the things that scroll under it
     know where their own end is. */
  padding:14px 15px var(--fab-clear);
  /* CENTRED ON THE SAME AXIS THE COMPOSER IS. `.na-plan.is-idle`'s own grid
     centres the title-and-composer pair between two equal flexible tracks; this
     column spans the same full height beside it, so centring its own content
     the same way lands the ways-in and the file list at the composer's own
     height instead of pinned to the top of a column the composer is nowhere
     near. Harmless once a plan and a long file list are on screen too — a list
     taller than the column simply fills it, exactly as `flex-start` would have. */
  justify-content:center;
  /* The edge, and the little that is behind it. The blur is what keeps a
     translucent fill from reading as a smear of the wash: the same treatment the
     top bar uses (`chrome.css`), at a lower strength because this one sits still
     while the page moves under the bar. */
  background:var(--glass);
  border:1px solid var(--glass-edge);
  border-radius:var(--radius-lg);
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
}
.na-plan-side[hidden] { display:none }
.na-plan-sidecol.is-folded .na-plan-side { display:none }
/* What is in it: the files, and nothing else. There was a second half under this
   one — the step under the cursor, its account and a composer — and it is gone
   (`planpanel.js`).

   IT DOES NOT SHRINK. The column is a scrollport, and a flex item in a bounded
   column shrinks below its content by default — which does not make the content
   smaller, only the box around it. `flex:0 0 auto` keeps this at the height of
   what is in it and lets the column scroll. */
.na-plan-side-files {
  flex:0 0 auto; min-width:0; display:flex; flex-direction:column;
}

/* ── the scientific figure window (planfigure.js) ─────────────────────────────
   A DOCKED PANEL STYLED AS A WINDOW: a title bar of its own, not just the panel
   chrome `.na-plan-side` already has, so it reads as a distinct instrument
   beside the plan rather than another column of it — the way a CAD or
   simulation tool's own dockable views carry their own bars inside one
   workspace. `grid-area:fig` places it (`.na-plan` above); it is hidden
   outright while idle, and everything else here is what it looks like once a
   request exists to draw. */
/* HOW TALL THIS IS DEPENDS ON WHAT IS IN IT, and neither answer is "all of it".
   Holding one sentence — nothing asked for yet, or a refusal — it is the height
   of that sentence: a full-height column with a line of grey text floating in
   the middle reads as a panel that failed to load. Holding a FIGURE it takes a
   fixed FRACTION OF THE SCREEN, not the column: the column runs the height of
   the tab, and a picture that tall is a picture that has taken the tab over —
   the plan is what this screen is for. Just under half the screen is enough to
   see what was drawn and still leaves the reading beside it looking like the
   main thing; the whole figure, at a size its labels can be read at, is one
   click away. .42 rather than the .4 it started at, and the pairing is the
   point: the drawing is square, so height past what its width allows is empty
   plate. At .46 with a 400px column it measured 99px of stage nothing could
   reach; the two numbers are set together for that reason.
   `planfigure.js` puts the state on this element for exactly this rule. */
.na-plan-figcol {
  grid-area:fig; display:flex; min-width:0; align-self:start;
}
.na-fig-win {
  width:100%; max-height:420px; min-height:0; display:flex; flex-direction:column;
  background:var(--surface); border:1px solid var(--border2);
  border-radius:var(--radius-lg); box-shadow:var(--elev-2);
  overflow:hidden;
}
/* The floor is for a short window (a laptop in a split screen): below about
   this a figure is too small to tell what it is a figure OF. */
/* A FRACTION OF `--screen-h`, NOT OF `vh`, and the difference is not cosmetic
   since the shell was scaled to 85%: the page's own coordinate space is no
   longer the viewport's, so a raw `vh` here would size this window against a
   ruler nothing else on the screen uses. Every other height in this file moved
   to the variable for that reason; this one arrived on a branch written before
   it did. */
.na-plan-figcol.is-ready .na-fig-win {
  height:calc(var(--screen-h) * .42); min-height:280px; max-height:none;
}
.na-fig-bar {
  flex:0 0 auto; display:flex; align-items:center; gap:8px;
  padding:10px 13px; border-bottom:1px solid var(--border);
  background:var(--surface-sunken);
}
.na-fig-dot {
  width:7px; height:7px; border-radius:50%; flex:0 0 auto;
  background:var(--muted3); opacity:.55;
}
/* A dot that is genuinely doing something while a figure is drafted — the same
   idea as a running step's own marker on the map — and settles once it lands. */
.na-fig-win.is-loading .na-fig-dot {
  background:var(--blue); opacity:1; animation:na-fig-pulse 1.1s ease-in-out infinite;
}
.na-fig-win.is-ready .na-fig-dot { background:var(--blue); opacity:1 }
.na-fig-win.is-error .na-fig-dot { background:var(--muted3); opacity:.4 }
@keyframes na-fig-pulse { 0%,100% { opacity:.4 } 50% { opacity:1 } }
@media (prefers-reduced-motion:reduce) { .na-fig-win.is-loading .na-fig-dot { animation:none; opacity:1 } }
.na-fig-title {
  font-family:var(--mono); font-size:10.5px; font-weight:600;
  letter-spacing:.09em; text-transform:uppercase; color:var(--muted2);
}
.na-fig-body {
  flex:1 1 auto; min-height:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:10px;
  padding:12px; overflow:hidden;
}
.na-fig-note {
  margin:0; font-size:12.5px; line-height:1.55; color:var(--muted);
  text-align:center; max-width:32ch;
}
/* The second line of a refusal: that the plan is fine. Quieter than the reason
   above it ON PURPOSE — the reason is the news and this is the reassurance, and
   at the same weight the two competed and the panel read as two separate
   problems. `margin-top` rather than the body's `gap`, which is the spacing
   between the note and a stage and is too much between two lines of one note. */
.na-fig-note.na-fig-sub { margin-top:-4px; font-size:11.5px; color:var(--muted2) }
.na-fig-loading { display:flex; align-items:center; gap:9px; color:var(--muted); font-size:12.5px }
/* THE PLATE THE FIGURE SITS ON, and it is a BUTTON: the panel is narrow, the
   figure is labelled, and one click opens it at a size those labels can be read
   at (`planfigure.js`). A button rather than a click handler on the image so it
   is reachable by keyboard and announced as something that does something —
   `all:unset` would drop the box model with it, so the browser's own button
   styling is turned off field by field instead.
   No floor of its own: the WINDOW's height decides (above), and a minimum here
   fought it — a window at .46 of a short screen would have been pushed taller
   by its own contents. */
.na-fig-stage {
  flex:1 1 auto; align-self:stretch; min-height:0; width:100%;
  display:flex; align-items:center; justify-content:center;
  padding:0; margin:0; border:0; font:inherit; color:inherit;
  overflow:hidden; cursor:zoom-in;
  border-radius:var(--radius-sm); background:var(--surface-sunken);
}
.na-fig-stage:focus-visible { outline:2px solid var(--blue); outline-offset:2px }
/* `contain` rather than `cover`: a figure is only a figure whole — cropping one
   to fill the box cuts the labels off the edges, which are the half that makes
   it readable at all. The intrinsic size of an SVG would otherwise size this
   box, so both dimensions are capped at the box instead. */
.na-fig-img {
  max-width:100%; max-height:100%; width:auto; height:auto;
  display:block; object-fit:contain;
}
/* The enlarged view (`dialog.js`'s wide panel). The figure carries its own
   backdrop, so it is centred on the panel rather than stretched across it. */
.na-fig-full { display:flex; align-items:center; justify-content:center; height:100% }
.na-fig-fullimg {
  max-width:100%; max-height:100%; display:block;
  border-radius:var(--radius-sm);
}
.na-fig-cap {
  flex:0 0 auto; font-family:var(--mono); font-size:10px; letter-spacing:.04em;
  color:var(--muted); text-align:center;
}
/* A NAMED FIGURE READS AS A CAPTION, NOT AS A CONTROL. The line under a figure is
   normally the mono hint that it opens — machinery, deliberately quiet. Under a
   structure it is the compound's own name and formula, which is content: set in
   the reading face, at reading size, so "Phenol · C6H6O" is not typed like a
   keyboard shortcut. */
.na-fig-cap.na-fig-named {
  font-family:inherit; font-size:11.5px; letter-spacing:0; color:var(--muted2);
}
/* A structure is drawn on a TRANSPARENT ground (RDKit, `clearBackground=false`)
   so it takes the stage's own colour under either theme — which also means it
   has no plate of its own to hold it off the edges, hence the inset. */
.na-fig-img.na-fig-struct, .na-fig-fullimg.na-fig-struct { padding:10px }
@media (max-width:720px) {
  /* Stacked with everything else in `.na-plan` (below) — the schematic caps its
     own height rather than claiming a fixed fraction of a column that no
     longer exists. */
  .na-fig-win { max-height:380px }
}

/* DECLARED AFTER `.na-plan-figcol` ABOVE, AND THAT IS LOAD-BEARING. The rule
   this turns off sets `display:flex` at the same specificity, so at equal weight
   the later declaration wins — written above it, the column stayed displayed,
   and because the two-column template below leaves its `grid-area:fig`
   undefined it was auto-placed into TWO implicit tracks instead. The grid
   computed as four columns and the work column came out NARROWER than before
   the breakpoint existed. A media query is not a stronger selector.

   THREE COLUMNS NEED A WIDE WINDOW, AND UNDER ONE THE PLAN WINS. The chart lays
   steps that are ready together SIDE BY SIDE, because a row is the set of boxes
   nothing separates in time and stacking them draws a sequence the plan does not
   contain. Two boxes and their gap need about 600px of work column; with the
   files panel beside it, measured, that only leaves room for a third column past
   about 1500px — at 1460 the row folds. So below that the figure window stands
   down and this is the two-column tab it always was. The plan is what the tab is
   FOR; the figure is beside it, and beside is a thing only a wide window has room
   for.

   Above the phone breakpoint only: stacked (below 721px) the work column is the
   whole width, so the chart has its room back and the window is simply one more
   block in the scroll. */
@media (min-width:721px) and (max-width:1519px) {
  .na-plan {
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"flow side" "search side";
  }
  .na-plan.has-note {
    grid-template-areas:"note note" "flow side" "search side";
  }
  .na-plan.is-idle, .na-plan.is-idle.has-note {
    grid-template-areas:"top side" "flow side" "search side" "foot side";
  }
  .na-plan-figcol { display:none }
  /* Two columns again, so column 1 is the work — which is the span that rule has
     always meant (see its own comment where it is declared). */
  .na-plan > .na-ac-backdrop.na-ac-inline { grid-column:1 }
}

/* The idle state is the whole point of the tab, so it is given room and a real
   first line rather than a small heading over an empty panel. */
.na-plan-title {
  font-size:clamp(25px,2.7vw,36px); font-weight:650; line-height:1.14;
  letter-spacing:-0.03em; text-align:center; max-width:28ch;
}
.na-plan.is-idle .na-plan-main { align-items:center; justify-content:center; gap:10px }
/* The cap this used to carry is the grid's job now. It was there because the
   sidebar shared a row with the invitation and a project with thirty files
   attached would push the question and its box down onto the bottom edge; the
   column is its own scrollport in its own track now, so nothing it holds can
   move anything beside it. */
.na-plan.is-idle .na-plan-sub { text-align:center; font-size:16px; max-width:56ch }
.na-plan-sub { color:var(--muted); font-size:14.5px; max-width:70ch; line-height:1.6 }
.na-plan-h { font-size:17px; font-weight:600; letter-spacing:-0.01em }
/* ── the Planner screen (planscreen.js) ──────────────────────────────────────
   FIVE SECTIONS IN THE ORDER THE WORK WENT IN: what you asked for, what was
   settled, what it worked out, what will be done, whether to do it. Each is
   headed, and the headings are in one key so the screen reads as one document
   rather than as five panels that happen to be stacked.

   The request's card used to be three untitled paragraphs and the reasoning had
   nowhere at all; the rules below are what those paragraphs already used, kept
   because they were right about the typography and wrong only about the shape. */
.na-ps-screen { display:flex; flex-direction:column; gap:12px; text-align:left }

/* EACH SECTION IS A CARD, and that is not decoration. Five headed regions set as
   bare text on the page background read as one undifferentiated column — the
   headings become small grey noise between paragraphs, and a reader looking for
   "what did it assume" scans for it instead of jumping to it. A border and a
   surface give each one an edge to stop at.

   TIGHT, NOT CRAMPED. This was written tight on purpose — a plan is twelve steps
   and four assumptions and a dozen sentences, and the screen's job is to let
   somebody take all of it in and press a button. But the sizes it was tight AT
   were the ones a panel uses, and this is a column somebody reads a paragraph of
   reasoning in: 12.5px bullets 3px apart are not dense, they are a wall, and a
   reader who has to squint at what a run is about does not take it in faster.
   So the type is at reading size and the gaps are set from it. Folding a section
   shut (`planfold.js`) is still how a reader who has read one gets the space
   back — that is the control for density, rather than the type scale. */
.na-ps {
  display:flex; flex-direction:column; gap:9px;
  border:1px solid var(--border); border-radius:var(--radius);
  background:var(--surface); padding:13px 16px 15px;
  box-shadow:var(--elev-1);
}
/* DELIBERATELY NOT TOUCHED by the reading-size pass below. This size is its own
   decision, made where the heading tiers were set — 17px in `--text` here, and
   scaled again per surface by `.na-results-section` and `.na-rail` further down.
   That pass is about the BODY of a section (the table, the bullets, the
   reasoning), which sat at 12.5px under a 17px heading; changing this line as
   well would silently undo the tier it belongs to. Change the tiers together or
   not at all. */
.na-ps-title {
  font-size:17px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--text); font-weight:600;
}

/* ── FOLDING A SECTION INTO ITS OWN RIBBON (planfold.js) ──────────────────────
   Shut, a section is one line that still says what is in it: the heading, and
   beside it "4 questions" or the first line of what the planner understood. A
   disclosure that leaves a bare title behind has taken something away and given
   nothing back — the reader has to open it again to find out whether it was the
   one they wanted. */
.na-ps-head {
  display:flex; align-items:flex-start; gap:8px; width:100%;
  background:none; border:0; padding:0; margin:0; font:inherit; color:inherit;
  text-align:left; cursor:pointer;
}
.na-ps-head:focus-visible { outline:none; box-shadow:var(--ring); border-radius:5px }
/* SHRINKS AND WRAPS rather than pushing the panel wide — the title got twice
   the font-size it had, and "Choose Recommended Enrichment and Grounding" no
   longer fits one line in a 640px card. `flex:none` kept it at its single-line
   width and let it run off the edge instead of wrapping down. */
.na-ps-head .na-ps-title { flex:1 1 auto; min-width:0 }
.na-ps-head .na-ps-caret { margin-top:5px }
/* Drawn rather than typed, for the same reason every other glyph here is: a
   character borrowed from a font is a different size in every theme. */
.na-ps-caret {
  flex:none; width:0; height:0; margin-bottom:2px;
  border-left:7px solid transparent; border-right:7px solid transparent;
  border-top:8.5px solid var(--muted);
  transition:transform .12s ease;
}
.na-ps.is-shut .na-ps-caret { transform:rotate(-90deg) }
.na-ps.is-shut .na-ps-body { display:none }
/* SHUT, THE TITLE STILL WRAPS RATHER THAN CLIPPING — a title cut down to
   "CHOOSE RECOMMENDED ENRICHMENT A…" is not what it says, and the reader who
   folded it away is entitled to still be able to read the whole of what they
   folded. `flex:1 1 0%` gives it the row's width minus whatever the ribbon
   (below) actually needs, so it wraps the same way the open title does. */
.na-ps.is-shut .na-ps-head .na-ps-title { flex:1 1 0%; }
/* WHAT IS IN IT, said on the ribbon. Only when shut: open, the section says it
   at length directly underneath, and a summary above that is the same thing
   twice. */
.na-ps-ribbon { display:none }
/* The WIDTH here belongs to the rule above it: the ribbon yields the row so a
   long title can wrap, which is why it is `0 1 auto` and capped rather than
   greedy. Only the type is this pass's — it is the summary of a section a
   reader is deciding whether to open, and at 12px under a 17px title it read
   as a footnote on the heading rather than as what the section says. */
.na-ps.is-shut .na-ps-ribbon {
  display:block; flex:0 1 auto; max-width:45%; min-width:0;
  font-size:14px; color:var(--muted2); line-height:1.45;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.na-ps.is-shut { padding-bottom:12px }
.na-ps-body { display:flex; flex-direction:column; gap:9px }
/* "Taken as given", "What it cannot do here" — a region INSIDE a section, and so
   the bottom rung of the heading ladder the tiers above set: 17.5 on the Results
   tab, 17 here, 15 in the rail, 14 for a card's kind tag, and this. It is the
   only one that is not a section's own title, which is why it stays the quiet
   uppercase label it always was rather than growing to match them. */
.na-ps-subtitle {
  font-size:13px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--muted); font-weight:600; margin-top:8px;
}

/* ONE — the request, in the reader's own words and editable in place. A textarea
   rather than a paragraph with an Edit button: the thing you are allowed to change
   should look changeable, and one that reveals itself on a press is a control the
   reader has to discover. Its height is set from its own content (`planscreen.js`)
   and capped — a box tall enough for an essay tells the reader the wrong thing
   about what belongs in it, and the top of this screen is not where to spend
   space, because the plan is below it. */
.na-ps-asked, .na-ps-prompt {
  font:inherit; font-size:16px; line-height:1.6; color:var(--text);
  white-space:pre-wrap;
}
.na-ps-prompt {
  width:100%; max-height:200px; resize:vertical; overflow:auto;
  padding:7px 9px; border:1px solid var(--border2); border-radius:8px;
  background:var(--surface-sunken); box-sizing:border-box;
}
.na-ps-prompt:focus-visible { outline:none; border-color:var(--blue); box-shadow:var(--ring) }
/* Hidden until the text differs: a disabled button that is always there reads as
   broken, and the sentence beside it is about something that has not happened. */
.na-ps-reask-row { display:flex; align-items:center; gap:10px; flex-wrap:wrap }
.na-ps-reask-row[hidden] { display:none }
.na-ps-reask { flex:none }

/* TWO — the dialogue, as the table it always was. Two columns of short text with
   one relationship between them; a reader scanning for their own answer scans a
   column, which is precisely what a definition list would not let them do. */
.na-ps-qa { width:100%; border-collapse:collapse; font-size:15.5px; line-height:1.6 }
/* A question wraps to three or four lines in the left column, so the row's own
   padding has to clear the space BETWEEN two wrapped questions — at 8px the last
   line of one answer sat as close to the next question as to its own. */
.na-ps-qa th, .na-ps-qa td {
  text-align:left; vertical-align:top; padding:12px 18px 12px 0;
  border-bottom:1px solid var(--border);
}
.na-ps-qa tbody tr:last-child th, .na-ps-qa tbody tr:last-child td { border-bottom:none }
/* A row a reader is scanning is a row worth marking, the same way any other
   scannable table in this app says so (`.na-block-table`). Both cells light
   together — they are one row of one answer, not two independent facts. */
.na-ps-qa tbody tr:hover th, .na-ps-qa tbody tr:hover td { background:var(--surface-sunken) }
.na-ps-qa-q { font-weight:500; color:var(--muted2); width:38%; padding-right:22px }
.na-ps-qa-a { color:var(--text) }

/* THREE — the reading. The one part of this screen the reader did not write
   themselves, and so the one part that can be WRONG: set apart from their own
   words by a rule down the side, the same mark an answer already carried. */
.na-ps-understanding {
  font-size:16.5px; line-height:1.6; color:var(--text); white-space:pre-wrap;
  border-left:2px solid var(--blue); padding-left:12px;
}
/* A BULLET HERE IS A PARAGRAPH, not a word. Every one of these runs to two, three
   or four wrapped lines, and at a 3px gap the space between two bullets was
   smaller than the space between the lines inside one — so the list read as a
   single grey block and the eye had nothing to count. The gap has to beat the
   line box it separates, which at this size and leading is ~8px. */
.na-ps-bullets, .na-ps-gaps ul, .na-ps-workflow {
  margin:0; padding-left:20px; display:flex; flex-direction:column; gap:8px;
  font-size:15.5px; line-height:1.6; color:var(--muted3);
}
/* Each assumption is ONE line with its reason folded onto the end of it, quieter
   and in the same row. Two stacked blocks per assumption turned four assumptions
   into eight paragraphs, and the reason — which is the half a reader can actually
   argue with — read as stray grey text under an unrelated sentence. */
.na-ps-assumed dl {
  margin:0; display:grid; grid-template-columns:1fr; gap:4px;
  font-size:15.5px; line-height:1.6;
}
.na-ps-assumed dt { color:var(--text) }
.na-ps-assumed dd { margin:0 0 9px; color:var(--muted); padding-left:15px }
.na-ps-assumed dd::before { content:'— '; }
/* What the work needs that this platform cannot do. Amber, because it is the one
   thing on this screen that is a reason NOT to press the button below it. */
.na-ps-gaps ul { color:var(--amber) }

/* FOUR — the work in English, then the chart. Numbered, because the order is the
   claim being made and a bulleted list of it would be a set. */
.na-ps-workflow { list-style:decimal; gap:9px; color:var(--text) }
/* The chart fills the section card it is in — the card already holds it to a
   readable column, and a second centred narrower column inside it left the legend
   indented into the middle of the page with nothing above it to line up with. */
.na-ps.is-workflow > .na-flow { margin-top:6px; max-width:none }

/* FIVE — whether to do it. The one section with no heading: the button says what
   it is, and a label over it would be a caption on a verb. */
.na-ps.is-approval { gap:0; padding:0; border:none; background:none }

/* ── what has stopped and is waiting on a person (plangate.js) ────────────────
   Directly above the approval row, because it is the same act one step later: the
   row approves the plan, and this approves one step of it that nobody could
   review until the run reached it. Amber down the side — it is the only thing on
   the screen the run is not getting past. */
.na-gate { border-left:3px solid var(--amber) }
.na-gate[hidden] { display:none }
.na-gate-row { display:flex; flex-direction:column; gap:7px; padding-top:4px }
.na-gate-row + .na-gate-row { border-top:1px solid var(--border); margin-top:10px; padding-top:12px }
.na-gate-what { font-size:15.5px; font-weight:600; color:var(--text); line-height:1.45 }
.na-gate-asks { font-size:14.5px; color:var(--muted2); line-height:1.6 }
/* WHAT IT WILL ACTUALLY DO. Not clamped, unlike the same text on a box: this is
   the thing being signed off, and a brief cut short is a signature on a sentence
   the reader did not finish. */
.na-gate-brief {
  font-size:14.5px; line-height:1.6; color:var(--muted3);
  border-left:2px solid var(--border2); padding-left:12px;
}
.na-gate-note {
  font:inherit; font-size:14.5px; color:var(--text); width:100%; max-width:520px;
  padding:6px 9px; border:1px solid var(--border2); border-radius:8px;
  background:var(--surface-sunken); box-sizing:border-box;
}
.na-gate-note:focus-visible { outline:none; border-color:var(--blue); box-shadow:var(--ring) }
.na-gate-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top:2px }
/* ENRICHMENT AND ACTIONS FOLD LIKE ANY OTHER PLANNER SECTION (`planfold.js`),
   reusing its head-and-ribbon rather than a second implementation of
   collapsing. But nested a level inside the gate's own amber-edged panel, a
   full `.na-ps` box per band would be two borders and two backgrounds for one
   decision — flattened the same way `execrail.js`'s nested sections are: the
   fold is what is wanted here, not a second panel around it. */
.na-gate > .na-ps { border:0; border-radius:0; background:none; padding:0; box-shadow:none }
.na-gate > .na-ps.is-shut { padding-bottom:0 }
/* THE TWO BANDS. Separated by rule rather than by colour: "find things out" and
   "change something" are different decisions, not different severities, and an
   amber block around the second would read as a warning about work the reader
   asked for. The band's own rows keep the divider they already had. */
.na-gate-band + .na-gate-band { border-top:1px solid var(--border); margin-top:14px; padding-top:12px }
.na-gate-band .na-ps-title { margin-bottom:2px }
.na-gate-rows { display:flex; flex-direction:column; margin:8px 0 10px }
/* THE ONE QUESTION, given the weight of a question rather than of a caption —
   it is the only thing on this panel that is asking rather than telling. */
.na-gate-asked {
  font-size:15px; color:var(--text); line-height:1.55; font-weight:600;
  margin:8px 0 5px;
}
.na-gate-answer {
  font:inherit; font-size:14.5px; color:var(--text); width:100%; max-width:520px;
  padding:6px 9px; border:1px solid var(--border2); border-radius:8px;
  background:var(--surface-sunken); box-sizing:border-box; margin-bottom:4px;
}
.na-gate-answer:focus-visible { outline:none; border-color:var(--blue); box-shadow:var(--ring) }
/* THE BAND'S OWN CHART. It is the same chart the plan screen draws, so it needs
   no styling of its own beyond room to sit in and a scroll for a band wider than
   the panel — a chart that clipped would hide exactly the step furthest down the
   chain, which is the one a reader most needs to see before approving. */
/* WHAT THE REPORT WILL CONTAIN. A list rather than a chart: these are sections of
   one document in the order it reads, and an arrow between two headings would be
   claiming a dependency the outline never stated. */
.na-gate-parts { display:flex; flex-direction:column; gap:3px; margin:8px 0 12px }
.na-gate-part-row {
  display:flex; align-items:flex-start; gap:9px; padding:6px 3px;
  font-size:15px; color:var(--text); line-height:1.55; cursor:pointer;
}
.na-gate-part-row:hover { background:var(--surface-sunken); border-radius:6px }
.na-gate-part { margin-top:2px; flex:none; accent-color:var(--blue) }
.na-gate-chart { margin:8px 0 4px; overflow-x:auto; overflow-y:hidden }
.na-gate-chart .na-flow-canvas { min-width:min-content }
.na-plan-banner:empty { display:none }
.na-plan-banner { display:flex; flex-direction:column; gap:8px }

/* ACTIONS' OWN SECTION (`plangate.js`'s `actionsEl`) — a plain wrapper, so the
   band `fill()`s into it draws its full `.na-ps` box rather than the flattened
   one `.na-gate > .na-ps` gives a nested band. Amber down the side too, same
   reason `.na-gate` carries it: this is still the run waiting on a decision,
   just a decision in a section of its own now. */
.na-actions-wrap[hidden] { display:none }
.na-actions-wrap > .na-ps { border-left:3px solid var(--amber) }
/* THE ENRICHMENT BAND'S OWN BUTTON, set apart from every other decision on this
   panel: white rather than filled, so approving the whole recommended set reads
   as the lighter of the two calls a reader can make here, and bottom-right of
   its own band so it is the last thing read rather than competing with the
   rows above it for the reader's eye. */
.na-gate-actions-end { justify-content:flex-end }
.na-btn-enrich-go {
  background:var(--surface-raised); border-color:var(--amber); color:var(--amber);
}
.na-btn-enrich-go:hover:not(:disabled) {
  background:var(--surface-sunken); border-color:var(--amber); color:var(--amber);
  box-shadow:none; filter:none;
}

/* THE COMPOSER. The one control the whole tab exists to serve, so it is the one
   surface that is raised off the page, and it is the only thing besides the
   primary button that the accent touches — on focus, where it means "this is
   where you are typing". */
/* In the `search` area of the grid above — never the panel's full width, so it
   cannot run under the sidebar or reach the rail on the other side. And CAPPED TO
   THE CARD EDGES: the same `--map-w` less the same gutter the stage cards, the
   objective and the run row are inset by, so the box lines up exactly with the
   thing it is about rather than merely being centred somewhere near it. Centred
   within the column, not within the panel — a box centred on a two-column panel
   is a box off-centre under its own column. */
.na-plan-search {
  grid-area:search;
  flex:0 0 auto; width:100%;
  max-width:calc(var(--map-w) - 2 * var(--map-gut)); margin:0 auto;
  border:1px solid var(--border2); border-radius:var(--radius-lg);
  background:var(--surface-raised); padding:10px 12px 9px;
  box-shadow:var(--elev-2);
  transition:border-color .15s ease, box-shadow .15s ease;
}
.na-plan-search:focus-within { border-color:var(--blue); box-shadow:var(--elev-2), var(--ring) }
/* WHICH BOX THIS IS. The follow-up bar is drawn to this box's recipe deliberately
   (`.na-follow-box` below), so shape cannot tell them apart and a name has to. Both
   tags take Rule 3's type from `.na-eyebrow` — a thing that NAMES rather than says
   is mono, small and letterspaced — and add only the chip and the tone, so the two
   are one decision made twice rather than two. The Planner's is the accent one:
   this box is where work begins. `inline-block` is what puts it on its own line
   above a `display:block` input, with no row to wrap it in. */
.na-plan-tag, .na-follow-tag {
  display:inline-block; border:1px solid var(--border); border-radius:999px;
  padding:2px 8px; line-height:1.5;
}
.na-plan-tag { color:var(--blue); border-color:var(--blue); background:var(--blue-dim); margin:0 0 1px 2px }
.na-plan-row { display:flex; align-items:center; justify-content:space-between; gap:8px }
/* TYPED AT READING SIZE, NOT AT CHROME SIZE. This is the one box on the screen a
   reader writes their own sentences into, and it was set a step below the prose
   the rest of the panel is read at — smaller than the plan it produces, under a
   heading three times its height. The request is the thing being composed here,
   so it is typed at the size it will be read at. The follow-up bar keeps the same
   one-step-down relation to it it always had (`.na-follow-input` below). */
/* TWO ROWS AT REST, AND IT GROWS FROM THERE. The floor was 130px — five rows,
   a well in the middle of an empty screen with a cursor on its first line and
   four blank ones under it. Two rows is the same invitation without the wait:
   `min-height` is one line-height per row plus this padding, and past that the
   pixel height is `plansearch.js`'s, measured off what has actually been typed
   and capped where this `max-height` states, after which the text scrolls. */
.na-plan-input {
  display:block; width:100%; resize:none; border:0; background:transparent;
  padding:8px 6px 6px; line-height:1.55; min-height:67px; max-height:220px;
  overflow-y:auto; font-size:17px; color:var(--text);
}
.na-plan-input::placeholder { color:var(--muted) }
.na-plan-input:focus { outline:none; border-color:transparent; box-shadow:none }
/* WITH A PLAN ON SCREEN THE SAME BOX IS THE FOLLOW-UP WINDOW, and it is one line
   tall (`plansearch.js`'s `REVISE_ROWS` and the note over it). The two rows above
   are for the empty screen a project starts on; under a plan even the second one
   is a footer that pushes the chart the change is about out of the way of the
   change being typed. `min-height:0` because that resting height is a floor set
   here, and the row attribute alone cannot get under it. It grows with what is
   typed exactly as the box above does — the
   pixel height is `plansearch.js`'s, measured — and stops at the same cap this
   `max-height` states, after which the text scrolls inside it. */
.na-plan-search.is-revising { padding:7px 12px 7px }
.na-plan-search.is-revising .na-plan-input {
  min-height:0; max-height:132px; overflow-y:auto; padding:4px 6px 3px;
}
.na-plan-actions { display:flex; gap:8px; flex:0 0 auto; align-items:center }
.na-plan-go { padding-inline:20px }
.na-plan-aside:empty { display:none }
.na-plan-aside { font-size:12.5px; color:var(--muted2); padding:8px 6px 2px; line-height:1.5 }

/* THE FLOW CHART. A plan says what waits for what and why (`app/scpca/reason.py`),
   and this draws that: rows down the page, arrows between them, one box per step.

   IT REPLACED A RAIL OF SWARMS AND A SPINE OF STAGE CARDS, neither of which was a
   thing anybody reasoned about — a swarm is which folder an agent's file lives in,
   a stage is "whose inputs are ready at the same moment". The picture was a drawing
   of the scheduler's working.

   BOXES ARE HTML AND ARROWS ARE SVG. Text wrapping, focus rings, real buttons and
   the theme's own tokens are native in the first and hand-built in the second; a
   curve between two measured points is the reverse. */
/* A NARROW CENTRED COLUMN, not a panel-wide ribbon. A chart stretched across a
   monitor is a document; the work is read down the middle. */
.na-flow {
  display:flex; flex-direction:column; gap:14px;
  width:100%; max-width:var(--map-w, 720px); margin-inline:auto;
}

/* The pieces of work — the legend AND the statement of which tabs open after
   Execute. One list doing both, because two could disagree about one run. */
.na-flow-key { display:flex; flex-wrap:wrap; gap:6px 16px; list-style:none; padding:0 }
.na-flow-key-item { --rb-hue:var(--rb1); display:flex; align-items:center; gap:7px; font-size:13.5px; color:var(--muted) }
.na-flow-swatch {
  width:10px; height:10px; border-radius:3px; flex:none;
  background:hsl(var(--rb-hue) / 0.85);
}
.na-flow-key-why { color:var(--muted2) }

/* The canvas is the positioning context BOTH layers measure against: the arrows
   are drawn at coordinates taken from the boxes' own rectangles, so they have to
   share an origin with them. */
/* Room on the left for a loop's back-edge to run up in. Only a loop uses it, and
   a chart without one is not visibly indented for it. */
.na-flow-canvas { position:relative; width:100%; padding-left:26px }
/* THE GAP IS WHAT AN ARROW IS DRAWN IN, and it has to be tall enough to hold a
   word: a label on a line in 34px of space is a label overlapping both boxes. */
.na-flow-rows { position:relative; display:flex; flex-direction:column; gap:46px; z-index:1 }

/* A row is the set of boxes at one depth. Centred, because the chart is read down
   the middle; wrapping, because six boxes at one depth on a narrow panel is a row
   that would otherwise run off the side. */
/* EACH BOX IS ITS OWN HEIGHT. `stretch` made every box in a row as tall as the
   tallest, so a one-line step beside a step with a brief on it came out as a tall
   empty rectangle — the row looked tidy and the chart looked padded. */
.na-flow-row { display:flex; flex-wrap:wrap; justify-content:center; align-items:flex-start; gap:16px }

/* THE ARROWS. Behind the boxes and untouchable — they carry no information a
   pointer can get at, and a line that swallowed a click would be a line that
   stopped a delete button working.

   DRAWN TO BE SEEN, BUT NOT TO BE THE PICTURE. They were `--border2` — the hairline
   token, tuned for the edge of a card sitting on a surface a shade away from it —
   which on a chart is a line the eye has to hunt for and an arrowhead nobody finds
   at all. Corrected to a solid 1.6px in `--border3`, and that overshot the other
   way: on a fourteen-step plan the arrows read as the subject and the boxes as
   what they connect, which is backwards. The BOXES are the work.

   SO: DASHED AND FAINT. A dash spends less ink over the same distance than a solid
   line without shortening it, and the opacity drops the whole layer behind the
   boxes rather than lightening the stroke colour — a lighter colour is a different
   line in dark mode and the same line in light, where an alpha is the same
   relation to whatever is behind it in both.

   THE HEAD KEEPS MORE OF ITS WEIGHT than the line it ends, and that asymmetry is
   deliberate. The line says two boxes are related; only the head says which way
   round, and it is a few pixels of it — fading both equally would have faded the
   half that carries the meaning at the same rate as the half that carries the
   distance. */
.na-fe { position:absolute; inset:0; z-index:0; pointer-events:none; overflow:visible }
.na-fe-line {
  fill:none; stroke:var(--border3); stroke-width:1.4;
  stroke-dasharray:7 5; stroke-linecap:round; opacity:.5
}
/* A dependency this shell WORKED OUT from what a step reads, rather than one the
   plan stated. Both are dashed now, so the two claims are told apart by the LENGTH
   of the dash rather than by its presence: a derived edge is a dotted trail, a
   stated one a long dash. Still two patterns, both quieter than they were. */
.na-fe-line.is-derived { stroke-dasharray:2 4; stroke:var(--border2); opacity:.62 }
.na-fe-head { fill:var(--muted); opacity:.68 }
.na-fe-plate { fill:var(--surface) }
/* THE BACK-EDGE. Routed round the outside — straight up it would cross every row
   it spans — and drawn in the accent so it reads as a different KIND of arrow
   rather than as one more dependency. */
/* SOLID AND AT FULL WEIGHT, now that every other edge is dashed and faded. It was
   distinguished by colour alone; it is distinguished by colour, weight and being
   the one unbroken line on the chart — which is right for the only arrow that says
   the work comes back round rather than that it carries on. */
.na-fe-line.is-loop {
  stroke:var(--blue); stroke-width:1.5; stroke-dasharray:none; opacity:.85
}
.na-fe-label.is-loop { fill:var(--blue); font-size:10.5px }
.na-fe-label { font-family:var(--sans); font-size:11px; fill:var(--muted2) }

/* ── one box ──────────────────────────────────────────────────────────────────
   The shape says what kind of step it is. `--fb-hue` is set per box from its
   group, and `--rb-hue` resolves it to one of the six rungs of the ramp the rest
   of the app already uses (theme.css), so a chart and a chart of another plan
   read alike and no two groups take the same colour. */
.na-fb {
  --rb-hue:var(--rb1);
  /* WIDER THAN THEY WERE, because the chart is read down a column and a narrow
     box spends its height on wrapping. At 230px a label of five words took two
     lines and a summary took three; wider, the same text is one and two, which on
     a plan of a dozen sequential steps is several hundred pixels of chart.

     AND NO WIDER THAN THIS. Two boxes and the gap between them have to fit the row
     — the row is the set of steps nothing separates in time, and a row that wraps
     draws a sequence the plan does not contain. */
  position:relative; display:flex; min-width:180px; max-width:270px;
  transition:opacity .12s ease;
}
[data-hue="1"] { --rb-hue:var(--rb2) }
[data-hue="2"] { --rb-hue:var(--rb3) }
[data-hue="3"] { --rb-hue:var(--rb4) }
[data-hue="4"] { --rb-hue:var(--rb5) }
[data-hue="5"] { --rb-hue:var(--rb6) }

/* The face is what is painted. It carries the border for the rounded kinds and is
   the clipped fill for the two that cannot have one. */
.na-fb-face {
  flex:1; display:flex; min-width:0;
  background:var(--surface);
  border:1.5px solid hsl(var(--rb-hue) / 0.45);
  /* A LINE ON TOP — a heavier, more saturated top edge than the other three
     sides, so the group a box belongs to reads at a glance even before the
     colour band lower on the card. The extra top padding that goes with it is
     also why these cards read a touch taller than before. */
  border-top-width:3px;
  border-top-color:hsl(var(--rb-hue) / 0.85);
  border-radius:var(--radius);
  /* Extra top padding, clear of the "Remove this step" pill floating above the
     corner — the label starts well below it rather than brushing its edge. */
  padding:20px 10px 10px;
}
.na-fb-body { display:flex; flex-direction:column; gap:3px; min-width:0; width:100% }
/* WRAPS, and that is the whole reason a box is HTML. `break-word` rather than a
   truncation: a step's name is the one thing on the chart a reader cannot do
   without, and an ellipsis in the middle of it is a box that says nothing. */
/* A CHART LABEL STAYS UNDER THE PROSE BESIDE IT. It steps up with the rest of the
   Planner, but not to body size: a box is scanned, not read, and a chart set in
   the same type as the list above it stops looking like a diagram. `flowlay.js`
   computes no pixels — the boxes are measured in the document after they are
   drawn — so the layout follows the type rather than having to be retuned. */
.na-fb-label {
  font-size:14.5px; font-weight:600; color:var(--text); line-height:1.4;
  overflow-wrap:anywhere;
}
.na-fb-authored { font-size:12px; color:var(--muted2); font-style:italic }
/* CLAMPED TO TWO LINES. A plan of twelve steps whose boxes each carry four lines
   of prose is twelve hundred pixels of chart, and the shape — which is the thing a
   flow chart is FOR — stops being readable at a glance. The whole of it is one
   press away on `.na-fb-more`, right below — never only in a hover tooltip, which
   a touch screen cannot reach. */
.na-fb-brief {
  font-size:12.5px; line-height:1.5; color:var(--muted3);
  border-left:2px solid hsl(var(--rb-hue) / 0.5); padding-left:7px;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden
}
/* A REAL BUTTON, not a hover. Quiet — it is the third-order control on a box, under
   the label and the explanation it opens — and left-aligned under the clamped text
   it belongs to rather than centred under the whole box. */
.na-fb-more {
  align-self:flex-start; font:inherit; font-size:12px; font-weight:600;
  letter-spacing:.02em; color:var(--blue); background:none; border:0;
  padding:0; margin-left:9px; cursor:pointer;
}
.na-fb-more:hover, .na-fb-more:focus-visible { text-decoration:underline }
.na-fb-more:focus-visible { outline:none }
.na-fb-more-body { line-height:1.5; white-space:pre-wrap }
/* THE LAST LINE OF A BOX: the price at the leading edge, the state word after it,
   and the way to what it produced at the far end. `margin-left:auto` on the last of
   them and NOT `justify-content:space-between`, because two of the three come and
   go — an unrun box has no state word (`.na-fb-state:empty`) and an unpriced one no
   chip — and spreading whatever is left over the whole width would move the
   survivors around under the reader. */
.na-fb-foot {
  display:flex; align-items:center; gap:8px; width:100%; min-height:15px;
  margin-top:2px;
}
.na-fb-foot:empty { display:none }
.na-fb-foot .na-fb-results { margin-left:auto }
/* THE THREE CLIPPED AND ROUNDED SHAPES HAVE NO BOTTOM-RIGHT CORNER TO SIT IN — it
   is exactly the part a diamond, a hexagon and a stadium cut away — so there the
   two stack down the middle, which is where everything else in those shapes is. */
.na-fb.is-decision .na-fb-foot, .na-fb.is-approval .na-fb-foot,
.na-fb.is-output .na-fb-foot { flex-direction:column; align-items:center; gap:3px }
.na-fb.is-decision .na-fb-foot .na-fb-results,
.na-fb.is-approval .na-fb-foot .na-fb-results,
.na-fb.is-output .na-fb-foot .na-fb-results { margin-left:0 }
/* THE WAY OUT OF THE CHART, and it is drawn as one: a chip rather than a second
   quiet word, because it does not open something on the box the way `More` does —
   it takes the reader to another tab, and a control that MOVES you should not look
   the same as one that unfolds a sentence. Hidden until the step is DONE
   (`flowbox.showResults`), so a chart nothing has finished on carries none. */
.na-fb-results {
  font:inherit; font-size:10.5px; font-weight:700; letter-spacing:.02em;
  color:var(--blue); background:var(--blue-dim);
  border:1px solid hsl(var(--rb-hue) / 0.55); border-radius:999px;
  padding:1px 8px; cursor:pointer; line-height:1.5;
}
.na-fb-results:hover { background:var(--blue-glow); border-color:var(--blue) }
.na-fb-results:focus-visible { outline:none; box-shadow:var(--ring) }
.na-fb-results[hidden] { display:none }
.na-fb-state:empty { display:none }
.na-fb-state { font-size:11px; color:var(--muted2); font-weight:600 }
/* A CHIP, NOT PROSE. It has to be legible at a glance across a chart of twelve
   boxes while never competing with the label, so: tabular mono at the small size,
   quiet by default, and the box's own group hue behind it — which ties the figure
   to the band of work it belongs to. */
.na-fb-cost {
  font-family:var(--mono); font-size:10px; font-weight:600; letter-spacing:.02em;
  font-variant-numeric:tabular-nums; color:var(--muted2); white-space:nowrap;
  background:hsl(var(--rb-hue) / 0.12); border:1px solid hsl(var(--rb-hue) / 0.35);
  border-radius:99px; padding:1px 7px;
}
/* A RUNNING OR FINISHED BOX HAS SPENT IT. Past tense is not something CSS can say,
   so the chip stops asserting itself instead of pretending it is still a quote. */
.na-fb.is-running .na-fb-cost, .na-fb.is-done .na-fb-cost { opacity:.55 }

/* data — a cylinder. The cap is drawn rather than typed, for the same reason
   every other glyph in this app is. */
.na-fb.is-data .na-fb-face { border-radius:14px / 22px; padding-top:14px }

/* output — a stadium: what comes out at the end. */
.na-fb.is-output .na-fb-face { border-radius:999px; padding-inline:20px; text-align:center }
.na-fb.is-output .na-fb-body { align-items:center }

/* decision and approval — clipped, so they cannot carry a border. The OUTER
   element is clipped and painted as the edge; the face is clipped a fraction
   smaller and painted as the fill, which is the border. */
/* WIDER THAN THEY ARE TALL, because a clipped shape only has its middle band to
   put text in: the label is centred there and the corners are the shape. Both
   carry the label alone (`flowbox.js`), which is what makes that band enough. */
.na-fb.is-decision, .na-fb.is-approval {
  background:hsl(var(--rb-hue) / 0.5); min-width:240px; padding:1.5px;
}
.na-fb.is-decision { clip-path:polygon(50% 0, 100% 50%, 50% 100%, 0 50%) }
.na-fb.is-approval { clip-path:polygon(14% 0, 86% 0, 100% 50%, 86% 100%, 14% 100%, 0 50%) }
.na-fb.is-decision .na-fb-face, .na-fb.is-approval .na-fb-face {
  border:0; border-radius:0; text-align:center;
}
.na-fb.is-decision .na-fb-face { clip-path:polygon(50% 0, 100% 50%, 50% 100%, 0 50%); padding:24px 42px }
.na-fb.is-approval .na-fb-face { clip-path:polygon(14% 0, 86% 0, 100% 50%, 86% 100%, 14% 100%, 0 50%); padding:12px 26px }
.na-fb.is-decision .na-fb-body, .na-fb.is-approval .na-fb-body { align-items:center }

/* AUTHORED — a step that does not exist yet and will be WRITTEN for this request.
   Dashed across every shape, because a reader approving a plan needs to see at a
   glance which boxes are catalogue work and which the system is about to invent. */
.na-fb.is-authored .na-fb-face { border-style:dashed; background:var(--bg) }
.na-fb.is-authored.is-decision, .na-fb.is-authored.is-approval { background:hsl(var(--rb-hue) / 0.3) }

/* State. `planned` is the resting look and says nothing; the other three change
   the edge, so a run is legible from across the room. */
.na-fb.is-running .na-fb-face { border-color:hsl(var(--rb-hue)); box-shadow:0 0 0 3px hsl(var(--rb-hue) / 0.14) }
/* THE DONE TINT IS PAINTED ON THE SURFACE, NOT INSTEAD OF IT. A translucent fill
   alone replaced the opaque `--surface` every other box has, and the arrow layer
   sits BEHIND the boxes — so the dashed edges that pass under a finished box came
   through it and read as being drawn over the chart, which is the one thing the
   layering exists to prevent. The tint is the same 8%; it is laid over the surface
   as a flat gradient so the face stays opaque. */
.na-fb.is-done .na-fb-face {
  border-color:hsl(var(--rb-hue) / 0.9);
  background:linear-gradient(hsl(var(--rb-hue) / 0.08), hsl(var(--rb-hue) / 0.08)),
             var(--surface);
}
.na-fb.is-refused .na-fb-face { border-color:var(--red); border-style:dashed }
.na-fb.is-refused .na-fb-state { color:var(--red) }
/* WAITING ON A PERSON IS NOT A FAILURE. Amber and dashed: dashed because nothing
   has run, amber because it is the colour this shell already uses for the panel
   that asks (`.na-gate`) — the card and the row a reader answers it in should
   look like the same thing happening, not two unrelated events. Red here said
   the run had broken when it had asked a question. */
.na-fb.is-waiting .na-fb-face { border-color:var(--amber); border-style:dashed }
.na-fb.is-waiting .na-fb-state { color:var(--amber) }

/* The one control on a box, top-right, outside the face so a clipped shape
   (a diamond or a hexagon) does not clip it away. Labelled and always on
   screen while the step is removable — a bare "×" nobody reads, shown only on
   hover, made you find it before you could use it. */
.na-fb-drop {
  position:absolute; top:-14px; right:-8px; z-index:2;
  padding:2px 9px; border-radius:999px; line-height:1.4; white-space:nowrap;
  border:1px solid var(--border2); background:var(--surface); color:var(--muted2);
  font:inherit; font-size:10.5px; font-weight:600; letter-spacing:.02em;
  cursor:pointer;
}
.na-fb-drop:hover, .na-fb-drop:focus-visible { color:var(--red); border-color:var(--red) }
.na-fb-drop:focus-visible { outline:none }

/* RUN PLANNER sits exactly where Execute does — the same slot, one of them at a
   time — so the eye does not have to find a new control after an edit. */
.na-run-replan[hidden], .na-run-go[hidden], .na-run-stop[hidden], .na-run-resume[hidden] { display:none }


/* ── an agent's own account of itself, in sections (`plandetail.js`) ─────────
   What is left of a much larger block. These three classes used to dress a panel
   beside the map as well; that panel is gone, and the one reader now is the step
   card in an execution tab, which has a full-width frame to show the record in.
   `.na-detail-h` takes its type from RULE 3 at the top of this file. */
.na-detail-lead { color:var(--muted2); line-height:1.55; font-size:13px }
.na-detail-h { margin-top:13px }
.na-detail-p { color:var(--muted3); line-height:1.6; font-size:13px }

/* ── Quick ask: the same pill on the project list and in the project window ──
   Mounted by `project.js` as a sibling of the tab shell and by `workspace.js` as
   a sibling of the home grid — it floats over whatever is on screen rather than
   living inside it, which is what lets one control serve both screens. */
.na-assistant-toggle {
  position:fixed; right:20px; bottom:20px; z-index:300;
  display:flex; align-items:center; gap:6px; padding:10px 16px;
  border-radius:999px; border:1px solid var(--border2); background:var(--blue);
  color:#fff; font-size:13px; font-weight:600; box-shadow:var(--elev-2); cursor:pointer;
}
.na-assistant-toggle:hover { background:var(--blue2) }
/* THE DEFAULT HEIGHT LEAVES ROOM TO STILL BE DRAGGED BIGGER. 640px cleared
   `fit()`'s own floor (`assistantsize.js`'s MIN_H) but not its ceiling: on an
   ordinary 720px-tall viewport the ceiling is 720 - GUTTER_Y(120) = 600, so a
   640px default was already clamped to the ceiling before anyone touched the
   grip — the resize feature this panel exists for had nothing left to do. 520
   is bigger than the original 360×480 without eating the headroom the drag and
   the arrow keys need to prove they still work. */
.na-assistant {
  position:fixed; right:20px; bottom:76px; z-index:300;
  width:min(480px, calc(var(--screen-w) - 40px)); height:min(520px, calc(var(--screen-h) - 120px));
  display:none; flex-direction:column; background:var(--surface-raised);
  border:1px solid var(--border2); border-radius:var(--radius-lg); box-shadow:var(--elev-3);
  overflow:hidden;
}
.na-assistant.is-open { display:flex; }
/* WHILE THE CORNER IS BEING DRAGGED. The transition is off (a panel that eases
   into every pointer move lags behind the cursor) and text selection is off, so a
   drag that crosses the thread does not sweep it into a selection. */
.na-assistant.is-sizing { user-select:none; transition:none }
.na-assistant-head {
  display:flex; align-items:center; justify-content:space-between;
  gap:8px; padding:10px 12px; border-bottom:1px solid var(--border); flex:none;
}
.na-assistant-title { font-size:13px; font-weight:600; color:var(--text) }
.na-assistant-tools { display:flex; align-items:center; gap:6px }
.na-asst-new { font-size:11.5px; padding:3px 9px }
/* THE CORNER YOU DRAG, in the one corner that is free to move: this panel is
   pinned bottom-right above its own pill, so it grows up and to the left. Drawn as
   two quiet strokes rather than a block — it is a grip, not a control with an
   opinion — and it takes focus, because the arrow keys size the window too. */
.na-asst-grip {
  position:absolute; top:0; left:0; width:18px; height:18px; z-index:2;
  cursor:nwse-resize; border-top-left-radius:var(--radius-lg);
  background:
    linear-gradient(to bottom right, var(--border2) 0 1.5px, transparent 1.5px) 4px 4px / 8px 8px no-repeat,
    linear-gradient(to bottom right, var(--border2) 0 1.5px, transparent 1.5px) 4px 4px / 13px 13px no-repeat;
}
.na-asst-grip:hover, .na-asst-grip:focus-visible {
  background:
    linear-gradient(to bottom right, var(--blue) 0 1.5px, transparent 1.5px) 4px 4px / 8px 8px no-repeat,
    linear-gradient(to bottom right, var(--blue) 0 1.5px, transparent 1.5px) 4px 4px / 13px 13px no-repeat;
  outline:none;
}
.na-assistant-body { flex:1; min-height:0; overflow-y:auto; padding:10px 12px; display:flex; flex-direction:column; gap:8px }
/* THE PLANNER'S ANSWERS LAND IN THIS THREAD TOO (`assistant.say`), and they are
   quotes out of the plan on screen rather than sentences written for the
   occasion — so they name the part of the record they came from, quietly, under
   the reply. Nothing else in this thread has one. */
.na-asst-empty { font-size:12px; color:var(--muted); line-height:1.5 }
.na-asst-thread { display:flex; flex-direction:column; gap:10px; flex:1; overflow:auto }
.na-asst-msg { display:flex; flex-direction:column; gap:2px; font-size:12.5px }
.na-asst-msg.is-you { align-items:flex-end; text-align:right }
.na-asst-who { font-size:10px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted) }
.na-asst-text {
  line-height:1.5; color:var(--muted3); border:1px solid var(--border2);
  border-radius:var(--radius); background:var(--bg); padding:7px 9px; max-width:100%;
}
.na-asst-msg.is-you .na-asst-text { background:var(--bg3); color:var(--text) }
.na-asst-msg.is-pending .na-asst-text { color:var(--muted); font-style:italic }
.na-asst-from { font-size:10.5px; color:var(--muted); font-style:italic }
/* THE SEARCH SHAPE — the answer, then the specifics, then what was opened.
   `.na-asst-points` is up to four one-line facts under the reply, indented just
   far enough to read as a list rather than as more sentences; the sources are the
   follow-up bar's own chip recipe (`.na-follow-src`) at this panel's size, because
   a source is a source wherever it is shown. */
.na-asst-points {
  margin:4px 0 0; padding-left:16px; display:flex; flex-direction:column; gap:3px;
  font-size:12px; line-height:1.45; color:var(--muted2);
}
.na-asst-points li { list-style:disc }
.na-asst-srcs { display:flex; flex-wrap:wrap; gap:5px; margin-top:6px }
.na-asst-src {
  display:inline-block; max-width:100%; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; padding:2px 8px; border:1px solid var(--border2);
  border-radius:999px; background:var(--surface-raised); color:var(--muted3);
  font-size:11px; text-decoration:none;
}
.na-asst-src:hover { border-color:var(--blue); color:var(--text) }
.na-asst-box { display:flex; flex-direction:column; gap:6px; padding:10px 12px; border-top:1px solid var(--border) }
.na-asst-input { font-size:12.5px; resize:vertical; min-height:52px }
.na-asst-row { display:flex; gap:6px; justify-content:flex-end }
.na-asst-row .na-btn { font-size:12px; padding:4px 10px }
/* THE WAY OUT, always there rather than conditional — see the file's own note. */
.na-asst-contact {
  flex:none; padding:8px 12px; border-top:1px solid var(--border);
  font-size:11px; color:var(--muted); line-height:1.5;
}
.na-asst-contact a { color:var(--blue) }
.na-asst-contact a:hover { text-decoration:underline }
.na-asst-chart { margin-top:6px; width:220px }
/* The one thing a reply can carry besides words and a chart: the project it
   found, or the one it offers to start. A BUTTON, aligned with the reply rather
   than the box, because the reader is the one who navigates. */
.na-asst-do {
  align-self:flex-start; margin-top:6px; font-size:12px; padding:4px 10px;
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* ── the follow-up bar: on every tab that shows work ────────────────────────
   The Planner has a box across the bottom of it, and this is that box in the
   tabs that are not the Planner (`followbar.js`). `followdock.js` puts the tab's
   own view and this bar in one panel as two rows: the work takes the height
   that is left and scrolls inside it, the bar is as tall as it needs to be and
   never moves. A bar that scrolled away with the work would be a control the
   reader has to go and find. */
.na-tabwork { display:flex; flex-direction:column; flex:1 1 auto; min-height:0 }
/* THE RIGHT PADDING IS THE ROOM THE Quick ask PILL NEEDS — the same reservation
   `.na-frame-dock` makes, for the same reason: a bar this wide reaches the panel's
   own right edge, which is exactly where that pill floats, fixed, over everything. */
.na-tabdock { flex:0 0 auto; padding:12px 120px 0 0; min-width:0 }

/* WIDE — nearly the full panel, so the box to type the next question into is as
   easy a target as the work it is under, not a narrow strip lost under a wide
   report. Still `margin:0 auto` so a `max-width` that does bind (an ultra-wide
   monitor) keeps it centred rather than pinned to one edge. */
.na-follow {
  width:100%; max-width:1400px; margin:0 auto;
  display:flex; flex-direction:column; gap:6px;
}
/* The thread is BOUNDED. An answer can be three paragraphs, and a bar that grew
   with it would push the work it is about off the screen — so it scrolls inside
   its own height, and the newest turn is what is shown (`followbar.js` pins the
   scroll to the bottom on every draw). */
.na-follow-body { max-height:min(calc(var(--screen-h) * .46), 400px); overflow-y:auto; min-height:0 }
.na-follow-body:empty { display:none }
.na-follow-thread { display:flex; flex-direction:column; gap:12px }
.na-follow-thread:empty { display:none }
.na-follow-msg { display:flex; flex-direction:column; gap:3px; font-size:13px }
/* THE QUESTION AS A SUB-HEADER — left-aligned, the heading its answer answers,
   at the same weight/size the answer's own block titles (`.na-follow-btitle`)
   use so the two read as one document, not a chat. Extra space above a question
   (never the first) sets each new exchange off from the answer before it. */
.na-follow-q { font-size:13px; font-weight:600; color:var(--text); line-height:1.4; margin:0 }
.na-follow-msg.is-you:not(:first-child) { margin-top:6px }
.na-follow-text {
  line-height:1.55; color:var(--muted3); border:1px solid var(--border2);
  border-radius:var(--radius); background:var(--bg); padding:8px 10px; max-width:100%;
  white-space:pre-wrap;
}
/* AN ANSWER IS NOT A BUBBLE. It is blocks, drawn by the same `blockbody.js` as
   every card in an execution tab — so it takes the width it needs, and a table
   inside it is the table `.na-block-table` already styles rather than a paragraph
   of pipes squeezed into a speech balloon. */
.na-follow-answer {
  width:100%; border:1px solid var(--border2); border-radius:var(--radius);
  background:var(--bg); padding:10px 12px; display:flex; flex-direction:column; gap:6px;
}
.na-follow-btitle { font-size:13px; font-weight:600; color:var(--text) }
.na-follow-btitle + .na-follow-block { margin-top:-2px }
/* Wide content scrolls INSIDE the block. The bar is pinned to the bottom of a
   panel, and a table that widened it would widen the tab. */
.na-follow-block { min-width:0; overflow-x:auto }
.na-follow-block .na-block-p:last-child { margin-bottom:0 }
.na-follow-msg.is-assistant { align-items:stretch; text-align:left }
.na-follow-msg.is-pending .na-follow-text { color:var(--muted); font-style:italic }
/* A question back is the one message that is WAITING for something, so it is the
   one that is marked. Nothing else in this thread is coloured: an answer is not
   a state. */
.na-follow-msg.is-asking .na-follow-text { border-color:var(--blue); color:var(--text) }
.na-follow-msg.is-refused .na-follow-text { border-color:var(--red); color:var(--text) }
/* How the message was read, under what was done about it. Quiet — a reader who
   agrees with the reading never has to look at it. */
.na-follow-line { font-size:11.5px; color:var(--muted2); line-height:1.5; max-width:100% }
.na-follow-srcs { display:flex; flex-wrap:wrap; gap:6px; margin-top:2px }
.na-follow-src {
  display:inline-block; max-width:280px; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; padding:3px 9px; border:1px solid var(--border2);
  border-radius:999px; background:var(--surface-raised); color:var(--muted3);
  font-size:11.5px; text-decoration:none;
}
.na-follow-src:hover { border-color:var(--blue); color:var(--text) }

/* The box itself — the same recipe as `.na-plan-search`, deliberately: one
   idiom for "this is where you type", wherever in the window you are. LOWER
   than that recipe on purpose: this box is wide now, and a bar this wide at
   the old height read as a footer rather than a line to type one line into. */
.na-follow-box {
  border:1px solid var(--border2); border-radius:var(--radius-lg);
  background:var(--surface-raised); padding:5px 10px 5px; box-shadow:var(--elev-2);
  transition:border-color .15s ease, box-shadow .15s ease;
}
.na-follow-box:focus-within { border-color:var(--blue); box-shadow:var(--elev-2), var(--ring) }
/* The other half of the pair — the chip itself is `.na-plan-tag`'s, above. Muted
   rather than accent, and that is the whole distinction: this bar is about work
   that has already run, so it does not ask for the eye the way the box work starts
   in does. It sits on tabs where the result is what a reader came for. */
.na-follow-tag { color:var(--muted); background:var(--bg3); margin:0 0 1px 1px }
/* ONE ROW AT REST, and it grows from there (`followbar.js`'s `resize`) the same
   way the Planner's own box does in its revise mode — up to `FOLLOW_MAX_PX`,
   after which the text scrolls inside it rather than pushing the bar any
   taller. `min-height` is the floor that pixel height is measured up from. */
.na-follow-input {
  display:block; width:100%; resize:none; border:0; background:transparent;
  padding:3px 4px 2px; line-height:1.4; min-height:26px; max-height:132px;
  overflow-y:auto; font-size:15.5px; color:var(--text);
}
.na-follow-input::placeholder { color:var(--muted) }
.na-follow-input:focus { outline:none; border-color:transparent; box-shadow:none }
.na-follow-row { display:flex; align-items:center; justify-content:space-between; gap:8px }
.na-follow-hint { font-size:12px; color:var(--muted2); min-width:0 }
.na-follow-hint:empty { display:none }
.na-follow-actions { display:flex; gap:8px; flex:0 0 auto; align-items:center }
.na-follow-go { padding-inline:16px }

/* What a follow-up planner is a follow-up ON, above the five sections. It is a
   summary and it says so (`planscreen.js`); the plan itself was made against the
   whole record. Bordered where the other sections are not, because it is the one
   block on the screen that is about a DIFFERENT piece of work. */
.na-plan-prev {
  border:1px solid var(--border2); border-left:3px solid var(--blue);
  border-radius:var(--radius); background:var(--bg); padding:9px 12px;
  text-align:left;
}
.na-plan-prev-body { font-size:13px; line-height:1.6; color:var(--muted3) }

/* THE ASSISTANT'S FLOATING TOGGLE IS FIXED TO THE BOTTOM-RIGHT of the window, and
   the bar is centred in the panel — so on a wide screen they never meet, and on a
   narrow one the toggle lands squarely on Follow up. The bar yields, because it is
   the thing that moves with the layout: it keeps its own height and takes a gutter
   under it, rather than the toggle being repositioned per screen. */
@media (max-width:1000px) {
  .na-tabdock { padding-bottom:58px }
}

/* ── the quick check ─────────────────────────────────────────────────────────
   The dialog every initial request passes through. It holds two states in one
   frame: the traversal while the first call is out, then the questions in its
   place.

   It is the most important thing on the WORK it is over, and it is the only
   surface in the app that gets full elevation plus the accent edge — but the
   shell around that work (the top bar, the left rail, the files panel beside
   the work) stays up. The request behind it is real work in progress, and so
   is what those hold: what project this is, what has been attached to it.
   Hiding them made the dialogue read as a different screen instead of a
   question about the one underneath, and the escape hatch out of it looked
   like the app itself had dropped away. */
.na-ac-backdrop {
  position:fixed; inset:0; z-index:550;
  display:flex; background:var(--surface-raised);
  animation:na-ac-in .16s ease-out;
}
/* `mount`ed inside a tab (`autoask.js`) rather than onto the page: a grid item
   placed over the WORK, spanning every row it has so it reaches from the top of
   the tab to the bottom whichever state (idle, working, planned) the tab is in.

   COLUMNS 1-2, WHICH IS THE FIGURE AND THE WORK — everything except the files
   panel in column 3. This used to read `grid-column:1` when the tab was two
   columns and column 1 WAS the work; the figure window (`planfigure.js`) added a
   column in front of it, and a rule naming a column by number does not move when
   the grid does. The dialogue came up in a 300px slot with its questions wrapped
   one word per line. The files panel is still deliberately left out of the span:
   what this exists to avoid is the whole-tab takeover, not to cover everything.

   NOT `position:absolute`. An absolutely-positioned grid item's containing
   block is the padding box of the grid CONTAINER once its own insets are all
   definite (`inset:0` makes them so) — the grid-placement properties only set
   its static position, which offsets don't consult once they are given — so
   it fills the whole tab, files panel included, exactly the takeover this
   exists to avoid. A normal, in-flow grid item placed on the same lines has
   no such override: it is sized to that area and nothing else. */
.na-plan > .na-ac-backdrop.na-ac-inline {
  position:relative; z-index:50;
  grid-column:1 / 3; grid-row:1 / -1;
  width:100%; height:100%; min-width:0; min-height:0;
}
.na-ac-panel {
  position:relative; overflow:hidden;
  display:flex; flex-direction:column; gap:0;
  width:100%; height:var(--screen-h); height:var(--screen-dh);
  background:var(--surface-raised); color:var(--text);
  border:0; border-radius:0;
  animation:na-ac-rise .2s cubic-bezier(.2,.8,.3,1);
}
.na-ac-inline .na-ac-panel {
  height:100%;
  border:1px solid var(--border); border-radius:var(--radius);
  box-shadow:var(--elev-2);
}
.na-ac-panel::before {
  content:''; position:absolute; inset:0 0 auto; height:2px; background:var(--accent-line); z-index:3;
}
@keyframes na-ac-in { from { opacity:0 } }
@keyframes na-ac-rise { from { opacity:0; transform:translateY(10px) } }
@media (prefers-reduced-motion:reduce) {
  .na-ac-backdrop, .na-ac-panel { animation:none }
}

.na-ac-top {
  position:sticky; top:0; z-index:2;
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:18px 28px; background:var(--surface-raised); border-bottom:1px solid var(--border);
}
.na-ac-title { font-size:16px; font-weight:600; letter-spacing:-0.015em; color:var(--text) }
.na-ac-close { width:32px; height:32px; font-size:13px; border-radius:var(--radius-sm) }

.na-ac-body {
  flex:1 1 auto; padding:32px 28px 28px; overflow-y:auto; min-height:0;
  display:flex; flex-direction:column; align-items:center;
}
/* Every direct child — the traversal loader, and later the wizard's own root —
   shares one centred reading column, so the two states of this dialog line up
   without either being wrapped in an extra element just to be centred. */
.na-ac-body > * { width:100%; max-width:1000px; }
/* The wizard's own root fills whatever height the body has to give, so its
   actions bar sits at the foot of the screen on a short one-question screen
   instead of floating right under the card with the rest of the page empty —
   and still scrolls with everything above it once a bundled screen is taller
   than the viewport. */
.na-ac-body > .na-ac { flex:1 1 auto; min-height:0 }

/* THE WAIT, IN BOTH PLACES IT IS WATCHED — the quick-check dialog, and the
   Planner panel once the dialog has closed and `/plan` is out.

   The loader's own layout puts the readout in a 190px column BESIDE the graph.
   That is right in a wide panel and wrong in a card: the graph is squeezed to a
   sliver and the title truncates mid-word. Stacked, the graph gets the full width
   of whatever it is in and the readout gets a line of its own.

   ONE RULE, TWO HOSTS, deliberately. These were two copies for about an hour and
   the second one was already drifting. */
.na-ac-wait { display:block }
.na-ac-wait .na-hg,
.na-plan.is-traversing .na-hg {
  flex-direction:column; gap:12px; padding:0; min-height:320px;
  border:0; background:transparent;
}
/* Full dialog width reads as a letterbox once the readout is stacked below the
   graph instead of beside it — the Planner's own traversal (below) is already
   capped for the same reason; this brings the quick-check dialog's loader in
   line with it. */
.na-ac-wait .na-hg { max-width:560px; margin:0 auto; }
.na-ac-wait .na-hg-cw,
.na-plan.is-traversing .na-hg-cw {
  flex:1 1 auto; min-height:250px;
  border:1px solid var(--border); border-radius:var(--radius);
  background:var(--surface-sunken); overflow:hidden;
}
.na-ac-wait .na-hg-side,
.na-plan.is-traversing .na-hg-side { flex:0 0 auto; gap:7px }
.na-ac-wait .na-hg-head,
.na-plan.is-traversing .na-hg-head { align-items:center }
/* The phase, as the instrument label it is. */
.na-ac-wait .na-hg-title,
.na-plan.is-traversing .na-hg-title {
  font-family:var(--mono); font-size:10.5px; font-weight:500;
  letter-spacing:.12em; text-transform:uppercase; color:var(--blue);
  border:1px solid var(--border2); border-radius:999px; padding:4px 11px;
  overflow:visible; text-overflow:clip;
}
.na-ac-wait .na-hg-pct,
.na-plan.is-traversing .na-hg-pct { font-size:14px }
.na-ac-wait .na-hg-phase,
.na-plan.is-traversing .na-hg-phase { font-size:12.5px; opacity:1; color:var(--muted3) }
.na-ac-wait .na-hg-micro, .na-ac-wait .na-hg-clock,
.na-plan.is-traversing .na-hg-micro, .na-plan.is-traversing .na-hg-clock { font-size:11px }
.na-ac-thinking {
  margin-top:14px; text-align:center; font-size:13px;
  color:var(--muted); font-style:italic;
}

/* the screens */
.na-ac { display:flex; flex-direction:column; gap:18px }
.na-ac-head { display:flex; flex-direction:column; gap:3px }
.na-ac-step {
  font-family:var(--mono); font-size:10.5px; font-weight:500;
  letter-spacing:.13em; text-transform:uppercase; color:var(--blue);
}
.na-ac-why { font-size:13.5px; color:var(--muted2); line-height:1.55; max-width:74ch }
.na-ac-why[hidden] { display:none }

.na-ac-qs { display:grid; gap:18px; align-items:start }
.na-ac-qs.is-cols-2 { grid-template-columns:repeat(2,minmax(0,1fr)) }
.na-ac-qs.is-cols-3 { grid-template-columns:repeat(3,minmax(0,1fr)) }
/* Each question is its own card — a bordered block rather than a run of
   options loose on the page — so a five-column screen still reads as five
   distinct questions instead of one wall of chips. */
.na-ac-q {
  display:flex; flex-direction:column; gap:12px; min-width:0;
  padding:16px 16px 18px; border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface-sunken);
}
.na-ac-q-text { font-size:15px; font-weight:600; line-height:1.4; letter-spacing:-0.015em; color:var(--text) }
.na-ac-qs.is-cols-2 .na-ac-q-text, .na-ac-qs.is-cols-3 .na-ac-q-text { font-size:13.5px; font-weight:600 }
.na-ac-opts { display:flex; flex-direction:column; gap:7px }

/* An option IS the answer, and exactly one per question is highlighted at all
   times — so what will be sent is visible rather than implied. The selected
   one is the only filled thing in the dialog.

   A FULL-WIDTH ROW, not a pill. A rounded-999px capsule reads fine for one
   short line, but wraps to two or three and the corner radius stops matching
   the shape it is drawn around — the text visibly spills past where the pill
   still thinks its edge is. A modest, constant border-radius holds its shape
   at any number of lines, which is the only thing a chip whose text length is
   not under this app's control can rely on. */
.na-ac-opt {
  font:inherit; font-size:13.5px; line-height:1.45; text-align:left;
  display:block; width:100%; box-sizing:border-box;
  padding:11px 15px; border-radius:var(--radius); cursor:pointer;
  border:1px solid var(--border2); background:var(--surface-raised); color:var(--muted3);
  transition:border-color .13s ease, background .13s ease, color .13s ease, box-shadow .13s ease;
}
.na-ac-opt:hover:not(:disabled) { color:var(--text); border-color:var(--border3); background:var(--bg3) }
.na-ac-opt:focus-visible { outline:2px solid var(--blue); outline-offset:2px }
.na-ac-opt:disabled { opacity:.45; cursor:default }
.na-ac-opt.is-on {
  color:var(--blue); background:var(--blue-dim);
  border-color:var(--blue); font-weight:600;
  box-shadow:none;
}
/* Always last, after whatever was offered: the user's own answer. */
.na-ac-opt.is-other { color:var(--muted) }
.na-ac-opt.is-other::before { content:'✏  '; opacity:.7 }
.na-ac-other-wrap[hidden] { display:none }
.na-ac-other { margin-top:2px; font-family:var(--sans); font-size:13.5px }
.na-ac-hint { font-size:12px; color:var(--muted); line-height:1.5 }

/* ── the depth screen ──────────────────────────────────────────────────────
   A DIAL RATHER THAN CHIPS, because what is being chosen is a quantity: one
   axis, more at one end. Ten options in the shape above would be a wall the eye
   cannot rank.

   What it buys and what it costs sit UNDER the dial and update as it moves, so
   the figures are never more than a glance from the control. They are the
   service's own arithmetic (`GET /scpca/depth`) and nothing here writes one. */
.na-ac-depth { display:flex; flex-direction:column; gap:10px }
.na-ac-depth-row { display:flex; align-items:center; gap:14px }
.na-ac-depth-range { flex:1; accent-color:var(--blue); cursor:pointer; min-width:0 }
.na-ac-depth-range:focus-visible { outline:2px solid var(--blue); outline-offset:4px }
/* The number is the one filled thing on the screen, like the selected chip
   above it: what will be sent is stated, never implied. */
.na-ac-depth-n {
  font-family:var(--mono); font-size:13px; font-weight:600; color:var(--blue);
  white-space:nowrap; min-width:8ch; text-align:right;
}
.na-ac-depth-buys { font-size:13.5px; line-height:1.55; color:var(--text) }
.na-ac-depth-costs { font-size:13px; line-height:1.55; color:var(--muted2) }
.na-ac-depth-note { max-width:74ch }
.na-ac-depth-note[hidden] { display:none }
/* Both ends named, so a reader who has not touched the dial still knows what
   moving it would mean. */
.na-ac-depth-ends {
  display:flex; justify-content:space-between; gap:12px;
  font-size:11.5px; color:var(--muted); padding-top:2px;
}

.na-ac-status { font-family:var(--mono); font-size:11.5px; letter-spacing:.04em; color:var(--blue) }
.na-ac-status[hidden] { display:none }

.na-ac-actions {
  position:sticky; bottom:0; z-index:2;
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  flex-wrap:wrap; margin-top:auto; padding:16px 0 4px; background:var(--surface-raised);
  border-top:1px solid var(--border);
}
.na-ac-right { display:flex; gap:8px; align-items:center }
/* Two lines: what pressing it does, and what it costs. */
.na-ac-enough {
  flex-direction:column; align-items:flex-start; gap:1px;
  padding:7px 12px; text-align:left; white-space:normal;
}
.na-ac-enough-lead { font-size:13px; font-weight:550; color:var(--muted3) }
.na-ac-enough-sub { font-size:11.5px; color:var(--muted) }
.na-ac-enough:hover .na-ac-enough-lead { color:var(--text) }
.na-ac-back[hidden] { display:none }
/* AND THE SAME FOR "I'M READY TO PLAN", which the depth screen hides. Both are
   `.na-btn`s and a `.na-btn` is a flex box, so `[hidden]` alone loses to the
   display rule and the button stays on screen — which on that screen would be a
   way past the price without seeing it. */
.na-ac-enough[hidden] { display:none }
.na-ac-go { padding-inline:22px }

@media (max-width:820px) {
  .na-ac-top, .na-ac-body { padding-left:16px; padding-right:16px }
  .na-ac-qs.is-cols-2, .na-ac-qs.is-cols-3 { grid-template-columns:minmax(0,1fr) }
  .na-ac-actions { flex-direction:column-reverse; align-items:stretch }
  .na-ac-right { justify-content:flex-end }
}

/* the clarifier */
.na-clarify {
  display:flex; flex-direction:column; gap:10px;
  border:1px solid var(--border2); border-radius:var(--radius);
  background:var(--bg); padding:18px;
}
.na-clarify-field { display:flex; flex-direction:column; gap:6px }
.na-clarify-q { font-size:14px }
/* The answers a question resolves to, where the server could name them all. Quiet and
   above the box rather than in place of it — they fill the box, so an answer nobody
   thought of is still a keystroke away. */
.na-clarify-choices { display:flex; flex-wrap:wrap; gap:6px }
.na-clarify-choice {
  padding:3px 10px; font-size:13px; cursor:pointer;
  color:var(--text); background:var(--bg3);
  border:1px solid var(--border); border-radius:999px;
}
.na-clarify-choice:hover { border-color:var(--blue); color:var(--blue) }
.na-clarify-choice:focus-visible { outline:none; box-shadow:var(--ring) }
.na-clarify-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:4px }

/* the system, proposed before the map (systemcard.js) — same shell as the clarifier,
   since it is the same kind of screen: a reasoning pass put to the user before the
   expensive half starts. */
.na-syscard {
  display:flex; flex-direction:column; gap:10px;
  border:1px solid var(--border2); border-radius:var(--radius);
  background:var(--bg); padding:18px;
}
.na-syscard-field { display:flex; flex-direction:column; gap:6px }
.na-syscard-field label { font-size:12.5px; color:var(--muted) }
.na-syscard-name, .na-syscard-summary { width:100%; resize:vertical }
.na-syscard-facets { display:flex; flex-direction:column; gap:8px }
.na-syscard-facets .k { font-size:12.5px; color:var(--muted); display:block; margin-bottom:2px }
.na-syscard-facets ul { margin:0; padding-left:18px; font-size:13.5px }
.na-syscard-status { font-size:12.5px; color:var(--muted) }
.na-syscard-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:4px }

/* EXECUTE. The point where the expensive half starts, so it is set apart from
   the map above it by a surface rather than by one more rule across the panel. */
/* The approval sits under the map and is capped to the same width (`--map-w`, set
   on `.na-plan-main`), so its left AND right edges line up with the stage cards it
   is approving. */
.na-run {
  display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  margin-top:6px; padding:16px 18px;
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface-sunken);
}
.na-run-status { font-size:12.5px; color:var(--muted); flex:1 1 220px; line-height:1.5 }
.na-run-go, .na-run-resume { padding-inline:22px }
.na-run-tip { flex:1 0 100%; font-size:12px; color:var(--muted); line-height:1.4 }

/* ── folding a side away ─────────────────────────────────────────────────────
   One control, two users: the Planner's detail panel and the execution tabs'
   left and right frames. It lives OUTSIDE what it hides — a control inside a
   collapsed panel is a control nobody can reach — and the chevron points where
   pressing it sends the panel. */
.na-frame-fold {
  flex:0 0 22px; align-self:flex-start; margin-top:2px;
  display:flex; align-items:center; justify-content:center;
  width:22px; height:26px; padding:0; cursor:pointer;
  background:var(--surface-sunken); border:1px solid var(--border);
  border-radius:var(--radius-sm); color:var(--muted);
  transition:color .13s ease, background .13s ease, border-color .13s ease;
}
.na-frame-fold[hidden] { display:none }
.na-frame-fold:hover { color:var(--text); background:var(--bg3); border-color:var(--border3) }
.na-frame-fold:focus-visible { outline:2px solid var(--blue); outline-offset:2px }
.na-frame-chev {
  width:7px; height:7px; border-right:1.5px solid currentColor; border-bottom:1.5px solid currentColor;
  transition:transform .18s ease;
}
.na-frame-fold-left  .na-frame-chev { transform:rotate(135deg) }
.na-frame-fold-right .na-frame-chev { transform:rotate(-45deg) }
.na-frame-fold-left.is-collapsed  .na-frame-chev { transform:rotate(-45deg) }
.na-frame-fold-right.is-collapsed .na-frame-chev { transform:rotate(135deg) }

/* ── the Results tab: one section per piece of work ──────────────────────── */

/* The list itself scrolls, the same flex-fill-then-scroll shape `.na-panel`
   gives every tab — the sections inside it are stacked, not laid side by
   side, so there is no viewport-height chain left for them to inherit. */
.na-results {
  display:flex; flex-direction:column; gap:16px;
  flex:1 1 auto; min-height:0; overflow-y:auto;
}
/* Plain fold, no ribbon (`execgroups.js` passes an empty `summary` on purpose —
   a section here never closes, so there is nothing a shut one would otherwise
   be reporting the loss of). */
.na-results-section { flex:0 0 auto }
.na-results-section > .na-ps-body { padding-top:2px }
/* The piece-of-work heading (DATA, KNOWLEDGE, OTHER STEPS…) reads as a label at
   the base 10px `.na-ps-title` size everywhere else it is used — but here it is
   the only heading a whole section of the run's work sits under, so it needs to
   read as one, not be mistaken for a caption: bigger, bold, and `--text` rather
   than the muted grey every other `.na-ps-title` uses — `--text`, not a literal
   black, because a reader can switch to the dark theme (`chrome.js`), where a
   hard-coded black would vanish; `--text` is already near-black on the light
   theme (`index.html`'s own default) and stays readable on the dark one. The
   caret beside it is scaled up too —
   `align-items:baseline` on `.na-ps-head` sinks a border-triangle to sit on the
   text baseline rather than centred on the line, so scaling the title alone
   left it tucked low and barely reading as bigger; `align-self:center` here
   fixes that for this row
   without touching every other fold's arrow, and the triangle itself is bigger
   again, closer to the height of the title it sits beside. */
/* The FOLLOW-UP section (`execresults.js`) borrows this heading and caret exactly
   — same 17.5px / 700 / `--text` title and scaled arrow as the run's own sections
   — WITHOUT wearing `.na-results-section`, which names the run's piece-of-work set
   and is read as that set by name elsewhere. */
.na-results-section > .na-ps-head .na-ps-title,
.na-follow-section  > .na-ps-head .na-ps-title { font-size:17.5px; font-weight:700; color:var(--text) }
.na-results-section > .na-ps-head .na-ps-caret,
.na-follow-section  > .na-ps-head .na-ps-caret {
  align-self:center;
  border-left-width:12px; border-right-width:12px; border-top-width:14px;
  margin-bottom:0;
}
/* THE CONVERSATION GROWS INLINE with the sections here — it is not the pinned,
   bounded strip it is when it falls back under a whole tab, so its own scroll and
   height cap come off and the column that holds the sections does the scrolling
   (mirrors `.na-frame-centre-thread` for the swarm view). A little air under the
   heading sets the first question off from it. Hidden until it holds an exchange,
   so a run nobody has asked about carries no empty fourth section. */
.na-follow-section > .na-ps-body { padding-top:6px }
.na-follow-section .na-follow-body { max-height:none; overflow:visible }
.na-follow-section:not(:has(.na-follow-msg)) { display:none }
/* One tier further down still: the small uppercase kind tag on every card
   (WRITTEN, RUNNING…) — the same family of uppercase label as the two
   headings above it, just the smallest of the three. 1.4x the base 10px,
   between the rail heading's 1.5x and the base it started at. */
.na-results-section .na-card-kind { font-size:14px }
/* A BOUNDED HEIGHT, not `flex:1 1 auto` — that only resolves against an
   ancestor with a definite height, and inside a scrolling list of several of
   these there is no "remaining space" to fill. Each section's own three
   frames (`frames.js`) still scroll within this box exactly as they would
   fill a whole tab; there just is not a whole tab's height to give them. */
.na-results-body { height:min(calc(var(--screen-h) * .72), 760px); min-height:0; display:flex }
.na-results-body > .na-exec { flex:1 1 auto; min-height:0 }

/* ── the Report tab: a generated PDF, in-app ─────────────────────────────── */

.na-report-viewer { display:flex; flex-direction:column; flex:1 1 auto; min-height:0 }
/* THE FRAME IS NEVER THE ONLY THING IN THE TAB. A browser that will not display a PDF
   in a page leaves the frame blank and says nothing about it, so the bar above carries
   the way out — Download, which re-signs its link on every press — and the room for the
   message that appears when a load does not arrive (`reportviewer.js`). */
.na-report-bar {
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding:8px 12px; border-bottom:1px solid var(--border); background:var(--surface-raised)
}
.na-report-name { margin:0; font-weight:500; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.na-report-status { flex:1 1 220px; min-width:0 }
.na-report-status:empty { flex-basis:0 }
/* The bar is one row; a notice in it is a line of text in that row, not the padded
   card it is when it stands alone under a form. */
.na-report-status .na-notice { padding:0; border:0; background:none; font-size:12px }
.na-report-acts { display:flex; gap:8px; margin-left:auto }
.na-report-frame { flex:1 1 auto; min-height:0; width:100%; border:0; background:var(--surface) }

/* ── the execution tabs: three frames ────────────────────────────────────── */

.na-project-banner:empty { display:none }

.na-exec { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0 }
.na-exec-head { display:flex; align-items:baseline; gap:12px; flex-wrap:wrap }
.na-exec-title { font-size:17px; font-weight:600 }
.na-exec-state { font-size:13px; color:var(--muted) }
.na-exec-state.is-running { color:var(--blue) }
.na-exec-state.is-done { color:var(--green) }
/* Amber, matching the flow map's own `.na-fb.is-waiting` (a gate is not a
   failure) — kept distinct from `is-refused` red on purpose. */
.na-exec-state.is-waiting { color:var(--amber) }
.na-exec-state.is-refused { color:var(--red) }

/* Left and right are sized in percent by `frames.js`; the centre takes the rest.
   `min-width:0` on all three is what stops a wide table from overriding the
   widths the user chose — without it a flex item refuses to shrink below its
   content and the separator stops meaning anything. */
.na-frames { display:flex; align-items:stretch; flex:1 1 auto; min-height:0; gap:0 }
.na-frame {
  display:flex; flex-direction:column; gap:10px;
  min-width:0; overflow:auto; padding:2px;
}
.na-frame[hidden] { display:none }
/* THE TWO SIDES ARE PANELS AND THE MIDDLE IS THE PAGE — the same ladder as the
   Planner, so a reader moving between the two tabs meets one idea of what a
   sidebar is. The centre stays bare: it is what is being read, and putting it in
   a box too would leave three boxes and no page. */
.na-frame-left, .na-frame-right {
  padding:12px 13px;
  background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius-lg); box-shadow:var(--elev-1);
}
.na-frame-centre { padding:2px 16px }
/* THE CENTRE, SPLIT: what is being read, and the bar that asks about it. The
   column itself stops scrolling and stops padding — its two children do both —
   so the reading scrolls under a bar that stays put, and a growing thread costs
   the READING its height rather than costing the two side panels theirs. */
.na-frame-centre.is-split {
  padding:0; overflow:hidden; gap:0;
}
.na-frame-centre.is-split > .na-frame-centre-read {
  flex:1 1 auto; min-height:0; overflow:auto; padding:2px 16px;
}
/* The work, then the conversation about it, in ONE scroll. The thread is not a
   frame and is not bounded: it reads as the continuation of the page it is under,
   which is what makes it one thing to read rather than two panes to manage. */
/* ── a control that belongs to the column, not to what is in it ──────────────
   Zero height and sticky, so it hangs OVER the reading at the column's top-right
   and stays there as the reading scrolls under it (`frames.js` on why not
   absolute). `pointer-events` is off on the strip and back on for the control
   itself, or an invisible zero-height row would still swallow the click on the
   first line of the heading beside it. */
.na-frame-tools {
  position:sticky; top:0; z-index:3; height:0; overflow:visible;
  display:flex; justify-content:flex-end; gap:6px; pointer-events:none;
}
.na-frame-tools:empty { display:none }
.na-frame-tools > * { pointer-events:auto }
.na-frame-tool {
  font-size:12.5px; padding:6px 11px; margin-top:8px;
  background:var(--surface); box-shadow:var(--elev-1);
  /* The reading runs under it, so it needs a ground of its own — a transparent
     control here would have a sentence printed through it. */
  backdrop-filter:blur(3px);
}
.na-frame-tool:disabled { opacity:.4 }
/* The FIRST heading in the column is the one the control hangs over, so it gets
   out of its way. Only the first: everything below it has scrolled past. */
.na-frame-tools + .na-frame-centre-body > .na-context:first-child > .na-context-head {
  padding-right:126px;
}

.na-frame-centre-body { flex:0 0 auto }
.na-frame-centre-thread { flex:0 0 auto; padding-bottom:4px }
.na-frame-centre-thread:empty { display:none }
/* The bounds that made it a pane come OFF here — the column it is in provides
   the scroll now, and a second scrollbar inside one is the thing being removed. */
.na-frame-centre-thread .na-follow-body { max-height:none; overflow:visible }
.na-frame-centre-thread .na-follow-body:not(:empty) {
  margin-top:16px; padding-top:16px; border-top:1px solid var(--border2);
}
/* ONLY THE COMPOSER IS PINNED, because it is what the next message is typed into
   and a reader should never have to scroll to find it. */
.na-frame-dock {
  flex:0 0 auto; min-height:0;
  padding:10px 120px 2px 16px; border-top:1px solid var(--border2);
}
.na-frame-dock:empty { display:none }
.na-frame-dock .na-follow { gap:0 }
.na-frame-cap { font-size:11px; letter-spacing:.05em; text-transform:uppercase; color:var(--muted) }
.na-frame-empty { font-size:13px; color:var(--muted); padding:8px 2px }

/* `.na-plan-sep` (`planpanel.js`'s own separator, between the work and the
   files/detail sidebar) shares this look on purpose — it is the same kind of
   control. It is a SEPARATE class rather than a second use of `.na-frame-sep`
   because several tabs stay mounted at once (`tabs.js`) and a query for
   `.na-frame-sep` alone would then match a hidden tab's separator as
   readily as the visible one's. */
.na-frame-sep, .na-plan-sep {
  flex:0 0 12px; align-self:stretch; cursor:col-resize;
  position:relative; background:transparent; touch-action:none;
}
/* The rule only shows while the pointer is on it. Between two panels that
   already have their own edges, a third line at rest is one line too many —
   what the handle needs to be is FINDABLE, which hover and focus do. */
.na-frame-sep::after, .na-plan-sep::after {
  content:''; position:absolute; top:8px; bottom:8px; left:5px; width:2px;
  border-radius:2px; background:transparent; transition:background .12s ease;
}
.na-frame-sep:hover::after, .na-frame-sep.is-dragging::after,
.na-plan-sep:hover::after, .na-plan-sep.is-dragging::after { background:var(--blue); width:2px }
.na-frame-sep:focus-visible, .na-plan-sep:focus-visible { outline:2px solid var(--blue); outline-offset:-1px; border-radius:4px }

.na-card {
  display:flex; flex-direction:column; gap:3px; text-align:left;
  font:inherit; font-size:13px; color:var(--text);
  padding:10px 12px; cursor:pointer;
  /* Sharper and flatter than the rest of the shell's radius scale on purpose —
     a card is a dense, repeated unit, not a panel, and a tighter corner reads
     denser and more structured. No shadow at rest or on hover: depth here comes
     from the border and a flat colour step, not a blur. */
  border:1px solid var(--border2); border-radius:var(--radius-sm); background:var(--bg);
  transition:border-color .12s ease, background .12s ease;
}
.na-card:hover { border-color:var(--border3); background:var(--surface-sunken) }
.na-card:focus-visible { outline:2px solid var(--blue); outline-offset:2px }
/* A tinted fill, not just a ring around an otherwise-plain card — the
   gradient is the same one the primary button and the avatar use
   (`--accent-fill`'s angle, at card strength), so "selected" reads as the
   accent taking the card rather than a border drawn on top of it. */
.na-card.is-selected {
  border-color:var(--blue);
  background:linear-gradient(135deg, var(--blue-dim) 0%, transparent 120%);
  box-shadow:0 0 0 3px var(--blue-dim);
}
.na-card.is-planner { border-style:dashed }
.na-card.is-refusal { border-color:var(--red) }
/* A GATE, READ AS ONE: the same amber, dashed cue the Planner's own gate chart
   gives a box waiting on a person (`.na-fb.is-waiting`), so a reader who has
   seen it there recognises it here without reading the label first. */
.na-card.is-waiting { border-color:var(--amber) }
.na-card.is-waiting .na-card-kind { color:var(--amber) }
/* A RUNNING STEP, WITHOUT THE TRAVERSAL. The card is a plain div now — what it
   says is which step and that it is running — so it does not take the pointer or
   the tab order the way a card you can open does. Its two controls (Stop, and the
   title once there is something written to open) are real buttons inside it. */
.na-card.is-step { cursor:default }
.na-step-head { display:flex; align-items:baseline; justify-content:space-between; gap:8px }
.na-step-stop {
  flex:0 0 auto; font:inherit; font-size:10px; font-weight:600; line-height:1;
  padding:3px 8px; border-radius:999px; border:1px solid var(--border);
  background:transparent; color:var(--muted); cursor:pointer;
}
.na-step-stop:hover { color:var(--text); border-color:var(--text) }
.na-step-stop:focus-visible { outline:2px solid var(--blue); outline-offset:2px }
/* The title becomes the way in once the step has written something. It keeps the
   title's own look rather than becoming a second button-shaped thing in a card
   that already has one. */
.na-step-open {
  font:inherit; font-weight:500; line-height:1.35; text-align:left;
  padding:0; border:0; background:transparent; color:var(--blue); cursor:pointer;
}
.na-step-open:hover { text-decoration:underline }
.na-step-open:focus-visible { outline:2px solid var(--blue); outline-offset:2px; border-radius:3px }
.na-card-kind { font-size:10px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted) }
.na-card-title { font-weight:500; line-height:1.35 }
/* Which step produced a card. Drawn only in a tab that holds more than one, which
   is now the ordinary case: a tab is a swarm. */
.na-card-from { font-size:11px; color:var(--muted); font-family:var(--mono) }
/* WHICH FILE a card is about, in a rail that holds cards about several. Monospaced
   because it is a filename and a reader is matching it character for character
   against the one they attached; darker than `from` because it is what the card is
   ABOUT, and that outranks which step happened to write it. */
.na-card-subject {
  font-size:11px; color:var(--muted2); font-family:var(--mono);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* THE BAND OVER A RUN OF CARDS ABOUT ONE THING. A divider, not a card: nothing to
   open, no pointer, no tab stop. It sits at the rail's left edge — outdented past
   the cards it groups, the same way a step card is — so the eye can find where one
   file's cards end and the next file's begin without reading any of them. */
.na-rail-band {
  display:flex; align-items:center; gap:8px;
  margin:6px 0 0; padding-top:8px; border-top:1px solid var(--border3);
}
.na-rail-band:first-child { margin-top:0; padding-top:0; border-top:0 }
.na-rail-band-name {
  font-size:11px; font-weight:600; letter-spacing:.02em; color:var(--text);
  font-family:var(--mono);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* A STEP, in a rail that holds several. It is the header of its own run of cards,
   so it is marked as a divider rather than as another card in the pile. */
.na-rail { display:flex; flex-direction:column; gap:8px }
.na-card.is-step { background:var(--surface); border-left:2px solid var(--border3) }
/* Everything a step produced sits under it, indented. The step card is the only
   thing at the rail's left edge, which is what makes the sequence scannable. */
.na-rail .na-card:not(.is-step) { margin-left:10px }
.na-card.is-step.is-running { border-left-color:var(--blue) }
.na-card.is-step.is-done { border-left-color:var(--green) }
.na-card.is-step.is-refused { border-left-color:var(--red) }
.na-card.is-step .na-card-kind { color:var(--muted2) }
.na-card.is-step.is-running .na-card-kind { color:var(--blue) }
.na-card.is-step.is-done .na-card-kind { color:var(--green) }
.na-rail .na-frame-empty { margin-left:10px }

/* ── THE SIDEBAR'S THREE SECTIONS (execrail.js) ───────────────────────────────
   Every run opens with an attempt and closes with a synthesis, and those two are
   the frame the work is read inside rather than two more piles of it. So the rail
   is three headed sections — attempt, reasoning, synthesis — reusing the Planner's
   own section and ribbon (`planfold.js`) so a fold looks and behaves the same in
   both places.

   FLATTER THAN A PLANNER SECTION, because a rail is 280px wide and a card already
   carries its own border: two nested boxes at that width leave the card about
   twenty pixels of prose. The heading and the fold are what is wanted here, not
   the panel around them. */
.na-rail > .na-ps {
  border:0; border-radius:0; background:none; padding:0; gap:7px;
  border-top:1px solid var(--border3); padding-top:10px;
}
.na-rail > .na-ps:first-child { border-top:0; padding-top:0 }
.na-rail > .na-ps > .na-ps-body { gap:8px }
/* THE TWO ENDS ARE THE RUN'S AND THE MIDDLE IS THIS TAB'S, and the heading is the
   only place that difference can be said without a sentence nobody will read
   twice. The ends are stated in the text colour; the reasoning keeps the muted
   heading every other foldable section has. The title sits INSIDE the head button
   on all three now that all three fold, so this reaches through it rather than
   selecting a child of the section. */
.na-rail > .na-rail-opening .na-ps-title,
.na-rail > .na-rail-closing .na-ps-title { color:var(--text) }
/* The rail's own three headings — Initial attempt, Reasoning, Final synthesized
   output — one step down from the piece-of-work title above them, so they grow
   by less: 1.5x the base 10px against that title's 1.75x. */
.na-rail > .na-ps > .na-ps-head .na-ps-title { font-size:15px }
.na-rail > .na-ps > .na-ps-head .na-ps-caret {
  border-left-width:6px; border-right-width:6px; border-top-width:7px;
  align-self:center; margin-bottom:0;
}
/* Shut, a section is one line and must not read as a gap between the two beside
   it — the ribbon is what says how much is folded away, so it gets the room to
   say it. All three fold, so all three get this. */
.na-rail > .na-ps.is-shut { padding-bottom:0 }

/* ONE MASTER CARD PER DEEP-RESEARCH PASS (`execrail.js`'s `openingSection`), for
   the same reason the note above flattens the ends of the frame: at 280px a
   full `.na-ps` panel per pass, nested a second level inside the already-flat
   "Initial passes" band, would leave its own cards no room to breathe. So a
   pass keeps its fold and its ribbon but trades the panel for a left rule —
   enough to say "everything below this line is one run" without a second box
   inside the first. */
.na-rail-opening > .na-ps-body > .na-ps {
  border:0; border-left:2px solid var(--border3); border-radius:0;
  background:none; padding:1px 0 1px 10px; gap:6px;
}
.na-rail-opening > .na-ps-body > .na-ps > .na-ps-body { gap:6px }
.na-rail-opening > .na-ps-body > .na-ps.is-shut { padding-bottom:1px }

/* A SECTION BEING WRITTEN, standing in the place it will land. It is a div, not a
   button — there is nothing to open until it lands, and the ordinary card for it
   replaces this the moment it does — so it does not take the pointer or the tab
   order. Marked the way a running step is marked, because that is what it is: the
   dashed edge is the whole difference between it and the card that replaces it, and
   nothing about it changes height while the section is written. */
.na-card.is-live { cursor:default; border-style:dashed; border-color:var(--border3) }
.na-card.is-live .na-card-kind { color:var(--blue) }
/* The section OPENED, at full width in the centre. The rail's card says only THAT it
   is being written (`blockcard.liveCard`); this is where it is read, and it carries the
   same `data-section`, so it fills as chunks land instead of catching up on a redraw. */
.na-context-live.na-raildoc-live { display:flex; flex-direction:column; gap:10px }

/* ── the rail's two sections ─────────────────────────────────────────────────
   The work and what it produced are two questions, and the frame answers them
   one under the other. A heading is a real button and its body is `hidden` when
   shut — folded away, not merely short, so nothing in a shut section catches a
   Tab. The chevron points where pressing it sends the section. */
.na-railsec { display:flex; flex-direction:column; gap:8px; min-width:0 }
.na-railsec-head {
  display:flex; align-items:center; gap:2px 8px; flex-wrap:wrap; width:100%; text-align:left;
  font:inherit; padding:7px 8px; cursor:pointer;
  color:var(--muted2); background:transparent;
  border:0; border-bottom:1px solid var(--border); border-radius:0;
}
.na-railsec-head:hover { color:var(--text) }
.na-railsec-head:focus-visible { outline:2px solid var(--blue); outline-offset:-2px; border-radius:var(--radius-sm) }
/* The NAME stays on one line and the tally drops below it in a narrow rail — a
   heading broken across two lines by the count beside it is harder to scan than
   a count on its own. */
.na-railsec-title { font-size:11px; letter-spacing:.05em; text-transform:uppercase; font-weight:600; white-space:nowrap }
.na-railsec-count { font-size:11px; color:var(--muted); font-variant-numeric:tabular-nums; margin-left:auto }
/* A refusal is never folded out of sight: the tally says so on the shut heading,
   in the refusal's own colour (Law 4). */
.na-railsec-count.is-refused { color:var(--red) }
.na-railsec-chev {
  flex:0 0 auto; width:0; height:0; margin-left:2px;
  border-left:5px solid currentColor; border-top:4px solid transparent; border-bottom:4px solid transparent;
  transition:transform .12s ease;
}
.na-railsec.is-open .na-railsec-chev { transform:rotate(90deg) }
.na-railsec-body { display:flex; flex-direction:column; gap:8px; min-width:0 }
.na-railsec-body[hidden] { display:none }

/* ONE SECTION OF THE ANSWER, INSIDE RESULTS — and it is the section itself, not a
   card standing for it. Results is the document: a heading and the writing under
   it, section by section, the way a review is read. The card chrome that used to
   be here (a kind chip, a title, three lines of preview) made a table of contents
   out of something that was already on the page, so it is gone from this section
   and stays in the traversal, where a card is the right shape for a step's own
   working. */
.na-raildoc { display:flex; flex-direction:column; gap:7px }
.na-raildoc + .na-raildoc { padding-top:11px; border-top:1px solid var(--border) }
.na-raildoc-h { font-size:12px; letter-spacing:.02em; font-weight:600; color:var(--muted3) }
/* The writing. Pressable — it opens at full width in the centre — but drawn as
   text rather than as a control, because it is text. */
.na-raildoc-part {
  display:flex; flex-direction:column; gap:7px;
  padding:2px 6px; margin:0 -6px; border-radius:var(--radius-sm);
  border-left:2px solid transparent; cursor:pointer;
  /* A grid inside a section is as wide as its columns and the rail is not. It
     scrolls in its own box rather than pushing the frame sideways — the rule the
     whole shell holds, and the reason a wide table is still openable at full size
     in the graphics frame by pressing it. */
  min-width:0; overflow-x:auto;
}
.na-raildoc-part:hover { background:var(--surface-sunken) }
.na-raildoc-part.is-selected { border-left-color:var(--blue); background:var(--surface-sunken) }
/* A SECTION THE PLAN PROMISED AND NOTHING HAS FILLED YET. It is on screen from the
   first moment — the document exists before its content does — so the heading is
   drawn back rather than made to look like one that has been written. What it is
   FOR sits under it, which is the whole reason an empty heading is worth showing. */
.na-raildoc.is-awaited .na-raildoc-h { color:var(--muted) }
.na-raildoc-what { font-size:11px; color:var(--muted); line-height:1.45 }
.na-raildoc-wait { font-size:11px; color:var(--muted2); font-style:italic }

/* the centre and the graphics frame */
/* THE CENTRE FRAME IS A DOCUMENT, NOT A PANEL. It is the widest surface in the
   app and the one a reader actually reads — a whole report, paragraph after
   paragraph — so it is set at reading size rather than at the panel size the
   rail and the docks use. 14px was inherited from the side panels, where the
   column is narrow and the text is glanced at; here it made a finished report
   look like a tooltip. The type inside it steps up with it, just below. */
.na-context { display:flex; flex-direction:column; gap:8px; font-size:17px }
.na-context + .na-context { padding-top:12px; border-top:1px solid var(--border) }
.na-context-head { display:flex; align-items:baseline; gap:10px; flex-wrap:wrap }
/* The title and what it is about are ONE column, so the tools stay on the title's
   line rather than being pushed down by a second one. `min-width:0` on both so a
   long filename ellipses instead of forcing the head wider than the frame. */
.na-context-heading { flex:1 1 auto; min-width:0 }
/* The card's own name has to stay ABOVE the `##` headings inside it — it was two
   points clear of them before and has to stay two points clear now. */
.na-context-title { font-size:21px; font-weight:600; flex:1 1 auto; min-width:0 }
/* WHICH FILE the open card is about. Under the title rather than beside it: it is
   what this is about, not a qualifier on the words above it. */
.na-context-subject {
  margin:2px 0 0; font-size:11px; color:var(--muted2); font-family:var(--mono);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* Beside the title where there is room, and on its own line when there is not —
   never squeezing the title, which is what the frame is for. */
.na-context-tools { display:flex; align-items:center; gap:6px; flex:0 0 auto; margin-left:auto }
.na-context-get { text-decoration:none; white-space:nowrap; font-size:12.5px; padding:6px 10px }

/* a step's answer, as it is written. `pre-wrap` because what arrives is raw and
   unformatted — the durable card that replaces it is the one that is laid out, and
   pretending otherwise would reflow the text under the reader as it grows. */
.na-context-live { line-height:1.6; color:var(--muted3); word-break:break-word }
.na-context-cursor { display:inline-block; width:7px; height:15px; vertical-align:-2px;
  background:var(--accent, currentColor); opacity:.75; animation:na-blink 1.05s steps(2) infinite }
@keyframes na-blink { 0%,49% { opacity:.75 } 50%,100% { opacity:0 } }
@media (prefers-reduced-motion: reduce) { .na-context-cursor { animation:none } }

/* A refused step's box to answer in — only drawn when the refusal itself said
   a search is not already handling it (`Refusal.route`). */
.na-context-answer { display:flex; flex-direction:column; gap:6px; margin-top:2px;
  padding:10px 12px; border:1px solid var(--border2); border-radius:8px; background:var(--bg2) }
.na-context-answer-label { font-size:13px; color:var(--muted2) }
.na-context-answer-row { display:flex; gap:8px; align-items:center }
.na-context-answer-input { flex:1 1 auto; min-width:0 }
.na-context-answer-error { font-size:12.5px; color:var(--red) }
/* THE SAME DECISION, READ FROM THE OTHER TAB. `.na-gate` gives a step waiting on
   a person an amber rule down the side — the one thing on screen the run is not
   getting past — and its own question the weight of one (`.na-gate-asked`). This
   is the same fact reached through a step's own card instead of the Planner's
   gate panel, so it carries the same two cues rather than reading as a quieter,
   second version of the same choice. */
.na-context-answer.is-approval { border-left:3px solid var(--amber) }
.na-context-answer.is-approval .na-context-answer-label {
  font-size:13px; font-weight:600; color:var(--text); line-height:1.5;
}

/* A generated-code request, open in the graphics frame — the same frame a
   figure or a table would, never a panel of its own (`execbody.js:codeBody`). */
.na-codegen-status { margin:0; font-size:13px; color:var(--muted2) }
.na-codegen-code {
  margin:0; padding:12px; overflow:auto; max-height:calc(var(--screen-h) * .6);
  background:var(--surface-sunken); border:1px solid var(--border); border-radius:var(--radius);
  font-family:var(--mono); font-size:12.5px; line-height:1.55; color:var(--text); white-space:pre-wrap;
}
.na-codegen-output { display:flex; flex-direction:column; gap:6px }
.na-codegen-stdout, .na-codegen-stderr {
  font-family:var(--mono); font-size:11.5px; white-space:pre-wrap; padding:8px 10px;
  border-radius:6px; background:var(--surface-sunken);
}
.na-codegen-stderr { color:var(--red) }
/* THE FILES A CODE RUN WROTE, and they are stacked rather than a wrapped row of
   buttons: these are figures now, not links (`execfiles.js`). A PNG the sandbox
   saved is the chart somebody asked for, and it is drawn at the width of the
   frame the way any other figure in this app is. */
.na-codegen-files { display:flex; flex-direction:column; gap:12px }
.na-codegen-file { margin:0; display:flex; flex-direction:column; gap:6px }
.na-codegen-img {
  display:block; max-width:100%; height:auto;
  border:1px solid var(--border); border-radius:var(--radius); background:var(--bg);
}
/* AN INTERACTIVE PLOT NEEDS A HEIGHT OF ITS OWN. An iframe has no intrinsic size
   to grow to, so without one it collapses to a sliver and reads as a failure to
   render rather than as the plot it is. */
.na-codegen-frame {
  display:block; width:100%; height:min(480px, calc(var(--screen-h) * .5));
  border:1px solid var(--border); border-radius:var(--radius); background:var(--bg);
}
.na-codegen-file-cap {
  display:flex; align-items:center; gap:10px;
  font-size:12px; color:var(--muted2);
}
.na-codegen-file-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* The download sits at the far end of the caption, away from the name, so a long
   filename pushes against it rather than over it. */
.na-codegen-file-cap .na-btn { margin-left:auto; flex:none }
.na-codegen-file-note { margin:0; font-size:12px; color:var(--muted2) }

/* ── the blocks themselves ───────────────────────────────────────────────── */

.na-block-h { font-size:13px; font-weight:600; margin-top:6px }
/* A writer's own `**subsection**` label. Sits UNDER `.na-block-h` in weight and
   size, because a card has both: `##` names a section of the answer and `**`
   names a region inside one. Drawn as plain body text they were
   indistinguishable, which read as the writing being inconsistent. */
.na-block-sub { font-size:12.5px; font-weight:600; color:var(--text);
                margin:10px 0 2px; letter-spacing:.01em }
.na-block-p { line-height:1.6; color:var(--muted3) }
.na-block-note { font-size:12px; color:var(--muted) }
/* BLOCK, NOT FLEX. `display:flex` here was only ever to get the `gap` between
   items — but flexbox on an `<ol>` is a well-known trap: it breaks the native
   marker/counter generation `<li>` numbering depends on, and every ordered
   list in this app counted "1, 1, 1…" as a result. Plain block flow numbers
   correctly on its own; the spacing comes back via margin instead of gap. */
.na-block-list { padding-left:20px; color:var(--muted3) }
.na-block-list > li + li { margin-top:4px }
/* THE WORKING BEHIND A NUMBER — a program a step ran and what it printed
   (`blockcode.js`). It borrows the code window's own `.na-codegen-*` faces
   deliberately: a program is a program whether a person asked for one or a
   planned calculation wrote one, and two house styles for it would read as two
   different kinds of evidence. Only the verdict line underneath is new.

   IT IS QUIET UNLESS THE RUN FAILED. A clean exit is a fact a reader glances at;
   a non-zero one is a fact about whether to believe the number above it, and that
   is the one this colours. */
.na-block-exit { font-size:12px; color:var(--muted); margin-top:2px }
.na-block-exit.is-bad { color:var(--red) }

/* A table's caption, and its way out of the column it is in. The number is set
   apart from the caption so "Table 3" reads as a label a sentence can point at
   rather than as the first two words of the sentence above the grid. */
.na-block-tablewrap { display:flex; flex-direction:column; gap:4px; margin:10px 0 2px }
.na-block-caption { display:flex; align-items:baseline; gap:2px; font-size:12px;
                    color:var(--muted); line-height:1.5 }
.na-block-tno { font-weight:700; color:var(--text); white-space:nowrap }
.na-block-tcap { flex:1 1 auto; min-width:0 }
.na-block-open { flex:0 0 auto; font:inherit; font-size:11px; font-weight:600;
  line-height:1; padding:2px 7px; margin-left:6px; border-radius:999px;
  border:1px solid var(--border); background:transparent; color:var(--muted);
  cursor:pointer }
.na-block-open:hover { color:var(--text); border-color:var(--text) }
/* A table lifted out into its own card, named where it was written. It carries no
   grid under it — the rows are in the frame beside this one — so it is drawn as a
   band the eye can stop at rather than a caption over nothing, which is what it
   looked like when the lift left only the heading behind. */
.na-block-tablewrap.is-ribbon {
  margin:12px 0; padding:8px 11px;
  border:1px solid var(--border); border-left:3px solid var(--blue);
  border-radius:var(--radius); background:var(--surface-sunken);
}
.na-block-tablewrap.is-ribbon .na-block-caption { margin:0 }
/* A figure's own sentence, under its number and name. Two different facts — what
   the picture is, and what was actually plotted in it — so it is set under the
   label rather than run into it, where it would read as a longer title. */
.na-block-ribcap { margin:3px 0 0; font-size:12px; line-height:1.5; color:var(--muted3) }

/* An equation wider than the rail scrolls instead of forcing the card wide —
   the same trade a table makes (`.na-block-scroll` below). KaTeX's own CSS
   does not guarantee this on its `.katex-display` wrapper. */
.na-block-p .katex-display { overflow-x:auto; overflow-y:hidden; padding:2px 0 }

.na-block-scroll { overflow-x:auto; border:1px solid var(--border); border-radius:var(--radius) }
.na-block-table { width:100%; border-collapse:collapse; font-size:12.5px }
.na-block-table th {
  text-align:left; font-weight:500; color:var(--muted);
  padding:8px 10px; border-bottom:1px solid var(--border); white-space:nowrap;
}
.na-block-table .na-block-units th { font-style:italic; padding-top:0 }
.na-block-table td { padding:8px 10px; border-bottom:1px solid var(--border); vertical-align:top }
.na-block-table tbody tr:last-child td { border-bottom:0 }

.na-block-figure { display:flex; flex-direction:column; gap:6px }
.na-block-img {
  display:block; max-width:100%; height:auto;
  border:1px solid var(--border); border-radius:var(--radius); background:var(--bg);
}

.na-block-fields { display:grid; grid-template-columns:auto 1fr; gap:4px 12px; font-size:13px }
.na-block-fields dt { color:var(--muted); white-space:nowrap }
.na-block-fields dd { color:var(--muted3) }

/* ── the same blocks, at reading size in the centre frame ────────────────────
   The sizes above are the PANEL scale: the follow-up dock, the assistant, a
   card quoted in a narrow column. The centre frame is the document (see
   `.na-context`), and every one of these is an absolute px rather than an `em`,
   so raising the container alone left the headings smaller than the paragraphs
   under them and the tables set two points below the prose that introduces them.
   The step is the same everywhere — one size up, in proportion — so a report
   reads as one piece of typography rather than as body text somebody enlarged. */
.na-context .na-block-h { font-size:19px }
.na-context .na-block-sub { font-size:16.5px }
.na-context .na-block-note { font-size:15px }
.na-context .na-block-caption { font-size:15px }
.na-context .na-block-open { font-size:13.5px; padding:4px 10px }
.na-context .na-block-ribcap { font-size:15px }
.na-context .na-block-table { font-size:15.5px }
.na-context .na-block-fields { font-size:16px }
/* The citation chip sits ON a line of body text, so it steps with it — a 10.5px
   chip in a 17px sentence is a speck rather than something to press. */
.na-context .na-cite { font-size:12.5px }

/* ── citations, and the record behind one ────────────────────────────────────
   `[7]` in a written section, as something you can open. It sits ON the line it
   was written in, so it must not change the line's height — hence the small
   type, the tight box and the baseline nudge rather than a taller inline block. */
.na-cite {
  display:inline-block; font:inherit; font-family:var(--mono); font-size:10.5px; font-weight:500;
  color:var(--blue); background:var(--blue-dim);
  border:1px solid var(--blue-dim); border-radius:3px;
  padding:0 4px; margin:0 1px; line-height:1.6; cursor:pointer;
  vertical-align:baseline; position:relative; top:-1px; white-space:nowrap;
  transition:background .12s ease, border-color .12s ease;
}
.na-cite:hover, .na-cite.is-open { background:var(--blue-glow); border-color:var(--blue) }
.na-cite:focus-visible { outline:2px solid var(--blue); outline-offset:1px }
/* A number matching no record this tab holds is stripped of every citation
   affordance: it reads as the bare number it is, because a reference nothing
   vouches for must not look like one something does (Law 1). */
.na-cite.is-unverified {
  color:var(--muted); background:transparent;
  border-color:var(--border2); border-style:dashed; cursor:help;
}

.na-cite-tip {
  position:fixed; z-index:60; max-width:360px;
  display:flex; flex-direction:column; gap:5px;
  padding:11px 13px; font-size:12px; line-height:1.55;
  color:var(--muted3); background:var(--surface-raised);
  border:1px solid var(--border3); border-radius:var(--radius);
  box-shadow:var(--shadow-lg, 0 10px 34px rgba(0,0,0,.28));
}
.na-cite-tip[hidden] { display:none }
.na-cite-tip.is-unverified { border-color:var(--red) }
/* Authors read first — the order a citation is actually read in — with the
   title underneath rather than above a line that repeats it. */
.na-cite-authors { color:var(--muted3); font-weight:500; line-height:1.4 }
.na-cite-title { color:var(--text); font-weight:500; line-height:1.45 }
.na-cite-meta { color:var(--muted) }
.na-cite-id { font-family:var(--mono); font-size:11px; color:var(--muted2); word-break:break-all }
.na-cite-id a { color:var(--blue); text-decoration:none }
.na-cite-id a:hover { text-decoration:underline }
.na-cite-warn { color:var(--red); font-weight:500 }

/* ── the Report tab ──────────────────────────────────────────────────────── */

.na-report { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0 }
.na-report-body { display:flex; flex-direction:column; flex:1 1 auto; min-height:0 }
.na-report-offer {
  display:flex; flex-direction:column; align-items:center; gap:10px;
  margin:auto; text-align:center; max-width:56ch; padding:32px 8px;
}
.na-report-offer .na-plan-sub { text-align:center }

/* ── "Generate a report", the last section of the Planner (planreports.js) ─
   THREE SCREENS IN ONE SECTION — the list, the checklist it proposes, and the
   run writing the thing — and until now each of them borrowed whatever type was
   nearest. Both headings were `.na-plan-title`, which is the IDLE SCREEN'S HERO:
   clamp(25px,2.7vw,36px), centred, capped at 28 characters. Inside a 15px
   left-aligned section it set "What should this report contain?" across three
   centred lines above a left-aligned checklist, and "Generating your report"
   likewise over a bare spinner. They are section headings and they use the
   section heading (`.na-plan-h`); what is below is given a shape of its own. */

.na-reports-head {
  display:flex; align-items:flex-start; justify-content:space-between; gap:16px;
}
.na-reports-head-main { display:flex; flex-direction:column; gap:3px; min-width:0 }

/* NOTHING GENERATED YET is the state this section is in the first time anybody
   sees it, so it is an offer rather than a paragraph beside a button. */
.na-reports-empty {
  display:flex; flex-direction:column; align-items:center; gap:9px;
  padding:26px 18px 24px; text-align:center;
  border:1px dashed var(--border2); border-radius:var(--radius);
  background:var(--surface-sunken);
}
.na-reports-empty .na-plan-sub { max-width:52ch; text-align:center }
.na-reports-empty .na-btn { margin-top:5px }
.na-reports-empty-mark {
  display:inline-flex; align-items:center; justify-content:center;
  width:38px; height:38px; border-radius:50%;
  color:var(--blue); background:var(--blue-dim);
}
.na-reports-glyph { display:block }

.na-reports-list { display:flex; flex-direction:column; margin-top:10px }
.na-reports-row {
  display:flex; align-items:center; gap:11px;
  padding:11px 8px; border-bottom:1px solid var(--border); border-radius:7px;
  transition:background .12s ease;
}
.na-reports-row:hover { background:var(--surface-sunken) }
.na-reports-row:last-child { border-bottom:none }
/* A superseded report is still downloadable and still a true record of what that
   generation said — so it recedes rather than being struck through or hidden. */
.na-reports-row.is-superseded { opacity:.72 }
.na-reports-row-mark {
  flex:none; display:inline-flex; align-items:center; justify-content:center;
  width:30px; height:30px; border-radius:8px;
  color:var(--blue); background:var(--blue-dim);
}
.na-reports-row-main { display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 auto }
.na-reports-row-title {
  display:flex; align-items:center; gap:7px; margin:0;
  font-weight:500; min-width:0;
}
.na-reports-row-title > span:first-child {
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.na-reports-chip {
  flex:none; font-size:10.5px; font-weight:600; letter-spacing:.04em;
  text-transform:uppercase; color:var(--muted2);
  padding:2px 6px; border-radius:999px; border:1px solid var(--border2);
}
.na-reports-row-meta { color:var(--muted2); font-size:12px; margin:0 }
.na-reports-row .na-btn { flex:none }

/* THE CHECKLIST, in a well of its own. It is the one thing on this screen the
   reader acts on, and un-framed it ran straight on from the two lines of prose
   above it as though it were more prose. */
.na-reports-parts {
  border:1px solid var(--border); border-radius:var(--radius);
  background:var(--surface-sunken); padding:9px 12px 11px; margin:10px 0 4px;
}
.na-reports-parts-head {
  margin:0 0 4px; font-size:11.5px; font-weight:600; letter-spacing:.03em;
  text-transform:uppercase; color:var(--muted2);
}
.na-reports-parts .na-gate-parts { margin:0 }
.na-reports-parts .na-gate-part-row:hover { background:var(--surface) }
.na-reports-part-text { display:flex; flex-direction:column; gap:1px; min-width:0 }
/* A row, because a shown figure or table puts its kind in front of its name as a chip
   (`.na-reports-sort`) — a section's own title is the same row with nothing in front. */
.na-reports-part-title {
  display:flex; align-items:baseline; gap:7px; min-width:0;
  font-size:14.5px; line-height:1.45;
}
/* Under the title, not after an em-dash on the same line — a `why` is a whole
   sentence, and inline it buried the heading it belongs to. */
.na-reports-why { color:var(--muted2); font-size:12.5px; line-height:1.5 }
.na-reports-omitted { color:var(--muted2); font-size:12px }

/* GENERATING. One block carrying what is happening, how far it has got and the
   way out — rather than a hero, a spinner and a grey line stacked loose. */
.na-reports-working {
  border:1px solid var(--border2); border-radius:var(--radius);
  background:var(--surface); overflow:hidden; box-shadow:var(--elev-1);
}
.na-reports-working-body { display:flex; align-items:center; gap:13px; padding:14px 15px }
.na-reports-working-main { display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 auto }
.na-reports-working .na-spinner { flex:none }
.na-reports-working .na-btn { flex:none }
/* INDETERMINATE, and deliberately so: nothing in the stream says how much of a
   report is written, and a bar that filled would be inventing that. */
.na-reports-bar { height:3px; background:var(--blue-dim); overflow:hidden }
.na-reports-bar > span {
  display:block; height:100%; width:38%; border-radius:999px;
  background:linear-gradient(90deg,transparent,var(--blue),transparent);
  animation:na-reports-sweep 1.5s ease-in-out infinite;
}
@keyframes na-reports-sweep {
  0% { transform:translateX(-100%) }
  100% { transform:translateX(340%) }
}
@media (prefers-reduced-motion:reduce) {
  .na-reports-bar > span { animation:none; width:100%; background:var(--blue); opacity:.5 }
}
/* The line the run's own stream writes into — one line, and it does not reflow the
   panel when a long step message lands in it. */
.na-reports-log {
  margin:0; color:var(--muted2); font-size:13px; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* WHAT A SECTION SHOWS, under the section. Indented and a step down in size, because
   the relationship is containment — these go IN that part — and a flat list of parts
   and figures at one weight would read as a document with a figure between every two
   headings. The rail is what makes the indent structural rather than a margin: unticking
   a part visibly takes its figures with it, which is what actually happens. */
.na-reports-part + .na-reports-part { margin-top:2px }
.na-reports-shows {
  display:flex; flex-direction:column; gap:1px;
  margin:0 0 4px 28px; padding-left:11px; border-left:1px solid var(--border2);
}
.na-reports-shown { padding:4px 3px }
.na-reports-shown .na-reports-part-title { font-size:13.5px; color:var(--muted3) }
.na-reports-shown .na-reports-why { font-size:12px }
/* The kind, as a chip in front of the name — at this level it is the column a reader
   scans, and set inline it read as the first word of the title. */
.na-reports-sort {
  flex:none; font-size:10px; font-weight:600; letter-spacing:.05em;
  text-transform:uppercase; color:var(--muted2);
  padding:1px 5px; border-radius:999px; border:1px solid var(--border2);
}

/* ── the files panel ─────────────────────────────────────────────────────── */

.na-files { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto }
/* The same panel, in a 320px column rather than across a tab. Everything that was
   side by side stacks, the type comes down a step, and the attached list gets a
   ceiling of its own — a project with forty files must not push the step detail
   off the bottom of the sidebar. */
/* ONE SCROLLER PER LIST, and none around them. The panel itself grows to its
   content; only the two LISTS are bounded, because they are the two things that
   grow without limit. Scrolling the panel as a whole as well put four nested
   scrollports in a 320px column, and a list of two files read as four half-rows. */
.na-files-aside { gap:10px; overflow:visible; flex:0 0 auto }
/* ONE COLUMN FOR WHAT THE ROW SAYS, A SECOND FOR THE ONE THING IT OFFERS — and
   this rule, not the base one, is the layout an attached file is ACTUALLY drawn
   with, because `filespanel.js` mounts the whole panel as `.na-files-aside`.
   The column is 320px, so the name and its size genuinely do have to stack; but
   collapsing to a single track stacked the "Open in 3D" control under them too,
   which detached the only way into the CAD workbench from the file it opens and
   left it reading as a stray button between rows. The text keeps the full width
   it needs, and the action sits on the name's own line where a reader looking at
   a file will find it. */
.na-files-aside .na-files-row { grid-template-columns:minmax(0,1fr) auto }
.na-files-aside .na-files-name,
.na-files-aside .na-files-meta,
.na-files-aside .na-files-state { grid-column:1 }
/* The wrapper around "Open in 3D" and "Remove" — not `.na-files-3d` directly any
   more, now that a row can carry both. Placed as one unit so the two share the
   one corner a 320px column has room for. */
.na-files-aside .na-files-rowactions { grid-column:2; grid-row:1; align-self:center }
.na-files-aside .na-files-state, .na-files-aside .na-files-meta { text-align:left }
/* The list scrolls; the button under it must not scroll away with it, or the one
   control in the panel is behind a scrollport once there are a dozen files. */
.na-files-aside .na-files-held { display:flex; flex-direction:column; gap:10px }
.na-files-aside .na-files-listcard > .na-files-list { max-height:300px; overflow-y:auto }
.na-files-aside .na-files-held > .na-btn { align-self:flex-start }
.na-files-aside .na-files-queue { max-height:200px; overflow-y:auto }
/* THE WAYS IN, AS A FIXED ROW ABOVE THE LIST — not trailing it, where a long
   list would push Upload out from under the reader's eye. */
.na-files-add-row { flex:0 0 auto; display:flex; flex-direction:column; gap:8px }
/* AND WHAT IS ALREADY IN, AS ITS OWN SECTION UNDER THEM. A border rather than
   the composer's full raised-card treatment: this is a subsection of the one
   panel, not a second surface floating on top of it — the box-in-a-box `.na-empty`
   was corrected out of above. */
.na-files-listcard {
  border:1px solid var(--border); border-radius:var(--radius);
  padding:12px; display:flex; flex-direction:column; gap:8px;
}
.na-files-listcard .na-files-h { margin-bottom:0 }
/* Grouped so the two share one gap and one place in the row's grid, in both the
   3-column base layout and the 2-column aside override above. */
.na-files-rowactions { display:flex; align-items:center; gap:2px }
.na-files-remove {
  flex:none; display:flex; align-items:center; justify-content:center;
  width:26px; height:26px; padding:0; border:0; border-radius:var(--radius-sm);
  background:transparent; color:var(--muted); cursor:pointer;
}
.na-files-remove svg { width:15px; height:15px }
.na-files-remove:hover, .na-files-remove:focus-visible { background:var(--red-dim); color:var(--red) }
/* NOT A CARD AT ALL. The column it sits in draws the one edge the region gets
   (`.na-plan-side`, above), and this card is the ONLY thing in that column while
   it is empty — a second box around it would be a box drawn 15px inside a box,
   which is how "nothing is attached" ends up looking like two things. The
   invitation is a mark, a title and a sentence, on the glass. */
.na-files-aside .na-empty {
  padding:28px 18px; border:0; background:transparent; box-shadow:none;
}
.na-files-aside .na-empty-title { font-size:14px }
.na-files-aside .na-empty-body { font-size:12.5px; margin-bottom:12px }
/* A banner with no refusal in it and a summary with no batch behind it are two
   blank lines that push the list they belong to down the column. */
.na-files-banner:empty, .na-files-summary:empty { display:none }
/* The real input, kept out of the way and out of the tab order — the button in
   front of it is the control, and two focus stops for one act is one too many. */
.na-files-pick { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none }
.na-files-h { font-size:13px; font-weight:600; color:var(--muted2); margin-bottom:6px }
.na-files-summary { font-size:13px; color:var(--text) }

.na-files-list { display:flex; flex-direction:column; gap:6px; list-style:none }
/* THREE TRACKS, NOT TWO: name, what-it-is, and what-you-can-do-with-it. The row
   was laid out for two children and grew a third when "Open in 3D" landed
   (`geomoffer.js`) — and a grid item with no track of its own wraps to an
   implicit row, so the one control that opens a CAD file appeared BELOW its own
   filename, indented under it, reading as part of the next row. The affordance
   was there the whole time; it was in the wrong place, which for a control
   nobody has been told about is the same as not being there at all. */
.na-files-row {
  display:grid; grid-template-columns:minmax(0,1fr) auto auto; align-items:center;
  gap:4px 14px; padding:9px 12px;
  border:1px solid var(--border); border-radius:var(--radius);
}
.na-files-name { color:var(--text); font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.na-files-meta { display:flex; gap:12px; font-size:12px; color:var(--muted); flex:none }
.na-files-state { font-size:12.5px; color:var(--muted2); text-align:right }

/* Progress is a measurement, so it spans the row under both cells rather than
   competing with the name for width. */
.na-files-bar { grid-column:1 / -1; height:3px; border-radius:3px; background:var(--border); overflow:hidden }
.na-files-fill { display:block; height:100%; background:var(--muted2); border-radius:3px }

/* THE FAST LOOK — a table and/or a chart, drawn the instant the local read
   resolves, spanning the row under both cells the same way the progress bar
   above does. Shown for every state a row passes through, not only once
   attached: what the file IS does not depend on whether it has finished
   uploading yet. */
.na-files-preview { grid-column:1 / -1; margin-top:4px; display:flex; flex-direction:column; gap:6px }
.na-files-preview .na-ch-note { margin-top:0 }
.na-files-preview .na-ch-svg { height:100px }

/* THE THREE REFUSALS DO NOT SHARE A LOOK, and none of them is carried by colour
   alone — the sentence in `.na-files-state` is the difference; this only stops
   them from reading as the same kind of row. */
.na-files-row.is-failed { border-color:var(--border2) }
.na-files-row.is-failed .na-files-state { color:var(--text); font-weight:600 }
.na-files-row.is-missing { border-style:dashed }
.na-files-row.is-missing .na-files-state { color:var(--text) }
.na-files-row.is-still-uploading { border-left:3px solid var(--muted) }
.na-files-row.is-still-uploading .na-files-state { color:var(--muted2); font-style:italic }
.na-files-row.is-attached .na-files-state { color:var(--muted) }

/* The rest of the states belong to a file taken from a connected drive, and each
   one is a different fact — so each one is a different row. Nothing here is
   colour on its own; the sentence carries the fact and this stops two of them
   from looking like one kind of thing. */
.na-files-row.is-not-in-library { border-style:dotted; border-color:var(--border2) }
.na-files-row.is-not-in-library .na-files-state { color:var(--text) }
.na-files-row.is-not-attached { border-left:3px solid var(--border2) }
.na-files-row.is-not-attached .na-files-state { color:var(--text) }
.na-files-row.is-expired { border-color:var(--red) }
.na-files-row.is-expired .na-files-state { color:var(--red) }
.na-files-row.is-not-connected { border-style:dashed; border-color:var(--muted) }
.na-files-row.is-not-connected .na-files-state { color:var(--muted2) }
.na-files-row.is-importing .na-files-state,
.na-files-row.is-attaching .na-files-state { color:var(--muted) }

/* ── unpacking a .zip: the one genuinely new transient state ─────────────── */

/* The fan-out is one synchronous call, so there is no N-of-M to show — the bar is
   indeterminate: a short warm-brown pane sliding across the track, in the accent
   the rest of the panel already uses. `is-unpacking` also tints the sentence. */
.na-files-row.is-unpacking .na-files-state { color:var(--accent); font-style:italic }
.na-files-bar.is-indeterminate { position:relative }
.na-files-bar.is-indeterminate .na-files-fill {
  width:38%; background:var(--accent-fill); animation:na-files-unpack 1.15s ease-in-out infinite;
}
@keyframes na-files-unpack {
  0% { transform:translateX(-100%) } 100% { transform:translateX(320%) }
}
@media (prefers-reduced-motion: reduce) {
  .na-files-bar.is-indeterminate .na-files-fill { animation:none; width:100%; opacity:.5 }
}
.na-files-row.is-unpacked .na-files-state { color:var(--text); font-weight:600 }

/* ── a member a .zip could not read: the RED error flag (a reader exists, this
      file is broken) — the deliberate opposite of the muted "no reader yet" pill
      below, which is "we don't read this format at all". ──────────────────── */
.na-files-failed { grid-column:1 / -1; margin-top:6px; display:flex; flex-direction:column; gap:5px }
.na-files-failsum { margin:0; font-size:12px; color:var(--red); font-weight:600 }
.na-files-faillist { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:3px }
.na-files-failrow {
  display:flex; align-items:center; gap:7px; font-size:12.5px; color:var(--text);
  border-left:2px solid var(--red); background:var(--red-dim);
  border-radius:var(--radius-sm); padding:3px 8px;
}
.na-files-failrow .na-files-name { flex:1 1 auto; min-width:0 }
.na-files-failwhy { color:var(--red); font-size:11.5px; flex:none }
.na-files-flagicon { width:14px; height:14px; color:var(--red); flex:none }

/* ── "no reader yet": a MUTED, informational pill on a member whose format has no
      parser. Neutral on purpose — this is a coverage gap, not a broken file. ── */
.na-files-pill.is-noreader {
  display:inline-flex; align-items:center; align-self:flex-start;
  font-size:11px; line-height:1.5; padding:1px 8px; border-radius:999px;
  background:var(--surface-sunken); border:1px solid var(--border2); color:var(--muted2);
  white-space:nowrap;
}

/* The .zip a member came out of — a subtle origin label in the meta row, not a
   heading and not a group; the members stay flat in the list. */
.na-files-origin { color:var(--muted2); font-style:italic }

/* ── where else a file can come from ─────────────────────────────────────── */

/* Which drive, asked when a drive is asked for — and the screen that answers when
   none is connected. Neither of them lives in the sidebar any more. */
.na-pick { display:flex; flex-direction:column; gap:12px; align-items:flex-start }
.na-pick-sub { font-size:13px; color:var(--muted); max-width:52ch }
.na-pick-list { display:flex; flex-direction:column; gap:6px; width:100%; list-style:none }
.na-pick-source { width:100%; justify-content:flex-start; text-align:left }
/* Not connected and could-not-be-checked are two different facts, so they are not
   the same line twice: the second is the one with something wrong with it. */
.na-pick-unknown {
  font-size:12.5px; color:var(--muted2);
  border-left:2px solid var(--border2); padding-left:10px;
}

/* ── the panel that walks a connected drive ──────────────────────────────── */

.na-lib { display:flex; flex-direction:column; gap:12px; min-height:0 }
.na-lib-account { font-size:12.5px; color:var(--muted) }
.na-lib-crumbs { display:flex; align-items:center; gap:4px; flex-wrap:wrap; font-size:12.5px }
.na-lib-crumb {
  background:none; border:none; padding:2px 4px; border-radius:var(--radius);
  color:var(--blue); font:inherit; font-size:12.5px; cursor:pointer;
}
.na-lib-crumb:hover { text-decoration:underline }
/* The folder you are in is not a link to itself. */
.na-lib-crumb.is-here { color:var(--muted2); cursor:default }
.na-lib-crumb + .na-lib-crumb::before { content:'›'; color:var(--muted); margin-right:6px }

.na-lib-listing { min-height:0; max-height:calc(var(--screen-h) * .46); overflow-y:auto }
.na-lib-list { display:flex; flex-direction:column; gap:2px; list-style:none }
.na-lib-row { border-bottom:1px solid var(--border) }
.na-lib-row:last-child { border-bottom:none }
.na-lib-pick, .na-lib-open {
  display:flex; align-items:center; gap:10px; width:100%;
  padding:8px 6px; background:none; border:none; font:inherit; text-align:left; cursor:pointer;
}
.na-lib-pick:hover, .na-lib-open:hover { background:var(--border); border-radius:var(--radius) }
.na-lib-name { color:var(--text); font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.na-lib-size { margin-left:auto; font-size:12px; color:var(--muted); flex:none }
.na-lib-icon { color:var(--muted2); flex:none }
.na-lib-check { flex:none; accent-color:var(--blue) }

.na-lib-more { display:flex; align-items:center; gap:10px; padding:10px 6px; flex-wrap:wrap }
.na-lib-more-note { font-size:12.5px; color:var(--muted2) }
.na-lib-foot { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap }
.na-lib-count { font-size:12.5px; color:var(--muted2) }
.na-lib-queue { max-height:calc(var(--screen-h) * .3); overflow-y:auto }

/* A source that cannot be browsed at all. The two reasons — never connected, and
   connected but lapsed — are one layout and two sentences, marked apart so a
   reader who has seen one recognises that this is the other. */
.na-lib-blocked { display:flex; flex-direction:column; gap:10px; align-items:flex-start }
.na-lib-blocked.is-expired { border-left:3px solid var(--red); padding-left:12px }
.na-lib-blocked.is-unconnected { border-left:3px solid var(--border2); padding-left:12px }

/* ── narrow screens ──────────────────────────────────────────────────────── */

@media (max-width:720px) {
  #nergyapp-root { padding:20px 14px 48px }
  .na-cell-desc { display:none }
  .na-table th:nth-child(2) { display:none }
  /* ONE COLUMN AND ONE SCROLL, in reading order: the work, what the project was
     given, then the box. NOT the grid — side by side there are two tracks that
     each scroll inside their own bounds, and stacking them left three such
     tracks competing for one screen's height. Every one of them can compress to
     nothing (a scrollport's minimum is zero), so they did: the map was clipped
     to a sliver and the box was drawn on top of the sidebar. Stacked, the PANEL
     is the scroller and each region is simply as tall as its content. */
  .na-panel { overflow-y:auto }
  .na-plan,
  .na-plan.is-idle, .na-plan.is-idle.has-note {
    display:flex; flex-direction:column; gap:16px; min-height:auto;
  }
  /* The empty tracks that centre the pair on a wide panel are gone with the
     second column, and the gap they had to be paid around comes back. */
  .na-plan.is-idle .na-plan-search { margin-top:0 }
  /* AND THE SAMPLE-DATA NOTE COMES BACK INTO THE FLOW. Idle on a wide panel it is
     pinned to the corner (`.na-plan.is-idle > .na-notice-note`): the grid centres
     the title-and-composer pair between two flexible tracks, and a footnote taking
     a row of its own would push the pair off that centre. There is no such centring
     here — this is a plain column — so out-of-flow buys nothing and costs the
     collision it was always going to cost: the note was drawn straight across
     "What would you like to look into?". Static, it is simply the first thing in
     the column, which is where it reads. */
  .na-plan.is-idle > .na-notice-note { position:static; inset:auto }
  .na-plan:not(.is-idle) .na-plan-flow { overflow:visible; padding-bottom:0 }
  /* Stacked, the column is the width of the screen — the cap above is about
     sharing a row with the map, and there is no row here. */
  /* !important because `planpanel.js` now sets an inline `width` on this
     element (desktop's fix for the same column stretching under a long line
     the max-width comment above describes) — an inline style always beats a
     plain stylesheet rule, so only `!important` gets the stack back here. */
  .na-plan-sidecol { width:100% !important; max-width:none !important; }
  /* Same reason: a desktop resize before the viewport narrowed can have left
     an inline `flex-basis` on this element too. */
  /* AND THE PILL'S CLEARANCE GOES WITH THE SCROLLPORT. `--fab-clear` is the room
     the fixed "Quick ask" pill needs at the END of something that scrolls under
     it; stacked, this is not the scroller (the panel is) and it is not the last
     region either — the composer is under it. What was invisible padding on a
     bare column is dead space inside a bordered one, so it comes off. */
  .na-plan-side {
    flex:1 1 auto !important; min-width:0; max-width:none; overflow-y:visible;
    padding-bottom:14px;
  }
  .na-plan-sep { display:none }
  /* Three columns on a phone is three unreadable columns. Stacked, the order is
     the reading order: what happened, what it says, what it looks like. The
     separators go with them — there is nothing left to adjust. */
  .na-frames { flex-direction:column }
  .na-frame { flex:0 0 auto !important; max-height:none }
  .na-frame-centre { padding:2px }
  /* Stacked, there are no side panels left to protect, so the split column stops
     being one: the reading and the bar are two blocks in the same flow and the
     bar keeps whatever height it needs. */
  .na-frame-centre.is-split { overflow:visible }
  .na-frame-centre.is-split > .na-frame-centre-read { overflow:visible; padding:2px }
  .na-frame-dock { overflow:visible; padding:10px 2px 2px }
  .na-frame-sep { display:none }
  /* The name and what happened to it both need the width; side by side neither
     gets it, and a truncated refusal is a refusal that did not carry its reason. */
  .na-files-row { grid-template-columns:minmax(0,1fr) }
  .na-files-state { text-align:left }
  .na-lib-listing { max-height:calc(var(--screen-h) * .4) }
}

/* ── the hypergraph loader ───────────────────────────────────────────────
   A traversal fills its host; the panel beside it says what is being done.
   Ported from newapp.html's .ngl-* rules (BIBLE.md §8.8) — the geometry is
   transcribed, the mounting is not, because this shell has no board to pin to. */
.na-hg{display:flex;gap:14px;align-items:stretch;width:100%;min-height:190px;
  border:1px solid var(--border);border-radius:10px;background:var(--bg2);padding:12px}
.na-hg-cw{flex:1 1 auto;min-width:0;position:relative}
.na-hg-c{position:absolute;inset:0;width:100%;height:100%;display:block}
.na-hg-side{flex:0 0 190px;display:flex;flex-direction:column;gap:6px;justify-content:center;min-width:0}
.na-hg-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px}
.na-hg-title{font-size:12px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.na-hg-pct{font-size:13px;font-weight:700;color:var(--text);font-variant-numeric:tabular-nums}
/* Stop, in the loader's own head. Quiet by default — it is an exit, not the thing
   the panel is for — and never allowed to shrink out of the head the way the
   title is, because a wait with no visible way out is the whole problem. */
.na-hg-stop{flex:0 0 auto;font:inherit;font-size:11px;font-weight:600;line-height:1;
  padding:3px 8px;border-radius:999px;border:1px solid var(--border);
  background:var(--surface,transparent);color:var(--muted);cursor:pointer}
.na-hg-stop:hover:not(:disabled){color:var(--text);border-color:var(--text)}
.na-hg-stop:disabled{opacity:.55;cursor:default}
.na-hg-bar{height:5px;border-radius:3px;background:var(--border);overflow:hidden}
/* The four-stop ramp, transcribed — the same colours the traversal walks through,
   so the bar and the graph read as one object rather than two. No width
   transition: the width is already eased frame by frame by the loader's clock,
   and a second easing on top of it only makes the bar lag the walk. */
.na-hg-fill{display:block;height:100%;width:0;border-radius:3px;
  background:linear-gradient(90deg,#12b5c9,#2f6bff,#7a52e6,#c23f9e)}
:root[data-theme="dark"] .na-hg-fill{background:linear-gradient(90deg,#35E0E0,#5B8AFF,#9F7BFF,#E070C0)}
.na-hg-phase{font-size:11px;color:var(--text);opacity:.85}
.na-hg-micro{font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}
.na-hg-clock{font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}

/* THE PLANNER'S TRAVERSAL IS A CARD IN THE MIDDLE OF THE PANEL, not a band across
   the top of it. Two reasons, and neither is taste:

     · while a request is being worked out the traversal is the ONLY thing on the
       panel, and pinning the only thing on a screen to its top edge leaves the eye
       in one corner of an empty page;
     · a graph stretched to the full width of a 1600px window is a graph nobody can
       follow — the nodes drift apart until the structure stops reading as one.

   So it is capped, centred, and given the height back that the width gave up. It
   is DELIBERATELY not the same shape as the one in the quick-check dialog: that one
   fills a card that is already the right size, this one has to make its own.

   `is-traversing` rather than `is-working`, because the working phase also draws
   the server's own questions and a refusal, and both of those want the top. */
.na-plan.is-traversing .na-plan-main { flex:1 1 auto; min-height:0; justify-content:center }
/* Narrower than the panel and taller than the band it replaces: the shape a graph
   reads in is closer to square than to a letterbox. The card is the surface — the
   stacked layout above draws the graph on its own sunken well inside it. */
.na-plan.is-traversing .na-hg {
  width:100%; max-width:560px; margin:0 auto; padding:18px;
  /* Taller than the band it replaces, and it SHRINKS. A fixed 400px card on a
     short window stops fitting, and a flex child that cannot shrink does not
     centre — it clings to the top and runs under the composer. `min()` against
     the room actually available is what keeps "in the middle" true at every
     height, rather than only at the one it was designed on. */
  flex:0 1 auto; min-height:0; height:min(440px, 100%);
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface); box-shadow:var(--elev-2);
}
.na-plan.is-traversing .na-hg-cw { min-height:0 }

/* Narrow: the panel goes under the traversal rather than squeezing it to a sliver. */
@media (max-width: 720px){
  .na-hg{flex-direction:column;min-height:240px}
  .na-hg-cw{min-height:150px}
  .na-hg-side{flex:0 0 auto}
}
/* The rail's traversal runs card-sized. */

/* ── setup screens: forms with labels, choices, folded detail ────────────────
   Compute, My Computer and the credential forms in Integrations are the first
   screens here that ask a user to FILL SOMETHING IN rather than pick from what is
   already there. A settings row puts its label and value on one line, which is
   right for a fact and wrong for a field: a box that stretches across the panel
   with its label pushed to the far left is a box nobody can see the label of.

   The rest of this block is the same argument in three more places: a choice
   between two ways of doing something is a card you click, not a radio you find;
   an install command is monospace and must not wrap; and everything the customer's
   IT team needs is present but folded, because they are not the person on the
   screen and the person on the screen should not have to scroll past them. */
.na-label-row{display:flex;flex-direction:column;gap:4px;margin-top:10px}
.na-label-row .na-label{margin:0}
.na-label-row .na-input,.na-label-row .na-select{width:100%}
.na-connform{margin-top:8px}

/* The Notifications checkboxes, stacked with spacing — scoped to this one wrapper
   so the shared `.na-check` (also an admin toolbar) is not restyled. */
.na-checks{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:4px 0}
/* Each row's own class, not `.na-check` — that one sizes a bare checkbox input to
   14x14, and a flex child gets blockified, so putting it on this label instead of
   the input squashed the row and overlapped the checkbox with its own text. */
.na-check-row{display:flex;align-items:center;gap:8px}

.na-choice{display:flex;align-items:flex-start;gap:10px;cursor:pointer}
.na-choice .na-check-box{margin-top:3px}
.na-choice .na-subtitle{margin:2px 0 0}
/* Indented and ruled off: what is under it belongs to the choice above it, and a
   reveal that sits flush with the choices reads as a fourth choice. */
.na-reveal{margin-top:12px;padding:12px 0 0 26px;border-top:1px solid var(--border)}

/* Stacked, left-aligned buttons that carry two lines each — a heading and what it
   is for. `.na-actions` is a row of small buttons; these are neither. */
.na-actions-stack{flex-direction:column;align-items:stretch;gap:8px}
.na-actions-stack .na-btn{display:flex;flex-direction:column;align-items:flex-start;gap:2px;text-align:left;padding:10px 12px}
.na-actions-stack .na-muted{font-size:11px}

.na-pre{white-space:pre;overflow-x:auto;background:var(--bg3);border:1px solid var(--border);
  border-radius:8px;padding:10px 12px;margin:6px 0 0;font-family:var(--mono);font-size:11.5px;color:var(--text)}

.na-fold{margin-top:10px;border-top:1px solid var(--border);padding-top:8px}
.na-fold>summary{cursor:pointer;font-size:12px;font-weight:600;color:var(--text);list-style:none}
.na-fold>summary::-webkit-details-marker{display:none}
.na-fold>summary::before{content:'▸ ';color:var(--muted)}
.na-fold[open]>summary::before{content:'▾ '}
.na-fold .na-subtitle{margin:6px 0 0}

/* ── the operator surfaces: tables, chips, charts ────────────────────────────
   Admin · user management and Admin · analytics. Both live in a WIDE panel — the
   settings overlay's 880px is right for a form and wrong for a twenty-column table
   or a heatmap, and squeezing either into it makes both unreadable. */
.na-modal-wide{width:min(1240px,calc(var(--screen-w) * .96));height:min(860px,calc(var(--screen-h) * .92))}
.na-panel-body{flex:1;overflow:auto;padding:14px 16px;min-width:0}

/* SCOPED to the panel. `.na-table` is already the workspace's project table, with
   its own padding and type scale; an unscoped override here would have quietly
   reshaped a screen this task never touched. */
.na-panel-body .na-table{font-size:12.5px}
.na-panel-body .na-table th{font-weight:600;color:var(--muted2);font-size:11px;padding:8px 10px;border-bottom:1px solid var(--border2)}
.na-panel-body .na-table td{padding:9px 10px}
.na-panel-body .na-table tr.is-dim td{opacity:.55}
.na-admin-list{overflow-x:auto}
.na-admin-acts{white-space:nowrap;text-align:right}
.na-admin-acts .na-btn{margin-left:4px}
.na-admin-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:4px 0 10px}
.na-admin-toolbar .na-input{flex:1;min-width:180px;font-family:var(--sans)}
.na-admin-toast{position:sticky;top:0;z-index:2;margin-bottom:8px}

.na-chips{display:flex;gap:6px;flex-wrap:wrap}
.na-chip-filter{background:var(--bg3);color:var(--muted2);border:1px solid var(--border);
  border-radius:999px;padding:5px 11px;font-size:11.5px;font-family:var(--sans);cursor:pointer}
.na-chip-filter:hover{color:var(--text);border-color:var(--border2)}
.na-chip-filter.is-active{background:var(--blue-dim);color:var(--text);border-color:var(--blue)}

.na-badge{display:inline-block;background:var(--bg3);border:1px solid var(--border);border-radius:5px;
  padding:2px 7px;font-size:10.5px;color:var(--muted2);white-space:nowrap}
.na-badge.is-admin{border-color:var(--blue);color:var(--blue)}
.na-badge.is-active{border-color:var(--green);color:var(--green)}
.na-badge.is-pending_approval{border-color:var(--amber);color:var(--amber)}
.na-badge.is-disabled,.na-badge.is-rejected,.na-badge.is-deleted{border-color:var(--red);color:var(--red)}
.na-muted.is-bad{color:var(--red)}

/* ── promo codes (adminpromos.js) ──────────────────────────────────────────
   A CODE THAT CANNOT BE REDEEMED IS DIMMED, NOT HIDDEN. Off, expired and used up
   are three different dead states an operator acts on differently — switch it back
   on, move the date, mint a new one — so the row stays legible and readable, and
   only recedes. A list that dropped them would lose the redemption record, which
   is the main reason to turn a code off rather than delete it. */
.na-promo-row.is-dead .na-key,
.na-promo-row.is-dead .na-muted{opacity:.6}
/* The form wraps rather than scrolls: it is five controls, and on a narrow
   settings pane a horizontal scrollbar over a form is a control you cannot find. */
.na-promo-amounts,.na-promo-when{flex-wrap:wrap;margin-top:8px}
.na-promo-amounts .na-input,.na-promo-when .na-input{max-width:150px}
/* Narrow, because it holds a date and nothing else — a full-width date input beside
   two buttons pushes them off the row. */
.na-promo-date{max-width:150px}

.na-budget{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 11px;margin-bottom:10px;
  border:1px solid var(--border);border-radius:10px;font-size:12px}
.na-budget-label{font-size:12px}
.na-budget-track{flex:1;min-width:110px;height:7px;border-radius:6px;background:var(--bg4);overflow:hidden}
.na-budget-fill{height:100%;background:var(--blue)}
.na-budget-fill.is-near{background:var(--amber)}
.na-budget-fill.is-over{background:var(--red)}
.na-budget-check{margin-top:0}
.na-budget .na-input{font-family:var(--sans)}

/* ── charts ──────────────────────────────────────────────────────────────── */
.na-ch-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;margin-bottom:14px}
.na-ch-kpi,.na-ch-cohort{background:var(--bg3);border:1px solid var(--border);border-radius:10px;padding:10px 12px}
.na-ch-kpi-v{font-size:20px;font-weight:600;color:var(--text);line-height:1.15}
.na-ch-kpi-l{font-size:11px;color:var(--muted2);margin-top:2px}
.na-ch-kpi-s{font-size:10.5px;color:var(--muted);margin-top:1px}
.na-ch-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:10px}
.na-ch-card{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:12px 14px;min-width:0}
.na-ch-card.is-wide{grid-column:1/-1}
.na-ch-title{font-size:12px;font-weight:600;color:var(--text);margin-bottom:10px}
.na-ch-empty{color:var(--muted);font-size:12px;padding:10px 0}
.na-ch-note{color:var(--muted);font-size:11px;margin-top:8px;line-height:1.5}
.na-ch-footnote{margin-top:14px}
.na-ch-cohorts{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:12px}

.na-ch-bars{display:flex;flex-direction:column;gap:6px}
.na-ch-bar-row,.na-ch-funnel-row{display:grid;grid-template-columns:minmax(70px,26%) 1fr auto;align-items:center;gap:10px}
.na-ch-bar-name{font-size:11.5px;color:var(--muted2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.na-ch-bar-track{height:8px;border-radius:6px;background:var(--bg4);overflow:hidden}
.na-ch-bar-fill,.na-ch-funnel-fill{height:100%;background:var(--blue);border-radius:6px}
.na-ch-bar-count{font-size:11px;color:var(--muted);font-family:var(--mono)}
.na-ch-funnel{display:flex;flex-direction:column;gap:7px}

.na-ch-svg{width:100%;height:130px;display:block}
.na-ch-axis{stroke:var(--border2)}
.na-ch-tick{font-size:9px;fill:var(--muted)}
.na-ch-line{fill:none;stroke-width:1.8}
.na-ch-area{stroke:none;opacity:.13}
.na-ch-arc{fill:none;stroke-linecap:butt}
.na-ch-donut{display:flex;gap:14px;align-items:center;flex-wrap:wrap}
.na-ch-donut svg{width:120px;height:120px;flex:none}
.na-ch-donut-total{font-size:20px;font-weight:600;fill:var(--text)}
.na-ch-donut-cap{font-size:8px;fill:var(--muted)}
.na-ch-legend{display:flex;flex-direction:column;gap:6px;font-size:11.5px;color:var(--muted2)}
.na-ch-legend span{display:flex;align-items:center;gap:6px}
.na-ch-legend i{width:9px;height:9px;border-radius:2px;flex:none;background:var(--ch1)}

/* One rule per series, three properties each — a bar's background, a line's stroke,
   a legend swatch. `chart.js` picks the CLASS and never a colour. */
.na-ch-1{--ch:var(--ch1)} .na-ch-2{--ch:var(--ch2)} .na-ch-3{--ch:var(--ch3)} .na-ch-4{--ch:var(--ch4)}
.na-ch-5{--ch:var(--ch5)} .na-ch-6{--ch:var(--ch6)} .na-ch-7{--ch:var(--ch7)} .na-ch-8{--ch:var(--ch8)}
.na-ch-bar-fill[class*="na-ch-"],.na-ch-legend i[class*="na-ch-"]{background:var(--ch)}
path.na-ch-line[class*="na-ch-"]{stroke:var(--ch)}
path.na-ch-area[class*="na-ch-"],circle.na-ch-arc[class*="na-ch-"],path.na-ch-arc[class*="na-ch-"]{fill:none;stroke:var(--ch)}
path.na-ch-area[class*="na-ch-"]{fill:var(--ch);stroke:none}

/* A table of strings — the file-attach preview's other half, alongside `xy()`.
   Its own scroller so a wide CSV does not push the sidebar out with it. */
.na-ch-table-wrap{max-width:100%;overflow-x:auto}
.na-ch-table{border-collapse:collapse;font-size:11.5px;font-family:var(--mono);white-space:nowrap}
.na-ch-table th,.na-ch-table td{padding:3px 8px;text-align:left;border-bottom:1px solid var(--border)}
.na-ch-table th{color:var(--muted2);font-weight:600}
.na-ch-table td{color:var(--text)}

.na-ch-hm{display:grid;grid-template-columns:34px repeat(24,1fr);gap:2px}
.na-ch-hm-lbl{font-size:9px;color:var(--muted);display:flex;align-items:center}
.na-ch-hm-lbl.is-hour{justify-content:center}
.na-ch-hm-cell{height:13px;border-radius:2px;background:var(--bg4)}
.na-ch-hm-cell.is-on{background:var(--ch1)}

.na-ch-kw{display:flex;flex-wrap:wrap;gap:6px}
.na-ch-kw span{display:inline-flex;align-items:center;gap:5px;background:var(--bg3);border:1px solid var(--border);
  border-radius:999px;padding:3px 9px;font-size:11.5px;color:var(--muted2)}
.na-ch-kw b{color:var(--text);font-family:var(--mono);font-size:10.5px}
.na-ch-prompts{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}
.na-ch-prompt-q{font-size:12px;color:var(--text)}
.na-ch-prompt-meta{font-size:10.5px;color:var(--muted);margin-top:2px}

/* Printing the analytics screen, and ONLY when it asked to be printed. Gated on
   `na-printing`, set by the Print button and cleared afterwards — ungated, these
   rules would blank every other print of the app. */
@media print {
  html.na-printing .na-topbar,html.na-printing #nergyapp-root{display:none!important}
  html.na-printing .na-overlay{position:static;background:none;display:block}
  html.na-printing .na-modal{width:auto;height:auto;border:0;background:none}
  html.na-printing .na-panel-body{overflow:visible;padding:0}
  html.na-printing .na-modal-top .na-actions{display:none}
  html.na-printing .na-ch-card{break-inside:avoid;border-color:#ccc}
  html.na-printing .na-ch-prompts{max-height:none;overflow:visible}
  /* `@page` is one rule for the whole document and the sheet below sets it to
     zero (it is how the browser's own header and footer are got rid of), so this
     print draws its own margin rather than inheriting one it no longer has. */
  html.na-printing .na-overlay{padding:14mm 13mm}
}

/* ── the print sheet ─────────────────────────────────────────────────────────
   Save as PDF is the browser's own (`printdoc.js` says why), and the sheet is
   the only thing on the page while it happens. The app is hidden WHOLE rather
   than the sheet being dug out of it: the frames are bounded scrollers three
   deep, so a stylesheet that merely hid their siblings would print one screenful
   of a twelve-page document and stop.

   ON PAPER, IN PAPER COLOURS. One of the two themes is a near-black page, and a
   dark-theme reader pressing Save as PDF must not be handed twelve pages of ink.
   So the sheet redeclares the surface ladder for the print only, at the values
   `theme.css` already uses for its light theme — not a second palette.

   THE MARGIN IS DRAWN HERE, NOT ASKED FOR. `@page { margin:0 }` is what stops
   the browser printing the date, the time, the page URL and `1/7` into the
   corners: those are drawn by the browser into the page margin, no stylesheet
   can restyle them, and with no margin there is nowhere to put them. The white
   space they were sitting in is then ours, and the sheet's own table lays it out
   — `<thead>` and `<tfoot>` repeat on every page, which is the only way to get a
   top and bottom margin onto page two and after (padding lands on page one and
   nowhere else). The side margins are the cells' own padding, which does repeat.
   A reader who WANTS the browser's furniture back still has the checkbox for it
   in the print dialogue; it just no longer has room, and defaults to off. */
.na-printsheet { display:none }

@media print {
  html.na-printing-doc, html.na-printing-doc body { background:#fff !important; height:auto !important }
  html.na-printing-doc body > *:not(.na-printsheet) { display:none !important }
  html.na-printing-doc .na-printsheet {
    display:block; position:static;
    --bg:#fff; --bg2:#fff; --bg3:#f2f4f7; --bg4:#e4e7ec;
    --surface:#fff; --surface-raised:#fff; --surface-sunken:#f6f8fd;
    --border:#d0d5dd; --border2:#c4cad5; --border3:#98a2b3;
    --text:#101828; --muted:#667085; --muted2:#475467; --muted3:#2e3b52;
    --blue:#A85A1E; --blue-dim:rgba(168,90,30,.10); --blue-glow:rgba(168,90,30,.18);
    --elev-1:none; --elev-2:none; --elev-3:none; --ring:none;
    color:var(--text); background:#fff; font-size:11pt; line-height:1.55;
  }
  html.na-printing-doc .na-printsheet * { box-shadow:none !important }

  /* ── the page: three rows, and two of them are the margin ────────────────── */
  .na-printsheet-page { width:100%; border-collapse:collapse; table-layout:fixed }
  /* The one table on the sheet that MUST break across pages — the document is
     inside it. The rule below that keeps tables whole would otherwise push a
     nine-page report at a single page and clip eight of them. */
  .na-printsheet-page, .na-printsheet-page > thead > tr > td,
  .na-printsheet-page > tbody, .na-printsheet-page > tbody > tr,
  .na-printsheet-page > tbody > tr > td { break-inside:auto !important }
  .na-printsheet-run { padding:14mm 14mm 6mm; border:0 }
  .na-printsheet-cell { padding:0 14mm; border:0; vertical-align:top }
  .na-printsheet-foot { padding:0 14mm 13mm; border:0; height:2mm }

  /* THE RUNNING HEAD, on every page. The mark keeps its colour on paper —
     `print-color-adjust` because browsers drop backgrounds and flatten colour by
     default, and a brandmark printed in grey is not the brandmark. */
  .na-printsheet-runbar {
    display:flex; align-items:center; justify-content:space-between; gap:8mm;
    border-bottom:.75pt solid #d0d5dd; padding-bottom:2.5mm;
  }
  .na-printsheet-brand {
    display:inline-flex; align-items:center; gap:6px; flex:0 0 auto;
    color:#A85A1E; print-color-adjust:exact; -webkit-print-color-adjust:exact;
  }
  .na-printsheet-mark { width:25px; height:25px; display:block }
  .na-printsheet-word {
    font-size:10.5pt; font-weight:650; letter-spacing:.08em; color:#101828;
  }
  /* What the document is, small, on the right — the half of the browser's header
     that was worth keeping. One line: on page six a wrapped title is a second
     rule of grey text, not a reminder. */
  .na-printsheet-runtitle {
    font-size:8.5pt; color:#667085; text-align:right;
    /* `min-width:0` or a flex item does not shrink below its own text, and a
       long tab name would push the bar off the side of the paper instead of
       being cut with an ellipsis. */
    min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;
  }

  /* ── the first page's head ───────────────────────────────────────────────── */
  .na-printsheet-head { margin:6mm 0 9mm }
  .na-printsheet-title { font-size:20pt; font-weight:600; margin:0 0 3mm; line-height:1.2;
    letter-spacing:-.01em }
  .na-printsheet-when { margin:0; font-size:9pt; color:#667085 }
  .na-printsheet-sec { margin:0 0 18px }
  .na-printsheet-h { font-size:13pt; font-weight:600; margin:0 0 6px; break-after:avoid }
  .na-printsheet-skip { margin:0; font-size:10pt; color:#667085; font-style:italic }
  /* A figure, a table or a card split across a page break is one a reader has to
     reassemble; a paragraph split across one is just reading. Scoped to the
     BODY, so the page table above — which is the thing that has to break — is
     not caught by a rule written about the tables inside it. */
  .na-printsheet-body figure, .na-printsheet-body table,
  .na-printsheet-body .na-block-scroll { break-inside:avoid }
  .na-printsheet h1, .na-printsheet h2, .na-printsheet h3, .na-printsheet h4 { break-after:avoid }
  /* The clone scrolls on screen and must not on paper, or the copy prints its
     first screenful and clips everything under it. */
  .na-printsheet .na-frame, .na-printsheet .na-block-scroll,
  .na-printsheet .na-frame-centre-read, .na-printsheet .na-results {
    overflow:visible !important; max-height:none !important; height:auto !important;
  }
  /* AND A COLUMN OF READING MUST BE ABLE TO BREAK IN THE MIDDLE. A flex
     container is monolithic to the printer — Chromium does not fragment one, it
     moves the whole box to the next page — so a two-section reading printed as
     a half-empty page followed by a page with one section on it. On paper the
     reading column is a column of blocks and nothing needs it to be flex, so it
     is not one; the `gap` it was using for rhythm comes back as a margin. */
  .na-printsheet .na-context { display:block !important }
  .na-printsheet .na-context > * + * { margin-top:8px }
  /* Paragraphs are separated on paper, where there is no scroll to give them
     rhythm — run together, a three-sentence section reads as one block. And one
     line of a paragraph alone at the foot or head of a page is a line the reader
     turns back for; two is the floor a typesetter would set. */
  .na-printsheet p { margin:0 0 8px; orphans:2; widows:2 }
  /* A FIGURE MUST NOT EAT A PAGE. These arrive as SVG with a viewBox and no
     intrinsic size, so unclamped they take the full column width at whatever
     aspect they were drawn at — which on a portrait page is most of it. Height
     is what is capped, and the width follows, so nothing is squashed. */
  .na-printsheet img { max-width:100% !important; height:auto !important; max-height:105mm }
  /* Nothing that only makes sense in the window: the fold chevrons, the sticky
     control that started the print, the follow-up dock. */
  .na-printsheet .na-frame-fold, .na-printsheet .na-frame-sep,
  .na-printsheet .na-frame-tools, .na-printsheet .na-frame-dock { display:none !important }
  /* ZERO, ON PURPOSE, AND THE SHEET DRAWS THE MARGIN ITSELF — the long note
     above this block says why. Changing this back to a real margin brings the
     browser's date, time, URL and page count back with it. */
  @page { margin:0 }
}

/* ── signing in ──────────────────────────────────────────────────────────────
   TWO PANELS: what the product does on the left, the form on the right. One
   state at a time on the right, and the left panel is decoration in the strict
   sense — below 900px it is gone and the form is the whole screen, unchanged.

   The card has no background of its own on the wide layout: the right panel is
   the surface, and the page's colour shows through, which is what keeps it
   correct in both themes without a second set of surface tokens.

   The four blocked states share this styling ON PURPOSE. They are told apart by
   what they SAY — a heading, the service's own sentence, and guidance — not by
   colour, because a user who cannot see the difference between two shades of red
   would otherwise be back to "sign-in failed". */

.na-signin { display:grid; grid-template-columns:56% 44%; min-height:100%; align-items:stretch }
.na-signin-pane {
  display:flex; align-items:center; justify-content:center;
  padding:32px clamp(20px,3vw,54px); overflow-y:auto;
  background:var(--bg2); border-left:1px solid var(--border);
}
.na-signin-card {
  width:100%; max-width:420px;
  display:flex; flex-direction:column; gap:4px;
}
.na-signin-brand { color:var(--blue); margin-bottom:12px; line-height:0 }
.na-signin-title { font-size:20px; font-weight:600; color:var(--text); margin-bottom:6px }
.na-signin-lead { font-size:13.5px; color:var(--muted2); line-height:1.55; margin-bottom:10px }
.na-signin-body { display:flex; flex-direction:column; gap:4px }
.na-signin-passwordrow { display:flex; flex-direction:column; gap:4px }
.na-signin-passwordrow[hidden] { display:none }
.na-signin-go { margin-top:16px; width:100%; justify-content:center }
.na-signin-go[hidden] { display:none }
.na-signin-forgot, .na-signin-back { align-self:flex-start; margin-top:14px; font-size:13px }
.na-signin-sendcode { align-self:flex-start; margin:10px 0 4px }
.na-signin-problem { margin-top:14px }
.na-signin-problem:empty { display:none }
.na-signin-provider:empty { display:none }

/* The second factor's methods, when there is more than one to choose from. */
.na-signin-methods { display:flex; flex-wrap:wrap; gap:8px; margin:6px 0 12px }
.na-signin-method {
  font:inherit; font-size:13px; cursor:pointer;
  padding:7px 12px; border-radius:var(--radius);
  border:1px solid var(--border2); background:transparent; color:var(--muted2);
}
.na-signin-method:hover { color:var(--text); border-color:var(--border2) }
.na-signin-method.is-on { color:var(--text); border-color:var(--blue); box-shadow:inset 0 0 0 1px var(--blue) }
.na-signin-method:focus-visible { outline:2px solid var(--blue); outline-offset:2px }

/* A state where the password was right and the answer is still no. The rule on
   the left is the only ornament, and it is the same for all four: what differs
   is the words. */
.na-signin-blocked {
  display:flex; flex-direction:column; gap:10px;
  border-left:3px solid var(--red); padding-left:14px;
}
.na-signin-blocked-title { font-size:16px; font-weight:600; color:var(--text) }
/* The service's OWN sentence, relayed rather than paraphrased — set apart so it
   reads as a quotation of what the service said, not as our summary of it. */
.na-signin-said { font-size:14px; color:var(--text); line-height:1.5 }
.na-signin-guidance { font-size:13.5px; color:var(--muted2); line-height:1.55 }
.na-signin-caveat { font-size:12.5px; color:var(--muted); line-height:1.55 }
.na-signin-blocked .na-btn { align-self:flex-start; margin-top:4px }

/* ── the two full-bleed screens ──────────────────────────────────────────────
   Both the way in and the workspace are now two panels wide, and neither fits
   the centred reading column `#nergyapp-root` is otherwise set in. Each screen
   has its OWN full-bleed hook (`.na-signin-full`, `.na-home`) — added in
   `render()` and taken off in teardown, exactly as the project window does with
   `.na-window`. Sign-in once borrowed the plan gate's `.na-gate` here, which drew
   a stray amber border down its left edge once that class gained one. */

/* `min-height:100vh` is on the base rule and has to be undone here: with a fixed
   top bar above them these screens are SHORTER than the viewport, and a minimum
   that says otherwise pushes the bottom of both panels off the screen. */
#nergyapp-root.na-signin-full,
#nergyapp-root.na-home {
  max-width:none; padding:0; gap:0; overflow:hidden;
  min-height:0; height:var(--screen-h);
}
body.na-has-topbar #nergyapp-root.na-signin-full,
body.na-has-topbar #nergyapp-root.na-home { height:calc(var(--screen-h) - var(--topbar-h)) }

/* ── the sign-in showcase (left panel) ───────────────────────────────────── */

.na-sc {
  position:relative; overflow:hidden;
  display:flex; flex-direction:column;
  padding:36px clamp(24px,3.4vw,52px);
  background:
    linear-gradient(160deg,var(--blue-dim),transparent 55%),
    linear-gradient(var(--border) 1px,transparent 1px) 0 0/24px 24px,
    linear-gradient(90deg,var(--border) 1px,transparent 1px) 0 0/24px 24px,
    var(--bg);
}
.na-sc-mark { color:var(--blue); flex:0 0 auto }
/* THE STAGE IS THE WHOLE PANEL. With the wordmark gone the deck fills the panel
   top to bottom, and each slide centres itself in it — so the content sits in the
   middle of the left half rather than pooling under a mark it no longer carries. */
.na-sc-stage { flex:1 1 auto; position:relative; min-height:0 }
.na-sc-slide {
  position:absolute; inset:0;
  display:flex; flex-direction:column; justify-content:center; gap:16px;
  opacity:0; transform:translateY(10px); pointer-events:none;
  transition:opacity .55s ease, transform .55s ease;
}
.na-sc-slide.is-on { opacity:1; transform:none; pointer-events:auto }
.na-sc-kicker {
  display:flex; align-items:center; gap:8px;
  font-family:var(--mono); font-size:clamp(11px,0.9vw,13px); font-weight:600;
  letter-spacing:.14em; text-transform:uppercase; color:var(--blue);
}
.na-sc-dot { width:8px; height:8px; border-radius:50%; background:var(--blue) }
/* SIZED TO FILL THE PANEL, not to sit in a narrow column inside it. Every width
   here is FLUID — a vw-driven clamp on the type, a `min(px,100%)` cap on the figure
   — so the content keeps pace with the panel from ~1280 up through 1920+ instead of
   freezing at a fixed px and leaving dead grid to the right on a wide monitor. The
   ceilings are held where the tallest slide (world model) still clears the panel
   height with room to spare at a short (768) viewport. */
.na-sc-headline {
  margin:0; font-size:clamp(30px,3.3vw,47px); line-height:1.12;
  letter-spacing:-.02em; color:var(--text); max-width:26ch; font-weight:650;
}
.na-sc-body { margin:0; font-size:clamp(16px,1.5vw,20px); line-height:1.6; color:var(--muted2); max-width:62ch }
.na-sc-figure {
  background:var(--bg2); border:1px solid var(--border); border-radius:14px;
  padding:16px 18px; max-width:min(800px,100%); box-shadow:var(--shell-elev);
}
.na-sc-svg { display:block; width:100%; height:auto }

/* ── format river: the tags drift, and fade at the edges ─────────────────────
   figureFormats draws each row's six tags twice end to end — repeat distance is
   384 user units, the full viewBox width — so a translateX of exactly 384 loops
   with no seam. The reverse row's second copy sits to the RIGHT only, so it runs
   −384 → 0; the other direction would bare the left edge. transform-box:fill-box
   keeps the translate in the SVG's own user units across browsers. */
@keyframes na-sc-drift-fwd { from { transform:translateX(0) } to { transform:translateX(-384px) } }
@keyframes na-sc-drift-rev { from { transform:translateX(-384px) } to { transform:translateX(0) } }
.na-sc-fmt-row { transform-box:fill-box; transform-origin:0 0; will-change:transform }
.na-sc-fmt-row[data-dir="1"]  { animation:na-sc-drift-fwd 32s linear infinite }
.na-sc-fmt-row[data-dir="-1"] { animation:na-sc-drift-rev 38s linear infinite }
.na-sc-formats {
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
}
@media (prefers-reduced-motion:reduce) { .na-sc-fmt-row { animation:none } }

/* ── model figure: core glow, orbit rings, flowing particles ─────────────────
   figureModel draws a radial glow behind the core and two dashed rings that turn
   about it in OPPOSITE directions. Each ring is a CLEAN wrapper <g>: the circle
   inside carries the position (cx/cy 150 60), so the wrapper is free to rotate
   about the core with transform-origin in view-box units. Every colour here is a
   stop or stroke token — none is written as a value. Motion is always on. */
.na-sc-glow-0 { stop-color:var(--blue); stop-opacity:.24 }
.na-sc-glow-1 { stop-color:var(--blue); stop-opacity:0 }
@keyframes na-sc-spin     { to { transform:rotate(360deg) } }
@keyframes na-sc-spin-rev { to { transform:rotate(-360deg) } }
.na-sc-orbit, .na-sc-field {
  transform-box:view-box; transform-origin:150px 60px; will-change:transform;
}
.na-sc-orbit { opacity:.6;  animation:na-sc-spin     16s linear infinite }
.na-sc-field { opacity:.32; animation:na-sc-spin-rev 26s linear infinite }

/* ── vault figure: wall pulse, scan sweep ────────────────────────────────────
   The pulse is a SEPARATE rect ringing the enclosure — the real wall never fades.
   The scan band is clipped to the enclosure and slides top→bottom in view-box
   units. Both take their colour from stroke/stop tokens. Always on. */
@keyframes na-sc-vault-pulse { 0%,100% { opacity:.1 } 50% { opacity:.5 } }
.na-sc-vault-pulse { animation:na-sc-vault-pulse 3.2s ease-in-out infinite }
@keyframes na-sc-vault-scan { from { transform:translateY(-18px) } to { transform:translateY(96px) } }
.na-sc-vault-scan {
  transform-box:view-box; will-change:transform;
  animation:na-sc-vault-scan 3.6s ease-in-out infinite;
}
.na-sc-scan-0 { stop-color:var(--blue); stop-opacity:0 }
.na-sc-scan-1 { stop-color:var(--blue); stop-opacity:.34 }

.na-sc-caption {
  font-family:var(--mono); font-size:9px; letter-spacing:.05em;
  color:var(--muted); text-align:center; margin-top:8px;
}
.na-sc-metrics { display:flex; gap:48px; flex-wrap:wrap }
.na-sc-metric-value { font-family:var(--mono); font-size:26px; font-weight:700; color:var(--blue); line-height:1 }
.na-sc-metric-label { font-size:13px; color:var(--muted); margin-top:3px }
.na-sc-dots { display:flex; gap:7px; margin-top:14px }
.na-sc-pip {
  width:26px; height:5px; border-radius:3px; border:0; padding:0;
  background:var(--border2); cursor:pointer; transition:.2s;
}
.na-sc-pip.is-on { background:var(--blue); width:34px }
.na-sc-pip:focus-visible { outline:2px solid var(--blue); outline-offset:3px }

/* ── "Contact us" note (top bar) ─────────────────────────────────────────────
   On the signed-out gate the bar offers a quiet way to reach us in place of the
   account chip. The note is a sibling in the flex actions row, so un-hiding it
   inline would shove the buttons; it is taken out of flow instead and dropped as
   a small panel just below the bar, right-aligned under its button. The row is
   made the positioning context for it. */
.na-topbar-actions { position:relative }
.na-contact-note {
  position:absolute; top:100%; right:0; margin-top:6px; z-index:20;
  max-width:260px; padding:8px 12px; font-size:12px;
  background:var(--bg2); border:1px solid var(--border); border-radius:8px;
  color:var(--muted2); box-shadow:var(--shell-elev);
}

/* ── the providers, and the way to a new account ─────────────────────────── */

.na-signin-sso { margin-top:18px }
.na-signin-divider {
  display:flex; align-items:center; gap:10px;
  font-size:12px; color:var(--muted); margin-bottom:12px;
}
.na-signin-divider::before, .na-signin-divider::after {
  content:''; flex:1 1 auto; height:1px; background:var(--border2);
}
.na-signin-ssogrid { display:grid; grid-template-columns:1fr 1fr; gap:8px }
.na-signin-ssobtn {
  display:flex; align-items:center; justify-content:center; gap:8px;
  font-size:13px; padding:9px 10px; color:var(--muted3);
}
.na-signin-ssobtn:hover:not(:disabled) { color:var(--text) }
.na-signin-ssoicon { flex:0 0 auto }
.na-signin-switch { margin-top:18px; font-size:13px; color:var(--muted) }
.na-signin-create { font-size:13px; font-weight:600; color:var(--blue) }
.na-signin-registerpanel, .na-signin-resetpanel, .na-signin-mfapanel, .na-signin-forgotpanel {
  display:flex; flex-direction:column; gap:4px;
}

@media (max-width:900px) {
  .na-signin { grid-template-columns:1fr }
  .na-sc { display:none }
  .na-signin-pane { border-left:0; background:var(--bg); align-items:flex-start; padding-top:44px }
  #nergyapp-root.na-signin-full,
  body.na-has-topbar #nergyapp-root.na-signin-full { height:auto; min-height:var(--screen-h); overflow:auto }
}

/* ── the workspace, as two panels ────────────────────────────────────────── */

/* TWO COLUMNS, AND THE LEFT ONE IS WIDER. Projects carry a description column
   that wants prose width; the library's four columns are a name and three
   measurements. `minmax(0,…)` on both, because a grid track's default `auto`
   minimum is the widest thing inside it — a long file name would otherwise push
   its own column past the panel and put a scrollbar under the whole screen. */
.na-home-grid {
  display:grid; grid-template-columns:minmax(0,1.08fr) minmax(0,1fr);
  height:100%; min-height:0;
}
.na-home-panel {
  display:flex; flex-direction:column; gap:18px; min-height:0;
  overflow-y:auto; padding:28px clamp(20px,2.6vw,40px) 48px;
  background:var(--bg2);
}
/* A bounded reading column inside a full-bleed panel — so a wide screen does not
   stretch the header and table into an unreadable ribbon. */
.na-home-panel-inner {
  display:flex; flex-direction:column; gap:18px; min-height:0;
  max-width:880px; margin-inline:auto; width:100%;
}
/* The right panel: the same panel, told apart by its ground rather than by a
   heavier rule. The border is the seam between the two, drawn once. */
.na-home-lib {
  background:var(--bg); border-left:1px solid var(--border);
  /* ROOM FOR THE Quick ask PILL, which is fixed over this exact corner. This
     column scrolls, so its last row — the file count and "Show more" — came to
     rest underneath the pill and could not be pressed. */
  padding-bottom:var(--fab-clear);
}
.na-home-panel .na-workspace-body { min-height:0 }
/* The table is in a column now, not across a page. Same columns, same order,
   less air — narrowing it is what keeps all four visible without dropping one. */
.na-home-panel .na-table th { padding:10px 12px }
.na-home-panel .na-table td { padding:11px 12px }
.na-home-panel .na-cell-title { min-width:130px }
.na-home-panel .na-cell-actions .na-btn,
.na-home-panel .na-libcell-actions .na-btn { padding:7px 10px; font-size:14px }
.na-home-panel .na-cell-actions .na-btn + .na-btn { margin-left:4px }

/* ── the library panel's own table ───────────────────────────────────────── */

/* The head holds a search field rather than a button, and a field has no
   baseline worth aligning a heading to — so this one aligns on the box. */
.na-lib-head { align-items:center }
.na-lib-search { width:min(210px,42%); flex:0 0 auto; font-size:13px; padding:7px 11px }

/* FIXED, NOT AUTO. A file name is the one unbounded thing in this table, and
   under `auto` the column sizes itself to the longest one — a single deep export
   path pushes the three measurement columns off the panel. Fixed widths for
   those three and the actions, the name takes what is left, and `text-overflow`
   has a bound to work against (it has none in an auto table). */
/* EACH WIDTH IS THE LONGEST VALUE THAT COLUMN HOLDS, AND NOT A PIXEL MORE. They
   carry the larger type set below — a column sized for 11.5px mono clips the
   same measurement at 12.5px — but every pixel spent here is taken from the file
   name, which is the only unbounded thing in the row and the one users actually
   read. Sized against the widest real value ("XRDML", "2.4 MB", "13 days ago",
   "Download") rather than rounded up for comfort. */
.na-lib-table { table-layout:fixed }
.na-lib-table th:nth-child(2), .na-lib-table td:nth-child(2) { width:64px }
.na-lib-table th:nth-child(3), .na-lib-table td:nth-child(3) { width:76px }
.na-lib-table th:nth-child(4), .na-lib-table td:nth-child(4) { width:104px }
.na-lib-table th:nth-child(5), .na-lib-table td:nth-child(5) { width:108px }
.na-libcell-name { min-width:0 }
.na-libname { display:flex; align-items:center; gap:7px; min-width:0 }
.na-libname-text {
  font-weight:550; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* What a row is doing, when it is doing anything other than sitting there. */
.na-libstate {
  flex:0 0 auto; font-family:var(--mono); font-size:10px; letter-spacing:.06em;
  text-transform:uppercase; color:var(--muted2);
  border:1px solid var(--border2); border-radius:999px; padding:2px 7px;
}
.na-libstate.is-bad { color:var(--red); border-color:var(--red) }
/* The reason, under the name. Wrapped, because a sentence that is clipped is a
   reason nobody can read (Law 4). */
.na-libwhy {
  margin-top:4px; color:var(--muted); font-size:12.5px; line-height:1.45;
  white-space:normal;
}
.na-libcell-type, .na-libcell-size { white-space:nowrap }
/* THE LIBRARY TABLE HAS ITS OWN CELL CLASSES rather than borrowing the projects
   table's. They look the same and are meant to; what they must not be is the
   same selector, because two tables on one screen answering to `.na-cell-actions`
   makes "every project row offers Open and Delete" a question about six rows in
   two different lists. */
.na-libcell-when {
  color:var(--muted); white-space:nowrap;
  font-family:var(--mono); font-size:11.5px;
}
.na-libcol-actions, .na-libcell-actions { white-space:nowrap; text-align:right }
/* Row actions rest at low contrast and come up on hover or focus — present,
   not competing with the four columns that carry the information. The resting
   value is the projects table's, for the reason given there: it is a contrast
   floor, and these two tables sit on one screen. */
.na-libcell-actions .na-btn { opacity:.8; transition:opacity .12s ease }
.na-lib-table tbody tr:hover .na-libcell-actions .na-btn,
.na-libcell-actions .na-btn:focus-visible { opacity:1 }
/* A format and a size are measurements: mono, so a column of them lines up —
   the same rule the timestamps next to them follow. */
.na-libtype {
  font-family:var(--mono); font-size:11px; letter-spacing:.04em; color:var(--muted2);
}
.na-libcell-size { font-family:var(--mono); font-size:11.5px; color:var(--muted) }

/* What is on screen out of what there is, and the way to the rest. Outside the
   table's card, so it reads as a note about the list rather than a last row of
   it. */
.na-libfoot {
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:2px 2px 0;
}
.na-libfoot-count { color:var(--muted); font-size:12.5px }

/* ── the home window's type scale ────────────────────────────────────────────
   THE HOME SCREEN READS ONE STEP LARGER THAN THE REST OF THE SHELL. It is the
   screen you land on and the one you scan rather than read — two lists of names
   across a full window — and at the shell's default sizes the rows were set for
   a reading column they no longer sit in.
   SCOPED TO `.na-home-panel`, not applied to the base classes. `.na-title`,
   `.na-table` and the library's cell classes are worn by the project window and
   every panel inside it; moving them here would move the whole app, which is not
   what was asked for. Both panels carry the class, so the two lists stay the
   same size as each other — which is the point of them being one screen.
   The RATIOS between the parts are the ones the base scale already chose: a
   heading against its subtitle, a name against the measurements beside it. Each
   value below is its base plus the same step, so the screen grows without
   re-deciding its own hierarchy. */
.na-home-panel .na-title { font-size:30px }
.na-home-panel .na-subtitle { font-size:15px }
.na-home-panel .na-table { font-size:15px }
/* The column captions are the mono uppercase rule (Rule 3); they take the step
   as a caption, staying clearly smaller than the rows they head. */
.na-home-panel .na-table th { font-size:11.5px }
.na-home-panel .na-lib-search { font-size:14px }
.na-home-panel .na-notice { font-size:14.5px }

/* The measurements — a timestamp, a size, a format. Mono, so these still line up
   in a column; a step larger, so they stay legible beside a 15px name. */
.na-home-panel .na-cell-when,
.na-home-panel .na-libcell-when,
.na-home-panel .na-libcell-size { font-size:12.5px }
.na-home-panel .na-libtype { font-size:12px }
.na-home-panel .na-libstate { font-size:11px }
/* The reason under a file name is a sentence, not a label — it takes the step as
   prose and keeps its wrapping. */
.na-home-panel .na-libwhy { font-size:13.5px }
.na-home-panel .na-libfoot-count { font-size:13.5px }

/* A FILE NAME WRAPS HERE RATHER THAN ENDING IN AN ELLIPSIS. The base table clips
   it (`.na-libname-text`) because a name is the one unbounded thing in a fixed
   layout and something has to give — but in a column that also carries a state
   chip, "giving" started meaning `sem-parti…`, and a name you cannot read is not
   a name. The column is still bounded; the row grows instead, which is the same
   trade `.na-libwhy` above already makes for the reason under it. `anywhere`
   because a file name is one unbroken token with no space to wrap at. Only where
   it is needed: a name that fits still occupies exactly one line. */
.na-home-panel .na-libname { align-items:flex-start }
.na-home-panel .na-libname-text {
  white-space:normal; overflow:visible; text-overflow:clip;
  overflow-wrap:anywhere; line-height:1.35;
}
/* The chip is set on the name's first line, not on the box's centre — with a
   two-line name, centred put READING beside the second half of the word. */
.na-home-panel .na-libstate { margin-top:1px }

.na-ex {
  position:relative; overflow:hidden; min-height:0;
  display:flex; flex-direction:column; padding:22px clamp(18px,2.4vw,32px);
  background:
    linear-gradient(var(--border) 1px,transparent 1px) 0 0/22px 22px,
    linear-gradient(90deg,var(--border) 1px,transparent 1px) 0 0/22px 22px,
    var(--bg);
}
.na-ex-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px }
.na-ex-heading { font-size:19px; font-weight:650; color:var(--text); letter-spacing:-.01em }
.na-ex-count { font-family:var(--mono); font-size:11px; color:var(--muted) }
.na-ex-stage { flex:1 1 auto; position:relative; min-height:0 }
.na-ex-slide {
  position:absolute; inset:0; display:flex; align-items:stretch;
  opacity:0; transform:translateY(8px); pointer-events:none; transition:.4s ease;
}
.na-ex-slide.is-on { opacity:1; transform:none; pointer-events:auto }

.na-ex-card {
  flex:1 1 auto; display:flex; flex-direction:column; gap:8px; min-height:0;
  font:inherit; text-align:left; cursor:pointer; overflow:hidden;
  background:var(--bg2); border:1px solid var(--border); border-radius:16px;
  padding:18px 20px; box-shadow:var(--shell-elev); color:var(--text);
  transition:border-color .12s ease, transform .12s ease;
}
.na-ex-card:hover { border-color:var(--blue); transform:translateY(-2px) }
.na-ex-card:focus-visible { outline:2px solid var(--blue); outline-offset:2px }
.na-ex-mode {
  font-family:var(--mono); font-size:9.5px; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; color:var(--ex-tone);
}
.na-ex-title { font-size:19px; font-weight:700; letter-spacing:-.01em; margin:0 }
/* The figures take the height that is left. Each drawing is `xMidYMid meet`, so
   a tall panel letterboxes the schematic rather than stretching it out of shape. */
.na-ex-abstract {
  flex:1 1 auto; min-height:120px;
  display:grid; grid-template-columns:1fr 1fr; gap:1px;
  background:var(--border); border:1px solid var(--border); border-radius:12px; overflow:hidden;
}
.na-ex-panel {
  display:flex; flex-direction:column; min-height:0; padding:8px 8px 4px;
  background:
    linear-gradient(var(--border) 1px,transparent 1px) 0 0/16px 16px,
    linear-gradient(90deg,var(--border) 1px,transparent 1px) 0 0/16px 16px,
    var(--bg);
}
.na-ex-svg { flex:1 1 auto; min-height:0; width:100%; display:block }
.na-ex-panel-label {
  font-family:var(--mono); font-size:8px; letter-spacing:.03em;
  color:var(--muted); margin-top:4px;
}
.na-ex-prompt { font-size:12.5px; line-height:1.5; color:var(--muted2); font-style:italic; margin:0 }
.na-ex-stats { display:grid; grid-template-columns:repeat(4,1fr); gap:6px }
.na-ex-stat {
  background:var(--bg); border:1px solid var(--border); border-radius:8px; padding:8px 9px;
}
.na-ex-stat-value { font-family:var(--mono); font-size:13px; font-weight:700; color:var(--ex-tone) }
.na-ex-stat-key {
  font-family:var(--mono); font-size:8px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--muted); margin-top:2px;
}
.na-ex-tags { display:flex; gap:6px; flex-wrap:wrap }
.na-ex-tag {
  font-family:var(--mono); font-size:9px; color:var(--muted2);
  background:var(--bg); border:1px solid var(--border); border-radius:999px; padding:3px 8px;
}
.na-ex-nav { display:flex; align-items:center; gap:10px; margin-top:12px }
.na-ex-nav-gap { flex:1 1 auto }
.na-ex-arrow {
  width:34px; height:34px; border:1px solid var(--border2); background:var(--bg2);
  border-radius:9px; display:grid; place-items:center; color:var(--muted2); cursor:pointer;
}
.na-ex-arrow:hover { border-color:var(--blue); color:var(--blue) }
.na-ex-arrow:focus-visible { outline:2px solid var(--blue); outline-offset:2px }

/* One accent per example, so the mode label and its numbers agree with each
   other and differ between cards. The series tokens, not new colours. */
.na-ex-tone-a { --ex-tone:var(--ch1) }
.na-ex-tone-b { --ex-tone:var(--ch5) }
.na-ex-tone-c { --ex-tone:var(--ch6) }
.na-ex-tone-d { --ex-tone:var(--ch3) }

@media (max-width:980px) {
  .na-home-grid { grid-template-columns:1fr }
  .na-ex { display:none }
  .na-home-panel { border-left:0; background:var(--bg) }
  /* STACKED, NOT DROPPED. Side by side the two panels are told apart by a
     vertical seam; one above the other they need a horizontal one, or the file
     list reads as more of the projects list. The library keeps its clearance for
     the pill, which is still fixed over the bottom of whatever is last. */
  .na-home-lib { border-top:1px solid var(--border) }
  /* Stacked, the panel is the whole width and the name column has room to spare
     — so the three measurements get back the air they were denied in a column. */
  .na-lib-table th:nth-child(4), .na-lib-table td:nth-child(4) { width:110px }
  /* Narrow, this screen stops being bounded and scrolls the page — the two panels
     are stacked and there is genuinely more than fits. The minimum is what is LEFT
     under the fixed bar, not a whole viewport: `100vh` here plus the bar's own
     height above it is a screen one bar too tall, which is a scrollbar on a screen
     with nothing to scroll. */
  #nergyapp-root.na-home { height:auto; min-height:var(--screen-h); overflow:auto }
  body.na-has-topbar #nergyapp-root.na-home {
    height:auto; min-height:calc(var(--screen-h) - var(--topbar-h)); overflow:auto;
  }
}

/* ── the palette the drawings use ────────────────────────────────────────────
   `dom.js`'s `svg()` takes a CLASS for every colour and never a value, so the
   two panels that draw — the sign-in showcase and the workspace examples — are
   correct in both themes for the same reason every other rule here is: the
   colour is a token, and the token knows which theme it is in. */

.na-st-blue   { stroke:var(--blue) }
.na-st-green  { stroke:var(--green) }
.na-st-amber  { stroke:var(--amber) }
.na-st-red    { stroke:var(--red) }
.na-st-teal   { stroke:var(--ch6) }
.na-st-violet { stroke:var(--ch5) }
.na-st-ink    { stroke:var(--text) }
.na-st-rule   { stroke:var(--border2) }

.na-fl-blue   { fill:var(--blue) }
.na-fl-green  { fill:var(--green) }
.na-fl-amber  { fill:var(--amber) }
.na-fl-red    { fill:var(--red) }
.na-fl-teal   { fill:var(--ch6) }
.na-fl-violet { fill:var(--ch5) }
.na-fl-rule   { fill:var(--border3) }

.na-sc-tick, .na-ex-tick {
  font-family:var(--mono); font-size:8.5px; fill:var(--muted);
}
.na-sc-tick { font-size:9px }

/* ── the geometry workbench ────────────────────────────────────────────────
   docs/GEOMETRY_VIEWER_PLAN.md.

   The canvas is sized by its container and NOT by CSS on the element itself:
   `glscene.resize()` sets the drawing buffer from the stage's measured box, so a
   CSS width on the canvas would stretch a correctly-sized buffer and every
   circle in the model would render as an ellipse. */
 /* THE SAME LADDER AS `.na-exec`, because it is now the same kind of thing: a
    tab's content, three frames, filling whatever `.na-panel` gives it. It used
    to be `position:fixed; inset:0; z-index:60` — an overlay over the whole
    window — which is what made it the one surface in the app you could not have
    open beside anything else. */
.geom-panel { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; }
.geom-head {
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
}
.geom-tools { display:flex; gap:6px; flex-wrap:wrap; }
.geom-tool {
  font:inherit; font-size:12px; padding:5px 10px; border-radius:6px;
  border:1px solid var(--border2); background:var(--bg3); color:var(--muted3);
  cursor:pointer;
}
.geom-tool:hover { color:var(--text); border-color:var(--border3); }
.geom-tool.is-on { color:var(--text); border-color:var(--blue); background:var(--blue-dim); }

/* One column's worth of the model panel or the work panel. The frame itself
   owns the padding and the scroll (`.na-frame`); this is only the stack. */
.geom-col { display:flex; flex-direction:column; }

/* THE CANVAS NEEDS A HEIGHT FROM SOMEWHERE. `.na-frame` is a flex column, so
   `flex:1` gives the stage the leftover, and `min-height` keeps it from
   collapsing to nothing on a short window — a zero-height canvas is a model
   that silently does not render. */
.geom-stage {
  flex:1 1 auto; min-width:0; min-height:240px; position:relative;
  background:var(--bg); border-radius:var(--radius-lg); overflow:hidden;
}
.geom-canvas { display:block; width:100%; height:100%; }

.geom-summary { font-size:12px; color:var(--muted2); margin:0 0 8px; }
.geom-note { font-size:12px; color:var(--muted); margin:0 0 8px; }
.geom-offer { display:flex; flex-direction:column; gap:10px; align-items:flex-start; }

/* The tree. `--geom-depth` is set per row rather than by nesting <ul>s, because
   a deep assembly nested as markup indents itself off the right of a 300px
   column long before it runs out of levels. */
.geom-tree { list-style:none; margin:0; padding:0; }
.geom-tree-row {
  display:flex; align-items:center; gap:6px; font-size:12px; min-height:24px;
  padding-left:calc(var(--geom-depth, 0) * 12px);
}
.geom-tree-row.is-group { color:var(--muted); font-weight:600; }
.geom-tree-eye { flex:none; margin:0; }
.geom-tree-swatch {
  width:10px; height:10px; border-radius:2px; flex:none;
  border:1px solid var(--border3);
}
.geom-tree-swatch.is-none { background:repeating-linear-gradient(45deg,var(--bg3),var(--bg3) 2px,var(--bg4) 2px,var(--bg4) 4px); }
.geom-tree-name {
  font:inherit; font-size:12px; background:none; border:0; padding:0; cursor:pointer;
  color:var(--text); text-align:left; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.geom-tree-name:hover { color:var(--blue); }
.geom-tree-detail { margin-left:auto; color:var(--muted); font-size:11px; flex:none; }

/* The file row's affordance. A disabled one keeps its label and carries the
   reason in its title — geomoffer.js explains why it is drawn at all. */
.na-files-3d { flex:none; }
.na-files-3d-btn {
  font:inherit; font-size:11px; padding:3px 8px; border-radius:5px;
  border:1px solid var(--border2); background:transparent; color:var(--muted2);
  cursor:pointer;
}
.na-files-3d-btn:hover:not(:disabled) { color:var(--text); border-color:var(--blue); }
.na-files-3d-btn:disabled { opacity:0.45; cursor:not-allowed; }

/* Selection, regions, and the marquee (phase 2). */
.geom-selection {
  display:flex; flex-wrap:wrap; align-items:center; gap:6px;
  margin:10px 0; padding:8px; border-radius:6px;
  background:var(--surface-sunken); border:1px solid var(--border);
}
.geom-selection-text { font-size:12px; color:var(--muted2); width:100%; }
.geom-selection .geom-tool:disabled { opacity:0.4; cursor:not-allowed; }

.geom-h { font-size:12px; margin:14px 0 6px; color:var(--muted2); }
.geom-region-host { margin-top:4px; }
.geom-region-add { display:flex; gap:6px; margin-bottom:8px; }
.geom-region-name {
  flex:1; min-width:0; font:inherit; font-size:12px; padding:5px 8px;
  border-radius:6px; border:1px solid var(--border2);
  background:var(--surface-sunken); color:var(--text);
}
.geom-regions { list-style:none; margin:0 0 10px; padding:0; }
.geom-region {
  display:flex; align-items:center; gap:6px; font-size:12px; min-height:26px;
}
.geom-region-name-btn {
  font:inherit; font-size:12px; font-family:var(--mono); background:none; border:0;
  padding:0; color:var(--text); cursor:pointer; text-align:left;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.geom-region-name-btn:hover { color:var(--blue); }
.geom-region-count { color:var(--muted); font-size:11px; }
.geom-region-drop {
  margin-left:auto; font:inherit; font-size:11px; background:none; border:0;
  color:var(--muted); cursor:pointer; flex:none;
}
.geom-region-drop:hover { color:var(--red); }

/* The box-select rectangle. Pointer-events off, or it swallows the pointerup
   that ends the very drag drawing it. */
.geom-marquee {
  position:absolute; pointer-events:none; z-index:2;
  border:1px solid var(--blue); background:var(--blue-dim);
}

/* Section, explode, appearance, measurement (phase 3). */
.geom-select {
  font:inherit; font-size:12px; padding:4px 6px; border-radius:6px;
  border:1px solid var(--border2); background:var(--bg3); color:var(--muted3);
}
.geom-slider { width:110px; accent-color:var(--blue); vertical-align:middle; }
.geom-slider:disabled { opacity:0.4; }
.geom-slider-label {
  display:inline-flex; align-items:center; gap:6px; font-size:12px; color:var(--muted2);
}
/* The measurement sits in the selection bar, beside the count. It is the answer
   to a question the user did not have to ask, so it must not look like a control. */
.geom-measure-text {
  width:100%; font-size:12px; font-family:var(--mono); color:var(--text);
}
.geom-measure-text:empty { display:none; }

/* The meshing panel (phase 4). */
.mesh-host { margin-top:4px; }
.mesh-field { display:flex; align-items:center; gap:8px; font-size:12px; margin-bottom:6px; }
.mesh-field > span { color:var(--muted2); flex:1; }
.mesh-number {
  width:88px; font:inherit; font-size:12px; padding:4px 6px; border-radius:6px;
  border:1px solid var(--border2); background:var(--surface-sunken); color:var(--text);
}
.mesh-number:disabled, .mesh-form select:disabled { opacity:0.45; }
.mesh-message:empty, .mesh-results:empty { display:none; }
.mesh-results { margin-top:10px; display:flex; flex-direction:column; gap:8px; align-items:flex-start; }

/* The verdict is the headline, and its colour IS the message — a mesh with
   inverted elements must not look like one with a slightly thin tetrahedron. */
.mesh-verdict { font-size:12px; margin:0; padding:6px 8px; border-radius:6px; }
.mesh-verdict.is-good     { color:var(--green); background:var(--green-dim); }
.mesh-verdict.is-warn     { color:var(--amber); background:rgba(251,191,36,0.12); }
.mesh-verdict.is-bad,
.mesh-verdict.is-inverted { color:var(--red); background:var(--red-dim); }

.mesh-metric { display:flex; align-items:flex-end; gap:8px; font-size:11px; width:100%; }
.mesh-metric-name { font-family:var(--mono); color:var(--muted2); flex:none; }
.mesh-metric-values { color:var(--muted); flex:1; }
/* Ten bars. "Mostly fine, with a tail" is a thing you see, not a thing you read. */
.mesh-hist { display:flex; align-items:flex-end; gap:1px; height:22px; flex:none; }
.mesh-hist-bar { width:4px; background:var(--border3); border-radius:1px 1px 0 0; }
.mesh-hist-bar.is-bad { background:var(--red); }

.mesh-group { display:flex; align-items:center; gap:6px; font-size:12px; }

/* Results / field colouring (phase 5). */
.field-host:empty { display:none; }
.field-range { display:flex; gap:6px; align-items:center; margin:6px 0; }
.field-range .mesh-number { width:74px; }

/* The legend. The strip's gradient comes from glcolormap.gradient(), the model's
   colours from glcolormap.sample() — the same table, so the two cannot disagree
   about what a colour means. */
.field-scale { display:flex; gap:8px; height:150px; margin:8px 0; }
.field-strip { width:16px; border-radius:3px; border:1px solid var(--border2); flex:none; }
.field-ticks { position:relative; flex:1; font-size:10px; font-family:var(--mono); }
.field-tick {
  position:absolute; left:0; transform:translateY(50%);
  color:var(--muted2); white-space:nowrap;
}
.field-probe {
  font-family:var(--mono); font-size:12px; color:var(--text); margin:4px 0 0;
}
.field-probe:empty { display:none; }
.field-warn { font-size:11px; color:var(--amber); margin:4px 0 0; }

/* ── the Geometry tab (geomtab.js) ──────────────────────────────────────────
   A LIST, NOT A VIEWPORT — see the module header. It shows every model the
   project holds and opens one; the WebGL context stays with the workbench,
   which is a tab of its own and not this one.

   CARDS, BECAUSE THERE IS ONE ACTION. A row is for a list you scan and a card
   is for a thing you act on — and this tab exists so that a part somebody
   uploaded has somewhere obvious to be opened from, rather than an 11px button
   on one row of a 320px sidebar. The card is sized for the button. */
.geom-tab { padding:16px 4px; }
.geom-tab-body { display:flex; flex-direction:column; gap:12px; }
.geom-tab-lede { font-size:13px; color:var(--muted); max-width:64ch; }
.geom-cards { display:flex; flex-direction:column; gap:10px; }
.geom-card {
  border:1px solid var(--border2); border-radius:8px; padding:14px 16px;
  background:var(--surface-raised);
  display:flex; flex-direction:column; gap:8px;
}
/* A model this deployment cannot open is drawn and dimmed, never hidden: a
   missing dependency must not look like a missing file. The reason sits under
   the head in `.geom-card-why`, because it is a sentence and not a tooltip. */
.geom-card.is-off { border-style:dashed; }
.geom-card.is-off .geom-card-name { color:var(--muted); }
.geom-card-head {
  display:flex; align-items:center; justify-content:space-between; gap:16px;
}
.geom-card-titles { display:flex; flex-direction:column; gap:2px; min-width:0; }
.geom-card-name {
  font-weight:600; font-size:15px; color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.geom-card-meta { font-size:12px; color:var(--muted2); }
.geom-card-open { flex:none; }
.geom-card-why { font-size:12.5px; color:var(--text); margin:0; max-width:64ch; }
.geom-card .geom-note { margin:0; }
.geom-held {
  border:1px solid var(--border2); border-radius:8px; padding:12px 14px;
  background:var(--surface-raised);
}
.geom-held-head {
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  margin-bottom:6px;
}
.geom-held-name { font-weight:600; font-size:14px; }
.geom-held-regions {
  list-style:none; margin:0; padding:0;
  display:flex; flex-wrap:wrap; gap:6px;
}
.geom-held-region {
  font-size:11px; color:var(--muted2); background:var(--bg3);
  border-radius:999px; padding:3px 9px; white-space:nowrap;
}

/* ── a model in the graphics frame (geompreview.js) ─────────────────────────
   A PREVIEW, and the styling says so: it fills the frame it is given, it has
   the view controls and nothing else, and the way through to the workbench is
   the one primary action on it. */
/* THE CARD HAS TO STRETCH, and only for this one kind of body. `framed()` wraps
   every graphics card in the same <article>, which sits at content height —
   right for a figure, which is as tall as its picture, and wrong for a model,
   which should have the frame. Selecting on what the card CONTAINS keeps
   `execbody.js` free of a class it would otherwise have to thread through
   `framed()` for one caller. */
.na-context:has(.na-context-geometry) {
  display:flex; flex-direction:column; flex:1 1 auto; min-height:0;
}
.na-context-geometry { display:flex; flex-direction:column; min-height:0; flex:1 1 auto; }
.geom-preview { display:flex; flex-direction:column; gap:8px; flex:1 1 auto; min-height:0; }
.geom-preview-head { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
/* Pushed to the end of the row so the controls that change the VIEW and the one
   that leaves it are not mistakable for each other. */
.geom-preview-open { margin-left:auto; font-size:12px; padding:5px 10px; }
.geom-preview-stage { min-height:220px; }
/* ── the analysis screen's cards (ancards.js, anplan.js) ──────────────────────
   newapp draws these four cards with inlined `style=` attributes. They are
   classes here so `theme.css` keeps owning the palette — the port is of the
   LAYOUT, and a colour written into a module is a colour that does not know the
   light theme exists (the same rule `chart.js` states).

   The one value newapp inlines that is NOT reproduced is the figure's `#fff`
   background. A white plate behind a chart is right in a light theme and a hole
   in a dark one; `--bg2` is the surface both themes already use for exactly
   this, and the figures are rendered with transparent backgrounds. */
.na-an-card {
  border:1px solid var(--border2); border-radius:12px;
  padding:20px 22px; margin:0 0 18px; background:var(--bg2);
  box-shadow:var(--elev-1);
}
.na-an-kicker {
  font-size:11px; font-family:var(--mono); letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted); margin:0 0 6px; font-weight:600;
}
.na-an-title { font-size:15px; font-weight:600; margin-bottom:12px }
.na-an-foot {
  margin-top:16px; display:flex; gap:12px; flex-wrap:wrap; align-items:center;
}
.na-an-headline { font-size:13.5px; font-weight:600; margin-bottom:6px }

/* label / value, the shape the file card is made of */
.na-an-fact { display:flex; gap:10px; font-size:12.5px; margin:4px 0 }
.na-an-fact-label { color:var(--muted); min-width:150px; flex:0 0 auto }
.na-an-fact-value { color:var(--text) }

.na-an-note { font-size:11.5px; color:var(--muted); margin:10px 0 0 }
.na-an-sub { font-size:12px; color:var(--muted); margin-top:2px }
.na-an-dim { color:var(--muted2) }
.na-an-objective { font-size:13px; color:var(--muted2); margin:0 0 4px }

/* The read-out parameters. Scrolls inside its own box rather than widening the
   card — a table that pushes the page sideways is a table nobody reads. */
.na-an-table-wrap { overflow-x:auto }
.na-an-table { width:100%; border-collapse:collapse; font-size:13px }
.na-an-table th {
  text-align:left; color:var(--muted); font-size:11px; text-transform:uppercase;
  letter-spacing:.08em; font-weight:600; padding:0 10px 6px 0; white-space:nowrap;
}
.na-an-table td { padding:5px 10px 5px 0; vertical-align:top }
.na-an-table td:last-child, .na-an-table th:last-child { padding-right:0 }
.na-an-num { font-family:var(--mono); font-size:12px }

/* the plan's groups */
.na-an-group { margin-top:14px }
.na-an-group-h {
  font-family:var(--mono); font-size:10px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--muted); margin-bottom:6px;
}
.na-an-list { margin:0; padding-left:18px; font-size:13px; line-height:1.5 }
.na-an-list > li { margin:7px 0 }
.na-an-tag {
  font-size:10px; color:var(--muted); font-family:var(--mono);
  text-transform:uppercase; letter-spacing:.05em; margin-left:6px;
}
.na-an-chart { font-size:11px; color:var(--muted); font-family:var(--mono); margin-left:6px }

/* A warning is amber and readable, never an alarm: most of what lands here is a
   caveat about the reading, not a failure of it. */
.na-an-warn { font-size:11.5px; color:var(--amber); margin-top:10px; line-height:1.5 }
.na-an-warn-list { margin:0; padding-left:18px; font-size:12.5px; line-height:1.6 }

/* Reduced to 80% of the card's width (was a forced `width:100%`, which
   stretched every figure to fill the card regardless of its own size — an XRD
   plot at that width ran taller than the screen and had to be scrolled to see
   whole). `max-width` rather than `width` so a figure smaller than this cap is
   never stretched up past its own resolution. */
.na-an-figure {
  width:auto; max-width:80%; height:auto; display:block;
  border:1px solid var(--border); border-radius:8px; background:var(--bg2);
}
/* The `display:block` above outranks the UA sheet's [hidden] rule, so it is
   restored here — same idiom as .na-meter[hidden] (chrome.css). Lets an
   implausible figure be hidden behind its "Show anyway" gate. */
.na-an-figure[hidden] { display:none }
/* The gate that stands in for an implausible figure until the reader asks to see
   it. No `display` of its own, so its own [hidden] toggle (on reveal) still works. */
.na-an-gate { margin-top:2px }
/* A quiet caveat on a figure that is shown but drawn on a guessed axis — amber and
   small like the other caveats here, and deliberately NOT the loop "Confidence"
   chip's vocabulary. */
.na-an-flag {
  font-size:11px; color:var(--amber); font-family:var(--mono);
  text-transform:uppercase; letter-spacing:.05em;
}

/* ── the files panel's two ways in (filespanel.js) ────────────────────────────
   Upload and Library sit together because they end in the same place — bytes in
   cloud storage, attached to this project — and reading them as one pair is what
   stops "which of these do I want" being a question. Upload leads, because it is
   the one every account can use; Library needs a drive connected first. */
/* CENTRED ALWAYS. In the empty card these three are the whole of what the region
   holds, under a title and a sentence that are themselves centred; pinning them
   to its left edge would read as leftover rather than placed. Above a list
   (`.na-files-add-row`) they stay centred for the same reason they are centred
   here — one arrangement of Upload/Library/My Computer, wherever it is drawn. */
.na-files-actions { display:flex; gap:8px; flex-wrap:wrap; justify-content:center }
.na-files-actions .na-files-add { flex:0 0 auto }
/* Only in the EMPTY card does the pair follow a title and a sentence — the top
   row above the file list (`.na-files-add-row`) puts its own gap between rows
   instead, and a second margin here would double it. */
.na-empty .na-files-actions { margin-top:12px }

/* What a refusal from either of them says, in the panel that owns them. Empty
   collapses so the gap does not sit under the buttons when there is nothing to
   report. */
.na-files-aside-note {
  font-size:12px; color:var(--muted); line-height:1.5; margin:8px 0 0;
}
.na-files-aside-note:empty { display:none; margin:0 }

/* ── the debug window (debugdock.js, debugview.js) ────────────────────────────
   An admin-only overlay, and the one surface in this app that is allowed to show
   an internal identifier: it exists to be read by somebody working out which step
   of a request is not happening, and a node id with the id taken out is not a
   debugging tool. It is off by default and drawn only after `/auth/me` has said
   `admin` — see `debugdock.js` for both halves of that.

   IT SITS ABOVE EVERYTHING, including the modal layers (600), on purpose: half of
   what is worth watching happens while a dialog is up, and a debug window a dialog
   covers is one you have to dismiss the thing you are debugging to read. It starts
   bottom LEFT, clear of the rail and deliberately not in the corner Quick ask's
   pill owns, and it can be dragged anywhere from its title bar. */
.na-dbg-dock {
  position:fixed; left:calc(var(--rail-w) + var(--rail-gap) * 2); bottom:16px; z-index:700;
  display:flex; flex-direction:column; align-items:flex-start; gap:8px;
}

/* Collapsed, this pill is the whole of the feature on screen — and the whole of
   its cost, which is one number being written once an animation frame. */
.na-dbg-pill {
  display:flex; align-items:center; gap:7px; padding:6px 12px;
  border-radius:999px; border:1px solid var(--border2); background:var(--surface-raised);
  color:var(--muted2); font-family:var(--mono); font-size:11px; box-shadow:var(--elev-2);
  cursor:pointer;
}
.na-dbg-pill:hover { color:var(--text); border-color:var(--border3) }
.na-dbg-dot {
  width:7px; height:7px; border-radius:50%; background:var(--green); flex:none;
}
.na-dbg-count { color:var(--muted) }
.na-dbg-bad { color:var(--muted) }
.na-dbg-bad.is-bad { color:var(--red) }

.na-dbg {
  display:none; flex-direction:column;
  width:min(520px, calc(var(--screen-w) - 32px)); height:min(340px, calc(var(--screen-h) - 140px));
  min-width:280px; min-height:140px;
  background:var(--surface-raised); border:1px solid var(--border2);
  border-radius:var(--radius-lg); box-shadow:var(--elev-3);
  /* `resize` needs a clipped box, and a clipped box is what this wants anyway —
     the list inside does the scrolling. */
  overflow:hidden; resize:both;
}
.na-dbg-dock.is-open .na-dbg { display:flex }
.na-dbg-dock.is-open .na-dbg-pill { display:none }
/* While the title bar is being dragged: no transition to lag behind the pointer,
   and no text selection to sweep the log into. */
.na-dbg-dock.is-dragging .na-dbg { transition:none }
.na-dbg-dock.is-dragging { user-select:none }

.na-dbg-head {
  display:flex; align-items:center; gap:8px; flex:none; cursor:grab;
  padding:8px 10px; border-bottom:1px solid var(--border); background:var(--bg3);
}
.na-dbg-dock.is-dragging .na-dbg-head { cursor:grabbing }
.na-dbg-title {
  font-family:var(--mono); font-size:11px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted2); flex:none;
}
/* The grip is drawn rather than labelled — the whole bar drags, and this only
   says so. */
.na-dbg-grip {
  flex:1 1 auto; height:8px; min-width:20px; align-self:center;
  background-image:repeating-linear-gradient(
    to right, var(--border2) 0 2px, transparent 2px 5px);
  background-size:100% 2px; background-repeat:no-repeat; background-position:0 50%;
}
/* The session id, in the title bar. A button because it is meant to be copied —
   `debugdock.js` says why — and quiet because it is a fact, not an action. */
.na-dbg-session {
  flex:none; padding:2px 7px; border-radius:var(--radius-sm);
  border:1px dashed var(--border2); background:transparent;
  color:var(--muted); font-family:var(--mono); font-size:10px; cursor:pointer;
}
.na-dbg-session:hover { color:var(--blue); border-color:var(--blue); border-style:solid }
.na-dbg-said { font-size:10.5px; color:var(--muted); flex:none }
.na-dbg-headtools { display:flex; align-items:center; gap:4px; flex:none }

.na-dbg-btn {
  padding:3px 8px; border-radius:var(--radius-sm); border:1px solid transparent;
  background:transparent; color:var(--muted2); font-size:11px; cursor:pointer;
}
.na-dbg-btn:hover { background:var(--surface-sunken); color:var(--text) }
.na-dbg-btn.is-on { background:var(--blue-dim); color:var(--blue); border-color:var(--border2) }
.na-dbg-off:hover { background:var(--red-dim); color:var(--red) }

.na-dbg-chips {
  display:flex; flex-wrap:wrap; gap:4px; padding:7px 10px 0; flex:none;
}
/* No chip pressed means every channel, which is what an operator opening this
   window wants before they know what they are looking for. */
.na-dbg-chip {
  padding:2px 7px; border-radius:999px; border:1px solid var(--border);
  background:transparent; color:var(--muted); font-family:var(--mono); font-size:10px;
  cursor:pointer;
}
.na-dbg-chip:hover { color:var(--text); border-color:var(--border2) }
.na-dbg-chip.is-on { background:var(--blue-dim); color:var(--blue); border-color:var(--blue) }

.na-dbg-tools { display:flex; align-items:center; gap:6px; padding:7px 10px; flex:none }
.na-dbg-search {
  flex:1 1 auto; min-width:0; padding:4px 8px; font-size:11.5px;
  border-radius:var(--radius-sm); border:1px solid var(--border);
  background:var(--surface-sunken); color:var(--text);
}
.na-dbg-search:focus { outline:none; border-color:var(--blue); box-shadow:var(--ring) }

.na-dbg-scroll {
  flex:1 1 auto; overflow-y:auto; overflow-x:hidden;
  border-top:1px solid var(--border); padding:4px 0 8px;
}
.na-dbg-nothing { margin:12px 10px; font-size:11.5px; color:var(--muted) }

/* One entry. The time and the channel are fixed columns so the eye can run down
   either; the data wraps onto its own line under the message rather than pushing
   the message out of the box. */
.na-dbg-row {
  display:grid; grid-template-columns:58px 62px minmax(0,1fr); gap:2px 8px;
  padding:2px 10px; font-family:var(--mono); font-size:10.5px; line-height:1.5;
  border-left:2px solid transparent;
}
.na-dbg-row:hover { background:var(--surface-sunken) }
.na-dbg-at { color:var(--muted); text-align:right }
.na-dbg-ch { color:var(--muted); overflow:hidden; text-overflow:ellipsis }
.na-dbg-msg { color:var(--muted3); overflow-wrap:anywhere }
.na-dbg-data { grid-column:3; color:var(--muted); overflow-wrap:anywhere }

.na-dbg-row.is-ok .na-dbg-msg { color:var(--green) }
.na-dbg-row.is-warn .na-dbg-msg { color:var(--amber) }
.na-dbg-row.is-warn { border-left-color:var(--amber) }
/* A failure is the row somebody is scrolling to find, so it is marked in the
   gutter as well as coloured — colour alone is a filter, not a landmark. */
.na-dbg-row.is-fail .na-dbg-msg { color:var(--red) }
.na-dbg-row.is-fail { border-left-color:var(--red); background:var(--red-dim) }

/* ══════════════════════════════════════════════════════════════════════════
   PLANT MONITORING (monitor.js, telpaint.js, telcharts.js, monitorlimits.js)

   NO COLOUR VALUES BELOW, only tokens — the same rule chart.js states, and it
   matters more here than anywhere else in the app: this is the one screen where
   colour carries a judgement about a customer's process, and a hex written in a
   module is a hex that does not know the light theme exists.

   AND COLOUR IS NEVER ALONE. Every state that is coloured also carries a word
   (`no reading`, `not a tag here`, `warn`, `stop`), because a control room is
   read by people who do not all see red, and printed by people whose printers
   do not have it.
   ══════════════════════════════════════════════════════════════════════════ */

#nergyapp-root.na-monitor {
  max-width:none; padding:0; gap:0; overflow:hidden; min-height:0; height:100vh;
}
body.na-has-topbar #nergyapp-root.na-monitor { height:calc(100vh - var(--topbar-h)) }

.na-monitor-grid {
  display:grid; grid-template-columns:minmax(0,1fr) minmax(300px,25rem);
  height:100%; min-height:0;
}
.na-monitor-main {
  display:flex; flex-direction:column; gap:18px; min-height:0;
  overflow-y:auto; padding:28px clamp(20px,2.6vw,40px) 48px;
}
.na-monitor-side {
  border-left:1px solid var(--border); background:var(--bg2);
  overflow-y:auto; padding:28px 20px 48px; min-height:0;
}
.na-monitor-head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px }

/* ── the sources strip ─────────────────────────────────────────────────── */

.na-monitor-sources { display:flex; flex-wrap:wrap; gap:10px }
.na-monitor-src {
  flex:1 1 240px; text-align:left; cursor:pointer;
  display:flex; flex-direction:column; gap:6px;
  padding:12px 14px; border:1px solid var(--border); border-radius:var(--radius);
  background:var(--surface); color:inherit; font:inherit;
  transition:border-color .12s ease, background .12s ease;
}
.na-monitor-src:hover { border-color:var(--border2) }
.na-monitor-src.is-on { border-color:var(--blue); background:var(--blue-dim) }
.na-monitor-src:focus-visible { outline:none; box-shadow:var(--ring) }
.na-monitor-src-head { display:flex; align-items:center; justify-content:space-between; gap:10px }
.na-monitor-src-name { font-weight:600; font-size:14px }

.na-tel-health {
  display:flex; flex-wrap:wrap; gap:4px 12px;
  font-size:11.5px; color:var(--muted); font-family:var(--mono);
}
.na-tel-kind { color:var(--muted2) }
/* Never heard from is not "a long time ago" — it is a different fact, and it is
   the one that means the agent has never connected at all. */
.na-tel-seen.is-never { color:var(--amber) }

/* ── a reading ─────────────────────────────────────────────────────────── */

.na-tel-value { font-family:var(--mono); font-size:14px; white-space:nowrap }
.na-tel-num { font-weight:600 }
.na-tel-unit { color:var(--muted); margin-left:4px; font-size:11.5px }
/* The three absences, told apart. `no reading` is amber because it usually
   recovers; `not a tag here` is red because it never will without a change. */
.na-tel-value.is-unread .na-tel-num { color:var(--amber) }
.na-tel-value.is-nottag .na-tel-num { color:var(--red) }
.na-tel-value.is-nosrc .na-tel-num { color:var(--muted) }
.na-tel-value.is-warn .na-tel-num { color:var(--amber) }
.na-tel-value.is-stop .na-tel-num { color:var(--red) }

.na-tel-chip, .na-tel-band {
  display:inline-block; padding:1px 7px; border-radius:999px;
  font-size:11px; font-weight:600; letter-spacing:.01em;
  border:1px solid var(--border2); color:var(--muted2); white-space:nowrap;
}
.na-tel-chip.is-ok, .na-tel-band { border-color:var(--border2) }
.na-tel-chip.is-unread { color:var(--amber); border-color:var(--amber) }
.na-tel-chip.is-nottag, .na-tel-band.is-stop, .na-tel-chip.is-stop {
  color:var(--red); border-color:var(--red); background:var(--red-dim);
}
.na-tel-chip.is-warn, .na-tel-band.is-warn { color:var(--amber); border-color:var(--amber) }
.na-tel-chip.is-nosrc { color:var(--muted); border-color:var(--border2) }

.na-tel-limits { font-family:var(--mono); font-size:11.5px; color:var(--muted2); white-space:nowrap }
.na-tel-limits.is-none { color:var(--muted); font-style:italic }
.na-tel-age { font-family:var(--mono); font-size:11.5px; color:var(--muted); white-space:nowrap }
.na-tel-age.is-none { color:var(--muted) }

/* ── the tag table ─────────────────────────────────────────────────────── */

.na-monitor-tags { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg) }
.na-monitor-tags th { font-size:11.5px; letter-spacing:.04em; text-transform:uppercase; color:var(--muted) }
.na-monitor-tags td { padding:11px 14px }
.na-monitor-tagrow.is-open { background:var(--blue-dim) }
.na-monitor-taglink {
  background:none; border:0; padding:0; cursor:pointer; text-align:left; font:inherit;
  display:flex; flex-direction:column; gap:1px; color:inherit;
}
.na-monitor-taglink:hover .na-monitor-taglabel { color:var(--blue) }
.na-monitor-taglink:focus-visible { outline:none; box-shadow:var(--ring); border-radius:var(--radius-sm) }
.na-monitor-taglabel { font-size:13.5px; font-weight:550 }
/* The registered name, under the label. Mono because it is what an agent sends
   and what a refusal names, and a person comparing it against a historian export
   is comparing characters. */
.na-monitor-tagname { font-family:var(--mono); font-size:11px; color:var(--muted) }
.na-monitor-none { color:var(--muted2); font-size:13.5px; line-height:1.6; max-width:62ch }
.na-monitor-strong { font-weight:600; color:var(--text) }

/* ── the trend ─────────────────────────────────────────────────────────── */

.na-monitor-trend:empty { display:none }
.na-monitor-trend {
  display:flex; flex-direction:column; gap:10px;
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface); padding:14px 16px 16px;
}
.na-monitor-trend-head { display:flex; align-items:center; gap:12px }
.na-monitor-trend-src { font-size:11.5px; color:var(--muted); font-family:var(--mono); flex:1 1 auto }

.na-tel-chart { display:flex; flex-direction:column; gap:8px }
.na-tel-plot { width:100%; height:190px; overflow:visible }
/* The band is the min–max of each bucket. Faint on purpose: it is the envelope
   the mean sits inside, and a filled area drawn as strongly as the line reads as
   a second series. */
.na-tel-band-path { fill:var(--blue-dim); stroke:none }
.na-tel-line { fill:none; stroke:var(--blue); stroke-width:1.6; stroke-linejoin:round; stroke-linecap:round }
.na-tel-dot { fill:var(--blue) }
.na-tel-dot.is-last { fill:var(--blue); stroke:var(--surface); stroke-width:1.5 }
.na-tel-rule { stroke-width:1; stroke-dasharray:4 3 }
.na-tel-rule.is-warn { stroke:var(--amber) }
.na-tel-rule.is-stop { stroke:var(--red) }
.na-tel-rule-label { font-family:var(--mono); font-size:9.5px; fill:var(--muted) }
.na-tel-rule-label.is-warn { fill:var(--amber) }
.na-tel-rule-label.is-stop { fill:var(--red) }
.na-tel-notes { display:flex; flex-wrap:wrap; gap:4px 14px }
.na-tel-note { font-size:11.5px; color:var(--muted) }
/* A gap is reported in words as well as drawn as a break — a hole in a line with
   no explanation reads as a rendering fault rather than as an outage. */
.na-tel-note.is-gap { color:var(--amber) }
.na-tel-chart.is-empty { padding:18px 0 }
.na-tel-chart-none { color:var(--muted2); font-size:13px; margin:0 }
.na-tel-spark { display:inline-block; width:60px; height:18px }
.na-tel-spark.is-none { color:var(--muted); font-family:var(--mono) }

/* ── outside limits, right now ─────────────────────────────────────────── */

.na-tel-panel { display:flex; flex-direction:column; gap:12px }
.na-tel-panel-head { display:flex; align-items:center; justify-content:space-between; gap:10px }
.na-tel-panel-title { font-size:14px; font-weight:650; margin:0 }
.na-tel-panel-count {
  font-family:var(--mono); font-size:12px; font-weight:700;
  padding:1px 9px; border-radius:999px; border:1px solid var(--border2); color:var(--muted2);
}
.na-tel-panel-count.is-stop { color:var(--red); border-color:var(--red); background:var(--red-dim) }
.na-tel-panel-none { font-size:13px; color:var(--muted2); line-height:1.6; margin:0 }
/* The caveat is quieter than the sentence but is never dropped: this panel is a
   statement about right now, and a reader who takes it for a log has been misled
   by its silence. */
.na-tel-panel-caveat { display:block; margin-top:6px; font-size:11.5px; color:var(--muted) }
.na-tel-panel-foot { font-size:11.5px; color:var(--muted); margin:0; line-height:1.5 }

.na-tel-limitlist { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px }
.na-tel-limitrow {
  border:1px solid var(--border); border-left:3px solid var(--amber);
  border-radius:var(--radius); background:var(--surface);
}
.na-tel-limitrow:has(.na-tel-band.is-stop) { border-left-color:var(--red) }
.na-tel-limitrow-btn {
  width:100%; text-align:left; background:none; border:0; font:inherit; color:inherit;
  cursor:pointer; padding:10px 12px; display:flex; flex-direction:column; gap:5px;
}
.na-tel-limitrow-btn:focus-visible { outline:none; box-shadow:var(--ring); border-radius:var(--radius) }
.na-tel-limitrow-head { display:flex; align-items:center; justify-content:space-between; gap:8px }
.na-tel-limitrow-tag { font-size:13px; font-weight:600 }
.na-tel-limitrow-body { display:flex; flex-wrap:wrap; align-items:center; gap:4px 12px }
.na-tel-limitrow-src { font-size:11px; color:var(--muted); font-family:var(--mono) }

@media (max-width:1100px) {
  /* The panel goes UNDER the table rather than away. What is outside its limits
     is the one thing on this screen nobody should have to widen a window to see. */
  .na-monitor-grid { grid-template-columns:1fr; grid-template-rows:minmax(0,1fr) auto; overflow-y:auto }
  .na-monitor-side { border-left:0; border-top:1px solid var(--border) }
}

/* ── the event log (monitorevents.js) ──────────────────────────────────────
   Under "Outside limits, right now" and visibly separate from it: the two answer
   different questions — what the plant is doing, and what it did — and a reader
   who takes one for the other has been misled by the layout rather than by the
   words. The rule between them is what says they are two panels. */

.na-monitor-events { border-top:1px solid var(--border); padding-top:16px }
.na-tel-events .na-tel-limitlist { gap:8px }
.na-tel-ev {
  border:1px solid var(--border); border-left:3px solid var(--border2);
  border-radius:var(--radius); background:var(--surface);
  padding:10px 12px; display:flex; flex-direction:column; gap:5px;
}
/* STILL HAPPENING is carried by the rule down the side AND by the word in the
   duration cell. Colour is never the only carrier — a control room is read by
   people who do not all see red. */
.na-tel-ev.is-open { border-left-color:var(--red) }
.na-tel-ev-open {
  width:100%; text-align:left; background:none; border:0; font:inherit; color:inherit;
  cursor:pointer; padding:0; display:flex; flex-direction:column; gap:5px;
}
.na-tel-ev-open:focus-visible { outline:none; box-shadow:var(--ring); border-radius:var(--radius) }
.na-tel-ev-peak { font-family:var(--mono); font-size:12px; font-weight:600 }
.na-tel-ev-at, .na-tel-ev-none { font-family:var(--mono); font-size:11.5px; color:var(--muted) }
.na-tel-ev-dur { font-size:11.5px; color:var(--muted2) }
.na-tel-ev-dur.is-open { color:var(--red); font-weight:600 }
.na-tel-ev-when { display:flex; align-items:baseline; justify-content:space-between; gap:8px; font-size:11.5px; color:var(--muted) }
.na-tel-ev-ack { margin:0; font-size:11.5px; color:var(--muted2) }
.na-tel-ev-note { display:block; margin-top:3px; color:var(--muted); font-style:italic }
.na-tel-ev-btn { align-self:flex-start; font-size:11.5px; padding:3px 10px }
/* A refused acknowledgement is INFORMATION — somebody else got there first — so
   it is shown on the row rather than swallowed or retried. */
.na-tel-ev-said { margin:0; font-size:11.5px; color:var(--amber); line-height:1.5 }

/* ── the plant, as a tree (assettree.js) ───────────────────────────────────
   Indentation is drawn with padding rather than with nested margins, so a deep
   plant does not walk off the left edge of a narrow panel, and every row keeps
   a full-width hit target — a tree whose click area narrows with depth is a
   tree whose vessels are the hardest things in it to select. */

.na-assettree-wrap { display:flex; flex-direction:column; gap:8px }
.na-assettree { list-style:none; margin:0; padding:0 }
.na-assettree .na-assettree { margin:0 }
.na-assettree-node { margin:0 }
.na-assettree-btn {
  width:100%; display:flex; align-items:center; gap:8px;
  padding:6px 10px; border:1px solid transparent; border-radius:var(--radius-sm);
  background:none; color:inherit; font:inherit; text-align:left; cursor:pointer;
}
.na-assettree-btn:hover { background:var(--bg3) }
.na-assettree-btn.is-on { background:var(--blue-dim); border-color:var(--blue) }
.na-assettree-btn:focus-visible { outline:none; box-shadow:var(--ring) }
/* The kind is a fixed column so a reader can run an eye down it — Site, Line,
   Unit — rather than parsing each label for what sort of thing it is. */
.na-assettree-kind {
  flex:0 0 auto; min-width:44px;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.03em;
  text-transform:uppercase; color:var(--muted);
}
.na-assettree-label { flex:1 1 auto; font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* Archived is a WORD as well as a dimming: a decommissioned train drawn only in
   a lighter grey is a train somebody reads as live on a bright screen. */
.na-assettree-btn.is-archived .na-assettree-label { color:var(--muted) }
.na-assettree-archived {
  font-size:10.5px; font-family:var(--mono); color:var(--muted);
  border:1px solid var(--border2); border-radius:999px; padding:0 6px;
}
/* What is read and not on the model. Separated by a rule rather than by being
   the last row of the tree — it is not part of the plant, and drawing it as one
   would be the quiet lie this bucket exists to prevent. */
.na-assettree-loose { border-top:1px solid var(--border); padding-top:8px }
.na-assettree-btn.is-loose .na-assettree-kind { color:var(--amber) }

/* ── stating a plant (assetedit.js, assetbind.js) ──────────────────────────
   The overlay reuses the settings panel's shell (dialog.openPanel), so what is
   here is only what is INSIDE it: two columns, a form, and the list of tags on
   a node. */

.na-assetedit {
  display:grid; grid-template-columns:minmax(220px,300px) minmax(0,1fr);
  gap:20px; align-items:start;
}
.na-assetedit-side {
  display:flex; flex-direction:column; gap:10px;
  border-right:1px solid var(--border); padding-right:16px; min-height:0;
}
.na-assetedit-form { min-width:0 }

.na-assetform { display:flex; flex-direction:column; gap:10px }
.na-assetform-head { display:flex; align-items:center; gap:10px; flex-wrap:wrap }
.na-assetform-kind {
  font-family:var(--mono); font-size:10.5px; letter-spacing:.03em;
  text-transform:uppercase; color:var(--muted);
}
/* The path is the stable address a customer's own records use, so it is mono and
   selectable — somebody comparing this against a tag export is comparing
   characters, not reading prose. */
.na-assetform-path { font-family:var(--mono); font-size:12px; color:var(--muted2); user-select:text }
.na-assetform-sub { font-size:13px; font-weight:650; margin:14px 0 0 }
.na-assetform-row { display:grid; grid-template-columns:88px minmax(0,1fr); gap:10px; align-items:center }
.na-assetform-lab { font-size:12.5px; color:var(--muted2) }
.na-assetform-actions { display:flex; flex-wrap:wrap; gap:8px; margin-top:4px }
.na-assetform-hint { font-size:11.5px; color:var(--muted); line-height:1.55; margin:0; max-width:64ch }
.na-assetform-none { font-size:13px; color:var(--muted2); line-height:1.6; margin:0; max-width:64ch }
.na-assetform-check { display:flex; align-items:center; gap:8px; font-size:12.5px; color:var(--muted2) }
.na-check { width:14px; height:14px; accent-color:var(--blue) }
.na-input-num { max-width:96px }

.na-assetbinds { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px }
.na-assetbind {
  display:grid; grid-template-columns:minmax(0,1fr) auto; gap:2px 10px;
  align-items:center; padding:8px 10px;
  border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--surface);
}
.na-assetbind-tag { font-size:13px; font-weight:550 }
.na-assetbind .na-btn { grid-row:1 / span 3; grid-column:2 }
/* The registered name and the role, under the label: what the agent sends, and
   what the tag MEANS on this unit. Both are mono because both are configuration
   somebody typed rather than prose we wrote. */
.na-assetbind-src, .na-assetbind-role {
  grid-column:1; font-family:var(--mono); font-size:11px; color:var(--muted);
}

/* ── the digital twin (twin.js, twinschematic.js, twinfield.js) ────────────
   It reuses the monitor's shell (`na-monitor-grid`), because it IS that shape:
   the plant down the left, the thing you picked in the middle, what is stated
   about it on the right. A second set of two-column rules would be a second
   thing to keep in step. */

.na-twin-main { display:flex; flex-direction:column; gap:16px; min-width:0 }
.na-twin-side { display:flex; flex-direction:column; gap:14px }
.na-twin-head { display:flex; align-items:center; gap:10px; flex-wrap:wrap }

/* The schematic. A wrapping row, and NO connectors between the boxes — the
   model holds a hierarchy, not connectivity, and an arrow would be a claim
   about a process nobody has described. */
.na-twinsch { display:flex; flex-direction:column; gap:8px }
.na-twinsch-flow { display:flex; flex-wrap:wrap; gap:12px; align-items:stretch }
/* An authored layout positions its children, so the flow becomes the canvas
   they are positioned in. */
.na-twinsch-flow.is-authored { position:relative; display:block; min-height:280px }
.na-twinsch-unit {
  flex:1 1 190px; max-width:280px; text-align:left; cursor:pointer;
  display:flex; flex-direction:column; gap:8px;
  padding:12px 14px; border:1px solid var(--border); border-left:3px solid var(--border2);
  border-radius:var(--radius); background:var(--surface); color:inherit; font:inherit;
}
.na-twinsch-unit:hover { border-color:var(--border2) }
.na-twinsch-unit.is-on { border-color:var(--blue); background:var(--blue-dim) }
.na-twinsch-unit:focus-visible { outline:none; box-shadow:var(--ring) }
.na-twinsch-unit.is-warn { border-left-color:var(--amber) }
.na-twinsch-unit.is-stop { border-left-color:var(--red) }
.na-twinsch-unit.is-sole { max-width:none }
.na-twinsch-unit-head { display:flex; align-items:center; justify-content:space-between; gap:10px }
.na-twinsch-unit-name { font-weight:600; font-size:13.5px }
.na-twinsch-tags { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px }
.na-twinsch-tag { display:flex; align-items:baseline; justify-content:space-between; gap:10px }
.na-twinsch-tag-name { font-size:11.5px; color:var(--muted2) }
.na-twinsch-untagged { margin:0; font-size:11.5px; color:var(--muted); font-style:italic }
.na-twinsch-note { margin:0; font-size:11.5px; color:var(--muted); line-height:1.55; max-width:76ch }

/* The field card. The only place in this app with an inline colour, and it is a
   DATUM rather than a theme choice — see twinfield.js. */
.na-twinfield {
  display:flex; flex-direction:column; gap:10px;
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface); padding:14px 16px 16px;
}
.na-twinfield-head { display:flex; align-items:center; gap:10px }
.na-twinfield-title { font-size:14px; font-weight:650; margin:0 }
.na-twinfield-fields { display:flex; flex-wrap:wrap; gap:6px }
.na-chipbtn {
  padding:2px 9px; border-radius:999px; border:1px solid var(--border2);
  background:none; color:var(--muted2); font:inherit; font-size:11.5px; cursor:pointer;
}
.na-chipbtn.is-on { border-color:var(--blue); color:var(--blue); background:var(--blue-dim) }
.na-chipbtn:focus-visible { outline:none; box-shadow:var(--ring) }

.na-twinfield-strip {
  display:flex; gap:2px; height:44px;
  border:1px solid var(--border); border-radius:var(--radius-sm); overflow:hidden;
}
.na-twinfield-seg { flex:1 1 0; position:relative; display:flex; align-items:flex-end; justify-content:center }
/* An unpainted element is HATCHED rather than left blank: blank reads as part of
   the container, and the whole point is that this segment has no measurement. */
.na-twinfield-seg.is-blank {
  background:repeating-linear-gradient(45deg,
    var(--bg3) 0 5px, var(--surface-sunken) 5px 10px);
}
.na-twinfield-seg-n { font-size:9.5px; font-family:var(--mono); color:var(--muted); padding-bottom:2px }
.na-twinfield-seg:not(.is-blank) .na-twinfield-seg-n { color:rgba(255,255,255,.85) }
.na-twinfield-ends {
  display:flex; justify-content:space-between;
  font-family:var(--mono); font-size:10px; color:var(--muted); letter-spacing:.04em;
}
.na-twinfield-legend { display:flex; flex-direction:column; gap:3px }
.na-twinfield-ramp { height:8px; border-radius:2px; border:1px solid var(--border) }
.na-twinfield-ticks {
  display:flex; justify-content:space-between;
  font-family:var(--mono); font-size:10px; color:var(--muted);
}
.na-twinfield-note, .na-twinfield-none {
  margin:0; font-size:11.5px; color:var(--muted); line-height:1.55; max-width:76ch;
}

/* What was stated, beside what was read. The two carry their basis as a word,
   because Law 1 is that a typed value and a measured one may never look alike. */
.na-twin-spec { margin:0; display:grid; grid-template-columns:minmax(0,1fr); gap:8px }
.na-twin-spec dt {
  font-family:var(--mono); font-size:11px; color:var(--muted); text-transform:uppercase;
  letter-spacing:.03em;
}
.na-twin-spec dd { margin:2px 0 0; display:flex; flex-wrap:wrap; align-items:baseline; gap:6px 10px }
.na-twin-spec-said { font-size:13.5px; font-weight:550 }
.na-twin-spec-read { display:flex; align-items:baseline; gap:6px }
.na-twin-basis {
  font-size:10px; font-family:var(--mono); color:var(--muted);
  border:1px solid var(--border2); border-radius:999px; padding:0 6px;
}

/* ── Live Instruments (instruments.js, instfleet.js, instsession.js) ───────
   The monitor's shell, mirrored: the FLEET is on the left here rather than the
   right, because a lab picks a machine and then looks at it, where a control
   room looks at a plant and glances at what is wrong. Same grid, opposite
   reading order, and it is the selection that decides which. */

#nergyapp-root.na-instruments { height:100vh; min-height:0; overflow:hidden }
body.na-has-topbar #nergyapp-root.na-instruments { height:calc(100vh - var(--topbar-h)) }

.na-instruments-grid {
  display:grid; grid-template-columns:minmax(280px,22rem) minmax(0,1fr);
  height:100%; min-height:0;
}
.na-instruments-side {
  border-right:1px solid var(--border); background:var(--bg2);
  overflow-y:auto; padding:28px 20px 48px; min-height:0;
}
.na-instruments-panel {
  display:flex; flex-direction:column; gap:18px; min-height:0;
  overflow-y:auto; padding:28px clamp(20px,2.6vw,40px) 48px;
}

.na-inst-fleet { display:flex; flex-direction:column; gap:12px }
.na-inst-fleet-head { display:flex; align-items:center; justify-content:space-between; gap:10px }
.na-inst-fleet-count {
  font-family:var(--mono); font-size:12px; font-weight:700;
  padding:1px 9px; border-radius:999px; border:1px solid var(--border2); color:var(--muted2);
}
.na-inst-fleet-sum { margin:0; font-size:11.5px; color:var(--muted); line-height:1.5 }
.na-inst-retired { color:var(--muted) }

.na-inst-cards { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px }
.na-inst-card {
  border:1px solid var(--border); border-left:3px solid var(--border2);
  border-radius:var(--radius); background:var(--surface);
}
/* The rule down the side is a SECOND carrier of the state, never the only one —
   every card says the state in a word as well (`na-inst-state`). */
.na-inst-card:has(.na-inst-state.is-running) { border-left-color:var(--blue) }
.na-inst-card:has(.na-inst-state.is-error) { border-left-color:var(--red) }
.na-inst-card:has(.na-inst-state.is-unreachable) { border-left-color:var(--amber) }
.na-inst-card.is-on { border-color:var(--border3); background:var(--surface-raised) }
.na-inst-card-btn {
  width:100%; text-align:left; background:none; border:0; font:inherit; color:inherit;
  cursor:pointer; padding:10px 12px; display:flex; flex-direction:column; gap:5px;
}
.na-inst-card-btn:focus-visible { outline:none; box-shadow:var(--ring); border-radius:var(--radius) }
.na-inst-card-head { display:flex; align-items:center; justify-content:space-between; gap:8px }
.na-inst-card-name { font-size:13px; font-weight:600 }
.na-inst-card-sub { font-size:11.5px; color:var(--muted) }
.na-inst-card-foot { display:flex; flex-wrap:wrap; gap:4px 10px; font-size:11px; color:var(--muted) }
.na-inst-seen.is-never { color:var(--amber) }
.na-inst-nodriver {
  font-family:var(--mono); border:1px solid var(--border2); border-radius:999px;
  padding:0 6px; color:var(--muted);
}

.na-inst-state {
  font-size:11px; font-weight:600; letter-spacing:.02em;
  padding:1px 8px; border-radius:999px; border:1px solid var(--border2); color:var(--muted2);
}
.na-inst-state.is-running { color:var(--blue); border-color:var(--blue) }
.na-inst-state.is-error { color:var(--red); border-color:var(--red); background:var(--red-dim) }
.na-inst-state.is-unreachable { color:var(--amber); border-color:var(--amber) }

/* ── the one that is open ─────────────────────────────────────────────── */

.na-inst-picked { display:flex; flex-wrap:wrap; align-items:center; gap:8px 12px }
.na-inst-picked-name { font-size:16px; font-weight:650; margin:0 }
.na-inst-picked-says {
  flex:1 1 100%; margin:0; font-size:12px; color:var(--muted2); line-height:1.55; max-width:80ch;
}

.na-inst-session {
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface); padding:16px 18px;
  display:flex; flex-direction:column; gap:12px;
}
.na-inst-session.is-live { border-color:var(--blue) }
.na-inst-session-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px }
.na-inst-session-title { margin:0; font-size:14px; font-weight:650 }
.na-inst-session-sub { margin:2px 0 0; font-size:11.5px; color:var(--muted) }
.na-inst-session-actions { display:flex; gap:8px }
.na-inst-session-reason { margin:0; font-size:12.5px; color:var(--amber); line-height:1.55 }

.na-inst-channels {
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px;
}
.na-inst-channel {
  border:1px solid var(--border); border-radius:var(--radius); padding:10px 12px;
  display:flex; flex-direction:column; gap:4px;
}
.na-inst-channel-head { display:flex; align-items:baseline; justify-content:space-between; gap:8px }
.na-inst-channel-name { font-size:12px; font-weight:600 }
.na-inst-channel-unit { font-size:11px; color:var(--muted); font-family:var(--mono) }
.na-inst-channel-body { display:flex; align-items:center; justify-content:space-between; gap:10px }
.na-inst-channel-foot { font-size:10.5px; color:var(--muted) }

/* ── starting one ─────────────────────────────────────────────────────── */

.na-inst-start {
  border:1px dashed var(--border2); border-radius:var(--radius-lg); padding:14px 16px;
  display:flex; flex-direction:column; gap:10px;
}
.na-inst-start-row { display:flex; flex-wrap:wrap; gap:10px }
.na-inst-start-row .na-input { flex:1 1 200px }
/* The sentence under the button is not fine print: on a deployment with no
   driver it is the difference between recording a run and starting a machine. */
.na-inst-start-note { margin:0; font-size:11.5px; color:var(--muted); line-height:1.55; max-width:80ch }
.na-inst-start-note.is-warn { color:var(--amber) }
.na-inst-said { margin:0; font-size:12px; color:var(--amber); line-height:1.55; max-width:80ch }

/* ── what the run left behind ─────────────────────────────────────────── */

.na-inst-artifacts { display:flex; flex-direction:column; gap:6px }
.na-inst-artifacts-title { margin:0; font-size:11.5px; color:var(--muted); text-transform:uppercase; letter-spacing:.04em }
.na-inst-artifact-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px }
.na-inst-artifact-list li { display:flex; align-items:baseline; gap:8px; font-size:12px }
.na-inst-artifact-kind {
  font-family:var(--mono); font-size:10px; text-transform:uppercase;
  border:1px solid var(--border2); border-radius:999px; padding:0 6px; color:var(--muted);
}
.na-inst-artifact-name { flex:1 1 auto }
.na-inst-artifact-in { font-size:10.5px; color:var(--muted) }
.na-inst-artifact-out { font-size:10.5px; color:var(--amber) }

/* ── the history ──────────────────────────────────────────────────────── */

.na-inst-history-title { margin:0 0 8px; font-size:11.5px; color:var(--muted); text-transform:uppercase; letter-spacing:.04em }
.na-inst-history { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px }
.na-inst-history-row { border:1px solid transparent; border-radius:var(--radius-sm) }
.na-inst-history-row.is-on { border-color:var(--border2); background:var(--surface) }
.na-inst-history-btn {
  width:100%; display:flex; align-items:baseline; gap:10px;
  background:none; border:0; font:inherit; color:inherit; cursor:pointer;
  padding:6px 10px; text-align:left;
}
.na-inst-history-btn:focus-visible { outline:none; box-shadow:var(--ring); border-radius:var(--radius-sm) }
.na-inst-history-sample { flex:1 1 auto; font-size:12.5px }
.na-inst-history-when { font-size:11px; color:var(--muted) }
.na-inst-history-state { font-size:11px; color:var(--muted2) }
.na-inst-history-state.is-failed { color:var(--red) }
.na-inst-history-state.is-refused { color:var(--amber) }
.na-inst-history-state.is-running { color:var(--blue) }

@media (max-width:1100px) {
  /* The fleet goes ABOVE the panel rather than away: which machine you are
     looking at is the first thing on this screen, not a detail beside it. */
  .na-instruments-grid { grid-template-columns:1fr; grid-template-rows:auto minmax(0,1fr); overflow-y:auto }
  .na-instruments-side { border-right:0; border-bottom:1px solid var(--border) }
}

/* ── the campaign panel (instcampaign.js) ─────────────────────────────────
   Under the session, because a campaign is what a run BELONGS to and a person
   reads it after they have seen what the machine is doing. */

.na-instruments-campaign:empty { display:none }
.na-camp-head { display:flex; align-items:baseline; gap:10px }
.na-camp-title { margin:0; font-size:13px; font-weight:650; text-transform:uppercase; letter-spacing:.04em; color:var(--muted) }
.na-camp-id { font-family:var(--mono); font-size:11px; color:var(--muted) }
.na-camp-sub { margin:0; font-size:11.5px; color:var(--muted); text-transform:uppercase; letter-spacing:.04em }

.na-camp-picker { display:flex; flex-direction:column; gap:6px; margin-top:10px }
.na-camp-families { display:flex; flex-wrap:wrap; gap:6px }
.na-camp-family {
  border:1px solid var(--border2); border-radius:999px; background:none;
  color:inherit; font:inherit; font-size:12px; padding:3px 12px; cursor:pointer;
}
.na-camp-family:hover:not(:disabled) { border-color:var(--border3) }
.na-camp-family.is-on { border-color:var(--blue); color:var(--blue) }
.na-camp-family:focus-visible { outline:none; box-shadow:var(--ring) }
/* The sentence that keeps a free thing looking free. Quiet, and never dropped. */
.na-camp-free { margin:0; font-size:11px; color:var(--muted); line-height:1.55; max-width:80ch }
.na-camp-open { display:flex; gap:8px; margin-top:10px }
.na-camp-open .na-input { flex:1 1 220px; max-width:24rem }

.na-camp-matrix { margin-top:12px; background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg) }
.na-camp-matrix th { font-size:11px; letter-spacing:.04em; text-transform:uppercase; color:var(--muted) }
.na-camp-matrix td { padding:8px 12px; font-size:12.5px }
.na-camp-num { font-family:var(--mono) }
/* A blank response cell is what the campaign is ASKING for — it is drawn as
   blank, and the column head is what says what goes in it. */
.na-camp-blank { background:var(--surface-sunken) }
.na-camp-centre {
  margin-left:6px; font-size:10px; font-family:var(--mono); color:var(--muted);
  border:1px solid var(--border2); border-radius:999px; padding:0 5px;
}
.na-camp-run { font-size:11.5px; padding:2px 10px }

.na-camp-analysis { display:flex; flex-direction:column; gap:8px; margin-top:16px }
.na-camp-n { margin:0; font-size:12px; color:var(--muted2) }
.na-camp-effects { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px }
.na-camp-effects li { display:flex; align-items:baseline; gap:10px; font-size:12.5px }
.na-camp-effect-name { flex:1 1 auto }
.na-camp-effect-value { font-family:var(--mono); font-weight:600 }
.na-camp-effect-sig { font-size:10.5px; color:var(--muted) }
.na-camp-effect-sig.is-on { color:var(--blue) }
.na-camp-curvature { margin:0; font-size:12px; color:var(--amber); line-height:1.55; max-width:80ch }

/* ── Closed loop (docs/OPERATIONS_SURFACES_PLAN.md §6.4, packet 19) ──────────
   A PROJECT TAB, not a root screen, so there is no `#nergyapp-root` height rule
   here the way `.na-instruments` needs one: this mounts inside a tab body that
   already scrolls. Two columns — the loop and its proposals on the left, the
   ladder on the right — because the ladder is what somebody looks at WHILE
   reading a proposal, and a panel you have to navigate to is a panel that is not
   read at the moment it matters. */
.na-loop-grid {
  display:grid; grid-template-columns:minmax(0,1fr) minmax(240px,20rem);
  gap:24px; align-items:start; padding:20px clamp(16px,2vw,32px) 40px;
}
.na-loop-panel { display:flex; flex-direction:column; gap:16px; min-width:0 }
.na-loop-aside { min-width:0 }
.na-loop-head { display:flex; flex-direction:column; gap:4px }
.na-loop-says { margin:0; font-size:12px; color:var(--muted); line-height:1.55; max-width:80ch }

/* The two halves. One loop object underneath, so this is a lens and not a mode —
   which is why it is a segmented control and not two tabs. */
.na-loop-seg { display:inline-flex; gap:0; border:1px solid var(--border2); border-radius:999px; padding:2px }
.na-loop-seg-btn {
  border:0; background:none; color:var(--muted); cursor:pointer;
  font:inherit; font-size:12px; padding:4px 14px; border-radius:999px;
}
.na-loop-seg-btn.is-on { background:var(--surface); color:var(--text) }
.na-loop-seg-btn:focus-visible { outline:none; box-shadow:var(--ring) }

.na-loop-summary { display:flex; flex-direction:column; gap:8px }
.na-loop-summary-head { display:flex; align-items:baseline; justify-content:space-between; gap:10px }
.na-loop-state {
  font-size:11px; letter-spacing:.03em; color:var(--muted);
  border:1px solid var(--border2); border-radius:999px; padding:1px 9px;
}
.na-loop-half-says { margin:0; font-size:12px; color:var(--muted2); line-height:1.55 }
.na-loop-counts { margin:0; font-size:11.5px; color:var(--muted); font-family:var(--mono) }
.na-loop-controls { display:flex; gap:8px; margin-top:4px }

.na-loop-passes, .na-loop-proposals { display:flex; flex-direction:column; gap:8px }
.na-loop-pass-list, .na-loop-prop-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px }
.na-loop-pass {
  display:flex; align-items:baseline; gap:10px; font-size:12.5px;
  border:1px solid var(--border); border-radius:var(--radius); padding:6px 12px;
  background:var(--surface);
}
/* An OPEN pass is not a finished one, and the difference is the whole reason
   `stop_cause` exists. */
.na-loop-pass.is-live { border-color:var(--blue) }
.na-loop-pass-n { font-weight:600 }
.na-loop-pass-cause { font-family:var(--mono); font-size:11.5px; color:var(--muted) }
.na-loop-pass-why { flex:1 1 auto; color:var(--muted2); font-size:11.5px }
.na-loop-pass-when { font-size:11px; color:var(--muted) }

.na-loop-prop {
  display:flex; flex-direction:column; gap:6px;
  border:1px solid var(--border); border-radius:var(--radius-lg); padding:12px 14px;
  background:var(--surface);
}
.na-loop-prop-head { display:flex; align-items:baseline; justify-content:space-between; gap:10px }
.na-loop-prop-kind { font-size:11px; letter-spacing:.04em; text-transform:uppercase; color:var(--muted) }
.na-loop-prop-state { font-size:11.5px; color:var(--muted2) }
.na-loop-prop-title { margin:0; font-size:13.5px; font-weight:600 }
/* THE BASIS IS NOT SMALL PRINT. Same size as the title's neighbour text: nothing
   on these screens shows a number without one, so it is not styled as an aside. */
.na-loop-prop-basis { margin:0; font-size:12.5px; color:var(--muted2); line-height:1.6; max-width:80ch }
.na-loop-prop-conf { margin:0; font-size:11.5px; color:var(--muted); font-family:var(--mono) }
.na-loop-prop-means { margin:0; font-size:12px; color:var(--muted); line-height:1.55; max-width:80ch }
.na-loop-prop-act { display:flex; gap:8px; align-items:center; flex-wrap:wrap }
.na-loop-reason { flex:1 1 220px; max-width:32rem }

.na-loop-decision { display:flex; flex-direction:column; gap:3px; border-left:2px solid var(--border2); padding-left:10px }
.na-loop-decision-line { margin:0; font-size:12px; color:var(--muted2); line-height:1.55 }
.na-loop-action { margin:0; font-size:12px; color:var(--muted); line-height:1.55 }
/* NOT AN ERROR AND NOT A SUCCESS: the loop held at the level somebody set it to.
   Drawn as its own thing, in the colour this stylesheet uses for "held". */
.na-loop-action.is-refused { color:var(--amber) }

.na-loop-ladder { display:flex; flex-direction:column; gap:10px }
.na-loop-ladder-head { display:flex; flex-direction:column; gap:2px }
.na-loop-ladder-sub { margin:0; font-size:11.5px; color:var(--muted); line-height:1.5 }
.na-loop-rungs { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px }
.na-loop-rung {
  display:flex; flex-direction:column; gap:4px;
  border:1px solid var(--border); border-radius:var(--radius); padding:10px 12px;
  background:var(--surface);
}
/* The kind no level admits, drawn as itself — no control, and the service's own
   sentence beside it. */
.na-loop-rung.is-never { background:var(--surface-sunken) }
.na-loop-rung-head { display:flex; align-items:baseline; justify-content:space-between; gap:8px }
.na-loop-rung-kind { font-size:12.5px; font-weight:600 }
.na-loop-rung-level { font-size:11px; font-family:var(--mono); color:var(--blue) }
.na-loop-rung-never { font-size:11px; font-family:var(--mono); color:var(--muted) }
.na-loop-rung-says { margin:0; font-size:11.5px; color:var(--muted2); line-height:1.55 }
.na-loop-rung-set { display:flex; align-items:center; gap:8px; margin-top:2px }
.na-loop-rung-set-label { font-size:11px; color:var(--muted) }
.na-loop-rung-pick { flex:0 1 12rem; font-size:12px }

@media (max-width: 900px) {
  .na-loop-grid { grid-template-columns:1fr }
}

/* The R&D half (packet 20). Sits above the loop's own record, because it is what
   PRODUCES a proposal — a reader works down from the evidence to the decision. */
.na-loop-half:empty { display:none }
.na-looprd { display:flex; flex-direction:column; gap:8px }
.na-looprd-evidence { margin:0; font-size:11.5px; color:var(--muted); font-family:var(--mono) }
.na-looprd-analysis { display:flex; flex-direction:column; gap:6px }
.na-looprd-n { margin:0; font-size:12px; color:var(--muted2) }
.na-looprd-controls { display:flex; flex-direction:column; gap:8px; align-items:flex-start }
.na-looprd-approved {
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  border:1px solid var(--border2); border-radius:var(--radius); padding:8px 12px;
  background:var(--surface);
}
.na-looprd-approved-title { font-size:12.5px }

/* The PLM half and the audit (packet 24). */
.na-loopplm { display:flex; flex-direction:column; gap:10px }
.na-loopplm-sub { margin:0; font-size:12px; font-weight:600 }
.na-loopplm-providers { list-style:none; margin:0; padding:0; display:flex; gap:8px; flex-wrap:wrap }
.na-loopplm-provider {
  display:flex; align-items:baseline; gap:8px; font-size:11.5px;
  border:1px solid var(--border); border-radius:999px; padding:2px 12px;
  color:var(--muted);
}
.na-loopplm-provider.is-on { border-color:var(--border2); color:var(--text) }
.na-loopplm-provider-name { font-weight:600 }
/* What it MAY do, in the service's words. Never inferred from the name. */
.na-loopplm-provider-can { font-family:var(--mono); font-size:10.5px; color:var(--muted) }
.na-loopplm-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px }
.na-loopplm-draft {
  display:flex; flex-direction:column; gap:4px;
  border:1px solid var(--border); border-radius:var(--radius); padding:10px 12px;
  background:var(--surface);
}
.na-loopplm-field { margin:0; font-size:12px; color:var(--muted2); line-height:1.55; max-width:80ch }
.na-loopplm-note { margin:0; font-size:11px; color:var(--muted); line-height:1.5 }
.na-loopplm-revision { display:flex; flex-direction:column; gap:4px }

.na-loopaudit { display:flex; flex-direction:column; gap:8px }
.na-loopaudit-head { display:flex; flex-direction:column; gap:2px }
.na-loopaudit-sub { margin:0; font-size:11.5px; color:var(--muted); line-height:1.5 }
.na-loopaudit-filters { display:flex; gap:6px; flex-wrap:wrap }
.na-loopaudit-filter {
  border:1px solid var(--border2); background:none; color:var(--muted);
  border-radius:999px; padding:2px 10px; font:inherit; font-size:11px; cursor:pointer;
}
.na-loopaudit-filter.is-on { color:var(--text); border-color:var(--blue) }
.na-loopaudit-filter:focus-visible { outline:none; box-shadow:var(--ring) }
.na-loopaudit-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px }
.na-loopaudit-row { display:flex; gap:10px; align-items:baseline; font-size:12px; padding:3px 0 }
.na-loopaudit-when { flex:0 0 6.5rem; font-size:11px; color:var(--muted); font-family:var(--mono) }
.na-loopaudit-what { flex:0 0 5.5rem; font-size:10.5px; letter-spacing:.03em; text-transform:uppercase; color:var(--muted) }
.na-loopaudit-says { flex:1 1 auto; color:var(--muted2); line-height:1.55 }
/* Neither a failure nor a success: the loop held where somebody set it. */
.na-loopaudit-row.is-refused .na-loopaudit-says { color:var(--amber) }

/* ── the Help wiki (wiki.js) ───────────────────────────────────────────────
   THREE VIEWS OVER ONE BOUNDED, SCROLLING CONTAINER. The screen binds to the
   viewport (`na-window`, set in `wiki.js`) exactly as the project window does,
   so the top bar stays put and this one element scrolls — `na-window` zeroes the
   shell's own padding, so the gutter is owned here. Levels 1 and 2 fill the
   width (the shell owns the window); only the article's reading column is
   capped, at the element, where line length is what matters.

   ACCENT SPENT SPARINGLY (rule 1). The category marks are neutral at rest and
   go to `--blue` (the app's rust accent — the token is misnamed, its value is
   the amber/rust used app-wide) only on hover or when current; nothing draws
   three accent things at once. */
.na-wiki {
  flex:1 1 auto; min-height:0; overflow-y:auto;
  padding:34px clamp(20px,3.2vw,44px) 64px;
}
.na-wiki-head { margin:0 0 22px }
.na-wiki-head .na-subtitle { margin-top:4px }

/* The search bar — full width above the grid, so the placeholder has room and
   nothing clips (the old narrow-rail clip is gone with the rail). */
.na-wiki-search { display:block; width:100%; margin:0 0 24px }

/* The back-link at the head of Levels 2 and 3 — the accent as "the way out". */
.na-wiki-back {
  display:inline-flex; align-items:center; gap:6px; margin:0 0 20px;
  font:inherit; font-size:14px; font-weight:560; cursor:pointer;
  color:var(--blue); background:none; border:none; padding:4px 2px;
}
.na-wiki-back:hover { text-decoration:underline; text-underline-offset:3px }
.na-wiki-back:focus-visible { outline:none; box-shadow:var(--ring); border-radius:var(--radius-sm) }
.na-wiki-back .na-wiki-arrow { width:18px; height:18px; flex:0 0 auto }

/* LEVEL 1 — the card grid. auto-fit/minmax so cards reflow to fill any width;
   `min(...,100%)` keeps a fixed min from ever overflowing a narrow screen. */
.na-wiki-grid {
  display:grid; gap:16px;
  grid-template-columns:repeat(auto-fit, minmax(min(260px,100%), 1fr));
}
/* A class `display` overrides the `[hidden]` attribute's own `display:none`, so
   the grid and the results list each need this to actually hide when the other
   is showing. */
.na-wiki-grid[hidden], .na-wiki-results[hidden] { display:none }
.na-wiki-cat {
  display:flex; flex-direction:column; align-items:flex-start; gap:6px;
  text-align:left; font:inherit; cursor:pointer;
  padding:20px; border-radius:var(--radius-lg);
  background:var(--surface); border:1px solid var(--border);
  transition:border-color .12s ease, background .12s ease;
}
.na-wiki-cat:hover { border-color:var(--blue); background:var(--surface-sunken) }
.na-wiki-cat:focus-visible { outline:none; box-shadow:var(--ring); border-color:var(--blue) }
.na-wiki-cat-mark { color:var(--muted3); margin-bottom:6px; transition:color .12s ease }
.na-wiki-cat-icon { width:24px; height:24px; display:block }
.na-wiki-cat:hover .na-wiki-cat-mark,
.na-wiki-cat:focus-visible .na-wiki-cat-mark { color:var(--blue) }
.na-wiki-cat-name { font-size:17px; font-weight:650; letter-spacing:-0.01em; color:var(--text) }
.na-wiki-cat-desc { font-size:13.5px; line-height:1.45; color:var(--muted) }
.na-wiki-cat-count {
  margin-top:auto; padding-top:10px;
  font-family:var(--mono); font-size:11px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--muted);
}

/* The landing's search results — a flat, ranked list in place of the grid, and
   the hand-off to Quick help at its foot (matched or not). */
.na-wiki-results { display:flex; flex-direction:column; gap:12px }
.na-wiki-empty { padding:12px 2px; color:var(--muted); font-size:14px }
.na-wiki-ask {
  align-self:flex-start; margin-top:4px; max-width:100%;
  text-align:left; white-space:normal;
}

/* LEVEL 2 — one category: header, then its article list. */
.na-wiki-section-head { margin:2px 0 24px }
.na-wiki-section-headrow { display:flex; align-items:center; gap:12px }
.na-wiki-section-mark { color:var(--muted3); display:inline-flex; flex:0 0 auto }
.na-wiki-section-mark .na-wiki-cat-icon { width:28px; height:28px }
.na-wiki-section-title { margin:0; font-size:26px; font-weight:680; letter-spacing:-0.02em; color:var(--text) }
.na-wiki-section-desc { margin:12px 0 0; font-size:15px; line-height:1.5; color:var(--muted) }

/* The article list (Level 2) and the search results (Level 1) share the row. */
.na-wiki-rows {
  display:grid; gap:12px;
  grid-template-columns:repeat(auto-fit, minmax(min(340px,100%), 1fr));
}
.na-wiki-row {
  display:flex; align-items:center; gap:16px; width:100%;
  text-align:left; font:inherit; cursor:pointer;
  padding:16px 18px; border-radius:var(--radius-lg);
  background:var(--surface); border:1px solid var(--border);
  transition:border-color .12s ease, background .12s ease;
}
.na-wiki-row:hover { border-color:var(--blue); background:var(--surface-sunken) }
.na-wiki-row:focus-visible { outline:none; box-shadow:var(--ring); border-color:var(--blue) }
.na-wiki-row-main { display:flex; flex-direction:column; gap:3px; min-width:0; flex:1 }
.na-wiki-row-title { font-size:15px; font-weight:620; color:var(--text) }
.na-wiki-row-summary { font-size:13px; line-height:1.45; color:var(--muted) }
.na-wiki-row .na-wiki-arrow {
  flex:0 0 auto; width:20px; height:20px; color:var(--muted);
  transition:color .12s ease, transform .12s ease;
}
.na-wiki-row:hover .na-wiki-arrow { color:var(--blue); transform:translateX(2px) }

/* LEVEL 3 — one article: the reading column, the section rail, and prev/next.
   The reading column is the ONE capped measure on this screen; the rail uses the
   leftover width rather than leaving it empty. */
.na-wiki-article-view { max-width:1040px }
.na-wiki-article-grid {
  display:grid; align-items:start; gap:56px;
  grid-template-columns:minmax(0,1fr) 240px;
}
.na-wiki-article-grid.is-solo { grid-template-columns:minmax(0,700px) }
.na-wiki-reading { min-width:0; max-width:700px }
.na-wiki-article-title { font-size:26px; font-weight:680; letter-spacing:-0.02em; margin:0 0 18px; color:var(--text) }
/* The article body is `blockprose.js`'s reader, sized for a page you sit and
   read; `.na-block-*` elsewhere keep their tighter report-card sizing — this is
   a scoped override, the same pattern `.na-home-panel .na-title` uses. */
.na-wiki-article .na-block-h { font-size:18px; margin-top:26px }
.na-wiki-article .na-block-sub { font-size:14.5px; margin-top:18px }
.na-wiki-article .na-block-p { font-size:14.5px; line-height:1.7; color:var(--text) }
.na-wiki-article .na-block-list { font-size:14.5px; line-height:1.7; color:var(--text) }
.na-wiki-article .na-block-note {
  font-size:13.5px; line-height:1.6; padding:10px 14px; border-radius:var(--radius);
  background:var(--bg2); border-left:3px solid var(--blue);
}

/* "More in this section" — the sibling articles, current one on the accent, in
   the same inset-bar treatment the old active nav link used. */
.na-wiki-rail { position:sticky; top:0; align-self:start }
.na-wiki-rail-label {
  margin:0 0 12px; font-family:var(--mono); font-size:11px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--muted);
}
.na-wiki-rail-links { display:flex; flex-direction:column }
.na-wiki-rail-link {
  text-align:left; font:inherit; font-size:14px; line-height:1.4; cursor:pointer;
  color:var(--muted2); background:none; border:none;
  padding:12px 0 12px 14px; border-top:1px solid var(--border);
}
.na-wiki-rail-links .na-wiki-rail-link:first-child { border-top:none }
.na-wiki-rail-link:hover { color:var(--text) }
.na-wiki-rail-link:focus-visible { outline:none; box-shadow:var(--ring); border-radius:var(--radius-sm) }
.na-wiki-rail-link.is-current { color:var(--blue); font-weight:600; box-shadow:inset 2px 0 0 var(--blue) }

.na-wiki-pn-divider { border-top:1px solid var(--border); margin:40px 0 24px }
.na-wiki-prevnext { display:grid; grid-template-columns:1fr 1fr; gap:16px }
.na-wiki-pn {
  display:flex; align-items:center; gap:12px;
  text-align:left; font:inherit; cursor:pointer;
  padding:14px 18px; border-radius:var(--radius-lg);
  background:var(--surface); border:1px solid var(--border);
  transition:border-color .12s ease, background .12s ease;
}
.na-wiki-pn:hover { border-color:var(--blue); background:var(--surface-sunken) }
.na-wiki-pn:focus-visible { outline:none; box-shadow:var(--ring); border-color:var(--blue) }
.na-wiki-pn-next { justify-content:flex-end; text-align:right }
.na-wiki-pn-text { display:flex; flex-direction:column; gap:3px; min-width:0 }
.na-wiki-pn-next .na-wiki-pn-text { align-items:flex-end }
.na-wiki-pn-kind {
  font-family:var(--mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted);
}
.na-wiki-pn-title { font-size:14px; font-weight:600; color:var(--text) }
.na-wiki-pn .na-wiki-arrow { flex:0 0 auto; width:20px; height:20px; color:var(--muted) }
.na-wiki-pn:hover .na-wiki-arrow { color:var(--blue) }

@media (max-width:900px) {
  .na-wiki-article-grid { grid-template-columns:minmax(0,1fr); gap:32px }
  .na-wiki-rail { position:static }
  .na-wiki-prevnext { grid-template-columns:1fr }
}
