/* DiscoveraVS — the workbench design system.
 *
 * Clinical and precise, not playful: dense, quiet, high-information. The
 * densities here are not stylistic preferences, they are the measured result of
 * fitting a compound table, a module rail and a property panel onto one 1440px
 * screen without any of them becoming unreadable. A looser 13px/38px variant
 * was tried and rolled back, so treat the numbers as load-bearing.
 *
 * Typography rule that runs through everything: IBM Plex Sans for prose and
 * labels, IBM Plex Mono for every piece of data — ids, SMILES, numbers, paths,
 * counts, section eyebrows. Numeric table columns are mono and right-aligned so
 * digits line up down the column.
 *
 * The look, since the 2026 refresh: three surface steps instead of five
 * near-whites that read as one — grey chrome, white work, a recessed step for
 * table heads and wells — with structure drawn by 1px hairlines and shadows
 * kept for what floats (menus, popovers, modals, the glass tools over a 3D
 * view). One blue, used with intent: a solid fill is the one primary action
 * in an area, and "pressed" or "current" is a tint. Every size is a token in
 * :root, so the interface rescales from one place — if the type reads too
 * small, raise --fs-md and --fs-base and change nothing else.
 */

/* The faces themselves, served from this repository. This was an
 * `@import url("https://fonts.googleapis.com/css2?...")`, which meant every
 * page of the interface opened a connection to Google on load — before a
 * compound existed — and `--offline` could not stop it, because the browser
 * makes that request and the server never sees it. The files below are IBM's
 * own OFL-1.1 build; see `fonts/OFL.txt` for the licence, `fonts/README.md`
 * for where they came from, and docs/PRIVACY.md for why the interface makes no
 * third-party request at all.
 *
 * Five faces, because the stacks below use five: Sans at 400/500/600 and Mono
 * at 400/500 — the same five the import asked Google for, so nothing about the
 * type changed when it moved here. Using a weight the list does not declare
 * does not fail loudly: the browser quietly draws the nearest one it has, so
 * adding `font-weight: 700` anywhere below means adding the Bold file here. */

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/IBMPlexSans-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/IBMPlexSans-Medium.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/IBMPlexSans-SemiBold.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/IBMPlexMono-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/IBMPlexMono-Medium.woff2") format("woff2");
}

:root {
  /* Brand, taken from the logo gradient and restrained to one blue family.
     Cyan appears only inside gradients and on dark surfaces. Each blue has one
     job: --accent marks things (focus, ticks, fills, indicators),
     --accent-strong fills the primary button (white text 5.1:1) and
     --accent-dark is accent text and links (5.7:1 on white). Pressed, "on" and
     "current" are a tint with --accent-line round it, never a second solid
     blue: one solid button per area is what makes the next step obvious. */
  --accent: #1a86d8;
  --accent-dark: #0e69b4;
  --accent-light: #3fcbe8;
  --accent-tint: #eaf4fd;
  --accent-tint-2: #dcebfa;   /* hover on something already tinted */
  --accent-line: #b9d8f3;     /* border of a tinted, pressed or selected thing */
  --accent-strong: #1670c0;   /* primary fill */
  --accent-hover: #105fa6;    /* primary :hover */
  --accent-press: #0d5494;    /* primary :active */

  /* Text. --muted was #8a99ab (2.9:1) and it is the colour of half the labels
     on screen, so it now passes AA. --faint is for placeholders only and
     --disabled for disabled controls only; neither is for prose. */
  --ink: #0d1726;
  --ink-2: #33465c;
  --ink-3: #4a5a6f;           /* 6.3:1 on --surface-3 */
  --muted: #5f7086;           /* 5.1:1 on white, 4.6:1 on --surface-3 */
  --disabled: #a3afbd;
  --faint: #7d8da0;           /* 3.4:1 */

  /* Surfaces: three steps. Chrome (rail, tab strip, status strip, left
     panels, modal foot) is a cool grey; the work (panes, toolbars, side
     panels, cards, menus, modals) is white; recessed things (table heads,
     wells, disabled fills) sit one step darker than chrome. */
  --surface: #ffffff;
  --surface-1: #f7f8fa;       /* chrome */
  --surface-2: #f7f8fa;       /* wells and empty states: the chrome step */
  --surface-3: #f0f3f6;       /* recessed */
  --surface-4: #ffffff;       /* side panels and the pager: plain work */
  --canvas: #eef1f5;          /* behind the public pages */
  --hover: #eff2f5;           /* neutral hover: controls, menu items, rail */
  --hover-row: #f6f8fa;       /* table-row hover: quieter, because rows are many */
  --press: #e6ebf0;           /* neutral :active */
  --selected-row: #f3f8fd;    /* a ticked row: between hover and the open one */

  /* Hairlines carry the structure. Controls get a border of their own, dark
     enough (1.75:1) to read as something you can click, not as a divider. */
  --line: #e1e6ec;            /* structure */
  --line-2: #ebeff3;          /* inner dividers */
  --line-3: #f1f4f7;          /* row separators */
  --line-strong: #d3dbe4;     /* emphasised dividers, card edges on grey */
  --line-control: #bac5d1;
  --line-control-hover: #94a3b5;

  --pass: #1a7a4d;            /* was #1f8a58 (4.3:1): AA for small text */
  --pass-tint: #eaf6ef;
  --pass-line: #bfe3cf;
  --fail: #a8443c;
  --fail-tint: #fdf2f1;
  --fail-line: #eccbc7;
  --fail-hover: #8f3730;      /* a solid destructive button's hover */

  /* One colour per module: its rail glyph, its tab marker, its empty-state
     tile and its Notebook entries. They are the identity of a place in the
     app, not a status — so they stay clear of --pass and --fail, which mean
     something. Chosen to stay distinguishable for the common red-green
     deficiencies: the pairs differ in lightness as well as hue. */
  --mod-compounds: #3f7fd0;
  --mod-load:      #2f7f6a;
  --mod-notebook:  #9a5b3d;
  --mod-sketcher:  #6b5fc4;
  --mod-search:    #8a6b1f;
  --mod-ligands:   #9b4d8c;
  --mod-filter:    #1f8a7a;
  --mod-offtarget: #b5622a;
  --mod-target:    #2f7d9e;
  --mod-pipeline:  #7a4fb0;
  --mod-results:   #35618c;
  /* Docking Setup (rail key "setup", pane "docking") and Fragments had no
     colour and fell back to grey, which read as "locked". */
  --mod-setup:     #2f7d9e;
  --mod-fragments: #74861f;

  /* Licence and trial states are amber everywhere — never red. A trial is not
     an error condition. Amber means nothing else, so it is never borrowed for
     a warning or a modified state. */
  --amber-ink: #8a6b1f;
  --amber-bg: #fdf8ec;
  --amber-line: #f2e6c8;
  --amber-strong: #d9a93a;    /* the 3px top rule of a licence/trial modal */

  /* Off-target heat cells: the same amber/red meaning, strong enough that a
     grid of them reads at a glance. */
  --heat-med-bg: #f8ecc9;
  --heat-med-line: #ecd9a4;
  --heat-high-bg: #f6dad6;
  --heat-high-line: #eac3bd;

  /* Dark surfaces: the 3D viewports and copyable command lines. The status
     strip left this group when it became light chrome. */
  --dark: #0d1726;
  --dark-1: #101a29;
  --dark-2: #16243a;
  --dark-line: #26364c;
  --dark-text: #8ba0b8;       /* 6.7:1 on --dark */
  --dark-accent: #5fc0e8;
  --dark-ink: #cfe0f0;        /* bright text on dark */
  /* Glass: the tool groups floating over a 3D view. Translucent, so the
     molecule behind stays visible; outlined, so the group stays findable. */
  --glass-bg: rgba(16, 26, 41, .72);
  --glass-line: rgba(139, 160, 184, .26);
  --glass-on: rgba(95, 192, 232, .18);
  --glass-on-line: rgba(95, 192, 232, .5);

  --sans: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Type scale. One ladder instead of a size per component: controls and
     table cells are --fs-md, prose is --fs-base. Only the five faces above
     exist, so weights stay at 400/500/600 (Mono 400/500). */
  --fs-2xs: 12px;             /* eyebrows, badges, table heads */
  --fs-xs: 13px;              /* meta, mono counts, pager, status strip */
  --fs-sm: 14px;              /* secondary text, notes, small buttons */
  --fs-md: 15.5px;            /* controls, cells, kv, menus, rail, tabs */
  --fs-base: 17px;            /* body, panel titles, modal text */
  --fs-lg: 18.5px;            /* card titles, the brand word, empty titles */
  --fs-xl: 22.5px;            /* h2, modal title */
  --fs-2xl: 27px;             /* h1 */
  --fs-3xl: 31.5px;           /* public h2 */
  --fs-display: 50px;         /* public h1 */

  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px; --sp-6: 32px;
  --gut: 16px;                /* the side padding of every bar and panel, so edges align */

  /* Fixed zone heights. The shell is a column of these; only the middle grows.
     Every one is a box with text in it, so they move with the type: measured
     against a 12.5px base, grown with it to 18.5px, and re-measured at the
     15.5px control scale (topbar 66, tabs 46, status 34, row 44, head 39
     before). Leaving them at the old values gave tall empty bars; shrinking
     them without the type clips rows and squeezes the tab strip. */
  --h-topbar: 56px;
  --h-tabs: 40px;
  --h-status: 28px;
  --h-row: 40px;
  --h-head: 34px;
  /* Every toolbar AND every panel head is at least this tall, so their bottom
     edges line up across a split instead of missing by a few pixels. */
  --h-bar: 48px;
  --w-rail: 236px;            /* .wnav shares it, so the two shells line up */
  /* What the rail collapses to: the glyph, its padding and nothing else. */
  --w-rail-min: 60px;
  --w-panel: 440px;           /* a one-compound side panel */
  --w-panel-wide: 600px;

  /* One control geometry: buttons, inputs and selects share a height, so a
     toolbar of mixed controls is one even row. */
  --h-control: 32px;
  --h-control-sm: 28px;
  --h-control-xs: 24px;
  --h-control-lg: 40px;       /* modals and the public pages */

  --r-xs: 4px;                /* badges, ticks, tiny buttons */
  --r-control: 6px;
  --r-card: 8px;
  --r-window: 12px;           /* modals */
  --r-pill: 999px;

  /* Elevation: only floating layers cast a shadow. */
  --shadow-xs: 0 1px 0 rgba(15, 26, 42, .04);
  --shadow-pop: 0 1px 2px rgba(15, 26, 42, .06), 0 10px 28px -8px rgba(15, 26, 42, .22);
  --shadow-modal: 0 2px 6px rgba(15, 26, 42, .08), 0 28px 64px -16px rgba(15, 26, 42, .38);
  --shadow-glass: 0 6px 18px -6px rgba(0, 0, 0, .55);
  --focus-ring: 0 0 0 3px rgba(26, 134, 216, .30);

  /* Motion: short, so state changes read as feedback, not animation. */
  --ease: cubic-bezier(.4, 0, .2, 1);
  --dur: .14s;
}

* { box-sizing: border-box; }

/* Several components below set an explicit `display`, which outranks the user
   agent's rule for [hidden] and leaves hidden elements on screen. Restore the
   attribute's meaning once, here, rather than in every component that needs it. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
}

body {
  font-family: var(--sans);
  font-size: var(--fs-base);
  color: var(--ink);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

/* Links are --accent-dark: --accent itself is 3.9:1 on white, too light for
   text at these sizes. */
a { color: var(--accent-dark); text-decoration: none; }
a:hover { color: var(--accent-hover); text-decoration: underline; }
/* A link drawn as a button is a button: no underline on hover, and it keeps
   its variant's colour rather than turning link-blue (the landing CTA and the
   account and sign-in buttons did). */
a.btn, a.btn:hover { text-decoration: none; }
a.btn:not(.btn--primary):not(.btn--danger):hover { color: var(--ink-2); }
a.btn--primary, a.btn--primary:hover, a.btn--danger, a.btn--danger:hover { color: #fff; }

.mono { font-family: var(--mono); }
.num { font-family: var(--mono); text-align: right; font-variant-numeric: tabular-nums; }

/* Section labels: small mono capitals above every grouped block. Quieter
   tracking than before (.16em spread a word across its column) but in the
   readable --muted, not a grey that disappears. */
.eyebrow {
  font: 500 var(--fs-2xs)/1 var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}
.eyebrow--lg { font-size: var(--fs-xs); letter-spacing: .08em; color: var(--muted); }

.quiet { color: var(--muted); }
/* Disabled-looking text only; secondary prose is .quiet. */
.dim { color: var(--disabled); }
.pass { color: var(--pass); }
.fail { color: var(--fail); }
.hidden { display: none !important; }

/* The .t-* utilities (size, ink, ellipsis) are at the end of this file, so
   they win over a component's own size: see "Utilities" there. */

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Scrollbars: thin and quiet. A data tool scrolls in many places at once
   (table, panel, rail) and full-width OS bars were heavier than the content
   beside them. Inside the dark surfaces the thumb is dark too. */
* { scrollbar-width: thin; }
html { scrollbar-color: #c3cdd8 transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: #c3cdd8; border-radius: 999px;
  border: 2px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background-color: #a9b6c4; }
::-webkit-scrollbar-track { background: transparent; }
.viewport, .vmenu, .command, .dk-mid { scrollbar-color: #3a4c63 transparent; }

/* The one animation: a running job's dot breathes, so "busy" reads without
   a spinner taking space. */
@keyframes dvs-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(26, 134, 216, .45); }
  50% { box-shadow: 0 0 0 4px rgba(26, 134, 216, 0); }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001s !important; animation-iteration-count: 1 !important;
    transition-duration: .001s !important;
  }
}

/* ====================================================================== */
/*  Buttons and controls                                                   */
/* ====================================================================== */

/* One geometry for every control: a button, an input and a select in the same
   toolbar are the same height and radius, so the row reads as one line. The
   button is inline-flex so an icon and a label centre together; a full-width
   button that wants its label on the left says so with justify-content. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: var(--h-control); padding: 0 12px;
  background: var(--surface); color: var(--ink-2);
  border: 1px solid var(--line-control); border-radius: var(--r-control);
  box-shadow: var(--shadow-xs);
  font: 500 var(--fs-md)/1.2 var(--sans);
  cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.btn:hover:not(:disabled) { background: var(--hover); border-color: var(--line-control-hover); }
.btn:active:not(:disabled) { background: var(--press); }
.btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.btn:disabled {
  color: var(--disabled); background: var(--surface-3); border-color: var(--line);
  box-shadow: none; cursor: not-allowed;
}

.btn--primary {
  background: var(--accent-strong); border-color: var(--accent-strong); color: #fff;
  box-shadow: 0 1px 0 rgba(15, 26, 42, .08), inset 0 1px 0 rgba(255, 255, 255, .12);
}
.btn--primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn--primary:active:not(:disabled) { background: var(--accent-press); }
/* Still recognisably the primary, but plainly not available. The label is
   3.4:1 on its tint, not 2.4:1: a disabled Start docking is the resting state
   of Setup until a target is protonated, so it is read, not just seen. */
.btn--primary:disabled { background: #e3edf7; border-color: #d3e3f2; color: #62809e; box-shadow: none; }

/* Larger button used on the marketing, sign-in and modal surfaces, where
   density is not the constraint. */
.btn--lg { min-height: var(--h-control-lg); padding: 0 18px; font-size: var(--fs-base); }

/* Small button for a picker's own toolbar — "choose all", "clear". Still a
   button: these were plain text links, which read as prose next to the count
   they sat beside and were routinely missed, so people ticked three hundred
   queries by hand. A control that performs an action should look like one. */
.btn--sm { min-height: var(--h-control-sm); padding: 0 10px; font-size: var(--fs-sm); }
.btn--xs {
  min-height: var(--h-control-xs); padding: 0 8px; font-size: var(--fs-xs);
  border-radius: var(--r-xs);
}
.btn--block { display: flex; width: 100%; }
.btn--icon { width: var(--h-control); padding: 0; }
.btn--sm.btn--icon { width: var(--h-control-sm); }

/* A secondary action that should not compete with its neighbours. */
.btn--ghost { background: transparent; border-color: transparent; box-shadow: none; }
.btn--ghost:hover:not(:disabled) { background: var(--hover); border-color: transparent; }

/* An action that reads as a link but behaves as a button (keyboard, disabled). */
.btn--link {
  min-height: 0; padding: 0; background: none; border: 0; box-shadow: none;
  color: var(--accent-dark); font-weight: 500;
}
.btn--link:hover:not(:disabled) { background: none; color: var(--accent-hover); text-decoration: underline; }
/* A link has no fill: not the grey press of .btn while clicked, and not the
   disabled well of .btn:disabled behind its text. */
.btn--link:active:not(:disabled), .btn--link:disabled { background: none; }

/* Destructive only (delete). NEVER licence or trial, which stay amber. */
.btn--danger { background: var(--fail); border-color: var(--fail); color: #fff; }
.btn--danger:hover:not(:disabled) { background: var(--fail-hover); border-color: var(--fail-hover); }
.btn--danger-quiet { color: var(--fail); }
.btn--danger-quiet:hover:not(:disabled) { background: var(--fail-tint); border-color: var(--fail-line); }

/* Pressed toggles and "the current one": a tint, never a second solid blue,
   so the one primary in the area stays the one thing that is solid. */
.btn.is-on, .btn[aria-pressed="true"], button.select.is-on {
  background: var(--accent-tint); border-color: var(--accent-line);
  color: var(--accent-dark); box-shadow: none;
}
.btn.is-on:hover:not(:disabled), .btn[aria-pressed="true"]:hover:not(:disabled),
button.select.is-on:hover { background: var(--accent-tint-2); border-color: var(--accent-line); }

/* An icon-only button: tab close, panel close, kebabs. Square, borderless
   until hovered, so a row of them does not look like a row of buttons. */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; border: 0; border-radius: var(--r-control);
  background: transparent; color: var(--muted);
  font: 400 16px/1 var(--sans); cursor: pointer;
}
.icon-btn:hover { background: var(--hover); color: var(--ink); }
.icon-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Inputs and selects share the button's height and radius. Focus is a ring,
   not a second outline inside the border: it reads at a glance and does not
   shift the text. */
.input, .select {
  min-height: var(--h-control); padding: 0 10px;
  border: 1px solid var(--line-control); border-radius: var(--r-control);
  background-color: var(--surface); color: var(--ink);
  font: 400 var(--fs-md)/1.3 var(--mono);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
textarea.input { padding: 7px 10px; line-height: 1.5; }
.input:hover:not(:disabled):not(:focus), .select:hover:not(:disabled):not(:focus) {
  border-color: var(--line-control-hover);
}
.input::placeholder { color: var(--faint); }
.input:focus, .select:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.input:disabled, .select:disabled {
  background-color: var(--surface-3); color: var(--disabled); cursor: not-allowed;
}
.select { font-family: var(--sans); font-size: var(--fs-md); color: var(--ink-2); }

/* One chevron for every native select, drawn in --muted. The OS chevron
   differed per platform and sat at a different inset in every pane. A data:
   URI, so it is not a request. A select that sets `background:` inline loses
   it: use background-color instead. */
select:not([multiple]):not([size]) {
  -webkit-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%235f7086' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center; background-size: 10px 6px;
}
select.select, select.input { padding-right: 28px; }
/* Buttons that borrow .select ("columns ▾", the set picker, Compare) are
   controls with a typed caret, so they get no chevron image — the caret is
   text and stays. */
button.select {
  display: inline-flex; align-items: center; gap: 6px; padding: 0 10px;
  text-align: left; cursor: pointer;
}
button.select:hover:not(:disabled) { background-color: var(--hover); }

/* A short numeric field, sized to hold its value.

   `type=number` reserves room for the spinner arrows ON TOP of the text, and
   that is easy to forget when picking a width: the two pH boxes in Setup were
   56px against a monospace "12.5" plus a spinner, so the value was clipped at
   the one place in the pane where reading it back matters — a target
   protonated at 12.5 showed "12." and looked like 12.

   Sized in `ch` plus a fixed allowance, so it follows the font rather than
   needing a new pixel count every time that changes: 4ch is the widest value
   these fields take ("14.0"), and the allowance covers padding, border and the
   spinner. Right-aligned because these are quantities. */
.input--num {
  width: calc(4ch + 42px);
  padding: 0 6px;
  text-align: right;
}

.field { display: flex; flex-direction: column; gap: 6px; }
.field > label { font-size: var(--fs-sm); font-weight: 500; color: var(--ink-2); }

/* Native checkboxes and radios in the accent, at the size of the custom
   .check, so the two kinds of tick on one screen look like one kind. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); width: 15px; height: 15px; }

/* Single native sliders: the Results thresholds, inside a toolbar chip with
   the figure they are set to beside them, and Search. Filter's dual slider
   copies these values in its own CSS. */
.rs-slider, input[type="range"].dvs-range {
  width: 110px; margin: 0 2px; height: 16px;
  accent-color: var(--accent); cursor: pointer;
}
.rs-slider:disabled { cursor: not-allowed; opacity: .5; }
.rs-slider:focus-visible, input[type="range"].dvs-range:focus-visible {
  outline: none; box-shadow: var(--focus-ring); border-radius: 999px;
}
.rs-slider__out {
  min-width: 44px; text-align: right;
  font: 400 var(--fs-xs) var(--mono); color: var(--ink-2);
}

/* ====================================================================== */
/*  Pills, badges, messages and callouts                                   */
/* ====================================================================== */

/* Status pills: the licence state in the top bar. */
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 10px;
  border-radius: var(--r-pill);
  font: 500 var(--fs-xs)/1 var(--mono);
}
.pill--trial { background: var(--amber-bg); border: 1px solid var(--amber-line); color: var(--amber-ink); }
.pill--licensed { background: var(--accent-tint); border: 1px solid var(--accent-line); color: var(--accent-dark); }
/* A pill that goes somewhere when clicked says so on hover. */
.pill.is-action { cursor: pointer; transition: border-color var(--dur) var(--ease); }
.pill.is-action:hover { border-color: currentColor; }
.pill.is-action:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* The small `licence` marker on a gated rail entry. */
.chip-licence {
  font: 500 var(--fs-2xs)/1 var(--mono);
  color: var(--amber-ink);
  background: var(--amber-bg);
  border: 1px solid var(--amber-line);
  border-radius: var(--r-xs);
  padding: 3px 6px;
}

/* The one small tag. Every pane had its own (.dk-tag, .tl-chip, .tl-mark,
   .load__badge, .nb__kind, .fr-legacy, .sr-q__frag, .pj-open-tag,
   .machine__here, account's .tag, .hero__badge), each a little different;
   they take this geometry and pick a tone. */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  height: 20px; padding: 0 6px;
  border: 1px solid var(--line); border-radius: var(--r-xs);
  background: var(--surface-3); color: var(--ink-3);
  font: 500 var(--fs-2xs)/1 var(--mono); letter-spacing: .02em; white-space: nowrap;
}
.badge--caps { text-transform: uppercase; letter-spacing: .06em; }
.badge--accent { background: var(--accent-tint); border-color: var(--accent-line); color: var(--accent-dark); }
.badge--pass { background: var(--pass-tint); border-color: var(--pass-line); color: var(--pass); }
.badge--fail { background: var(--fail-tint); border-color: var(--fail-line); color: var(--fail); }
/* Licence and trial ONLY. */
.badge--amber { background: var(--amber-bg); border-color: var(--amber-line); color: var(--amber-ink); }
/* In a module's colour: set --glyph on the node (or in a rule), e.g.
   "--glyph: var(--mod-results)". The plain values first are the fallback for
   a browser without color-mix(). */
.badge--mod {
  background: #f4f6f9;
  background: color-mix(in srgb, var(--glyph, var(--muted)) 9%, #fff);
  border-color: #dfe5ec;
  border-color: color-mix(in srgb, var(--glyph, var(--muted)) 28%, #fff);
  /* The module hue darkened towards ink: plain module colour on its own
     tint measured 3.7-4.3:1, under AA for 12px text. */
  color: var(--ink-3);
  color: color-mix(in srgb, var(--glyph, var(--ink-3)) 80%, var(--ink));
}

/* Inline status text beside a control: saved, failed, working. */
.msg { font-size: var(--fs-sm); line-height: 1.45; color: var(--ink-3); }
.msg--ok { color: var(--pass); }
.msg--err { color: var(--fail); }
.msg--busy { color: var(--accent-dark); }
.msg--warn { color: var(--amber-ink); }   /* licence/trial only */

/* A tinted box with a 3px rule on the left: a note that belongs to a section
   and must not be mistaken for its content. */
.callout {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 12px;
  border: 1px solid var(--line); border-left-width: 3px; border-radius: var(--r-control);
  background: var(--surface-2); color: var(--ink-2);
  font-size: var(--fs-sm); line-height: 1.5;
}
.callout--info { background: var(--accent-tint); border-color: var(--accent-line); border-left-color: var(--accent); }
.callout--ok { background: var(--pass-tint); border-color: var(--pass-line); border-left-color: var(--pass); }
.callout--err { background: var(--fail-tint); border-color: var(--fail-line); border-left-color: var(--fail); color: var(--fail); }
/* Licence, trial and "not available here" only. */
.callout--amber { background: var(--amber-bg); border-color: var(--amber-line); border-left-color: var(--amber-strong); color: var(--amber-ink); }

/* The one big figure of a section (docking's count, Filter's kept N). */
.stat-big { font: 500 36px/1 var(--mono); letter-spacing: -.02em; color: var(--accent-dark); }
/* "Loading…" in a list that has not arrived yet. */
.loading { padding: 24px 0; color: var(--muted); font-size: var(--fs-sm); }

/* ====================================================================== */
/*  Project shell — top bar, rail, tab strip, status strip                 */
/* ====================================================================== */

.shell {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
  background: var(--surface);
}

/* White, like the work under it: the grey chrome is the rail and the strips,
   and a grey bar on top made the whole screen read as a frame. */
.topbar {
  height: var(--h-topbar);
  flex: 0 0 var(--h-topbar);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 var(--gut);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
/* The wrapper keeps the topbar's own gap, so grouping logo and name into one
   link leaves the bar looking exactly as it did. */
.topbar__home {
  display: flex; align-items: center; gap: 10px;
  color: inherit; text-decoration: none;
}
.topbar__home:hover { color: inherit; text-decoration: none; }
.topbar__logo {
  width: 30px; height: 30px; flex: 0 0 30px;
  background: url("/static/logo.svg") center / contain no-repeat;
}
/* The same size as the workspace shell's brand: it is the same word beside
   the same mark, and reading smaller here made the workbench look like a
   different application. */
.topbar__name { font-size: var(--fs-lg); font-weight: 600; letter-spacing: -.01em; }
.topbar__sep { width: 1px; height: 20px; background: var(--line-strong); margin: 0 6px; }

/* The project chip: the project's name, and the way back to the list of
   projects. The set count moved to the set chip below.

   Project and set looked like two identical boxes, so neither read as what it
   is. The project is now a breadcrumb (borderless, it is where you are) and
   the set is the control (outlined, it is what you change). A tiny mono label
   in a pseudo-element names each: no DOM change, and textContent — which the
   tests and the menus read — is unchanged. */
.projchip {
  display: flex; align-items: center; gap: 6px;
  height: var(--h-control); padding: 0 8px;
  border: 1px solid transparent; border-radius: var(--r-control);
  background: transparent;
  cursor: pointer;
  font: inherit;
}
.projchip:hover { background: var(--hover); }
.projchip::before, .setchip::before {
  font: 500 10.5px/1 var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); margin-right: 2px;
}
.projchip::before { content: "Project"; }
.setchip::before { content: "Set"; }
.projchip__name { font: 500 var(--fs-md) var(--mono); color: var(--ink); }
.projchip__caret { color: var(--muted); font-size: 10px; }

/* The set chip, beside the project chip: which compound set the analysis
   modules show, and its count. The count lives here and nowhere else
   globally, as it did on the project chip; it is a badge now, so the divider
   that separated it went. */
.setchip {
  display: flex; align-items: center; gap: 8px; min-width: 0; max-width: 420px;
  height: var(--h-control); padding: 0 10px;
  border: 1px solid var(--line-control); border-radius: var(--r-control);
  background: var(--surface); box-shadow: var(--shadow-xs);
  cursor: pointer; font: inherit;
}
.setchip:hover { border-color: var(--line-control-hover); background: var(--surface); }
.setchip:focus-visible, .projchip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.setchip__name {
  font: 500 var(--fs-md) var(--sans); color: var(--ink); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.setchip__div { display: none; }
.setchip__count {
  font: 500 var(--fs-2xs)/1 var(--mono); color: var(--ink-3);
  background: var(--surface-3); border: 1px solid var(--line); border-radius: var(--r-xs);
  padding: 3px 6px; white-space: nowrap;
}

/* Its menu. Fixed to the viewport like the other shell menus, and scrolled
   inside itself: a project with forty sets must not push the actions off the
   bottom of the screen. */
.setmenu {
  position: fixed; z-index: 40; width: 370px; max-height: calc(100vh - 90px);
  overflow-y: auto; padding: 4px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
  display: flex; flex-direction: column; gap: 1px;
}
.setmenu__note.is-bad { color: var(--fail); }
/* One set: a tick column, the name, the count, and under them its lineage. */
.setmenu__set {
  display: grid; grid-template-columns: 18px minmax(0, 1fr) auto;
  column-gap: 8px; row-gap: 2px; align-items: baseline;
  width: 100%; text-align: left; border: 0; background: none;
  padding: 6px 10px; border-radius: var(--r-control); font: inherit; cursor: pointer;
}
button.setmenu__set:hover { background: var(--hover); }
.setmenu__set.is-active { background: var(--accent-tint); }
.setmenu__set.is-busy, .setmenu__set.is-archived { cursor: default; }
.setmenu__mark { color: var(--accent-dark); font-size: var(--fs-sm); }
.setmenu__name {
  font-size: var(--fs-md); color: var(--ink); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.setmenu__set.is-busy .setmenu__name { color: var(--ink-3); }
.setmenu__count { font: 400 var(--fs-xs) var(--mono); color: var(--muted); white-space: nowrap; }
.setmenu__line, .setmenu__acts { grid-column: 2 / -1; }
.setmenu__line { font-size: var(--fs-xs); color: var(--muted); }
.setmenu__acts { display: flex; gap: 6px; padding-top: 3px; }

/* ---- menus: one look for every dropdown ------------------------------ */

/* Every pane drew its own dropdown (.cmp__menu, .nb__menu, .rs-colmenu,
   .dvsr-menu, the shell's add-tab and account menus): different shadows,
   paddings and row heights for the same thing. They add the class `menu`
   and keep their rows as `rail__item`, restyled here through
   `.menu .rail__item` so no row has to be rebuilt. The set menu shares the
   row, head, rule and note looks. */
.menu {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
  box-shadow: var(--shadow-pop); padding: 4px;
  display: flex; flex-direction: column; gap: 1px;
  min-width: 180px; max-height: calc(100vh - 24px); overflow-y: auto; z-index: 60;
}
/* A head can wrap ("kept from <a long file name>"): a real line height, so
   its lines do not collide; the padding gives the one-line head its old 24px. */
.menu__head, .setmenu__head {
  padding: 6px 10px 2px;
  font: 500 var(--fs-2xs)/1.35 var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.menu__rule, .setmenu__rule { height: 1px; background: var(--line-2); margin: 4px 6px; }
.menu__note, .setmenu__note {
  padding: 4px 10px 6px;
  font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-3);
}
.menu__item, .menu .rail__item, .setmenu__item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  min-height: 32px; padding: 5px 10px;
  border: 0; border-radius: var(--r-control); background: none; box-shadow: none;
  font: 400 var(--fs-md)/1.3 var(--sans); color: var(--ink-2);
  text-align: left; text-decoration: none; cursor: pointer;
}
.menu__item:hover:not(:disabled), .menu .rail__item:hover:not(:disabled), .setmenu__item:hover:not(:disabled) {
  background: var(--hover); color: var(--ink); text-decoration: none;
}
.menu__item:disabled, .menu .rail__item:disabled, .setmenu__item:disabled {
  color: var(--disabled); background: none; cursor: not-allowed;
}
.menu__item.is-on, .menu .rail__item.is-on { color: var(--accent-dark); }
.menu__item.is-danger, .menu .rail__item.is-danger { color: var(--fail); }
.menu__item.is-danger:hover:not(:disabled), .menu .rail__item.is-danger:hover:not(:disabled) {
  background: var(--fail-tint); color: var(--fail);
}
/* A menu row is not a rail entry: no "active" bar. */
.menu .rail__item::before { content: none; }
.menu .rail__item .check, .menu__item .check { flex: 0 0 16px; }
/* A field inside a menu (a filter box, a threshold) sits on the rows' inset. */
.menu > .input, .menu > .select { margin: 2px 6px; }

/* "Save to: [set]" beside an add button. */
.set-pick { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-sm);
  color: var(--ink-3); min-width: 0; }
.set-pick__label { white-space: nowrap; }
.set-pick__select {
  height: var(--h-control-sm); min-height: 0; font-size: var(--fs-sm);
  padding: 0 26px 0 8px; max-width: 260px; min-width: 0;
}

/* The set dialogs: a name, and for a merge the sets in the order they win. */
.setdlg__err { color: var(--fail); }
.setdlg__tick { display: flex; align-items: center; gap: 8px; font-size: var(--fs-md); color: var(--ink-3); }
.setdlg__list { display: flex; flex-direction: column; gap: 2px; max-height: 260px;
  overflow-y: auto; border: 1px solid var(--line); border-radius: var(--r-card); padding: 4px; }
.setdlg__pick {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) auto 22px; gap: 8px;
  align-items: center; text-align: left; border: 0; background: none;
  padding: 6px 8px; border-radius: var(--r-control); font: inherit; cursor: pointer;
}
.setdlg__pick:hover { background: var(--hover); }
.setdlg__name { font-size: var(--fs-md); color: var(--ink); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.setdlg__meta { font: 400 var(--fs-xs) var(--mono); color: var(--muted); }
.setdlg__pos { font: 600 var(--fs-xs) var(--mono); color: var(--accent-dark); text-align: right; }

.topbar__right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
/* Outlined and neutral: a filled blue circle competed with the primary
   button for the eye, and the circle is only the way to the account. */
.avatar {
  width: 28px; height: 28px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface); color: var(--ink-3);
  border: 1px solid var(--line-control);
  font: 500 var(--fs-xs)/1 var(--mono);
  text-decoration: none;
}
.avatar:hover { text-decoration: none; background: var(--hover); color: var(--ink); }
/* No initials to show: a drawn person mark (the shell sets .is-anon), not a
   typed bullet that sat off-centre in the font's own metrics. */
.avatar.is-anon::before {
  content: ""; width: 16px; height: 16px; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='5.5' r='2.8' fill='none' stroke='black' stroke-width='1.5'/%3E%3Cpath d='M2.8 14c.6-2.6 2.7-4.2 5.2-4.2s4.6 1.6 5.2 4.2' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='5.5' r='2.8' fill='none' stroke='black' stroke-width='1.5'/%3E%3Cpath d='M2.8 14c.6-2.6 2.7-4.2 5.2-4.2s4.6 1.6 5.2 4.2' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

.body { flex: 1; display: flex; min-height: 0; }

/* ---- left rail: the launcher ---------------------------------------- */

.rail {
  width: var(--w-rail);
  flex: 0 0 var(--w-rail);
  border-right: 1px solid var(--line);
  background: var(--surface-1);
  display: flex;
  flex-direction: column;
  padding: 10px 8px;
}
.rail__groups { display: flex; flex-direction: column; gap: 14px; flex: 1; }
.rail__group { display: flex; flex-direction: column; gap: 1px; }
.rail__group > .eyebrow { padding: 0 10px 6px; }

.rail__item {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  min-height: 34px; padding: 0 10px;
  border-radius: var(--r-control);
  font-size: var(--fs-md);
  color: var(--ink-2);
  text-decoration: none;
  border: 0; background: none; width: 100%;
  font-family: var(--sans);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease);
}
.rail__item:hover { background: var(--hover); color: var(--ink); text-decoration: none; }
/* The module in the active tab: a tinted row and a 3px bar at the rail's
   edge. The old 2px inset line alone was easy to lose in a column of
   coloured glyphs. */
.rail__item.is-active {
  background: var(--accent-tint);
  color: var(--accent-dark);
  font-weight: 600;
  box-shadow: none;
}
.rail__item.is-active::before {
  content: ""; position: absolute; left: -8px; top: 7px; bottom: 7px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--accent);
}
.rail__item.is-active:hover { background: var(--accent-tint-2); color: var(--accent-dark); }
/* Locked still opens the licence explanation, so it keeps the pointer. */
.rail__item.is-locked, .rail__item.is-locked:hover { color: var(--muted); background: none; cursor: pointer; }
.rail__item:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* The module glyph: a small picture on the module's colour (RAIL_ICONS in
   shell.js), or a two-letter monogram for a module without one. The pictures
   were chosen one by one for reading at this size -- a wrong icon is worse
   than a letter -- and the colour still does most of the identifying. */
.rail__glyph {
  flex: 0 0 22px; width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-control);
  font: 600 var(--fs-2xs) var(--mono);
  color: #fff; background: var(--glyph, var(--muted));
  letter-spacing: 0;
}
.rail__item.is-locked .rail__glyph { background: var(--disabled); }
/* A picture needs more room than two letters to read: a size up from the
   monogram tile, and still well inside the collapsed rail. */
.rail__glyph--icon {
  flex-basis: 24px; width: 24px; height: 24px; border-radius: var(--r-control);
}
.rail__glyph--icon svg {
  width: 15px; height: 15px; display: block;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* State dot: filled accent = this module has content, hollow = untouched. */
.dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 6px; background: var(--accent); }
.dot--empty { background: var(--surface); border: 1.5px solid #a9b8c8; }
/* In a rail row the dot is a badge on the glyph's top-right corner, open or
   collapsed. Beside the glyph it took part in the row's layout: it pushed
   every label a step right, and in the collapsed rail it pushed the column
   of glyphs off the rail's axis. The ring is the row's own background, so
   the badge reads as cut out of the glyph. */
.rail__item > .dot {
  position: absolute; left: 29px; top: 5px;
  width: 8px; height: 8px;
  box-shadow: 0 0 0 2px var(--surface-1);
}
.rail__item:hover > .dot { box-shadow: 0 0 0 2px var(--hover); }
.rail__item.is-active > .dot { box-shadow: 0 0 0 2px var(--accent-tint); }

.rail__value { margin-left: auto; font: 400 var(--fs-xs) var(--mono); color: var(--muted); }
.rail__value--pass { color: var(--pass); }
.rail__value--accent { color: var(--accent-dark); }
.rail__value--ink { color: inherit; }

.rail__foot {
  border-top: 1px solid var(--line-2);
  padding: 10px 2px 2px;
  display: flex; flex-direction: column; gap: 8px;
}
/* The way out of a project. It used to be a plain sentence in the footer, easy
   to miss when the workbench is the only thing on screen — so it is a control
   you can see and aim at: drawn as a secondary button. */
.rail__foot a {
  display: flex; align-items: center; gap: 8px;
  min-height: var(--h-control); padding: 0 10px;
  border: 1px solid var(--line-control); border-radius: var(--r-control);
  background: var(--surface); box-shadow: var(--shadow-xs);
  font-size: var(--fs-md); color: var(--ink-2); text-decoration: none;
}
.rail__foot a:hover {
  background: var(--hover); border-color: var(--line-control-hover);
  color: var(--ink); text-decoration: none;
}
.rail__foot-mark { color: var(--muted); font-family: var(--sans); }
.rail__legend { font: 400 var(--fs-2xs)/1.7 var(--mono); color: var(--muted); padding: 0 4px; }
.rail__legend .dot { display: inline-block; vertical-align: 0; margin-right: 4px; }
/* The legend's "open in a tab" mark is the active row's bar, drawn small. */
.rail__legend .bar {
  display: inline-block; width: 3px; height: 10px; background: var(--accent);
  border-radius: 2px; vertical-align: -1px; margin-right: 6px;
}

/* ---- the rail, collapsed -------------------------------------------- */

/* Collapsing hands the width to the work and keeps the launcher: the glyphs
   stay, and they are the part you aim at once you know the app. It is a view
   of the same rail rather than a different rail — every item is still there,
   in the same order, with its colour and its state dot, so what you learned
   about where things are still holds when it is narrow.

   The width is animated because the panes beside it are laid out in fractions
   of what is left: a jump reads as the page breaking, a slide reads as the
   rail moving. */
.rail {
  transition: width .18s var(--ease),
              flex-basis .18s var(--ease);
}
.rail.is-min {
  width: var(--w-rail-min);
  flex: 0 0 var(--w-rail-min);
  padding-left: 6px; padding-right: 6px;
}
/* The names, the counts and the group headings are what the width was for. */
.rail.is-min .rail__label,
.rail.is-min .rail__value,
.rail.is-min .chip-licence,
.rail.is-min .rail__group > .eyebrow,
.rail.is-min .rail__legend { display: none; }
.rail.is-min .rail__item {
  justify-content: center;
  padding: 6px 0;
  gap: 0;
}
.rail.is-min .rail__item.is-active::before { left: -6px; }
.rail.is-min .rail__item > .dot { left: auto; right: 9px; top: 3px; }
.rail.is-min .rail__group { gap: 3px; }
.rail.is-min .rail__foot { padding: 10px 0 2px; }
.rail.is-min .rail__foot a { justify-content: center; padding: 0; }

/* The toggle, at the top of the rail: it governs the whole rail, so it sits
   above it rather than below the last module in it. Icon only — a square the
   size of a glyph, so the collapsed rail is a single column of equal marks
   with this one at its head. */
.rail__head {
  display: flex; justify-content: flex-end;
  padding: 0 1px 8px;
}
.rail__toggle {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: 1px solid transparent; border-radius: var(--r-control);
  background: none; color: var(--muted);
  cursor: pointer;
}
.rail__toggle svg {
  width: 16px; height: 16px;
  stroke: currentColor; stroke-width: 1.6; stroke-linecap: round;
}
.rail__toggle:hover { background: var(--hover); border-color: transparent; color: var(--ink); }
.rail.is-min .rail__head { justify-content: center; padding: 0 0 8px; }

/* ---- main column: tabs + content ------------------------------------ */

.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }

/* The strip scrolls sideways instead of squeezing: with eight modules open
   the labels were crushed to nothing and "+" fell off the end. The bar is
   hidden (a wheel or a trackpad scrolls it, and the shell keeps the active
   tab in view); "+" sticks to the right edge over a fade. */
.tabs {
  height: var(--h-tabs);
  flex: 0 0 var(--h-tabs);
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--line);
  background: var(--surface-1);
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; overscroll-behavior-x: contain;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  position: relative;
  /* Never shrinks: a tab squeezed to its minimum kept the marker, the count
     and the close button and lost the label ("C…", "R…" with twelve open).
     The strip scrolls instead, and only a name longer than 240px is cut. */
  flex: 0 0 auto; min-width: 104px; max-width: 240px;
  display: flex; align-items: center; gap: 8px;
  padding: 0 8px 0 12px;
  border: 0; border-right: 1px solid var(--line);
  background: none;
  text-decoration: none;
  font: 500 var(--fs-md) var(--sans);
  color: var(--ink-3);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
/* The module's colour as a small square: the same identity as its rail
   glyph, so a tab and its rail entry are visibly one thing. The shell marks
   each tab with data-tab (not data-pane, which the panes' own CSS roots on). */
.tab::before {
  content: ""; flex: 0 0 8px; width: 8px; height: 8px; border-radius: 2px;
  background: var(--tab-mod, var(--muted));
}
.tab:hover { background: var(--hover); color: var(--ink-2); text-decoration: none; }
.tab.is-active {
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}
.tab:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
.tab__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tab__count { font: 400 var(--fs-xs) var(--mono); color: var(--muted); font-weight: 400; }
.tab > .dot { flex: 0 0 6px; }
.tab__close {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 20px; width: 20px; height: 20px; margin-left: auto;
  border-radius: var(--r-xs); color: var(--muted);
  font-size: 16px; line-height: 1;
}
.tab:not(.is-active):not(:hover) .tab__close { opacity: .55; }
.tab__close:hover { background: var(--press); color: var(--ink); }
.tabs__add {
  position: sticky; right: 0; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  width: 48px; padding: 0 0 0 10px;
  border: 0; cursor: pointer;
  color: var(--muted); font-size: 20px; line-height: 1;
  background: linear-gradient(90deg, rgba(247, 248, 250, 0), var(--surface-1) 12px);
}
.tabs__add:hover { color: var(--accent-dark); }
.tab[data-tab="compounds"] { --tab-mod: var(--mod-compounds); }
.tab[data-tab="fragments"] { --tab-mod: var(--mod-fragments); }
.tab[data-tab="load"]      { --tab-mod: var(--mod-load); }
.tab[data-tab="notebook"]  { --tab-mod: var(--mod-notebook); }
.tab[data-tab="ligands"]   { --tab-mod: var(--mod-ligands); }
.tab[data-tab="sketcher"]  { --tab-mod: var(--mod-sketcher); }
.tab[data-tab="search"]    { --tab-mod: var(--mod-search); }
.tab[data-tab="filter"]    { --tab-mod: var(--mod-filter); }
.tab[data-tab="offtarget"] { --tab-mod: var(--mod-offtarget); }
.tab[data-tab="docking"]   { --tab-mod: var(--mod-setup); }
.tab[data-tab="results"]   { --tab-mod: var(--mod-results); }

.main__panes { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.pane { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.pane[hidden] { display: none; }
/* The workbench's own fallback when a pane's script fails to load. */
.pane-failed { margin: 40px auto; max-width: 560px; }

/* Toolbar above a table. White, the height of a panel head (--h-bar), so a
   toolbar and the side panel beside it share one bottom edge. */
.toolbar {
  display: flex; align-items: center; gap: 8px;
  min-height: var(--h-bar);
  padding: 8px var(--gut);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  flex: 0 0 auto;
}
/* Opt-in: a toolbar with more controls than a narrow window holds. */
.toolbar--wrap { flex-wrap: wrap; row-gap: 8px; }
.toolbar__spacer { margin-left: auto; }
/* A control in a toolbar keeps its label on one line. Squeezed, a button
   like "columns ▾" broke into two lines and the row grew uneven; it is the
   search box and the source picker that should give up width instead. */
.toolbar > button, .toolbar > .btn, .toolbar > .select, .toolbar > span { white-space: nowrap; }
.toolbar > button, .toolbar > .btn { flex-shrink: 0; }
/* A hairline between groups of controls, on the first of the next group.
   On a button or span only: an input or select has no ::before. */
.tb-sep { position: relative; margin-left: 9px; }
.tb-sep::before {
  content: ""; position: absolute; left: -9px; top: 6px; bottom: 6px; width: 1px;
  background: var(--line-strong);
}

/* ---- segmented control ----------------------------------------------- */

/* Mutually exclusive modes (Notebook's views, Off-target's mode, Search's
   mode, Results' view and target switches) were rows of separate buttons,
   one of them solid blue — they read as actions, not as a choice. One well
   with the current option raised. The children may be plain buttons, .btn,
   .btn--primary (the current one, where a test pins that class) or .is-on;
   every case draws the same. The dark twin over a 3D view is .vp-seg. */
.seg {
  display: inline-flex; align-items: stretch; gap: 2px; padding: 2px;
  background: var(--surface-3); border: 1px solid var(--line); border-radius: var(--r-control);
}
.seg > button, .seg > .btn, .seg > .btn:hover:not(:disabled) {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 26px; padding: 0 10px;
  border: 0; border-radius: var(--r-xs); background: transparent; box-shadow: none;
  color: var(--ink-3); font: 500 var(--fs-sm)/1 var(--sans);
  cursor: pointer; white-space: nowrap;
}
.seg > button:hover:not(:disabled):not(.is-on):not(.btn--primary) {
  color: var(--ink); background: rgba(255, 255, 255, .65);
}
.seg > button.is-on, .seg > [aria-pressed="true"],
.seg > .btn.btn--primary, .seg > .btn.btn--primary:hover:not(:disabled) {
  background: var(--surface); color: var(--accent-dark);
  box-shadow: 0 0 0 1px var(--line), 0 1px 2px rgba(15, 26, 42, .10);
}
.seg > button:disabled { color: var(--disabled); cursor: not-allowed; }
.seg > button:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ---- status strip ---------------------------------------------------- */

/* Light chrome now, like the rail and the tab strip: a black band along the
   bottom pulled the eye off the work for a line that is usually "no job
   running". A running job still stands out — an accent dot that breathes. */
.status {
  height: var(--h-status);
  flex: 0 0 var(--h-status);
  background: var(--surface-1);
  border-top: 1px solid var(--line);
  display: flex; align-items: center; gap: 16px;
  padding: 0 var(--gut);
  font: 400 var(--fs-xs) var(--mono);
  color: var(--muted);
}
.status__job { display: inline-flex; align-items: center; gap: 8px; color: var(--accent-dark); }
.status__job::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
}
.status__job.is-running::before { animation: dvs-pulse 1.4s ease-in-out infinite; }
.status__job.is-idle { color: var(--muted); }
.status__job.is-idle::before { background: var(--line-control); }

/* ====================================================================== */
/*  Tables                                                                 */
/* ====================================================================== */

.table { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.table__scroll { flex: 1; overflow-y: auto; overflow-x: hidden; }

.tr {
  display: grid;
  align-items: center;
  height: var(--h-row);
  border-bottom: 1px solid var(--line-3);
  padding: 0 8px;
  font-size: var(--fs-md);
  transition: background-color .08s linear;
}
/* Grid cells may shrink below their content, so an ellipsis can happen
   instead of a long id running into the next column. */
.tr > * { min-width: 0; }
.tr--head {
  height: var(--h-head);
  border-bottom: 1px solid var(--line);
  background: var(--surface-3);
  font: 500 var(--fs-2xs) var(--mono);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  position: sticky;
  top: 0;
  z-index: 1;
}
.tr--head .num { font-weight: 500; }
/* A header row carrying long words in narrow numeric columns. With tracking
   "CONFIDENCE" is wider than the column it heads, so the headers met each
   other with no gap; with ordinary tracking they fit, and the row still
   reads as a header. */
.tr--head-tight { font-size: var(--fs-2xs); letter-spacing: .03em; }
/* Results headers that sort on click, marked the way the off-target grid
   marks its own: colour and a rule under the active one, no caret. */
.tr--head .rs-sortable { cursor: pointer; -webkit-user-select: none; user-select: none; }
.tr--head .rs-sortable:hover { color: var(--ink); }
.tr--head .rs-sortable.is-sorted { color: var(--accent-dark); box-shadow: inset 0 -2px 0 var(--accent); }
.tr:hover:not(.tr--head) { background: var(--hover-row); }
/* Three row states that must be told apart at a glance: ticked (a pale
   tint and a light bar), open in the side panel (the tint and a solid bar),
   and hover (neutral). Ticked used to be a near-white nobody could see. */
.tr.is-selected { background: var(--selected-row); box-shadow: inset 3px 0 0 var(--accent-line); }
.tr.is-active, .tr.is-active:hover { background: var(--accent-tint); box-shadow: inset 3px 0 0 var(--accent); }
/* A failing row is dimmed, never hidden — you have to be able to see what the
   filter is about to throw away. Its cells dim, not the row: the verdict and
   the reason are what you came to read, so they stay at full strength. */
.tr.is-failed > * { opacity: .5; }
.tr.is-failed > .verdict, .tr.is-failed > .reason { opacity: 1; }
/* Separate rule: a browser without :has() drops only this one. */
.tr.is-failed > :has(> .verdict), .tr.is-failed > :has(> .reason) { opacity: 1; }

.tr .id {
  font-family: var(--mono); font-weight: 500; color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding-right: 12px;
}
.tr .smiles {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding-right: 12px;
}
.tr .num { padding-left: 8px; }

/* Checkbox cell. The tick is drawn, not typed: a "✓" glyph sat at a
   different height in every font and blurred at 13px. */
.check {
  position: relative; display: inline-block;
  width: 16px; height: 16px; flex: 0 0 16px;
  border-radius: var(--r-xs); border: 1px solid var(--line-control);
  background: var(--surface);
  vertical-align: -3px; cursor: pointer;
  transition: border-color var(--dur), background-color var(--dur);
}
.check:hover { border-color: var(--accent); }
.check:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.check.is-on {
  background: var(--accent); border-color: var(--accent);
  color: #fff; font-size: 0; line-height: 0;
}
.check.is-on::after {
  content: ""; position: absolute; left: 4.5px; top: 1.5px; width: 4px; height: 8px;
  border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg);
}
/* The same box as a radio: one of several. */
.check--radio { border-radius: 50%; }
.check--radio.is-on { background: var(--surface); border-color: var(--accent); }
.check--radio.is-on::after {
  left: 3px; top: 3px; width: 8px; height: 8px; border: 0;
  border-radius: 50%; background: var(--accent); transform: none;
}

/* 2D depiction thumbnail in a table cell.

   Big enough to read. At 36 x 26 px a structure was a smudge that identified
   nothing, and a table of compounds is a table of structures first. The image
   is requested at twice this size (DVSPager.THUMB_PX) so it stays sharp on a
   high-density screen. A row holding one grows to fit it; every other row
   keeps the compact height. A clean white plate: the striped placeholder
   read as a texture behind every molecule. */
.thumb {
  width: 168px; height: 120px; border-radius: var(--r-control);
  border: 1px solid var(--line-2);
  background: var(--surface);
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  transition: border-color var(--dur);
}
.thumb svg, .thumb img { width: 100%; height: 100%; object-fit: contain; }
.tr:hover .thumb { border-color: var(--line); }
/* On the tinted open row the white plate would be a hole: let the tint
   through, and multiply the drawing's white background away. */
.tr.is-active .thumb { background: transparent; border-color: var(--accent-line); }
.tr.is-active .thumb img { mix-blend-mode: multiply; }
.tr:has(.thumb) { height: auto; min-height: 132px; padding-top: 6px; padding-bottom: 6px; }
.tr--head:has(.thumb) { min-height: 0; }

.table__more {
  display: flex; align-items: center; justify-content: center;
  height: var(--h-row);
  font: 400 var(--fs-sm) var(--mono);
  color: var(--muted);
}

/* pass / fail markers in the filter preview: a small tinted pill, so the
   verdict column scans as a column of states. */
.verdict {
  display: inline-flex; align-items: center; height: 22px; padding: 0 8px;
  border-radius: var(--r-pill);
  font: 500 var(--fs-xs)/1 var(--mono);
}
.verdict--pass { color: var(--pass); background: var(--pass-tint); }
.verdict--fail { color: var(--fail); background: var(--fail-tint); }
.reason { font: 400 var(--fs-sm) var(--mono); color: var(--fail); }

/* ====================================================================== */
/*  Side panels                                                            */
/* ====================================================================== */

.panel {
  border-left: 1px solid var(--line);
  background: var(--surface-4);
  display: flex; flex-direction: column;
  min-height: 0;
}
/* A left panel holds settings for the work beside it: chrome, not work. */
.panel--left { border-left: 0; border-right: 1px solid var(--line); background: var(--surface-1); }
.panel__head {
  min-height: var(--h-bar);
  padding: 8px var(--gut);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 8px;
  flex: 0 0 auto;
  background: var(--surface);
}
/* What a panel head says, in three weights: a title in prose, a compound's
   id in mono, and a quiet line of meta. Each ellipsises rather than wraps,
   so the head keeps its height. */
.panel__title {
  font: 600 var(--fs-base)/1.3 var(--sans); color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.panel__id {
  font: 500 var(--fs-base)/1.3 var(--mono); color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.panel__meta { font: 400 var(--fs-xs) var(--mono); color: var(--muted); white-space: nowrap; }
.panel__body { padding: 14px var(--gut); display: flex; flex-direction: column; gap: 10px; overflow-y: auto; }
.panel__section {
  padding: 14px var(--gut); display: flex; flex-direction: column; gap: 10px;
  border-bottom: 1px solid var(--line-2);
}
.panel__foot {
  margin-top: auto; padding: 12px var(--gut);
  border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 8px;
  flex: 0 0 auto; background: var(--surface);
}

/* key/value line — the workhorse of every property panel. A long value
   wraps under itself, right-aligned, instead of pushing its key out. */
.kv {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  font-size: var(--fs-md); line-height: 1.45;
}
.kv > span:first-child { color: var(--muted); flex: 0 1 auto; max-width: 60%; overflow-wrap: anywhere; }
.kv > span:last-child {
  font-family: var(--mono); color: var(--ink);
  text-align: right; min-width: 0; overflow-wrap: anywhere;
}
/* A list of kv lines separated by rules, for a long property table. */
.kv--rule { padding: 6px 0; border-bottom: 1px solid var(--line-3); }
.kv--rule:last-child { border-bottom: 0; }
/* A value that is words, not data. */
.kv > span.kv__prose { font-family: var(--sans); }

/* One structure plate for every side panel (beside .cmp__depict,
   .dvsf-depict, .ot-depict, .sr-depict, .tl-depict, .nb__depict), so a
   compound looks the same wherever it is opened. */
.plate {
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-card);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}

/* One compound's SMILES in a side panel, with a copy button. The same block
   in every pane that opens a single compound — Compounds, Results, Target
   ligands, Library search, Filter, Off-target, Notebook — each building it
   with its own smilesBlock(), so the look lives here and cannot drift.

   The string wraps anywhere rather than being cut: a SMILES has no spaces to
   break at, and an ellipsis hides exactly the part someone came to copy.
   Selection is switched on explicitly, not inherited: a panel can sit inside
   something that turns it off, and a SMILES that cannot be selected has to be
   retyped by hand, which is how structures get changed by accident. */
.smi-block {
  display: flex; flex-direction: column; gap: 5px;
  background: var(--surface-3); border: 1px solid var(--line-2);
  border-radius: var(--r-control); padding: 6px 8px 8px 10px;
}
.smi-block__head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.smi-block__label {
  font: 500 var(--fs-2xs)/1 var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.smi-block__note {
  font: 400 var(--fs-2xs) var(--mono); color: var(--muted);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.smi-block__copy {
  margin-left: auto; flex: 0 0 auto;
  background: var(--surface); color: var(--ink-3);
  border: 1px solid var(--line-strong); border-radius: 4px;
  padding: 1px 8px; font: 500 12.5px/18px var(--mono); cursor: pointer;
}
.smi-block__copy:hover { border-color: var(--accent); color: var(--accent-dark); }
.smi-block__copy.is-done { border-color: var(--pass); color: var(--pass); }
.smi-block__text {
  font: 400 14px/1.55 var(--mono); color: var(--ink-2);
  white-space: normal; overflow-wrap: anywhere;
  -webkit-user-select: text; user-select: text; cursor: text;
}

/* ====================================================================== */
/*  Charts — CSS only. Never a decorative illustration.                    */
/* ====================================================================== */

/* A progress bar fills the rest of a row. Its basis is its own size, not 0:
   with `flex: 1` (basis 0) a bar stacked in a column — a modal body, an empty
   card, a panel section, all flex columns now — collapsed to no height. It
   never shrinks, so a scrolling column cannot squeeze it away either. */
.bar {
  height: 6px; background: var(--line-2);
  border-radius: var(--r-pill); overflow: hidden; flex: 1 0 auto;
}
.bar__fill { height: 100%; background: var(--accent); border-radius: inherit; }
.bar--lg { height: 8px; }

.meter { display: flex; align-items: center; gap: 10px; font-size: var(--fs-md); }
/* The label grows to its word (up to a cap) instead of a fixed 52px that
   clipped longer names. */
.meter__label {
  width: auto; min-width: 52px; max-width: 120px;
  font-family: var(--mono); color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.meter__value { font-family: var(--mono); width: 36px; text-align: right; }

/* Histogram above a range slider. Bars are plain divs sized by percentage. */
.hist { display: flex; align-items: flex-end; gap: 1px; height: 34px; }
.hist__bar { flex: 1; background: var(--accent-line); border-radius: 1px 1px 0 0; min-height: 1px; }
.hist__bar.is-out { background: var(--line); }

/* Dual-handle range row */
.range { display: flex; flex-direction: column; gap: 6px; }
.range__head { display: flex; align-items: baseline; gap: 8px; font-size: var(--fs-md); min-width: 0; }
.range__head .name {
  color: var(--ink-2); font-weight: 500;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.range__head .vals {
  margin-left: auto; font: 400 var(--fs-sm) var(--mono); color: var(--ink-3); white-space: nowrap;
}
.range__inputs { display: flex; align-items: center; gap: 6px; }
.range__inputs input {
  width: 100%; height: var(--h-control-sm);
  border: 1px solid var(--line-control); border-radius: var(--r-control);
  padding: 0 6px; font: 400 var(--fs-sm) var(--mono); color: var(--ink);
}
.range__inputs input:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }

/* Element exclusion grid — an excluded element is red, not greyed. */
.elements { display: flex; flex-wrap: wrap; gap: 4px; }
.element {
  height: var(--h-control-sm); padding: 0 8px;
  border: 1px solid var(--line-control); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink-2);
  font: 400 var(--fs-sm) var(--mono); cursor: pointer;
}
.element:hover { border-color: var(--line-control-hover); }
.element.is-excluded { background: var(--fail-tint); border-color: var(--fail-line); color: var(--fail); }

/* Moiety picker cells: a depiction over a tick and a label. Sized so a
   fragment sheet reads as a sheet — several columns at a glance — rather than
   as a list you scroll one row at a time. A chosen cell gets a full accent
   ring, not just a border colour, so it reads in a sheet of thirty. */
.moi {
  display: flex; flex-direction: column; gap: 4px; padding: 6px;
  border: 1px solid var(--line); border-radius: var(--r-card); cursor: pointer;
  background: var(--surface);
  transition: border-color var(--dur), box-shadow var(--dur);
}
.moi:hover { border-color: var(--line-control-hover); box-shadow: var(--shadow-xs); }
.moi.is-on { border-color: var(--accent); background: var(--accent-tint); box-shadow: 0 0 0 1px var(--accent) inset; }
.moi img { display: block; }
.moi__foot { display: flex; align-items: center; gap: 5px; min-width: 0; }
.moi__lab {
  font-size: var(--fs-xs); line-height: 1.25; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.moi__n { margin-left: auto; font: 400 var(--fs-2xs) var(--mono); color: var(--muted); }

/* Alert checkbox row with its count */
.alert-row { display: flex; align-items: center; gap: 8px; font-size: var(--fs-md); cursor: pointer; }
.alert-row__count { margin-left: auto; font: 400 var(--fs-xs) var(--mono); color: var(--muted); }

/* ====================================================================== */
/*  Dark viewports (3D receptor and pose views)                            */
/* ====================================================================== */

/* Dark on purpose: a molecule's colours read on near-black, and the view is
   the one place the eye should rest on. Its controls float over it as glass
   groups — translucent, so the structure behind stays visible. */
.viewport {
  flex: 1; min-height: 0; position: relative;
  background: var(--dark);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.viewport__hint {
  position: absolute; left: 12px; bottom: 10px; max-width: calc(100% - 120px);
  font: 400 var(--fs-xs) var(--mono); color: var(--dark-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  pointer-events: none;
}
.viewport__label {
  position: absolute; left: 12px; top: 10px;
  font: 500 var(--fs-xs) var(--mono); color: var(--dark-accent);
}
/* A group of tools: one glass bar, the tools inside it borderless. */
.viewport__tools, .vp-seg {
  display: inline-flex; gap: 2px; padding: 3px;
  background: var(--glass-bg); border: 1px solid var(--glass-line); border-radius: var(--r-card);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  box-shadow: var(--shadow-glass);
}
.viewport__tools { position: absolute; right: 10px; top: 10px; }
/* A tool on its own is a small glass button; in a group it drops its own
   glass, so the group reads as one object. */
.viewport__tool {
  height: 26px; padding: 0 9px;
  display: inline-flex; align-items: center;
  background: var(--glass-bg); border: 1px solid var(--glass-line); border-radius: var(--r-control);
  color: var(--dark-text);
  font: 500 var(--fs-xs) var(--mono); cursor: pointer;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  transition: color var(--dur), background-color var(--dur), border-color var(--dur);
}
.vp-seg .viewport__tool, .viewport__tools .viewport__tool {
  background: transparent; border-color: transparent;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.viewport__tool:hover:not(:disabled) { color: #fff; background: rgba(255, 255, 255, .07); }
.viewport__tool.is-on { color: #fff; background: var(--glass-on); border-color: var(--glass-on-line); }
.viewport__tool:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--dark-accent); }
/* The Results viewer's Display menu: what the 3D view draws, each on or off.
   Capped and scrolled inside itself, so on a short window it cannot run off
   the bottom of the view it sits in. */
.vmenu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 5;
  min-width: 240px; max-height: 248px; overflow-y: auto; padding: 4px;
  background: rgba(13, 23, 38, .94); border: 1px solid var(--glass-line);
  border-radius: var(--r-card);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  box-shadow: var(--shadow-glass);
  display: flex; flex-direction: column; gap: 1px;
}
.vmenu__h {
  font: 500 var(--fs-2xs) var(--mono); letter-spacing: .06em; text-transform: uppercase;
  color: var(--dark-text); padding: 6px 8px 3px;
}
.vmenu__line { display: flex; align-items: center; gap: 6px; }
.vmenu__line > .vmenu__row { flex: 1; }
.vmenu__row {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: 0; border-radius: var(--r-xs); padding: 4px 8px;
  color: var(--dark-text); font: 400 var(--fs-xs) var(--mono); text-align: left; cursor: pointer;
}
.vmenu__row:hover { background: var(--glass-on); }
.vmenu__row.is-on { color: #fff; }
/* An option the pose on screen cannot use; its title says why. */
.vmenu__row.is-disabled { opacity: .5; cursor: not-allowed; }
.vmenu__row.is-disabled:hover { background: none; }
.vmenu__row .check { background: transparent; border-color: #4a6480; }
.vmenu__row .check.is-on { background: var(--accent); border-color: var(--accent); }

/* ====================================================================== */
/*  Modals                                                                 */
/* ====================================================================== */

.modal-host { position: fixed; inset: 0; z-index: 50; display: none; }
.modal-host.is-open { display: block; }
.modal__backdrop {
  position: absolute; inset: 0;
  background: rgba(13, 23, 38, .36);
  display: flex; align-items: center; justify-content: center;
}
.modal {
  width: 540px; max-width: calc(100vw - 48px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-window);
  box-shadow: var(--shadow-modal);
  overflow: hidden;
}
.modal--wide { width: 640px; }
/* A licence or trial modal: an amber rule across the top says which kind of
   news this is before a word is read — and it is amber, never red. */
.modal--amber { border-top: 3px solid var(--amber-strong); }
.modal__body { padding: 24px 24px 20px; display: flex; flex-direction: column; gap: 14px; }
/* Never taller than the window: past it, the title went off the top and the
   buttons off the bottom. The body scrolls; the foot and its actions stay. */
.modal { max-height: calc(100vh - 32px); display: flex; flex-direction: column; }
.modal__body { overflow-y: auto; min-height: 0; flex: 1 1 auto; }
.modal__foot { flex: 0 0 auto; }
/* The eyebrow, title and lead text, as one group with its own rhythm. */
.modal__head { display: flex; flex-direction: column; gap: 6px; }
.modal__eyebrow { color: var(--muted); }
.modal__eyebrow--amber { color: var(--amber-ink); }
.modal__eyebrow--fail { color: var(--fail); }
.modal__title { margin: 0; font-size: var(--fs-xl); font-weight: 600; letter-spacing: -.01em; line-height: 1.25; }
.modal__text { margin: 0; font-size: var(--fs-base); line-height: 1.6; color: var(--ink-3); }
.modal__note { margin: 0; font-size: var(--fs-sm); line-height: 1.55; color: var(--muted); }
/* A note that reports a failure ("still missing" after Recheck). */
.modal__note.is-bad { color: var(--fail); }
.modal__list { margin: 0; padding-left: 18px; font-size: var(--fs-sm); line-height: 1.55; color: var(--ink-3); }
.modal__list > li + li { margin-top: 4px; }
.modal__foot {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 24px;
  border-top: 1px solid var(--line);
  background: var(--surface-1);
}
.modal__foot .right { margin-left: auto; display: flex; gap: 8px; }
/* In a modal's foot the large buttons step down a size: the foot is a row of
   choices, not a landing page. */
.modal__foot .btn--lg { min-height: 36px; padding: 0 16px; font-size: var(--fs-md); }
/* Cancel is a quiet text action, so the one real choice stands alone. */
.modal__cancel {
  display: inline-flex; align-items: center; min-height: 36px; padding: 0 12px;
  border-radius: var(--r-control); color: var(--ink-3);
  font: 500 var(--fs-md) var(--sans); text-decoration: none;
}
.modal__cancel:hover { background: var(--hover); color: var(--ink); text-decoration: none; }

/* Option list inside a modal (what you can do now vs what needs a licence). */
.options {
  display: flex; flex-direction: column; gap: 8px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 12px 14px; background: var(--surface-2);
}
.options__row { display: flex; align-items: center; gap: 10px; font-size: var(--fs-md); }
.options__row > span:first-child { flex: 1; color: var(--ink-2); }
.options__state { font: 500 var(--fs-xs) var(--mono); }
/* Included now / needs a licence or trial (amber) / not here / neutral. */
.options__state--ok { color: var(--pass); }
.options__state--warn { color: var(--amber-ink); }
.options__state--bad { color: var(--fail); }
.options__state--muted { color: var(--muted); }
.options__rule { height: 1px; background: var(--line-2); }

/* A shell blurred behind a modal. */
.is-blurred { filter: blur(1.2px); opacity: .45; pointer-events: none; }

/* Copyable command line, for "here is the exact fix". */
.command {
  display: flex; align-items: center; gap: 10px;
  background: var(--dark-1); border-radius: var(--r-control);
  padding: 8px 12px;
  font: 400 var(--fs-sm) var(--mono); color: var(--dark-ink);
  overflow-x: auto;
}
.command button {
  margin-left: auto; background: none; border: 1px solid var(--dark-line);
  color: var(--dark-text); border-radius: var(--r-xs); height: 24px; padding: 0 8px;
  font: 400 var(--fs-xs) var(--mono); cursor: pointer; flex: 0 0 auto;
}
.command button:hover { color: #fff; border-color: var(--dark-text); }

/* ====================================================================== */
/*  Workspace shell (home, projects, account) — looser than the workbench   */
/* ====================================================================== */

.wshell { display: flex; height: 100vh; overflow: hidden; background: var(--surface); }
/* The same width as the workbench rail, so the two shells line up when you
   move between them; the brand row is the top bar's height, so the nav and
   the page header share one line. */
.wnav {
  width: var(--w-rail); flex: 0 0 var(--w-rail);
  border-right: 1px solid var(--line);
  background: var(--surface-1);
  display: flex; flex-direction: column;
  padding: 0 8px 12px;
  gap: 14px;
}
/* The brand is a link back to Home from every workspace screen. It keeps the
   surrounding text colour rather than the link colour: it reads as the app's
   name, and only behaves as a link when you reach for it. */
.wnav__brand {
  display: flex; align-items: center; gap: 10px;
  height: var(--h-topbar); flex: 0 0 var(--h-topbar);
  margin: 0 -8px; padding: 0 16px;
  border-bottom: 1px solid var(--line);
  color: inherit; text-decoration: none;
}
.wnav__brand:hover { color: inherit; text-decoration: none; }
/* One size for the mark on all three workspace pages. Each page used to set
   this itself — two of them in a width attribute on the tag — so the logo grew
   on whichever page someone remembered. */
.wnav__brand img { width: 30px; height: 30px; display: block; }
.wnav__word { font-size: var(--fs-lg); font-weight: 600; letter-spacing: -.01em; }
.wnav__items, .wnav__group { display: flex; flex-direction: column; gap: 1px; }
.wnav__group > .eyebrow { padding: 0 10px 6px; }
/* Drawn like a rail entry, active state included, so moving from the
   workspace into a project does not change the language of the nav. */
.wnav__item {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  min-height: 34px; padding: 0 10px; border-radius: var(--r-control);
  font-size: var(--fs-md); color: var(--ink-2); text-decoration: none;
}
.wnav__item:hover { background: var(--hover); text-decoration: none; color: var(--ink); }
.wnav__item.is-active { background: var(--accent-tint); color: var(--accent-dark); font-weight: 600; }
.wnav__item.is-active::before {
  content: ""; position: absolute; left: -8px; top: 7px; bottom: 7px; width: 3px;
  border-radius: 0 3px 3px 0; background: var(--accent);
}
.wnav__item.is-static, .wnav__item.is-static:hover { color: var(--disabled); background: none; cursor: default; }
.wnav__foot {
  margin-top: auto; padding: 10px 10px 0;
  border-top: 1px solid var(--line-2);
  display: flex; align-items: center; gap: 6px;
  font: 400 var(--fs-2xs) var(--mono); color: var(--muted);
}
.wnav__foot .dot { width: 6px; height: 6px; flex: 0 0 6px; background: var(--pass); }

/* The page's own header row, level with the nav's brand row. */
.wtop {
  flex: 0 0 var(--h-topbar); height: var(--h-topbar);
  display: flex; align-items: center; gap: 12px;
  padding: 0 40px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  position: sticky; top: 0; z-index: 2;
}
.wtop__title { margin: 0; font-size: var(--fs-lg); font-weight: 600; letter-spacing: -.01em; }
.wtop__right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.wtop__right a { font-size: var(--fs-sm); }

/* A column, so the page's .wtop header is a flex item like the nav's brand. */
.wmain { flex: 1; overflow-y: auto; min-width: 0; display: flex; flex-direction: column; }
.wmain__inner { max-width: 1120px; padding: 28px 40px 56px; display: flex; flex-direction: column; gap: 24px; }

.card {
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--surface);
}
.card__pad { padding: 16px 20px; }

.h1 { margin: 0; font-size: var(--fs-2xl); font-weight: 600; letter-spacing: -.015em; line-height: 1.2; }
.h2 { margin: 0; font-size: var(--fs-xl); font-weight: 600; letter-spacing: -.01em; line-height: 1.25; }
.lede { margin: 0; font-size: var(--fs-base); line-height: 1.6; color: var(--ink-3); }

/* Empty state — explains rather than showing dead tiles. A solid well now:
   the dashed border read as "drop something here" where nothing can be
   dropped. The optional tile carries the module's colour (--glyph), so an
   empty pane still says which module it is. */
.empty {
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 28px;
  display: flex; flex-direction: column; gap: 12px;
  background: var(--surface-2);
}
.empty--center { margin: 48px auto 0; width: calc(100% - 48px); max-width: 640px; }
.empty__icon {
  width: 40px; height: 40px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  background: #eef2f7;
  background: color-mix(in srgb, var(--glyph, var(--accent)) 12%, #fff);
}
.empty__icon::before {
  content: ""; width: 16px; height: 16px; border-radius: 4px;
  background: var(--glyph, var(--accent));
}
/* Used with .eyebrow, which gives it its look; this only drops a <p>'s margin. */
.empty__eyebrow { margin: 0; }
.empty__title { margin: 0; font: 600 var(--fs-lg)/1.3 var(--sans); color: var(--ink); }
.empty__text { margin: 0; font-size: var(--fs-md); line-height: 1.6; color: var(--ink-3); max-width: 62ch; }
.empty__actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }

/* One stacking rule for the workspace pages on a narrow window; each page
   used to carry its own copy at its own breakpoint. */
@media (max-width: 860px) {
  .wshell { flex-direction: column; height: auto; min-height: 100vh; overflow: visible; }
  .wnav { width: auto; flex: 0 0 auto; border-right: 0; border-bottom: 1px solid var(--line); }
  .wnav__items { flex-direction: row; flex-wrap: wrap; }
  .wtop { padding: 0 20px; }
  .wmain__inner { padding: 20px; }
  /* Stacked, the window scrolls, not .wmain: a scroll box that never
     scrolls would hold the header to nothing. */
  .wmain { overflow: visible; }
  .wtop { position: static; }
}

/* ====================================================================== */
/*  Public pages (marketing, sign in) — the only wide-measure typography    */
/* ====================================================================== */

.public { background: var(--canvas); min-height: 100%; }
.wrap { max-width: 1060px; margin: 0 auto; padding: 0 32px; }
.public h1 { font-size: var(--fs-display); font-weight: 600; letter-spacing: -.8px; line-height: 1.15; margin: 0; }
.public h2 { font-size: var(--fs-3xl); font-weight: 600; letter-spacing: -.3px; margin: 0; }
.public p { line-height: 1.65; color: var(--ink-3); }

.split { display: flex; min-height: 100vh; }
.split__dark {
  width: 46%; flex: 0 0 46%;
  background: linear-gradient(160deg, #0d1726 0%, #16243a 60%, #12314c 100%);
  color: #dbe7f2;
  padding: 48px 46px;
  display: flex; flex-direction: column;
}
.split__light { flex: 1; display: flex; align-items: center; justify-content: center; padding: 48px; background: var(--surface); }

@media (max-width: 900px) {
  .split { flex-direction: column; }
  .split__dark { width: 100%; flex: 0 0 auto; }
}

/* Screen-reader-only text, for icon-only controls. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ====================================================================== */
/*  Explanations, on demand                                                */
/*                                                                         */
/*  A section's description is worth reading once. Left on the screen it is */
/*  read never — the eye learns the shape of the paragraph and skips it —   */
/*  and it costs the same vertical space on the thousandth run as on the    */
/*  first. So the prose lives behind an ⓘ and each section keeps only what  */
/*  changes: counts, states, results, errors.                              */
/* ====================================================================== */

.sec-head { display: flex; align-items: center; gap: 6px; }

.info-i {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: 0 0 16px;
  border-radius: 50%; cursor: help; user-select: none;
  border: 1px solid var(--line-control);
  color: var(--muted); background: var(--surface);
  font: 600 11px/1 var(--sans); font-style: italic;
}
/* A 16px target is small to aim at; the hit area is larger than the mark. */
.info-i::after { content: ""; position: absolute; inset: -5px; }
.info-i:hover, .info-i:focus-visible {
  border-color: var(--accent); color: var(--accent-dark);
  background: var(--accent-tint); outline: none; box-shadow: var(--focus-ring);
}

/* One popover for the whole app, in two modes: a paragraph for an ⓘ, a
   figure for a 2D structure. One element, so two can never be open at once. */
.pop {
  position: fixed; z-index: 400; display: none;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
  pointer-events: none;
}
.pop--text {
  max-width: 280px; padding: 9px 11px;
  font-size: var(--fs-sm); line-height: 1.55; color: var(--ink-2);
}
.pop--fig { padding: 6px; }
.pop--fig svg { display: block; width: 180px; height: 145px; }
.pop__cap {
  text-align: center; font: 400 var(--fs-2xs) var(--mono);
  color: var(--muted); padding: 3px 0 1px;
}

/* ====================================================================== */
/*  Pager — a page bar under a table                                       */
/*  Shared: every table of compounds or fragments pages, and a second copy */
/*  of this would drift from the first. The bar itself is built by         */
/*  assets/dvs-pager.js.                                                   */
/* ====================================================================== */

.pager {
  flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center;
  gap: 12px; row-gap: 8px; min-height: 44px; padding: 6px var(--gut);
  border-top: 1px solid var(--line); background: var(--surface-4);
}
.pager__at { font: 400 var(--fs-xs) var(--mono); color: var(--ink-3); white-space: nowrap; }
.pager__at b { font-weight: 500; color: var(--ink); }
.pager__nav { margin-left: auto; display: flex; align-items: center; gap: 4px; }
.pager__btn {
  border: 1px solid var(--line-control); background: var(--surface);
  color: var(--ink-3); border-radius: var(--r-control);
  min-width: 28px; height: var(--h-control-sm);
  cursor: pointer; font: 500 var(--fs-sm) var(--mono); line-height: 1;
}
.pager__btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-dark); }
.pager__btn:disabled { color: var(--disabled); background: var(--surface-3); border-color: var(--line); cursor: default; }
.pager__page { font: 400 var(--fs-xs) var(--mono); color: var(--muted); padding: 0 8px; white-space: nowrap; }
/* The page-size picker: its own height, not the control height, and room on
   the right for the chevron so "100 / page" is not cut. */
.pager__size { height: var(--h-control-sm); min-height: 0; font-size: var(--fs-xs); padding: 0 22px 0 8px; line-height: 26px; }

/* ====================================================================== */
/*  Radar                                                                  */
/*  dvs-radar.js builds these classes and cannot inject CSS of its own, so */
/*  its look lives here. Its menu takes the shared .menu look.             */
/* ====================================================================== */

.dvsr-menu { min-width: 200px; }
.dvsr-cog {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px;
  border: 1px solid var(--line-control); border-radius: var(--r-control);
  background: var(--surface); color: var(--muted); cursor: pointer;
}
.dvsr-cog:hover { color: var(--ink); border-color: var(--line-control-hover); }
.dvsr-cap { font: 400 var(--fs-xs) var(--mono); color: var(--muted); text-align: center; }
/* A rule the compound breaks: the caption says so in red. */
.dvsr-cap.is-fail { color: var(--fail); }
/* A rule row holds three stacked lines: its name, its limits and its source.
   The name is the row's label; the chosen rule's is a weight heavier. */
.dvsr-menu .menu__item { flex-direction: column; align-items: flex-start; gap: 2px; }
.dvsr-menu .menu__item > :first-child { font-weight: 500; }
.dvsr-menu .menu__item.is-on > :first-child { font-weight: 600; }

/* ====================================================================== */
/*  Utilities                                                              */
/*  Last in the file on purpose: a class that says "small" or "ink-3" is  */
/*  added to a component to change it, so it must win over the component's */
/*  own rule at equal specificity (`lede t-sm`, `kv t-sm`, `lede t-md`).   */
/*  Pane CSS is injected after this file and still wins over them.        */
/* ====================================================================== */

/* So a pane can say "small" with a class instead of repeating an inline
   font-size that drifts from the scale. */
.t-2xs { font-size: var(--fs-2xs); }
.t-xs { font-size: var(--fs-xs); }
.t-sm { font-size: var(--fs-sm); }
.t-md { font-size: var(--fs-md); }
.t-base { font-size: var(--fs-base); }
.t-lg { font-size: var(--fs-lg); }
.t-ink3 { color: var(--ink-3); }
.t-muted { color: var(--muted); }
.t-ellipsis { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* .quiet stays early (it sits on badges and buttons whose own colour must
   win: Filter's `quiet badge badge--accent`), so the two components that
   take it as "muted" say so here. */
.lede.quiet, .panel__id.quiet { color: var(--muted); }
