/* STARNET — motion.css : the PREMIUM MOTION + FEEDBACK foundation (Phase 1).
   Additive layer over style.css/app.css. Same skin, same amber, same VT323 —
   the premium feel comes from MATERIAL, DEPTH & RHYTHM, not the aesthetic.

   Rules of this file:
   - Never introduce a hardcoded colour. Only the existing phosphor/role vars.
   - Motion goes through the token layer below (--t-fast/med/slow + shared easings).
   - Animate only transform / opacity / colour / border / box-shadow — NEVER layout
     (width/height/top/left) on hot paths; the canvas game loop runs at 60fps behind.
   - A single prefers-reduced-motion block at the bottom collapses everything.

   Motion tokens are static (no var() refs) so they live safely on :root. Any
   COMPOSITE var that references another var still MUST be declared on `body`
   (the theme swap re-evaluates it there) — see style.css --bezel/--well note. */

:root {
  --t-fast: 120ms;
  --t-med: 220ms;
  --t-slow: 400ms;
  /* one shared ease-out for entrances/settling, one soft ease for state flips */
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
}

/* ============================================================
   2 · INTERACTIVE ELEMENTS — tactile transitions + hover bloom
   Buttons, provider chips, swatches, tab headers, dock items,
   inputs & selects gain a fast transition on the properties that
   actually change on hover/active. No layout props animated.
   ============================================================ */
.btn, .btn-xl, .btn-sm, .prov,
.bb, .bb-grp, .bb.sm,
.lb-tab, .mc-seg-btn, .set-theme,
.phosphor-swatches .swatch,
.term-x,
input[type=text], input[type=password], input:not([type]),
textarea, select {
  transition:
    border-color var(--t-fast) var(--ease-soft),
    background-color var(--t-fast) var(--ease-soft),
    box-shadow var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
/* FLOOR — every native <button> inherits the tactile transition by default, so new buttons get the premium
   feel for free without being re-listed above. Element-level specificity (0,0,1) on purpose: ANY bespoke
   per-button transition in an earlier sheet (.chat-send, .chat-attach-btn, .mic-btn, .x-btn family, …) still
   wins the cascade — this is a floor that ADDS coverage, never a clobber. Only the safe props are animated. */
button {
  transition:
    border-color var(--t-fast) var(--ease-soft),
    background-color var(--t-fast) var(--ease-soft),
    box-shadow var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}

/* subtle hover bloom — a soft phosphor glow, no layout shift */
.btn:hover, .btn-xl:hover, .btn-sm:hover, .prov:hover,
.lb-tab:hover, .set-theme:hover,
.term-x:hover {
  box-shadow: 0 0 12px var(--ph-glow2);
}
/* the .btn-xl already carries a resting shadow — keep it and add the bloom */
.btn-xl:hover {
  box-shadow: 0 0 0 2px rgba(0,0,0,.85), 0 0 18px var(--ph-glow2), 0 0 12px var(--ph-glow2);
}

/* :active — keep the existing translateY(1px) sink, add a brief brightness dip */
.btn:active, .btn-xl:active, .btn-sm:active, .prov:active,
.bb:active, .bb-grp:active, .lb-tab:active,
.term-x:active {
  filter: brightness(0.86);
  transition-duration: 60ms;
}
/* FLOOR — every native <button> gets the press-sink by default (low specificity so a bespoke :active still
   wins; where a button already sinks via translateY, this just adds the brightness dip). */
button:active {
  filter: brightness(0.86);
  transition-duration: 60ms;
}

/* ============================================================
   3 · UNIFIED FOCUS RING — one keyboard-focus treatment app-wide.
   Kept low-specificity so it applies everywhere but existing
   themed :focus (border/glow) rules still layer their own accent.
   ============================================================ */
:focus-visible {
  outline: 2px solid var(--ph-bright);
  outline-offset: 2px;
}
/* mouse focus never draws the ring (matches native :focus-visible intent) */
:focus:not(:focus-visible) { outline: none; }

/* ============================================================
   4 · THEMED SCROLLBARS — thin phosphor bars app-wide, incl. panel
   bodies and textareas. Firefox via scrollbar-* ; Chromium via
   ::-webkit-scrollbar. Thumb brightens on hover.
   ============================================================ */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--ph-dim) var(--ph-faint);
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--ph-faint); }
::-webkit-scrollbar-thumb {
  background: var(--ph-dim); border-radius: 4px;
  transition: background var(--t-fast) var(--ease-soft);
}
::-webkit-scrollbar-thumb:hover { background: var(--ph); }
::-webkit-scrollbar-corner { background: var(--ph-faint); }

/* ============================================================
   5 · WINDOW CLOSE — reverse CRT power-off before DOM removal.
   toggleTerm/closeTerm add .term-closing; this collapses the
   glass to a bright line then blackout, matching term-power in.
   ============================================================ */
@keyframes term-power-off {
  0%   { transform: translate(-50%, -50%) scale(1, 1); filter: brightness(1); opacity: 1; }
  55%  { transform: translate(-50%, -50%) scale(1.006, 0.06); filter: brightness(2.4); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(0.9, 0.004); filter: brightness(6); opacity: 0; }
}
.term.term-closing {
  animation: term-power-off 190ms var(--ease-soft) forwards;
  pointer-events: none;
}
/* a window that was dragged (transform:none, explicit left/top) closes without
   the translate() so it doesn't jump to centre on the way out. */
.term.term-closing.term-moved {
  animation: term-power-off-moved 190ms var(--ease-soft) forwards;
}
@keyframes term-power-off-moved {
  0%   { transform: scale(1, 1); filter: brightness(1); opacity: 1; }
  55%  { transform: scale(1.006, 0.06); filter: brightness(2.4); opacity: 1; }
  100% { transform: scale(0.9, 0.004); filter: brightness(6); opacity: 0; }
}
.term-scrim.term-closing { animation: scrim-out 180ms var(--ease-soft) forwards; }
@keyframes scrim-out { from { opacity: 1; } to { opacity: 0; } }

/* ============================================================
   6 · TAB / SECTION CROSSFADE — builder-swapped bodies fade in.
   StationUI stamps .swap-in on the injected content root; the
   class is short-lived (removed after the animation) so a repeat
   swap re-triggers it.
   ============================================================ */
@keyframes swap-in {
  from { opacity: 0; transform: translateY(3px); }
  to   { opacity: 1; transform: translateY(0); }
}
.swap-in { animation: swap-in var(--t-med) var(--ease-out); }

/* ============================================================
   7 · TOAST / NOTIFICATION — a station READOUT CARD, not a web toast.
   StationUI.notify() mirrors the stored notification to a transient
   card in #toast-stack. Each card is a piece of rack hardware:

     ▐✓▌  MODEL                      [01:48]
          GPT / GPT 5.6 LUNA / HIGH
     ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔───────────────────────   ← dwell rail, draining

   · a LIT SEVERITY BLADE down the left edge — solid phosphor with an
     ink glyph, the same inverted-block language as the molded header
     strips (panelchrome §2). 'info' leaves the blade UNLIT: severity
     is a whisper, not a traffic light.
   · a RECESSED FACE with the CRT micro-etch and a machined top
     hairline (the bench-instrument material from widgets.css).
   · an ENGRAVED LABEL over a BRIGHT VALUE when the caller led with
     "LABEL: …" — the dim-label/bright-figure grammar of every gauge
     in the station. Plain prose just gets the one message line.
   · a bracketed corner STAMP + the panel-chrome registration ticks,
     framing the readout area on its plate.
   · a DWELL RAIL that drains for exactly as long as the card will
     live (--dwell is set by toast() from the same number that arms
     the dismiss timer, so the bar can never lie about the time left).

   MATTE, per the surface law: depth comes from molding, seating
   shadows and hairlines — never an outer glow wash.
   ============================================================ */
/* WHERE THE RACK IS BOLTED — the cabinet's bottom-left corner, measured, never guessed.
   Above every overlay tier: toasts fired while RECRUITMENT (.mkt, z 9000) is open used to be
   buried under it at z 960 (audit P0 #13), so the rack keeps --z-toast and stays a <body> child
   (inside #screen-game — a z:10 stacking context — it would drop under the tooltip card, the
   live-voice panel and the brand mark).

   The anchor used to be the literal numbers `left: 268px * --cz; bottom: 128px * --cz`, and they
   pinned the rack to NOTHING. Measured live at 1600x960 (2026-08-15): the columns run x 11→1589
   and end at y 896, so the rack sat 16px inside the stage's left edge and floated 64px above the
   floor line — no edge of the cabinet to read it against, which is exactly what "not aligned"
   looks like. Worse, the CREW seam is DRAGGABLE (--crew-w, leftrail.js): drag the rail to 380px
   and the stage starts at x 400 while the rack stayed at 268 — the card was then sitting ON the
   crew list it was supposed to be parked beside.

   So the seat is MEASURED off the cabinet by StationUI.seatToastRack() (the syncTermBand idiom:
   read the real rects, divide by the TEXT SIZE zoom exactly once, publish the answer as vars):
     --toast-x   the columns' LEFT edge  (#bottombar.left — the shell's outer padding line, the
                 same x as #topbar, the CREW rail and the dock: the longest alignment line in the
                 frame, and it exists even when the rail is hidden or cinema collapses a column)
     --toast-b   the columns' BOTTOM edge (#stage-wrap.bottom — where the rail, the stage and
                 COMMS all end; the rack rests ON that seam instead of hovering over it)
     --toast-cap the room between those two edges and the stage's right edge, so the rack can
                 never reach COMMS at any scale. This replaces the old 1120px fork, which was
                 hand-guessing the same promise one breakpoint at a time.
   Measuring beats arithmetic here because #screen-game re-flows its padding, gap, rows AND
   columns at 1120/1000/860px and again for crew-rail-off + cinema; the fallbacks below are the
   ≤1000px gutter numbers, so a frame that has not measured yet still opens sane.

   ONE rack width for the whole stack (align-items: stretch, not flex-start): shrink-to-fit cards
   turn a burst into a ragged staircase — a rack of instruments is flush, so the frames, blades,
   stamps and dwell rails all line up column-by-column no matter how long each message is.

   The rack is sized so ordinary one-line prose still fits on one line: dropping below ~356px
   starts wrapping, and a wrapped compact card is TALLER than the roomy one it was meant to shrink.
   Re-measured 2026-08-15 against four real notify() strings: at 232px (the CREW rail's own width)
   two of them went to THREE lines — so the rack keeps its 356px design width and overhangs the
   rail rather than matching it. The width stays content-scaled on purpose: the card's type is what
   you READ and still grows with the TEXT SIZE dial, while --toast-x/-b/-cap arrive already divided
   by that zoom, so where it sits and how much room it may claim do not. */
#toast-stack {
  position: fixed; z-index: var(--z-toast, 9600);
  left: var(--toast-x, 8px); bottom: var(--toast-b, 58px);
  display: flex; flex-direction: column-reverse; gap: 6px; align-items: stretch;
  pointer-events: none; width: min(356px, var(--toast-cap, 37vw));
}
.toast {
  --sev: var(--ph);        /* the blade colour for this card — remapped per severity below */
  --lamp-w: 22px;
  position: relative; display: flex; align-items: stretch; overflow: hidden;
  pointer-events: auto; width: 100%;
  border: 1px solid rgba(var(--ph-rgb), 0.16); border-radius: var(--r-sm);
  background-color: var(--panel2);
  background-image:
    repeating-linear-gradient(0deg, rgba(0,0,0,0.11) 0 1px, transparent 1px 3px),
    linear-gradient(180deg, rgba(var(--ph-bright-rgb), 0.05), rgba(0,0,0,0) 44%),
    linear-gradient(180deg, rgba(0,0,0,0.28), rgba(0,0,0,0.62));
  box-shadow:
    inset 0 1px 0 rgba(var(--ph-bright-rgb), 0.10),   /* machined top hairline */
    inset 0 -1px 0 rgba(0,0,0,0.5),
    0 0 0 1px rgba(0,0,0,0.85),                       /* black seating ring against the canvas */
    0 8px 20px rgba(0,0,0,0.62),
    0 2px 4px rgba(0,0,0,0.5);
  animation: toast-in var(--t-med) var(--ease-out);
  transition: border-color var(--t-fast) var(--ease-soft);
}
/* the registration ticks (panelchrome §1), framing the readout area only — the blade owns the left edge */
.toast::after {
  content: ''; position: absolute; pointer-events: none; opacity: 0.34;
  top: 3px; right: 4px; bottom: 3px; left: calc(var(--lamp-w) + 3px);
  background:
    linear-gradient(var(--ph-dim), var(--ph-dim)) left  top    / 5px 1px,
    linear-gradient(var(--ph-dim), var(--ph-dim)) left  top    / 1px 5px,
    linear-gradient(var(--ph-dim), var(--ph-dim)) right top    / 5px 1px,
    linear-gradient(var(--ph-dim), var(--ph-dim)) right top    / 1px 5px,
    linear-gradient(var(--ph-dim), var(--ph-dim)) left  bottom / 5px 1px,
    linear-gradient(var(--ph-dim), var(--ph-dim)) left  bottom / 1px 5px,
    linear-gradient(var(--ph-dim), var(--ph-dim)) right bottom / 5px 1px,
    linear-gradient(var(--ph-dim), var(--ph-dim)) right bottom / 1px 5px;
  background-repeat: no-repeat;
}

/* ---- the severity blade: a molded phosphor keycap with an ink glyph ---- */
.toast-lamp {
  flex: 0 0 auto; width: var(--lamp-w); align-self: stretch;
  display: grid; place-items: center;
  font-style: normal; font-size: 11px; line-height: 1;
  color: var(--ink); text-shadow: none;
  background:
    repeating-linear-gradient(0deg, rgba(0,0,0,0.10) 0 1px, transparent 1px 3px),
    linear-gradient(180deg,
      color-mix(in srgb, #fff 10%, var(--sev)) 0%,
      var(--sev) 52%,
      color-mix(in srgb, #000 16%, var(--sev)) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.16),
    inset -1px 0 0 rgba(0,0,0,0.45),
    inset 0 -1px 0 rgba(0,0,0,0.22);
  animation: toast-strike 300ms var(--ease-out);
}
/* INFO = an UNLIT indicator. No caller severity means nothing to shout about: a recessed
   housing with a dim glyph, no strike. (The severity-is-a-whisper rule, made material.) */
.toast.sev-info { --sev: var(--ph-dim); }
.toast.sev-info .toast-lamp {
  color: var(--ph-dim);
  background: linear-gradient(180deg, rgba(0,0,0,0.42), rgba(0,0,0,0.66));
  box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb), 0.06), inset -1px 0 0 rgba(0,0,0,0.6);
  animation: none;
}
.toast.sev-good { --sev: var(--ok); }
.toast.sev-gold { --sev: var(--gold); }
.toast.sev-warn { --sev: var(--warn); }
.toast.sev-bad  { --sev: var(--bad); }

/* ---- the readout face ---- */
.toast-body {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px 49px 7px 9px;   /* right pad keeps the message clear of the corner stamp */
}
.toast-kind {
  font-size: 9px; letter-spacing: 1.6px; line-height: 1;
  color: var(--ph-dim); text-shadow: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.toast-txt {
  font-size: var(--fs-1); line-height: 1.22; letter-spacing: var(--ls-1);
  color: var(--text); text-shadow: 0 0 5px var(--ph-glow2);
  overflow-wrap: anywhere;
}
/* a labeled card is a READOUT — the message is the VALUE, so it takes the figure grade */
.toast.labeled .toast-txt { color: var(--ph-bright); letter-spacing: var(--ls-2); }
/* the corner stamp, in the same bracketed form the NOTIFICATIONS panel records use.
   On a LABELED card it rides the label's line (label left, stamp right — one instrument row);
   with no label there is no top row to join, so it centres on the message instead of floating
   above it. Kept clear of the right-hand registration ticks either way. */
.toast-ts {
  position: absolute; top: 6px; right: 12px; pointer-events: none;
  font-size: 9px; letter-spacing: 1px; line-height: 1;
  color: var(--ph-dim); opacity: 0.62; text-shadow: none;
  font-variant-numeric: tabular-nums;
}
.toast:not(.labeled) .toast-ts { top: 50%; transform: translateY(-50%); }
.toast-ts::before { content: '['; }
.toast-ts::after  { content: ']'; }
/* the dwell rail — drains across --dwell (set by toast() from the dismiss timer itself) */
.toast-dwell {
  position: absolute; left: var(--lamp-w); right: 0; bottom: 0; height: 2px;
  background: var(--sev); opacity: 0.34; transform-origin: left center;
  animation: toast-dwell var(--dwell, 4200ms) linear var(--t-med) forwards;
}
/* hover: a state change on the frame, never a bloom. An ACTIONABLE card (it opens something)
   lights its whole frame to the severity colour so the click-through reads as offered. */
.toast:hover { border-color: var(--ph-dim); }
.toast.actionable { cursor: pointer; }
.toast.actionable:hover { border-color: var(--sev); }
.toast.actionable:hover .toast-txt { color: var(--ph-bright); }

.toast.leaving { animation: toast-out var(--t-med) var(--ease-soft) forwards; }
@keyframes toast-in {
  from { opacity: 0; transform: translate3d(-20px, 4px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
@keyframes toast-out {
  from { opacity: 1; transform: translateX(0); max-height: 96px; }
  to   { opacity: 0; transform: translateX(-20px); max-height: 0; margin-top: -7px; }
}
/* the lamp STRIKES on arrival: a flood that settles into the molded resting state.
   Box-shadow only — the 100% stop is the .toast-lamp resting value, so there is no snap. */
@keyframes toast-strike {
  0%   { box-shadow: inset 0 0 0 40px rgba(255,255,255,0.34), inset -1px 0 0 rgba(0,0,0,0.45); }
  100% { box-shadow: inset 0 1px 0 rgba(255,255,255,0.16),
                     inset -1px 0 0 rgba(0,0,0,0.45),
                     inset 0 -1px 0 rgba(0,0,0,0.22); }
}
@keyframes toast-dwell {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}

/* ============================================================
   8 · TOGGLE SWITCHES — inset phosphor switches.
   Checkbox stays in the DOM (a11y + label click) but is visually
   replaced by a quiet inset track and a solid sliding handle.
   Scoped to .set-row so only Settings-style toggles are affected.
   ============================================================ */
.set-row input[type=checkbox], .mkt input[type=checkbox] {
  -webkit-appearance: none; appearance: none;
  position: relative; vertical-align: middle;
  box-sizing: border-box; width: 44px; height: 24px; margin: 0 8px 0 0; padding: 0;
  border: 1px solid var(--ph-dim); border-radius: 5px;
  background: var(--panel2);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.65);
  cursor: pointer; flex: 0 0 auto;
  transition: border-color var(--t-fast) var(--ease-soft),
              background-color var(--t-fast) var(--ease-soft),
              box-shadow var(--t-fast) var(--ease-soft);
}
.set-row input[type=checkbox]::before, .mkt input[type=checkbox]::before {
  content: none;
}
.set-row input[type=checkbox]::after, .mkt input[type=checkbox]::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 3px;
  background: color-mix(in srgb, var(--ph) 65%, var(--panel2));
  box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb),.25), 0 1px 2px rgba(0,0,0,.5);
  transition: transform var(--t-fast) var(--ease-out),
              background-color var(--t-fast) var(--ease-soft),
              box-shadow var(--t-fast) var(--ease-soft);
}
.set-row input[type=checkbox]:checked, .mkt input[type=checkbox]:checked {
  border-color: var(--ph);
  background: color-mix(in srgb, var(--ph) 20%, var(--panel2));
  box-shadow: inset 0 1px 3px rgba(0,0,0,.45);
}
.set-row input[type=checkbox]:checked::before, .mkt input[type=checkbox]:checked::before {
  content: none;
}
.set-row input[type=checkbox]:checked::after, .mkt input[type=checkbox]:checked::after {
  transform: translateX(20px);
  background: var(--ph-bright);
  box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb),.45), 0 1px 2px rgba(0,0,0,.5);
}
.set-row input[type=checkbox]:hover:not(:disabled), .mkt input[type=checkbox]:hover:not(:disabled) {
  border-color: var(--ph-bright);
}
.set-row input[type=checkbox]:focus-visible, .mkt input[type=checkbox]:focus-visible {
  outline: 2px solid var(--ph-bright); outline-offset: 3px;
}
.set-row input[type=checkbox]:disabled, .mkt input[type=checkbox]:disabled {
  opacity: 0.6; cursor: not-allowed;
}
/* keep the label text aligned with the new pill — ONLY on rows that carry a
   checkbox (a text-only .set-row keeps its block flow). :has() is supported in
   the Chromium the desktop app / preview run on. */
.set-row:has(> input[type=checkbox]) { display: flex; align-items: center; }

/* ============================================================
   9 · UNIFIED LOADING + EMPTY STATES.
   .loading  — a CRT block-cursor + pulsing label ("reading…").
   .empty-state — a dim glyph + prose, centred.
   Applied to the model catalog / memory core / crew / task board.
   ============================================================ */
.loading {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ph-dim); font-size: 14px; letter-spacing: 1px;
  padding: 8px 2px;
}
.loading::before {
  content: '▮'; color: var(--ph);
  animation: load-blink 1s steps(1) infinite;
  text-shadow: 0 0 6px var(--ph-glow);
}
.loading.pulse { animation: load-pulse 1.4s ease-in-out infinite; }
@keyframes load-blink { 50% { opacity: 0; } }
@keyframes load-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
.empty-state {
  text-align: center; color: var(--ph-dim);
  padding: 22px 16px; line-height: 1.5; font-size: 13px;
}
.empty-state .es-glyph {
  display: block; font-size: 30px; color: var(--ph-dim);
  opacity: 0.6; margin-bottom: 8px; text-shadow: 0 0 8px var(--ph-glow2);
}
.empty-state b { display: block; color: var(--ph); font-size: 15px; letter-spacing: 1px; margin-bottom: 4px; }
.empty-state span { display: block; font-size: 12px; opacity: 0.85; }
/* compact variant — the console-inline empties (no glyph/CTA, just a dim line) */
.empty-state.sm { padding: 10px 4px; font-size: 12px; line-height: 1.4; }
/* the one-off dim console empties share the empty-state grade through here (declared after the
   panels' own sheets) so they re-tint/re-size in ONE place; each keeps only its padding / italic /
   alignment / border delta in its own file. Distinct-color empties (.fb-empty bright, .mkt-empty
   --text, .key-empty CTA), the structured COMMS .cmsg-empty (starter chips) and .kb-empty-col
   (a wrapper around a real .empty-state) are intentionally not folded in. */
.mkt-dos-empty, .model-dock-empty, .mc-empty, .cd-empty, .proj-empty, .up-empty, .wg-pop-none {
  color: var(--ph-dim); font-size: 12px; line-height: 1.4;
}

/* ============================================================
   10 · PROGRESS / GAUGE EASING — fills glide instead of snapping.
   The context gauge already eases (style.css); this covers the
   generic fills + adds an optional shimmer sweep on active bars.
   ============================================================ */
.crew-prog > div,
.stat-bar > div,
#xpfill {
  transition: width var(--t-slow) var(--ease-out), background var(--t-med) var(--ease-soft);
}
/* opt-in shimmer sweep for an ACTIVE progress bar (add .bar-active).
   transform-only sweep — cheap, no repaint of the bar body. */
.bar-active { position: relative; overflow: hidden; }
.bar-active::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 46px;
  background: linear-gradient(90deg, transparent, var(--ph-glow), transparent);
  animation: bar-shim 1.9s linear infinite;
  pointer-events: none;
}
@keyframes bar-shim {
  0%   { transform: translateX(-46px); }
  100% { transform: translateX(420px); }
}

/* ============================================================
   12 · PREMIUM WINDOW CHROME (Phase 2) — corner brackets, header LED,
   header hairline + top light-grade, footer plate. Generic on the
   shared .term shell so every plain floating window gets it for free.
   Feature windows (.term.feature) keep their molded casing and are
   excluded. Everything here is static / transform-or-opacity only and
   pointer-events:none where it overlays content, so dragging, resize,
   the focus trap and the .term-closing power-off are untouched. Colours
   are phosphor/role vars only, so theme swaps re-tint correctly.
   ============================================================ */
/* status LED at the header's left — "this window is live". Static ok-green
   (a role var, stays green across theme swaps); a slow subtle pulse. */
.term-led {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  background: var(--ok); box-shadow: 0 0 8px var(--ok);
  animation: term-led-pulse 2.4s ease-in-out infinite;
}
@keyframes term-led-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
/* keep the LED + title grouped at the left; the close button stays hard-right.
   (The head is justify-content:space-between with gap — pushing the title's right
   margin to auto collapses the extra gap so led+title read as one unit.) */
.term-head .term-title { margin-right: auto; }
/* hairline gradient rule under the header (layers over the existing 1px border) */
.term:not(.feature):not(.pw) .term-head::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: linear-gradient(90deg, var(--ph-dim), transparent 92%);
  pointer-events: none;
}
/* corner L-brackets + top light-grade, grouped in one static overlay layer.
   z above the glass (.term::before is z6) so the brackets read on the glass. */
.term-chrome { position: absolute; inset: 0; pointer-events: none; z-index: 7; border-radius: inherit; overflow: hidden; }
.term-brk {
  position: absolute; width: 13px; height: 13px; border: 2px solid var(--ph);
  opacity: 0.5; box-shadow: 0 0 6px var(--ph-glow2);
}
.term-brk.tl { top: 6px; left: 6px; border-right: 0; border-bottom: 0; }
.term-brk.tr { top: 6px; right: 6px; border-left: 0; border-bottom: 0; }
.term-brk.bl { bottom: 6px; left: 6px; border-right: 0; border-top: 0; }
.term-brk.br { bottom: 6px; right: 6px; border-left: 0; border-top: 0; }
/* faint top light-grade for glass depth — a static gradient, no repaint cost */
.term-grade {
  position: absolute; left: 0; right: 0; top: 0; height: 40%;
  background: linear-gradient(180deg, rgba(255, 205, 130, 0.05), transparent 88%);
}
/* footer plate — thin bottom strip: status dot + term key left, grip dots right.
   A real flex child after the body, so it never overlaps scroll content. */
.term-foot {
  flex: 0 0 auto; position: relative; z-index: 7;
  display: flex; align-items: center; gap: var(--s-2);
  padding: 4px 12px 5px; border-top: 1px solid var(--ph-dim);
  background: linear-gradient(0deg, var(--ph-glow2), transparent);
  font-size: var(--fs-0); letter-spacing: var(--ls-3); color: var(--ph-dim);
}
.term-foot-d {
  width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  background: var(--ok); box-shadow: 0 0 6px var(--ok);
}
.term-foot-sp { flex: 1; }
.term-foot-grip { color: var(--ph-dim); letter-spacing: 2px; opacity: 0.7; }
/* tiny windows: the plate can crowd — hidden below a small height via :has (Chromium). */
.term:not(.feature) { }

/* ============================================================
   13 · CARD LANGUAGE (Phase 2) — recruitment-bay entrance + hover lift.
   A shared .cardIn entrance (opacity+translateY, ease-out, backwards) with
   a capped per-index stagger set inline via --ci (index) on the row, and a
   hover lift + border brighten + phosphor glow. Transform/opacity only.
   Applied to the existing row/card classes across the panels — additive,
   no markup rename. The stagger var falls back to 0 when unset.
   ============================================================ */
@keyframes cardIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.crew-row, .ag-item,
.kb-card, .gx-tro, .perk, .mc-row, .sk-card,
.key-row, .prov-card, .notif-row, .cn-row {
  animation: cardIn 0.34s var(--ease-out) backwards;
  animation-delay: calc(min(var(--ci, 0), 8) * 25ms);
  transition:
    border-color var(--t-fast) var(--ease-soft),
    box-shadow var(--t-fast) var(--ease-soft),
    background-color var(--t-fast) var(--ease-soft),
    transform var(--t-fast) var(--ease-out);
}
/* live task-board refresh (stationui refreshBoardLive): a background data poke rebuilds the board
   in place — replaying the entrance stagger on every rail refresh reads as a full-board flicker.
   Entrance motion belongs to user-initiated opens only. */
.kb-live-refresh .kb-card, .kb-live-refresh .empty-state { animation: none; }

/* hover lift — transform + border brighten + glow. Scoped to the interactive
   cards (crew/agent/kanban/quest/perk rows); .mc-row etc. inherit the entrance
   but a lift is applied only where the whole row is clickable. */
.crew-row:hover, .ag-item:hover,
.kb-card:hover, .gx-tro:hover, .perk:hover, .prov-card:hover {
  transform: translateY(-1px);
  box-shadow: 0 0 12px var(--ph-glow2);
}

/* ============================================================
   14 · SECTION DIVIDER (Phase 2) — label + diamond node + fading rule.
   Generic .sec swapped in for ad-hoc headers where markup allows. Matches
   the v2 mockup: amber label, a rotated-square node, a gradient hairline
   that fades to the right. Uses phosphor vars only.
   ============================================================ */
.sec {
  display: flex; align-items: center; gap: 9px; margin: 16px 0 10px;
}
.sec:first-child { margin-top: 4px; }
.sec .sec-l {
  color: var(--ph); letter-spacing: var(--ls-3); font-size: var(--fs-1);
  text-shadow: 0 1px 0 #000, 0 0 8px var(--ph-glow2); flex: 0 0 auto;
}
.sec .sec-r {
  flex: 1; height: 4px;
  background:
    linear-gradient(90deg, var(--ph-dim), transparent 92%) top / 100% 1px no-repeat,
    linear-gradient(90deg, var(--ph-faint), transparent 70%) bottom / 100% 1px no-repeat;
}
.sec .sec-nd {
  width: 6px; height: 6px; transform: rotate(45deg); flex: 0 0 auto;
  background: var(--ph); box-shadow: 0 0 6px var(--ph-glow);
}
.sec .sec-tag {
  flex: 0 0 auto; color: var(--ph-dim); font-size: var(--fs-0);
  letter-spacing: var(--ls-2); border: 1px solid var(--ph-dim);
  border-radius: var(--r-sm); padding: 0 6px;
}

/* ============================================================
   14b · BRIEF BLOCK (Phase 2) — a left-border framed note (v2 mockup
   .brief pattern). Used for the ROUTINES intro. Phosphor left rail,
   dim key label, readable body. Vars only.
   ============================================================ */
.brief-block {
  border-left: 2px solid var(--ph); padding: 9px 13px; margin: 4px 0 12px;
  background: rgba(0, 0, 0, 0.25);
  box-shadow: 0 0 12px var(--ph-glow2), inset 0 1px 0 rgba(var(--ph-bright-rgb), 0.04);
}
.brief-block .brief-k {
  font-size: var(--fs-0); color: var(--ph-dim); letter-spacing: var(--ls-3); margin-bottom: 5px;
}
.brief-block .brief-v { font-size: var(--fs-1); color: var(--text); line-height: 1.55; }

/* ============================================================
   15 · EMPTY-STATE CTA (Phase 2) — a small action button inside an
   .empty-state that focuses the relevant existing input. Inherits the
   .btn hover bloom from section 2; just sizing + spacing here.
   ============================================================ */
.empty-state .es-cta {
  margin-top: 10px; display: inline-block;
  background: transparent; color: var(--ph);
  border: 1px solid var(--ph-dim); border-radius: var(--r-sm);
  font-family: inherit; font-size: var(--fs-1); letter-spacing: var(--ls-2);
  padding: 3px 12px; cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-soft),
              background-color var(--t-fast) var(--ease-soft),
              box-shadow var(--t-fast) var(--ease-soft),
              color var(--t-fast) var(--ease-soft);
}
.empty-state .es-cta:hover {
  border-color: var(--ph); color: var(--ph-bright);
  background: var(--ph-faint); box-shadow: 0 0 12px var(--ph-glow2);
}

/* ============================================================
   17 · ACTIVE-WORK SHIMMER (Phase 2 · H) — the crew row's in-flight bar.
   The bar is collapsed until the row carries .working (toggled from the
   real agent.run.start/end state in crewTick). While working it opens to
   a thin filled rail and the .bar-active shimmer sweeps across it, so a
   running agent reads as alive. Indeterminate (full-width fill), not a %.
   ============================================================ */
.crew-row .crew-prog {
  height: 0; margin-top: 0; overflow: hidden;
  transition: height var(--t-med) var(--ease-out), margin-top var(--t-med) var(--ease-out);
}
.crew-row.working .crew-prog { height: 3px; margin-top: 4px; }
.crew-row.working .crew-prog > div {
  width: 100%; background: var(--ph); box-shadow: 0 0 6px var(--ph-glow);
}
/* the shimmer sweep only animates while working (the ::after is present always via
   .bar-active, but with a collapsed 0-height parent it's clipped and invisible). */

/* ============================================================
   16 · QUEST JOURNAL — readable briefing and station console material.
   Scoped so GROWTH and TROPHY grids sharing .gx-tros are untouched.
   ============================================================ */
/* History and deferred quests retain a responsive grid. The active briefing overrides it below.
   motion.css loads after app.css, so quest-grid geometry has one owner here. */
.gx-quests .q-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr)); gap: 16px; align-items: stretch; }
.gx-quests .q-grid .gx-tro { padding: 16px; }
.gx-quests .q-grid .gx-tro .nm { font-size: 22px; margin: 0; font-weight: normal; line-height: 1.15; color: var(--ph-bright); }
.gx-quests .q-grid .gx-tro .sub { font-size: 16px; margin-top: 6px; line-height: 1.3; opacity: 1; color: var(--ph-bright); }
/* Recorded progress stays readable inside its collapsed history disclosure. */
.gx-quests .q-done .gx-tro { opacity: 1; }
.gx-quests .q-done .gx-tro:hover { opacity: 1; }
/* give the OPEN/DONE dividers a touch more air in the log */
.gx-quests .gx-sec { margin-top: 15px; }

/* Commander journal: a mission index and one generous briefing. Counts and state come from
   the existing quest projection; the visual hierarchy adds no rewards or progression rules. */
.quests-win .term-body { container-type: inline-size; }
.gx-quests { color: var(--ph-bright); text-shadow: none; font-size: 16px; }
.q-journal-header { display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 2px 6px 10px; border-bottom: 1px solid var(--ui-edge); margin-bottom: 8px; }
.q-journal-eyebrow { color: var(--ph); font-size: 13px; letter-spacing: .16em; }
.q-journal-header h2 { margin: 4px 0 0; font-size: 24px; font-weight: normal; line-height: 1; }
.q-journal-count { display: flex; align-items: center; gap: 8px; padding: 6px 10px;
  border: 1px solid var(--ui-edge); border-radius: var(--r-sm); background: var(--panel2); box-shadow: var(--ui-well); }
.q-journal-count strong { font-size: 26px; color: var(--gold); font-weight: normal; line-height: 1; }
.q-journal-count span { max-width: 75px; color: var(--ph); font-size: 14px; line-height: 1.15; }
.gx-quests .q-track { padding: 14px; }
.gx-quests .q-track .sub, .gx-quests .q-track-goal, .gx-quests .q-ns-text { font-size: 17px; opacity: 1; }
.gx-quests .q-track .q-go { font-size: 16px; padding: 10px 14px; }
.gx-quests summary { cursor: pointer; font-size: 16px; line-height: 1.4; color: var(--ph-bright); }
.gx-quests summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.q-filters { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 8px 0; border: 1px solid var(--ui-edge);
  border-bottom: 0; border-radius: var(--r-sm) var(--r-sm) 0 0; background: var(--ui-face), var(--panel2);
  box-shadow: inset 0 1px 0 var(--ui-highlight); }
.q-filter { font: inherit; font-size: 15px; padding: 8px 12px; color: var(--ph);
  border: 0; border-bottom: 2px solid transparent; background: transparent; border-radius: 0; cursor: pointer; }
.q-filter span { margin-left: 8px; font-size: 14px; color: var(--ph); }
.q-filter[aria-pressed="true"] { border-bottom-color: var(--ph); background: rgba(var(--ph-rgb), .08); color: var(--ph-bright); }
.q-filter:hover { color: var(--ph-bright); background: rgba(var(--ph-rgb), .05); }
.q-filter:focus-visible, .q-mission:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: -3px; }
.q-journal { display: grid; grid-template-columns: minmax(250px, .9fr) minmax(0, 1.65fr); gap: 0; align-items: stretch;
  border: 1px solid var(--ui-edge); border-radius: 0 0 var(--r-sm) var(--r-sm); background: var(--panel2); box-shadow: var(--ui-well); }
.q-mission-list { display: flex; flex-direction: column; gap: 3px; max-height: 308px; overflow-y: auto; padding: 6px;
  border-right: 1px solid var(--ui-edge); background: rgba(0,0,0,.18); scrollbar-gutter: stable; }
.q-mission { display: flex; align-items: center; flex-shrink: 0; gap: 10px; width: 100%; text-align: left; padding: 10px;
  background: transparent; color: var(--ph-bright); border: 1px solid transparent;
  border-left: 2px solid transparent; border-radius: var(--r-sm); cursor: pointer; font: inherit;
  transition: background-color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out); }
.q-mission:hover { background: rgba(var(--ph-rgb), .05); }
.q-mission.selected { border-color: var(--ui-edge); border-left-color: var(--ph); background: var(--ui-face), rgba(var(--ph-rgb), .05);
  box-shadow: inset 0 1px 0 var(--ui-highlight); }
.q-mission-number { font-size: 17px; color: var(--ph); flex-shrink: 0; font-variant-numeric: tabular-nums; }
.q-mission-copy { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.q-mission-kind { font-size: 12px; letter-spacing: .1em; color: var(--ph); }
.q-mission-title { font-size: 17px; line-height: 1.15; overflow-wrap: anywhere; }
.q-mission-arrow { margin-left: auto; font-size: 22px; color: var(--ph); visibility: hidden; }
.q-mission.selected .q-mission-arrow { visibility: visible; }
.gx-quests .q-briefing { display: block; min-width: 0; }
.gx-quests .q-briefing .q-card { min-height: 308px; height: 100%; padding: 14px 18px; gap: 4px;
  border: 0; border-radius: 0; background: var(--ui-face); box-shadow: inset 0 1px 0 var(--ui-highlight); }
.gx-quests .q-briefing .q-card::before { display: none; }
.gx-quests .q-briefing .q-card:hover { translate: none; box-shadow: inset 0 1px 0 var(--ui-highlight); }
.q-card-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 4px; }
.q-card-meta .q-kind { margin: 0; font-size: 13px; padding: 3px 6px; color: var(--ph); border-color: var(--ui-edge); opacity: 1; }
.q-card-state { font-size: 12px; letter-spacing: .08em; color: var(--ph); }
.q-card-meta .q-dismiss { margin-left: auto; min-width: 32px; min-height: 32px; font-size: 16px; opacity: 1; }
.gx-quests .q-briefing .q-hd { gap: 12px; align-items: center; }
.gx-quests .q-briefing .q-hd .gl { color: var(--ph); font-size: 22px; line-height: 1; opacity: 1; }
.gx-quests .q-grid .q-hd .nm { padding: 0; border: 0; background: none; box-shadow: none; }
.gx-quests .q-briefing .q-hd .nm { font-size: 23px; opacity: 1; letter-spacing: .01em; overflow-wrap: anywhere; }
.gx-quests .q-briefing .q-card .sub { color: var(--ph-bright); letter-spacing: .015em; }
.gx-quests .q-grid .q-card .q-cw { border-top: 1px solid var(--ui-edge); padding: 10px 0 0; text-indent: 0;
  font-size: 16px; line-height: 1.3; opacity: 1; }
.q-field-label { display: block; font-size: 13px; line-height: 1; letter-spacing: .12em; color: var(--ph); margin-bottom: 7px; }
.q-objective-mark { color: var(--ph); }
.gx-quests .q-grid .q-card .q-reward { color: var(--gold); background: rgba(var(--gold-rgb), .035);
  border: 0; border-left: 2px solid rgba(var(--gold-rgb), .45); padding: 9px 12px; text-indent: 0; margin: 8px 0 0;
  font-size: 16px; line-height: 1.3; opacity: 1; }
.q-reward .q-field-label { color: var(--gold); }
.gx-quests .q-grid .q-card .q-actions { padding-top: 10px; }
.gx-quests .q-actions .q-go { font-size: 16px; line-height: 1.2; min-height: 40px; padding: 9px 14px;
  background: var(--ui-face), rgba(var(--ph-rgb), .1); color: var(--ph-bright); border-color: rgba(var(--ph-rgb), .45);
  border-radius: var(--r-sm); box-shadow: inset 0 1px 0 var(--ui-highlight), 0 3px 0 rgba(0,0,0,.7); letter-spacing: .06em; }
.gx-quests .q-actions .q-go:hover { color: var(--ph-bright); border-color: var(--ph); background-color: rgba(var(--ph-rgb), .17); }
.gx-quests .q-actions .q-queue { min-height: 40px; min-width: 40px; font-size: 20px; }
.gx-quests .consent-btn, .gx-quests input, .gx-quests textarea { font-size: 16px; }
.gx-quests .q-life-quest label, .gx-quests .q-life-goal label { color: var(--ph-bright); font-size: 16px; }
.gx-quests .q-journey-card .sub, .gx-quests .q-refresh-card .sub { font-size: 16px; line-height: 1.4; }
.q-journal-records { margin-top: 12px; border: 1px solid var(--ui-edge); border-radius: var(--r-sm); background: var(--panel2); }
.q-journal-records > details + details { border-top: 1px solid var(--ui-edge); }
.gx-quests .q-journal-records > details > summary,
.gx-quests .q-progress-section > summary { display: flex; align-items: center; gap: 10px; min-height: 44px;
  margin: 0; padding: 10px 14px; list-style: none; font-size: 15px; line-height: 1.3; letter-spacing: .04em; color: var(--ph-bright); }
.q-journal-records > details > summary::-webkit-details-marker, .q-progress-section > summary::-webkit-details-marker { display: none; }
.q-journal-records > details > summary::before, .q-progress-section > summary::before { content: '›'; flex: 0 0 16px;
  font-size: 22px; line-height: 1; text-align: center; color: var(--ph); }
.q-journal-records > details[open] > summary::before, .q-progress-section[open] > summary::before { transform: rotate(90deg); }
.q-journal-records > details > summary:hover, .q-progress-section > summary:hover { background: rgba(var(--ph-rgb), .05); }
.q-journal-records > details[open] > summary { border-bottom: 1px solid var(--ui-edge); background: var(--ui-face); }
.q-section-count, .q-section-note { margin-left: auto; color: var(--ph); font-size: 14px; letter-spacing: 0; }
.q-section-count { min-width: 28px; padding: 2px 7px; line-height: 1; text-align: center; border: 1px solid var(--ui-edge); border-radius: var(--r-sm); }
.q-section-body { padding: 14px; }
.gx-quests .q-journey-card { border: 0; background: transparent; padding: 0; margin: 0; }
.q-progress-overview { display: grid; grid-template-columns: 1.5fr 1fr; gap: 12px; }
.gx-quests .q-progress-overview > div { margin: 0; padding: 14px; border: 1px solid var(--ui-edge); border-radius: var(--r-sm);
  background: var(--ui-face), var(--panel2); box-shadow: inset 0 1px 0 var(--ui-highlight); }
.gx-quests .q-progress-overview .q-journey-title, .gx-quests .q-progress-overview .q-ns-eyebrow { font-size: 14px; color: var(--ph); letter-spacing: .08em; }
.q-progress-overview .q-evolution { display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 12px; }
.q-progress-overview .q-evolution-name { font-size: 24px; margin-top: 6px; }
.q-progress-overview .gx-tag { font-size: 16px; line-height: 1.3; }
.q-progress-overview .q-evolution .gx-tag { margin-left: 0; }
.gx-quests .q-progress-overview .sub { margin-top: 10px; line-height: 1.4; }
.q-progress-goal { padding: 14px 0; }
.q-progress-section { border-top: 1px solid var(--ui-edge); }
.q-milestones.q-progress-section { margin: 0; padding: 0; }
.gx-quests .q-progress-section > summary { padding-inline: 0; }
.q-progress-section > .q-section-body { padding: 0 0 14px 26px; }
.q-metric-editor { margin-top: 16px; padding: 12px 14px; border: 1px solid var(--ui-edge); border-radius: var(--r-sm); background: var(--panel2); }
.gx-quests .q-metric-create { grid-template-columns: repeat(3,minmax(0,1fr)); gap: 10px; padding-top: 10px; }
.q-metric-create label { display: flex; flex-direction: column; gap: 6px; font-size: 16px; color: var(--ph-bright); }
.q-metric-create .q-metric-name-field { grid-column: 1 / -1; }
.gx-quests .q-metric-create input, .gx-quests .q-metric-actions input { width: 100%; min-height: 36px; padding: 7px 9px; font-size: 16px; }
.q-metric-create .q-metric-add { grid-column: 1 / -1; justify-self: start; min-height: 40px; padding: 8px 16px; }
.gx-quests .q-mastery-row { padding: 8px 10px; font-size: 16px; }
.gx-quests .q-journey-title { font-size: 18px; line-height: 1.3; }
.q-proof-list .sub + .sub { padding-top: 12px; margin-top: 12px; border-top: 1px solid var(--ui-edge); }
.q-journal-empty { padding: 32px 18px; text-align: center; }
.q-journal-empty > span { color: var(--gold); font-size: 40px; }
.q-journal-empty h3 { font-size: 24px; font-weight: normal; }
.q-journal-empty p { font-size: 17px; line-height: 1.4; }
@container (max-width: 640px) {
  .q-journal { grid-template-columns: minmax(0, 1fr); }
  .q-mission-list { max-height: 160px; border-right: 0; border-bottom: 1px solid var(--ui-edge); }
  .gx-quests .q-briefing .q-card { min-height: 0; padding: 14px; }
  .q-journal-header h2 { font-size: 24px; }
  .q-journal-count { gap: 6px; }
  .q-filter { flex: 1 1 auto; padding: 9px; }
  .gx-quests .q-track-empty { flex-wrap: wrap; }
  .q-progress-overview { grid-template-columns: 1fr; }
  .q-section-body { padding: 14px; }
  .q-journal-records .q-section-note { display: none; }
  .gx-quests .q-metric-create { grid-template-columns: minmax(0,1fr); }
  .q-progress-section > .q-section-body { padding-left: 0; }
}

/* ============================================================
   18 · MINIMIZE-TO-STRIP — a dock strip of minimized-window chips. Built by
   StationUI (stationui.js): header double-click minimizes; minimizing collapses
   the window toward the strip (a compressed power-off), hides it (kept alive),
   and drops a chip into #term-strip in the bottom bar. Chip click restores.
   Phosphor / VT323 only, theme vars only, transform/opacity motion only.
   ============================================================ */

/* hidden (minimized) window: fully removed from layout + interaction, element kept alive */
.term.term-min-hidden { display: none !important; }

/* collapse animation — a compressed variant of the CRT power-off, toward the bottom (the strip lives in
   the bottom bar). transform/opacity only. Two variants mirror the close ones: centered vs dragged. */
.term.term-minimizing {
  animation: term-minimize 200ms var(--ease-soft) forwards;
  pointer-events: none;
}
.term.term-minimizing.term-moved { animation: term-minimize-moved 200ms var(--ease-soft) forwards; }
@keyframes term-minimize {
  0%   { transform: translate(-50%, -50%) scale(1, 1); opacity: 1; }
  100% { transform: translate(-50%, calc(-50% + 40px)) scale(0.7, 0.05); opacity: 0; }
}
@keyframes term-minimize-moved {
  0%   { transform: scale(1, 1) translateY(0); opacity: 1; }
  100% { transform: scale(0.7, 0.05) translateY(40px); opacity: 0; }
}
/* restore: replay a CRT power-on. A centered (never-dragged) window keeps the translate()-based
   keyframes; a .term-moved window carries no centering transform, so it gets a plain variant. Both
   are stamped as an explicit class-scoped animation so it re-triggers even though the base .term-power
   already ran once on the original mount. */
.term.term-restoring { animation: term-power-on 0.42s var(--ease-out); }
@keyframes term-power-on {
  0%   { transform: translate(-50%, -50%) scale(1, 0.012); filter: brightness(6); }
  9%   { transform: translate(-50%, -50%) scale(1, 0.04); }
  46%  { transform: translate(-50%, -50%) scale(1.004, 1.03); filter: brightness(2.2); }
  100% { transform: translate(-50%, -50%) scale(1, 1); filter: brightness(1); }
}
.term.term-restoring.term-moved { animation: term-power-on-moved 0.42s var(--ease-out); }
@keyframes term-power-on-moved {
  0%   { transform: scale(1, 0.012); filter: brightness(6); }
  9%   { transform: scale(1, 0.04); }
  46%  { transform: scale(1.004, 1.03); filter: brightness(2.2); }
  100% { transform: scale(1, 1); filter: brightness(1); }
}

/* THE STRIP — docked in #bottombar before .bb-right. Empty → collapsed to nothing. */
#term-strip {
  display: flex; align-items: center; gap: var(--s-2);
  min-width: 0; max-width: 46vw;
  margin: 0 var(--s-2);
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: thin; scrollbar-color: var(--ph-dim) transparent;
}
#term-strip:not(.has-chips) { display: none; }
#term-strip::-webkit-scrollbar { height: 6px; }
#term-strip::-webkit-scrollbar-track { background: transparent; }
#term-strip::-webkit-scrollbar-thumb { background: var(--ph-dim); border-radius: 3px; }
#term-strip::-webkit-scrollbar-thumb:hover { background: var(--ph); }

/* a chip — tiny LED + VT323 title, phosphor border, hover bloom (premium-chrome language). */
.term-chip {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 1 auto;
  min-width: 54px; max-width: 148px;
  padding: 3px 10px; border-radius: var(--r-sm);
  border: 1px solid var(--ph-dim); background: rgba(0,0,0,0.35);
  color: var(--ph); font-family: inherit; font-size: var(--fs-1); letter-spacing: var(--ls-2);
  cursor: pointer; text-shadow: 0 0 6px var(--ph-glow2); white-space: nowrap;
  /* enter/leave: transform + opacity + a width/margin collapse so neighbours slide over on removal */
  opacity: 0; transform: translateY(6px) scale(0.96);
  transition:
    opacity var(--t-med) var(--ease-out),
    transform var(--t-med) var(--ease-out),
    border-color var(--t-fast) var(--ease-soft),
    box-shadow var(--t-fast) var(--ease-soft),
    background-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.term-chip.in { opacity: 1; transform: translateY(0) scale(1); }
.term-chip.out { opacity: 0; transform: translateY(6px) scale(0.96); pointer-events: none; }
.term-chip:hover { border-color: var(--ph); color: var(--ph-bright); background: var(--ph-faint); box-shadow: 0 0 12px var(--ph-glow2); }
.term-chip:active { filter: brightness(0.86); transition-duration: 60ms; }
.term-chip-led {
  width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
  background: var(--ok); box-shadow: 0 0 6px var(--ok);
  animation: term-led-pulse 2.4s ease-in-out infinite;
}
.term-chip-t { overflow: hidden; text-overflow: ellipsis; min-width: 0; }

/* ============================================================
   11 · REDUCED MOTION — collapse everything added above to near-0.
   Respects the user's OS setting. (The idle looping animations here
   — toast/loading/shimmer — are transform/opacity only and cheap, but
   still honoured.)
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .term.term-closing, .term.term-closing.term-moved,
  .term.term-minimizing, .term.term-minimizing.term-moved,
  .term.term-restoring, .term.term-restoring.term-moved,
  .term-scrim.term-closing,
  .swap-in,
  .toast, .toast.leaving, .toast-lamp,
  .bar-active::after,
  .term-led, .ag-psweep, .term-chip-led,
  .crew-row, .ag-item, .kb-card, .gx-tro, .perk, .mc-row, .sk-card,
  .key-row, .prov-card, .notif-row, .cn-row,
  .loading, .loading.pulse, .loading::before {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
  button, [role="button"],
  .btn, .btn-xl, .btn-sm, .prov, .bb, .bb-grp, .lb-tab,
  .mc-seg-btn, .set-theme, .phosphor-swatches .swatch, .term-x, .term-chip,
  .empty-state .es-cta,
  input, textarea, select,
  .set-row input[type=checkbox], .set-row input[type=checkbox]::after,
  .mkt input[type=checkbox]::after,
  .crew-row:hover, .ag-item:hover, .kb-card:hover, .gx-tro:hover, .perk:hover, .prov-card:hover,
  .gx-quests .q-done .gx-tro:hover,
  .crew-prog > div, .stat-bar > div, #xpfill,
  ::-webkit-scrollbar-thumb {
    transition-duration: 0.01ms !important;
  }
  /* the toast dwell rail is a TIME readout, not decoration — collapsing its duration would snap it
     to zero width (i.e. delete it). Stop it draining and let it rest as a static hairline instead. */
  .toast-dwell { animation: none !important; opacity: 0.22; }
  /* ui-irritants: retire the first-run pulse under reduced-motion (rule appended below) */
  .model-dock-toggle.first-run { animation: none !important; }
  .mc-rec { animation-duration: 0.01ms !important; }
}

/* ============================================================
   ui-irritants lane — appended section (owned by agent/ui-irritants)
   Three targeted UX polish moves:
   (1) model chip first-run pulse — a subtle glow breath so new users
       notice the model/effort selector exists; retired after one
       interaction (JS clears .first-run + persists a localStorage guard).
   (2) memory-core record cards join the shared .cardIn entrance
       (the .mc-rec class was missing from §13's list).
   ============================================================ */
@keyframes modeldock-firstrun {
  0%, 100% { box-shadow: 0 0 0 0 transparent; border-color: var(--ph-dim); }
  50%      { box-shadow: 0 0 12px var(--ph-glow); border-color: var(--ph); }
}
.model-dock-toggle.first-run {
  animation: modeldock-firstrun 2.2s var(--ease-soft) 3;
}
/* memory-core belief cards — the entrance the shared card language already grants siblings.
   Stagger via --ci when set (renderMemoryList can add it); falls back to 0. */
.mc-rec {
  animation: cardIn 0.34s var(--ease-out) backwards;
  animation-delay: calc(min(var(--ci, 0), 8) * 25ms);
}

/* (3) NOTIFICATION SEVERITY — the toast half of this pass now lives in §7, where the lead glyph
   became the molded severity BLADE (.toast-lamp) and the coloured left edge became the blade
   itself. Nothing is appended here any more: a `.toast` rule this late in the file overrides §7
   by cascade order, which is exactly how the old `align-items: baseline` would flatten the blade.
   The NOTIFICATIONS-panel rows (.nf / .nf-sev) keep the glyph+edge treatment in app.css §3468. */

/* ============================================================
   console-windows lane — appended (owned by agent/console-windows)
   The console section-swap reuses the shared .swap-in crossfade (already
   collapsed under reduced-motion above). The rail items animate colour/
   background/border on hover+select; retire those transitions too under
   reduced-motion so a motion-averse user gets an instant, static rail.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .con-rail-item { transition-duration: 0.01ms !important; }
}

/* ============================================================
   settings-truth lane — armed REMOVE pulse (owned by agent/settings-truth)
   The two-step key REMOVE arms on first click; the armed CONFIRM button
   breathes so the destructive state is unmistakable (the calm→armed jump
   was being misread as "nothing happened"). Transform/box-shadow only.
   Static colour/fill lives in app.css; retired under reduced-motion.
   ============================================================ */
@keyframes rm-arm-pulse {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50%      { box-shadow: 0 0 10px 1px var(--bad); }
}
.bb.danger.armed { animation: rm-arm-pulse 1.1s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .bb.danger.armed { animation: none !important; }
}
