/* =================================================================================================
   Design tokens — the Curlscape palette (curlscape.com).

   Everything visual in this sheet resolves through these; there are no ad-hoc colours below. The
   values are the brand's own: a COOL slate neutral ramp (its --gray-*) and a single teal accent
   (its --primary / --brand-600, #0781a6). The previous sheet was a warm copper-on-cream theme —
   every off-white, every tint and every accent has been re-based here rather than patched, because
   mixing a warm ramp with a cool accent is what makes an interface read as "off".

   Rule unchanged from before: use the tokens. A new near-identical grey or a second accent is how
   the last sheet drifted.
   ============================================================================================== */
:root {
  /* --- surfaces: ONE cool neutral ramp --- */
  --bg: #f6f8fb;            /* the page */
  --panel: #ffffff;         /* raised cards, the conversation surface */
  --surface-1: #f8fafc;     /* inset panels, code blocks, tool bodies */
  --surface-2: #eff3f8;     /* chips, tool icons, hover fills */
  --surface-3: #e1e7ef;     /* pressed / active chips */
  --muted: #cbd5e1;         /* idle dots, scrollbars, disabled fills */

  --ink: #0f1729;           /* brand --foreground */
  --ink-soft: #48566a;      /* brand --gray-600 */
  --ink-faint: #8494a9;
  --line: #e4eaf1;
  --line-strong: #cfd8e3;

  --accent: #0781a6;        /* brand --primary — the single accent */
  --accent-soft: #e6f6fb;   /* its tint, for chips/fills */
  --accent-ink: #07698d;    /* brand --brand-700, for accent text on light */
  --accent-deep: #0a5371;   /* brand --brand-800, for pressed/hover */
  --accent-bright: #0da1c9; /* brand --brand-500, the light end of the mark gradient */
  --accent-glow: rgba(7,129,166,.16);

  /* status: a tint + a border for each, so cards read as a set. Hues sit in the brand's own
     success/warning/destructive family, desaturated enough to live beside the teal. */
  --ok: #12795a;      --ok-soft: #e6f5ef;   --ok-line: #c3e5d7;
  --warn: #9a6410;    --warn-soft: #fdf3e3; --warn-line: #f0dcb6;
  --err: #bf3d3d;     --err-soft: #fdeceb;  --err-line: #f3cdc9;

  /* shadows: tinted with the ink (slate), not black — a neutral-black shadow on a cool ground
     reads as grime. Kept soft: elevation is carried by hairlines here, not by drop shadows. */
  --shadow-sm: 0 1px 2px rgba(15,23,41,.04), 0 1px 3px rgba(15,23,41,.03);
  --shadow-md: 0 8px 24px rgba(15,23,41,.07), 0 2px 6px rgba(15,23,41,.04);
  --shadow-lg: 0 28px 64px rgba(15,23,41,.12), 0 8px 20px rgba(15,23,41,.06);

  --radius: 14px;
  --radius-sm: 10px;
  --radius-lg: 16px;

  /* type scale — whole pixels only */
  --fs-2xs: 10px;   /* micro-labels */
  --fs-xs: 11px;
  --fs-sm: 12px;
  --fs-md: 13px;
  --fs-base: 14px;
  --fs-lg: 16px;

  /* The readable measure for prose. The conversation surface is now full-width (it runs from the
     sidebar to the right edge), so the text inside it — not the panel — is what gets capped. */
  --measure: 820px;
  /* The run pane does NOT read at the conversation's measure. Its content is a log — label +
     summary rows, plan tables, tool cards, code and solver output — which wants width, not a
     764px column with a field of empty page either side of it. So it fills the pane, with a
     generous cap that only bites on an ultrawide. */
  --measure-run: 1440px;
  --run-gutter: 28px;
  /* Rows reserve this much horizontal padding inside the measure, so their TEXT starts 28px in.
     Blocks that draw a border instead of text — the plan card, the composer — subtract it from
     their own width instead, or their edge would sit 28px outside the column everything else is
     reading down. `--measure-box` is that pre-computed width; use it for anything with a border. */
  --measure-inset: 28px;
  --measure-box: calc(var(--measure) - 2 * var(--measure-inset));

  /* The one icon that has to be CSS rather than markup (generated content — see .agent-node.prior).
     Same drawing as ICON.loop in app.js; carried as a mask so `background` still tints it. */
  --mask-loop: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.4 7.4A5.5 5.5 0 1012 11.7'/%3E%3Cpath d='M13.8 3.4v3.9h-3.9'/%3E%3C/svg%3E");

  /* Curlscape's own stack: Inter for text, Space Grotesk for display, JetBrains Mono for code. */
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --display: "Space Grotesk", "Inter", -apple-system, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--ink);
  font-size: var(--fs-base);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Two-column shell: sidebar (agent pipeline) + full-width workspace. */
#app {
  display: grid;
  grid-template-columns: 280px 1fr;
  grid-template-rows: 100vh;
  height: 100vh;
  overflow: hidden;
}

/* ---------------- Sidebar ---------------- */
.sidebar {
  background: var(--panel);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  padding: 22px 18px 16px;
  gap: 18px;
  min-height: 0;
}
.brand { display: flex; align-items: center; gap: 11px; padding: 2px 4px; }
.brand-mark {
  width: 30px; height: 30px; border-radius: 9px;
  background: linear-gradient(140deg, var(--accent-bright), var(--accent-deep));
  color: #fff; display: grid; place-items: center; font-size: 13px;
  box-shadow: var(--shadow-sm);
}
.brand-name { font-family: var(--display); font-weight: 600; font-size: 15px; letter-spacing: -.015em; }

.field-label {
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: .01em;
  color: var(--ink-faint); display: block; margin-bottom: 8px;
}
.field-label.section { margin-bottom: 10px; }

.project-block { display: flex; flex-direction: column; }
.project-row { display: flex; gap: 8px; }
/* The chevron is a background image, so it does NOT reserve space the way a native select arrow
   does — the padding has to. `padding-right` is the chevron's inset (12) + its width (10) + a
   12px gap, so a long project name is clipped with an ellipsis well before it reaches the mark
   instead of running underneath it. */
.project-select {
  flex: 1; min-width: 0; appearance: none; border: 1px solid var(--line-strong);
  background: var(--panel); border-radius: var(--radius-sm); padding: 9px 34px 9px 12px;
  font: inherit; font-size: var(--fs-md); color: var(--ink); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238494a9' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
}
.project-select:focus { outline: none; border-color: var(--accent); }

/* Square, and the same height as whatever it sits beside — in `.project-row` that comes from the
   flex stretch, so no explicit height here (one would also break the `.sm` variant). */
.icon-btn {
  border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink-soft);
  width: 42px; flex: none; border-radius: var(--radius-sm); cursor: pointer;
  font-size: 18px; line-height: 1;
  display: grid; place-items: center; transition: all .15s ease;
}
.icon-btn:hover { border-color: var(--accent); color: var(--accent); }

/* ---------------- Icons ----------------
   Every mark in the app is now a drawn `svg.ic` on one 16px grid (see ICON in app.js), replacing
   the typographic symbols — ▤ ◈ ❉ ⌕ ▸ ✕ — the UI used to borrow from the text font. Those took
   their weight, width and baseline from whatever font the OS resolved, so a row of them never
   shared an optical line and several simply did not exist on Windows. Size is set HERE, by the
   container, so the markup stays one string per icon and a chip can size its mark without a
   variant of the icon existing. `flex: none` on the base rule stops any of them being squashed by
   a long label beside it. */
.ic { width: 14px; height: 14px; display: block; flex: none; }
.brand-mark .ic, .icon-btn .ic { width: 16px; height: 16px; }
.icon-btn.sm .ic { width: 13px; height: 13px; }
.run-btn .ic, .stop-btn .ic, .zip-btn .ic, .head-btn .ic, .suggestion .ic,
.tool-check .ic, .tool-x .ic, .an-state .ic { width: 13px; height: 13px; }
.link-btn .ic, .tool-ic .ic { width: 12px; height: 12px; }
.msg-assistant .av .ic { width: 13px; height: 13px; }
.rl-item .rl-mark .ic { width: 13px; height: 13px; margin-top: 2px; }
.feed-note .fn-ic .ic, .agent-verdict .v-ic .ic { width: 13px; height: 13px; }
.art-ic .ic, .att-ic .ic { width: 21px; height: 21px; }
.empty-mark .ic, .license-mark .ic { width: 22px; height: 22px; }
.drop-ic .ic { width: 26px; height: 26px; }
.lb-mark { color: var(--accent); display: flex; justify-content: center; }
.lb-mark .ic { width: 30px; height: 30px; }

/* ---- agent pipeline tree (sidebar) ---- */
.pipeline-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.pipeline-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.run-btn {
  border: 1px solid var(--accent); background: var(--accent); color: #fff; border-radius: 8px;
  padding: 5px 13px 5px 11px; font: inherit; font-size: var(--fs-xs); font-weight: 600; cursor: pointer;
  letter-spacing: .01em; transition: all .14s; box-shadow: var(--shadow-sm);
  display: inline-flex; align-items: center; gap: 6px;
}
.run-btn:hover { background: var(--accent-ink); }
.run-btn:disabled { background: var(--muted); border-color: var(--muted); cursor: not-allowed; }
/* The per-project model picker, between the pipeline head and the tree — it governs the crew
   below it. A quieter, shorter `.project-select`: this is a setting you touch rarely, so it must
   not compete with Run or with the agent tree. Its label sits inline (the `.field-label` default is
   a block with 8px under it, which would push the tree down for no reason). */
.model-row { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; }
.model-row .field-label { margin: 0; flex: none; }
.model-select {
  flex: 1; min-width: 0; appearance: none; border: 1px solid var(--line); background: var(--panel);
  border-radius: var(--radius-sm); padding: 5px 30px 5px 9px; font: inherit;
  font-size: var(--fs-xs); color: var(--ink-soft); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238494a9' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center;
  transition: border-color .14s, color .14s;
}
.model-select:hover:not(:disabled) { border-color: var(--line-strong); color: var(--ink); }
.model-select:focus { outline: none; border-color: var(--accent); color: var(--ink); }
.model-select:disabled { opacity: .5; cursor: default; }

.agent-tree { overflow-y: auto; display: flex; flex-direction: column; gap: 2px; margin: 0 -6px; padding: 0 6px 6px; }
.phase-group { margin-bottom: 6px; }
.phase-label {
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: .01em;
  color: var(--ink-faint); padding: 8px 8px 4px; display: flex; align-items: center; gap: 7px;
}
.phase-label .pl-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); transition: background .3s; }
.phase-group.active .pl-dot { background: var(--accent); }
.phase-group.done .pl-dot { background: var(--ok); }
.agent-node {
  display: flex; align-items: center; gap: 11px; padding: 7px 10px; border-radius: 8px; cursor: pointer;
  border: 1px solid transparent; transition: background .12s; position: relative;
}
.agent-node:hover { background: var(--surface-1); }
.agent-node.selected { background: var(--accent-soft); border-color: var(--accent-soft); }
/* A step that is due but hasn't run has nothing to show — no transcript, no tool calls, no verdict.
   Clicking it used to open an empty inspector, which reads as a bug rather than as "not yet". So it
   is inert until it starts: dimmed, no pointer, no hover lift. `.locked` is set by buildTree() and
   cleared by setNodeStatus() the moment the node leaves `pending`, so the affordance appears exactly
   when there is something behind it. */
.agent-node.locked { cursor: default; opacity: .5; }
.agent-node.locked:hover { background: none; }
/* ONE signal per column. The mark on the left says what KIND of agent this is and never changes
   colour; the state on the right says what HAPPENED and is the only coloured thing in the row.
   Both columns used to carry the status — eighteen filled, tinted chips down a narrow rail, with a
   second tinted pill on three of the rows — so every row shouted and the eye had nothing to land
   on. Now the accent appears once, on the step that is actually working. */
.agent-node .an-ic {
  width: 18px; height: 18px; display: grid; place-items: center; flex: none;
  color: var(--ink-faint); transition: color .25s;
}
.agent-node .an-ic svg { width: 14px; height: 14px; display: block; }
.agent-node.pending .an-ic { color: var(--muted); }
.agent-node.running .an-ic { color: var(--accent); }
.an-body { min-width: 0; flex: 1; }
.an-label { font-size: var(--fs-sm); font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.agent-node.pending .an-label { color: var(--ink-faint); }
.an-state { flex: none; display: flex; align-items: center; }
.an-state > span { display: flex; }
.an-spin { width: 11px; height: 11px; border: 2px solid var(--accent-soft); border-top-color: var(--accent); border-radius: 50%; animation: spin .7s linear infinite; }
.an-state .ok { color: var(--ok); } .an-state .warn { color: var(--warn); } .an-state .error { color: var(--err); }
.an-state .skip { color: var(--ink-faint); }
/* Transport backoff and its terminal form. Both borrow the existing --warn ramp rather than a new
   colour (CLAUDE.md §10): the step has not failed and must not read like the error family. The
   spinner keeps turning while we wait, because the run genuinely is still going. */
.an-spin.waiting { border-color: var(--warn-line); border-top-color: var(--warn); }
.agent-node.waiting .an-label { color: var(--warn); }
.agent-node.stalled { opacity: .6; }
.agent-node.stalled .an-label { color: var(--ink-faint); }
/* a step the plan decided this run doesn't need */
.agent-node.skipped { opacity: .45; }
.agent-node.skipped .an-label { text-decoration: line-through; }
/* orphaned by a server restart — dimmed like a skip, but NOT struck through: this one did run,
   we just don't know how far it got. */
.agent-node.interrupted, .agent-node.cancelled { opacity: .6; }
.phase-group.skipped .pl-dot { background: transparent; border: 1px solid var(--muted); }

.sidebar-foot { border-top: 1px solid var(--line); padding-top: 12px; margin-top: 4px; }

/* ---------------- Workspace ---------------- */
.center { display: flex; flex-direction: column; min-width: 0; min-height: 0; height: 100%; background: var(--bg); }
.center-head {
  display: flex; align-items: center; gap: 24px;
  padding: 11px 28px; border-bottom: 1px solid var(--line); background: rgba(246,248,251,.82);
  backdrop-filter: blur(8px);
}
.crumb { display: flex; align-items: center; gap: 8px; font-size: var(--fs-sm); flex: none; }
.crumb-app { font-weight: 600; color: var(--ink); }
.crumb-sep { color: var(--ink-faint); }
.crumb-project { color: var(--ink-soft); }
.tabs { display: flex; gap: 4px; margin: 0 auto 0 8px; }
.tab {
  border: none; background: none; font: inherit; font-size: var(--fs-md); font-weight: 500;
  color: var(--ink-faint); padding: 7px 14px; border-radius: 9px; cursor: pointer;
  display: flex; align-items: center; gap: 7px; transition: all .14s;
}
.tab:hover { color: var(--ink-soft); background: var(--surface-1); }
.tab.active { color: var(--ink); background: var(--surface-2); }
/* Visualization and Artifacts are empty until the case produces something. An empty tab you can
   still open is a dead end you have to discover by clicking; greying it out says the same thing
   without the trip. The tooltip carries the reason (setTabEnabled() in app.js). */
.tab.disabled { color: var(--muted); cursor: not-allowed; }
.tab.disabled:hover { color: var(--muted); background: none; }
.tab.disabled .tab-badge { background: var(--surface-2); color: var(--muted); }
.tab-badge { font-size: var(--fs-2xs); background: var(--accent-soft); color: var(--accent-ink); border-radius: 20px; padding: 1px 7px; min-width: 18px; text-align: center; }
.tab-badge:empty { display: none; }
/* Visualization's badge is a state, not a count — a drawn dot rather than a "●" from the text font,
   which arrived at whatever size and baseline the font felt like. */
.tb-dot { display: block; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* Header-level view control. It sits with the tabs because it does the same kind of thing — it
   changes what the workspace shows — rather than on the conversation, which is content. */
.head-btn {
  flex: none; display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink-soft);
  font: inherit; font-size: var(--fs-sm); font-weight: 500;
  padding: 6px 12px; border-radius: 9px; cursor: pointer; transition: all .14s;
  white-space: nowrap;
}
.head-btn:hover { border-color: var(--line-strong); background: var(--surface-1); color: var(--ink); }
.head-btn .hb-ic { display: flex; color: var(--ink-faint); transition: color .14s; }
.head-btn.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-ink); }
.head-btn.on .hb-ic { color: var(--accent); }
/* --- Chat / Pipeline segment (header) ---
   The workspace's two surfaces are exclusive, so the control that picks between them is a segment,
   not a toggle: a toggle says "extra panel on/off", which is what the docked rail used to be. Only
   meaningful on the Pipeline tab — switchTab() hides it on the others. */
.pane-seg { flex: none; display: flex; gap: 2px; padding: 2px; border-radius: 10px; background: var(--surface-2); }
.pane-seg[hidden] { display: none; }
.pane-seg-btn {
  display: inline-flex; align-items: center; gap: 7px; border: none; background: none; font: inherit;
  font-size: var(--fs-sm); font-weight: 500; color: var(--ink-faint); padding: 5px 12px;
  border-radius: 8px; cursor: pointer; transition: all .14s; white-space: nowrap;
}
.pane-seg-btn svg { width: 14px; height: 14px; }
.pane-seg-btn:hover:not(.active) { background: var(--surface-3); color: var(--ink-soft); }
.pane-seg-btn.active { background: var(--panel); color: var(--ink); box-shadow: var(--shadow-sm); }
/* The other side moved while you were here. Cleared the moment you switch to it. */
.pane-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: none; }
.pane-dot[hidden] { display: none; }

.status-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ink-faint); transition: background .3s; flex: none; }
.status-dot.idle { background: var(--muted); }
.status-dot.busy { background: var(--accent); box-shadow: 0 0 0 0 var(--accent-glow); animation: pulse 1.6s infinite; }
@keyframes pulse { 0%{box-shadow:0 0 0 0 rgba(7,129,166,.35)} 70%{box-shadow:0 0 0 7px rgba(7,129,166,0)} 100%{box-shadow:0 0 0 0 rgba(7,129,166,0)} }

.view { flex: 1; min-height: 0; display: flex; flex-direction: column; position: relative; }
.view.hidden { display: none; }

/* ================= Pipeline view: conversation surface + docked run rail =================
   The run rail beside the conversation owns everything mechanical — the run log and the per-agent
   inspector — so pipeline chatter never shares a scroll with the conversation, and inspecting an
   agent never covers it.

   The conversation is no longer a floating card inside a padded shell — it IS the workspace. It
   runs edge to edge: flush against the sidebar's rule on the left, to the window edge on the right,
   and up to the header with no title bar of its own (the header's breadcrumb already says where you
   are, so a second "Conversation" heading was a band of chrome that bought nothing). The run pane,
   when shown, takes a fixed column off the right and the conversation simply gets narrower.

   Because the surface is now as wide as the window, the readable measure moves onto the CONTENT:
   message rows, notes, plan cards and the composer all centre themselves at --measure so a reply
   never becomes a 1400px line. */
.work { flex: 1; min-height: 0; display: flex; gap: 0; padding: 0; }
.chat-panel {
  flex: 1 1 auto; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; position: relative;
  background: var(--panel); border: 0; border-radius: 0; box-shadow: none; overflow: hidden;
}

/* Drag-and-drop veil. Hidden until `.chat-panel.dropping` is set by the dragenter/dragleave depth
   counter in app.js, and `pointer-events:none` throughout — the veil must never become the drop
   target itself, or moving over it would fire dragleave on the panel and flicker the overlay off
   mid-drag. It sits above the feed but inside the panel's rounded clip. */
.drop-veil {
  position: absolute; inset: 0; z-index: 6; display: grid; place-items: center;
  background: rgba(246,248,251,.88); backdrop-filter: blur(2px);
  opacity: 0; visibility: hidden; transition: opacity .12s; pointer-events: none;
}
.chat-panel.hidden { display: none; }
.chat-panel.dropping .drop-veil { opacity: 1; visibility: visible; }
.drop-card {
  text-align: center; padding: 26px 34px; border-radius: var(--radius-lg);
  background: var(--panel); border: 2px dashed var(--accent); box-shadow: var(--shadow-md);
  max-width: 380px;
}
.drop-ic { color: var(--accent); display: flex; justify-content: center; margin-bottom: 10px; }
.drop-title { font-size: var(--fs-md); font-weight: 600; color: var(--ink); }
.drop-sub { font-size: var(--fs-2xs); color: var(--ink-faint); line-height: 1.5; margin-top: 5px; }

.chat-feed { flex: 1; min-height: 0; overflow-y: auto; padding: 28px 0 12px; scroll-behavior: smooth; }
.msg-row { max-width: var(--measure); margin: 0 auto 20px; padding: 0 var(--measure-inset); }

/* --- the run pane (run log + agent inspector) --- */
/* It used to dock as a fixed 400px column beside the conversation. Now it IS the workspace when
   the header segment says Pipeline, and the conversation is hidden behind it — a plan card, a
   verdict line and an agent transcript all read badly in a third of the window, and the chat lost
   its measure to pay for them. Full width, same surface as the conversation, no card and no
   hairline; the content fills it out to --measure-run (see the token), which is wide enough that
   on an ordinary window the pane simply IS the width of the workspace. */
.run-rail {
  flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column;
  background: var(--panel); border-left: 0;
  overflow: hidden;
}
.run-rail.hidden { display: none; }
.rr-head {
  flex: none; display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  border-bottom: 1px solid var(--line); background: var(--surface-1);
}
/* flex:none, not flex:1 — across a full-width pane a stretched two-button segment reads as a
   toolbar rather than a switch. margin-right:auto keeps Stop and the close button on the right. */
.seg { display: flex; gap: 2px; background: var(--surface-2); border-radius: 9px; padding: 2px; flex: none; margin-right: auto; }
.seg-btn {
  flex: none; min-width: 88px; border: none; background: none; font: inherit; font-size: var(--fs-xs); font-weight: 500;
  color: var(--ink-soft); padding: 5px 10px; border-radius: 7px; cursor: pointer; transition: all .14s;
}
.seg-btn:hover:not(.active) { background: var(--surface-3); }
.seg-btn.active { background: var(--panel); color: var(--ink); box-shadow: var(--shadow-sm); }
.icon-btn.sm { width: 28px; height: 28px; font-size: var(--fs-xs); border-radius: 8px; }
/* Stop is destructive-ish (it abandons work in flight), so it wears the error tint — but quietly,
   as an outline: it sits next to the run log for the whole run and must not compete with it. */
.stop-btn {
  flex: none; border: 1px solid var(--err-line); background: var(--err-soft); color: var(--err);
  border-radius: 8px; padding: 5px 11px 5px 9px; font: inherit; font-size: var(--fs-2xs); font-weight: 600;
  cursor: pointer; transition: all .14s; display: inline-flex; align-items: center; gap: 6px;
}
.stop-btn:hover { background: var(--err); border-color: var(--err); color: #fff; }
.stop-btn:disabled { opacity: .5; cursor: default; background: var(--surface-2); border-color: var(--line); color: var(--ink-faint); }
.stop-btn.hidden { display: none; }
/* The scrollbar stays at the window edge (padding, not max-width) while the content honours
   --measure-run — the same trick the conversation uses via .msg-row, at the run pane's own,
   much wider measure. */
.run-log { flex: 1; min-height: 0; overflow-y: auto; scroll-behavior: smooth;
  padding: 16px max(var(--run-gutter), calc((100% - var(--measure-run)) / 2)) 20px; }
.run-log.hidden { display: none; }
.run-log .feed-note, .run-log .plan-card { max-width: none; margin-left: 0; margin-right: 0; padding-left: 0; padding-right: 0; }
.run-log .plan-card { padding: 14px 16px; }
.run-log .plan-card .pl-row { flex-direction: column; gap: 2px; }
.run-log .plan-card .pl-k { width: auto; }
.run-log-empty { padding: 48px 22px; text-align: center; font-size: var(--fs-sm); color: var(--ink-faint); }
.rl-item {
  display: grid; grid-template-columns: 16px 1fr; gap: 2px 8px; padding: 7px 2px;
  border-bottom: 1px solid var(--line); animation: rise .25s ease;
}
.rl-item .rl-mark { grid-row: 1 / span 2; display: flex; justify-content: center; color: var(--ink-faint); }
.rl-item.ok .rl-mark { color: var(--ok); }
.rl-item.warn .rl-mark { color: var(--warn); }
.rl-item.error .rl-mark { color: var(--err); }
.rl-item.waiting .rl-mark, .rl-item.stalled .rl-mark { color: var(--warn); }
.rl-item .rl-label { font-size: var(--fs-sm); font-weight: 500; color: var(--ink); }
.rl-item.skipped .rl-label { color: var(--ink-faint); text-decoration: line-through; }
.rl-item .rl-text { font-size: var(--fs-xs); color: var(--ink-soft); line-height: 1.45; }

/* Dropping the run pane on a narrow window made sense while it was an EXTRA column stealing
   room from the conversation. It is now one of the two surfaces the header segment picks
   between, so hiding it would leave the Pipeline side of that segment showing nothing. */

.agent-empty { max-width: 560px; margin: 6vh auto 0; text-align: center; padding: 0 28px; animation: rise .5s ease; }
.empty-mark {
  width: 46px; height: 46px; border-radius: 13px; margin: 0 auto 22px;
  background: var(--accent-soft); color: var(--accent); display: grid; place-items: center; font-size: 19px;
}
.agent-empty h1 { font-family: var(--display); font-weight: 500; font-size: 24px; letter-spacing: -.02em; margin-bottom: 12px; }
.agent-empty p { color: var(--ink-soft); font-size: var(--fs-base); margin: 0 auto 22px; max-width: 500px; }
.agent-empty .hero-hint { font-size: var(--fs-sm); color: var(--ink-faint); margin-bottom: 0; }
.suggestion {
  border: 1px solid var(--line-strong); background: var(--panel); border-radius: var(--radius-sm);
  padding: 13px 16px; font: inherit; font-size: var(--fs-md); color: var(--ink); cursor: pointer;
  transition: all .15s ease; box-shadow: var(--shadow-sm); width: 100%;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.suggestion.primary { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }
.suggestion.primary:hover { background: var(--accent-ink); transform: translateY(-1px); box-shadow: var(--shadow-md); }

@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.msg-user .bubble {
  background: var(--ink); color: #f1f5f9; padding: 10px 15px; border-radius: 15px 15px 4px 15px;
  display: inline-block; max-width: 72%; float: right; clear: both;
  font-size: var(--fs-md); line-height: 1.5;
  box-shadow: var(--shadow-sm); white-space: pre-wrap; word-break: break-word;
}
.msg-user::after { content: ""; display: block; clear: both; }

/* One inner column for everything a reply contains. The avatar HANGS into a 30px gutter
   (22px mark + 8px gap) and the name, the prose, the tool cards and the artifact chips all start on
   the same indented edge — so the eye has one entry point per reply and can see at a glance what
   belongs to it. Flush-left, which is how this read before, put the speaker's name, the paragraph,
   the bullet and the code block all on the same x: a wall with no structure in it. The gutter is a
   negative margin rather than a wider row, so the measure and the centring are untouched. */
.msg-assistant { padding-left: 58px; }
.msg-assistant .who { display: flex; align-items: center; gap: 8px; margin-bottom: 7px;
  margin-left: -30px; }
.msg-assistant .av {
  width: 22px; height: 22px; border-radius: 7px; background: linear-gradient(140deg,var(--accent-bright),var(--accent-deep));
  color: #fff; display: grid; place-items: center; flex: none;
}
.msg-assistant .who-name { font-size: var(--fs-sm); font-weight: 600; color: var(--ink-soft); }

/* feed notes (pipeline lifecycle: launch / loop-back / done) */
/* Same gutter as a reply (28px row padding + 22px mark + 8px gap = the 58px column), so a lifecycle
   note reads as another turn in the conversation instead of a differently-indented interruption. */
.feed-note {
  max-width: var(--measure); margin: 0 auto 20px; padding: 0 var(--measure-inset); display: flex; gap: 8px;
  align-items: flex-start; animation: rise .35s ease;
}
.feed-note .fn-ic { width: 22px; height: 22px; border-radius: 7px; display: grid; place-items: center;
  flex: none; background: var(--accent-soft); color: var(--accent); }
.feed-note .fn-ic.ok { background: var(--ok-soft); color: var(--ok); }
.feed-note .fn-ic.warn { background: var(--warn-soft); color: var(--warn); }
.feed-note .fn-ic.loop { background: var(--surface-2); color: var(--ink-soft); }
.feed-note .fn-title { font-size: var(--fs-md); font-weight: 500; color: var(--ink); padding-top: 1px; }
.feed-note .fn-list { margin: 6px 0 0; padding-left: 18px; font-size: var(--fs-sm); color: var(--ink-soft); }
.feed-note .fn-list li { margin: 2px 0; }
.feed-note .fn-brief { margin-top: 4px; font-size: var(--fs-sm); color: var(--ink-soft); font-style: italic; }

/* the Planner's plan — shown before the rest of the run proceeds */
.plan-card {
  max-width: var(--measure-box); margin: 0 auto 22px; padding: 15px 17px; background: var(--panel);
  border: 1px solid var(--line-strong); border-left: 3px solid var(--accent);
  border-radius: 12px; box-shadow: var(--shadow-sm); animation: rise .35s ease;
}
.plan-card .pl-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.plan-card .pl-badge {
  font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .01em;
  color: #fff; background: var(--accent); padding: 2px 10px; border-radius: 20px; flex: none;
}
.plan-card .pl-case { font-family: var(--display); font-size: var(--fs-lg); line-height: 1.35; }
.plan-card .pl-row { display: flex; gap: 12px; padding: 5px 0; font-size: var(--fs-sm); align-items: baseline; }
.plan-card .pl-k {
  flex: none; width: 104px; font-size: var(--fs-xs); font-weight: 500;
  color: var(--ink-faint); padding-top: 2px;
}
.plan-card .pl-v { flex: 1; min-width: 0; color: var(--ink-soft); }
.plan-card .pl-v ul { margin: 0; padding-left: 16px; }
.plan-card .pl-v li { margin: 2px 0; }
.plan-card .pl-v code { font-family: var(--mono); font-size: var(--fs-xs);
  background: var(--surface-2); padding: 1px 5px; border-radius: 4px; color: var(--accent-ink); }
.plan-card .pl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.plan-card .pl-chip {
  font-size: var(--fs-2xs); font-weight: 500; padding: 3px 9px; border-radius: 20px;
  background: var(--accent-soft); color: var(--accent-ink);
}
.plan-card .pl-chip.off { background: var(--surface-2); color: var(--ink-faint); text-decoration: line-through; }

.prose { font-size: var(--fs-base); color: var(--ink); }
.prose p { margin: 0 0 11px; }
.prose p:last-child { margin-bottom: 0; }
.prose h1,.prose h2,.prose h3 { font-weight: 600; margin: 18px 0 9px; letter-spacing: -.01em; }
.prose h1 { font-size: 17px; } .prose h2 { font-size: 15px; } .prose h3 { font-size: var(--fs-base); }
.prose ul,.prose ol { margin: 0 0 11px; padding-left: 20px; }
.prose li { margin: 3px 0; }
.prose code {
  font-family: var(--mono); font-size: var(--fs-sm);
  background: var(--surface-2); padding: 1.5px 5px; border-radius: 5px; color: var(--accent-ink);
}
.prose pre {
  background: var(--surface-1); border: 1px solid var(--line); border-radius: 10px; padding: 13px 15px;
  overflow-x: auto; margin: 0 0 12px;
}
.prose pre code { background: none; padding: 0; color: var(--ink); font-size: var(--fs-sm); line-height: 1.5; }
.prose table { border-collapse: collapse; margin: 0 0 12px; font-size: var(--fs-sm); width: 100%; }
.prose th,.prose td { border: 1px solid var(--line-strong); padding: 6px 10px; text-align: left; }
.prose th { background: var(--surface-1); font-weight: 600; }
.prose a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }
.prose strong { font-weight: 600; }
.cursor { display: inline-block; width: 6px; height: 14px; background: var(--accent); margin-left: 1px;
  border-radius: 1px; vertical-align: text-bottom; animation: blink 1s steps(2) infinite; }
@keyframes blink { 50% { opacity: 0; } }

/* tool activity */
.tools { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; }
.tool {
  border: 1px solid var(--line); background: var(--panel); border-radius: 10px; overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.tool-head { display: flex; align-items: center; gap: 9px; padding: 8px 12px; cursor: pointer; font-size: var(--fs-sm); }
.tool-ic { width: 18px; height: 18px; border-radius: 5px; display: grid; place-items: center;
  font-size: 10px; background: var(--surface-2); color: var(--ink-soft); flex: none; }
.tool-name { font-weight: 600; color: var(--ink); }
.tool-desc { color: var(--ink-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }
.tool-stage { font-size: var(--fs-2xs); font-weight: 500; letter-spacing: .01em;
  color: var(--accent); background: var(--accent-soft); padding: 2px 7px; border-radius: 20px; flex: none; }
.tool-spin { width: 12px; height: 12px; border: 2px solid var(--line-strong); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite; flex: none; }
.tool-check { color: var(--ok); flex: none; display: flex; }
.tool-x { color: var(--err); flex: none; display: flex; }
@keyframes spin { to { transform: rotate(360deg); } }
.tool-body { display: none; border-top: 1px solid var(--line); padding: 10px 12px; background: var(--surface-1); }
.tool.open .tool-body { display: block; }
.tool-body pre { font-family: var(--mono); font-size: var(--fs-xs); color: var(--ink-soft);
  white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow-y: auto; }

/* permission card */
.perm-card { border: 1px solid var(--warn-line); background: var(--warn-soft); border-radius: 10px; padding: 12px 14px; margin-top: 10px; }
.perm-card .pc-title { font-size: var(--fs-sm); font-weight: 600; margin-bottom: 4px; }
.perm-card .pc-tool { font-family: ui-monospace, monospace; font-size: var(--fs-xs); color: var(--accent-ink); }
.perm-actions { display: flex; gap: 8px; margin-top: 10px; }
.perm-actions button { border-radius: 8px; padding: 6px 14px; font: inherit; font-size: var(--fs-sm); font-weight: 500; cursor: pointer; border: 1px solid var(--line-strong); background: var(--panel); }
.perm-actions .allow { background: var(--ink); color: #fff; border-color: var(--ink); }

/* ---- an agent is blocked on YOUR answer ----
   Deliberately the accent card rather than the warn tint used by .perm-card: a permission prompt is
   a guardrail interrupting the agent, this is the agent asking you a question about the case. The
   composer answers it by default (app.js::sendMessage), so the inline input is a convenience, not
   the only way in. */
.ask-card {
  border: 1px solid var(--line-strong); border-left: 3px solid var(--accent);
  background: var(--panel); border-radius: 12px; padding: 14px 16px; margin-top: 4px;
  box-shadow: var(--shadow-sm); animation: rise .3s ease;
}
.ask-card.waiting { border-left-color: var(--accent); background: var(--accent-soft); }
.ask-card.answered { border-left-color: var(--ok); }
.ask-card.timedout { border-left-color: var(--warn); background: var(--warn-soft); }
.ask-card .ask-q { font-family: var(--display); font-size: var(--fs-md); line-height: 1.45; color: var(--ink); }
.ask-card .ask-row { display: flex; gap: 8px; margin-top: 12px; }
.ask-card .ask-in {
  flex: 1; min-width: 0; font: inherit; font-size: var(--fs-sm); padding: 8px 11px;
  border: 1px solid var(--line-strong); border-radius: 9px; background: var(--panel); color: var(--ink);
}
.ask-card .ask-in:focus { outline: none; border-color: var(--accent); }
.ask-card .ask-send {
  flex: none; border: 1px solid var(--accent); background: var(--accent); color: #fff;
  border-radius: 9px; padding: 8px 16px; font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
}
.ask-card .ask-send:hover { background: var(--accent-ink); }
.ask-card .ask-note { margin-top: 10px; font-size: var(--fs-2xs); color: var(--ink-soft); line-height: 1.5; }

/* ---------------- Agent inspector (docked in the run rail, never a full-screen cover) -------- */
.agent-detail { flex: 1; min-height: 0; display: flex; flex-direction: column; animation: fade .18s ease; }
.agent-detail.hidden { display: none; }
.agent-activity { flex: 1; min-height: 0; overflow-y: auto;
  padding: 14px max(var(--run-gutter), calc((100% - var(--measure-run)) / 2)) 24px; }
.agent-pane { padding: 0; animation: rise .3s ease; }
.agent-banner {
  display: flex; align-items: center; gap: 11px; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 12px; background: var(--surface-1); box-shadow: var(--shadow-sm); margin-bottom: 14px;
}
/* The inspector's mark is the same kind-mark as the tree's, so the same agent reads the same in
   both places. Here it KEEPS its tinted chip: it is one per pane, not one per row, so it is a
   subject rather than clutter — and it is the pane's status at a glance. */
.agent-banner .ab-ic { width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center;
  flex: none; background: var(--accent-soft); color: var(--accent); }
.agent-banner .ab-ic svg { width: 17px; height: 17px; display: block; }
.agent-banner.ok .ab-ic { background: var(--ok-soft); color: var(--ok); }
.agent-banner.warn .ab-ic { background: var(--warn-soft); color: var(--warn); }
.agent-banner.error .ab-ic { background: var(--err-soft); color: var(--err); }
.ab-body { flex: 1; min-width: 0; }
.ab-crumb { font-size: var(--fs-2xs); color: var(--ink-faint); letter-spacing: .01em; }
.ab-title { font-size: var(--fs-md); font-weight: 600; letter-spacing: -.01em; margin-top: 1px; }
.ab-status { flex: none; font-size: var(--fs-2xs); font-weight: 600; padding: 4px 10px; border-radius: 20px; }
.ab-status.running { background: var(--accent-soft); color: var(--accent-ink); }
.ab-status.ok { background: var(--ok-soft); color: var(--ok); }
.ab-status.warn { background: var(--warn-soft); color: var(--warn); }
.ab-status.error { background: var(--err-soft); color: var(--err); }
.ab-status.pending { background: var(--surface-2); color: var(--ink-faint); }
.ab-status.skipped { background: var(--surface-2); color: var(--ink-faint); font-style: italic; }
.ab-status.interrupted, .ab-status.cancelled { background: var(--surface-2); color: var(--ink-faint); }
.ab-status.waiting { background: var(--warn-soft); color: var(--warn); }
.ab-status.stalled { background: var(--surface-2); color: var(--ink-faint); font-style: italic; }
.agent-stream { display: flex; flex-direction: column; gap: 10px; }
.agent-stream .prose { font-size: var(--fs-sm); }
.agent-verdict { display: flex; align-items: center; gap: 10px; padding: 13px 16px; border-radius: 12px;
  font-size: var(--fs-md); font-weight: 500; margin-top: 4px; }
.agent-verdict.ok { background: var(--ok-soft); color: var(--ok); border: 1px solid var(--ok-line); }
.agent-verdict.warn { background: var(--warn-soft); color: var(--warn); border: 1px solid var(--warn-line); }
.agent-verdict.error { background: var(--err-soft); color: var(--err); border: 1px solid var(--err-line); }
.agent-verdict.stalled { background: var(--surface-2); color: var(--ink-soft); border: 1px solid var(--line); }
.agent-verdict .v-ic { display: flex; flex: none; }

/* The inspector with nothing selected. It is a pane about ONE step, so with no step picked it has
   nothing to draw — and drawing nothing (a blank column) reads as a broken pane rather than an
   empty one. The mark is the kind-mark, unfilled and faint: it belongs to the same family as the
   tree's rows without competing with them. */
.pane-empty { padding: 56px 26px; text-align: center; animation: fade .22s ease; }
.pane-empty .pe-mark { width: 40px; height: 40px; margin: 0 auto 14px; border-radius: 12px;
  display: grid; place-items: center; background: var(--surface-2); color: var(--ink-faint); }
.pane-empty .pe-mark svg { width: 18px; height: 18px; display: block; }
.pane-empty .pe-head { font-family: var(--display); font-size: var(--fs-md); font-weight: 600;
  color: var(--ink-soft); letter-spacing: -.01em; margin-bottom: 7px; }
.pane-empty .pe-sub { font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-faint);
  max-width: 30em; margin: 0 auto; }

/* ---------------- Files attached to the conversation ----------------
   Chips ride in a `.msg-user` row, so they sit on the user's side like the bubble above them —
   an attachment IS a user turn, not a system notice. Each chip is an <a> to the stored file. */
.att-group { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
.att-chip {
  display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit;
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: 12px;
  padding: 8px 12px 8px 8px; box-shadow: var(--shadow-sm); max-width: 280px;
  transition: border-color .14s, box-shadow .14s, transform .14s;
}
.att-chip:hover { border-color: var(--accent); box-shadow: var(--shadow-md); transform: translateY(-1px); }
.att-thumb { width: 38px; height: 38px; border-radius: 8px; object-fit: cover; flex: none;
  background: var(--surface-2); border: 1px solid var(--line); }
.att-ic { width: 38px; height: 38px; border-radius: 8px; display: grid; place-items: center; flex: none;
  font-size: 15px; background: var(--accent-soft); color: var(--accent); }
.att-meta { min-width: 0; }
.att-name { font-size: var(--fs-sm); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Carries the size and, when it applies, "text extracted" / "text not readable" — which copy the
   agents actually read is a material fact, so it is stated on the chip rather than buried. */
.att-sub { font-size: var(--fs-2xs); color: var(--ink-faint); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------------- Composer (docked at the foot of the conversation panel) ---------------- */
.composer-wrap { flex: none; padding: 12px 18px 14px; border-top: 1px solid var(--line); background: var(--surface-1); }
.composer {
  display: flex; align-items: flex-end; gap: 10px;
  max-width: var(--measure-box); margin: 0 auto;
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: 14px;
  padding: 8px; box-shadow: var(--shadow-sm); transition: border-color .15s, box-shadow .15s;
}
.composer:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.composer textarea {
  flex: 1; border: none; resize: none; font: inherit; font-size: var(--fs-base); color: var(--ink);
  background: none; outline: none; max-height: 180px; line-height: 1.5; padding: 5px 0;
}
/* Quiet by default — attaching is the secondary action in the composer; Send keeps the weight.
   (The composer's left padding drops to 8px so this button, not the text, sets the left inset.) */
.attach-btn {
  width: 34px; height: 34px; margin-bottom: 2px; border-radius: 10px; flex: none; cursor: pointer;
  border: 1px solid transparent; background: none; color: var(--ink-faint);
  display: grid; place-items: center; transition: all .15s;
}
.attach-btn:hover { background: var(--surface-2); border-color: var(--line-strong); color: var(--accent); }
.attach-btn:disabled { opacity: .45; cursor: default; }
/* Send and Run are the same kind of act — commit this to the crew — so they wear the same accent. */
.send-btn { width: 38px; height: 38px; border-radius: 11px; border: none; background: var(--accent); color: #fff;
  cursor: pointer; display: grid; place-items: center; flex: none; transition: all .15s; }
.send-btn:hover { background: var(--accent-ink); }
.send-btn:disabled { background: var(--muted); cursor: not-allowed; }
.composer-hint { text-align: center; font-size: var(--fs-2xs); color: var(--ink-faint); margin-top: 8px; }
.composer-hint b { color: var(--ink-soft); font-weight: 600; }
/* Replaces the keyboard hint while a run is live — the composer used to be disabled here, so the
   affordance has to be stated rather than assumed. */
.run-hint {
  text-align: center; font-size: var(--fs-2xs); color: var(--accent-ink); line-height: 1.5;
  margin: 6px auto 0; max-width: 520px;
}
.run-hint.hidden { display: none; }

/* ---------------- Visualization view ---------------- */
#vizView { display: flex; flex-direction: column; }
#vizView.hidden { display: none; }
.viz-toolbar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 12px 24px; border-bottom: 1px solid var(--line); background: var(--panel); flex: none;
}
.viz-tb-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.viz-tb-right { display: flex; align-items: center; gap: 12px; flex: none; }
.viz-chip {
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: 0; text-transform: capitalize;
  color: var(--ink-soft); background: var(--surface-2); padding: 4px 11px; border-radius: 20px; flex: none;
}
.viz-chip.ok { background: var(--ok-soft); color: var(--ok); }
.viz-asset-name { font-size: var(--fs-sm); color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.viz-field-wrap { display: inline-flex; align-items: center; gap: 8px; font-size: var(--fs-xs); color: var(--ink-faint); }
.viz-field-wrap[hidden] { display: none; }
.viz-field {
  appearance: none; border: 1px solid var(--line-strong); background: var(--panel); border-radius: 8px;
  padding: 6px 26px 6px 11px; font: inherit; font-size: var(--fs-sm); color: var(--ink); cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%239a968f' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
  /* the list grew when multi-component arrays gained per-component entries ("U · X"); cap it so a
     long field name cannot push the Reset button off the toolbar */
  max-width: 240px; text-overflow: ellipsis;
}
.viz-field:focus { outline: none; border-color: var(--accent); }
.viz-btn { border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink-soft);
  border-radius: 8px; padding: 6px 13px; font: inherit; font-size: var(--fs-sm); cursor: pointer; transition: all .14s; }
.viz-btn:hover { border-color: var(--accent); color: var(--accent-ink); }
.viz-btn[hidden] { display: none; }

/* the Visualizer's narration strip — why you are looking at this */
.viz-note {
  flex: none; display: flex; align-items: flex-start; gap: 9px; padding: 10px 24px;
  background: var(--accent-soft); border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm); color: var(--accent-ink); animation: rise .3s ease;
}
.viz-note[hidden] { display: none; }
.viz-note .vn-ic { flex: none; display: flex; padding-top: 2px; }

.viz-body { position: relative; flex: 1; min-height: 0; background:
  radial-gradient(circle at 50% 38%, #ffffff, #e9eff6); overflow: hidden; }
.viz-viewport { position: absolute; inset: 0; }
.viz-viewport canvas { display: block; width: 100% !important; height: 100% !important; }
/* server-side ParaView Web viewer surface (overlays the viewport region when active) */
.viz-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: var(--surface-1); }
.viz-frame[hidden] { display: none; }
.viz-image-wrap { position: absolute; inset: 0; display: grid; place-items: center; padding: 24px; }
.viz-image-wrap img { max-width: 100%; max-height: 100%; border-radius: 10px; box-shadow: var(--shadow-md); }

.viz-colorbar { position: absolute; top: 20px; right: 20px; background: rgba(255,255,255,.94);
  border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px; box-shadow: var(--shadow-sm);
  backdrop-filter: blur(4px); min-width: 96px; }
.viz-colorbar[hidden] { display: none; }
.viz-colorbar .cb-title { font-size: var(--fs-2xs); font-weight: 600; color: var(--ink); margin-bottom: 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.viz-colorbar .cb-scale { display: flex; align-items: stretch; gap: 8px; height: 150px; }
.viz-colorbar .cb-scale span { font-size: var(--fs-2xs); color: var(--ink-soft); display: flex; flex-direction: column;
  justify-content: space-between; font-variant-numeric: tabular-nums; }
.viz-colorbar .cb-grad { width: 14px; border-radius: 4px; border: 1px solid var(--line);
  /* cool-warm (blue→grey→red), matches the vtk.js LUT */
  background: linear-gradient(to top, #3b4cc0, #dcdcdc, #b40426); }

/* In-viewport banner: what the user must know while looking at the 3D panel (fallback in use,
   renderer reconnecting). Sits ON the viewport rather than in the toolbar subtitle — the point is
   that it is impossible to miss at the moment the panel looks wrong. */
.viz-banner {
  position: absolute; top: 12px; left: 50%; transform: translateX(-50%); z-index: 6;
  max-width: min(620px, calc(100% - 32px)); padding: 9px 15px; border-radius: var(--radius-sm);
  font-size: var(--fs-sm); line-height: 1.45; text-align: center;
  box-shadow: var(--shadow-md); animation: rise .3s ease;
  background: var(--warn-soft); border: 1px solid var(--warn-line); color: var(--warn);
}
.viz-banner.info { background: var(--accent-soft); border-color: var(--line-strong); color: var(--accent-ink); }
.viz-banner[hidden] { display: none; }

.viz-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; text-align: center; padding: 32px; pointer-events: none; }
.viz-overlay[hidden] { display: none; }
.viz-overlay h1 { font-family: var(--display); font-weight: 500; font-size: 21px; letter-spacing: -.02em; margin-bottom: 10px; }
.viz-overlay p { color: var(--ink-soft); font-size: var(--fs-md); max-width: 440px; }
.viz-overlay code { font-family: var(--mono); font-size: var(--fs-xs); background: var(--surface-2);
  padding: 1px 5px; border-radius: 5px; color: var(--accent-ink); }

/* solver progress panel */
.solver-panel { flex: none; border-top: 1px solid var(--line); background: var(--panel); padding: 14px 24px; }
.solver-panel.hidden { display: none; }
.sp-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.sp-title { font-size: var(--fs-2xs); font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); }
.sp-status { font-size: var(--fs-2xs); font-weight: 600; padding: 3px 10px; border-radius: 20px; background: var(--surface-2); color: var(--ink-soft); }
.sp-status.running { background: var(--accent-soft); color: var(--accent-ink); }
.sp-status.running::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); margin-right: 6px; vertical-align: middle; animation: blink 1.2s infinite; }
.sp-status.done { background: var(--ok-soft); color: var(--ok); }
.sp-status.failed { background: var(--err-soft); color: var(--err); }
.sp-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.sp-metric { border: 1px solid var(--line); border-radius: 10px; padding: 10px 13px; background: var(--bg); }
.sp-k { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); margin-bottom: 4px; }
.sp-v { font-size: var(--fs-base); font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* result gallery (rendered contour thumbnails) */
.viz-gallery { flex: none; border-top: 1px solid var(--line); background: var(--panel); padding: 12px 24px; }
.viz-gallery[hidden] { display: none; }
.vg-title { font-size: var(--fs-2xs); font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-faint); margin-bottom: 10px; }
.vg-strip { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.vg-thumb { flex: none; width: 116px; cursor: pointer; }
.vg-thumb img { width: 116px; height: 78px; object-fit: cover; border-radius: 8px; border: 1px solid var(--line);
  transition: border-color .14s; display: block; }
.vg-thumb:hover img { border-color: var(--accent); }
.vg-thumb span { display: block; font-size: var(--fs-2xs); color: var(--ink-faint); margin-top: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------------- Artifacts view (full page) ---------------- */
#artifactsView { overflow-y: auto; padding: 24px 32px 40px; }
#artifactsView.hidden { display: none; }
.art-page-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.art-page-title { font-family: var(--display); font-weight: 500; font-size: 20px; letter-spacing: -.01em;
  display: flex; align-items: center; gap: 10px; }
.art-page-title .count { background: var(--surface-2); color: var(--ink-soft); border-radius: 20px; padding: 2px 10px;
  font-size: var(--fs-xs); font-family: var(--font); letter-spacing: 0; }
.rail-actions { display: inline-flex; align-items: center; gap: 8px; }
.zip-btn { display: inline-flex; align-items: center; gap: 5px; font: inherit; font-size: var(--fs-xs);
  font-weight: 600; letter-spacing: .02em; color: var(--ink-soft); background: var(--panel);
  border: 1px solid var(--line-strong); border-radius: 9px; padding: 7px 13px; cursor: pointer;
  transition: background .14s, border-color .14s, color .14s, transform .14s; }
.zip-btn:hover:not(:disabled) { background: var(--ink); color: #fff; border-color: var(--ink); }
.zip-btn:active:not(:disabled) { transform: translateY(1px); }
.zip-btn:disabled { opacity: .4; cursor: default; }
.zip-btn .zip-ic { display: flex; }

/* The tab is a LIST OF GROUPS, each holding its own card grid. `#artifacts` therefore stops being
   the grid itself — one flat grid is what put four screenshots, two geometry files and eighty-four
   postProcessing tables in the same undifferentiated wall. */
.artifacts-list { display: flex; flex-direction: column; gap: 26px; }
.art-group { display: flex; flex-direction: column; gap: 12px; }
/* A <button>, not a <div>: the header is the toggle, so it must be tabbable and respond to Enter
   without re-implementing either. Stripped back to text — it sits above content, not on it. */
.art-group-head {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 4px 2px;
  border: none; background: none; font: inherit; color: var(--ink); cursor: pointer;
  text-align: left; border-bottom: 1px solid var(--line); padding-bottom: 9px;
}
.art-group-head:hover { color: var(--accent-ink); }
.art-group-head .agh-label { font-size: var(--fs-md); font-weight: 600; letter-spacing: -.01em; }
.art-group-head .agh-ic { display: flex; color: var(--ink-faint); }
.art-group-head:hover .agh-ic { color: var(--accent); }
.art-group-head .agh-ic .ic { width: 15px; height: 15px; }
/* Points right when shut, down when open — the one piece of state the header has to carry. */
.art-group-head .agh-chev { display: flex; color: var(--ink-faint); transition: transform .16s; transform: rotate(90deg); }
.art-group-head .agh-chev .ic { width: 13px; height: 13px; }
.art-group.collapsed .agh-chev { transform: rotate(0deg); }
.art-group-head .count {
  background: var(--surface-2); color: var(--ink-soft); border-radius: 20px; padding: 2px 9px;
  font-size: var(--fs-2xs); font-weight: 500;
}
.art-group.collapsed .artifacts-grid { display: none; }

.artifacts-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(252px, 1fr)); gap: 14px; }
.artifact {
  border: 1px solid var(--line); border-radius: 12px; padding: 12px; display: flex; align-items: center; gap: 12px;
  cursor: pointer; transition: all .14s; background: var(--panel); box-shadow: var(--shadow-sm);
}
.artifact:hover { border-color: var(--accent); box-shadow: var(--shadow-md); transform: translateY(-1px); }
.art-thumb { width: 48px; height: 48px; border-radius: 8px; object-fit: cover; flex: none; background: var(--surface-2); border: 1px solid var(--line); }
.art-ic { width: 48px; height: 48px; border-radius: 8px; display: grid; place-items: center; flex: none;
  font-size: 18px; background: var(--accent-soft); color: var(--accent); }
.art-info { min-width: 0; flex: 1; }
.art-name { font-size: var(--fs-sm); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.art-kind { font-size: var(--fs-xs); color: var(--ink-soft); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.art-where { font-size: var(--fs-2xs); color: var(--ink-faint); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.art-dl { color: var(--ink-faint); flex: none; opacity: 0; transition: opacity .14s; }
.artifact:hover .art-dl { opacity: 1; }
.artifacts-empty { font-size: var(--fs-md); color: var(--ink-faint); padding: 40px 4px; text-align: center;
  grid-column: 1 / -1; }

/* lightbox */
.lightbox { position: fixed; inset: 0; background: rgba(15,23,41,.44); backdrop-filter: blur(3px);
  display: grid; place-items: center; z-index: 50; animation: fade .2s; padding: 40px; }
.lightbox.hidden { display: none; }
@keyframes fade { from { opacity: 0; } }
.lightbox-inner { background: var(--panel); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  max-width: 92vw; max-height: 88vh; overflow: hidden; display: flex; flex-direction: column; }
.lightbox-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px;
  border-bottom: 1px solid var(--line); font-weight: 600; font-size: var(--fs-md); }
.lightbox-body { overflow: auto; padding: 0; }
.lightbox-body img { display: block; max-width: 86vw; max-height: 78vh; }
.lightbox-body .doc { padding: 28px 34px; max-width: 760px; }
.lightbox-body .doc.prose { max-width: 760px; }
/* PDFs open in the browser's own viewer. Sized in viewport units for the same reason the image
   rule is: the lightbox itself is capped, and a document needs the whole of what that allows. */
.doc-frame { display: block; width: 78vw; max-width: 900px; height: 78vh; border: none; background: var(--surface-2); }

/* A result carried over from the attempt before a loop-back: readable, clearly not current (P1). */
.agent-node.prior { opacity: .5; }
/* The one mark that cannot be an <svg> in the markup — it is generated content, so it is the same
   drawing carried in as a mask, which keeps it tintable by `color` like the rest. */
.agent-node.prior .an-state::after {
  content: ""; display: inline-block; width: 11px; height: 11px; margin-left: 4px;
  vertical-align: -1px; background: var(--ink-faint);
  -webkit-mask: var(--mask-loop) center/contain no-repeat; mask: var(--mask-loop) center/contain no-repeat;
}
/* One backoff, in the agent's own stream. A separate class from `.prose` on purpose: the
   inspector renders `text` events as the agent's words, and a system wait is not the agent
   talking. */
.wait-note { display: flex; align-items: center; gap: 9px; margin: 10px 0; padding: 8px 12px;
  border-radius: var(--radius-sm); background: var(--warn-soft); border: 1px solid var(--warn-line);
  color: var(--warn); font-size: var(--fs-xs); line-height: 1.45; }
.wait-note .wn-ic { display: flex; flex: none; }
.wait-note .wn-ic .ic { width: 13px; height: 13px; }
.prior-note { margin-bottom: 12px; padding: 9px 13px; border-radius: var(--radius-sm);
  background: var(--warn-soft); border: 1px solid var(--warn-line); color: var(--warn);
  font-size: var(--fs-sm); line-height: 1.45; }

/* Why a phase was dropped, on the phase label itself (P6). */
.phase-label .pl-why { margin-left: 8px; font-size: var(--fs-2xs); color: var(--ink-faint);
  text-transform: none; letter-spacing: 0; font-weight: 400; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.phase-group.skipped .phase-label { opacity: .75; }

/* A tool call that FAILED must not look like one that succeeded (P7). */
.tool.failed, .tool-card.failed { border-color: var(--err-line); background: var(--err-soft); }
.tool.failed .tool-x, .tool-card.failed .tool-x { color: var(--err); font-weight: 700; }

/* in-app modal (project naming) — replaces the browser's unstyleable prompt() box */
.modal { position: fixed; inset: 0; background: rgba(15,23,41,.44); backdrop-filter: blur(3px);
  display: grid; place-items: center; z-index: 60; animation: fade .2s; padding: 40px; }
.modal[hidden] { display: none; }
/* #nameModal is the prompt/confirm box, and the People screen raises it on top of itself to confirm
   a removal or a password reset. Same z-index as its opener plus a later position in the DOM meant
   the People card painted over it and swallowed the click, so every destructive action was
   unconfirmable. It always sits above another modal, never beside one. */
#nameModal { z-index: 70; }
.modal-card { background: var(--panel); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  width: min(420px, 92vw); padding: 26px 28px; animation: rise .22s ease; }
.modal-title { font-family: var(--display); font-weight: 500; font-size: 19px; letter-spacing: -.02em;
  margin-bottom: 16px; }
.modal-label { display: block; font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-faint); margin-bottom: 6px; }
.modal-input { width: 100%; padding: 10px 13px; font: inherit; font-size: var(--fs-md); color: var(--ink);
  background: var(--surface-1); border: 1px solid var(--line-strong); border-radius: var(--radius-sm); }
.modal-input:focus { outline: none; border-color: var(--accent); background: var(--panel); }
.modal-err { min-height: 17px; font-size: var(--fs-xs); color: var(--err); margin-top: 6px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 14px; }
.btn-ghost { border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink-soft);
  border-radius: 8px; padding: 8px 15px; font: inherit; font-size: var(--fs-sm); cursor: pointer; }
.btn-ghost:hover { border-color: var(--ink-faint); color: var(--ink); }
.btn-primary { border: 1px solid var(--accent-deep); background: var(--accent); color: #fff;
  border-radius: 8px; padding: 8px 15px; font: inherit; font-size: var(--fs-sm); font-weight: 500; cursor: pointer; }
.btn-primary:hover { background: var(--accent-deep); }
.btn-danger { border: 1px solid var(--err); background: var(--err); color: #fff;
  border-radius: 8px; padding: 8px 15px; font: inherit; font-size: var(--fs-sm); font-weight: 500; cursor: pointer; }
.btn-danger:hover { filter: brightness(.92); }
.modal-body { font-size: var(--fs-sm); line-height: 1.55; color: var(--ink-soft); margin-bottom: 16px; }
.modal-body[hidden], .modal-label[hidden], .modal-input[hidden] { display: none; }

/* --- sign-in gate ---------------------------------------------------------------------------
   `body.signed-out` is the switch, not `#app { display:none }`: #nameModal and #lightbox are
   siblings of #app, so hiding #app alone would leave them on screen over the gate. One class,
   everything but the gate goes. */
body.signed-out > #app,
body.signed-out > .modal,
body.signed-out > .lightbox { display: none !important; }
/* The licence screen is a FOURTH thing to switch off against, and it needs its own class because it
   also has to hide `.auth-gate` — which `signed-out` deliberately does not, that being the one
   thing it shows. Without this the two gates stack, and the sign-in form on top invites an
   out-of-term user to keep trying a password that is not the problem. */
body.license-ended > #authGate { display: none !important; }
.license-gate .auth-card { text-align: center; }
.license-gate .modal-body { margin: 0 0 4px; }
.license-mark { color: var(--warn); width: 52px; height: 52px;
  margin: 4px auto 16px; display: grid; place-items: center; border-radius: 50%;
  background: var(--warn-soft); border: 1px solid var(--warn-line); }
/* The term itself, spelled out under the sentence. An end date the user can read is what makes the
   message checkable against whatever they were told they had bought. */
.license-term { margin-top: 14px; padding: 9px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--surface-2); font-size: var(--fs-2xs);
  color: var(--ink-faint); font-family: var(--mono); }
.license-term[hidden] { display: none; }
.auth-gate { position: fixed; inset: 0; z-index: 90; display: grid; place-items: center;
  padding: 40px; background: var(--surface-1); animation: fade .2s; overflow: auto; }
.auth-gate[hidden] { display: none; }
.auth-card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg); width: min(400px, 92vw); padding: 30px 30px 26px; animation: rise .22s ease; }
.auth-brand { display: flex; align-items: center; gap: 11px; margin-bottom: 22px; }
.auth-card .modal-label { margin-top: 14px; }
.auth-card .modal-body { margin-bottom: 4px; }
.auth-submit { width: 100%; padding: 11px; font-size: var(--fs-md); margin-top: 10px; }
.auth-alt { margin-top: 14px; text-align: center; font-size: var(--fs-xs); color: var(--ink-faint); }
.linkish { border: 0; background: none; padding: 0; font: inherit; font-size: inherit;
  color: var(--accent-deep); text-decoration: underline; cursor: pointer; }
.linkish:hover { color: var(--accent); }
/* …except in the sidebar's account footer, where three underlined words in a row read as a strip
   of link soup rather than as the quiet housekeeping they are. Underline on hover only. */
.user-pill-actions .linkish { color: var(--ink-faint); text-decoration: none; }
.user-pill-actions .linkish:hover { color: var(--accent-ink); text-decoration: underline; }
.user-pill { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 8px; font-size: var(--fs-2xs); color: var(--ink-faint); }
.user-pill[hidden] { display: none; }
.user-pill-actions { display: flex; align-items: center; gap: 10px; flex: none; }
.user-pill-actions button[hidden] { display: none; }

/* --- accounts screen ------------------------------------------------------------------------ */
.modal-card-wide { width: min(620px, 94vw); }
.users-list { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden;
  max-height: 46vh; overflow-y: auto; }
.user-row { display: flex; align-items: center; gap: 12px; padding: 11px 13px;
  border-bottom: 1px solid var(--line); background: var(--panel); }
.user-row:last-child { border-bottom: 0; }
.user-row.is-disabled { background: var(--surface-2); }
.user-row.is-disabled .user-row-name { color: var(--ink-faint); text-decoration: line-through; }
.user-row-who { flex: 1; min-width: 0; }
.user-row-name { font-size: var(--fs-sm); color: var(--ink); }
.user-row-mail { font-size: var(--fs-2xs); color: var(--ink-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-tag { font-size: var(--fs-2xs); letter-spacing: .01em;
  padding: 2px 7px; border-radius: 99px; border: 1px solid var(--line-strong); color: var(--ink-faint);
  flex: none; }
.user-tag.is-admin { border-color: var(--accent); color: var(--accent-deep); }
.user-tag.is-ok { border-color: var(--ok-line); background: var(--ok-soft); color: var(--ok); }
.user-tag.is-warn { border-color: var(--warn-line); background: var(--warn-soft); color: var(--warn); }
.user-tag.is-error { border-color: var(--err-line); background: var(--err-soft); color: var(--err); }
.user-row-acts { display: flex; gap: 9px; flex: none; }
/* A refusal the guards would issue anyway is shown here as a disabled control with the reason on
   hover — a button that 409s is a worse way to learn the rule than a button that explains it. */
.user-row-acts button[disabled] { opacity: .38; cursor: not-allowed; text-decoration: none; }
/* --- licences (platform operator) ---
   Reuses the accounts rows wholesale; only the trailing controls differ. Native date inputs, so
   there is no picker to style and none to ship. */
.modal-card-licence { width: min(760px, 94vw); }
/* Two date inputs, a dash and a Save take ~370px of a row, which squeezed the name column until
   "· 29 days left" — the one number an operator actually scans this list for — ellipsised away.
   Hence the wider card and a floor under the name column rather than a smaller font. */
.license-row .user-row-who { min-width: 190px; }
.license-dates { display: flex; align-items: center; gap: 7px; flex: none; }
.license-date { width: 140px; padding: 5px 8px; font-size: var(--fs-2xs); }
.license-dash { font-size: var(--fs-2xs); color: var(--ink-faint); }

.users-add { display: flex; gap: 8px; margin-top: 14px; }
.users-add .modal-input { flex: 1; }
.users-add .btn-primary { flex: none; white-space: nowrap; }
/* The one-time password handoff. There is no mail in this system, so this line is the only copy —
   it stays until the admin dismisses it rather than fading like a toast. */
.user-secret { margin-top: 12px; padding: 11px 13px; border-radius: var(--radius-sm);
  border: 1px solid var(--warn-line); background: var(--warn-soft); font-size: var(--fs-xs);
  color: var(--ink-soft); }
.user-secret code { font-family: var(--mono); font-size: var(--fs-sm); color: var(--ink);
  background: var(--surface-1); border: 1px solid var(--line-strong); border-radius: 5px;
  padding: 2px 7px; user-select: all; }
#userPillName { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Project pin/rename/delete (ASSESSMENT-2 G2) — quiet text actions; tidying the picker is
   housekeeping and should not compete with "Run ▸" for attention. */
.project-actions { display: flex; gap: 4px; margin-top: 8px; margin-left: -7px; }
.link-btn { border: 0; background: none; padding: 4px 7px; border-radius: 7px; font: inherit;
  font-size: var(--fs-2xs); color: var(--ink-faint); cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; gap: 5px; transition: background .14s, color .14s; }
.link-btn:hover:not(:disabled) { color: var(--ink); background: var(--surface-2); }
.link-btn.danger:hover:not(:disabled) { color: var(--err); }
.link-btn:disabled { opacity: .4; cursor: default; }
/* Pinned is a state, not an action, so it reads in the accent rather than the faint grey — it is
   the one thing in this row that is currently TRUE of the project. */
.link-btn.on { color: var(--accent-ink); }

/* Which project holds the single global render session (see _viz_lease in app/main.py). Shown only
   when the holder is another project: the one case where the picker has to explain why this
   project's Visualization tab fell back to the in-browser viewer. */
.lease-chip {
  display: flex; align-items: center; gap: 6px; margin-top: 7px;
  padding: 5px 9px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line);
  font-size: var(--fs-2xs); color: var(--ink-soft); line-height: 1.35;
}
/* The attribute is what JS toggles, and the rule above sets a non-`none` display — without this
   override the chip would never hide. */
.lease-chip[hidden] { display: none; }
.lease-chip .lc-mark { color: var(--accent); flex: none; display: flex; }
.lease-chip .lc-name { color: var(--ink); font-weight: 500; }

/* Visible keyboard focus (ASSESSMENT-2 G7). The sheet had no :focus-visible rule at all, so
   tabbing through the app gave no indication of where you were. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: var(--muted); border-radius: 6px; border: 2px solid var(--panel); }
::-webkit-scrollbar-thumb:hover { background: var(--line-strong); }
.hidden { display: none !important; }
