/* console_shell.css — the staff shell's GLOBAL layer, and deliberately nothing else.
 *
 * This file is NOT a shared design system that surfaces inherit from. It holds only what
 * genuinely belongs to the DOCUMENT rather than to any one surface — which, as of
 * 2026-08-10, includes the rail frame at the bottom of this file:
 *
 *   ⚠ The chrome-stays-copied line moved once, deliberately, and only once. Each surface
 *   still owns its own interior and its own sub-nav (the copied-chrome decision, analysis
 *   console support-parity rebuild ADR, Decision 5). What was extracted is the rail FRAME
 *   — brand header, the four stacked surface entries, the operator chip — because the
 *   persistent-rail design requires the frame to outlive a crossing, and three copies
 *   living inside three destroyed-and-rebuilt fragments structurally cannot (the staff
 *   console persistent rail ADR, Decision 1, narrowing the shell ADR's Decision 6). The
 *   drift trigger those decisions named has still not fired and is not being claimed.
 *
 * What lives here:
 *
 *   1. The 52 design tokens BOTH surfaces already defined with IDENTICAL values. Measured,
 *      not assumed: the two :root blocks were compared property by property and zero of
 *      the 52 differed. Each surface's OWN extras (Support has 18, Analysis 3) stay on
 *      that surface's root in its own stylesheet, where they still cascade to everything
 *      it renders. Hoisting only the agreed set is what keeps this from quietly becoming
 *      the shared-shell extraction the copied-chrome decision's revisit trigger has not
 *      fired for.
 *   2. The reset, `body`, and the scrollbars — one document, so these can only be declared
 *      once.
 *   3. The boot splash and the login overlay. These are the SHELL's, not a surface's,
 *      because they are exactly what is on screen when NO surface is mounted.
 *   4. The two @keyframes both surfaces declared identically. See the warning below.
 *
 * ⚠ @keyframes IS A GLOBAL NAMESPACE AND SURFACE SCOPING CANNOT PROTECT IT. The scoped
 * surface stylesheet decision (shell ADR, Decision 13) makes every SELECTOR safe by
 * emitting it under `[data-surface="…"]`, and that genuinely retires the two measured
 * conflicts and every future one. It does nothing for animation names: an @keyframes
 * declared inside a scoped block still registers globally, so two surfaces declaring
 * `ff-bob` differently is a silent last-one-wins that no wrapper prevents. Today both
 * surfaces declare `boot-spin` and `ff-bob` with byte-identical bodies — harmless, and
 * exactly the state that drifts unnoticed. They are hoisted HERE so there is one copy;
 * surface-unique names (`ff-pulse`, `anchflash`) stay with their surface, where they
 * cannot collide with anything.
 */

:root {
  /* ============ Flexfilm Design System — the agreed subset ============
     Ported from the claude.ai/design "Flexfilm Console" export. Flex Blue on ink +
     warm-paper panes; Open Sans throughout, at four weights (see the font block below —
     the two-family original is retired on this plane). */
  /* Flex Blue scale (sampled from the logo globe) */
  --flex-blue-50:#e6f4fb; --flex-blue-100:#c3e4f5; --flex-blue-200:#8dcdee;
  --flex-blue-300:#4fb4e4; --flex-blue-400:#1fa2dc; --flex-blue-500:#009fda;
  --flex-blue-600:#0082b3; --flex-blue-700:#006689; --flex-blue-800:#004a64;
  --flex-blue-900:#002f41;
  /* Cool slate neutrals */
  --ink-0:#ffffff; --ink-25:#f7f9fb; --ink-50:#f0f3f6; --ink-100:#e4e8ec;
  --ink-200:#cdd3d9; --ink-300:#a7b0b8; --ink-400:#7d8790; --ink-500:#5a646d;
  --ink-600:#3d454c; --ink-700:#242a2f; --ink-800:#14181b; --ink-900:#0a0c0e;
  /* Semantic accents */
  --success:#2aa462; --success-600:#238a52; --success-soft:#e8f6ee;
  --warning:#e0a100;
  --danger:#d72c2c;  --danger-soft:#fcebeb;
  --flex-blue:#009fda;
  /* Attention (amber) — "a customer is owed a reply" */
  --attn-bg:#fef5dd; --attn-fg:#8a6200; --attn-dot:#e0a100;
  /* Internal traffic (demo) — a yellow held DELIBERATELY apart from the amber attention
     family above. --attn-* means a reply is owed; this means the opposite, that nothing
     is owed because the traffic is ours. */
  --demo-bg:#ffdd57; --demo-fg:#4a3800; --demo-soft:#fff6cc; --demo-line:#f0d688;
  /* ---- THE RAIL PALETTE (Connor, 2026-08-10 — the Shopify-admin rail) ----
     The rail was ink-900 with white-on-dark text until now. It is light, and these seven
     tokens are the WHOLE palette; nothing in the rail should name a colour that is not
     one of them.

     ⚠ THIS IS THE ONE PART OF THE RAIL THAT CANNOT BE PER-SURFACE, and that is why it is
     a token block and not a set of literals. The rail frame belongs to the shell, but the
     sub-nav rendered INSIDE it belongs to each surface and each surface styles `.nav-item`
     in its own stylesheet (the copied-chrome decision, analysis console support-parity
     rebuild ADR, Decision 5). Those copies are expected to diverge on padding, counts and
     which rows exist — they must NEVER diverge on foreground colour, because the failure
     is not "two rails look slightly different." It is white-on-white: a surface still
     carrying the dark-rail values renders an INVISIBLE sub-nav on a light rail, and the
     operator sees an empty column rather than anything that reads as broken. Tokens here
     mean a surface can only get that wrong by deliberately naming a different colour.

     The neutrals are deliberately NOT the --ink-* scale. That scale is cool slate, tuned
     for the panes to the right of the rail; these are true neutrals, sampled to match the
     reference. Mixing the two families in one column is visible as a temperature shift. */
  --rail-bg:#f1f1f1;          /* the column itself */
  --rail-fg:#000000;          /* parent entries, and any child that is hovered or current */
  --rail-fg-muted:#616161;    /* child entries at rest — the parent/child distinction IS this */
  --rail-fg-dim:#a1a1a1;      /* inert: Internal, and the un-wired Settings/Alerts rows */
  --rail-hover:rgba(255,255,255,.72); /* the soft hover pill */
  --rail-sel:#ffffff;         /* the current row's pill — opaque, so it reads as lifted */
  --rail-line:#e0e0e0;        /* the rail's right edge against the near-white panes */
  /* Shared height for the two single-bar column heads, so their divider lines meet
     across the column border. Measured in a browser, not derived. */
  --headbar-h:74px;
  /* Borders / shadows / motion */
  --border:var(--ink-100); --border-strong:var(--ink-200);
  --shadow-1:0 1px 2px rgba(10,12,14,.06);
  --shadow-2:0 2px 6px rgba(10,12,14,.08),0 1px 2px rgba(10,12,14,.04);
  --ease-out:cubic-bezier(.2,.8,.2,1);
  /* Fonts — ONE family for the whole staff console (Connor, 2026-08-10). Quantico is gone
     from this plane; the display ROLE is now carried by weight, size and letterspacing on
     the body face.

     ⚠ --font-display is kept as an ALIAS rather than deleted, and it is declared as
     `var(--font-body)` rather than as a second copy of the same stack. Both halves are
     deliberate. Keeping the token means the ~35 display-role declarations across the four
     stylesheets still SAY which role they are playing, so a future decision to give the
     console a display face again is one line here and not an archaeology exercise. Making
     it an alias rather than a duplicate value means the two cannot silently drift apart —
     the failure mode this file's own header warns about for @keyframes, arriving through
     a different door.

     ⚠ The customer widget is NOT part of this and still ships Quantico by design
     (web_ui_app/src/design-system.css, plus the four bundled .woff2 files). The console
     and the storefront are two typographic systems now; that is Connor's call, not an
     oversight, and the widget's files must not be "tidied" to match. */
  --font-body:'Open Sans',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  --font-display:var(--font-body);
  /* Back-compat aliases both surfaces ride on */
  --bg:var(--ink-25); --panel:var(--ink-0); --line:var(--border);
  --ink:var(--ink-900); --muted:var(--ink-400);
  --hover:var(--ink-50); --active:var(--ink-50); --sel:var(--flex-blue-50);
}

* { box-sizing: border-box; }

/* The `hidden` attribute must always win — class rules that set `display` would
   otherwise override the UA [hidden]{display:none}. JS toggles `.hidden` on these. */
[hidden] { display: none !important; }

body { margin:0; font:14px/1.55 var(--font-body); color:var(--ink-900); background:var(--ink-25);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; }
button { font: inherit; cursor: pointer; }
::-webkit-scrollbar { width:10px; height:10px; }
::-webkit-scrollbar-thumb { background:var(--ink-200); border-radius:99px; border:3px solid transparent; background-clip:content-box; }
::-webkit-scrollbar-thumb:hover { background:var(--ink-300); background-clip:content-box; }

/* ---- boot gate (prevents the login-overlay FOUC) ----
   The splash sits above BOTH the mounted surface and the login overlay (z-index:100) and
   shows only while <html> has `.booting`, so the wrong-surface paint→correct flip is
   hidden until /api/me decides. The shell drops `.booting` only after it has already set
   the correct state, so removing the splash reveals the final surface with no flash in
   either direction (signed-in → surface, signed-out → login). */
.boot-splash { position:fixed; inset:0; display:none; align-items:center; justify-content:center;
  background:var(--ink-25); z-index:100; }
html.booting .boot-splash { display:flex; }
.boot-spinner { width:26px; height:26px; border:3px solid var(--ink-100);
  border-top-color:var(--flex-blue-500); border-radius:50%; animation:boot-spin .7s linear infinite; }

/* ---- the two @keyframes both surfaces declared identically ----
   Hoisted so there is exactly ONE copy. See the file header: scoping a surface's rules
   does not scope its animation names, so two copies here would be a silent collision the
   moment either drifts. Surface-unique names stay in their surface stylesheet. */
@keyframes boot-spin { to { transform:rotate(360deg); } }
@keyframes ff-bob { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-10px);} }

/* ---- login overlay (Polaris / Shopify-Inbox aesthetic) ----
   ONE overlay for the whole platform, which is the visible half of the one-origin,
   one-service, one-session decision (shell ADR, Decision 1): a staff member signs in
   once and every surface is already open to them. Previously each surface carried its
   own copy of this, and crossing between them could land on a second sign-in. */
.overlay { position: fixed; inset: 0; background: var(--ink-25); display: none;
  align-items: center; justify-content: center; z-index: 50; }
.overlay.on { display: flex; }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 14px;
  padding: 28px 28px 24px; width: 340px; box-shadow: 0 16px 48px rgba(0,0,0,.18); }
.card .brand-mark { width: 40px; height: 40px; border-radius: 10px; background: var(--ink);
  color: #fff; font-weight: 700; font-size: 20px; display: flex; align-items: center;
  justify-content: center; margin-bottom: 16px; }
.card h2 { margin: 0 0 3px; font-size: 17px; font-weight: 650; color: var(--ink); }
.card p { margin: 0 0 18px; color: var(--muted); font-size: 13px; }
.card input { width: 100%; padding: 9px 12px; border: 1px solid var(--line);
  border-radius: 8px; font: inherit; color: var(--ink); background: #fff; margin-bottom: 10px; }
.card input:focus { outline: none; border-color: var(--ink); }
.card .err { color: #b91c1c; font-size: 12.5px; min-height: 17px; margin-bottom: 8px; }
.card #login-btn, .card #login-pass-btn { width: 100%; padding: 9px 16px; }
.card [hidden] { display: none !important; }
.card .ok-msg { color: #15803d; font-size: 13px; margin: 12px 0 0; }
.card .alt { margin: 14px 0 0; font-size: 12.5px; }
.card .alt a { color: var(--muted); text-decoration: underline; cursor: pointer; }
.card .alt a:hover { color: var(--ink); }
.card button.primary { background:var(--ink-900); color:#fff; border:1px solid var(--ink-900);
  border-radius:8px; padding:8px 16px; font-family:var(--font-display); font-weight:700;
  font-size:12px; letter-spacing:.06em; text-transform:uppercase; }
.card button.primary:hover { background:var(--ink-700); }
.card button:disabled { opacity: .45; cursor: default; }

/* The broken-deploy state. Lives on the shell rather than in any surface stylesheet for
   the obvious reason: it renders when a surface's own CSS is exactly what failed to load. */
.mount-error { max-width: 34rem; margin: 18vh auto; padding: 0 1.5rem; }
.mount-error h2 { font-family: var(--font-display); font-weight: 800; font-size: 19px; margin: 0 0 10px; }
.mount-error p { color: var(--muted); font-size: 13.5px; margin: 0 0 12px; line-height: 1.55; }
.mount-error .detail { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; color: var(--danger); word-break: break-word; }
.mount-error button.primary { background: var(--ink-900); color: #fff;
  border: 1px solid var(--ink-900); border-radius: 8px; padding: 8px 16px;
  font-family: var(--font-display); font-weight: 700; font-size: 12px;
  letter-spacing: .06em; text-transform: uppercase; }

/* ════════════════════════════════════════════════════════════════════════════════════════
   THE APPLICATION FRAME AND THE PERSISTENT RAIL
   (the staff console persistent rail ADR, 2026-08-10)

   ⚠ These rules are UNSCOPED, and that is the decision rather than an omission. Every
   surface stylesheet is emitted under `[data-surface="…"]` so copies that are MEANT to
   diverge cannot collide (the scoped surface stylesheet decision, staff console
   single-origin shell ADR, Decision 13). The elements below are not any surface's — they
   are the document's, rendered once and never unmounted — so they are declared at document
   scope, and the three surface copies of these rules were DELETED rather than left in
   place (Decision 10). A scoped copy of a rail rule would now match nothing while still
   reading as authoritative to the next person who opens the file.

   What stays SCOPED and therefore still lives in each surface's own stylesheet: `.nav-item`
   and everything it carries. Those style the surface's OWN sub-nav — Support's three
   lifecycle buckets, Analysis's two queues, each surface's Settings and alerts rows — which
   the surfaces are expected to diverge on (the copied-chrome decision, analysis console
   support-parity rebuild ADR, Decision 5). They render inside the rail but they belong to
   the surface, and `data-surface` sits on #app precisely so they still match there.
   ════════════════════════════════════════════════════════════════════════════════════════ */

/* Two columns: the rail, then whatever the mounted surface renders. The 240px matches the
   width the three surfaces' own `.layout` grids each declared for their rail column, so the
   extraction is pixel-neutral — the rail did not move, it changed owner. */
#app { display:grid; grid-template-columns:240px 1fr; grid-template-rows:minmax(0,1fr);
  height:100vh; background:var(--ink-25); }
#app > * { min-height:0; min-width:0; }

/* No `#surface-root:empty { display:none }` any more, and its absence is deliberate. It
   existed because the mount point WAS the whole page, so an empty one had to collapse
   rather than sit there as a blank block. Now the rail paints immediately and the mount
   point is one grid cell — an empty cell beside a rendered rail is the correct picture of
   "signed in, no surface yet," and hiding it would collapse the grid instead. */
#surface-root { overflow:hidden; }

/* ---- the rail frame ----
   ⚠ The right border is not decoration. On the dark rail the column boundary was carried
   by a 20-step luminance jump and no line was needed; --rail-bg against the ink-25 panes
   is a four-step difference, so without an explicit edge the rail and the list beside it
   read as one undifferentiated field. */
.nav { background:var(--rail-bg); border-right:1px solid var(--rail-line); display:flex;
  flex-direction:column; min-width:0; padding:14px 12px 12px; }

/* The rail's own icon base. The three surface stylesheets each declare `svg.ic` and every
   one of them is emitted under `[data-surface="…"]`, which matches here only because
   `data-surface` sits on #app — so the rail's icons are sized by whichever surface happens
   to be mounted. That is fine while one is, and it is nothing at all during boot and on a
   mount failure, when an unsized inline <svg> falls back to the SVG default of 300×150 and
   blows the column apart. The shell styles the shell's own chrome. */
.nav svg.ic { width:17px; height:17px; stroke:currentColor; fill:none; stroke-width:1.8;
  stroke-linecap:round; stroke-linejoin:round; flex:0 0 auto; }

.nav .store { display:flex; align-items:center; gap:10px; padding:4px 8px 16px; }
.nav .store .brand-orb { width:26px; height:26px; }
.nav .store .name { font-family:var(--font-display); font-weight:800; letter-spacing:.04em;
  color:var(--rail-fg); font-size:16px; flex:1; min-width:0; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; text-transform:uppercase; }

/* ---- the stacked surface entries ----
   The stack scrolls as one, because an expanded panel's contents (Support's buckets plus
   its filters group) can outgrow a short viewport while three collapsed heads still need
   to be reachable. Scrolling the whole stack keeps the four entries in one scroll context
   rather than nesting a scroller inside a scroller. */
.surface-stack { flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:2px;
  padding-top:2px; }

.surface-entry { display:flex; flex-direction:column; }

/* The head is the whole clickable row, and it is now SENTENCE CASE at body scale with a
   leading icon (Connor, 2026-08-10).

   ⚠ The parent and its children are deliberately the SAME face, the SAME size and the
   SAME weight, and differ ONLY in colour — parent at --rail-fg, children at
   --rail-fg-muted. That is the whole hierarchy, and it is a stated requirement rather
   than a default that drifted into place, so a later "the parent should be bigger /
   bolder / uppercase again" is a change to the design and not a fix to it. It replaces
   the previous scheme, where the parent was 11px uppercase letterspaced 700 and the
   children were 14px sentence case — two different voices, which made the stack read as
   a heading with unrelated links under it rather than as one tree. */
.surface-head { display:flex; align-items:center; gap:12px; padding:8px 12px; border-radius:8px;
  font-size:13px; font-weight:500; letter-spacing:0; color:var(--rail-fg);
  text-decoration:none; cursor:pointer;
  transition:background .15s var(--ease-out), color .15s var(--ease-out); }
.surface-head .se-label { flex:1; min-width:0; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; }
.surface-head:hover { background:var(--rail-hover); }

/* The mounted surface's entry. `.current` is set by the router on the head and `.open` on
   the entry; they always move together because the accordion IS the route (Decision 2).
   An OPAQUE white pill with a hairline shadow, against a rail that is itself grey — the
   current row is the one that looks lifted OFF the column, which is the only cue left
   once colour is spent on the parent/child distinction. */
.surface-head.current { background:var(--rail-sel); color:var(--rail-fg); font-weight:600;
  box-shadow:0 1px 2px rgba(0,0,0,.07); }

/* The caret. A CSS triangle rather than a glyph so it inherits colour and cannot be
   substituted by a font that lacks it. Points right when collapsed, down when open.
   ⚠ `currentColor` was the right call on the dark rail and is the wrong one here: the
   head's colour is now near-black, so an inherited caret reads as loud as the label it
   trails. Pinned to the muted token and kept at low opacity — a disclosure arrow is
   chrome, not content. */
.se-mark { flex:0 0 auto; width:0; height:0; border-style:solid;
  border-width:4px 0 4px 5px;
  border-color:transparent transparent transparent var(--rail-fg-muted);
  opacity:.5; transition:transform .18s var(--ease-out); transform-origin:35% 50%; }
.surface-entry.open .se-mark { transform:rotate(90deg); }

/* Internal: inert, and it must LOOK inert at a glance. No caret at all — a placeholder
   with a disclosure arrow promises something to disclose. Its ICON stays, and dims with
   the label: an inert row that kept a full-strength icon reads as an enabled row whose
   text failed to load. */
.surface-head.inert { color:var(--rail-fg-dim); cursor:default; }
.surface-head.inert:hover { background:none; color:var(--rail-fg-dim); }
.surface-entry.inert .se-mark { display:none; }

/* Same rule, second cause: a MOUNTED surface that contributed no sub-nav (Evals today) has
   nothing to disclose either, so it shows no caret and does not fold (Connor, 2026-08-10 —
   "if a workspace menu item does not have a subnav, then it should not have a caret").
   `data-subnav` is stamped by the router at adoption time from a fact it already holds; the
   shell never reads inside the panel to decide this (the slot contract, Decision 3). The
   selector is `="0"` rather than `:not([data-subnav="1"])` deliberately — an entry that is
   not mounted carries NO stamp, and it must keep its caret, because whether it has a sub-nav
   is unknown until it mounts rather than known to be false. */
.surface-entry[data-subnav="0"] .se-mark { display:none; }

/* ---- the surface's own sub-nav, hosted here ----
   Collapsed panels are display:none rather than height-animated. An accordion that animates
   its height has to measure content that the surface has not rendered yet — the panel is
   filled at mount, which is after the class is set — and a measured-wrong height is a
   visibly stuck panel. The caret rotation carries the motion instead. */
.surface-panel { display:none; padding:2px 0 6px; }
.surface-entry.open .surface-panel { display:block; }
/* Collapsed entries carry no counts and the shell fetches nothing on their behalf
   (Decision 12) — a deliberate no, recorded because empty space beside a label is exactly
   where a reader assumes something was forgotten. */

/* The pending state (Decision 7). Prepare-then-swap MOVES the crossing's latency behind a
   still-rendered outgoing surface rather than removing it, so without an acknowledgement
   the click reads as ignored — which is not obviously better than the flash it replaced.
   Only possible because the rail now outlives the click. */
.surface-entry.pending .surface-head { color:var(--rail-fg); background:var(--rail-hover); }
.surface-entry.pending .se-mark { border-color:transparent; width:9px; height:9px;
  border:2px solid var(--rail-line); border-top-color:var(--rail-fg-muted); border-radius:50%;
  transform:none; animation:boot-spin .7s linear infinite; }

/* ---- rail foot: the two permanent action rows, then the operator chip ----
   ⚠ `.rail-action` is a SHELL class, deliberately not the surfaces' `.nav-item`, even though
   the two look identical. The rows render on every surface including Evals, and Evals's
   stylesheet carries no rail rules at all — so styling them as `.nav-item` would leave them
   unstyled exactly where the amendment exists to make them appear. Giving the shell's own
   chrome its own class also keeps the two vocabularies from fusing: a surface restyling its
   buckets must not silently restyle the platform's Settings row. */
.rail-foot { margin-top:8px; }
.fbtns { display:flex; flex-direction:column; gap:2px; }

/* Sized and coloured as a CHILD entry (13px / 500 / --rail-fg-muted), not as a parent.
   These are actions on whatever surface you are already looking at, so they sit at the
   same level in the tree as the buckets above them — giving them the parent's near-black
   would put Settings on a level with Support. */
.rail-action { position:relative; display:flex; align-items:center; gap:12px; padding:8px 12px;
  border-radius:8px; cursor:pointer; color:var(--rail-fg-muted); font:inherit; font-size:13px;
  font-weight:500; text-align:left; width:100%; background:none; border:none; user-select:none;
  transition:background .15s var(--ease-out), color .15s var(--ease-out); }
.rail-action:hover { background:var(--rail-hover); color:var(--rail-fg); }
.rail-action .ic { flex:0 0 auto; color:currentColor; }
.rail-action > span { flex:1; min-width:0; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; }

/* The un-wired default. A surface that wires nothing gets a row that renders, does nothing,
   and is visibly not a button — the idiom already set for the alerts row on Analysis. It is
   what an operator sees on Evals, and it is honest: there is nothing to configure there. */
.rail-action.inert, .rail-action.alerts-inert { color:var(--rail-fg-dim); cursor:default; }
.rail-action.inert:hover, .rail-action.alerts-inert:hover { background:none;
  color:var(--rail-fg-dim); }

/* Alerts: a status dot trailing the label — green when armed, red when off. The label is
   pinned (not flex:1) so the dot sits beside the text rather than at the far edge. */
#notify-label { flex:0 0 auto !important; }
.rail-action .adot { width:8px; height:8px; border-radius:99px; flex:0 0 auto; }
.rail-action.alerts-on .adot { background:var(--success); }
.rail-action.alerts-off .adot { background:#d72c2c; }
/* The operator chip. Opaque white on the grey rail rather than the old translucent white-on-
   ink: a 5%-white wash over --rail-bg is indistinguishable from --rail-bg, so the chip's
   whole "this is a distinct object at the foot of the column" reading would have vanished
   silently while every rule still looked present. */
.nav .opchip { display:flex; align-items:center; gap:11px; margin-top:8px; padding:9px 10px;
  border-radius:10px; background:var(--rail-sel); border:1px solid var(--rail-line); }
.nav .opchip .opav { width:32px; height:32px; border-radius:99px; background:var(--flex-blue-500);
  color:#fff; display:flex; align-items:center; justify-content:center;
  font-family:var(--font-display); font-weight:700; font-size:12px; flex:0 0 auto; }
.nav .opchip .opmeta { display:flex; align-items:baseline; justify-content:space-between;
  flex:1 1 auto; min-width:0; gap:8px; }
.nav .opchip .opname { color:var(--rail-fg); font-size:13px; font-weight:600; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; }
.nav .opchip .opsignout { color:var(--rail-fg-muted); font-size:11px; text-decoration:underline;
  cursor:pointer; flex:0 0 auto; white-space:nowrap; }
.nav .opchip .opsignout:hover { color:var(--rail-fg); }
