:root {
  --bg: #f3f4f6;
  --surface: #ffffff;
  /* The light-grey canvas every diagram sits on: the BPMN modeler, the live and
     replay views, the Operations process view and the DMN editor. One lever for
     all diagram backgrounds. The dot grid (--diagram-dot) is a modeling
     affordance overlaid in the editor only. Changing this does not affect the
     bpmn.io watermark, which stays fully visible per its licence (see ~L748). */
  --diagram-bg: #f3f4f6;
  --diagram-dot: #d7dae0;
  --border: #e2e4e9;
  --border-strong: #cdd0d6;
  --text: #14161a;
  --muted: #62666d;
  --accent: #0b5cff;
  --accent-hover: #0a4ad1;
  --accent-soft: #eaf0ff;
  /* The ink laid on an accent-coloured fill: the label of a primary button, a
     count badge, an active segment. It was `#fff` written out at each of those
     rules, which is correct for the stock blue and a guess for anybody else's
     brand colour — a pale accent leaves white text at a contrast ratio no
     accessibility audit passes, and nothing in the CSS could express that.
     As a token the theme can flip it: theme.js derives white or near-black from
     the chosen accent's luminance (derivePalette), so a light brand colour gets
     dark ink instead of an unreadable button. Its name starts with `--accent` on
     purpose — the no-flash bootstrap in index.html applies exactly that prefix,
     so the derived ink reaches first paint with the rest of the palette. */
  --accent-ink: #ffffff;
  --danger: #c0392b;
  --danger-soft: #fdecea;
  --warn: #b7791f;
  --warn-soft: #fef6e7;
  /* The amber the landscape's status marks use, and deliberately not --warn.
     The two are solving different problems: --warn is text and thin borders on
     pale backgrounds, where luminance contrast is what makes it readable, and it
     is tuned dark for that. A mark on a canvas is not read, it is *noticed* — and
     what draws the eye there is saturation, which is why the muted ochre above
     disappears as a ring on a tinted circle while this does not. Kept out of the
     dense views for the same reason it is used here.
     Deepened a step with the two greys below, so the mark keeps its lead over the
     structure it is drawn against: 3.19 to 3.59 against the canvas. */
  --mesh-attention: #cc6f05;
  /* The landscape's neutral ink, and deliberately not --border-strong. Same
     argument as the amber above, in the other direction: --border-strong is a
     hairline between two panels a hand's width from the eye, where anything darker
     would draw attention a border has no business drawing. The same value on a
     canvas is a node that is *there* and cannot be seen — and the nodes it happens
     to fall on are the unremarkable ones, so a landscape drawn in it shows its
     findings sharply and its structure barely at all. These two carry the outlines
     and the edges instead.

     Both were a step too quiet, and it was measurable rather than a matter of taste.
     WCAG 2.1 asks 3:1 of a graphical object against what it is drawn on, and the
     canvas is white: the edges were #adb2ba at 2.13, under the floor, on the
     thinnest mark in the picture; the process outline was #8b9099 at 3.21, the bare
     minimum, on the most numerous node there is. So each moved one step down —
     the edges take the value the outlines had, and the outlines take a darker one —
     which leaves the edges exactly at the floor, where the thinnest thing on the
     canvas belongs, and the outlines with room above it.

     They stay grey, and the status marks stay ahead of them: those carry hue and a
     filled badge, and this is a hairline. Quiet is still the intent; it was simply
     quieter than legible. */
  --mesh-ink: #6e747d;
  --mesh-line: #8b9099;
  --ok: #2d8a4e;
  /* The interface's two type stacks. They are named here so a surface that is not
     app.css can ask for the same ones: form-theme.css hands --font-sans to the
     vendored form-js runtime, which otherwise asks for an 'IBM Plex Sans' nothing
     ships and lands on the browser's generic sans — a form in a different typeface
     from the console around it. The rest of this file still writes the stack out
     inside its `font:` shorthands; pointing those at the token is the next step,
     and until it happens these two are a definition point, not yet a lever an
     operator can pull. */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --topbar-h: 48px;
  --radius: 8px;
  --shadow: 0 1px 2px rgba(20, 22, 26, 0.06), 0 1px 3px rgba(20, 22, 26, 0.08);
  color-scheme: light;
}

/* Canvas fills, mixed once here rather than at each shape.

   These sit apart from the palette above because `fill` fails differently from
   every other property in this file. A `background` that cannot be resolved
   computes to `transparent` and nobody notices. `fill` is inherited and its initial
   value is *black* — and a paint that cannot be resolved does not fall back to the
   declaration before it, it becomes `unset`, inherits, and reaches that black. So a
   class box whose tint fails to compute is not a slightly-off box: it is a solid
   black one, with its label painted black on top of it. The one failure this file
   can produce that destroys a diagram outright is the one it had no fallback for.

   Registering each tint closes that path. A mix a browser will not compute — an
   unsupported `color-mix()`, or an `--accent-soft` that reached the root malformed
   from the theme cache the bootstrap in index.html applies unvalidated — is invalid
   at computed-value time, and a *registered* property answers that with its initial
   value instead of with the guaranteed-invalid one. Each literal below is its own
   mix evaluated against the default palette, so the floor under a themed instance
   is the stock colour, not a placeholder: the tint is lost, the drawing is not. */
@property --canvas-fill { syntax: "<color>"; inherits: true; initial-value: #f3f4f6; }
@property --surface-fill { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
@property --class-fill { syntax: "<color>"; inherits: true; initial-value: #eef2fb; }
@property --enum-fill { syntax: "<color>"; inherits: true; initial-value: #eee9e2; }
@property --uml-store-fill { syntax: "<color>"; inherits: true; initial-value: #e2e3e6; }
@property --im-store-fill { syntax: "<color>"; inherits: true; initial-value: #ede6dd; }
@property --object-fill { syntax: "<color>"; inherits: true; initial-value: #eef2fb; }
@property --sim-scope-fill { syntax: "<color>"; inherits: true; initial-value: rgba(11, 92, 255, .08); }

:root {
  --canvas-fill: var(--bg);
  --surface-fill: var(--surface);
  --class-fill: color-mix(in srgb, var(--accent-soft) 60%, var(--bg));
  --enum-fill: color-mix(in srgb, #b26a00 8%, var(--bg));
  --uml-store-fill: color-mix(in srgb, var(--muted) 12%, var(--bg));
  --im-store-fill: color-mix(in srgb, #b26a00 10%, var(--bg));
  --object-fill: color-mix(in srgb, var(--accent-soft) 55%, var(--bg));
  --sim-scope-fill: color-mix(in srgb, var(--accent) 8%, transparent);
}

* { box-sizing: border-box; }
/* An id/class `display` rule outranks the UA [hidden] rule; restore it explicitly. */
[hidden] { display: none !important; }
html, body { height: 100%; }
body {
  margin: 0;
  font: 14px/1.5 var(--font-sans);
  color: var(--text);
  background: var(--bg);
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
b { font-weight: 600; }

/* ---------- Top bar ---------- */
.topbar {
  position: sticky; top: 0; z-index: 20;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: 6px;
  padding: 0 10px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.brand { display: inline-flex; align-items: center; gap: 8px; color: var(--text); }
.brand b { font-size: 15px; }
.topbrand:hover { text-decoration: none; }
.mark {
  display: inline-grid; place-items: center;
  width: 24px; height: 24px; border-radius: 6px;
  background: #000; color: #fff;
  overflow: hidden;
}
/* The built-in mark is an inline SVG glyph taking the box's colour; the tile is
   this box's own background, so the glyph carries its padding in its viewBox. */
.mark svg { width: 100%; height: 100%; display: block; }
/* When an org logo is set, the mark holds the uploaded image instead of the glyph:
   drop the black tile so a transparent PNG/SVG sits on the bar cleanly, and fit
   the image inside the box without distortion. */
.mark.has-logo { background: transparent; }
.mark .mark-img { width: 100%; height: 100%; object-fit: contain; display: block; }
.divider { width: 1px; height: 24px; background: var(--border); margin: 0 6px; }
/* The view names never wrap and never push the icons off the bar. Two-word names
   broke onto two lines and the account button was cut off at the right edge as soon as
   the names outgrew the window — at every width, not only on a phone. So the bar takes
   what is left beside the icons and scrolls sideways within it when that is not
   enough, which is what it already did below 860px. */
.topnav {
  display: flex; align-items: center; gap: 2px; height: 100%;
  flex: 0 1 auto; min-width: 0; overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.topnav::-webkit-scrollbar { display: none; }
.topnav > a, .topnav-group-btn {
  display: inline-flex; align-items: center; height: 100%; flex: 0 0 auto;
  padding: 0 12px; color: var(--muted); font-weight: 500; white-space: nowrap;
  border-bottom: 2px solid transparent;
}
.topnav > a:hover, .topnav-group-btn:hover { color: var(--text); text-decoration: none; }
.topnav > a.active, .topnav-group-btn.active { color: var(--text); border-bottom-color: var(--accent); }
/* A group of pages behind one entry (Console › Administration). Its menu is the shared
   dropdown, position:fixed, so the bar's sideways scroll cannot clip it. */
.topnav-group { display: flex; height: 100%; flex: 0 0 auto; }
.topnav-group-btn { gap: 4px; background: none; border-top: 0; border-left: 0; border-right: 0; font: inherit; font-weight: 500; cursor: pointer; }
.topnav-caret { font-size: 10px; }
.topnav-group .dropdown-menu a.active { font-weight: 600; background: var(--bg); }
/* Everything else on the bar keeps its size; the organisation's name gives way first,
   and goes altogether where the names need its room. */
.topbar > :not(.topnav):not(.spacer):not(.org) { flex: 0 0 auto; }
.topbar .org { flex: 0 1 auto; min-width: 0; }
@media (max-width: 1100px) {
  .topbar .org { display: none; }
  .topnav > a, .topnav-group-btn { padding: 0 8px; }
}
/* On a phone the app's name gives its room to the view names. Logo, name and six
   buttons were wider than the screen: the view names had no width left at all, and in
   Operations, the longer name, the account button was pushed off the right edge. The
   mark beside it and the app drawer, which marks the open app, still say where one is. */
@media (max-width: 520px) {
  .topbar #app-name { display: none; }
}
/* Count on a nav entry — today the parked tokens on Operations › Incidents
   (ADR-0151). Red and always legible, including on the muted inactive entry: an
   operator should see it without first landing on the view it belongs to. */
.topnav .nav-badge {
  margin-left: 6px; min-width: 18px; padding: 0 6px; border-radius: 999px;
  background: var(--danger); color: #fff;
  font: 700 11px/18px system-ui, sans-serif; text-align: center;
}
.spacer { flex: 1; }
.org { color: var(--muted); font-size: 13px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.icon-btn {
  display: inline-grid; place-items: center;
  width: 32px; height: 32px; border-radius: 6px;
  border: none; background: transparent; color: var(--muted); cursor: pointer;
}
.icon-btn:hover { background: var(--bg); color: var(--text); }
.icon-btn.grid { color: var(--text); }
.avatar {
  background: #6b2b2b; color: #fff; font-size: 11px; font-weight: 700;
  border-radius: 50%; width: 28px; height: 28px;
}
.avatar:hover { background: #6b2b2b; }

/* ---------- App-switcher drawer ---------- */
#scrim { position: fixed; inset: 0; background: rgba(0,0,0,0.25); z-index: 30; }
#drawer {
  position: fixed; top: 0; left: 0; bottom: 0; width: 260px; z-index: 31;
  background: var(--surface); border-right: 1px solid var(--border);
  box-shadow: 2px 0 12px rgba(0,0,0,0.12);
  display: flex; flex-direction: column;
}
.drawer-head { display: flex; align-items: center; gap: 10px; height: var(--topbar-h); padding: 0 10px; border-bottom: 1px solid var(--border); }
#drawer-apps { padding: 8px; display: flex; flex-direction: column; gap: 2px; }
#drawer-apps a {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 6px; color: var(--text); font-weight: 500;
}
#drawer-apps a:hover { background: var(--bg); text-decoration: none; }
#drawer-apps a.active { background: var(--accent-soft); color: var(--accent); }
#drawer-apps a .soon { margin-left: auto; font-size: 11px; color: var(--muted); font-weight: 500; }
/* The two shop surfaces are pages of their own and open in their own window, so
   the drawer says so before the click rather than after it: an entry that silently
   opens a second window is the same surprise whichever way it is resolved. It is a
   ::after on the attribute that already carries the fact, so nothing has to keep a
   second flag in step and the glyph stays out of the link's accessible name. */
#drawer-apps a[target="_blank"]::after {
  content: "\2197"; margin-left: auto; font-size: 12px; color: var(--muted); line-height: 1;
}

/* ---------- Content ---------- */
main { padding: 22px; max-width: 1120px; margin: 0 auto; }
.editor-mode main, main.editor { max-width: none; padding: 0; margin: 0; }

.card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 22px;
}
.card + .card { margin-top: 18px; }
/* A data table lives in a card, and a card is a fixed-width box. A cell that cannot
   wrap — an action column, a monospace identifier — makes the table's *min-content*
   width larger than the card, and a table cannot be drawn narrower than that: it is
   laid out past the card's right edge, so the header rule stops, the border stops, and
   the buttons hang in the page beside it. Scroll it inside the card instead.
   Deliberately a rule about every card that holds a table, not about the one table
   that happened to overflow — the next column added to any of them would do this
   again (ADR-0163). */
.card:has(> table) { overflow-x: auto; }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 760px) { .grid2 { grid-template-columns: 1fr; } }

/* The construction kit (ADR-0395): one row per service, one radio per answer. The
   cells carry the console's own inline field, with the field's stacking margin taken
   back out — inside a table the row is the spacing.

   A cell holds the control and nothing else. The word belongs to the column heading,
   which says it once; repeating it in every cell cost the table 180px of width, which
   is the difference between a kit that fits in the column beside the product list and
   one that scrolls sideways in it. The radio is centred under its heading so a row
   reads as three positions rather than three left edges, and the name a cell no longer
   shows it keeps for a reader who cannot see the column — the radio's own label names
   the product and the answer together. */
.assemble td .field.inline { margin: 0; justify-content: center; }
.assemble td:not(:first-child) { text-align: center; }
.assemble th { white-space: nowrap; }

/* The product form is read in two passes — what the catalogue shows, then how an
   order is handled — so it is laid out in two columns with section headings that
   span both. A field carrying an explanation takes `wide` and keeps the full
   width: prose in a half column is a column of syllables.

   The breakpoint is .grid2's, deliberately the same number rather than a second
   one chosen for this form: two form layouts that reflow at different widths is a
   difference a reader notices and nobody meant.

   No colour is spelled out here. The rule and the hint take --border and --muted,
   so this form follows a theme change like everything else on the page. */
.product-form { display: grid; grid-template-columns: 1fr 1fr; gap: 0 18px; }
.product-form > .wide,
.product-form > .form-sec,
.product-form > .form-sec-hint,
.product-form > .row { grid-column: 1 / -1; }
.product-form > .form-sec {
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted);
}
.product-form > .form-sec:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.product-form > .form-sec-hint { margin: 4px 0 4px; font-size: 12px; color: var(--muted); }
/* Each section is a <details> a maintainer can fold away (catalog-admin.js remembers
   which). The summary wears the section heading's look, with a marker that turns, and
   the body keeps the form's own two-column grid so a field's .wide still spans it. */
.product-form > .form-group { grid-column: 1 / -1; border-top: 1px solid var(--border); padding: 10px 0 4px; }
.product-form > .form-group:first-child { border-top: 0; padding-top: 0; }
/* What publishing needs, at the top of the form and kept current while it is filled
   in (catalog-admin.js publishNeeds). Each entry is a button to its field. */
.product-form > .publish-needs {
  grid-column: 1 / -1; margin: 0 0 12px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface);
}
.product-form > .publish-needs + .form-group { border-top: 0; padding-top: 0; }
.publish-needs-head { margin: 0 0 6px; font-weight: 600; }
.publish-needs ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 16px; }
.publish-needs .publish-need { border-color: transparent; padding: 2px 4px; font-weight: 400; text-align: left; }
.publish-needs li.ok .publish-need { color: var(--ok); }
.publish-needs li.missing .publish-need { color: var(--danger); text-decoration: underline; }
.publish-needs-note { margin: 6px 0 0; font-size: 12px; color: var(--muted); }
.product-form > .form-group > summary.form-sec {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px;
  font-size: 13px; letter-spacing: 0.02em; text-transform: uppercase; color: var(--muted);
  margin: 0; padding: 4px 0; user-select: none;
}
.product-form > .form-group > summary.form-sec::-webkit-details-marker { display: none; }
.product-form > .form-group > summary.form-sec::before {
  content: "\25B8"; display: inline-block; transition: transform 0.12s ease;
}
.product-form > .form-group[open] > summary.form-sec::before { transform: rotate(90deg); }
.product-form > .form-group > summary.form-sec:hover { color: var(--text); }
.product-form > .form-group > .form-sec-hint { margin: 0 0 6px; font-size: 12px; color: var(--muted); }
.form-group-body { display: grid; grid-template-columns: 1fr 1fr; gap: 0 18px; }
.form-group-body > .wide,
.form-group-body > .form-sec-hint,
.form-group-body > .row { grid-column: 1 / -1; }
/* The product's picture in its editor: bounded here rather than by the file, because
   what was uploaded is what is served. contain and not cover — a crop would cut the
   top off somebody's product. */
.product-picture {
  display: block; max-width: 220px; max-height: 140px; width: auto;
  margin: 0 0 8px; border: 1px solid var(--border); border-radius: 8px;
  object-fit: contain; background: var(--surface);
}
@media (max-width: 760px) { .product-form, .form-group-body { grid-template-columns: 1fr; } }

/* ---------- One box per language, side by side ----------
   Every text a product carries is a map keyed by the catalogue's declared language
   tags, so the form draws the list rather than a fixed pair: --langs is set inline
   from that list's length, and a catalogue that adds a fifth language grows a fifth
   column without anybody editing this rule.

   minmax(0, 1fr) and not 1fr: a grid track's default minimum is its content, so a
   long untranslated name in one box would push the others out of the card instead
   of wrapping inside its own.

   Below the breakpoint they stack, because four boxes across a phone are four boxes
   nobody can type in. */
.langrow { display: grid; grid-template-columns: repeat(var(--langs, 1), minmax(0, 1fr)); gap: 10px; }
.langbox { display: block; min-width: 0; }
.langbox > .langtag {
  display: block; margin: 0 0 3px;
  font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted);
}
.langbox input, .langbox textarea { width: 100%; box-sizing: border-box; }
@media (max-width: 760px) { .langrow { grid-template-columns: 1fr; } }

/* ---------- The shapes a product is ordered in ----------
   One row per shape: a narrow column for the id that never changes, then one equal
   column per language. --langs is set inline from the catalogue's own list, like
   the language row above, so a fifth language grows a fifth column.

   The header row is labels rather than a <table>: the grid is a list of inputs and
   a table would bring cells that have to be styled back into looking like this.

   Below the breakpoint the columns collapse to one, which stacks id and names —
   readable, and the only shape that fits a phone. */
.vargrid { display: grid; gap: 6px; }
.varrow {
  display: grid;
  grid-template-columns: minmax(0, 0.6fr) repeat(var(--langs, 1), minmax(0, 1fr));
  gap: 6px;
}
.varrow input { width: 100%; box-sizing: border-box; }
.varhead {
  font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted);
}
.vargrid + .btn { margin-top: 6px; }
@media (max-width: 760px) { .varrow { grid-template-columns: 1fr; } }
/* The product's actions (ADR-0429): key, message, effect, who may ask, then one label
   per declared language. The same grid idiom as the shapes, one column wider. */
.actgrid { display: grid; gap: 6px; }
.actrow {
  display: grid;
  grid-template-columns: minmax(0, 0.7fr) minmax(0, 1fr) minmax(0, 0.5fr) minmax(0, 1fr) repeat(var(--langs, 1), minmax(0, 0.8fr));
  gap: 6px; align-items: center;
}
.actrow input, .actrow select { width: 100%; box-sizing: border-box; }
.actrow input[readonly] { background: var(--bg); color: var(--muted); }
.acthead {
  font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted);
}
.acttrigs { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12px; }
.acttrig { display: inline-flex; align-items: center; gap: 3px; white-space: nowrap; }
.acteffect { font-size: 12px; color: var(--muted); }
.actgrid + .btn { margin-top: 6px; }
@media (max-width: 760px) { .actrow { grid-template-columns: 1fr; } }

/* ---------- The product list and the editor beside it ---------- */

/* The catalogue's own page is a working surface and not a page to read, so it drops
   the centred content column the way the Tasks inbox does (app.js sets the class on
   the route). Two columns inside 1120px would give a table of products and a form
   about 520px each, and both hold more than that. What is *prose* on this page keeps
   its own measure — the cards and the explanations below the headings state a
   max-width of their own, so nothing here turns a paragraph into a line across a
   2000px screen. */
.catalog-mode main { max-width: none; }

/* Where a product's form opens.

   It first rendered under the whole list, which is fine with three products and
   unusable with forty: opening one near the bottom pushed the form below everything,
   read after a long scroll and with no sight of the row it belonged to. Then it took
   a column beside the list, level with its row — which on a wide screen was a third
   of the page, scrolling inside itself, while the width was on the list.

   So an existing product's form and kit open in a row of their own directly under
   the product (tr.product-edit-row), across the table's width, and a new product's
   under the list's buttons (.product-panels), where the two containers live. Both are
   in the flow of the page: nothing is measured, nothing sticks, and the row the
   reader clicked does not move when the form opens under it.

   The card keeps a measure of its own, so a form on a 2000px screen does not run two
   fields a metre apart. */
.product-editor > .card,
.assemble-editor > .card { margin: 14px 0; max-width: 1280px; }
/* A table cannot be drawn narrower than its min-content width, so on a narrow screen
   it scrolls inside its own box, for the reason a card holding a table does
   (ADR-0163). */
.product-table { overflow-x: auto; }
/* Stacked, a side form keeps the measure every other card on this page keeps, and the
   air that separates it from the list above. The column below takes both away. */
.cat-side > .card { margin-top: 18px; max-width: 640px; }
/* Which row the open panel belongs to: the panel is directly under it, and the same
   tint joins the two. */
.product-list tr.editing > td { background: var(--accent-soft); }
.product-list tr.product-edit-row > td { padding: 0 8px 14px; background: var(--accent-soft); white-space: normal; }
.product-list tr.product-edit-row .card { margin: 0; }
@media (min-width: 1400px) {
  /* A list beside the form that acts on it. The catalogue page has three of these —
     the catalogues and the one being created, the relations and the pair being
     related, who maintains it and who is being added — so the shape is stated once
     and each section says which half it is. The breakpoint was measured when the
     product list and its kit were one of the pairs; they no longer are, and the
     remaining pairs fit within it.

     A share of the page with both ends held: never under 380px, where the labelled
     controls start to crowd, and never over 620px, because these forms are one column
     and a text field two thirds of a wide screen across is harder to read, not easier.
     The list takes everything else, which is what the width of this page is for.

     Deliberately a min-width rule: stacked stays the default and this whole block is
     the enhancement, so a viewport that never matches it reads exactly the page it
     read before. */
  .cat-cols { display: flex; align-items: flex-start; gap: 18px; }
  .cat-cols > .cat-main { flex: 1 1 auto; min-width: 0; }
  .cat-cols > .cat-side { flex: 0 0 clamp(380px, 35%, 620px); }
  /* In a column the form starts level with the list beside it, and takes the column's
     width rather than the measure it keeps when it is stacked underneath. */
  .cat-cols > .cat-side > .card { margin: 0; max-width: none; }
}

h1 { font-size: 20px; margin: 0 0 12px; }
h2 { font-size: 15px; margin: 0 0 10px; }
.muted { color: var(--muted); }
.row { display: flex; align-items: center; gap: 10px; }
.between { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

/* Version tag: an accent chip shown beside the deploy version in Operations. */
.ver-tag {
  display: inline-block; font: 600 11px/1.6 ui-monospace, monospace; color: var(--accent);
  background: var(--accent-soft); border: 1px solid var(--accent-soft);
  padding: 0 6px; border-radius: 5px; white-space: nowrap;
}
/* How many versions are deployed, beside the latest one. A small muted badge instead
   of a sentence, so a narrow Versions column can't break mid-phrase. */
.ver-count {
  display: inline-block; font: 600 11px/1.6 system-ui, sans-serif; color: var(--muted);
  background: var(--bg); border: 1px solid var(--border);
  padding: 0 6px; border-radius: 9px; margin-left: 4px; white-space: nowrap; cursor: default;
}
/* An overview cell that must stay on one line (a timestamp, a version). */
td.nowrap { white-space: nowrap; }
/* One primary action plus the ⋯ overflow menu, right-aligned on one line, so every
   row keeps the same height regardless of which actions it offers. */
td.row-actions { text-align: right; white-space: nowrap; }
td.row-actions > * { vertical-align: middle; }
td.row-actions .icon-btn { margin-left: 4px; }

/* The tick-box column of a table whose rows can be acted on as a set (the incidents
   list). Narrow and centred, so a list that is usually read one row at a time does not
   pay for the column it only sometimes needs. */
th.row-pick, td.row-pick { width: 28px; text-align: center; padding-left: 10px; padding-right: 0; }
td.row-pick input { cursor: pointer; }

/* The Incidents view drops the centred content column (app.js sets the class on its
   route). Its cause table did not fit 1120px once a process id, a timestamp and the
   action line were on it — the card scrolled it and cut the actions off — and the
   width a wide window adds is what its messages need. The explanation above the
   tables is prose, so it keeps the measure it had in the column (1120px less the
   padding) rather than becoming one line across the screen. */
.incidents-mode main { max-width: none; }
.inc-lead { max-width: 1076px; }
/* An incident message is the one free-text cell of those tables, and the one holding
   the longest unbroken strings — a token endpoint's URL, a JSON error body. Wrapping
   only at spaces, one such string set the whole table's minimum width; breaking
   anywhere, the message is the column that gives. The min-width stops it giving so
   much that nothing in it can be read: below that, the card scrolls (ADR-0163). */
td.inc-msg { overflow-wrap: anywhere; min-width: 24ch; }

/* Operations instances toolbar: a process filter and a jump-to-instance box. */
.ops-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 12px; }
.ops-search { position: relative; flex: 1 1 240px; min-width: 180px; }
.ops-search svg { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); color: var(--muted); pointer-events: none; }
/* input[type=text] qualifier keeps this above the generic `input[type=text]` reset
   (same specificity, defined later), so the left padding that clears the search icon
   is not overridden back to 10px — which let the placeholder overlap the magnifier. */
.ops-search input[type="text"] {
  width: 100%; font: 13px/1.5 ui-monospace, monospace; padding: 7px 10px 7px 29px;
  border: 1px solid var(--border-strong); border-radius: 7px; background: var(--surface); color: var(--text);
}
.ops-search input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.ops-jump { display: inline-flex; align-items: center; gap: 6px; }
.ops-jump input {
  font: 13px/1.5 ui-monospace, monospace; padding: 7px 10px; width: 160px;
  border: 1px solid var(--border-strong); border-radius: 7px; background: var(--surface); color: var(--text);
}
.ops-jump input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
/* The jump is a compact arrow button, not a wide label: a squeezed flex row used to
   wrap "Open replay" onto two lines. Toolbar buttons never wrap their label. */
.ops-jump-go { flex: none; padding: 7px 12px; font-size: 15px; line-height: 1; }
.ops-toolbar .btn, .ops-varsearch .btn { white-space: nowrap; }
/* Variable-content instance search. */
.ops-varsearch { display: flex; align-items: center; gap: 8px; margin: 0 0 6px; }
.ops-varsearch .ops-search { flex: 1 1 320px; }
.var-hint code { font: 12px/1.4 ui-monospace, monospace; background: var(--bg); border: 1px solid var(--border); padding: 0 4px; border-radius: 4px; }
.var-results td { vertical-align: top; }
.var-hit { display: inline-flex; gap: 6px; align-items: baseline; font: 12px/1.5 ui-monospace, monospace; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 1px 7px; margin: 1px 0; }
.var-hit b { color: var(--muted); font-weight: 600; }
.var-hit .var-kind { font: 600 10px/1.6 system-ui; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 0 6px; }
.var-results mark { background: #fff3c4; color: #7a5b00; border-radius: 2px; padding: 0 1px; }
ol.steps { margin: 10px 0; padding-left: 20px; }
ol.steps li { margin: 6px 0; }

/* The Console landing card leads with the brand mark beside its heading. It is the
   same ".mark" box as the top bar — so an org logo (logo.js) replaces the built-in
   glyph here too — only larger, and the heading's bottom margin moves to the row. */
.welcome-head { display: flex; align-items: center; gap: 12px; margin: 0 0 12px; }
.welcome-head h1 { margin: 0; }
.welcome-version { font-size: 13px; font-family: ui-monospace, monospace; }
.welcome-mark { width: 48px; height: 48px; border-radius: 10px; flex: none; }

.btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: 6px; border: 1px solid var(--accent);
  background: var(--accent); color: var(--accent-ink); font: inherit; font-weight: 600; cursor: pointer;
}
.btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); text-decoration: none; }
.btn.ghost { background: transparent; color: var(--accent); }
.btn.ghost:hover { background: var(--accent-soft); }
.btn.neutral { background: var(--surface); color: var(--text); border-color: var(--border-strong); }
.btn.neutral:hover { background: var(--bg); }
.btn.ghost.danger { color: var(--danger); }
.btn.ghost.danger:hover { background: #fdecea; }
/* Solid danger button (modal confirm, bulk-terminate CTA). Scoped :not(.ghost) so it
   never clobbers the transparent background of a .btn.ghost.danger row action — the
   two share specificity, and without this the later rule would win and paint red text
   on a red fill. */
.btn.danger:not(.ghost) { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn.danger:not(.ghost):hover { background: #a5322a; border-color: #a5322a; }
.btn.sm { padding: 4px 10px; font-size: 12px; border-radius: 5px; }
/* A button that holds a state rather than firing once. The look is drawn from
   `aria-pressed`, so what a screen reader is told and what the eye sees cannot drift
   apart — there is no second class to keep in step
   (ADR-0240).
   `.on` is the older, class-driven spelling of the same thing. */
.btn.neutral.on, .btn.toggle[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.btn.toggle[aria-pressed="false"] { color: var(--muted); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* A link styled as a button is shut by taking its href away and saying so in
   aria-disabled (the Modeler's form doors), so it reads as a disabled button does. */
.btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
.btn.ghost[aria-disabled="true"]:hover { background: transparent; }

/* Bulk-terminate selection over the live "All instances" list */
.var-panel .vp-inst.picked { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.var-panel .vp-pick { display: inline-flex; align-items: center; margin-right: 2px; cursor: pointer; }
.var-panel .vp-pick input { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: var(--danger); }
.confirm-modal { width: min(440px, 92vw); }
.confirm-modal .modal-foot { justify-content: flex-end; }

/* Form fields (login + user management) */
.field { display: block; margin: 10px 0; font-size: 13px; color: var(--muted); }
/* A row of secondary actions under a field (e.g. the call-activity "create new
   process" button); nudged up so it reads as attached to the field above. */
.field-actions { display: flex; flex-wrap: wrap; gap: 6px; margin: -4px 0 10px; }
/* number is in this list because a field that carries one is still a field: left out,
   it was the one control on a form drawn in the browser's own default while every
   input beside it wore the console's — which reads as a defect in the page rather
   than as a type. The pickers that want a narrow numeric box (the starmap's depth,
   the playground's fields) state their own width and still do. */
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="number"],
.field input:not([type]) {
  display: block; width: 100%; box-sizing: border-box; margin-top: 5px;
  padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 6px;
  font: inherit; color: var(--text); background: var(--surface);
}
.field input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

/* Placeholders are suggestions, not values. Italic (real input never is) plus a lighter
   tone so an example like `count(totals) > 2` reads at a glance as a hint, not an entry. */
input::placeholder,
textarea::placeholder {
  color: var(--muted);
  opacity: 0.65;
  font-style: italic;
}
.field.inline { display: flex; align-items: center; gap: 8px; color: var(--text); }
.field.inline input { width: auto; margin: 0; }

/* Entra attribute capture mask (ADR-0172, amended): a per-operation form that assembles
   the worker's attributes JSON. Each row stacks a caption, an input, and a hint to
   match the .field look; a dashed rule sets off the "Weitere Attribute" escape hatch,
   and an error line flags a malformed escape-hatch JSON. */
.entra-mask { display: block; margin-top: 6px; }
.entra-mask .emask-row { display: block; margin: 0 0 9px; font-size: 13px; color: var(--muted); }
.entra-mask .emask-row > span { display: block; }
.entra-mask .emask-row input[type="text"],
.entra-mask .emask-row textarea {
  display: block; width: 100%; box-sizing: border-box; margin-top: 5px;
  padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 6px;
  font: inherit; color: var(--text); background: var(--surface);
}
.entra-mask .emask-row textarea { resize: vertical; }
.entra-mask .emask-row input:focus,
.entra-mask .emask-row textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.entra-mask .emask-row small.muted { display: block; margin-top: 3px; font-size: 12px; color: var(--muted); }
.entra-mask .emask-extra { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--border-strong); }
.entra-mask .emask-err { margin-top: 6px; font-size: 12px; color: var(--danger); }
/* A labelled checkbox property (e.g. Executable) in the properties panel. */
.pcheck { display: inline-flex; align-items: center; gap: 8px; margin: 10px 0 0; font-size: 13px; color: var(--text); cursor: pointer; }
.pcheck input { width: 15px; height: 15px; margin: 0; accent-color: var(--accent); cursor: pointer; }

/* Stat tiles */
.stats { display: flex; gap: 34px; flex-wrap: wrap; }
.stat b { display: block; font-size: 30px; font-weight: 700; line-height: 1.1; }
.stat span { font-size: 12px; color: var(--muted); }
/* A pill is a label, and like a chip it keeps to one line: its round ends are made for
   one, and a pill folded by a narrow table column reads as a tall blob. A column too
   narrow for it moves it under the cell's text instead. */
.pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; padding: 2px 9px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); white-space: nowrap; }
.chip { display: inline-block; font: 12px/1.6 ui-monospace, monospace; padding: 1px 7px; border-radius: 5px; background: var(--bg); border: 1px solid var(--border); white-space: nowrap; }

/* Artifact id availability (ADR-0222). A draft is stored under its
   process id and a form under its own id, so those fields are the artifact's identity:
   an id another artifact already holds is refused at Save, and the field says so while
   it is being typed rather than after. `.id-taken` is that collision; `.id-pending` is
   a form id retyped but not yet saved — the rename is real once Save runs. */
.field input.id-taken,
.field input[type="text"].id-taken,
/* A task row that decides an order. It is a link, not a label: the row says what
   it is and the chip is the way to where it is decided. Coloured with the accent
   rather than a state colour — an approval waiting is not a warning, it is work. */
.chip.approval { border-color: var(--accent); color: var(--accent-hover); text-decoration: none; }
.chip.approval:hover { background: var(--accent-soft, var(--bg)); }
.chip.id-taken { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
.field input.id-taken:focus,
.field input[type="text"].id-taken:focus { border-color: var(--danger); box-shadow: 0 0 0 2px rgba(192, 57, 43, 0.18); }
.chip.id-pending { border-style: dashed; border-color: var(--accent); }

/* Decision work that has not been written to the model every reference resolves
   (ADR-0321). It marks the editor's bar and the Explorer row, and
   it is a statement of fact rather than a warning — a draft is where an unfinished
   decision is meant to be — so it reads as an accent, not as an error. */
.chip.draft-chip { border-style: dashed; border-color: var(--accent); color: var(--accent); }

/* The version a decision is deployed at, on the decision editor's bar
   (ADR-0322). It is the counterpart of the "Deployment
   <key>" crumb a BPMN diagram opened from a deployment carries, and it states
   something that is live in the engine — so it reads solid and settled, next to
   the dashed draft chip which states the opposite. */
.chip.deployed-chip { border-color: var(--ok); color: var(--ok); background: var(--ok-soft, transparent); }

/* Trying a decision against sample inputs, in the decision editor
   (ADR-0326). It is the .start-panel strip the
   BPMN editor opens for a deploy, plus room for the answer: a row of typed inputs,
   then the outputs and the rule matrix below them. The matrix can be wider than the
   window on a table with many inputs, so the panel scrolls rather than the page. */
.dmn-test-panel { max-height: 46vh; overflow: auto; }
.dmn-test-inputs { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; }
.dmn-test-inputs .field { margin: 0; min-width: 160px; }
.dmn-test-inputs .field span .muted { font-weight: normal; }
/* The panel's controls follow what the decision declares, so it carries kinds the
   rest of the console has never needed: a list for a boolean, a calendar for a date.
   Styled here rather than by widening the shared `.field` rule, which names its
   input types one by one — adding date and time to it would restyle every date field
   in the console to settle one panel. */
.dmn-test-inputs .field input[type="date"],
.dmn-test-inputs .field input[type="time"],
.dmn-test-inputs .field input[type="datetime-local"],
.dmn-test-inputs .field select {
  display: block; width: 100%; box-sizing: border-box; margin-top: 5px;
  padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 6px;
  font: inherit; color: var(--text); background: var(--surface);
}
.dmn-test-inputs .field select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft);
}
.dmn-test-result { margin-top: 10px; }
.dmn-test-result .res { margin-bottom: 10px; }

/* A searchable declaration the model cannot honour (ADR-0244). Red is certain — the
   deploy refuses a repeated name, and a structured value can never be indexed. Amber is
   a question: the diagram does not write that name, but something outside the model
   still might. */
.sv-marks { display: flex; flex-wrap: wrap; gap: 4px; margin: -6px 2px 12px; }
.chip.sv-err { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
.chip.sv-warn { border-color: var(--warn); background: var(--warn-soft); color: var(--warn); }
.id-check { margin: 4px 0 10px; font-size: 12px; color: var(--danger); }
/* The form editor's collision note sits in the toolbar beside the id chip. */
.id-warn { font-size: 12px; color: var(--danger); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46ch; }
.pill.ok { background: #e7f6ec; color: var(--ok); }
.pill.err { background: #fdecea; color: var(--danger); }
.pill.warn { background: #fdf3e2; color: #b26b00; }
/* Nobody is there: the quietest pill there is, because "offline" is the resting
   state of most of a user list and must not read as a warning (presence.go). */
.pill.off { background: var(--bg); color: var(--muted); border: 1px solid var(--border); }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); display: inline-block; }
.pill.err .dot, .pill.warn .dot, .pill.off .dot { background: currentColor; }
/* A pill can be the link to what it counts — the incident flag in the Operations
   lists opens the version (or instance) it is about (ADR-0150). */
/* Deployed, but something in the model will not resolve at runtime (ADR-0158). */
.deploy-warnings {
  margin: 8px 0 10px; padding: 8px 10px; border-radius: var(--radius);
  background: var(--warn-soft); color: var(--warn); font-size: 12.5px; line-height: 1.5;
}
.deploy-warnings ul { margin: 5px 0 0; padding-left: 18px; }
.deploy-warnings li + li { margin-top: 4px; }
/* What switching the catalogue off strands, on an administrator's dashboard
   (ADR-0434): processes still working orders that will fail at the order routes. */
.catalogue-stranded, .tokens-expiring { border: 1px solid var(--warn); background: var(--warn-soft); margin-bottom: 18px; }
.catalogue-stranded h2, .tokens-expiring h2 { color: var(--warn); }
.catalogue-stranded ul, .tokens-expiring ul { margin: 6px 0 10px; padding-left: 18px; }
/* The storage guard (ADR-0481): an alert above the dashboard when there is
   something to do — amber while the room runs low, red once writes are refused — and the
   administrator's card with one bar per file system. The bar's track is what this server
   can ever have (used + available, as df counts it); the dark mark is where the reserve
   begins, so the distance between the fill and the mark is the room left before a freeze. */
.storage-alert { border: 1px solid var(--warn); background: var(--warn-soft); margin-bottom: 18px; }
.storage-alert h2 { color: var(--warn); }
.storage-alert.storage-frozen, .storage-alert.storage-halted { border-color: var(--danger); background: var(--danger-soft); }
.storage-alert.storage-frozen h2, .storage-alert.storage-halted h2 { color: var(--danger); }
.storage-card { margin-top: 18px; }
.storage-fs { margin: 10px 0 14px; }
.storage-paths code { font-size: 12px; }
.storage-bar { position: relative; height: 10px; border-radius: 5px; background: var(--border); margin: 6px 0 4px; }
.storage-used { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 5px; background: var(--ok); }
.storage-used.storage-warn { background: var(--warn); }
.storage-used.storage-frozen, .storage-used.storage-halted { background: var(--danger); }
.storage-used.storage-unknown { background: var(--muted); }
.storage-reserve { position: absolute; top: -3px; bottom: -3px; width: 2px; background: var(--text); }
.storage-nums { font-size: 12px; }
.storage-forecast { margin: 4px 0 8px; }
.storage-error { color: var(--danger); }
/* How the card gives disk back, and the dialog that deletes finished instances
   (ADR-0486). */
.storage-reclaim { border-top: 1px solid var(--border); margin-top: 12px; padding-top: 10px; }
.storage-reclaim-title { font-size: 14px; margin: 0 0 6px; }
.storage-reclaim-actions { gap: 8px; flex-wrap: wrap; margin: 8px 0 4px; }
.storage-reclaim-result:empty { display: none; }
.confirm-modal.purge-modal { width: min(560px, 94vw); }
.purge-preview { margin: 10px 0 4px; min-height: 1.4em; }
.purge-preview p, .purge-progress p { margin: 4px 0; }
.purge-versions, .purge-notes { margin: 4px 0 8px 18px; padding: 0; font-size: 12.5px; }
.purge-off { color: var(--danger); }
.purge-progress { margin-top: 10px; }
#api-tokens #tok-soon ul { margin: 6px 0; padding-left: 18px; }
/* The variable editor inside the incident's fix dialog (ADR-0158). */
.inc-vars-modal { max-width: 620px; width: 92vw; }
.inc-vars-json {
  width: 100%; box-sizing: border-box; resize: vertical; min-height: 180px;
  font: 12.5px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--text);
}
.inc-vars-json:focus { outline: none; border-color: var(--accent); }
/* Who set a variable by hand (ADR-0098/0152): an operator correction, marked apart
   from the values the process computed itself. */
.c-actor {
  font: 600 10px/1.6 system-ui, sans-serif; white-space: nowrap;
  padding: 0 6px; border-radius: 999px;
  color: var(--warn); background: var(--warn-soft);
}
/* A value the selected element itself wrote, on the Variables tab's Output side
   (ADR-0159), so its own contribution stands out from what it inherited. */
.c-changed {
  font: 700 10px/1.6 system-ui, sans-serif; white-space: nowrap;
  padding: 0 5px; border-radius: 999px;
  color: var(--ok); background: var(--ok-soft, var(--accent-soft));
}
/* Input / Output switch in the replay Variables toolbar (ADR-0159). */
.v-side { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); }
.v-side-btn {
  background: none; border: none; cursor: pointer; border-radius: 4px;
  padding: 2px 8px; font: 600 11px/1.6 system-ui, sans-serif; color: var(--muted);
}
.v-side-btn:hover { color: var(--text); }
.v-side-btn.active { background: var(--surface); color: var(--accent); box-shadow: 0 1px 2px rgba(0,0,0,.06); }
/* A step a person forced rather than one the engine drove (ADR-0159). It leads the
   Details tab's blocks because it changes how every other fact on the step reads. */
.ops-manual {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--warn); background: var(--warn-soft);
}
.ops-manual h4 { margin: 0 0 4px; font-size: 12px; color: var(--warn); }
.ops-manual p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text); }
.ops-manual-reason { margin-top: 6px !important; font-style: italic; }

/* The loop block on the Details tab (ADR-0077/0133): what the loop was told to repeat
   while, what this round read, and what the loop then did. Neutral rather than warning-
   coloured — a loop doing what it was told is the normal case, and the block is there to
   be read, not to alarm. */
.ops-loop {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface-2, var(--surface));
}
.ops-loop h4 { margin: 0 0 6px; font-size: 12px; color: var(--accent); }
.ops-loop .ops-props { margin: 0; }
.ops-loop code { font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
.ops-loop-out { margin: 6px 0 0; font-size: 12px; line-height: 1.5; color: var(--text); }

/* The repair form on an incident (ADR-0169): the modeler's own fields, in place of the
   raw JSON document. The form-js viewer brings its own field styling, so this only gives
   it room to breathe and a scroll of its own — a repair form may be long, and the modal
   must not grow past the viewport because of it. */
.repair-modal .modal-body { max-width: 560px; }
.repair-form { margin-top: 10px; max-height: 46vh; overflow-y: auto; }
.repair-err { color: var(--warn); }
/* Which form is being shown, so an operator can tell a stale binding from a wrong one
   without leaving the dialog. */
.repair-which { font-size: 12px; }

/* Moving a running instance to another version of its process (ADR-0162). The plan is
   the dialog's centre of gravity — the refusals in particular, since a migration that
   cannot go through is the case the dry run exists for — so it reads as a block rather
   than as a paragraph of the form around it. */
.mig-modal .modal-body { max-width: 560px; }
.mig-plan {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface-2, var(--surface));
  max-height: 240px; overflow-y: auto;
}
.mig-verdict { margin: 0; font-size: 13px; font-weight: 600; }
.mig-verdict.ok { color: var(--ok); }
.mig-verdict.bad { color: var(--warn); }
.mig-problems { margin: 6px 0 0; padding-left: 18px; font-size: 12px; line-height: 1.6; }
.mig-problems li { margin: 0 0 2px; }
.mig-refused { max-height: 220px; overflow-y: auto; }
.mig-map { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
.mig-map h4 { margin: 0 0 4px; font-size: 12px; color: var(--muted); }
.mig-moved { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.6; }
.mig-err { color: var(--warn); }

/* The fork alternative (ADR-0389). It sits below the in-place
   plan and reads as the second answer to the same question, not as a second dialog: the
   rebinding is what an operator wants whenever it holds, and a fork ends the instance it
   continues. Dimmed while the rebinding is still on the table, for exactly that reason. */
.mig-fork {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 8px;
  border: 1px dashed var(--border); background: var(--surface-2, var(--surface));
}
.mig-fork.alt { opacity: 0.72; }
.mig-fork h4 { margin: 0 0 4px; font-size: 12px; color: var(--muted); }
.mig-resume { margin: 6px 0 0; max-height: 132px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.mig-resume-opt { display: flex; align-items: center; gap: 6px; font-size: 12px; }
.mig-resume-opt input { margin: 0; }

/* The migration boundary on the replay history (ADR-0162): the steps above it and below
   it ran on different versions of the process, so it is drawn as a rule across the list
   rather than as another element row — a reader scanning the column should not be able
   to mistake it for a step the token walked. */
.ops-hrow.mig {
  border-top: 1px solid var(--accent); border-bottom: 1px solid var(--accent);
  background: var(--accent-soft);
}
.ops-hrow.mig .ops-hname { font-style: italic; }
/* A fork row is the same kind of rule as a migration — the story does not continue below
   it — but it is a different fact: the work left for another instance rather than
   carrying on here under a new version (ADR-0389). */
.ops-hrow.mig.fork { border-top-style: dashed; border-bottom-style: dashed; }
.ops-hicon.mig { color: var(--accent); }
.ops-mig {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--accent); background: var(--accent-soft);
}
.ops-mig h4 { margin: 0 0 4px; font-size: 12px; color: var(--accent); }
.ops-mig p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text); }
.ops-mig-reason { margin-top: 6px !important; font-style: italic; }

/* The status column holds a pill and, when the runtime could not build the client, the
   reason under it. Sized for that message rather than left to a layout that split three
   columns evenly — which put the status halfway across the row and stranded the actions
   an inch of empty table away from it. */
.conn-status-col { width: 220px; }
/* Why a stored worker is not usable, under its status pill (ADR-0158). */
.conn-problem {
  margin-top: 3px; max-width: 340px; color: var(--danger);
  font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere;
}
/* The worker a parked task resolves through, named on the incident itself
   (ADR-0160). An operator reading "no worker registered as X" has to know which
   integration X is before they can go and fix it, and the incident is where they are
   standing. Amber when the model references a name nobody configured. */
.inc-conn { margin: 4px 0 6px; }
.inc-conn .pill { font-weight: 600; }
/* The shared worker dialog (ADR-0160): the same form whether it is opened from
   Console › Workers or from the incident a misconfigured worker parked. */
.conn-modal { max-width: 640px; width: 92vw; }
.conn-fields { display: flex; flex-wrap: wrap; gap: 8px; align-items: end; }
.conn-fields .field { margin: 0; }
.conn-modal .conn-problem {
  max-width: none; margin: 0 0 10px; padding: 7px 9px;
  border-radius: var(--radius); background: var(--danger-soft);
}
.conn-enabled { display: flex; align-items: center; gap: 7px; margin-top: 10px; font-size: 12.5px; }
.conn-enabled input { width: auto; margin: 0; }
/* Which deployed processes resolve through a worker, under its row (ADR-0163) —
   the line that turns Delete from a click into a decision. It is a count on a chip
   rather than the list itself: spelled out, a worker twenty-one deployed definitions
   reference drew fourteen wrapped lines of links, and the endpoint, the status and the
   actions beside them were pushed apart by it. The list is one click behind it. */
.conn-usage { font-size: 12px; margin-top: 5px; line-height: 1.5; }
.conn-usage-btn {
  display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
  font: 12px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--muted); background: var(--bg);
  border: 1px solid var(--border); border-radius: 999px; padding: 1px 10px; cursor: pointer;
}
.conn-usage-btn b { color: var(--text); font-weight: 600; }
.conn-usage-btn:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.conn-usage-btn:hover b { color: var(--accent); }
.conn-usage-more { font-size: 13px; opacity: .6; }

a.pill { text-decoration: none; font-weight: 600; }
a.pill:hover { filter: brightness(0.94); }
tr.done { opacity: .72; } /* finished instances are de-emphasized vs running ones */

/* Tables */
table { border-collapse: collapse; width: 100%; }
thead th { text-align: left; font-size: 12px; color: var(--muted); font-weight: 600; padding: 8px 12px; background: var(--bg); border-bottom: 1px solid var(--border); }
/* Shared data-table enhancer (table.js): click-to-sort headers plus a per-column
   filter row beneath the header. Used by every data table so they behave alike. */
thead th.dt-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
thead th.dt-sortable:hover { color: var(--text); }
thead th.dt-sortable[aria-sort] { color: var(--text); }
th.dt-sortable .dt-ind { display: inline-block; width: 9px; font-size: 9px; line-height: 1; margin-left: 5px; opacity: .85; }
/* The filter row is shown by default — it is how a list is searched, so it is on screen
   with the list — and the funnel toggle takes it away and brings it back. The funnel
   highlights only while a filter is actually set: showing the row is the normal state,
   so highlighting that would mark every table in the UI as special. */
.dt-filter-toggle { display: inline-flex; align-items: center; justify-content: center; background: none; border: none; cursor: pointer; color: var(--muted); padding: 2px; border-radius: 4px; line-height: 0; vertical-align: middle; }
.dt-filter-toggle:hover { color: var(--text); background: var(--bg); }
.dt-filter-toggle.dt-has-filters { color: var(--accent); }
th.dt-toggle-cell { text-align: right; }
th.dt-toggle-host .dt-filter-toggle { margin-right: 6px; }
thead tr.dt-filter-row { display: none; }
table.dt-filters-open thead tr.dt-filter-row { display: table-row; }
thead tr.dt-filter-row th { padding: 3px 8px 7px; background: var(--bg); border-bottom: 1px solid var(--border); }
.dt-filter {
  width: 100%; min-width: 0; box-sizing: border-box; padding: 4px 7px;
  border: 1px solid var(--border); border-radius: 5px;
  font: inherit; font-size: 12px; font-weight: 400; color: var(--text); background: var(--surface);
}
.dt-filter::placeholder { color: var(--muted); opacity: .7; }
.dt-filter:focus { outline: none; border-color: var(--accent); }
tbody td { padding: 11px 12px; border-bottom: 1px solid var(--border); }
tbody tr:hover { background: #fafbfc; }
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 16px; }
.tabs button { background: none; border: none; padding: 10px 12px; font: inherit; color: var(--muted); cursor: pointer; border-bottom: 2px solid transparent; }
.tabs button.active { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }
/* The "nothing here yet" placeholder — a centred, padded line where a list or a table
   would be. Held off the vendored properties panels on purpose: bpmn-js, dmn-js and
   form-js all mark a group header (and form-js a collapsible entry's title) whose
   entries are unset with `empty`. That is their state flag, not our placeholder, and a
   bare `.empty` reached it — 34px of padding all round turned every unset group in the
   Form and DMN editors into a 68px block with its title pushed in and truncated, beside
   the 27px rows of the groups that happened to be filled. Not neutralised inside the
   panel but excluded from it, so the vendor's own styling stands rather than being
   overridden back to something of ours. */
.empty:not([class*="bio-properties-panel"]) { text-align: center; color: var(--muted); padding: 34px; }

textarea, input[type=text], select { width: 100%; font: 13px/1.5 ui-monospace, monospace; padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 6px; background: var(--surface); color: var(--text); }
select { font-family: inherit; cursor: pointer; }
input[type=text] { font-family: inherit; }
label.field { display: block; margin: 10px 0; }
/* Direct child only: the field's caption. A descendant selector here would also
   capture nested <span>s such as the FEEL editor's syntax-highlight tokens,
   forcing them to display:block (stacking every token on its own line) and
   repainting them muted. */
label.field > span { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }

/* A documentation field holds prose, not code — the panel's textareas default to a
   monospace face for FEEL and scripts, which reads badly for a paragraph. Give the
   documentation editor the UI font and let it grow vertically instead. */
.doc-field textarea { font-family: inherit; resize: vertical; min-height: 56px; }

/* Toast */
#toast {
  position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%);
  background: #14161a; color: #fff; padding: 10px 16px; border-radius: 8px;
  box-shadow: 0 6px 24px rgba(0,0,0,0.25); z-index: 50; font-size: 13px; max-width: 90vw;
}
#toast.err { background: var(--danger); }
#toast.ok { background: var(--ok); }
#toast.warn { background: var(--warn); }

/* ---------- Editor ---------- */
.editor {
  position: fixed; inset: var(--topbar-h) 0 0 0; display: flex; flex-direction: column;
  background: var(--surface);
}
/* The bar wraps rather than pushing its right-hand end past the window: on a narrow
   window Deploy used to sit off the edge, unreachable, and adding a third tab made it
   show up sooner. Wrapping costs a row of height only when the row is actually full. */
.editor-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; min-height: 44px; padding: 0 10px; border-bottom: 1px solid var(--border); }
.editor-bar .etabs { display: flex; gap: 2px; flex: none; }
/* The bar is one row and its right-hand actions are the working end of it, so what
   gives when the window is narrow has to be the breadcrumb — not Deploy sliding off
   the edge. The crumb is the only variable-length thing here (it carries the diagram
   name), and it is the one part a reader can reconstruct from the canvas. */
.editor-bar > .crumbs { flex: 0 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.editor-bar > .btn { flex: none; }
/* The overflow menu at the end of the bar (ADR-0229). It borrows
   the .dropdown-menu look but not app.js's delegated machinery — see wireBarMenu — so it
   is placed here rather than by placeMenu's viewport coordinates. The bar sits in a
   position:fixed .editor with nothing clipping it, so absolute is enough. */
.editor-bar > .dropdown { flex: none; display: flex; }
/* What the bar shows and what the bar does are different kinds of thing, and the rule
   between them says so. */
.editor-bar .bar-div { flex: none; width: 1px; align-self: stretch; margin: 9px 6px; background: var(--border-strong); }
.editor-bar .dropdown-menu { position: absolute; left: auto; right: 0; top: calc(100% + 4px); }
.editor-bar .bar-more { font-size: 20px; line-height: 1; padding: 3px 9px; }
.editor-bar .bar-more[aria-expanded="true"] { background: var(--bg); border-color: var(--border); color: var(--text); }
.editor-bar .etabs button { background: none; border: none; padding: 8px 12px; font: inherit; font-weight: 600; color: var(--muted); cursor: pointer; border-bottom: 2px solid transparent; }
.editor-bar .etabs button.active { color: var(--text); border-bottom-color: var(--accent); }
.editor-bar .crumbs { color: var(--muted); font-size: 13px; margin-right: 6px; }
/* What the Modeler has open — the draft or version N — and how it stands against the
   other (shown-version.js). The bar wraps, and at 900px it has only a few dozen pixels
   to spare, fewer with a wider system font — so the chip claims no room of its own:
   a zero basis keeps it from ever starting a second row, and a large grow factor lets
   it take the free space up to its own text width before the spacer gets the rest.
   Short of that it is ellipsised; the whole sentence is in its title and its panel. */
.editor-bar .shown-version { position: relative; flex: 100 1 0; min-width: 2.5em; max-width: max-content;
  display: flex; margin-right: 6px; }
.editor-bar .shown-version[hidden] { display: none; }
.editor-bar .sv-chip { border: 0; cursor: pointer; font: inherit; font-size: 12px; min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; line-height: 18px; }
.editor-bar .sv-chip.off { border: 1px solid var(--border); }
.editor-bar .sv-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.editor-bar .sv-pop { position: absolute; left: 0; top: calc(100% + 6px); z-index: 60; width: 340px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 10px 30px rgba(20, 22, 26, .14); padding: 10px 12px; font-size: 13px; color: var(--text); }
.editor-bar .sv-pop[hidden] { display: none; }
.sv-rows { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; margin: 0; }
.sv-rows dt { color: var(--muted); white-space: nowrap; }
.sv-rows dd { margin: 0; overflow-wrap: anywhere; }
.sv-note { margin: 10px 0 0; line-height: 1.45; }
.sv-note.warn { background: var(--warn-soft); color: var(--text); border-left: 3px solid var(--warn); padding: 6px 8px; border-radius: 4px; }
.sv-links { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
/* Deploy on a version opened read-only leaves the draft alone; the panel says so first. */
.deploy-from-version { margin: 0 0 10px; padding: 6px 8px; border-left: 3px solid var(--warn);
  background: var(--warn-soft); border-radius: 4px; font-size: 13px; line-height: 1.45; }
.editor-bar nav.crumbs { display: flex; align-items: center; gap: 6px; }
.editor-bar nav.crumbs a { color: var(--muted); text-decoration: none; }
.editor-bar nav.crumbs a:hover { color: var(--text); text-decoration: underline; }
.editor-bar nav.crumbs .crumb-sep { color: var(--border); }
.editor-bar nav.crumbs .crumb-current { color: var(--text); font-weight: 600; }
.editor-body { flex: 1; display: flex; min-height: 0; }
#canvas { flex: 1; min-width: 0; height: 100%; background: var(--diagram-bg); }
/* The dot grid is an editing affordance — show it only in the Design/Modeler
   view (.editor without the read-only .live modifier). The live, collaboration
   and Operations replay views reuse #canvas but stay flat grey. */
.editor:not(.live) #canvas {
  background:
    radial-gradient(circle, var(--diagram-dot) 1px, transparent 1px) 0 0 / 22px 22px,
    var(--diagram-bg);
}
.props {
  width: 300px; flex: 0 0 auto; border-left: 1px solid var(--border); background: var(--surface);
  display: flex; flex-direction: column; overflow: auto;
}
/* Draggable divider between the canvas and the properties panel, so authoring
   long FEEL/script expressions has room. */
.props-resizer {
  flex: 0 0 6px; cursor: col-resize; background: transparent;
  position: relative; z-index: 2;
}
.props-resizer::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 2px; width: 2px;
  background: var(--border); transition: background 0.12s;
}
.props-resizer:hover::before, .props-resizer.dragging::before { background: var(--accent, var(--border-strong)); }
.props .msg-list { display: flex; flex-direction: column; gap: 6px; }
.props .msg-row { display: flex; gap: 6px; align-items: center; }
.props .msg-row input { flex: 1 1 40%; min-width: 0; padding: 6px 8px; border: 1px solid var(--border-strong); border-radius: 5px; font: inherit; }
.props .msg-row .msg-del { flex: 0 0 auto; padding: 4px 9px; line-height: 1; }
.props .sig-list { display: flex; flex-direction: column; gap: 6px; }
.props .sig-row { display: flex; gap: 6px; align-items: center; }
.props .sig-row input { flex: 1 1 auto; min-width: 0; padding: 6px 8px; border: 1px solid var(--border-strong); border-radius: 5px; font: inherit; }
.props .sig-row .sig-del { flex: 0 0 auto; padding: 4px 9px; line-height: 1; }
.props .err-list { display: flex; flex-direction: column; gap: 6px; }
.props .err-row { display: flex; gap: 6px; align-items: center; }
.props .err-row input { flex: 1 1 auto; min-width: 0; padding: 6px 8px; border: 1px solid var(--border-strong); border-radius: 5px; font: inherit; }
.props .err-row .err-del { flex: 0 0 auto; padding: 4px 9px; line-height: 1; }
.props .esc-row { display: flex; gap: 6px; align-items: center; }
.props .esc-row input { flex: 1 1 auto; min-width: 0; padding: 6px 8px; border: 1px solid var(--border-strong); border-radius: 5px; font: inherit; }
.props .esc-row .esc-del { flex: 0 0 auto; padding: 4px 9px; line-height: 1; }
/* The element header and the property body. Both diagram panels wear them — the
   Modeler's and the class canvas's (.im-side) — because a person moves between the
   two in one session and the difference had nothing behind it. */
.props .phead, .im-side .phead { padding: 14px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
.props .ptype, .im-side .ptype { width: 26px; height: 26px; border: 1px solid var(--border-strong); border-radius: 5px; display: grid; place-items: center; font-size: 11px; color: var(--muted); }
.props .psec, .im-side .psec { padding: 14px 16px; border-bottom: 1px solid var(--border); }
.props .psec > h3, .im-side .psec > h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); margin: 0 0 10px; }
.props .kv, .im-side .kv { font-size: 12px; color: var(--muted); }
.props .kv b, .im-side .kv b { color: var(--text); font-weight: 600; }
.problems { height: 30px; flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 0 14px; border-top: 1px solid var(--border); font-size: 12px; color: var(--muted); background: var(--bg); cursor: pointer; user-select: none; }
.problems:hover { background: var(--surface); }
.problems:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.problems .badge { background: var(--border); border-radius: 999px; padding: 0 7px; font-weight: 600; color: var(--text); }
.problems.has-errors .badge { background: var(--danger); color: #fff; }
.problems.has-warnings .badge { background: var(--warn); color: #fff; }
.problems .prob-summary { color: var(--muted); }
.problems .prob-caret { color: var(--muted); transition: transform 0.12s; }
.problems.open .prob-caret { transform: rotate(180deg); }
/* Filter chips live in the bar; a click on one toggles the view, not the drawer. */
.prob-filters { display: inline-flex; gap: 2px; }
.prob-filters button { font: inherit; font-size: 11px; padding: 1px 8px; border: 1px solid var(--border-strong); background: var(--surface); color: var(--muted); border-radius: 999px; cursor: pointer; }
.prob-filters button.active { background: var(--accent-soft); border-color: var(--accent-soft); color: var(--accent); font-weight: 600; }
/* Drawer of findings, opening upward above the bar (it shares the editor's column
   flex, so it pushes the canvas up rather than overlaying it). */
.prob-list { flex: 0 0 auto; max-height: 240px; overflow: auto; border-top: 1px solid var(--border); background: var(--surface); }
.prob-list .prob-empty { margin: 0; padding: 10px 14px; font-size: 12px; }
.prob-row { display: flex; gap: 8px; align-items: flex-start; padding: 7px 14px; border-bottom: 1px solid var(--border); font-size: 12px; cursor: default; }
.prob-row[data-el] { cursor: pointer; }
.prob-row[data-el]:hover { background: var(--bg); }
.prob-sev { flex: 0 0 16px; width: 16px; height: 16px; display: grid; place-items: center; border-radius: 999px; font-size: 10px; font-weight: 700; color: #fff; margin-top: 1px; }
.prob-row.prob-error .prob-sev { background: var(--danger); }
.prob-row.prob-warning .prob-sev { background: var(--warn); }
.prob-msg { flex: 1 1 auto; color: var(--text); }
.prob-el { flex: 0 0 auto; font: 600 11px/1.5 ui-monospace, monospace; color: var(--muted); background: var(--bg); border-radius: 4px; padding: 0 5px; }
.prob-rule { flex: 0 0 auto; color: var(--muted); font-size: 11px; }
/* Canvas highlight for the element a clicked problem points at. */
.djs-element.atlas-problem .djs-visual > :is(rect, circle, polygon, path) {
  stroke: var(--danger) !important; stroke-width: 2px !important;
}
.deploy-wrap { position: relative; }
.coming { display: grid; place-items: center; height: 100%; color: var(--muted); text-align: center; }

/* ---------- Panorama ArchiMate viewer ---------- */
.panorama-editor .editor-bar { padding-left: 14px; }
.panorama-view-tabs { display: flex; min-width: 0; overflow-x: auto; align-self: stretch; }
.panorama-view-tab {
  flex: 0 0 auto; border: 0; border-bottom: 2px solid transparent; background: none;
  padding: 0 12px; color: var(--muted); font: 600 12px/42px ui-sans-serif, system-ui, sans-serif; cursor: pointer;
}
.panorama-view-tab:hover { color: var(--text); background: var(--bg); }
.panorama-view-tab.active { color: var(--text); border-bottom-color: var(--accent); }
.panorama-status { color: var(--muted); font-size: 12px; white-space: nowrap; margin: 0 4px; }
.panorama-lock { color: var(--ok); }
/* The canvas controls belong to the canvas, so they are positioned against the
   stage rather than against the editor — against the editor they sat on top of the
   palette and swallowed its clicks.
   The box itself takes no pointer events, only its buttons do. A floating control
   over a diagram covers whatever is under it, and moving it to another corner only
   changes which shapes it steals clicks from; both corners were tried, and both
   broke a different test. */
/* The canvas controls, on both diagram-js surfaces. One definition rather than two
   because zooming a diagram is the same act on the Panorama canvas and on the class
   canvas, and ADR-0237 asks the second to look like the first — which a copied block
   stops doing the day either copy is touched. The group ignores the pointer so a drag
   that starts beside a button still pans the sheet underneath. */
.panorama-tools, .im-tools, .og-tools {
  position: absolute; right: 12px; top: 12px; z-index: 8; display: flex; flex-direction: column;
  padding: 3px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface);
  box-shadow: 0 2px 8px rgba(0,0,0,.08); pointer-events: none;
}
.panorama-tools > *, .im-tools > *, .og-tools > * { pointer-events: auto; }
.panorama-tools .icon-btn, .im-tools .icon-btn, .og-tools .icon-btn { width: 30px; height: 28px; border-radius: 4px; font-weight: 700; }
.im-tool-sep { height: 1px; margin: 3px 2px; background: var(--border); }
/* An armed tool is a mode, and a mode has to be visible or it is a trap: the button
   stays lit for exactly as long as the next drag draws a box instead of panning. */
.im-tools .icon-btn.active { background: var(--accent); color: var(--accent-ink); }
.im-tools .icon-btn.active:hover { background: var(--accent); color: var(--accent-ink); }
.im-canvas.marquee, .im-canvas.marquee .djs-container { cursor: crosshair; }
/* The list of what a box took hold of. Each line goes back to editing that one. */
.im-many { display: flex; flex-direction: column; gap: 2px; }
.im-many-row {
  display: flex; gap: 8px; align-items: baseline; text-align: left; width: 100%;
  padding: 4px 6px; border: 1px solid transparent; border-radius: 4px;
  background: transparent; color: var(--text); font: inherit; cursor: pointer;
}
.im-many-row:hover { background: var(--bg); border-color: var(--border); }
.panorama-canvas { flex: 1; min-width: 0; height: 100%; position: relative; overflow: hidden; background: var(--diagram-bg); }
.panorama-canvas .djs-container { font-family: inherit; }
.panorama-canvas .archimate-label { font: 600 12px/1.3 ui-sans-serif, system-ui, sans-serif; fill: #20252a; pointer-events: none; }
.panorama-canvas .djs-element.selected .djs-outline { stroke: var(--accent); stroke-width: 2px; }

/* Runtime marks on the diagram (ADR-0189 §6, P4d). The record's constraint is the
   whole design: layer colours remain intact, and runtime state is a border, a badge
   and a text legend. A layer colour is semantics — it says whether an element is
   business, application or technology — so nothing below sets `fill` on an element.
   The border is a separate rect drawn around it and the badge sits beside it. */
.panorama-stage { flex: 1; min-width: 0; display: flex; flex-direction: column; position: relative; }
.panorama-stage .panorama-canvas { flex: 1; }
.panorama-canvas-mark { pointer-events: none; }
.panorama-mark-border { fill: none; stroke-width: 2px; stroke-dasharray: none; }
.panorama-mark-dot { stroke-width: 1.5px; }
.panorama-mark-glyph { font: 700 10px/1 ui-sans-serif, system-ui, sans-serif; }
.panorama-mark-critical .panorama-mark-border { stroke: var(--danger); }
.panorama-mark-critical .panorama-mark-dot { fill: var(--danger); stroke: var(--danger); }
.panorama-mark-critical .panorama-mark-glyph { fill: #fff; }
.panorama-mark-attention .panorama-mark-border { stroke: var(--warn); }
.panorama-mark-attention .panorama-mark-dot { fill: var(--warn-soft); stroke: var(--warn); }
.panorama-mark-attention .panorama-mark-glyph { fill: var(--warn); }
/* OK is a small quiet dot rather than nothing: "observed and fine" and "not
   observed at all" must not look the same, and it is not something to shout. */
.panorama-mark-ok .panorama-mark-border { stroke: var(--ok); stroke-opacity: 0.45; }
.panorama-mark-ok .panorama-mark-dot { fill: var(--ok); stroke: var(--ok); }
/* The legend is not optional decoration: a mark nobody can decode is decoration,
   and the record asks for the words in the same breath as the badges. */
.panorama-live-legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px;
  padding: 8px 12px; border-top: 1px solid var(--border); background: var(--surface);
  font-size: 12px;
}
.panorama-live-swatch { display: inline-flex; align-items: center; gap: 6px; }
.panorama-properties { width: 300px; }
.panorama-props-empty { margin: auto; padding: 32px 24px; text-align: center; color: var(--muted); }
.panorama-props-empty b { color: var(--text); }
.panorama-props-empty p { font-size: 12px; line-height: 1.5; }
.panorama-selection-icon { font-size: 28px; color: var(--accent); margin-bottom: 8px; }
/* Observations in the properties panel (ADR-0189 §6). The severity is a bar on
   the left edge and a word, never a recoloured element: ArchiMate layer colours
   carry semantics, and overwriting them with health is what the record forbids. */
.panorama-obs {
  border-left: 3px solid var(--border-strong); padding: 6px 0 6px 9px;
  margin: 0 0 10px; font-size: 12px; display: flex; flex-direction: column; gap: 4px;
}
.panorama-obs:last-of-type { margin-bottom: 0; }
.panorama-obs-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.panorama-obs p { margin: 0; color: var(--muted); }
.panorama-obs code { font-size: 11px; overflow-wrap: anywhere; }
.panorama-obs-detail { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 11px; }
.panorama-sev-critical { border-left-color: var(--danger); }
.panorama-sev-attention { border-left-color: var(--warn); }
.panorama-sev-ok { border-left-color: var(--ok); }
.panorama-unwatched { font-style: italic; }
/* The drift journal in the properties panel (ADR-0189 P5). A list of transitions,
   deliberately not a chart: this is a correlation surface, and a sparkline would
   promise a continuity the journal does not have. Its limits sit with it, closed
   but never hidden, because "nothing changed" and "nobody looked" read the same
   without them. */
.panorama-drift { list-style: none; margin: 0; padding: 0; font-size: 12px; }
.panorama-drift-entry {
  border-left: 3px solid var(--border-strong); padding: 6px 0 6px 9px;
  margin: 0 0 8px; display: flex; flex-direction: column; gap: 3px;
}
.panorama-drift-entry:last-child { margin-bottom: 0; }
.panorama-drift-entry code { font-size: 11px; overflow-wrap: anywhere; }
.panorama-drift-entry p { margin: 0; }
.panorama-drift-limits { margin-top: 10px; font-size: 11px; color: var(--muted); }
.panorama-drift-limits summary { cursor: pointer; }
.panorama-drift-limits ul { margin: 6px 0 0; padding-left: 16px; display: flex; flex-direction: column; gap: 5px; }
.panorama-drift-limits b { color: var(--text); }
.panorama-obs-since { font-size: 11px; }
/* The authoring toolbar (ADR-0189 §2). The status is the one place that says
   whether there is unsaved work, so it changes colour rather than only wording:
   "no unsaved changes" and "three shapes moved" must not read alike at a glance. */
.panorama-tool-sep {
  width: 1px; align-self: stretch; margin: 2px 4px; background: var(--border-strong);
}
/* The palette (ADR-0189 §2). Built from the subset the server serves, so it can
   only offer what the write path accepts — and it carries that subset's limits
   under a fold, because a palette is the one thing somebody reads without reading
   anything else. */
.panorama-palette {
  width: 172px; flex: 0 0 auto; overflow-y: auto; padding: 10px;
  border-right: 1px solid var(--border); background: var(--surface); font-size: 12px;
}
.panorama-palette h3 { margin: 0 0 8px; font-size: 12px; }
.panorama-palette h4 {
  margin: 10px 0 4px; font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--muted);
}
.panorama-palette-item {
  display: block; width: 100%; text-align: left; margin-bottom: 3px; padding: 4px 6px;
  border: 1px solid var(--border); border-radius: 4px; background: var(--bg);
  color: var(--text); font-size: 11px; cursor: pointer;
}
.panorama-palette-item:hover { border-color: var(--accent); }
.panorama-palette-empty { padding: 8px; }
.panorama-subset-limits { margin-top: 12px; font-size: 10px; color: var(--muted); }
.panorama-subset-limits summary { cursor: pointer; }
.panorama-subset-limits ul { margin: 6px 0 0; padding-left: 14px; display: flex; flex-direction: column; gap: 5px; }
.panorama-subset-limits b { color: var(--text); }
.panorama-connect-row {
  display: flex; align-items: center; gap: 6px; margin-bottom: 5px; font-size: 12px;
}
.panorama-connect-row > span:first-child { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.panorama-connect-row select { font-size: 11px; max-width: 120px; }
.panorama-status.panorama-dirty { color: var(--warn); font-weight: 600; }
/* Historical context from stores outside Atlas (ADR-0189 P5b). A bar strip, not a
   chart: a smooth line with axes would promise a continuity that a query against
   somebody else's retention does not have, and this stays a correlation surface. */
.panorama-ctx {
  border-left: 3px solid var(--border-strong); padding: 6px 0 6px 9px;
  margin: 0 0 10px; font-size: 12px; display: flex; flex-direction: column; gap: 4px;
}
.panorama-ctx:last-of-type { margin-bottom: 0; }
.panorama-ctx-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.panorama-ctx code { font-size: 11px; overflow-wrap: anywhere; }
.panorama-ctx p { margin: 0; }
.panorama-ctx-available { border-left-color: var(--ok); }
.panorama-ctx-empty, .panorama-ctx-not-configured, .panorama-ctx-unidentifiable { border-left-color: var(--border-strong); }
.panorama-ctx-unreachable, .panorama-ctx-refused { border-left-color: var(--warn); }
.panorama-measure { margin-top: 6px; display: flex; flex-direction: column; gap: 3px; }
.panorama-measure-head { display: flex; justify-content: space-between; gap: 8px; }
.panorama-spark {
  display: flex; align-items: flex-end; gap: 1px; height: 34px;
  background: var(--surface); border-radius: 3px; padding: 2px; overflow: hidden;
}
.panorama-spark-bar { flex: 1 1 0; min-width: 1px; background: var(--accent); border-radius: 1px; }
.panorama-ctx-limits { margin-top: 10px; font-size: 11px; color: var(--muted); }
.panorama-ctx-limits summary { cursor: pointer; }
.panorama-ctx-limits ul { margin: 6px 0 0; padding-left: 16px; display: flex; flex-direction: column; gap: 5px; }
.panorama-ctx-limits b { color: var(--text); }
.panorama-kv { display: grid; grid-template-columns: 82px minmax(0, 1fr); gap: 8px; align-items: start; margin: 8px 0; font-size: 12px; }
.panorama-kv span { color: var(--muted); }
.panorama-kv b, .panorama-kv code { min-width: 0; overflow-wrap: anywhere; }
.panorama-kv code { font-size: 11px; }
.panorama-no-view { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; color: var(--muted); }
.panorama-no-view > div { color: var(--accent); font-size: 38px; }
.panorama-no-view h2 { color: var(--text); margin: 8px 0 0; }
.panorama-no-view p { margin: 6px 0; }
.panorama-problems { cursor: default; }
@media (max-width: 820px) {
  .panorama-properties { display: none; }
  .panorama-status { display: none; }
  .panorama-editor .crumb-current { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* ---------- FEEL editor ---------- */
/* A textarea layered over a highlighted <pre> that mirrors its text and scroll
   (see feel.js). Both layers MUST share identical box metrics so the coloured
   backdrop lines up under the transparent caret; keep font/padding/wrapping in
   sync if you touch either. */
/* Shared buildless code editor (code-editor.js) — the surface behind FEEL, the
   fx expression toggle, and the polyglot script fields (PowerShell/Python/JS). */
.code-editor { position: relative; border: 1px solid var(--border-strong); border-radius: 6px; background: var(--surface); }
.code-editor:focus-within { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.code-editor .ce-input,
.code-editor .ce-highlight {
  margin: 0; padding: 8px 10px;
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: normal; tab-size: 2;
  white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;
  border: 0;
}
.code-editor .ce-input {
  position: relative; z-index: 2; display: block; width: 100%; min-height: 2.9em;
  background: transparent; color: transparent; caret-color: var(--text);
  resize: vertical; outline: none; overflow: auto;
}
/* A selection keeps the text area's text transparent. Without this rule the browser
   paints selected text in its own selection colour (Chromium on Windows and Linux
   does), so the plain text appears on top of the coloured copy below it. The tint
   is translucent, so the coloured text reads through it. */
.code-editor .ce-input::selection { color: transparent; background: rgba(11, 92, 255, 0.22); }
.code-editor .ce-highlight {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  overflow: hidden; color: var(--text);
}
.code-editor .ce-highlight code { font: inherit; }
/* The FEEL '=' expression marker (fx toggle), shown as a dimmed, read-only prefix —
   it can't be edited or deleted, only cleared by switching fx back off. */
.code-editor .ce-locked { color: var(--muted); opacity: 0.7; }
/* Hidden measuring twin used to locate the caret for popup placement. */
.code-editor .ce-mirror {
  visibility: hidden; height: auto; overflow: hidden;
  top: 0; left: 0; z-index: -1;
}
/* Script fields don't wrap — long lines scroll horizontally so line numbers and
   error markers align one-to-one with logical lines. */
.code-editor.nowrap .ce-input,
.code-editor.nowrap .ce-highlight { white-space: pre; word-break: normal; overflow-wrap: normal; }
.code-editor.nowrap .ce-highlight { overflow: hidden; }
/* Line-number gutter. */
.code-editor.has-gutter .ce-input,
.code-editor.has-gutter .ce-highlight { padding-left: calc(var(--ce-gutter, 2ch) + 18px); }
.code-editor .ce-gutter {
  position: absolute; top: 0; left: 0; bottom: 0; z-index: 1;
  width: calc(var(--ce-gutter, 2ch) + 14px); overflow: hidden;
  padding: 8px 6px 8px 0; box-sizing: border-box;
  background: var(--bg); border-right: 1px solid var(--border);
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--muted);
  text-align: right; user-select: none;
}
.code-editor .ce-ln { height: 1.5em; }
/* Virtual drawing (code-editor.js opts.virtual): the backdrop and the gutter hold only
   the lines in view and are moved onto the text area's lines by a transform, which an
   inline <code> would ignore. */
.code-editor.virtual .ce-highlight code { display: block; will-change: transform; }
.code-editor.virtual .ce-gutter-lines { will-change: transform; }
.code-editor .ce-ln.err { color: var(--danger); font-weight: 700; }
/* Error markers: a translucent band behind the offending line. */
.code-editor .ce-markers { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.code-editor .ce-marker.err {
  position: absolute; left: 0; right: 0;
  background: rgba(207, 34, 46, 0.12); box-shadow: inset 2px 0 0 var(--danger);
}

/* Token colours — a light, GitHub-like FEEL palette. */
.tok-comment  { color: #6e7781; font-style: italic; }
.tok-string   { color: #0a3069; }
.tok-number   { color: #0550ae; }
.tok-keyword  { color: #cf222e; }
.tok-literal  { color: #0550ae; }
/* pointer-events:auto lets elementsFromPoint see the builtin span through the
   textarea on top (the rest of the backdrop stays pointer-transparent), which is
   how the signature tooltip hit-tests a hovered builtin. */
.tok-builtin  { color: #8250df; pointer-events: auto; }
.tok-cmdlet   { color: #8250df; pointer-events: auto; }
.tok-variable { color: #953800; }
.tok-punct    { color: #57606a; }
/* PowerShell-specific token colours (also plain-language friendly). */
.tok-operator  { color: #cf222e; }
.tok-parameter { color: #0550ae; }
.tok-type      { color: #116329; }
.tok-member    { color: var(--text); }
.tok-text      { color: var(--text); }
.tok-error     { color: var(--danger); text-decoration: wavy underline; }
/* Markdown: structure markers stand out, prose stays plain (dev-lang.js). */
.tok-md-head  { color: #0550ae; font-weight: 700; }
.tok-md-bold  { color: var(--text); font-weight: 700; }
.tok-md-em    { color: var(--text); font-style: italic; }
.tok-md-code  { color: #0a7c5a; }
.tok-md-link  { color: var(--accent); }
.tok-md-list  { color: #cf222e; }
.tok-md-quote { color: #6e7781; }
.tok-md-rule  { color: #6e7781; }
/* HTML fragments (dev-lang.js). */
.tok-html-tag    { color: #116329; }
.tok-html-attr   { color: #8250df; }
.tok-html-entity { color: #953800; }
/* Template tags — {{ }}, {{#if }}, {{/loop}} — around the FEEL they hold (dev-lang.js). */
.tok-tpl-tag     { color: #0550ae; font-weight: 700; }

/* Completion popup. */
.feel-pop {
  position: absolute; z-index: 30; min-width: 200px; max-width: 340px; max-height: 220px;
  overflow: auto; background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: 6px; box-shadow: 0 6px 24px rgba(20,22,26,0.18); padding: 4px;
}
.feel-opt {
  display: flex; align-items: baseline; gap: 8px; padding: 4px 6px; border-radius: 4px;
  cursor: pointer; font: 12px/1.4 ui-monospace, monospace; white-space: nowrap;
}
.feel-opt.active { background: var(--accent-soft); }
.feel-opt-kind {
  flex: 0 0 auto; width: 24px; text-align: center; font-size: 10px; font-weight: 700;
  text-transform: uppercase; color: #fff; background: var(--muted); border-radius: 3px; padding: 1px 0;
}
.feel-opt-kind.k-function { background: #8250df; }
.feel-opt-kind.k-cmdlet   { background: #8250df; }
.feel-opt-kind.k-variable { background: #953800; }
.feel-opt-kind.k-keyword  { background: #cf222e; }
.feel-opt-kind.k-operator { background: #cf222e; }
.feel-opt-kind.k-literal  { background: #0550ae; }
.feel-opt-kind.k-parameter { background: #0550ae; }
.feel-opt-kind.k-type     { background: #116329; }
.feel-opt-kind.k-env      { background: #6e7781; }
.feel-opt-label { flex: 0 0 auto; color: var(--text); }
.feel-opt-detail { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; color: var(--muted); text-align: right; }

/* Live-validation state: a red border while the expression doesn't compile, and
   the compiler's message below the field. */
.code-editor.invalid { border-color: var(--danger); }
.code-editor.invalid:focus-within { border-color: var(--danger); box-shadow: 0 0 0 2px #fdecea; }
.feel-status { font-size: 11px; color: var(--danger); margin: 4px 0 0; white-space: pre-wrap; word-break: break-word; }

/* Signature tooltip shown when hovering a builtin function name. */
.feel-tip {
  position: absolute; z-index: 40; max-width: 240px;
  background: #14161a; color: #fff; border-radius: 6px; padding: 6px 8px;
  box-shadow: 0 6px 20px rgba(20,22,26,0.28); pointer-events: none;
}
.feel-tip-sig { font: 12px/1.4 ui-monospace, monospace; font-weight: 700; }
.feel-tip-doc { margin-top: 3px; font-size: 11px; color: #cdd0d6; }

/* A short hint under a FEEL field pointing at the completion shortcut. */
.feel-hint { font-size: 11px; color: var(--muted); margin: 4px 0 0; }
.feel-hint kbd {
  font: 11px ui-monospace, monospace; background: var(--bg); border: 1px solid var(--border-strong);
  border-bottom-width: 2px; border-radius: 4px; padding: 0 4px;
}
/* An inline text button (e.g. the FEEL "Test" toggle) styled as a link. */
.linklike { background: none; border: 0; padding: 0; font: inherit; color: var(--accent); cursor: pointer; }
.linklike:hover { text-decoration: underline; }

/* Camunda-style fx switch on a value-or-expression field (attachExpressionToggle):
   a small pill in the field label, highlighted when the field holds a FEEL
   expression. */
.fx-toggle {
  float: right; margin: -1px 0 0 6px; padding: 1px 6px; cursor: pointer;
  font: italic 700 11px/1.5 ui-monospace, monospace; color: var(--muted);
  background: none; border: 1px solid var(--border-strong); border-radius: 4px;
}
.fx-toggle:hover { border-color: var(--accent); color: var(--accent); }
.fx-toggle.active { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }

/* "Test expression" panel: sample variables + Run + the evaluated result. */
.feel-test { margin: 4px 0 0; }
.feel-test-vars { font-size: 12px; }
.feel-test-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.feel-test-run { padding: 4px 12px; font-size: 12px; }
.feel-test-out { font: 12px/1.4 ui-monospace, monospace; word-break: break-word; }
.feel-test-out.ok { color: var(--ok); }
.feel-test-out.err { color: var(--danger); }
/* Multi-line result/error stream for a script Run (enhanceScript). */
.ps-run-detail {
  margin: 6px 0 0; padding: 8px 10px; max-height: 220px; overflow: auto;
  border-radius: 6px; background: var(--bg); border: 1px solid var(--border);
  font: 12px/1.5 ui-monospace, monospace; white-space: pre-wrap; word-break: break-word;
}
.ps-run-detail.err { color: var(--danger); border-color: var(--danger); background: #fff5f5; }

/* ---------- JSON structure editor (json-editor.js) ---------- */
.json-editor {
  position: relative; border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); margin: 4px 0 0;
}
.json-editor:focus-within { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.json-editor .json-input,
.json-editor .json-highlight {
  font: 12px/1.4 ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  padding: 8px 10px; margin: 0; border: 0; background: transparent; white-space: pre-wrap;
  word-wrap: break-word; overflow-wrap: break-word;
}
.json-editor .json-input {
  position: relative; z-index: 1; display: block; width: 100%; resize: vertical;
  color: transparent; caret-color: var(--text); outline: none; -webkit-text-fill-color: transparent;
}
/* As in the code editor: a selection keeps the text area's text transparent, under a
   tint the coloured text reads through. */
.json-editor .json-input::selection { color: transparent; -webkit-text-fill-color: transparent; background: rgba(11, 92, 255, 0.22); }
.json-editor .json-highlight {
  position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 0;
}
.json-editor .json-highlight code { font: inherit; }
.json-editor.invalid { border-color: var(--danger); }
.json-editor.invalid:focus-within { border-color: var(--danger); box-shadow: 0 0 0 2px #fdecea; }
.json-status { font-size: 11px; color: var(--danger); margin: 4px 0 0; white-space: pre-wrap; word-break: break-word; }

/* Compact mode: a tidy one-liner when empty/unfocused, growing to a comfortable,
   vertically resizable box once it holds (or gains) content — so a nested array is
   actually editable inline, with the ⤢ button for the full modal beyond that. */
.json-editor.compact .json-input { resize: none; min-height: 34px; height: 34px; transition: height 0.15s; }
/* Both layers reserve the toolbar's room: on the text area alone, the coloured copy
   broke its lines 46px later than the text did, under the buttons. */
.json-editor.compact .json-input,
.json-editor.compact .json-highlight { padding-right: 56px; }
.json-editor.compact.expanded .json-input { height: auto; min-height: 108px; resize: vertical; }

/* Toolbar floats in the top-right corner (a row, so the format + expand buttons sit
   within a collapsed field's height; the input reserves right padding for them). */
.json-toolbar {
  position: absolute; top: 4px; right: 4px; z-index: 2;
  display: flex; flex-direction: row; gap: 3px;
}
.json-toolbar .icon-btn {
  width: 24px; height: 24px; font-size: 11px; opacity: 0.55;
  background: var(--surface); border: 1px solid var(--border); border-radius: 5px;
}
.json-toolbar .icon-btn:hover { opacity: 1; color: var(--accent); border-color: var(--accent); }

/* Expand-to-modal: a full-size JSON editor for comfortable editing of big values. */
.json-modal-overlay {
  position: fixed; inset: 0; z-index: 70; padding: 24px;
  background: rgba(20, 22, 26, 0.5); display: flex; align-items: center; justify-content: center;
}
.json-modal {
  width: min(760px, 100%); max-height: 86vh; display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 20px 60px rgba(20, 33, 61, 0.35);
}
.json-modal-head { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.json-modal-head strong { font-size: 14px; }
.json-modal-body { padding: 14px; background: var(--bg); overflow: auto; flex: 1; }
.json-modal-body .json-editor { margin: 0; width: 100%; }
.json-modal-body .json-input { min-height: min(52vh, 440px); resize: vertical; }

/* JSON token colours — a light palette consistent with the FEEL editor. */
.jtok-key      { color: #953800; }
.jtok-string   { color: #0a3069; }
.jtok-number   { color: #0550ae; }
.jtok-literal  { color: #0550ae; font-weight: 600; }
.jtok-punct    { color: #57606a; }
.jtok-error    { color: var(--danger); text-decoration: wavy underline; }

/* ---------- Start-variable declaration editor (Process panel) ---------- */
#sv-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 4px; }
.sv-row { display: flex; align-items: center; gap: 6px; }
.sv-row .sv-name, .sv-row .sv-default { flex: 2 1 0; min-width: 0; padding: 6px 8px; font-size: 12px; }
.sv-row .sv-type, .sv-row .sv-source { flex: 0 0 auto; width: auto; padding: 6px 6px; font-size: 12px; }
.sv-req { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; color: var(--muted); white-space: nowrap; flex: 0 0 auto; margin: 0; }
.sv-req input { width: auto; }
.sv-del { flex: 0 0 auto; width: 26px; height: 26px; font-size: 12px; }
.sv-del:hover { color: var(--danger); background: #fdecea; }
/* JSON-typed declaration rows stack vertically so the editor has room. */
.sv-row-json { flex-wrap: wrap; }
.sv-row-json .sv-json-default { flex: 1 1 100%; font: 12px/1.4 ui-monospace, monospace; padding: 6px 8px; resize: vertical; min-height: 48px; }
/* Required marker shared by the declaration editor and the typed Deploy form. */
.req-star { color: var(--danger); font-weight: 700; }

/* bpmn-js watermark must remain visible (license). Just ensure it isn't clipped. */
.bjs-powered-by { z-index: 5; }

/* ---------- Live overlay ---------- */
/* In the live view the diagram is read-only: hide editing affordances. */
.live .djs-palette, .live .djs-context-pad, .live .djs-direct-editing-parent { display: none !important; }
/* Elements are highlighted in two states: green where tokens live right now,
   gray where tokens have only passed through (the history heatmap). */
.djs-element.atlas-active .djs-visual > :nth-child(1) {
  stroke: var(--ok) !important; stroke-width: 2.5px !important;
}
.djs-element.atlas-visited .djs-visual > :nth-child(1) {
  stroke: var(--muted) !important; stroke-width: 2px !important;
  stroke-dasharray: 4 3 !important;
}
/* A catch event armed by an event-based gateway: waiting, but not the place the wait
   is counted — the race is one wait however many branches it has, and it is counted
   once, on the gateway. Green like a live element because it is live, dashed because
   the token here is a branch of a race and not a token that got this far on its own.
   Both runtime views draw it: the live overlay's count and the step replay's token dot
   sit on the gateway, and the branches carry this outline in either (ADR-0249). */
.djs-element.atlas-armed .djs-visual > :nth-child(1) {
  stroke: var(--ok) !important; stroke-width: 2.5px !important;
  stroke-dasharray: 5 4 !important;
}
/* Each element carries up to three counts side by side, reading past → present: gray
   (completed here and moved on), amber (cancelled here), green (live here now). */
.token-badges { display: inline-flex; gap: 3px; align-items: center; pointer-events: none; }
.token-badge {
  background: var(--ok); color: #fff; border-radius: 999px;
  min-width: 20px; height: 20px; padding: 0 6px;
  font: 700 12px/20px system-ui, sans-serif; text-align: center;
  box-shadow: 0 1px 4px rgba(0,0,0,0.35); pointer-events: none;
}
/* History badge: a token that completed here and flowed on. */
.token-badge.history { background: var(--muted); }
/* Cancelled badge: a token that arrived and was then terminated — a losing branch of an
   event-based gateway, an activity a boundary event interrupted, a scope torn down. Amber,
   not the incident red: nothing here is broken or waiting for an operator. */
.token-badge.cancelled { background: var(--warn); }
/* Each of the three counts can be switched off from the legend (see mountLive). The class
   lands on the canvas, so the poll that rebuilds the overlays cannot undo it, and the
   legend — which lives outside the canvas — keeps showing all three samples: they are the
   switches. The live badge carries no modifier of its own, so it is the one that is
   neither gray nor amber. */
.badges-hide-passed .token-badge.history,
.badges-hide-cancelled .token-badge.cancelled,
.badges-hide-live .token-badge:not(.history):not(.cancelled) { display: none; }
/* The element the instance list is filtered to (ADR-0261).
   It is drawn in the accent colour rather than in any of the token colours: green,
   gray and amber all say something about where tokens are, and this says something
   about what the panel beside the diagram is showing. Solid and thick so it reads as
   the one element under discussion, whatever its token state happens to be. */
.djs-element.atlas-filtered .djs-visual > :nth-child(1) {
  stroke: var(--accent) !important; stroke-width: 3px !important;
}
/* An element whose token is parked behind an incident (ADR-0061) is drawn red over
   its live-token green, and carries a badge with the reason. Without it a stuck task
   is pixel-identical to one that is legitimately waiting, which is exactly how a
   failing process comes to look like a running one (ADR-0150). */
.djs-element.atlas-incident .djs-visual > :nth-child(1) {
  stroke: var(--danger) !important; stroke-width: 3px !important;
}
/* The Live view's note that a deployed definition's drawing was adjusted after it was
   deployed (ADR-0251). Quiet on purpose: it is a caveat about
   the picture, not a state of the process. */
.live-layout-note { margin-left: 8px; font-size: 11px; }

/* The three annotation badges below are deliberately the size of the token count and
   not of the word they used to spell out (ADR-0252).
   A pill wide enough for "⚠ 2 incidents" is 90px, and 90px is most of a task's width
   or three times an event's: wherever it was anchored it came down on a caption. The
   glyph identifies the badge, the count is the only text that varies, and the words
   live in the tooltip, the aria-label and the panel that lists the thing. They widen
   past a circle only when they carry a number. */
.incident-badge {
  display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  white-space: nowrap;
  background: var(--danger); color: #fff; border-radius: 999px;
  min-width: 20px; height: 20px; padding: 0 4px; box-sizing: border-box;
  font: 700 11px/20px system-ui, sans-serif;
  box-shadow: 0 1px 4px rgba(0,0,0,0.3); pointer-events: none;
}
/* "Open task" badge on a user-task element that has a waiting job: a one-click jump
   from the diagram to that task's form in the Tasks app. */
.task-open {
  display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  white-space: nowrap;
  background: var(--accent, #2563eb); color: #fff; border-radius: 999px;
  min-width: 20px; height: 20px; padding: 0 4px; box-sizing: border-box;
  text-decoration: none;
  font: 700 11px/20px system-ui, sans-serif;
  box-shadow: 0 1px 4px rgba(0,0,0,0.3); cursor: pointer;
}
.task-open:hover { filter: brightness(1.08); }
.task-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Drill-down from a call activity's replay into the child instance it started
   (ADR-0076). On the diagram it is an invisible click hotspot sized over the "+"
   call-activity marker — the marker itself becomes the target, with no extra
   emblem; a faint highlight on hover signals it is clickable. In the Details panel
   it is a plain text link. */
/* Drill-in badge on a call activity whose child instance is known: the one control on
   a replayed shape, so it is filled rather than outlined and is always visible. Its
   predecessor was a transparent hotspot over the "+" marker that only revealed itself
   on hover, which meant operators did not find it at all. */
.ca-child-btn {
  display: grid; place-items: center; width: 20px; height: 20px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink); text-decoration: none; cursor: pointer;
  font: 700 13px/1 system-ui, sans-serif; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.ca-child-btn:hover { filter: brightness(1.1); }
.ca-child-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ca-child-link { color: var(--accent, #2563eb); font-weight: 600; text-decoration: none; }
.ca-child-link:hover { text-decoration: underline; }

/* The drill-in cue on a call activity (ADR-0245), in two parts. The ring appears while
   the *shape* is hovered: it says "there is a way in here" and says it silently, which
   is what the invisible hotspot this replaced never managed. The label belongs to the
   14px marker rather than the shape around it, so it appears only once the pointer is
   on the ring — a sentence that follows every pass over a task is noise, and by then
   the reader is one double-click from the callee's own name anyway.
   Pointer-events off on both: the double-click is handled on the canvas, and a div
   sitting over the marker would swallow it. */
.djs-overlay-atlas-call-drill { pointer-events: none; transform-origin: 0 0; }
.ca-drill-ring {
  display: block; width: 26px; height: 26px; margin: -13px 0 0 -13px; box-sizing: border-box;
  /* No fill: the ring frames the "+" it is pointing at rather than covering it, and
     what it encloses is exactly the area the double-click is accepted in. */
  border: 2px solid var(--accent); border-radius: 7px; background: transparent;
}
/* Under the pointer it reads as a button rather than an outline — with a halo around
   it, never a fill: the "+" it frames is the thing being pointed at. */
.ca-drill-ring.on { box-shadow: 0 0 0 4px var(--accent-soft); }
.ca-drill-tip {
  display: block; width: max-content; transform: translateX(-50%); white-space: nowrap;
  background: var(--accent); color: var(--accent-ink); border-radius: 999px; padding: 1px 7px;
  font: 600 10.5px/1.5 system-ui, sans-serif; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
/* "Decision" badge on a business rule task that has been evaluated: opens the
   decision-inspection panel (inputs, outputs, and how it was decided; ADR-0064). */
.decision-badge {
  display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  white-space: nowrap;
  background: #6b3fd4; color: #fff; border: none; border-radius: 999px;
  min-width: 20px; height: 20px; padding: 0 4px; box-sizing: border-box;
  cursor: pointer;
  font: 700 11px/20px system-ui, sans-serif;
  box-shadow: 0 1px 4px rgba(0,0,0,0.3);
}
.decision-badge:hover { filter: brightness(1.1); }
.decision-badge.on { outline: 2px solid #fff; box-shadow: 0 0 0 2px #6b3fd4; }


/* Decision-evaluation detail (Operations): inputs as typed pills, results tagged
   with the rule that fired, and the matched-rule decision table revealed on hover —
   mirroring temis' Operate view. Semantic colours (green fired-rule/ok, red no-match,
   blue hit row) are deliberate and separate from the app accent. */
.in-pills { display: flex; flex-wrap: wrap; gap: 4px; }
.pill-kv { font: 12px/1.5 ui-monospace, monospace; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 1px 7px; white-space: nowrap; }
.pill-kv b { color: var(--muted); font-weight: 600; }
.res { display: flex; flex-direction: column; gap: 3px; min-width: 190px; }
/* The name, the answer and the rule that produced it read as one line, left to
   right. This was a three-column grid with the value right-aligned, which is fine
   in a narrow table cell and wrong everywhere else: in the replay panel it threw
   the answer against the far edge of a full-width card, a hand's width from the
   name it answers to, with white space in between and nothing to tie them. A
   result is a phrase — "entscheid = abgelehnt" — so it is set as one. */
.res-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; font-size: 12.5px; padding: 2px 4px; border-radius: 5px; }
.res-row.hoverable { cursor: help; }
.res-row.hoverable:hover, .res-row.hoverable:focus-visible { background: var(--accent-soft); outline: none; }
.res-row.hoverable .res-key::after { content: "\229e"; margin-left: 5px; color: var(--accent); font-weight: 700; opacity: 0.8; }
.res-key { color: var(--muted); font-weight: 600; white-space: nowrap; }
/* The answer carries the emphasis, not its label: it is the thing being read. */
.res-val {
  font: 600 12px/1.5 ui-monospace, monospace; color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: 5px;
  padding: 0 7px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.res-rule { font: 600 10px/1.4 ui-monospace, monospace; color: #15803d; background: #dcfce7; padding: 1px 7px; border-radius: 999px; }

/* The hover popover: a frosted card holding the matched-rule table. */
.dec-pop {
  position: fixed; z-index: 60; pointer-events: none; max-width: 480px;
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--border-strong); border-radius: 10px; padding: 9px 11px;
  box-shadow: 0 10px 30px rgba(20, 33, 61, 0.22);
}
.dec-pop[hidden] { display: none; }
.pop-title { font: 700 12px/1 system-ui, sans-serif; margin-bottom: 6px; color: var(--text); }
.mtable { margin-top: 6px; }
.mtable-head { display: flex; align-items: center; gap: 8px; font: 600 11px/1 system-ui, sans-serif; margin-bottom: 4px; color: var(--text); }
.mtable-policy { font: 10px/1 ui-monospace, monospace; background: var(--bg); color: var(--muted); padding: 2px 5px; border-radius: 5px; }
.mgrid { border-collapse: collapse; font-size: 11px; }
.mgrid th, .mgrid td { border: 1px solid var(--border); padding: 2px 6px; text-align: left; white-space: nowrap; color: var(--text); }
.mgrid th { background: var(--bg); font-weight: 600; }
.mgrid code { font: 11px/1 ui-monospace, monospace; }
.mcol-idx { width: 20px; text-align: center; color: var(--muted); }
/* The rule that fired is green, and so is a condition that held — one colour, one
   meaning ("the case satisfied this"), at two strengths. It used to be blue, which
   made the row that carried the result a third thing to learn on a grid that is
   already saying two. The row is the stronger green and carries its output in bold,
   so a rule whose conditions all held but which the hit policy passed over — the
   thing an author is usually squinting at — still reads apart from the one that
   won. */
.mrule.is-hit { background: #dcfce7; }
.mrule.is-hit td { border-color: #86efac; }
.mrule.is-hit .mcol-idx { color: #15803d; font-weight: 700; }
.mrule.is-hit .mout { color: #14532d; font-weight: 700; }
.mcell.is-ok { color: #166534; background: rgba(22, 163, 74, 0.08); }
.mcell.is-no { color: #b91c1c; }
.mcell.is-skip { color: #c3cad6; }
.mout { font: 11px/1.2 ui-monospace, monospace; }

.legend-swatch {
  display: inline-block; width: 22px; height: 0;
  border-top: 2.5px solid var(--ok); vertical-align: middle; margin-right: 4px;
}
.legend-swatch.history { border-top: 2px dashed var(--muted); }
.legend-swatch.armed { border-top: 2.5px dashed var(--ok); }
.legend-swatch.incident { border-top-color: var(--danger); }

/* A legend entry that is also a switch: the Live view's three count badges turn their own
   number on the diagram off and on. It has to read as part of the legend line rather than
   as a button — hence the bar's own type and colour, and no chrome until the pointer is
   on it — and to say which way it is thrown: dimmed while its count is hidden. */
.legend-toggle {
  display: inline-flex; align-items: center; gap: 4px;
  border: 0; background: none; padding: 1px 4px; margin: 0;
  font: inherit; color: inherit; line-height: inherit; cursor: pointer; border-radius: 4px;
}
.legend-toggle:hover { background: var(--surface); color: var(--text); }
.legend-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.legend-toggle[aria-pressed="false"] { opacity: 0.4; }

/* The legend under a runtime diagram is a key, not a status line: it has to stay readable
   at whatever width the window happens to be. The bar it shares with the Modeler's problem
   list is one fixed-height row, which on a narrow screen broke every entry across two or
   three lines and then clipped them at 30px — "completed here / and moved on" cut in half
   under the border. So here an entry never breaks in the middle; the row wraps between
   entries instead, and the bar grows by whole rows to hold them. On a wide screen nothing
   moves: one row, same height as before. */
.editor.live .problems {
  height: auto; min-height: 30px; flex-wrap: wrap;
  padding-top: 3px; padding-bottom: 3px; row-gap: 4px;
}
.editor.live .problems > * { white-space: nowrap; }

/* The incident block that leads the live variables panel: what is parked, why, and
   the resolve. Red-on-red so it reads as a problem at a glance, and never collapses
   a message — an operator needs the provider's own words to act on them. */
.vp-incidents {
  border: 1px solid var(--danger); border-radius: 8px;
  padding: 8px 10px; margin-bottom: 10px; background: #fdecea;
}
.vp-incidents .vp-title { color: var(--danger); }
.vp-incidents .inc-row + .inc-row { border-top: 1px solid rgba(0,0,0,0.09); margin-top: 8px; padding-top: 8px; }
.vp-incidents .inc-where { font-size: 12px; }
/* The incident row's actions, beside a diagram rather than in a table: the live view's
   variables panel is user-resizable and the replay's Details tab is narrow, so three
   buttons have to wrap onto a second line instead of pushing the panel wider than the
   operator made it (ADR-0163). */
.vp-incidents .inc-actions { display: flex; flex-wrap: wrap; gap: 4px; }
.vp-incidents .inc-msg {
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow-wrap: anywhere; margin: 3px 0 6px;
}
/* The replay's Details tab renders the very same block (ADR-0151) — one incident
   vocabulary across both diagrams — so it needs no rules of its own. */
/* Inline version/instance pickers in the live toolbar. */
.bar-select { display: inline-flex; align-items: center; gap: 6px; margin-left: 10px; }
.bar-select > span { font-size: 12px; color: var(--muted); }
.bar-toggle {
  display: inline-flex; align-items: center; gap: 5px; margin-left: 10px;
  font-size: 12px; color: var(--muted); white-space: nowrap; cursor: pointer;
}
.bar-select select { width: auto; min-width: 96px; max-width: 240px; padding: 5px 8px; font-size: 13px; }

/* Variables panel. In the replay view it sits beneath the diagram (a wide strip);
   in the Live view it is a collapsible, resizable side panel (.side) mirroring the
   Modeler's properties, so operators can read and copy long values with room. */
.var-panel {
  border-top: 1px solid var(--border); background: var(--surface);
  padding: 12px 16px; max-height: 30vh; overflow: auto; flex: 0 0 auto;
}
/* Side variant (Live view): a right-hand column, full editor-body height, its width
   draggable via #var-resizer. */
.var-panel.side {
  border-top: 0; border-left: 1px solid var(--border);
  width: 340px; max-height: none; height: 100%;
  padding: 0 0 12px; flex: 0 0 auto;
}
.var-panel.side .vp-head {
  position: sticky; top: 0; z-index: 1; margin: 0; padding: 12px 14px 10px;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.var-panel.side .vp-tasks, .var-panel.side .vars { padding: 12px 14px 0; }
.var-panel.side > p.muted { padding: 12px 14px; margin: 0; }
.var-panel.side .vp-table { width: auto; margin: 4px 14px 0; }
.var-panel.side .vp-insts { padding: 12px 14px; }
/* In a narrow side column chips wrap their text instead of forcing a scroll. */
.var-panel.side .vp-inst-vars .chip { white-space: normal; word-break: break-word; }

/* "All instances" overview as stacked cards (fits the narrow side column, where a
   wide table would overflow). */
.var-panel .vp-insts { display: flex; flex-direction: column; gap: 8px; }
.var-panel .vp-inst {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); padding: 8px 10px;
}
.var-panel .vp-inst-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.var-panel .vp-inst-head > b { font: 600 12px/1.4 ui-monospace, monospace; }
.var-panel .vp-inst-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
.var-panel .vp-inst-vars { margin-top: 6px; }
.var-panel .vp-inst-vars:empty { display: none; }

/* Operator metadata for an instance — when it started, how long it ran (or has
   been running), and the message correlation key it began with — shown under the
   header in the single-instance detail and inside each overview card. */
.var-panel .vp-meta {
  display: flex; flex-wrap: wrap; gap: 3px 14px; margin-top: 6px;
  font-size: 11px; color: var(--muted); text-transform: none; letter-spacing: 0;
}
.var-panel.side > .vp-meta { padding: 6px 14px 0; }
.var-panel .vp-meta-item { display: inline-flex; align-items: baseline; gap: 5px; }
.var-panel .vp-meta-k {
  text-transform: uppercase; letter-spacing: 0.03em; font-size: 10px; opacity: 0.75;
}
.var-panel .vp-meta code {
  font: 600 11px/1.4 ui-monospace, monospace; color: var(--text);
  background: var(--surface); border: 1px solid var(--border);
  padding: 0 5px; border-radius: 4px;
}
.var-panel .vp-elapsed { font-variant-numeric: tabular-nums; }
/* Collapsed: the panel and its divider fold away, leaving the diagram full-width.
   The toolbar toggle (#vars-toggle) reflects state via aria-pressed. */
.editor.live.vars-collapsed .var-panel.side,
.editor.live.vars-collapsed #var-resizer { display: none; }
#vars-toggle[aria-pressed="false"] { color: var(--muted); }
.var-panel .vp-head {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--muted); margin-bottom: 8px; display: flex; align-items: center;
  gap: 8px; flex-wrap: wrap;
}
.var-panel .vp-title { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.var-panel .vp-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }
.var-panel .vp-actions .replay-link { text-transform: none; letter-spacing: 0; }

/* Find-an-instance row above the listed page. The panel shows one page of a
   version that may hold hundreds of thousands of instances, so this — not the
   list — is how an operator reaches a specific one. */
/* The chip that says what the listing is narrowed to, and the way back out of it
   (ADR-0261). It sits above the search row because it is
   the coarser of the two filters and because an operator who clicked a shape needs
   to see, without hunting, that the short list in front of them is a filtered one
   and not an empty process. */
.var-panel .vp-filter {
  display: flex; align-items: center; gap: 6px; margin-bottom: 8px;
  padding: 4px 6px 4px 10px; border-radius: var(--radius);
  background: var(--accent-soft); border: 1px solid var(--accent);
  font-size: 12px;
}
.var-panel .vp-filter b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.var-panel .vp-filter-k { color: var(--muted); }
.var-panel .vp-filter .btn { margin-left: auto; }
.var-panel .vp-search { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.var-panel .vp-search-q {
  flex: 1 1 auto; min-width: 0; padding: 4px 8px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); color: inherit; font: inherit; font-size: 12px;
}
.var-panel .vp-search-q:focus { outline: none; border-color: var(--accent); }
.var-panel .vp-search-err { margin: -4px 0 8px; font-size: 12px; color: var(--danger); }
/* An archive note is information, not a failure: it explains where a row came from,
   or why purged history was not searched. It reads in the muted voice rather than
   the danger one, which is reserved for something that actually went wrong. */
.var-panel .vp-archive-note { font-size: 12px; line-height: 1.5; }
/* "Load more" walks the newest-first cursor into older instances. */
.var-panel .vp-more { display: flex; justify-content: center; margin-top: 8px; }

/* Copy buttons: one per scalar field, per JSON card, and a "Copy all" in the head.
   A brief green check confirms the write (the ::after swaps in on .copied). Shared
   by the Live view's side panel (.var-panel) and the Operations replay inspector
   (.ops-tab-body), so both surfaces get identical, first-class copy affordances. */
.var-panel .vcopy, .ops-tab-body .vcopy, .var-modal .vcopy {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 22px; height: 22px; padding: 0; border: 1px solid var(--border);
  border-radius: 5px; background: var(--surface); color: var(--muted);
  cursor: pointer; transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.var-panel .vcopy:hover, .ops-tab-body .vcopy:hover, .var-modal .vcopy:hover { color: var(--text); border-color: var(--border-strong); background: var(--bg); }
.var-panel .vcopy.copied, .ops-tab-body .vcopy.copied, .var-modal .vcopy.copied { color: var(--ok, #2d8a4e); border-color: #bfe3cb; background: #eaf7ef; }
.var-panel .vcopy.copied svg, .ops-tab-body .vcopy.copied svg, .var-modal .vcopy.copied svg { display: none; }
.var-panel .vcopy.copied::after, .ops-tab-body .vcopy.copied::after, .var-modal .vcopy.copied::after { content: "\2713"; font: 700 12px/1 system-ui, sans-serif; }
.var-panel .vcopy-all, .ops-tab-body .vcopy-all, .var-modal .vcopy-all {
  width: auto; height: auto; padding: 3px 9px; font-size: 11px;
  display: inline-flex; gap: 4px;
}
.var-panel .vcopy-all svg, .ops-tab-body .vcopy-all svg, .var-modal .vcopy-all svg { display: none; }

/* Waiting-task links: jump from a running instance to its user task's form. */
.var-panel .vp-tasks {
  display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px;
}
.var-panel .vp-tasks-head {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted);
}
.var-panel .task-link {
  display: flex; align-items: center; gap: 8px; text-decoration: none;
  padding: 7px 10px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--text); font-size: 13px;
}
.var-panel .task-link:hover { border-color: var(--accent, var(--border-strong)); background: var(--accent-soft); }
.var-panel .task-link .task-link-name { font-weight: 600; word-break: break-word; }
.var-panel .task-link .task-link-tag {
  font: 600 9px/1.6 system-ui, sans-serif; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 0 5px; border-radius: 4px; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border);
}
.var-panel .task-link .task-link-go { margin-left: auto; color: var(--accent, var(--muted)); font-weight: 600; white-space: nowrap; }
.var-panel .task-link.inline {
  display: inline-flex; padding: 2px 7px; font-size: 12px; gap: 4px;
  margin-right: 6px; vertical-align: middle;
}
/* The resolve dialog (the Incidents table, where the operator picks a retry budget)
   quotes the failure that raised the incident. */
.inc-modal-msg {
  margin: 0 0 10px; padding: 7px 9px; border-radius: var(--radius);
  background: var(--danger-soft); color: var(--danger);
  font: 12px/1.5 ui-monospace, monospace; word-break: break-word;
}
.var-panel .vp-table { width: 100%; }
.var-panel .vp-table td { padding: 6px 10px 6px 0; border-bottom: 1px solid var(--border); vertical-align: top; }
.var-panel .vp-table td:first-child { font: 12px/1.6 ui-monospace, monospace; white-space: nowrap; }
.var-panel .chip { margin: 0 4px 4px 0; }

/* Polished variables presentation, shared by the Live view side panel and the
   Operations replay inspector: scalars as a labeled field grid, JSON values as
   collapsible, syntax-highlighted cards. Scoped to the `.vars` container the body
   is wrapped in (not to a particular panel), so every surface that shows variables
   renders them identically — the replay tab is not a second-class citizen. */
.vars { display: flex; flex-direction: column; gap: 10px; }
.vars .vgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px;
}
.vars .vfield {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); padding: 7px 10px 8px; min-width: 0;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.vars .vfield:hover { border-color: var(--border-strong); box-shadow: var(--shadow); }
.vars .vf-head { display: flex; align-items: center; gap: 6px; }
.vars .vf-head .vcopy { margin-left: auto; width: 20px; height: 20px; opacity: 0; }
.vars .vfield:hover .vf-head .vcopy, .vars .vf-head .vcopy:focus-visible { opacity: 1; }
.vars .vk { font: 600 12px/1.4 ui-monospace, monospace; color: var(--muted); word-break: break-word; }
/* The type badge. One rule for both surfaces that carry one — the replay's variables
   table, and the Modeler's Variables panel, which says the same thing about the same
   variable before anything has run. */
.vars .vtag, .vars-panel .vtag {
  font: 600 9px/1.6 system-ui, sans-serif; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 0 5px; border-radius: 4px; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border); white-space: nowrap;
}
.vars .vv { font: 13px/1.5 ui-monospace, monospace; margin-top: 3px; word-break: break-word; }
/* Scope chip on a subprocess-local variable in the replay variables table (ADR-0074). */
.c-scope {
  font: 600 9px/1.6 system-ui, sans-serif; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 0 5px; margin-left: 6px; border-radius: 4px; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border); white-space: nowrap; vertical-align: middle;
}
/* What a value in the replay variables table names — a person, a process, a product —
   beside the value itself (varrefs.js). A link only where the reader may open it. */
.c-ref {
  display: inline-flex; align-items: center; gap: 4px; margin-left: 6px; max-width: 100%;
  font: 600 11px/1.6 system-ui, sans-serif; white-space: nowrap; vertical-align: middle;
  padding: 0 7px; border-radius: 999px; color: var(--accent); background: var(--accent-soft);
  text-decoration: none; cursor: default;
}
.c-ref svg { flex: none; }
.c-ref .c-ref-name { overflow: hidden; text-overflow: ellipsis; }
a.c-ref { cursor: pointer; }
a.c-ref:hover .c-ref-name { text-decoration: underline; }
.c-ref:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.c-ref.missing { color: var(--muted); background: var(--surface); border: 1px dashed var(--border); font-style: italic; }
.ref-pop { max-width: 340px; }
.ref-pop-kind { font: 600 11px/1.4 system-ui, sans-serif; color: var(--muted); }
.ref-pop-id { font: 11.5px/1.5 ui-monospace, monospace; color: var(--text); margin-top: 2px; word-break: break-all; }
.ref-pop-note, .ref-pop-go { font: 11.5px/1.45 system-ui, sans-serif; color: var(--muted); margin: 6px 0 0; }
.ref-pop-go { color: var(--accent); }
.vars .vv.num  { color: #0a7c5a; }
.vars .vv.str  { color: var(--text); }
.vars .vv.bool { color: #b26a00; font-weight: 600; }
.vars .vv.null { color: var(--muted); font-style: italic; }

.vars .vjson {
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; background: var(--surface);
}
/* The card header is a row: an expand/collapse toggle button that fills the width,
   plus a copy button. (The toggle can't wrap the copy — nested buttons are invalid.) */
.vars .vj-head {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 7px 4px 0; background: var(--bg); border-bottom: 1px solid transparent;
}
.vars .vjson:not(.collapsed) .vj-head { border-bottom-color: var(--border); }
.vars .vj-toggle {
  display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0;
  padding: 3px 10px; background: none; border: 0; cursor: pointer;
  font: inherit; text-align: left; color: var(--text);
}
.vars .vj-head:hover { background: var(--accent-soft); }
.vars .vj-toggle .chev { color: var(--muted); font-size: 11px; transition: transform 0.12s ease; }
.vars .vjson.collapsed .vj-toggle .chev { transform: rotate(-90deg); }
.vars .vj-sum { margin-left: auto; color: var(--muted); font-size: 12px; }
.vars .vj-body {
  margin: 0; padding: 10px 12px; border-top: 1px solid var(--border);
  font: 12.5px/1.55 ui-monospace, monospace; white-space: pre;
  overflow: auto; max-height: 220px; tab-size: 2;
}
.vars .vjson.collapsed .vj-body { display: none; }
.vars .j-key  { color: #7a3e9d; }
.vars .j-str  { color: #0a7c5a; }
.vars .j-num  { color: #0b5cff; }
.vars .j-bool { color: #b26a00; }
.vars .j-null { color: var(--muted); }

/* Start-variables input, revealed under the live toolbar by "Start instance". */
.start-panel {
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  background: var(--surface-2, var(--surface));
}
.start-panel label.field { margin: 0 0 8px; }
.start-panel .row { align-items: center; gap: 8px; }
.start-panel .err { font-size: 12px; color: var(--danger, #c0392b); }

/* Process documentation (ADR-0138): the published versions of one process, newest
   first. Each row is one immutable version — its download and, when someone has
   published it, the revocable public link. */
.doc-history { margin-top: 12px; max-height: 260px; overflow-y: auto; }
.doc-history .doc-empty { margin: 4px 0; font-size: 13px; }
.doc-version {
  padding: 8px 0; border-top: 1px solid var(--border);
}
.doc-version:first-child { border-top: none; }
.doc-version-head { display: flex; align-items: baseline; gap: 8px; font-size: 13px; }
.doc-version-head .muted { font-size: 12px; }
.doc-version .doc-note { margin: 2px 0 4px; font-size: 12px; }
.doc-version .row { gap: 8px; align-items: center; margin-top: 2px; }
.doc-link { font-size: 12px; }
.btn.small { padding: 2px 8px; font-size: 12px; }

/* The Deploy & run confirmation: a success line plus the link that completes the
   deploy→run→observe roundtrip into Operations. */
.deploy-success .deploy-success-msg { margin: 0 0 10px; font-weight: 600; color: var(--ok); }
.deploy-success .row { align-items: center; gap: 8px; }

/* ---------- Collaboration replay ---------- */
/* A message travelling along a message-flow edge: an envelope dot with a soft
   halo, drawn in a dedicated SVG layer above the diagram (ADR-0038). */
.atlas-msg-dot { fill: var(--accent); stroke: #fff; stroke-width: 1.5px; }
.atlas-msg-halo { fill: var(--accent); opacity: 0.18; }
/* The edge a message is crossing right now: accent stroke with marching ants so
   the direction of travel reads at a glance. */
.djs-connection.atlas-flow-active .djs-visual > :nth-child(1) {
  stroke: var(--accent) !important; stroke-width: 2.5px !important;
  stroke-dasharray: 6 4 !important; animation: atlas-ants 0.6s linear infinite;
}
@keyframes atlas-ants { to { stroke-dashoffset: -10; } }
/* The receiving element pulses as a message lands on it. */
.djs-element.atlas-flow-hit .djs-visual > :nth-child(1) {
  stroke: var(--accent) !important; stroke-width: 3px !important;
  animation: atlas-pulse 0.7s ease-out;
}
@keyframes atlas-pulse { 0% { stroke-width: 1px; } 40% { stroke-width: 4px; } 100% { stroke-width: 2px; } }

/* Replay transport bar under the toolbar: play/scrub controls + a clock. */
.replay-bar {
  display: flex; align-items: center; gap: 12px; padding: 10px 16px;
  border-bottom: 1px solid var(--border); background: var(--surface);
}
.replay-bar .btn.play { min-width: 84px; }
.replay-bar input[type="range"] { flex: 1; accent-color: var(--accent); }
.replay-bar .clock { font: 12px ui-monospace, monospace; color: var(--muted); min-width: 150px; text-align: right; }
.replay-bar .speed { width: auto; min-width: 64px; padding: 5px 8px; font-size: 13px; }
.token-legend { display:flex; flex-wrap:wrap; gap:8px; padding:8px 12px; border-top:1px solid var(--border); }
.token-chip { display:inline-flex; align-items:center; gap:6px; font-size:12px; }
.token-chip i { display:inline-grid; place-items:center; width:20px; height:20px; border:3px solid var(--token-color); border-radius:50%; font-style:normal; font-weight:800; }
.atlas-token-waiting .djs-visual > :first-child { stroke:#d55e00 !important; stroke-width:5px !important; stroke-dasharray:6 4; }
/* The element the operator selected to inspect: a filled accent-tinted outline,
   the Operate "this node is selected" affordance. */
.djs-element.atlas-selected .djs-visual > :nth-child(1) {
  stroke: var(--accent) !important; stroke-width: 2.5px !important; fill: var(--accent-soft) !important;
}

/* --- Operate-style instance replay --- */
.ops-replay .ops-meta {
  display: flex; align-items: center; gap: 18px; padding: 10px 16px;
  border-bottom: 1px solid var(--border); background: var(--surface);
}
.ops-title { display: inline-flex; align-items: center; gap: 8px; font-size: 15px; }
.ops-status { font-size: 12px; color: var(--muted); }
.ops-status.live { color: var(--accent); }
.ops-status.done { color: var(--ok); }
.ops-fields { display: flex; gap: 20px; flex-wrap: wrap; }
.ops-fields > div { display: flex; flex-direction: column; gap: 2px; }
.ops-fields label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }
.ops-fields span { font-size: 13px; color: var(--text); }
.mono { font-family: ui-monospace, monospace; }

/* Draggable divider between the diagram and the bottom panel (Instance History +
   inspector): a thin surface strip with a centred grip, row-resize cursor, and a
   focus ring for keyboard resize. */
.ops-resizer {
  flex: 0 0 7px; height: 7px; cursor: row-resize; background: var(--surface);
  display: flex; align-items: center; justify-content: center;
}
.ops-resizer::after {
  content: ""; width: 40px; height: 3px; border-radius: 2px;
  background: var(--border-strong); transition: background 0.12s;
}
.ops-resizer:hover::after, .ops-resizer.dragging::after { background: var(--accent); }
.ops-resizer:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ops-split { display: flex; flex: 0 0 auto; height: 280px; border-top: 1px solid var(--border); background: var(--surface); }
.ops-history { flex: 0 0 42%; display: flex; flex-direction: column; border-right: 1px solid var(--border); min-width: 0; }
.ops-detail { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.ops-panel-head {
  display: flex; align-items: center; gap: 12px; padding: 8px 14px; font-weight: 600; font-size: 13px;
  border-bottom: 1px solid var(--border); background: var(--bg);
}
.ops-toggle { margin-left: auto; font-weight: 400; font-size: 12px; color: var(--muted); display: inline-flex; align-items: center; gap: 5px; }
.ops-history-list { overflow: auto; flex: 1; padding: 4px 0; }
/* Which round of a loop a history row is, beside the element's name — six activations
   of one task are otherwise six identical rows (ADR-0077/0133). */
.ops-hiter {
  font: 600 10px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 0 4px; margin-left: 5px; border-radius: 4px;
  color: var(--muted); background: var(--bg); border: 1px solid var(--border);
}
.ops-hrow {
  display: flex; align-items: center; gap: 8px; padding: 5px 14px; cursor: pointer; font-size: 13px;
  border-left: 3px solid transparent;
}
.ops-hrow:hover { background: var(--bg); }
/* Two different facts, so two different marks. ".cur" is where the *playhead* stands —
   a position on the timeline — and ".sel" is the element instance the inspector is
   showing. They used to be filled in the same colour, differing only by a 3px bar, so
   scrubbing to one element and then clicking another on the diagram read as the history
   having selected two rows at once. The playhead now marks the gutter and tints its
   time; only a selection is filled. */
.ops-hrow.cur { border-left-color: var(--accent); }
.ops-hrow.cur .ops-htime { color: var(--accent); font-weight: 600; }
.ops-hrow.sel { background: var(--accent-soft); }
.ops-hrow.root { font-size: 13px; }
.ops-hrow .ops-hicon { width: 16px; text-align: center; color: var(--ok); }
.ops-hrow .ops-hicon.live { color: var(--accent); }
.ops-hrow .ops-hicon.inc { color: var(--danger); }
.ops-hrow.inc .ops-hname { color: var(--danger); font-weight: 600; }
.ops-hrow .ops-hicon.proc { color: var(--muted); }
.ops-hrow:not(.root):not(.done) .ops-hname { color: var(--text); }
.ops-hname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ops-htime { font-family: ui-monospace, monospace; font-size: 11px; color: var(--muted); }

.ops-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); background: var(--bg); padding: 0 8px; }
.ops-tabs button {
  border: none; background: transparent; padding: 9px 12px; font: inherit; font-size: 13px; color: var(--muted);
  cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.ops-tabs button:hover { color: var(--text); }
.ops-tabs button.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.ops-tab-body { overflow: auto; flex: 1; padding: 12px 16px; }
.ops-props { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; margin: 0; font-size: 13px; }
.ops-props dt { color: var(--muted); }
.ops-props dd { margin: 0; color: var(--text); word-break: break-word; }
.ops-props dt.hint { grid-column: 1 / -1; color: var(--muted); font-style: italic; margin-top: 8px; }
/* The element's documentation in the replay's Details tab (ADR-0025) — what the modeler
   wrote about this step, below its properties. Prose, so it reads at a comfortable
   measure rather than in the property grid; its markup comes from the shared .md rules,
   which keep the author's line breaks as breaks. */
.ops-doc { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.ops-doc > h4 { margin: 0 0 6px; font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.ops-doc .md { font-size: 13px; max-width: 70ch; }
.ops-empty { color: var(--muted); font-size: 13px; padding: 8px 2px; }
.ops-live { color: var(--accent); font-weight: 600; }

/* Variables inspector header in the replay tab: a caption row that names the scope
   the values belong to (the activated element / replay step), a count chip, and the
   copy-all action — so the tab reads as a deliberate data surface, not a raw dump. */
.ops-tab-body .vp-head {
  display: flex; align-items: center; gap: 8px 10px; flex-wrap: wrap;
  margin: -2px 0 12px; padding-bottom: 10px; border-bottom: 1px solid var(--border);
}
.ops-tab-body .vp-title {
  font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em;
  display: inline-flex; align-items: center; gap: 6px; min-width: 0;
}
.ops-tab-body .vp-title b { color: var(--text); text-transform: none; letter-spacing: 0; font-weight: 600; }
.ops-tab-body .vp-count {
  font: 600 11px/1.6 system-ui, sans-serif; color: var(--muted);
  padding: 1px 8px; border-radius: 999px; background: var(--bg); border: 1px solid var(--border);
  white-space: nowrap;
}
.ops-tab-body .vp-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }
/* In the Variables toolbar the filter takes the free space; count sits by the scope
   and copy-all pins right after it. */
.ops-tab-body .v-toolbar .vp-actions { margin-left: 0; }
.ops-tab-body .v-filter { position: relative; margin-left: auto; display: inline-flex; align-items: center; }
.ops-tab-body .v-filter input {
  font: 13px/1.4 ui-monospace, monospace; padding: 5px 9px 5px 27px; width: 150px;
  border: 1px solid var(--border-strong); border-radius: 6px; background: var(--surface); color: var(--text);
}
.ops-tab-body .v-filter input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.ops-tab-body .v-filter svg { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); color: var(--muted); pointer-events: none; }

/* Variables table (Operate-style): a dense, scannable Name · Value · Type grid.
   Long identifiers get their own wide column and can't wrap against the badge. */
.ops-tab-body .v-scroll { overflow: auto; }
table.vt { width: 100%; border-collapse: collapse; font-size: 13px; }
.vt thead th {
  position: sticky; top: 0; z-index: 1; text-align: left; background: var(--bg);
  font: 600 11px/1.5 system-ui, sans-serif; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
  padding: 7px 12px; border-bottom: 1px solid var(--border);
}
.vt tbody tr { border-bottom: 1px solid var(--border); }
.vt tbody tr:last-child { border-bottom: none; }
.vt tbody tr:hover { background: var(--accent-soft); }
.vt td { padding: 8px 12px; vertical-align: top; }
.vt .c-name { font: 600 12.5px/1.45 ui-monospace, monospace; color: var(--text); width: 40%; word-break: break-all; }
.vt .c-valcell { min-width: 0; }
.vt .c-val { font: 13px/1.5 ui-monospace, monospace; word-break: break-word; }
.vt .c-val.num  { color: #0a7c5a; }
.vt .c-val.bool { color: #b26a00; font-weight: 600; }
.vt .c-val.null { color: var(--muted); font-style: italic; }
.vt .c-type { width: 1%; white-space: nowrap; }
.vt .c-act  { width: 1%; white-space: nowrap; text-align: right; }
.vt .c-act .vcopy { opacity: 0; }
.vt tbody tr:hover .c-act .vcopy, .vt .c-act .vcopy:focus-visible { opacity: 1; }
.vt .v-none { color: var(--muted); padding: 14px 12px; }

/* Object/array value → a "···" button that opens the deep-inspect modal. */
.vt .v-open {
  display: inline-flex; align-items: center; gap: 8px; max-width: 100%; min-width: 0;
  font: inherit; font-size: 12.5px; cursor: pointer; text-align: left;
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
  padding: 2px 9px; border-radius: 6px;
}
.vt .v-open:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.vt .v-open .v-chev { color: var(--muted); flex: none; font-size: 10px; }
.vt .v-open:hover .v-chev { color: var(--accent); }
.vt .v-open .v-sum { font: 600 11px/1.5 system-ui, sans-serif; white-space: nowrap; flex: none; }
.vt .v-open .peek { color: var(--muted); font: 12px/1.4 ui-monospace, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.vt .v-open:hover .peek { color: var(--accent); }
.vtag.obj { color: var(--accent); border-color: var(--accent-soft); }

/* The way out of a structure opened in place. Sits in the toolbar rather than only on the
   row, because the JSON itself may have pushed that row off screen. */
.v-toolbar .v-collapse { flex: none; }

/* A structure opened in place. The table is where an operator already is, and the shape
   of an object or a list is most of what they came for — the window (.v-big) stays one
   step further in, for the values too big to read in a row's width. */
.vt .v-struct > td { padding: 0 10px 10px; background: var(--bg); }
.vt .v-struct .vj-body {
  /* Bounded against the viewport too: in the replay's side panel a fixed 320px is most of
     the tab, so two open structures buried the table they belong to. */
  margin: 0; max-height: min(320px, 34vh); overflow: auto; padding: 8px 10px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--surface);
  font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre;
}
.vt .v-big {
  margin-top: 6px; padding: 2px 8px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  font: 11px/1.6 system-ui, sans-serif;
}
.vt .v-big:hover { border-color: var(--accent); color: var(--accent); }

/* Deep-inspect modal: an object/array variable's full value as pretty, colored JSON. */
.var-modal-ov {
  position: fixed; inset: 0; z-index: 60; padding: 24px;
  background: rgba(20, 22, 26, 0.42); display: flex; align-items: center; justify-content: center;
}
.var-modal-ov[hidden] { display: none; }
.var-modal {
  width: min(620px, 100%); max-height: 82vh; display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow);
}
.var-modal-head { display: flex; align-items: center; gap: 10px; padding: 11px 12px; border-bottom: 1px solid var(--border); }
.var-modal-title { font: 600 13px/1.4 ui-monospace, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.var-modal-body { overflow: auto; padding: 12px 14px; background: var(--bg); }
.var-modal-body .vj-body {
  margin: 0; padding: 0; border: 0; background: none; max-height: none;
  white-space: pre; font: 12.5px/1.6 ui-monospace, monospace; color: var(--text); tab-size: 2;
}
.var-modal .j-key  { color: #7a3e9d; }
.var-modal .j-str  { color: #0a7c5a; }
.var-modal .j-num  { color: #0b5cff; }
.var-modal .j-bool { color: #b26a00; }
.var-modal .j-null { color: var(--muted); }

/* Decisions tab: the shared .dec-* cards, grouped by the task that evaluated them. */
.ops-tab-body .dec-group { margin-bottom: 14px; }
.ops-tab-body .dec-group:last-child { margin-bottom: 0; }
.ops-tab-body .dec-group-h {
  font: 600 12px/1.4 system-ui, sans-serif; color: var(--muted);
  margin: 0 0 8px; padding-bottom: 5px; border-bottom: 1px solid var(--border);
}
/* One evaluation as a card — in the replay Decisions tab and the live view's
   decision panel: header, then Inputs and Result on a label/content grid, with the
   shared temis-style result rows and hover table (.res-*/.mtable/.dec-pop) inside. */
.dec-card2 {
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg);
  padding: 8px 10px; margin-bottom: 8px;
}
.dec-card2:last-child { margin-bottom: 0; }
.dec-card2-h { font: 600 12.5px/1.4 ui-monospace, monospace; word-break: break-word; }
.dec-card2-h .muted { font-weight: 400; font-size: 11px; }
.dec-sect2 { display: grid; grid-template-columns: 58px 1fr; gap: 8px; align-items: start; margin-top: 7px; }
.dec-sect2-l { font: 600 10px/1.7 system-ui, sans-serif; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
/* In the narrow live-view side panel the cards need horizontal gutters (the replay
   tab already pads its body). */
/* The way into the decision's graph. It is a link in a card, so it is set as one
   rather than as a button: the card is being read, not operated. */
.dec-open {
  border: 0; background: none; padding: 0; cursor: pointer; text-align: left;
  font: 600 12px/1.6 system-ui, sans-serif; color: var(--accent);
}
.dec-open:hover { text-decoration: underline; }
.var-panel.side .dec-card2 { margin-left: 14px; margin-right: 14px; }
.var-panel.side .dec-card2:first-of-type { margin-top: 12px; }

/* Execution-count badge Operate draws at a node's corner. */
.ops-badge {
  display: inline-grid; grid-auto-flow: column; place-items: center; gap: 3px;
  min-width: 20px; height: 20px; padding: 0 6px; white-space: nowrap;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 999px;
  font: 700 12px/1 ui-monospace, monospace; color: var(--text); box-shadow: var(--shadow);
}

/* A looping activity's badge counts its rounds, not its activations, and carries the ↻ so
   the number is read as "ran this often" rather than as the plain execution count every
   other element's badge is. */
.ops-badge.loop { color: var(--accent); border-color: var(--accent); }
/* The ↻ is a marker beside the number, not part of it: a hair smaller, and never the
   thing that wraps — a badge is one line inside a pill, at any count. */
.ops-badge .ops-badge-m { font-size: 11px; line-height: 1; }

/* The selected element's in/out card, hung under it on the replay canvas: what its
   input mappings were handed, and what it wrote (drawIOOverlay, ADR-0068/0159). It is
   a read-out, not a control — pointer-events stay off so a click always reaches the
   diagram beneath it. */
.io-ov {
  pointer-events: none; user-select: none; width: 240px;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 8px;
  box-shadow: var(--shadow); overflow: hidden; font-size: 11px; color: var(--text);
}
/* The card is one opaque surface: what it covers is covered, badges included.
   diagram-js wraps every overlay in the same bare position:absolute div and gives none of
   them a z-index, so they paint in the order their *elements* first got an overlay — data
   order, not a decision. That order is systematically wrong for this card, which hangs
   below and to the right of its element and therefore covers shapes drawn after it: their
   execution-count and incident badges landed on top of it, and a covered neighbour's "11"
   sat among the card's rows reading as one of its own values. One positive z-index puts
   the card above every sibling overlay (they are all z-index auto in the overlay root's
   stacking context), which is the layer the thing being read belongs on; the transport
   bar's I/O toggle is how a reader gets the model underneath back. */
.djs-overlay-atlas-io { z-index: 2; }
.io-ov-h {
  padding: 4px 8px; border-bottom: 1px solid var(--border);
  background: var(--bg); font-weight: 600; font-size: 11px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.io-sec { display: flex; gap: 7px; padding: 5px 8px; align-items: flex-start; }
.io-sec + .io-sec { border-top: 1px dashed var(--border); }
.io-lbl {
  flex: 0 0 auto; margin-top: 1px; padding: 1px 5px; border-radius: 4px;
  font: 700 9px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase;
  letter-spacing: .04em; background: var(--accent-soft); color: var(--accent);
}
/* Out reads green against in's accent blue, mixed from the success token rather than
   a literal so it follows the theme. */
.io-sec.out .io-lbl { background: color-mix(in srgb, var(--ok) 13%, var(--surface)); color: var(--ok); }
.io-rows { flex: 1 1 auto; min-width: 0; display: grid; gap: 1px; }
.io-row { display: flex; gap: 6px; align-items: baseline; min-width: 0; }
.io-n {
  flex: 0 1 auto; min-width: 0; max-width: 62%; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.io-v {
  flex: 1 1 auto; min-width: 0; text-align: right;
  font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.io-none, .io-more { color: var(--muted); font-style: italic; }
/* The loop's own row on the card: the round, its condition and what the loop decided. It
   is neither an in nor an out, so its label reads in the muted tone rather than claiming
   one of those two colours. */
.io-sec.loop .io-lbl { background: color-mix(in srgb, var(--muted) 13%, var(--surface)); color: var(--muted); }

/* Implementation-type marker the editor draws on a task in the Implement view (and the
   runtime views), so its executable nature reads at a glance — a script task's language
   (PowerShell/Python/JavaScript) or a service task's Worker Type (REST) (drawImplBadges).
   A round disc carrying the type icon, sized and placed to sit over the shape's own
   top-left BPMN marker — the corner-glyph look of a worker task, not a floating label. */
.impl-badge {
  display: grid; place-items: center; width: 26px; height: 26px;
  background: var(--surface); border-radius: 50%; box-shadow: 0 0 0 1px var(--border-strong);
  cursor: default;
}
.impl-badge svg { display: block; width: 16px; height: 16px; border-radius: 3px; }

/* The shop badge on a send or receive task the shop's commands run through (ADR-0429 §6,
   drawShopBadges). Smaller than the implementation badge and placed beside the task's
   top-left envelope rather than over it: that envelope is the BPMN marker saying which way
   the message goes, and it has to stay readable. */
.shop-badge {
  display: grid; place-items: center; width: 20px; height: 20px;
  background: var(--surface); border-radius: 50%; box-shadow: 0 0 0 1px var(--border-strong);
  cursor: default;
}
.shop-badge svg { display: block; width: 13px; height: 13px; border-radius: 3px; }

/* The wizard badge on a subprocess marked atlas:wizard (ADR-0449, drawWizardBadges): the
   shop badge's size, in the subprocess's top-right corner, clear of its name. */
.wizard-badge {
  display: grid; place-items: center; width: 20px; height: 20px;
  background: var(--surface); border-radius: 50%; box-shadow: 0 0 0 1px var(--border-strong);
  cursor: default;
}
.wizard-badge svg { display: block; width: 13px; height: 13px; border-radius: 3px; }

/* The form mark on a start event or user task that names a form (ADR-0465,
   drawFormBadges): the wizard badge's size, but a link — a click opens the form, so it
   says so on hover. */
.form-badge {
  display: grid; place-items: center; width: 20px; height: 20px;
  background: var(--surface); border-radius: 50%; box-shadow: 0 0 0 1px var(--border-strong);
  color: var(--accent); cursor: pointer; text-decoration: none;
}
.form-badge:hover { background: var(--accent-soft); box-shadow: 0 0 0 1px var(--accent); }
.form-badge svg { display: block; width: 13px; height: 13px; }
/* The context pad's "Open form", as a stencil like the class canvas's marks, so it takes
   the entry's colour. */
.djs-context-pad .entry.atlas-cp-form::before {
  content: ""; display: block; width: 100%; height: 100%;
  background-color: var(--text);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: 17px 17px; mask-size: 17px 17px;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%222.5%22%20y%3D%221.5%22%20width%3D%2211%22%20height%3D%2213%22%20rx%3D%221.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%2F%3E%3Cpath%20d%3D%22M5%205.5h6M5%208.5h6M5%2011.5h3.5%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%222.5%22%20y%3D%221.5%22%20width%3D%2211%22%20height%3D%2213%22%20rx%3D%221.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%2F%3E%3Cpath%20d%3D%22M5%205.5h6M5%208.5h6M5%2011.5h3.5%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E");
}
.djs-context-pad .entry.atlas-cp-form { display: inline-block; }

/* Label naming the process a collaboration pool runs (drawPoolProcessCaptions). A pool is a
   participant; the canvas draws its name vertically in the left band but never the process it
   executes. This writes the process name the same way — vertical, bottom-to-top — just inside
   the pool body beside that band, centered on the pool's height, so it reads as the
   participant's counterpart. The wrapper is a zero-size pin at the anchor; the text is
   centered on it and rotated. Only the text is interactive (for its hover title); the pin
   blocks nothing, so clicks fall through to the pool. */
.pool-process-vlabel { position: relative; width: 0; height: 0; pointer-events: none; }
.pool-process-vlabel .ppv-text {
  position: absolute; left: 0; top: 0;
  transform: translate(-50%, -50%) rotate(-90deg); transform-origin: center;
  white-space: nowrap; font-size: 12px; color: var(--text);
  pointer-events: auto; cursor: default;
}

/* A data object's data state, written under its name (drawDataStateLabels). Set in the
   type bpmn-js gives an external label — 11px Arial — so the two lines read as one
   caption rather than as a label with an annotation stuck under it. The wrapper is a
   zero-size pin at the anchor and blocks nothing, so clicks fall through to the box; the
   text alone takes pointer events, for its hover title.
   A state nothing writes is set back: it never reaches the compiled model, so the
   diagram should not present it with the same weight as one the engine acts on. The
   reduced weight is deliberately readable rather than faint — it is a difference in
   role, not a disabled control. */
.data-state-label { position: relative; width: 0; height: 0; pointer-events: none; }
.data-state-label .ds-text {
  position: absolute; left: 0; top: 0; transform: translateX(-50%);
  white-space: nowrap; font: 11px/1.2 Arial, sans-serif; color: var(--text);
  pointer-events: auto; cursor: default;
}
.data-state-label .ds-text.ds-unwritten { color: var(--muted); }

/* ---------- Live collaboration sessions (ADR-0140) ---------- */
/* A floating roster of co-editors, centered over the canvas, plus a lock badge on
   each element another participant is editing and a brief pulse on a remote edit. */
.editor-body { position: relative; }
.collab-presence {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  z-index: 5; display: flex; gap: 4px; pointer-events: none;
}
.collab-avatar {
  position: relative;
  display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 50%; font-size: 11px; font-weight: 700; color: #fff;
  background: hsl(var(--h, 210), 55%, 45%);
  box-shadow: 0 0 0 2px var(--surface), var(--shadow);
  pointer-events: auto; cursor: default;
}
.collab-avatar.me { box-shadow: 0 0 0 2px var(--accent), var(--shadow); }
/* ×N badge when one identity has several open streams (a second tab / reconnect). */
.collab-count {
  position: absolute; right: -3px; bottom: -3px;
  min-width: 13px; height: 13px; padding: 0 2px;
  display: grid; place-items: center;
  font-size: 9px; font-style: normal; font-weight: 700; line-height: 1;
  color: var(--text); background: var(--surface);
  border-radius: 7px; box-shadow: 0 0 0 1px var(--border-strong);
}
.collab-lock {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 1px 6px; border-radius: 10px; font-size: 11px; font-weight: 600;
  color: #fff; background: hsl(var(--h, 210), 55%, 42%);
  box-shadow: var(--shadow); white-space: nowrap; cursor: default;
}
.collab-touched .djs-visual > :first-child {
  stroke: var(--accent) !important; stroke-width: 3px !important;
  transition: stroke 0.2s ease;
}

/* Warning badge in a shape's top-right corner, so the author sees the finding while
   modeling rather than only at Deploy/Validate. Two editors mark two different things
   with it — a BPMN element that cannot run yet, and a decision model whose graph and
   logic disagree about a knowledge model — and one badge is the point: a warning on a
   shape means the same thing wherever it is drawn. */
.unsup-badge {
  display: grid; place-items: center; width: 18px; height: 18px;
  background: var(--warn); color: #fff; border-radius: 50%;
  font-size: 11px; line-height: 1; box-shadow: 0 0 0 2px var(--surface); cursor: default;
}

/* Type icon in the service-task kind picker (serviceTaskKindHTML) — the same disc as
   the canvas marker so the picker and the diagram show the Worker Type identically.
   Carries a `glyph` SVG when the kind has one (REST), else the kind's letter/emoji. */
.stkind-icon {
  flex: 0 0 22px; height: 22px; display: grid; place-items: center;
  background: var(--surface); border-radius: 50%; box-shadow: 0 0 0 1px var(--border-strong);
  font-size: 12px; color: var(--text);
}
.stkind-icon svg { display: block; width: 15px; height: 15px; border-radius: 3px; }

/* Message-flow log beneath the diagram: one row per delivery, the current step
   highlighted as the playhead crosses it. */
.flow-log { border-top: 1px solid var(--border); background: var(--surface); max-height: 30vh; overflow: auto; flex: 0 0 auto; }
.flow-log .fl-head {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--muted); padding: 10px 16px 6px; display: flex; align-items: center; gap: 8px;
}
.flow-log table { width: 100%; border-collapse: collapse; font-size: 13px; }
.flow-log td { padding: 6px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.flow-log td.tnum { font: 12px ui-monospace, monospace; color: var(--muted); white-space: nowrap; width: 1%; }
.flow-log tr.cur td { background: var(--accent-soft); }
.flow-log tr.done td:not(.tnum) { color: var(--text); }
.flow-log tr.pending td:not(.tnum) { color: var(--muted); }
.flow-log .arrow { color: var(--muted); margin: 0 6px; }
.flow-log .msg-name { font-weight: 600; }
.flow-log .empty { padding: 14px 16px; color: var(--muted); }
.replay-link { margin-left: 8px; font-size: 12px; }

/* ---------- Documentation prose (Markdown) ----------
   An element's documentation is written as Markdown and rendered by markdown.js
   (ADR-0250). One ruleset serves every surface that shows
   it — the Tasks app's work instruction today, the replay's Details tab and the
   exported document next — so the same text reads the same wherever it is read, which
   is the whole promise of documenting once and publishing everywhere.

   Every element the renderer can emit is styled here. What is left out arrives with the
   browser's own margins, which are built for a page and are wrong inside a panel: a
   heading in a 70ch block would open a gap the size of the block itself. */
.md { line-height: 1.55; overflow-wrap: anywhere; }
/* The block's own padding sets the distance to its frame; the first and last child must
   not add to it, or a documented task sits visibly lower than an undocumented one. */
.md > :first-child { margin-top: 0; }
.md > :last-child { margin-bottom: 0; }
.md p { margin: 0 0 8px; }
/* Relative sizes, not absolute: the same block is read in panels that set different body
   sizes, and a heading has to stay one step above its own prose in each of them. */
.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 { margin: 14px 0 6px; line-height: 1.3; font-weight: 600; }
.md h1 { font-size: 1.15em; }
.md h2 { font-size: 1.08em; }
.md h3 { font-size: 1.02em; }
.md h4, .md h5, .md h6 { font-size: 1em; }
.md ul, .md ol { margin: 0 0 8px; padding-left: 22px; }
.md li { margin: 2px 0; }
.md li > ul, .md li > ol { margin: 2px 0 0; }
.md blockquote {
  margin: 8px 0; padding: 2px 0 2px 12px;
  border-left: 2px solid var(--border-strong); color: var(--muted);
}
.md code {
  font: 0.92em/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 0 4px;
}
/* A code block scrolls rather than wrapping: an expression broken across lines by the
   panel width reads as a different expression. */
.md pre {
  margin: 8px 0; padding: 10px 12px; overflow-x: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
}
.md pre code { padding: 0; border: 0; background: none; }
.md hr { margin: 12px 0; border: 0; border-top: 1px solid var(--border); }
.md a { color: var(--accent); }
.md strong { font-weight: 600; }
.md del { color: var(--muted); }

/* ---------- Collapsible sections ---------- */
.section-toggle {
  display: inline-flex; align-items: center; gap: 6px; background: none; border: none;
  padding: 0; font: inherit; cursor: pointer; color: inherit;
}
.section-toggle::before {
  content: ""; display: inline-block; width: 0; height: 0;
  border-left: 5px solid var(--muted); border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  transition: transform 0.15s ease;
}
.section-toggle[aria-expanded="true"]::before { transform: rotate(90deg); }
.section-body[hidden] { display: none !important; }

/* ---------- Tasks (Outlook-style inbox, ADR-0028) ---------- */
.tasks-mode main { max-width: none; }
.tasks {
  display: grid;
  grid-template-columns: 232px 360px 1fr;
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  min-height: calc(100vh - var(--topbar-h) - 44px);
}
/* The Start view is a two-pane (list | form) variant of the inbox layout. */
#start-grid { grid-template-columns: 340px 1fr; }
/* Start's application groups: a heading row that opens and closes the processes
   under it, styled like the folder headings so it reads as a section, not a task. */
.start-group { list-style: none; }
.start-group-head {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 9px 14px 7px; border: none; border-bottom: 1px solid var(--border);
  background: var(--bg); font: inherit; font-size: 11.5px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
  cursor: pointer; text-align: left;
}
.start-group-head:hover { color: var(--text); }
.start-group-head::before {
  content: ""; flex: 0 0 auto; width: 0; height: 0;
  border-left: 5px solid currentColor; border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  transition: transform .12s;
}
.start-group-head[aria-expanded="true"]::before { transform: rotate(90deg); }
.start-group-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The favourite star on a Start row: faint until hovered or set, so a column of
   empty stars does not compete with the names. */
.start-fav {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; margin: -3px -6px -3px -8px; padding: 0;
  border: none; border-radius: 6px; background: transparent; cursor: pointer; color: var(--muted);
}
.start-fav svg { fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linejoin: round; }
.start-fav:hover { color: var(--text); background: var(--bg); }
.tasks-item:not(:hover) .start-fav:not(.on):not(:focus-visible) { opacity: .35; }
.start-fav.on { color: #d99a00; }
.start-fav.on svg { fill: currentColor; }
.tasks > * { background: var(--surface); }

/* Folder rail */
.tasks-folders { padding: 14px 10px; display: flex; flex-direction: column; gap: 12px; }
.tasks-identity { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.tasks-identity input {
  font: inherit; padding: 6px 8px; border: 1px solid var(--border-strong);
  border-radius: 6px; background: var(--surface); color: var(--text);
}
.tasks-me { font: inherit; font-weight: 600; color: var(--text); padding: 2px 0; }
.tasks-assign {
  font: inherit; padding: 8px 10px; border: 1px solid var(--border-strong);
  border-radius: 6px; background: var(--surface); color: var(--text); cursor: pointer;
}
.tasks-folders nav { display: flex; flex-direction: column; gap: 2px; }
.tasks-folder {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px; border: none; border-radius: 6px; background: transparent;
  font: inherit; font-weight: 500; color: var(--text); cursor: pointer; text-align: left;
}
.tasks-folder:hover { background: var(--bg); }
.tasks-folder.active { background: var(--accent-soft); color: var(--accent); }
.tasks-count {
  font-size: 12px; color: var(--muted); background: var(--bg);
  border-radius: 999px; padding: 0 7px; min-width: 20px; text-align: center;
}
.tasks-folder.active .tasks-count { background: #fff; color: var(--accent); }

/* List pane */
.tasks-list-pane { display: flex; flex-direction: column; min-height: 0; position: relative; }
/* Draggable divider on the list pane's right edge: resizes the list column so the
   detail (and its form) can be widened or narrowed. */
.tasks-col-resizer {
  position: absolute; top: 0; bottom: 0; right: 0; width: 11px; transform: translateX(50%);
  z-index: 6; cursor: col-resize;
}
.tasks-col-resizer::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 4px; width: 2px;
  background: transparent; transition: background 0.12s;
}
.tasks-col-resizer:hover::before, .tasks-col-resizer.dragging::before { background: var(--accent); }
.tasks-list-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px; border-bottom: 1px solid var(--border);
}
.tasks-list-head h2 {
  margin: 0; font-size: 15px;
  /* A folder carries a name its owner chose, which can be longer than the four
     built-in labels this header was sized for. It ellipsises rather than wrapping,
     because a wrapped title pushes the actions onto a second line. */
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tasks-list { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1; }
.tasks-item { padding: 11px 14px; border-bottom: 1px solid var(--border); cursor: pointer; }
.tasks-item:hover { background: var(--bg); }
.tasks-item.selected { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.tasks-item-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tasks-item-title {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* The name is what a person reads down the list, so it gets the flexible share
     and the chips beside it give way. Without this the two chips take their full
     width and a task called "Ersatzgerät beschaffen" renders as "Ers…". */
  flex: 1 1 auto; min-width: 3em;
}
/* Shrink factor 2 against the title's 1: when the row is tight, the process id
   gives way before the task's name does. */
.tasks-item-top .chip { flex: 0 2 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 44%; }
.tasks-item-sub { font-size: 12px; margin-top: 3px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tasks-empty { padding: 16px 14px; }

/* Priority & due date (ADR-0091) */
.prio-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #b26b00; margin-right: 6px; vertical-align: middle; flex: 0 0 auto; }
.due-badge { font-size: 11px; padding: 1px 7px; border-radius: 999px; background: var(--bg); border: 1px solid var(--border); color: var(--muted); white-space: nowrap; flex: 0 0 auto; }
.due-badge.overdue { background: #fdecea; border-color: #f3c9c3; color: var(--danger); font-weight: 600; }
.due-text.overdue { color: var(--danger); font-weight: 600; }
.tasks-empty.err { color: var(--danger); }

/* Inbox toolbar: a filter box and a sort control under the list header, plus a
   Select toggle in the header for multi-select. */
.tasks-list-head { justify-content: flex-start; gap: 8px; }
.tasks-list-head h2 { margin-right: auto; }
#task-select.on { color: var(--accent); background: var(--accent-soft); }
.tasks-toolbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 14px; border-bottom: 1px solid var(--border);
}
.tasks-search { position: relative; flex: 1 1 140px; min-width: 120px; }
.tasks-search svg { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); color: var(--muted); pointer-events: none; }
.tasks-search input {
  width: 100%; font: 13px/1.4 ui-monospace, monospace; padding: 6px 9px 6px 27px;
  border: 1px solid var(--border-strong); border-radius: 7px; background: var(--surface); color: var(--text);
}
.tasks-search input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.tasks-sort { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); white-space: nowrap; }
.tasks-sort select {
  font: inherit; font-size: 13px; padding: 6px 8px;
  border: 1px solid var(--border-strong); border-radius: 7px; background: var(--surface); color: var(--text);
}

/* Bulk-action bar, shown while in select mode. */
.tasks-bulk {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 14px; background: var(--accent-soft); border-bottom: 1px solid var(--border); font-size: 13px;
}
.tasks-bulk[hidden] { display: none; }
.tasks-bulk-all { display: inline-flex; align-items: center; gap: 6px; color: var(--text); cursor: pointer; }
.tasks-bulk-count { font-weight: 600; color: var(--accent); margin-right: auto; }
.tasks-bulk-actions { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.tasks-bulk .btn { padding: 4px 9px; font-size: 12px; }
.tasks-bulk .tasks-assign { padding: 4px 8px; font-size: 12px; }

/* Truncation banner: the list is capped and newest-first, so a full page means
   more tasks exist. Offers the "load older" cursor page alongside the filter hint. */
.tasks-trunc {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 14px; background: var(--warn-soft, var(--accent-soft));
  border-bottom: 1px solid var(--border); font-size: 12.5px; color: var(--muted);
}
.tasks-trunc[hidden] { display: none; }
.tasks-trunc span { margin-right: auto; }
.tasks-trunc .btn { padding: 4px 9px; font-size: 12px; }

/* Multi-select rows: a checkbox left of each item; whole row toggles it. */
.tasks-item { display: flex; align-items: flex-start; gap: 10px; }
.tasks-item-body { flex: 1; min-width: 0; }
.tasks-item.picked { background: var(--accent-soft); }
.tasks-check { width: 16px; height: 16px; margin: 2px 0 0; flex: 0 0 auto; accent-color: var(--accent); pointer-events: none; }

/* Detail pane */
.tasks-detail { padding: 22px; overflow-y: auto; }
.tasks-detail-empty { display: flex; align-items: center; justify-content: center; height: 100%; }
.tasks-detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
.tasks-detail-head h1 { margin: 0; font-size: 20px; }
.tasks-detail-actions { display: flex; gap: 8px; flex-shrink: 0; }
/* The task's documentation — the modeler's instruction for the person doing the work
   (ADR-0025). It sits between the title and the metadata rows, where it is read first,
   and its prose is Markdown rendered by the shared .md rules above: the author's line
   breaks survive as breaks, and a list of checks arrives as a list. */
.tasks-doc {
  margin: 0 0 20px; padding: 12px 14px; max-width: 70ch;
  border-left: 3px solid var(--accent); border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--bg);
}
.tasks-doc > h2 { margin: 0 0 6px; font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
/* A step of a wizard (ADR-0449, wizardBlock): which step this is in the person's walk
   through the form, and the screens before it. The instruction panel's shape, so the two
   read as one family above the form. */
.tasks-wizard {
  margin: 0 0 16px; padding: 10px 14px; max-width: 70ch;
  border-left: 3px solid var(--accent); border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--bg);
}
.tasks-wizard > h2 { margin: 0; font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.tasks-wizard > p { margin: 4px 0 0; font-size: 13px; }
/* The instruction panel is itself tinted, so code inside it takes the surface colour:
   a chip the same shade as its background is not a chip. */
.tasks-doc .md code, .tasks-doc .md pre { background: var(--surface); }
/* An approval, read and decided in the inbox (#1029). Marked off the way the work
   instruction above is, because it is the same kind of thing: what this person needs
   before acting. The accent border says "this row is not an ordinary task". */
.tasks-approval {
  margin: 0 0 20px; padding: 12px 14px; max-width: 70ch;
  border-left: 3px solid var(--accent); border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--bg);
}
.tasks-approval > h2 {
  margin: 0 0 10px; font-size: 12px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.tasks-approval .tasks-field { grid-template-columns: 110px 1fr; }
.appr-decide { margin: 14px 0 0; display: flex; flex-direction: column; gap: 8px; }
.appr-reason-label { font-size: 13px; }
#appr-reason {
  width: 100%; padding: 8px 10px; font: inherit; color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  resize: vertical;
}
.appr-together { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.appr-buttons { display: flex; gap: 8px; }
/* What an approval decides, on its own row in the list. Every approval task is
   called "Genehmigen", so without this a queue of them is fourteen identical lines. */
.tasks-item-appr { color: var(--text); }
.tasks-fields { display: flex; flex-direction: column; gap: 10px; max-width: 520px; }
.tasks-field { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 12px; }
.tasks-field-label { font-size: 13px; }
.tasks-form-placeholder {
  margin-top: 24px; padding: 16px; border: 1px dashed var(--border-strong);
  border-radius: var(--radius); background: var(--bg);
}
.tasks-form-placeholder p { margin: 0; max-width: 60ch; }
/* The task detail's Form / Process tabs: the form to complete and a read-only
   diagram of the task's instance with progress overlaid, so the assignee sees what
   has already run (gray), where the tokens are now (green), and their own task
   (blue) — and, by what's left plain, what is still ahead. Underline tabs, matching
   the Operations inspector. */
.tasks-detail-tabs { display: flex; gap: 2px; margin-top: 22px; border-bottom: 1px solid var(--border); }
.tasks-detail-tabs button {
  border: none; background: transparent; padding: 9px 12px; font: inherit; font-size: 13px; color: var(--muted);
  cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tasks-detail-tabs button:hover { color: var(--text); }
.tasks-detail-tabs button.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.tasks-tab-body { padding-top: 16px; }
.tp-legend { display: flex; flex-wrap: wrap; gap: 16px; margin: 0 0 10px; font-size: 12px; color: var(--muted); }
.tp-legend span { display: inline-flex; align-items: center; gap: 6px; }
.tp-legend .tp-sw { width: 12px; height: 12px; border-radius: 3px; border: 2px solid var(--border); }
.tp-legend .tp-sw.here { border-color: var(--accent); background: var(--accent-soft); }
.tp-legend .tp-sw.active { border-color: var(--ok); }
.tp-legend .tp-sw.done { border-color: var(--muted); border-style: dashed; }
.tp-canvas {
  position: relative; height: 360px; border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden; background: var(--diagram-bg);
}
.tp-canvas .tp-msg { padding: 16px; }
/* Read-only: hide the editing palette, context pad and inline editor (as the live
   and replay views do) so the process view reads as a diagram, not an editor. */
.tp-canvas .djs-palette,
.tp-canvas .djs-context-pad,
.tp-canvas .djs-direct-editing-parent { display: none !important; }
/* The drill-down chrome inside the Tasks app's process view
   (ADR-0246). Double-clicking
   a call activity's "+" descends into the child instance *in place* rather than
   navigating: this diagram sits beside a form somebody is filling in, and Operations —
   where the full replay lives — is a role the assignee may not hold. So the descent
   needs its own two words of chrome: where you are, and the way back. It sits over the
   diagram (the canvas is position:relative) and only appears once you have descended. */
.tp-drill {
  position: absolute; z-index: 5; top: 8px; left: 8px; right: 8px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px; pointer-events: none;
}
.tp-drill > * { pointer-events: auto; }
/* The transient note sits along the bottom instead: a diagram is drawn from its
   top-left, so a message at the top covers the elements it is about. */
.tp-drill-bottom { top: auto; bottom: 8px; }
.tp-drill-back {
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  border-radius: 999px; padding: 3px 11px; cursor: pointer;
  font: 600 12px/1.5 system-ui, sans-serif; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.tp-drill-back:hover { border-color: var(--accent); color: var(--accent); }
.tp-drill-here {
  border-radius: 999px; padding: 3px 11px; background: var(--accent); color: var(--accent-ink);
  font: 600 12px/1.5 system-ui, sans-serif; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.tp-drill-note {
  border-radius: 999px; padding: 3px 11px; background: var(--surface); color: var(--muted);
  border: 1px solid var(--border); font: 12px/1.5 system-ui, sans-serif;
}

/* The instance's current variables, listed beneath the diagram in the Process tab. */
.tp-vars { margin-top: 16px; max-width: 640px; }
.tp-vars-head { margin: 0 0 8px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
.tp-vars-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.tp-vars-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
.tp-vars-table tr:last-child td { border-bottom: none; }
.tp-var-k { width: 40%; max-width: 260px; font: 12px ui-monospace, monospace; color: var(--muted); word-break: break-word; }
.tp-var-v code { font: 12px ui-monospace, monospace; color: var(--text); word-break: break-word; white-space: pre-wrap; }
/* Form editor (form-js Playground, ADR-0028) — the split editor+preview surface
   renders itself into this container, which just needs to fill the body. */
.form-playground { flex: 1; min-height: 0; overflow: hidden; }
.form-name-input {
  font: inherit; font-weight: 600; padding: 6px 10px;
  border: 1px solid var(--border-strong); border-radius: 6px; min-width: 220px;
  background: var(--surface); color: var(--text);
}

/* Three-tab form editor: Design / Validate / Editor share one body; only the
   active pane is shown. Each pane fills the body via flex. */
.fv-body { position: relative; }
.fv-pane { flex: 1; min-width: 0; min-height: 0; display: none; }
.fv-pane.active { display: flex; }

/* ----- Validate pane: a vertical stack of collapsible, resizable panels ----- */
.fv-validate { flex: 1; display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.fv-panel { display: flex; flex-direction: column; min-height: 0; background: var(--surface); overflow: hidden; }
.fv-preview { flex: 1 1 auto; }              /* absorbs spare height */
.fv-input, .fv-output { flex: 0 0 auto; height: 160px; }
.fv-panel.collapsed { flex: 0 0 auto !important; height: auto !important; }
.fv-panel.collapsed .fv-panel-body { display: none; }
.fv-panel-body { flex: 1; min-height: 0; overflow: auto; }
.fv-preview .fv-panel-body { padding: 16px; }
.fv-form { max-width: 720px; }

/* Panel header: a click target that collapses the panel (chevron rotates). */
.fv-head {
  display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
  padding: 7px 12px; border-bottom: 1px solid var(--border); background: var(--bg);
  cursor: pointer; user-select: none;
}
.fv-editor > .fv-head { cursor: default; }
.fv-head .fv-title { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
.fv-head .fv-hint { font-size: 12px; }
.fv-head .fv-hint.ok { color: var(--ok); }
.fv-head .fv-hint.err { color: var(--danger); }
.fv-caret {
  background: none; border: none; cursor: pointer; color: var(--muted);
  font-size: 11px; line-height: 1; padding: 2px; transition: transform 0.15s;
}
.fv-panel.collapsed .fv-caret { transform: rotate(-90deg); }
.fv-head .fv-act { padding: 4px 10px; font-size: 12px; }

/* Draggable dividers between the stacked panels (mirror .props-resizer). */
.fv-hresizer { flex: 0 0 6px; cursor: row-resize; background: transparent; position: relative; z-index: 2; }
.fv-hresizer::before {
  content: ""; position: absolute; left: 0; right: 0; top: 2px; height: 2px;
  background: var(--border); transition: background 0.12s;
}
.fv-hresizer:hover::before, .fv-hresizer.dragging::before { background: var(--accent); }

/* The Input textarea is upgraded in place by attachJSONEditor (json-editor.js),
   which wraps it in .json-editor; make that wrapper fill the panel body. */
.fv-input .fv-panel-body { padding: 8px 10px; }
.fv-input .json-editor { margin: 0; width: 100%; }
.fv-input .json-input { min-height: 90px; }
.fv-output-pre {
  margin: 0; padding: 12px; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre-wrap; word-break: break-word; color: var(--text);
}
.fv-out-label { text-transform: uppercase; letter-spacing: 0.03em; font-size: 11px; }
.fv-out-label.err { color: var(--danger); }

/* ----- Editor pane: raw schema in the JSON code editor ----- */
.fv-editor { flex: 1; display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.fv-editor-body { flex: 1; min-height: 0; overflow: auto; padding: 12px 14px; }
.fv-editor .json-editor { margin: 0; width: 100%; }
.fv-editor .json-input { min-height: calc(100vh - 180px); }

/* ----- Design pane: collapse/resize affordances layered onto the Playground's
   own areas (form-editor.js enhanceDesignLayout). Same look as Validate. ----- */
/* A section header doubles as its collapse toggle; the caret marks it. */
#pane-design .fjs-pgl-section > .header.fv-sec-head { cursor: pointer; user-select: none; }
#pane-design .fv-sec-caret {
  display: inline-block; width: 1em; margin-right: 2px; color: var(--muted);
  font-size: 0.8em; line-height: 1; transition: transform 0.15s; vertical-align: middle;
}
#pane-design .fjs-pgl-section.fv-collapsed .fv-sec-caret { transform: rotate(-90deg); }
/* Collapsed: keep only the header (its flex basis shrinks it to that height). */
#pane-design .fjs-pgl-section.fv-collapsed > :not(.header) { display: none; }
/* The two stacked regions the areas are re-parented into: editor + optional
   preview on top, the decoupled Form Input/Output data region below. */
#pane-design .fv-top { position: relative; display: flex; flex-direction: row; min-height: 0; overflow: hidden; }
#pane-design .fv-bottom { display: flex; flex-direction: row; min-height: 0; overflow: hidden; }
#pane-design .fv-top > .fjs-pgl-section,
#pane-design .fv-bottom > .fjs-pgl-section { min-width: 0; min-height: 0; overflow: hidden; }
/* Show/hide-preview toggle sitting in the editor's own header. */
#pane-design .fv-preview-toggle {
  margin-left: 8px; padding: 2px 9px; font: inherit; font-size: 12px; line-height: 1.5;
  color: var(--muted); background: var(--surface); border: 1px solid var(--border);
  border-radius: 6px; cursor: pointer; white-space: nowrap;
}
#pane-design .fv-preview-toggle:hover { border-color: var(--accent); color: var(--accent); }
#pane-design .fv-preview-toggle.on { color: var(--text); }
/* Draggable gutters between the editor/preview and top/bottom regions. */
#pane-design .fv-grid-cresizer {
  position: absolute; top: 0; bottom: 0; width: 9px; margin-left: -4px; z-index: 6; cursor: col-resize;
}
#pane-design .fv-grid-rresizer {
  position: absolute; left: 0; right: 0; height: 9px; margin-top: -4px; z-index: 6; cursor: row-resize;
}
#pane-design .fv-grid-cresizer::before { content: ""; position: absolute; top: 0; bottom: 0; left: 3px; width: 3px; background: transparent; transition: background 0.12s; }
#pane-design .fv-grid-rresizer::before { content: ""; position: absolute; left: 0; right: 0; top: 3px; height: 3px; background: transparent; transition: background 0.12s; }
#pane-design .fv-grid-cresizer:hover::before, #pane-design .fv-grid-cresizer.dragging::before,
#pane-design .fv-grid-rresizer:hover::before, #pane-design .fv-grid-rresizer.dragging::before { background: var(--accent); }
/* Width dividers for the palette (left) and properties (right) columns. */
#pane-design .fv-side-resizer { flex: 0 0 6px; align-self: stretch; position: relative; z-index: 6; cursor: col-resize; }
#pane-design .fv-side-resizer::before { content: ""; position: absolute; top: 0; bottom: 0; left: 2px; width: 2px; background: var(--border); transition: background 0.12s; }
#pane-design .fv-side-resizer:hover::before, #pane-design .fv-side-resizer.dragging::before { background: var(--accent); }
/* A side column (Components / Properties) collapses to nothing; the chevron on its
   resizer stays put so it can be reopened. */
#pane-design .fjs-pgl-palette-container,
#pane-design .fjs-pgl-properties-container { position: relative; }
/* The side resizer widens the properties *column*, but form-js pins the panel inside
   it to a fixed --properties-panel-width (250px). Left alone, dragging the column wider
   grows a white strip beside the panel instead of the panel, and dragging it narrower
   clips the panel instead of shrinking it — so let the panel follow its column. */
#pane-design .fjs-pgl-properties-container .fjs-properties-container { width: 100%; min-width: 0; }
/* The Components palette had the same defect on the left: form-js pins it to a fixed
   --palette-width (270px) and centres its tile groups at a fixed 236px. A wider column
   left an empty band between the palette and the canvas, so the palette no longer sat
   against its resizer; a narrower one cut the tiles and the search box off. So the
   palette fills its column, and its tiles flow into as many columns as the width
   holds — three at the default width, as in the reference modeler, even beside a
   classic 17px scrollbar (hence 70px tiles at the least, not form-js's fixed 72px). */
#pane-design .fjs-pgl-palette-container .fjs-palette-container { width: 100%; min-width: 0; box-sizing: border-box; }
#pane-design .fjs-pgl-palette-container .fjs-palette-group { width: auto; margin: 0 8px; }
#pane-design .fjs-pgl-palette-container .fjs-palette-fields {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
}
#pane-design .fjs-pgl-palette-container .fjs-palette-field { width: auto; }
#pane-design .fjs-pgl-palette-container .fjs-palette-search { flex: 1 1 auto; min-width: 0; }
#pane-design .fv-rail { width: 0 !important; min-width: 0 !important; overflow: hidden; }
#pane-design .fv-railbtn {
  position: absolute; top: 8px; left: 50%; transform: translateX(-50%); z-index: 8;
  width: 20px; height: 22px; padding: 0; display: grid; place-items: center;
  font: 600 13px/1 system-ui, sans-serif; cursor: pointer; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
}
#pane-design .fv-railbtn:hover { border-color: var(--accent); color: var(--accent); }
/* form-js renders each property group header as a 32px grey bar, chunkier than the
   BPMN modeler's lighter property-group headers; slim it so the panels read alike. */
#pane-design .bio-properties-panel-group-header { height: 27px; }
#pane-design .bio-properties-panel-group-header-button { height: 20px; line-height: 20px; }

.tasks-form { margin-top: 22px; max-width: 640px; }
.tasks-form .fjs-container { --color-background: var(--surface); }

/* ---------- Public start link (ADR-0029) ---------- */
.tasks-publish { margin-top: 26px; max-width: 640px; padding-top: 20px; border-top: 1px solid var(--border); }
.tasks-publish .publish-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.tasks-publish .publish-hint { margin: 0 0 12px; max-width: 60ch; font-size: 13px; }
.tasks-publish .chip.ok { color: var(--ok, #2d8a4e); border-color: #bfe3cb; background: #eaf7ef; }
.tasks-publish .publish-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.tasks-publish .publish-url {
  flex: 1 1 auto; min-width: 0; font: 13px/1.5 ui-monospace, monospace;
  padding: 7px 9px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); color: var(--text);
}

/* ---------- Dropdown menu (Create new ▾, row ⋯ actions) ---------- */
.dropdown { position: relative; display: inline-block; }
.dropdown-menu {
  /* Fixed, not absolute. A card holding a table scrolls horizontally
     (.card:has(> table)), and a box that is not "visible" on one axis clips the other
     too — so a row's menu was cut off at the card's bottom edge whenever the card was
     short, taking Delete with it. Fixed escapes every such ancestor, at the price of
     needing its coordinates from placeMenu instead of from CSS. */
  position: fixed; left: 0; top: 0; min-width: 210px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 10px 30px rgba(20, 22, 26, .14); padding: 6px; z-index: 60;
}
.dropdown-menu[hidden] { display: none; }
.dropdown-menu button, .dropdown-menu a {
  display: flex; align-items: center; gap: 9px; width: 100%; box-sizing: border-box;
  text-align: left; background: none; border: 0; padding: 8px 10px; border-radius: 6px;
  font: inherit; color: var(--text); cursor: pointer; text-decoration: none; white-space: nowrap;
}
.dropdown-menu button:hover, .dropdown-menu a:hover { background: var(--bg); }
.dropdown-menu button.danger { color: var(--danger); }
.dropdown-menu .sep { height: 1px; background: var(--border); margin: 6px 4px; }
/* A nested list inside a menu. Twenty-odd projects listed inline pushed a row's real
   actions off the bottom of the page, so the move targets live in a flyout instead. It
   opens on hover and on keyboard focus — never on the hidden attribute, which is what
   closeAllMenus drives for top-level menus. Opens to the right, as a submenu does
   everywhere else, flipping left only when the right would run off screen — and scrolls
   rather than growing past the viewport. */
.submenu { position: relative; }
.submenu-toggle { width: 100%; }
.submenu-caret { margin-left: auto; padding-left: 10px; color: var(--muted); }
.submenu-menu {
  /* Placed by placeSubmenu beside its row rather than by placeMenu under a trigger. */
  display: none; max-width: 340px;
  max-height: 320px; overflow-y: auto; overscroll-behavior: contain;
}

/* A project name can be long, and a menu row does not wrap by default; here it may,
   rather than pushing the flyout wider than the space it has to open into. */
.submenu-menu button { white-space: normal; }
/* Open is a class, not `:hover` — app.js holds it for a moment after the pointer leaves
   so the diagonal reach for the flyout survives crossing the rows in between. Keyboard
   focus opens it outright; there is nothing to reach across. */
.submenu.sm-open > .submenu-menu, .submenu:focus-within > .submenu-menu { display: block; }
.submenu.sm-open > .submenu-toggle, .submenu:focus-within > .submenu-toggle { background: var(--bg); }
.dropdown-menu .mlabel {
  font-size: 11px; color: var(--muted); padding: 6px 10px 3px; text-transform: uppercase; letter-spacing: .04em;
}
.dropdown-menu .mi-icon { width: 16px; color: var(--muted); flex: 0 0 16px; text-align: center; }
.btn.dropdown-toggle::after { content: "▾"; margin-left: 7px; font-size: 11px; opacity: .8; }
/* Help ("?") menu: the API Explorer link's trailing ↗ and the disabled hint. */
.dropdown-menu a .ext { margin-left: auto; color: var(--muted); font-size: 12px; }
.dropdown-menu .help-note { padding: 8px 10px; font-size: 13px; color: var(--text); white-space: normal; }
.dropdown-menu .help-note .muted { color: var(--muted); }
.dropdown-menu .help-note code { font-size: 12px; background: var(--bg); padding: 0 4px; border-radius: 4px; }
.icon-btn {
  background: none; border: 1px solid transparent; border-radius: 6px; cursor: pointer;
  color: var(--muted); font-size: 18px; line-height: 1; padding: 4px 8px;
}
.icon-btn:hover { background: var(--bg); border-color: var(--border); }

/* ---------- Modeler project view ---------- */
.crumb { font-size: 13px; color: var(--muted); margin-bottom: 2px; }
.crumb a { color: var(--muted); text-decoration: none; }
.crumb a:hover { text-decoration: underline; }
.type-chip {
  display: inline-flex; align-items: center; gap: 6px; min-width: 60px;
}
.artifact-name { display: flex; align-items: center; gap: 10px; }
/* A superseded deployment version: still listed and still addressable, but no
   longer the one that starts instances, so it recedes behind the current row. */
.muted-row > td { opacity: .62; }
.filter-input { max-width: 280px; margin: 14px 0 12px; }
tr[hidden] { display: none; }

/* The decision editor (ADR-0320). It is a page like
   the BPMN and form editors, so its frame — .editor, .editor-bar, .etabs,
   .editor-body — is theirs; only the canvas, the properties column and the hint
   below them are its own. It was a full-screen modal over the current view until a
   decision became a deployable artifact in its own right (ADR-0319). */
.dmn-body { flex: 1; min-height: 0; display: flex; }
.dmn-canvas { flex: 1; min-height: 0; position: relative; overflow: hidden; background: var(--diagram-bg); }
.dmn-canvas .dmn-decision-table-container { height: 100%; overflow: auto; }
/* The two expression views: a decision whose logic is a single FEEL expression, and
   a business knowledge model, which dmn-js opens in its boxed-expression view. Both
   draw a bordered box a few hundred pixels tall rather than filling the canvas the
   way a table does, so they get a gutter — a box flush against the chrome on all
   four sides does not read as a box — and their sections get a surface to sit on.
   dmn-js borders those sections but leaves them transparent, which is right on its
   own white demo page and wrong on the Modeler's grey canvas, where it would leave
   the expression lying on the backdrop. */
.dmn-canvas .dmn-literal-expression-container,
.dmn-canvas .dmn-boxed-expression-container {
  box-sizing: border-box; padding: 18px; overflow: auto;
}
.dmn-canvas .dmn-boxed-expression-container .dmn-boxed-expression-section,
.dmn-canvas .dmn-literal-expression-container .textarea,
.dmn-canvas .dmn-literal-expression-container .literal-expression-properties {
  background: var(--surface);
}
/* dmn-js uses `editor` as a state class inside its own components — the decision's
   literal-expression view and the knowledge model's boxed-expression view both put
   it on their editable body (`<div class="textarea editor">`), and the DRD's
   decision-name field carries it too. Atlas's `.editor` is the full-bleed
   page shell (`position: fixed; inset: var(--topbar-h) 0 0 0`), so those elements
   were being pinned over the whole viewport: opening a decision whose logic is a
   literal expression drew its CodeMirror across the bar, and the view tabs, Save,
   Deploy and Test underneath it could not be clicked. A fixed element is not
   clipped by the canvas's `overflow: hidden`, so nothing else stopped it.
   Undoing the four properties that rule sets, inside the dmn-js subtree only,
   leaves dmn-js's own styling for the class untouched. */
.dmn-js-parent .editor {
  position: static;
  inset: auto;
  display: block;
  background: none;
}
/* The Camunda-style properties panel column (DRG view). dmn-js-properties-panel
   ships its own internal styling; we only size the column and separate it. */
.dmn-props { width: 320px; flex: none; overflow: auto; border-left: 1px solid var(--border); background: var(--bg); }
.dmn-props[hidden] { display: none; }
.dmn-hint { padding: 8px 14px; border-top: 1px solid var(--border); font-size: 12px; }
/* Author-time findings about the model on screen (dmn-warnings.js). It sits between
   the canvas and the hint because it is about what is on the canvas, and it is hidden
   whenever there is nothing to say — a strip that is always there is a strip nobody
   reads. Each row is a button: the finding names a shape, and clicking it goes to it. */
.dmn-warn {
  flex: 0 0 auto; max-height: 124px; overflow: auto;
  padding: 7px 14px; border-top: 1px solid var(--border);
  background: var(--warn-soft); color: var(--warn); font-size: 12px; line-height: 1.5;
}
.dmn-warn[hidden] { display: none; }
.dmn-warn ul { margin: 0; padding-left: 17px; }
.dmn-warn li + li { margin-top: 3px; }
.dmn-warn button {
  font: inherit; color: inherit; background: none; border: none; padding: 0;
  text-align: left; cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
  text-decoration-style: dotted;
}
.dmn-warn button:hover { text-decoration-style: solid; }
/* The repair one finding can offer. It is a button, not a link: the finding text next
   to it navigates, and this one changes the model — two different acts should not look
   like one. */
.dmn-warn button.dmn-warn-fix {
  text-decoration: none; border: 1px solid currentColor; border-radius: 4px;
  padding: 0 7px; margin-left: 4px; font-size: 11.5px; white-space: nowrap;
}
.dmn-warn button.dmn-warn-fix:hover { background: var(--surface); }
/* One finding in the strip does not deploy — the decision reads a name nothing gives
   it, which the engine refuses. It is marked because the others are things that run:
   a reader who has learned that the strip is advisory would otherwise file this one
   with them and find out at Deploy. */
.dmn-warn li.dmn-warn-error { color: var(--danger); }

/* Where a business rule task's input mapping and the decision's own inputs disagree
   (editor.js renderDecisionDrift). It sits under the rows it is about, and is hidden
   whenever they agree — the same rule the findings strip follows, for the same reason.
   A note rather than an alarm: this deploys and runs, which is exactly the problem. */
.dmn-drift {
  margin-top: 6px; padding: 6px 9px; border-radius: 4px;
  background: var(--warn-soft); color: var(--warn); font-size: 12px; line-height: 1.5;
}
.dmn-drift[hidden] { display: none; }
.dmn-drift p { margin: 0; }
.dmn-drift p + p { margin-top: 4px; }

/* Collapsible property groups (Camunda-style tidy panel). A generic groupify
   turns each <h3> section in the properties panel into a header that toggles its
   fields, with a chevron and a filled dot when the group carries content. */
/* A subtle expand-all / collapse-all control above the property groups. */
.pgroup-tools { display: flex; align-items: center; justify-content: flex-end; gap: 6px; padding: 2px 2px 0; }
.pgroup-all {
  background: none; border: none; cursor: pointer; padding: 2px; font-size: 11px;
  color: var(--muted); letter-spacing: .01em;
}
.pgroup-all:hover { color: var(--accent); text-decoration: underline; }
.pgroup-all-sep { color: var(--border-strong); font-size: 11px; }

/* A caption naming the section the groups below it belong to (e.g. the chosen Worker Type
   above its Mail provider / Message / Failure handling groups). It is a label, not a
   control: no chevron, no hover, not focusable — so it never suggests a collapse it
   cannot do. Deliberately unlike .pgroup-head (smaller, uppercase, muted) so the two
   never read as the same thing. */
.pgroup-caption {
  display: flex; align-items: center; gap: 7px;
  padding: 10px 2px 5px 0; border-top: 1px solid var(--border);
  font: 700 11px/1.4 system-ui, sans-serif; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); cursor: default; user-select: none;
}
.pgroup-caption:first-child { border-top: none; }
.pgroup-caption-icon { display: inline-flex; line-height: 0; flex: none; }
/* The caption already draws the section's rule; don't double it on the first group. */
.pgroup-caption + .pgroup { border-top: none; }

.pgroup { border-top: 1px solid var(--border); }
.pgroup:first-child { border-top: none; }
.pgroup-tools + .pgroup { border-top: none; }
/* Group rows are deliberately compact (bpmn-js / form-js properties density): a
   collapsed panel should show the whole outline of an element at a glance rather than
   a short list spread over a screen. ~34px per collapsed row. */
.pgroup-head {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: none; cursor: pointer; text-align: left;
  padding: 8px 2px 8px 0; color: var(--text);
  font-size: 13px; font-weight: 600; letter-spacing: .01em;
}
.pgroup-head:hover { color: var(--accent); }
.pgroup-chevron {
  color: var(--muted); font-size: 10px; width: 12px; display: inline-block;
  transition: transform .12s ease; transform: rotate(0deg);
}
.pgroup:not(.collapsed) .pgroup-chevron { transform: rotate(90deg); }
.pgroup-title { flex: 1; }
.pgroup-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: none; }
.pgroup-body { padding: 0 0 10px 0; }
.pgroup.collapsed .pgroup-body { display: none; }
.pgroup .pgroup-body > h3 { display: none; } /* a nested legacy header, if any */

/* Uniform vertical rhythm inside a property group. Different element branches emit
   a mix of fields, hint paragraphs, and rows, each with its own margins — so gaps
   drifted between 8px, 10px and the UA <p> default (which "verreisst" the panel).
   Pin every direct child to one 10px step (hints a touch tighter), and zero the
   first child so it never doubles the header's gap. Scoped to the group body, so it
   can't leak into other panels. */
.pgroup-body > * { margin-top: 8px; margin-bottom: 0; }
.pgroup-body > *:first-child { margin-top: 0; }
.pgroup-body > p { margin-top: 8px; font-size: 12px; line-height: 1.5; color: var(--muted); }

/* The class a data object's Type points at, shown in the Modeler's own panel
   (ADR-0230). Deliberately a card and not the canvas's SVG box: loading a diagram
   bundle to draw one static rectangle is not a trade worth making, and what a reader
   needs from it — the kind, the members, which one is the business key — is text.
   The visual language is the canvas's, so the two read as the same thing. */
.im-card {
  border: 1px solid var(--border); border-radius: 7px; overflow: hidden;
  background: var(--bg); margin: 2px 0 4px;
}
.im-card-head {
  display: flex; align-items: baseline; gap: 7px; padding: 7px 10px 6px;
  background: color-mix(in srgb, var(--accent-soft) 60%, var(--bg));
  border-bottom: 1px solid var(--border);
}
.im-card-stereo { font: 10px/1.4 system-ui, sans-serif; color: var(--muted); letter-spacing: .02em; }
.im-card-name { font: 600 13px/1.4 system-ui, sans-serif; color: var(--text); }
.im-card-open { font: 11px/1.4 system-ui, sans-serif; color: var(--accent); text-decoration: none; flex: none; }
.im-card-open:hover { text-decoration: underline; }
.im-card-attrs { list-style: none; margin: 0; padding: 6px 10px 8px; }
.im-card-attrs li { display: flex; gap: 8px; font: 12px/1.7 ui-monospace, monospace; }
.im-card-attrs li .n { color: var(--text); }
/* The business key is the fact the whole model turns on — what makes two of these the
   same one — so it is marked here exactly as the canvas marks it. */
.im-card-attrs li.key .n { font-weight: 700; }
.im-card-attrs li .t { color: var(--muted); }
.im-card-attrs li.none { color: var(--muted); font: italic 11.5px/1.6 system-ui, sans-serif; }

/* A type nothing models yet. Stated rather than refused: a diagram is routinely drawn
   before the vocabulary it names exists. */
.im-nomatch {
  margin: 6px 0 0; font: 12px/1.55 system-ui, sans-serif; color: #b26a00;
}
.im-nomatch .btn { margin-left: 4px; vertical-align: baseline; }

/* A derived, read-only property (e.g. Decision ID, set by the decision picker):
   dimmed with a dashed border and a small "derived" tag beside the label, so it
   reads as computed rather than a blank to fill by hand. */
.props input[readonly] { background: var(--bg); color: var(--muted); border-style: dashed; cursor: default; }
.props input[readonly]:focus { box-shadow: none; border-color: var(--border-strong); }
.field-derived {
  font: 600 9px/1.6 system-ui, sans-serif; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--accent); background: var(--accent-soft); border: 1px solid var(--accent-soft);
  padding: 0 5px; border-radius: 4px; margin-left: 6px; vertical-align: middle;
}

/* Input/Output variable mapping groups (ADR-0068), Camunda-style: a collapsible
   list group per direction whose header carries an add button, a count badge and a
   collapse chevron, with one collapsible card per mapping. */
.io-group { border-top: 1px solid var(--border); }
.io-group-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 2px 8px 0; cursor: pointer; user-select: none;
}
.io-group-title {
  flex: 1; font-size: 13px; font-weight: 600; letter-spacing: .01em; color: var(--text);
}
.io-group-head:hover .io-group-title { color: var(--accent); }
.io-group-add {
  display: inline-grid; place-items: center; width: 22px; height: 22px; flex: none;
  border: none; background: none; border-radius: 5px; cursor: pointer;
  color: var(--muted); font-size: 17px; line-height: 1;
}
.io-group-add:hover { background: var(--accent-soft); color: var(--accent); }
.io-group-count {
  display: inline-grid; place-items: center; min-width: 20px; height: 20px; flex: none;
  padding: 0 5px; border-radius: 10px; background: var(--muted); color: #fff;
  font-size: 11px; font-weight: 700;
}
.io-group-chevron {
  order: -1; /* lead with the chevron, like a .pgroup header, so the titles align */
  color: var(--muted); font-size: 10px; width: 12px; flex: none; text-align: center;
  transition: transform .12s ease;
}
.io-group.collapsed .io-group-chevron { transform: rotate(-90deg); }
.io-group.collapsed .io-group-body { display: none; }
.io-group-body { padding: 0 0 12px 0; }
.io-group-hint { font-size: 12px; margin: 8px 0 0; }

.io-map-list { display: flex; flex-direction: column; gap: 8px; }
.io-map { border: 1px solid var(--border); border-radius: 6px; background: var(--surface); }
.io-map-head {
  display: flex; align-items: center; gap: 6px; padding: 8px 8px 8px 6px;
  cursor: pointer; user-select: none;
}
.io-map-chevron {
  color: var(--muted); font-size: 11px; width: 12px; flex: none; text-align: center;
  transition: transform .12s ease;
}
.io-map.collapsed .io-map-chevron { transform: rotate(-90deg); }
.io-map-title {
  flex: 1; font-size: 13px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.io-map-del {
  display: inline-grid; place-items: center; width: 24px; height: 24px; flex: none;
  border: none; background: none; border-radius: 5px; cursor: pointer;
  color: var(--muted); opacity: 0; transition: opacity .1s ease;
}
.io-map-head:hover .io-map-del,
.io-map-del:focus-visible { opacity: 1; }
.io-map-del:hover { background: #fdecea; color: var(--danger); }
.io-map-body { padding: 0 10px 10px; }
.io-map.collapsed .io-map-body { display: none; }
.io-map-body .field { margin: 8px 0 0; }
.io-fx {
  float: right; font: italic 700 12px/1.4 ui-monospace, monospace; color: var(--accent);
}
.io-assign { display: flex; align-items: stretch; }
.io-assign .io-eq {
  display: grid; place-items: center; width: 30px; flex: none;
  border: 1px solid var(--border-strong); border-right: none;
  border-radius: 6px 0 0 6px; background: var(--bg); color: var(--muted);
  font: 700 13px/1 ui-monospace, monospace;
}
.io-assign .io-source {
  flex: 1 1 auto; min-width: 0; margin-top: 0 !important;
  border-radius: 0 6px 6px 0 !important;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Docked Variables panel (Camunda-style) — a modeling aid listing the variables
   the diagram writes and which element writes each. Toggled from the toolbar. */
.vars-panel {
  width: 300px; flex: none; display: flex; flex-direction: column;
  border-left: 1px solid var(--border); background: var(--surface); overflow: hidden;
}
.vars-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 11px 14px; border-bottom: 1px solid var(--border); font-size: 14px;
}
.vars-filter {
  margin: 10px 12px 8px; padding: 7px 10px; border: 1px solid var(--border);
  border-radius: 6px; font: inherit; color: var(--text); background: var(--bg);
}
.vars-filter:focus { outline: none; border-color: var(--accent); background: var(--surface); }
.vars-list { overflow: auto; padding: 0 6px 12px; }
.var-row { padding: 8px; border-radius: 6px; }
/* What a process could read from its namespaces but does not declare yet, under the
   Variables list, each with its Declare button (ADR-0456). A collapsible group like
   Form / Process; the extra gap sets it apart from the variables the process has. */
.vars-group.vars-available { margin-top: 10px; }
.var-row-available .var-name { color: var(--muted); }
.var-row-available .var-declare { margin-left: auto; padding: 2px 8px; font-size: 12px; }
.var-row:hover { background: var(--bg); }
.var-row.open { background: var(--bg); }
/* Name left, type badge right — a column of badges reads down, where a badge after a
   name of unpredictable length has to be hunted for on every row. */
.var-head { display: flex; align-items: center; gap: 6px; }
.var-name { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; font-weight: 600; color: var(--text); min-width: 0; word-break: break-word; }
.var-head .vtag { flex: 0 0 auto; margin-left: auto; }
.var-meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
/* What the variable held on the run named above the list. A scalar shows as it is; a
   structure gets the same collapsed summary the replay uses — the brackets carry the
   difference between a list and one of its elements, which truncated text does not. */
.var-val {
  margin-top: 4px; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.var-peek {
  display: flex; align-items: center; gap: 6px; width: 100%; margin-top: 4px;
  padding: 2px 7px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--text); cursor: pointer; text-align: left;
}
.var-peek:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.var-peek .vp-chev { flex: none; font-size: 10px; color: var(--muted); transition: transform .12s ease; }
.var-peek[aria-expanded="true"] .vp-chev { transform: rotate(90deg); }
.var-peek:hover .vp-chev { color: var(--accent); }
.var-peek .vp-sum { flex: none; font: 600 11px/1.5 system-ui, sans-serif; white-space: nowrap; }
.var-peek .vp-peek {
  min-width: 0; color: var(--muted); font: 12px/1.4 ui-monospace, monospace;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.var-peek:hover .vp-peek { color: var(--accent); }
.var-json {
  margin: 4px 0 0; padding: 8px 10px; max-height: 260px; overflow: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap;
}
/* Which run the values came from. A value with no run named beside it reads as "what
   this is", when it is "what this was, once". */
.vars-sample {
  display: flex; align-items: flex-start; gap: 6px; margin: 0 12px 8px;
  font-size: 11.5px; color: var(--muted); line-height: 1.5;
}
/* The line is one flex item, not four: an instance key is fifteen digits, and as bare
   inline content each run of it became its own item and broke where it liked. */
.vars-sample .vs-text { min-width: 0; flex: 1; word-break: break-word; }
.vars-sample b { color: var(--text); font-weight: 600; }
.vars-reload {
  margin-left: auto; flex: none; width: 22px; height: 22px; padding: 0;
  border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
  background: var(--surface); color: var(--muted); font-size: 12px; line-height: 1;
}
.vars-reload:hover { border-color: var(--accent); color: var(--accent); }
.var-origin { color: var(--accent); cursor: pointer; }
.var-origin:hover { text-decoration: underline; }
.vars-empty { padding: 14px; color: var(--muted); font-size: 13px; line-height: 1.5; }
/* Origin groups (Form / Process): a clear separator plus a click-to-collapse header
   with a chevron and a count, so the two scopes read as distinct sections. */
.vars-group { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 2px; }
.vars-group:first-child { border-top: none; margin-top: 0; }
.vars-group-head {
  display: flex; align-items: center; gap: 6px; width: 100%;
  background: none; border: none; cursor: pointer; text-align: left;
  padding: 9px 8px 6px; font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
}
.vars-group-head:hover { color: var(--text); }
.vg-chevron { font-size: 9px; width: 10px; flex: none; color: var(--muted); transition: transform .12s ease; }
.vars-group:not(.collapsed) .vg-chevron { transform: rotate(90deg); }
.vg-title { flex: 1; }
.vg-count {
  font-weight: 600; font-size: 10px; letter-spacing: 0; color: var(--muted);
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 0 6px;
}
.vars-group.collapsed .vars-group-body { display: none; }
.var-row[draggable="true"] { cursor: grab; }
.var-row[draggable="true"]:active { cursor: grabbing; }
/* Token simulation and Variables are rows in the editor bar's overflow menu now
   (ADR-0229). A menu row says "on" with a check at its end — the
   solid accent fill these two used to take is how a *button* reads as held down, and on
   a menu row it reads as the row under the pointer instead. */
.dropdown-menu button.active { color: var(--accent); }
.dropdown-menu button.active .mi-icon { color: var(--accent); }
.dropdown-menu button.active::after { content: "\2713"; margin-left: auto; padding-left: 14px; font-weight: 600; }

/* ---------- Sharing scopes (ADR-0071): visibility pill + share dialog ---------- */
.pill.vis { background: var(--bg); color: var(--muted); border: 1px solid var(--border); margin-left: 8px; vertical-align: middle; }
.pill.vis.shared { background: var(--accent-soft); color: var(--accent); border-color: transparent; }

.modal-ov {
  position: fixed; inset: 0; z-index: 60; padding: 24px;
  background: rgba(20, 22, 26, 0.42); display: flex; align-items: center; justify-content: center;
}
.modal {
  width: min(520px, 100%); max-height: 84vh; display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow);
}
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.modal-head h2 { margin: 0; font-size: 16px; }
.modal-body { overflow-y: auto; overflow-x: hidden; padding: 4px 16px 14px; }
.modal-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; border-top: 1px solid var(--border); }
.modal-actions { display: flex; align-items: center; gap: 8px; }

/* The Events page (events.js): each of its two sections is a <details> that folds away,
   its summary the section's heading with a caret that turns, and beside it a line saying
   what the section holds and warns about — readable while the section is folded. A row's
   detail is a dialog wide enough for the tables in it. */
.ev-section { margin-top: 18px; }
.ev-section > summary {
  cursor: pointer; list-style: none; user-select: none;
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; margin: 0 0 10px;
}
.ev-section > summary::-webkit-details-marker { display: none; }
.ev-section > summary::before {
  content: "\25B8"; display: inline-block; font-size: 12px; color: var(--muted); transition: transform .12s ease;
}
.ev-section[open] > summary::before { transform: rotate(90deg); }
.ev-section > summary h2 { margin: 0; }
.ev-section > summary:hover h2 { color: var(--accent); }
.ev-sum-warn { color: var(--warn); font-weight: 600; }
.modal.ev-modal { width: min(960px, 100%); }
.modal.ev-modal:focus { outline: none; }
.ev-modal .modal-head h2 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ev-modal .modal-foot { justify-content: flex-end; }

/* Deleting an application (deletedialog.js): two modes, and for the irreversible one
   the server's preview of what goes and what stops working outside it. */
.modal.purge-modal { width: min(640px, 100%); }
.purge-choice { display: flex; gap: 10px; align-items: flex-start; margin: 12px 0 0; cursor: pointer; line-height: 1.45; }
.purge-choice input { margin-top: 3px; }
.purge-h { margin: 16px 0 6px; font-size: 13px; }
.purge-list { margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.55; }
.purge-names { margin: 2px 0 4px; padding-left: 16px; font-size: 12px; color: var(--muted); }
.purge-warning { margin: 14px 0 10px; color: var(--danger); font-weight: 600; }
.purge-refusal { margin: 12px 0 0; color: var(--danger); }

/* The Used-by dialog behind that chip: every deployed process that resolves through one
   worker, grouped by process — a model redeployed five times is one entry with five
   versions rather than five entries that read as five different processes. */
.modal.usage-modal { width: min(680px, 100%); }
.usage-intro { margin: 8px 0 12px; font-size: 12.5px; line-height: 1.6; }
.usage-filter { margin: 0 0 10px; }
.usage-filter:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.usage-group { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; margin-bottom: 8px; }
.usage-group-head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 7px 10px; background: var(--bg);
}
.usage-group-head b { font-size: 13px; }
.usage-ver {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 5px 10px; border-top: 1px solid var(--border); font-size: 13px;
  color: var(--text); text-decoration: none;
}
.usage-ver:hover { background: var(--accent-soft); text-decoration: none; }
.usage-ver-v { flex: 0 0 auto; font: 600 12px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--accent); }
.usage-el { font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--muted); overflow-wrap: anywhere; }
.usage-live { margin-left: auto; flex: 0 0 auto; }
.usage-empty { color: var(--muted); margin: 14px 0; text-align: center; }

/* The system overview dialog (welcome card, help menu). The diagram is shown whole: it is
   capped by the viewport's height as well as the dialog's width, so the overview never
   needs scrolling; "Full size" in the footer is where it is read in detail. The dialog
   takes the diagram's width rather than a fixed one, so a short viewport does not leave
   it floating in empty space. */
.modal.sysov-modal { width: fit-content; max-width: min(1600px, 100%); max-height: 94vh; }
.sysov-modal .modal-foot { justify-content: flex-end; }
.sysov-body { padding: 12px 16px; }
/* 160px is the head, the foot and the body's padding: what the dialog spends besides the
   diagram, so the two together stay inside the dialog's own 94vh. */
.sysov-body img {
  display: block; margin: 0 auto; max-width: 100%; max-height: calc(94vh - 160px);
  width: auto; height: auto; border-radius: 8px; border: 1px solid var(--border);
}

/* The process's own start form, opened by Deploy & run (ADR-0028). Wider than a confirm
   dialog because it is a real form somebody laid out — a two-column layout squeezed into
   520px is a worse form than the Tasks app shows for the same schema. */
.startform-modal { width: min(680px, 100%); }
.startform-modal .modal-foot { justify-content: flex-end; }
.startform-host { padding-top: 8px; }
/* form-js paints its own white; inside a themed dialog it has to take the dialog's. */
.startform-host .fjs-container { background: transparent; }
/* Its attribution mark lands mid-dialog, between the last field and the buttons, where
   it reads as part of the form. The canvas already carries one for the page. */
.startform-host .fjs-powered-by { display: none; }

/* The start variables declared on the process that its start form does not ask for,
   beneath the form in the Deploy & run / Start instance modal and in the Tasks app.
   A rule above them, so they read as the second half of the same start rather than as
   more of the form somebody laid out. */
.sf-extra { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
.sf-extra-note { font-size: 12px; margin: 0 0 4px; }
.tasks-form-extra { max-width: 640px; }

/* "Generate this form" (ADR-0260). A brief, the process it belongs
   to, and one button. Wider than a confirm dialog because the brief is prose: a
   three-line box invites three words, and three words is not a brief. */
.fg-modal { width: min(560px, 100%); }
.fg-modal .field textarea,
.fg-modal .field select {
  display: block; width: 100%; box-sizing: border-box; margin-top: 5px;
  padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 6px;
  font: inherit; color: var(--text); background: var(--surface);
}
.fg-modal .field textarea { resize: vertical; min-height: 88px; }
.fg-modal .field textarea:focus,
.fg-modal .field select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
/* The optional-but-worth-it note on the process picker: the same size as the label it
   qualifies, not a second voice. */
.fg-modal .field > span em { font-style: normal; opacity: 0.75; }
.fg-modal .modal-foot { gap: 16px; }

.share-sec { margin-top: 16px; }
.share-sec .mlabel { margin: 0 0 8px; font-weight: 600; color: var(--text); font-size: 13px; }
.muted.small, p.small, .small { font-size: 12px; }
.share-sec > .small { margin: 8px 0 0; }

.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: 8px; overflow: hidden; }
.seg button { border: 0; background: var(--surface); color: var(--muted); padding: 6px 18px; font: inherit; font-size: 13px; cursor: pointer; }
.seg button + button { border-left: 1px solid var(--border-strong); }
.seg button.active { background: var(--accent); color: var(--accent-ink); }

/* Repository gallery (ADR-0081): a card grid of installable templates. */
.repo-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); align-content: start; }
.repo-card { display: flex; flex-direction: column; gap: 10px; margin: 0; }
.repo-card + .repo-card { margin-top: 0; }
.repo-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.repo-title { min-width: 0; }
.repo-head h3 { margin: 0; font-size: 15px; letter-spacing: -0.01em; }
.repo-author { font-size: 12px; margin-top: 2px; }
.repo-desc { margin: 0; color: var(--muted); font-size: 13px; flex: 1; }
.repo-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.repo-foot { display: flex; justify-content: flex-end; margin-top: 2px; }

.member-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; }
.member-row + .member-row { border-top: 1px solid var(--border); }
.member-id { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-row .role-sel { max-width: 110px; }
.avatar.sm { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; background: var(--accent); color: var(--accent-ink); flex: 0 0 auto; }

.add-row { display: flex; gap: 8px; align-items: center; }
.add-row input, .add-row select, .role-sel {
  font: inherit; font-size: 13px; color: var(--text); background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 6px; padding: 6px 8px; margin: 0;
}
.add-row #add-uid { flex: 1 1 auto; min-width: 0; }
.add-row #add-role { flex: 0 0 auto; width: 104px; }
.add-row #add-btn { flex: 0 0 auto; }
.role-sel { flex: 0 0 auto; font-size: 12px; padding: 4px 6px; }

/* Grant audit log (ADR-0184): the Activity section's rows. */
.audit-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 5px 0; font-size: 13px; color: var(--text); }
.audit-row + .audit-row { border-top: 1px solid var(--border); }
.audit-row > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.audit-row > .small { flex: 0 0 auto; white-space: nowrap; }

/* ---------- Token simulation (Design view, ADR-0078) ---------- */
/* A client-side walkthrough: tokens move along sequence flows so the control flow
   reads at a glance. No engine, no deploy — a teaching aid on the unsaved diagram. */
/* In simulation mode the diagram is driven by tokens, not edited: hide the modeling
   affordances so clicks mean "spawn / choose a path", not "add a shape". */
.editor.sim-active .djs-palette,
.editor.sim-active .djs-context-pad { display: none !important; }
.editor.sim-active .djs-element { cursor: pointer; }

/* Control bar under the toolbar: play / step / reset / speed + live counts. */
.sim-bar {
  display: flex; align-items: center; gap: 12px; padding: 8px 14px;
  border-bottom: 1px solid var(--border); background: var(--surface);
}
.sim-bar .btn.play { min-width: 84px; }
.sim-bar .sim-speed { font-size: 12px; color: var(--muted); display: inline-flex; align-items: center; gap: 6px; }
.sim-bar .sim-speed .speed {
  font: inherit; font-size: 13px; color: var(--text); background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 6px; padding: 5px 8px;
}
.sim-bar .sim-auto { font-size: 12px; color: var(--muted); display: inline-flex; align-items: center; gap: 5px; cursor: pointer; user-select: none; }
.sim-bar .sim-auto input { margin: 0; cursor: pointer; }
.sim-bar .sim-mi { font-size: 12px; color: var(--muted); display: inline-flex; align-items: center; gap: 6px; }
.sim-bar .sim-mi input {
  font: inherit; font-size: 13px; color: var(--text); background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 6px; padding: 5px 6px; width: 52px;
}
.sim-bar .sim-hint { font-size: 12px; color: var(--accent); }
.sim-bar .sim-stats { font: 12px ui-monospace, monospace; color: var(--muted); }

/* A token in flight: a filled dot with a soft halo, drawn in its own SVG layer above
   the diagram (diagram coordinates, so it tracks pan/zoom). */
.atlas-sim-dot { fill: var(--accent); stroke: #fff; stroke-width: 1.5px; }
.atlas-sim-halo { fill: var(--accent); opacity: 0.18; }

/* The resting-token count badge sitting on an element. */
.atlas-sim-token {
  display: inline-block; background: var(--accent); color: var(--accent-ink); border-radius: 999px;
  min-width: 20px; height: 20px; padding: 0 6px; box-sizing: border-box;
  font: 700 12px/20px system-ui, sans-serif; text-align: center;
  box-shadow: 0 1px 4px rgba(0,0,0,0.35); pointer-events: none;
}
/* A partial join: how many of a converging gateway's branches have arrived. */
.atlas-sim-join {
  display: inline-block; background: var(--surface); color: var(--muted);
  border: 1px solid var(--border-strong); border-radius: 999px;
  padding: 0 6px; height: 18px; font: 700 11px/18px system-ui, sans-serif;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2); pointer-events: none;
}
/* Multi-instance activity: instances still to run (a simulated, fixed multiplicity). */
.atlas-sim-mi {
  display: inline-block; background: var(--surface); color: var(--accent);
  border: 1px solid var(--accent); border-radius: 5px;
  padding: 0 5px; height: 18px; font: 700 11px/18px ui-monospace, monospace;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2); pointer-events: none;
}
/* The "spawn a token here" affordance on every start event. */
.atlas-sim-spawn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 999px; background: var(--ok); color: #fff;
  font-size: 10px; cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.35);
}
.atlas-sim-spawn:hover { filter: brightness(1.08); }

/* Element holding one or more tokens right now. */
.djs-element.atlas-sim-here .djs-visual > :nth-child(1) {
  stroke: var(--accent) !important; stroke-width: 2.5px !important;
}
/* A gateway that is waiting for the user to choose a path. */
.djs-element.atlas-sim-deciding .djs-visual > :nth-child(1) {
  stroke: var(--accent) !important; stroke-width: 2.5px !important;
}
/* An embedded subprocess the flow has entered and is running its nested content inside. The
   held token still gives it the accent "here" outline; this adds a faint tint so it reads as
   an active *container*, not just an element holding a token. */
.djs-element.atlas-sim-scope .djs-visual > :nth-child(1) {
  fill: var(--sim-scope-fill) !important;
  stroke: var(--accent) !important; stroke-width: 2.5px !important;
}
/* A token lands on / leaves an element (start / end pulse). */
.djs-element.atlas-sim-hit .djs-visual > :nth-child(1) {
  stroke: var(--ok) !important; stroke-width: 3px !important;
  animation: atlas-pulse 0.65s ease-out;
}
/* The flow a token is travelling right now: accent stroke with marching ants. */
.djs-connection.atlas-sim-flow .djs-visual > :nth-child(1) {
  stroke: var(--accent) !important; stroke-width: 2.5px !important;
  stroke-dasharray: 6 4 !important; animation: atlas-ants 0.6s linear infinite;
}
/* An outgoing flow offered as a choice at a diverging gateway — click it to send the
   waiting token that way. The modelled default flow is emphasised. */
.djs-connection.atlas-sim-choice { cursor: pointer; }
.djs-connection.atlas-sim-choice .djs-visual > :nth-child(1) {
  stroke: var(--accent) !important; stroke-width: 3px !important;
  animation: atlas-sim-glow 1.1s ease-in-out infinite;
}
.djs-connection.atlas-sim-default .djs-visual > :nth-child(1) {
  stroke: var(--ok) !important;
}
/* An inclusive-gateway branch the user has armed for the multi-select fork — solid, so it
   reads as "selected", distinct from the pulsing "choose me" glow of an offered flow. */
.djs-connection.atlas-sim-armed .djs-visual > :nth-child(1) {
  stroke: var(--ok) !important; stroke-width: 3.5px !important; animation: none !important;
}
@keyframes atlas-sim-glow { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }

/* A thrown message/signal dot, flying from a throw event to the catches it wakes. Warm,
   so it never reads as a control-flow token (which is accent-blue). */
.atlas-sim-msg-dot { fill: var(--warn); stroke: #fff; stroke-width: 1.5px; }

/* "Fire this event" affordance on a parked catch / an armed boundary event. */
.atlas-sim-fire {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 999px; background: var(--warn); color: #fff;
  font-size: 11px; cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.35);
  animation: atlas-sim-glow 1.4s ease-in-out infinite;
}
.atlas-sim-fire:hover { filter: brightness(1.08); animation: none; }

/* A catch-like element a thrown message/signal just reached (the throw→catch link). */
.djs-element.atlas-sim-ping .djs-visual > :nth-child(1) {
  stroke: var(--warn) !important; stroke-width: 3px !important;
  animation: atlas-pulse 0.9s ease-out;
}

/* Global "jump to a process" search palette (topbar search / Ctrl-⌘-K / "/") */
.sp-ov { position: fixed; inset: 0; z-index: 70; display: flex; justify-content: center; align-items: flex-start; padding: 12vh 16px 16px; background: rgba(20, 22, 26, 0.42); }
.sp { width: min(620px, 96vw); max-height: 70vh; display: flex; flex-direction: column; overflow: hidden; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); }
.sp-head { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.sp-icon { flex: 0 0 auto; color: var(--muted); }
.sp-head input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; font: inherit; font-size: 15px; color: var(--text); }
.sp-esc { flex: 0 0 auto; font: 600 11px system-ui; color: var(--muted); border: 1px solid var(--border); border-radius: 5px; padding: 1px 6px; }
.sp-results { overflow-y: auto; padding: 6px; }
.sp-empty { padding: 18px 14px; color: var(--muted); font-size: 13px; }
.sp-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; border: 0; background: transparent; padding: 9px 10px; border-radius: 8px; cursor: pointer; font: inherit; color: var(--text); }
.sp-item.on { background: var(--accent-soft); }
.sp-name { flex: 0 1 auto; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sp-id { flex: 1 1 auto; min-width: 40px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sp-badge { flex: 0 0 auto; font-size: 11px; font-weight: 600; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px; }
.sp-badge.deployed { color: var(--accent); border-color: transparent; background: var(--accent-soft); }
.sp-meta { flex: 0 0 auto; font-size: 11px; font-weight: 600; color: var(--muted); }
.sp-targets { flex: 0 0 auto; display: inline-flex; gap: 4px; }
.sp-tgt { font-size: 11px; font-weight: 600; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; cursor: pointer; }
.sp-tgt:hover { border-color: var(--accent); color: var(--accent); }
.sp-tgt.on { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.sp-foot { display: flex; gap: 16px; padding: 8px 14px; border-top: 1px solid var(--border); color: var(--muted); font-size: 11px; }
.sp-foot kbd { font: 600 11px system-ui; border: 1px solid var(--border); border-radius: 4px; padding: 0 5px; margin-right: 2px; }

/* ---------- Appearance / company-colour theming (Console → Organization) ----------
   The controls that pick a brand accent (theme.js). The accent variables they set
   live on :root; these rules only style the picker itself. */
.theme-swatches { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.theme-swatch {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px 6px 8px; border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--surface); color: var(--text); font: inherit; font-size: 13px; cursor: pointer;
}
.theme-swatch:hover { border-color: var(--accent); }
.theme-swatch.active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.theme-dot { width: 15px; height: 15px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15); }
.theme-custom { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
/* A tidy, square colour well — browsers render input[type=color] as a chunky
   button otherwise. */
.theme-custom input[type="color"] {
  width: 34px; height: 34px; padding: 0; border: 1px solid var(--border-strong);
  border-radius: 7px; background: var(--surface); cursor: pointer;
}
.theme-custom input[type="color"]::-webkit-color-swatch-wrapper { padding: 3px; }
.theme-custom input[type="color"]::-webkit-color-swatch { border: none; border-radius: 4px; }
/* input[type=text] qualifier so this outranks the generic `input[type=text]`
   width:100% reset (same-element, higher specificity), keeping the hex box a
   compact inline field rather than a full-width row. */
input[type="text"].theme-hex {
  width: 108px; font: 13px/1.5 ui-monospace, monospace; text-transform: uppercase;
  padding: 7px 10px; border: 1px solid var(--border-strong); border-radius: 7px;
  background: var(--surface); color: var(--text);
}
input[type="text"].theme-hex:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
/* A live sample of the accent in context, so a choice is visible without leaving
   the page. */
.theme-preview { display: inline-flex; align-items: center; gap: 12px; }
.theme-preview .theme-link { color: var(--accent); font-weight: 600; }

/* Brand logo controls (ADR-0148): a live sample of the mark next to the upload
   button, separated from the colour controls by a hairline. */
.logo-row { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
.logo-controls { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.logo-sample {
  width: 40px; height: 40px; border-radius: 8px;
  border: 1px solid var(--border);
}

/* ---------- XML source (xml-source.js) ---------- */
/* The document behind a diagram or a decision, as text: the shared .modal frame,
   grown to give a whole document room, with the code-editor.js surface filling it the
   way it fills the Developer View. */
.modal.xml-modal { width: min(1120px, 100%); height: min(860px, 88vh); max-height: 88vh; }
.xml-modal .modal-head h2 { display: flex; align-items: center; gap: 8px; }
.xml-kind { font-size: 11px; font-weight: 600; }
.xml-body {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 8px;
  padding: 10px 16px; background: var(--bg);
}
.xml-hint { margin: 0; font-size: 12px; line-height: 1.5; }
.xml-editor { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.xml-editor .code-editor { flex: 1 1 auto; min-height: 0; }
.xml-editor .ce-input { height: 100%; resize: none; }
.xml-status {
  flex: 1 1 auto; min-width: 0; font-size: 12px; line-height: 1.45; color: var(--muted);
  white-space: pre-wrap; overflow-wrap: anywhere; max-height: 4.4em; overflow: auto;
}
.xml-status.err { color: var(--danger); }
.xml-status.warn { color: var(--warn); }
.xml-status.ok { color: var(--ok); }
/* "</>" is three glyphs where the other menu icons are one: set it small enough to
   keep the 16px column every row aligns its label on. */
.dropdown-menu .mi-icon.mi-code { font: 600 9.5px/1 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: -0.5px; }

/* ---------- Developer View (dev-view.js, ADR-0145) ---------- */
/* A full-screen modal that lifts one code-bearing field out of the property panel:
   the same code-editor.js surface, plus the in-scope variables, the language's
   function reference, its help pages and ready-made examples. Opened with F2. */
.dev-overlay {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(20, 22, 26, 0.45);
  display: grid; place-items: center; padding: 24px;
}
.dev-modal {
  width: min(1180px, 96vw); height: min(760px, 92vh);
  min-width: 520px; min-height: 320px;
  max-width: 100vw; max-height: 100vh;
  display: flex; flex-direction: column;
  background: var(--surface); border-radius: 10px;
  box-shadow: 0 18px 60px rgba(20, 22, 26, 0.35); overflow: hidden;
  /* The native grip in the bottom-right corner resizes the window; dragging the
     header moves it (dev-view.js), which takes it out of the overlay's centering
     grid and onto explicit coordinates. */
  resize: both;
}
.dev-modal.placed { position: absolute; margin: 0; }
/* While moving, the pointer owns the whole modal — no text selection, no hover. */
.dev-modal.moving { user-select: none; cursor: grabbing; }
.dev-head { cursor: grab; }
.dev-modal.moving .dev-head { cursor: grabbing; }
/* Keep the footer text clear of the resize grip. */
.dev-foot { padding-right: 22px; }

/* The splitter between the code and the reference. 8px of grab area around a 1px
   rule, so it is easy to hit without drawing a thick line. */
.dev-split {
  flex: 0 0 8px; position: relative; cursor: col-resize;
  background: none; border: 0; padding: 0; align-self: stretch;
}
.dev-split::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 3px; width: 2px;
  background: var(--border); transition: background 0.12s ease;
}
.dev-split:hover::before, .dev-split.dragging::before, .dev-split:focus-visible::before { background: var(--accent); }
.dev-split:focus-visible { outline: none; }
/* A collapsed panel is a fixed rail: there is nothing left to resize. */
.dev-side.collapsed ~ .dev-split, .dev-body:has(.dev-side.collapsed) .dev-split { display: none; }
.dev-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-bottom: 1px solid var(--border); background: var(--surface);
}
.dev-head-sp, .dev-foot-sp { flex: 1 1 auto; }
.dev-badge {
  flex: 0 0 auto; font: 700 10px/1 system-ui, sans-serif; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--accent-ink); background: var(--accent);
  border-radius: 4px; padding: 4px 6px;
}
.dev-title { font-size: 14px; }
.dev-dirty { color: var(--warn); font-size: 11px; }
.dev-body { flex: 1 1 auto; min-height: 0; display: flex; }
.dev-main {
  flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
  padding: 12px; gap: 10px; background: var(--bg);
}
.dev-editor { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.dev-editor .code-editor { flex: 1 1 auto; min-height: 0; }
.dev-editor .ce-input { height: 100%; resize: none; }
.dev-editor .feel-status { margin-top: 6px; }

/* Test panel — the same evaluate/run round trip as the inline field, in place. */
.dev-run {
  flex: 0 0 auto; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); padding: 10px; display: grid; gap: 6px;
}
.dev-run-label { font-size: 12px; color: var(--muted); }
/* Tall enough for a prefilled instance sample (a pretty-printed object), capped so
   the code area keeps the room. */
.dev-run-vars {
  width: 100%; font: 12px/1.5 ui-monospace, monospace; padding: 6px 8px;
  border: 1px solid var(--border-strong); border-radius: 6px; background: var(--bg);
  resize: vertical; max-height: 30vh;
}
.dev-run-row { display: flex; align-items: center; gap: 10px; }
.dev-run-out { font: 12px/1.5 ui-monospace, monospace; color: var(--muted); }
.dev-run-out.ok { color: var(--ok); }
.dev-run-out.err { color: var(--danger); }
.dev-run-detail {
  margin: 0; max-height: 140px; overflow: auto; white-space: pre-wrap;
  font: 12px/1.5 ui-monospace, monospace; background: var(--bg);
  border: 1px solid var(--border); border-radius: 6px; padding: 8px;
}
.dev-run-detail.err { color: var(--danger); border-color: #f3c9c4; background: var(--danger-soft); }

/* Preview panel — a mail template rendered with the sample variables
   (ADR-0487). It takes a fair share of the column, since reading the
   result is the point; the code keeps the rest. An HTML body is shown as a sandboxed page (no
   scripts, no same-origin), the posture the outbox already gives a rendered mail. */
.dev-run.dev-preview { flex: 0 0 48%; min-height: 220px; display: flex; flex-direction: column; }
.dev-run.dev-preview[hidden] { display: none; }
.dev-run.dev-preview .dev-run-vars { max-height: 18vh; flex: 0 0 auto; }
.dev-preview-out { flex: 1 1 auto; min-height: 0; display: flex; }
.dev-preview-out.stale { opacity: .45; }
.dev-preview-text {
  flex: 1 1 auto; margin: 0; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere;
  font: 13px/1.55 system-ui, sans-serif; background: var(--bg);
  border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px;
}
.dev-preview-html {
  flex: 1 1 auto; width: 100%; min-height: 120px; border: 1px solid var(--border);
  border-radius: 6px; background: #fff;
}
.dev-preview-problems {
  margin: 0; padding: 6px 8px 6px 24px; font-size: 12px; color: var(--warn);
  background: var(--surface); border: 1px dashed var(--border); border-radius: 6px;
  max-height: 80px; overflow: auto;
}

/* Side panel: tabs + one filter + the active pane. */
.dev-side {
  flex: 0 0 320px; min-width: 0; display: flex; flex-direction: column;
  background: var(--surface);
}
.dev-tabs { display: flex; align-items: stretch; gap: 2px; padding: 8px 8px 0; border-bottom: 1px solid var(--border); }
/* The fold-away control: the panel is a reference, and a wide script sometimes
   wants the whole modal. */
.dev-side-toggle {
  flex: 0 0 auto; border: 0; background: none; cursor: pointer; padding: 0 6px 6px;
  color: var(--muted); font: 600 14px/1 system-ui, sans-serif;
}
.dev-side-toggle:hover { color: var(--text); }
.dev-side.collapsed { flex-basis: 42px; }
.dev-side.collapsed .dev-filter, .dev-side.collapsed .dev-pane { display: none; }
.dev-side.collapsed .dev-tabs {
  flex-direction: column; align-items: center; gap: 8px;
  padding: 8px 0; border-bottom: 0;
}
.dev-side.collapsed .dev-tab {
  writing-mode: vertical-rl; padding: 10px 3px;
  border-bottom: 0; border-left: 2px solid transparent;
}
.dev-side.collapsed .dev-tab.active { border-left-color: var(--accent); }
.dev-side.collapsed .dev-side-toggle { padding: 0 0 4px; }
.dev-tab {
  flex: 1 1 auto; padding: 6px 4px; border: 0; background: none; cursor: pointer;
  font: 600 12px/1.4 system-ui, sans-serif; color: var(--muted);
  border-bottom: 2px solid transparent;
}
.dev-tab:hover { color: var(--text); }
.dev-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.dev-filter {
  margin: 8px; padding: 6px 8px; border: 1px solid var(--border-strong);
  border-radius: 6px; background: var(--bg); font-size: 12px;
}
.dev-filter:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.dev-pane { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 0 8px 12px; }
.dev-empty { color: var(--muted); font-size: 12px; padding: 8px 4px; }
.dev-group { margin-bottom: 10px; }
.dev-group h4 {
  margin: 8px 4px 4px; font: 700 10px/1.4 system-ui, sans-serif;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted);
}
.dev-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%; text-align: left;
  padding: 5px 6px; border: 0; border-radius: 5px; background: none; cursor: pointer;
}
.dev-item:hover { background: var(--accent-soft); }
.dev-item-name { flex: 0 0 auto; font: 600 12px/1.5 ui-monospace, monospace; color: var(--text); }
.dev-item-detail {
  flex: 1 1 auto; min-width: 0; font-size: 11px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right;
}
/* A row that also shows the value the variable holds in a real instance stacks:
   name + origin on top, the live value under it. */
.dev-item.stacked { flex-direction: column; align-items: stretch; gap: 1px; }
.dev-item-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.dev-item-value {
  font: 11px/1.4 ui-monospace, monospace; color: #0a7c5a;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left;
}
/* Where the live values came from, and how to ask again. */
.dev-sample-strip {
  margin: 0 4px 6px; padding: 5px 6px; border-radius: 5px;
  background: var(--bg); border: 1px solid var(--border);
  font-size: 11px; color: var(--muted);
}
.dev-sample-strip b { color: var(--text); }
.dev-sample-pick {
  font: 600 11px/1.4 ui-monospace, monospace; color: var(--text);
  border: 1px solid var(--border-strong); border-radius: 5px;
  background: var(--surface); padding: 1px 4px; max-width: 190px;
}
.dev-sample-pick:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
/* The instance search under it, and what the last search came to. */
.dev-sample-find { margin: 5px 0 0; }
.dev-sample-search {
  width: 100%; box-sizing: border-box; padding: 3px 6px;
  font: 11px/1.4 ui-monospace, monospace; color: var(--text);
  border: 1px solid var(--border-strong); border-radius: 5px; background: var(--surface);
}
.dev-sample-search:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.dev-sample-note { margin-top: 4px; }
/* A reference row: the row itself opens the help page, the "+" inserts the call. */
.dev-row { display: flex; align-items: center; gap: 2px; }
.dev-row .dev-item { flex: 1 1 auto; min-width: 0; }
.dev-quick {
  flex: 0 0 auto; width: 22px; height: 22px; border: 0; border-radius: 5px;
  background: none; color: var(--muted); cursor: pointer; opacity: 0;
  font: 700 14px/1 ui-monospace, monospace;
}
.dev-row:hover .dev-quick, .dev-quick:focus { opacity: 1; }
.dev-quick:hover { color: var(--accent); background: var(--accent-soft); }
.dev-help-about, .dev-help-fn { padding: 8px 4px; }
.dev-help-fn { border-top: 1px solid var(--border); margin-top: 8px; }
.dev-pane h4 { margin: 4px 0 6px; font-size: 13px; }
.dev-pane h5 { margin: 10px 0 4px; font: 700 10px/1.4 system-ui, sans-serif; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.dev-pane p { margin: 0 0 8px; font-size: 12px; color: var(--text); }
.dev-help-sig, .dev-help-ex, .dev-snip pre {
  margin: 0 0 8px; padding: 8px; overflow: auto; white-space: pre-wrap;
  font: 12px/1.5 ui-monospace, monospace; background: var(--bg);
  border: 1px solid var(--border); border-radius: 6px;
}
.dev-help-fn .btn { margin-right: 6px; }
.dev-snip { padding: 8px 4px; border-bottom: 1px solid var(--border); }
.dev-snip h4 { margin: 0 0 2px; }
.dev-foot {
  display: flex; align-items: center; gap: 12px;
  padding: 7px 12px; border-top: 1px solid var(--border);
  font-size: 11px; color: var(--muted); background: var(--surface);
}
.dev-foot kbd {
  font: 600 10px/1.4 ui-monospace, monospace; border: 1px solid var(--border-strong);
  border-bottom-width: 2px; border-radius: 4px; padding: 0 4px; background: var(--bg); color: var(--text);
}
.dev-confirm { color: var(--warn); font-weight: 600; }
/* The inline affordance on an upgraded field: a quiet "</>" that opens the view,
   shown once the field is hovered or focused so it never competes with the code. */
.code-editor .dev-open {
  position: absolute; top: 4px; right: 4px; z-index: 3;
  border: 1px solid var(--border); border-radius: 5px; background: var(--surface);
  color: var(--muted); font: 600 10px/1 ui-monospace, monospace; padding: 3px 5px;
  cursor: pointer; opacity: 0; transition: opacity 0.12s ease;
}
.code-editor:hover .dev-open, .code-editor:focus-within .dev-open { opacity: 1; }
.code-editor .dev-open:hover { color: var(--accent); border-color: var(--accent); }
/* The FEEL assistant's mini button (feel.js, feel-assistant.js). Unlike "</>" it is
   always shown, quietly: it is how an author learns that the assistant exists at all.
   It takes the top-right corner, and "</>" moves one place left beside it. */
.code-editor .feel-ai-open {
  position: absolute; top: 4px; right: 4px; z-index: 3;
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 18px; padding: 0;
  border: 1px solid var(--border); border-radius: 5px; background: var(--surface);
  color: var(--muted); opacity: 0.7; cursor: pointer; transition: opacity 0.12s ease;
}
.code-editor:hover .feel-ai-open, .code-editor:focus-within .feel-ai-open { opacity: 1; }
.code-editor .feel-ai-open:hover { color: var(--accent); border-color: var(--accent); }
.code-editor.has-feel-ai .dev-open { right: 28px; }
/* Room for the button, on the input and on both layers behind it (the highlight and
   its measuring twin) alike — a padding on one alone would shift the colouring off
   the text it colours. */
.code-editor.has-feel-ai .ce-input,
.code-editor.has-feel-ai .ce-highlight { padding-right: 30px; }

/* The verdict of a worker check, under the worker form (ADR-0150). It says
   what was proved, so it is a sentence and not a status dot — and a failure keeps the
   provider's own words, which is the part that tells an operator what to change. */
.conn-test-result { font: 12.5px/1.5 system-ui, sans-serif; overflow-wrap: anywhere; }
.conn-test-result.ok { color: var(--ok); }
.conn-test-result.err { color: var(--danger); }

/* ---------- Preview mail outbox (ADR-0150) ---------- */
/* One delivered-but-never-sent message per row: the summary is the envelope, the
   open state is the message — text, rendered HTML, and the framed source. */
.ob-msg { border-bottom: 1px solid var(--border); padding: 10px 14px; }
.ob-msg:last-child { border-bottom: 0; }
.ob-msg > summary { cursor: pointer; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ob-msg > summary::marker { color: var(--muted); }
.ob-body { padding: 10px 2px 4px; }
.ob-body h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 12px 0 4px; }
.ob-head { display: flex; flex-direction: column; gap: 2px; font-size: 13px; }
.ob-head b { color: var(--muted); font-weight: 600; margin-right: 4px; }
.ob-pre {
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 8px 10px; margin: 0; max-height: 340px; overflow: auto;
  font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; overflow-wrap: anywhere;
}
/* The HTML body is composed from process variables, so it is untrusted markup: it is
   rendered in a sandboxed frame (no scripts, no same-origin) and never inlined into
   the console's own DOM — the same posture an uploaded SVG gets (ADR-0148). */
.ob-html {
  width: 100%; height: 320px; border: 1px solid var(--border); border-radius: 6px;
  background: #fff;
}

/* ---------- Mock Active Directory (ADR-0213) ---------- */
/* A forest as a containment tree: the DN read right to left, one list per level, an
   entry opening to its attributes. Containers the mock never got an entry for are
   rendered muted, because they exist only because something below them does. */
.admock-worker { margin-top: 14px; }
/* The card is one collapsible section: its summary is the worker line, so collapsing
   leaves the identity and the freshness stamp on screen and puts the forests away. */
.admock-card > summary {
  cursor: pointer; display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.admock-card > summary::marker { color: var(--muted); }
.admock-card-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.admock-card-head h2 { margin: 0; }
.admock-card-state { white-space: nowrap; }
.admock-forest { border-top: 1px solid var(--border); margin-top: 12px; padding-top: 10px; }
.admock-forest:first-of-type { border-top: 0; }
.admock-forest h3 { margin: 0 0 4px; font-size: 13px; font-weight: 600; }
.admock-tree { list-style: none; margin: 4px 0 0; padding-left: 16px; }
.admock-tree.admock-root { padding-left: 0; }
.admock-tree li { position: relative; padding: 1px 0; }
.admock-tree ul { border-left: 1px solid var(--border); }
.admock-node { font: 12.5px/1.7 ui-monospace, SFMono-Regular, Menlo, monospace; }
.admock-entry > summary { cursor: pointer; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.admock-entry > summary::marker { color: var(--muted); }
.admock-detail { padding: 4px 0 8px 14px; }
.admock-dn { font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; margin-bottom: 4px; }
/* width:auto against the global table{width:100%}: an attribute list is two narrow
   columns beside a tree, not a data table spanning the card. */
.admock-attrs { width: auto; border-collapse: collapse; font-size: 12.5px; }
.admock-attrs th, .admock-attrs td { border: 0; }
.admock-attrs th {
  text-align: left; font-weight: 600; color: var(--muted); vertical-align: top;
  padding: 2px 12px 2px 0; white-space: nowrap;
}
.admock-attrs td { padding: 2px 0; vertical-align: top; overflow-wrap: anywhere; }
.admock-ops { margin-top: 12px; }
.admock-ops > summary { cursor: pointer; color: var(--muted); font-size: 13px; }

/* ---------- Mock database journal (ADR-0224) ---------- */
/* The mock directory's sibling, and deliberately flatter: there is no tree here
   because this mockup holds no state — what it has is a sequence, and a sequence
   reads best as rows. */
.sqlmock-table { width: 100%; margin-top: 4px; }
/* Scoped to the table so it outranks .admock-attrs td, which sets padding: 2px 0 and
   would otherwise run the sequence number straight into the statement ("1SELECT …"). */
.sqlmock-table .sqlmock-seq {
  width: 1%; padding: 2px 14px 2px 0; text-align: right;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.sqlmock-stmt { white-space: pre-wrap; overflow-wrap: anywhere; }
.sqlmock-bound { margin-top: 2px; font-size: 12px; }
/* A statement nobody seeded is the entry an operator came here for: it is what they
   paste into the prepared answers. So it is coloured, not merely listed. */
.sqlmock-failed .sqlmock-stmt { color: var(--err, #b3261e); }
.sqlmock-detail { margin-top: 2px; font-size: 12px; color: var(--err, #b3261e); overflow-wrap: anywhere; }
.sqlmock-table tr + tr > td, .sqlmock-table tr + tr > th { padding-top: 8px; }

/* ---------- Mockups (ADR-0216) and the SharePoint card (ADR-0484) ---------- */
/* Between its two siblings: a site has state, drawn as one table per list or library,
   and a journal, drawn as the mock database draws its sequence. */
.spmock-list { margin-top: 8px; }
.spmock-list > summary { cursor: pointer; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.spmock-list > summary::marker { color: var(--muted); }
.spmock-table { width: 100%; margin: 4px 0 0; }
.spmock-table thead th { border-bottom: 1px solid var(--border); padding: 2px 12px 2px 0; }
.spmock-table td { padding: 2px 12px 2px 0; }
.spmock-folder td:first-child code { font-weight: 600; }
.spmock-size { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--muted); }
.spmock-journal .spmock-at { white-space: nowrap; width: 1%; padding-right: 12px; font-size: 12px; }
/* A kind with no card of its own yet (ADR-0216): its data as served, plainly. */
.mockups-data {
  margin: 8px 0 0; max-height: 360px; overflow: auto; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ---------- Secret shape hints (ADR-0155) ---------- */
/* A vault secret is a name and an opaque string, so the form has to say what the
   string is for: which worker resolves it and what shape that worker needs. */
.secret-hint { margin: 0 0 10px; font-size: 12.5px; line-height: 1.55; }
.secret-hint p { margin: 0 0 6px; }
.secret-skeleton {
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 8px 10px; margin: 0 0 6px; overflow-x: auto;
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* A credential bundle is several lines that have to be pasted and read back before
   saving — masking it is what made the shape unknowable in the first place. */
.secret-json {
  width: 100%; resize: vertical;
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* Why a value cannot be what the worker needs, said where it was typed. */
.secret-error {
  margin: 8px 0 0; padding: 8px 10px; border-radius: 6px;
  border: 1px solid var(--danger); background: #fdecea; color: var(--danger);
  font-size: 12.5px; line-height: 1.5; overflow-wrap: anywhere;
}
.secret-set-row .field { margin: 0; }

/* ---------- Release notes (Console landing page) ----------
   The CHANGELOG as this binary was built from it, release by release (see
   renderReleaseNotes in app.js). Everything is theme-variable driven so it follows
   the accent and any theme. The section, each release and each change is a native
   <details>. */
.release-notes { margin-top: 18px; }
/* The section header doubles as the collapse toggle. A caret marks open/closed in
   place of the default marker. */
.release-notes .rn-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; cursor: pointer; list-style: none;
}
.release-notes .rn-head::-webkit-details-marker { display: none; }
.release-notes .rn-title {
  font-size: 18px; font-weight: 650; display: inline-flex; align-items: center; gap: 8px;
}
.release-notes .rn-title::before {
  content: "▸"; color: var(--muted); font-size: 12px; transition: transform .12s ease;
}
.release-notes .rn-root[open] .rn-title::before { transform: rotate(90deg); }

.release-notes .rn-list { margin-top: 10px; }

/* One release. The row with its version, date and size is always visible. */
.release-notes .rn-release { border-top: 1px solid var(--border); padding: 10px 2px; }
.release-notes .rn-release:first-child { border-top: 0; }
.release-notes .rn-release > summary {
  cursor: pointer; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; list-style: none;
}
/* A flex summary loses its marker, so a release and a change draw the caret the
   section header draws: a row that opens should say so before it is clicked. */
.release-notes .rn-release > summary::-webkit-details-marker,
.release-notes .rn-change > summary::-webkit-details-marker { display: none; }
.release-notes .rn-release > summary::before,
.release-notes .rn-change > summary::before {
  content: "▸"; color: var(--muted); font-size: 11px; flex: none; transition: transform .12s ease;
}
.release-notes .rn-release[open] > summary::before,
.release-notes .rn-change[open] > summary::before { transform: rotate(90deg); }
.release-notes .rn-version { font-weight: 650; }
.release-notes .rn-date,
.release-notes .rn-count { font: 12px/1.6 ui-monospace, monospace; color: var(--muted); }

.release-notes .rn-body { margin: 8px 0 2px; padding-left: 18px; }
.release-notes .rn-body p { margin: 0 0 8px; color: var(--text); }
.release-notes .rn-intro { line-height: 1.5; }
.release-notes ul.rn-intro { margin: 0 0 8px; padding-left: 20px; }
.release-notes ul.rn-intro li { margin: 3px 0; }
.release-notes .rn-body code { font-size: 0.92em; overflow-wrap: anywhere; }

/* One change: its category and headline, with the rest of the entry on click. */
.release-notes .rn-changes { margin-top: 4px; }
.release-notes .rn-change { border-top: 1px solid var(--border); padding: 7px 0; }
.release-notes .rn-change:first-child { border-top: 0; }
.release-notes .rn-change > summary {
  cursor: pointer; display: flex; align-items: baseline; gap: 8px; list-style: none;
}
.release-notes .rn-cat { flex: none; }
.release-notes .rn-change-title { font-weight: 600; }
.release-notes .rn-change-body { margin: 6px 0 2px; padding-left: 18px; }

.release-notes .rn-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-top: 8px; }
.release-notes .rn-link { font-size: 13px; font-weight: 600; }

/* "Show all changes" and "Show older releases" reveal what the first paint leaves out. */
.release-notes .rn-more {
  appearance: none; background: transparent; border: 0;
  color: var(--accent); font: 600 13px/1 system-ui, sans-serif; cursor: pointer; padding: 4px 0;
}
.release-notes .rn-list + .rn-more { margin-top: 10px; }
.release-notes .rn-more:hover { text-decoration: underline; }

/* ---------- Key features (Console landing page) ----------
   A short statement of what Atlas is, below the dashboard's own tiles (see
   key-features.js). Bilingual, and collapsible like the release notes above it:
   someone who has read it once should be able to fold it away. The
   entries are a responsive two-column grid that collapses to one on a narrow
   window, so fourteen short items stay scannable instead of becoming a wall. */
.key-features { margin-top: 18px; }
.key-features .kf-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; cursor: pointer; list-style: none;
}
.key-features .kf-head::-webkit-details-marker { display: none; }
.key-features .kf-title {
  font-size: 18px; font-weight: 650; display: inline-flex; align-items: center; gap: 8px;
}
.key-features .kf-title::before {
  content: "\25B8"; color: var(--muted); font-size: 12px; transition: transform .12s ease;
}
.key-features .kf-root[open] .kf-title::before { transform: rotate(90deg); }

/* Segmented EN/DE toggle. It sets the language of the whole landing page. */
.key-features .kf-lang { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.key-features .kf-lang button {
  appearance: none; border: 0; background: var(--surface); color: var(--muted);
  font: 600 12px/1 system-ui, sans-serif; padding: 5px 9px; cursor: pointer;
}
.key-features .kf-lang button + button { border-left: 1px solid var(--border); }
.key-features .kf-lang button:hover { background: var(--bg); }
.key-features .kf-lang button.active { background: var(--accent-soft); color: var(--accent); }

.key-features .kf-intro { margin: 10px 0 0; }
.key-features .kf-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px 22px;
  margin: 14px 0 2px; padding: 0; list-style: none;
}
@media (max-width: 760px) { .key-features .kf-grid { grid-template-columns: 1fr; } }
.key-features .kf-item { display: flex; flex-direction: column; gap: 2px; }
.key-features .kf-item-title { font-weight: 600; }
.key-features .kf-item-text { color: var(--muted); }

/* The Workers view (ADR-0157). Two tables that only mean something read together:
   a job type's queue depth, and who is pulling it. The one state worth catching —
   work piling up with nobody serving it — is a row style rather than a number the
   eye has to find, because an operator arrives here already suspecting something. */
.wk-num { text-align: right; font-variant-numeric: tabular-nums; }
/* A queue nobody is draining. Deliberately the same red as an incident: it is the
   same class of problem, and it usually becomes one. */
tr.wk-stuck > td { background: var(--danger-soft); }
/* Why a row is the way it is, under the thing it explains. Block by default rather
   than only inside a stuck row: a supervised worker parked with nothing to serve
   carries a reason too, and inline it ran straight into its own name. The red is what
   is specific to a stuck row — a parked worker is not an error. */
.wk-why { display: block; margin-top: 2px; font-size: 12px; color: var(--muted); }
tr.wk-stuck .wk-why { color: var(--danger); }
.wk-count-bad { color: var(--danger); font-weight: 600; }
/* A worker that has stopped pulling. Amber, not red: it may simply be idle — the
   red row above is what says whether anyone should care. */
tr.wk-stale > td { background: var(--warn-soft); }
.wk-stale-at { color: #b26b00; font-weight: 600; }
/* The footnote each table carries: what the numbers do and do not cover. */
.wk-note {
  margin: 0; padding: 10px 16px; border-top: 1px solid var(--border);
  background: var(--bg); color: var(--muted); font-size: 12px;
}
/* The version a linked definition is, rendered inside the link: the row can point at
   a superseded version, and which one it is has to travel with the name. Muted so the
   name still reads as the link, and non-breaking so it never wraps away from it. */
.wk-card .ver-of {
  color: var(--muted); font-size: 11px; font-weight: 600; white-space: nowrap;
}
/* Tables sit flush inside their card, so the card pads only its header. */
.wk-card { padding: 0; overflow: hidden; }
.wk-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 15px 16px 12px; }
/* The job-type table folds away, closed by default: an installation runs a handful of
   types and the table lists every one it has ever seen, so the page opens on the
   question rather than on rows of answer. What must survive the fold is the alarm —
   hence .wk-unserved in the summary. */
summary.wk-head { cursor: pointer; list-style: none; user-select: none; }
summary.wk-head::-webkit-details-marker { display: none; }
summary.wk-head:hover { background: var(--bg); }
.wk-caret { display: inline-block; font-size: 10px; color: var(--muted); transition: transform .12s ease; }
.wk-fold[open] > summary .wk-caret { transform: rotate(90deg); }
.wk-fold:not([open]) > summary.wk-head { padding-bottom: 15px; }
.wk-unserved { color: var(--danger); font-weight: 600; }
/* Which process a job type belongs to. Without it a table of fifty types is fifty
   names with no way back to the model that needs them. */
.wk-used { display: block; margin-top: 2px; font-size: 12px; color: var(--muted); }
.wk-used a { color: var(--muted); }
.wk-used a:hover { color: var(--accent); }
/* A supervised worker's captured output. Bounded server-side to the tail, which is
   what an operator wants and what keeps a chatty worker out of the server's memory. */
.wk-log {
  margin: 0; padding: 10px 12px; max-height: 260px; overflow: auto;
  background: var(--bg); border-radius: 6px;
  font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap;
}
tr.wk-log-row > td { padding: 6px 12px 12px; }
/* Which process runs a catalog kind's work on this server (ADR-0164/0168/0173). Said
   in the picker, so an author sees it while choosing, and again on the chosen kind,
   where they have committed. The two tones carry the difference that matters: amber is
   the engine's own loop waiting on the call, the neutral pill is a worker doing the
   waiting instead — a fact about the install, not something to act on. */
.stkind-where {
  margin-left: 6px; padding: 1px 6px; border-radius: 999px; vertical-align: 1px;
  font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em;
}
.stkind-where-engine { background: var(--warn-soft); color: #b26b00; }
.stkind-where-worker { background: var(--accent-soft); color: var(--accent); }
/* One row per kind, one line per row: the name on the left, the placement badge parked
   at the right edge where the eye can run down a column of them instead of hunting for
   each one after a name of unpredictable length. The badge never wraps — "on a worker"
   broken across two lines would cost the row the very line it just saved. */
.stkind-row {
  display: flex; gap: 8px; align-items: center; margin-bottom: 4px; padding: 6px 8px;
  border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
}
.stkind-row:hover { background: var(--bg); }
.stkind-row-on { background: var(--accent-soft); border-color: var(--accent); }
.stkind-name { min-width: 0; line-height: 1.3; }
/* Set tighter here than where the badge appears alone: in a 270px panel the pill and the
   name are competing for the same line, and every pixel the pill gives back is a name
   that stays on one. */
.stkind-row > .stkind-where {
  flex: 0 0 auto; margin-left: auto; white-space: nowrap;
  padding: 1px 5px; font-size: 9.5px; letter-spacing: 0;
}
/* The chosen kind's description. Every other row carries it as a tooltip; the one the
   author has committed to spells it out, because that is the one being read. */
.stkind-desc { display: block; margin-top: 1px; font-size: 12px; color: var(--muted); }
/* The picker's group titles. They ride along the top of the panel's scroll so the answer
   to "where am I in this list" is always on screen — nineteen kinds is past what a flat
   run of rows can carry. */
.stkind-group + .stkind-group { margin-top: 10px; }
.stkind-group-h {
  position: sticky; top: 0; z-index: 1; padding: 5px 2px 4px; margin-bottom: 5px;
  background: var(--surface); border-bottom: 1px solid var(--border);
  font: 600 10.5px/1.4 system-ui, sans-serif; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted);
}
.stkind-notice {
  margin: 8px 0 10px; padding: 8px 10px; border-radius: 6px;
  background: var(--warn-soft); color: var(--text); font-size: 12.5px; line-height: 1.5;
}
/* A kind that already runs on a worker is being described, not warned about, so the
   box drops the amber and keeps only the shape. */
.stkind-notice-worker { background: var(--accent-soft); }
.stkind-notice code {
  font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--surface); border: 1px solid var(--border); border-radius: 4px; padding: 0 4px;
}

/* What a Worker Type needs before it works, in the panel where it is chosen
   (ADR-0289). Three parts, deliberately weighted: the
   `needs` line and the handbook link are always visible — they are what someone who has
   never configured this type has to see without knowing to look — and the step-by-step
   is folded away, because an author who already has the worker wants the fields, not the
   recipe. The steps carry markup of their own (code spans, bold), so they are styled
   here rather than inheriting the flat .muted hint. */
.wtdoc { margin: 0 0 10px; }
/* Inside the Setup group the two blocks are the group's whole body, so the notice loses
   the top margin it needs when it stands alone and the doc block loses the bottom one:
   the group's own padding does that job, and doubling it reads as a gap nobody chose. */
.wt-group .stkind-notice { margin-top: 0; }
.wt-group .wtdoc { margin-bottom: 0; }
.wtdoc-needs {
  margin: 0; padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px 6px 0 0;
  border-bottom: none; background: var(--surface); font-size: 12.5px; line-height: 1.5;
}
.wtdoc-more {
  border: 1px solid var(--border); border-radius: 0; background: var(--surface);
  font-size: 12.5px; line-height: 1.5;
}
.wtdoc-more > summary {
  padding: 6px 10px; cursor: pointer; font-weight: 600; color: var(--accent);
  list-style-position: inside;
}
.wtdoc-more[open] > summary { border-bottom: 1px solid var(--border); }
.wtdoc-steps { margin: 8px 0; padding: 0 10px 0 26px; }
.wtdoc-steps li { margin: 0 0 7px; }
.wtdoc-steps li:last-child { margin-bottom: 0; }
.wtdoc-trap {
  margin: 0; padding: 8px 10px; border-top: 1px solid var(--border);
  background: var(--warn-soft);
}
/* When these steps were last read against the real thing. Quieter than the steps it
   qualifies — it is a caveat, not an instruction — but present, because a set of menu
   paths in somebody else's product with no date on it asks for more trust than it has
   earned. */
.wtdoc-checked {
  margin: 0; padding: 7px 10px; border-top: 1px solid var(--border);
  color: var(--muted); font-size: 11.5px; line-height: 1.45;
}
.wtdoc code, .wtdoc-trap code {
  font: 11.5px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 0 3px;
  overflow-wrap: anywhere;
}
/* The link is a row of its own under the block: a deep link into the handbook this
   server serves itself, so it also works on an installation with no internet. */
.wtdoc-link {
  display: block; padding: 6px 10px; border: 1px solid var(--border); border-radius: 0 0 6px 6px;
  background: var(--surface); color: var(--accent); font-size: 12.5px; font-weight: 600;
  text-decoration: none;
}
.wtdoc-link:hover { background: var(--accent-soft); }
.wtdoc-link .ext { color: var(--muted); font-size: 11px; }

/* One worker's recent jobs (ADR-0157). The list is for scanning outcomes, so a failed
   job is the thing that stands out and its variables stay folded until asked for. */
.wkjobs-modal { width: min(860px, 94vw); }
.wkjobs-modal .modal-body { max-height: 62vh; overflow: auto; }
.wkjob-list { display: flex; flex-direction: column; gap: 8px; }
.wkjob { border: 1px solid var(--border); border-left: 3px solid var(--border); border-radius: 6px; padding: 8px 10px; }
.wkjob-completed { border-left-color: var(--ok, #2e7d32); }
.wkjob-failed { border-left-color: var(--danger); }
/* Ended in a BPMN error (ADR-0089): an answer the model may have a path for, so not
   painted as a failure. */
.wkjob-error-thrown { border-left-color: var(--accent); }
/* Still out with the worker: neither good news nor bad, and the age is what tells a
   job still being worked from a lease whose worker has gone. */
.wkjob-in-flight { border-left-color: #b26b00; }
.wkjob-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
/* A worker's own error text. It is usually an interpreter's stack trace, so the line
   breaks in it are the readable part and must survive; a tall one scrolls inside its
   own box rather than pushing the rest of the list off the screen. */
.wkjob-err { margin: 6px 0 0; }
.wkjob-err pre {
  margin: 4px 0 0; padding: 8px; max-height: 180px; overflow: auto;
  background: var(--danger-soft, var(--bg)); border: 1px solid var(--border); border-radius: 4px;
  color: var(--danger); font-size: 12px; white-space: pre-wrap; word-break: break-word;
}
/* Whether this failure still has attempts behind it. On its own line because it is
   the first thing to read, and the stack trace below it is the second. */
.wkjob-attempts { font-size: 12px; color: var(--muted); font-weight: 600; }
.wkjob-parked { color: var(--danger); }
.wkjob-io { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 6px; }
.wkjob-vars summary { cursor: pointer; font-size: 12px; color: var(--muted); }
.wkjob-vars summary:hover { color: var(--accent); }
.wkjob-vars pre {
  margin: 6px 0 0; padding: 8px; max-height: 220px; overflow: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
  font-size: 12px; white-space: pre-wrap; word-break: break-word;
}

/* What the configured clio worker holds, under the in-memory tail. Separated by a
   rule rather than a card, because they are the same rows from two places and the
   distinction that matters is only "this run" versus "before it". */
.wkjob-history { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }
.wkjob-history-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }

/* Remove-member ✕ inside a group member chip (ADR-0180). */
.chip-x { border: 0; background: none; color: var(--muted); cursor: pointer; font-size: 11px; padding: 0 0 0 5px; line-height: 1; }
.chip-x:hover { color: var(--danger); }

/* The AI-access page (ADR-0200): a numbered walk-through and the values it hands
   out for pasting elsewhere. Built from the existing tokens so it inherits the
   themes (theme.js) like every other surface. */
.step { display: flex; gap: 14px; padding: 14px 0; border-top: 1px solid var(--border); }
.step:first-of-type { border-top: 0; }
.step-n {
  flex: none; width: 26px; height: 26px; border-radius: 999px;
  display: grid; place-items: center; font-size: 13px; font-weight: 700;
  background: var(--accent-soft); color: var(--accent);
}
.step-body { flex: 1; min-width: 0; }
.step-title { display: block; font-weight: 600; color: var(--text); margin-bottom: 6px; }

/* A value the operator has to move into another application's dialog. The value
   stays selectable, because a copy button is not a substitute for one on the
   deployments where the clipboard API is unavailable. */
.copy-row { margin: 10px 0; }
.copy-label { font-size: 12px; color: var(--muted); margin-bottom: 3px; }
.copy-value { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.copy-value code {
  font: 13px/1.6 ui-monospace, monospace; user-select: all; word-break: break-all;
  background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 3px 8px;
}

/* Something the operator has to fix for what follows to work at all — louder than
   .muted, quieter than an error, because nothing has failed yet. */
.warn-note {
  margin: 12px 0; padding: 10px 12px; border-radius: var(--radius);
  background: var(--warn-soft); border: 1px solid var(--warn); color: var(--text); font-size: 13px;
}
.warn-note code { font-family: ui-monospace, monospace; font-size: 12px; }

/* The API-token page (ADR-0194). The scope picker wears the inputs' look; the
   result panel is set off from the form it answers; and an expired token keeps its
   row — the listing is where somebody sees what to reissue — but reads as one. */
#api-tokens .field select {
  display: block; width: 100%; box-sizing: border-box; margin-top: 5px;
  padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 6px;
  font: inherit; color: var(--text); background: var(--surface);
}
#api-tokens .tok-roles { margin-top: 14px; }
#api-tokens .tok-reach { margin: 4px 0 10px; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); }
#api-tokens .tok-result { margin-top: 16px; padding: 14px; border: 1px solid var(--border-strong); border-radius: var(--radius); background: var(--bg); }
#api-tokens tr.tok-expired > td:first-child { box-shadow: inset 3px 0 0 var(--danger); }
#api-tokens tr.tok-expired b { text-decoration: line-through; text-decoration-color: var(--danger); }

/* Worker ownership (ADR-0205): who else may configure one, inline in its row. */
.conn-share { margin-top: 10px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }
.conn-share-head { font-size: 13px; font-weight: 600; }
/* A worker somebody else configures: present, referenceable, and visibly not yours. */
.conn-foreign td { opacity: .72; }

/* Panorama's derived landscape mesh (ADR-0211). The graph is computed by the
   server and laid out in the browser; these rules only style the result. Node
   fills come from the kind table in panorama-mesh.js so the legend and the
   canvas cannot drift apart. */
/* The Starmap drops the centred content column (see setChrome). Everything on this
   canvas is sized by fitting a world into it, so width withheld from the card is
   width taken off every node, every label and every gap at once — the one view where
   the reading column costs more than it gives. */
.starmap-mode main { max-width: none; }
/* The class diagram takes the window, the way the Modeler does: no centred column, no
   page gutter, and no frame around the editor — a drawing surface that stops 22px
   short of the edge is a window inside a window. The bar, the sheet and the problem
   strip keep the borders that separate them from each other and lose only the ones
   that separated them from the page. */
.infomodel-mode main { max-width: none; padding: 0; margin: 0; }
.infomodel-mode .im-editor { height: calc(100vh - var(--topbar-h, 96px)); }
.infomodel-mode .im-bar { border-left: 0; border-right: 0; border-top: 0; border-radius: 0; }
.infomodel-mode .im-body { border-left: 0; border-right: 0; }
.infomodel-mode .im-problems { border-left: 0; border-right: 0; border-bottom: 0; border-radius: 0; }
.mesh-card { display: flex; flex-direction: column; gap: 12px; }
/* One line, and it stays one line: the header is a row of controls, and a row that
   wraps into three stacked lines stops reading as one. What gives under pressure is
   the search box, down to a floor where its placeholder is still readable; below
   that the row wraps rather than leaving the empty stub it used to become. */
.mesh-head { display: flex; align-items: center; gap: 10px; flex-wrap: nowrap; }
.mesh-head h1 { margin: 0; }
/* What is on screen, under the row that changes it. It used to sit in the head as
   the one item with no width of its own: flexbox gave every control the size it
   asked for and squeezed the sentence into a column one word wide. Right-aligned
   because it is a reading of the picture rather than a control on it, and the eye
   should find the controls first. */
.mesh-subhead {
  display: flex; justify-content: flex-end; align-items: baseline; gap: 8px;
  margin-top: -4px; font-size: 13px;
}
/* Splits the row: when the landscape was read, and the switch that keeps that true,
   sit under the search box they belong beside; how much is on screen stays against
   the right edge, where it has always been. */
.mesh-subhead-gap { flex: 1 1 auto; }
/* The Live switch is a footnote on a line of footnotes, not a control in the header
   row: it changes nothing about the picture except whether it stays true. */
.mesh-live { font-size: 13px; color: var(--muted); }
/* A picture the server would not re-read. Said in the danger ink rather than the
   muted grey the rest of the line is in, because "four minutes old" and "four minutes
   old and no longer being kept up" are different facts and only one of them is a
   reason to stop believing what is drawn. */
.mesh-stale { color: var(--danger); }
/* Going into the selected node. Square, because it is an action on one thing rather
   than a phrase, and next to the search because both narrow the picture. */
.mesh-drill-in {
  width: 28px; height: 28px; padding: 0; font-size: 15px; line-height: 1;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--text); cursor: pointer; flex: none;
}
.mesh-drill-in:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.mesh-drill-in:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Nothing selected is nothing to go into. Dimmed rather than hidden, so the control
   is somewhere a reader can learn about before they need it. */
.mesh-drill-in:disabled { opacity: 0.4; cursor: default; }
/* Export sits at the end of the head, pushed away from the search box: it acts on
   the picture as a whole, and a button that saves a file should not be adjacent to
   the one that narrows what would be in it. */
/* How the picture is drawn (ADR-0211 §8) — Atlas's own kinds, the same landscape
   sized by what is running on it, by what is stuck on it or by how long it has been
   stuck, or a projection into another vocabulary. Beside the picture, not in the answer column: it changes the
   drawing rather than what is being said about it. */
.mesh-notation { margin-left: auto; font-size: 13px; color: var(--muted); }
.mesh-notation-pick {
  /* Sized by its longest option, not by the row. Every select on this page inherits
     width:100% from the form styling, which in a flex row means "take all of it" —
     and it did, absorbing the slack the auto-margin was meant to hold and squeezing
     the search box back into the stub this row exists to keep it out of. */
  width: auto; flex: none; height: 28px; padding: 0 6px; font-size: 13px;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--text);
}
/* The type a projection calls this node, under its name. C4's own idiom, and
   ArchiMate's corner icon written out — an icon at this zoom is a smudge. It follows
   the name's own reveal rule, so it appears when the name does. */
.mesh-type { font-size: 11px; fill: var(--muted); pointer-events: none; }
.mesh-application .mesh-type { font-size: 15px; }
/* What a heat weighting counts, under the name (ADR-0211 §8). Same size and same
   reveal as the type line — it is an annotation on the node rather than a second
   label — and in the accent rather than the muted grey, because unlike the type it is
   a number that changes. */
.mesh-runs { font-size: 11px; fill: var(--accent); pointer-events: none; }
.mesh-application .mesh-runs { font-size: 15px; }
/* Except a number about incidents, which is not neutral information — how many of
   them, or how long the oldest has stood. It is the same ink the critical outline
   uses, so the number agrees with the badge already on the node rather than competing
   with it, and it is a second channel behind the size for a reader who does not
   separate these hues. */
.mesh-runs-incidents,
.mesh-runs-incident-age { fill: var(--danger); }
/* How long the oldest parked token has been standing, in the panel. Under the reason
   rather than inside it: the reason is the engine's own sentence, and the age is this
   view's reading of a timestamp beside it. */
.mesh-parked-since { margin: 4px 0 0; font-size: 12px; }
/* A switch beside the notation picker that adds something to the picture. There is
   one left — the drafts — since the running counts became a way of drawing the
   landscape rather than an overlay on it, and moved into the picker: a switch and a
   list entry are different gestures, and "also show me this" is only the first of
   them. The class stays general because the next thing genuinely added to a picture
   belongs here rather than in the list. */
.mesh-toggle {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  font-size: 13px; color: var(--text); white-space: nowrap; cursor: pointer;
}
.mesh-toggle input { margin: 0; cursor: pointer; }
/* Disabled while an answer is being fetched — the drafts switch re-asks the server
   rather than re-drawing what is already here, which is exactly why it stayed a
   switch — and it says so by going dim, so a second click during the wait is not
   silently swallowed. */
.mesh-toggle input:disabled { cursor: progress; }
.mesh-toggle:has(input:disabled) { opacity: 0.55; cursor: progress; }
/* What the projection drops. Collapsed by default and never absent: a projection
   that hides its loss is the failure §8's theme ban exists to prevent, and one that
   shouts it every time is one nobody reads twice. */
.mesh-projection { font-size: 13px; }
.mesh-projection summary { cursor: pointer; display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.mesh-projection summary .muted { font-size: 11px; }
.mesh-projection > p { margin: 6px 0 4px; }
.mesh-loss { margin: 0; padding-left: 18px; color: var(--muted); font-size: 12px; }
.mesh-loss li { margin: 2px 0; }
.mesh-export { display: inline-flex; gap: 6px; }
.mesh-export button {
  height: 28px; padding: 0 10px; font-size: 13px; line-height: 1;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface);
  color: var(--text); cursor: pointer; white-space: nowrap;
}
.mesh-export button:hover { border-color: var(--border-strong); }
.mesh-export button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mesh-legend {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 12px; background: var(--bg);
  border: 1px solid var(--border); border-radius: 8px;
}
.mesh-swatches { display: flex; flex-wrap: wrap; gap: 16px; }
.mesh-swatch { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }
/* The size scale on a heat weighting: reference circles a reader holds the picture
   against. A row of its own under the shapes, because the circles are taller than a
   line of text and threading them among the kinds would push the shapes off the line.
   Baseline-aligned at the bottom rather than centred, so the numbers under them form
   one line however far apart the circles are in size. */
.mesh-scale { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 6px; }
/* Each mark is a button: clicking it narrows the picture to the band it stands for.
   Styled back down to the shape it had as a label — the affordance is the pointer and
   the hover, because a row of framed buttons would read as chrome and compete with
   the circles, which are the thing being compared. */
.mesh-scale-step {
  display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
  background: none; border: 1px solid transparent; border-radius: 8px;
  padding: 2px 6px; cursor: pointer; font: inherit; color: inherit;
}
.mesh-scale-step:hover { background: var(--hover, rgba(0, 0, 0, 0.04)); }
.mesh-scale-step:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The chosen band, marked the way the rest of this view marks a chosen thing. */
.mesh-scale-step[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft, rgba(0, 0, 0, 0.06)); }
.mesh-scale-step[aria-pressed="true"] .mesh-scale-tick { color: var(--text); font-weight: 600; }
/* The circles carry their own fill and stroke, read off the process kind where the
   picture reads them (see heatScaleMarks): a second copy here would be a colour that
   could drift from the nodes the scale is for. */
.mesh-scale-tick { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.mesh-meta { font-size: 13px; color: var(--text); }
/* A filtered or collapsed picture states so in words, not only in shading — the
   whole point is that incompleteness is legible rather than inferred. */
.mesh-note { margin: 0; font-size: 13px; color: var(--muted); }
/* The surface is a fixed viewport rather than a box that grows to fit its content.
   The canvas fills it exactly and the viewBox is computed to the same aspect ratio,
   which is what removes the empty bands a settled graph used to sit inside: with
   preserveAspectRatio's default, any mismatch between the two is letterboxed. */
/* The picture and the key that explains it, in one column beside the side panel. The
   stage is only the canvas and the controls floating over it, so those controls keep
   the canvas's own corner: measured against a box that also held the key they would
   sink to the bottom of it. */
.mesh-plot { display: flex; flex-direction: column; gap: 12px; flex: 1; min-width: 0; }
.mesh-stage { position: relative; }
.mesh-surface {
  overflow: hidden; border: 1px solid var(--border); border-radius: 8px;
  height: clamp(460px, 72vh, 960px);
  /* pan-y, not none: a horizontal drag pans the zoomed mesh, and a vertical one
     still scrolls the page. Trapping the page scroll under a full-width canvas is
     the worse failure on a touch screen, and the zoom buttons stay reachable. */
  touch-action: pan-y;
}
.mesh-canvas {
  display: block; width: 100%; height: 100%; background: var(--surface);
  /* Nodes are dragged across this surface, and a drag over text selects it. There is
     nothing here worth selecting — the names are decoration on shapes — and a blue
     smear of half-selected labels trailing a drag is the picture telling somebody
     their gesture went to the browser rather than to the graph. */
  user-select: none; -webkit-user-select: none;
}
/* A touch that starts on a node is a drag, so the browser must not take it for a
   scroll; the background keeps pan-y, where scrolling the page is still what a
   vertical swipe should do. */
.mesh-node { touch-action: none; }
/* Grab only where there is something to grab: at the fitted frame the whole
   landscape is already on screen and dragging is deliberately inert. */
.mesh-canvas.mesh-zoomed { cursor: grab; }
.mesh-canvas.mesh-zoomed:active { cursor: grabbing; }
/* The zoom controls float over the canvas, which means they are also *in front of*
   it: a node under them takes no pointer, so it cannot be selected or dragged. The
   fitted view now holds this corner clear (see fitView), and the panel itself passes
   pointers through everywhere except on its own buttons, so the gaps between them
   stop being dead canvas too. */
.mesh-zoom {
  position: absolute; right: 10px; bottom: 10px; display: flex; gap: 4px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 4px;
  pointer-events: none;
}
.mesh-zoom button { pointer-events: auto; }
.mesh-zoom button {
  min-width: 30px; height: 26px; padding: 0 8px; font-size: 14px; line-height: 1;
  border: 1px solid var(--border); border-radius: 6px; background: var(--bg);
  color: var(--text); cursor: pointer;
}
.mesh-zoom button:hover { border-color: var(--border-strong); }
.mesh-zoom button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Three derived kinds, three strokes (see EDGE_KEY in panorama-mesh.js, which names
   them for the legend and draws its swatches with these same classes).

     calls    — solid. One process invoking another is the plain case.
     uses     — dashed. A process reaching a worker or a decision: still a dependency,
                so it is read against `calls` rather than apart from it.
     contains — fine dots, faded. Not a dependency at all but the structure the other
                two hang on, so it recedes: nothing fails *along* a containment edge,
                and the two kinds that carry a failure path should be the ones the eye
                lands on.

   non-scaling-stroke is what makes the three tellable apart at all. Everything else
   on this canvas is drawn in world units and scaled by the viewBox, and the opening
   view of a large landscape scales by about a half — which took the stroke to under
   a pixel and shrank every dash with it, so two patterns converged into one pale
   line and the difference only appeared once you had zoomed in. Widths and dashes
   are now in the pixels a reader actually sees, at every magnification. */
.mesh-edge { stroke: var(--mesh-line); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.mesh-edge-uses { stroke-dasharray: 6 4; }
/* An edge whose counter was read and holds nothing. Quieter, never recoloured: on this
   canvas colour is kind and severity, and a path nothing has walked is neither a fault
   nor another kind of thing (ADR-0400). Opacity alone also keeps it distinguishable
   from a filtered-out edge, which the .mesh-relating rule below takes much further. */
.mesh-edge-untaken { opacity: 0.35; }
.mesh-edge-contains { stroke-dasharray: 1 3; opacity: 0.7; stroke-linecap: round; }
/* The catalogue's lines, in the same three patterns and for the same reasons, because
   a fourth pattern would not be told from the three at a glance. `offers` is the solid
   structural line a catalogue hangs its products on; `composition` is solid too — a
   part that always comes with the whole is as firm a claim as a call; `aggregation`
   takes the dashed pattern that already means "and this one is optional to it"; and
   `requires` takes the dotted one, which already reads as the faintest claim on the
   canvas. The key beside the picture names each in words, which is what actually
   tells them apart: the pattern groups them, the legend distinguishes them. */
.mesh-edge-offers { opacity: 0.8; }
.mesh-edge-aggregation { stroke-dasharray: 6 4; }
.mesh-edge-requires { stroke-dasharray: 1 3; opacity: 0.7; stroke-linecap: round; }
/* A definition and a server that holds it (ADR-0401). Dash-dot, which is the one
   pattern none of the three above uses: solid is an invocation or an offering, 6 4 a
   dependency, 1 3 structure or precedence. Distinct rather than borrowed, because the
   legend leads each row with its line style and two kinds sharing one would make that
   row stop being the distinction. */
.mesh-edge-deploys { stroke-dasharray: 10 3 2 3; opacity: 0.85; }
/* A line a notation has named. The dash is the derived picture's free channel and
   ArchiMate's is spoken for — a dashed line with an open arrowhead is a Flow there,
   and a dotted one a Realization — so an ArchiMate line is solid and its two ends
   carry the whole distinction. After the per-kind rules, which is what lets one
   class undo all three of them. */
.mesh-edge-marked { stroke-dasharray: none; opacity: 1; }
/* The marks on those ends. context-stroke takes the colour from the line the marker
   is drawn on, so a hovered edge lit in the accent is lit to its point; where it is
   not understood the fill and stroke attributes on the marks stand and they are the
   resting line colour. Neither reading can be the wrong relationship — that is the
   shape's job, not the colour's. */
.mesh-edge-mark { fill: context-stroke; }
.mesh-edge-mark-open {
  fill: none; stroke: context-stroke; stroke-width: 1.2;
  /* The head of a hairline is a hairline. The line it ends keeps one width at every
     zoom, and an outline that thickened with the picture would read as a different
     mark the further in somebody went. */
  vector-effect: non-scaling-stroke;
}
/* Labels (ADR-0211 §7's legibility half). A few hundred names on one canvas is a
   wall of text with circles behind it, so most names are revealed rather than
   painted: the element is always in the DOM — screen readers and the node's
   aria-label do not depend on hover — and only its ink is hidden, so showing one
   costs a repaint and never a re-layout.

   A label that appears over its neighbours needs to win against them, hence the
   halo (paint-order + a stroke in the page background) and the raised stacking of
   the hovered node. Without it a revealed name is unreadable exactly where the
   graph is dense, which is the only place it was needed. */
.mesh-label { font-size: 15px; fill: var(--text); pointer-events: none; }
.mesh-label-ink {
  opacity: 0; transition: opacity 80ms linear;
  paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round;
}
/* Which names are painted is decided by the zoom, not by the node count: the graph
   is laid out in a world of its own size, so there is always room beside a node for
   its name — what changes is whether that name is large enough on screen to read.
   The tier classes are set from the current viewBox (see labelTier), so names appear
   as the view moves in and nothing is re-rendered to show them. */
.mesh-names-all .mesh-named .mesh-label-ink { opacity: 1; }
.mesh-names-anchors .mesh-application.mesh-named .mesh-label-ink { opacity: 1; }
/* And a name with nowhere to go stays unpainted even at the tier that paints them
   all (see placeCaptions). Two names on top of each other are worse than one name
   and a node whose name you ask for: the overlap costs both of them, and it also
   looks like a rendering fault rather than a full canvas.
   The states in which the reader is asking about this particular node are excluded
   in the selector rather than left to be overridden below: a rule that has to be
   beaten by another rule is a rule that stops working the day somebody adds a class,
   and this one has to lose reliably — hiding a name from the picture is fine, hiding
   it from somebody pointing at the node is not. */
.mesh-names-all .mesh-crowded:not(:hover):not(:focus-visible):not(.mesh-related):not(.mesh-relating-self):not(.mesh-picked) .mesh-label-ink,
.mesh-names-anchors .mesh-crowded.mesh-application:not(:hover):not(:focus-visible):not(.mesh-related):not(.mesh-relating-self):not(.mesh-picked) .mesh-label-ink {
  opacity: 0;
}
/* An application's name is set larger because its circle is: it crosses the
   readable threshold first, which is what makes "where is Billing" answerable
   before the detail is. */
.mesh-application .mesh-label { font-size: 26px; }
.mesh-node:hover .mesh-label-ink,
.mesh-node:focus-visible .mesh-label-ink { opacity: 1; }
/* Pointing at a node shows what it is connected to. Everything unrelated falls
   back rather than disappearing — a landscape with half of it deleted is a
   different landscape, and the question is "what does this touch", not "what if
   the rest were gone". */
.mesh-relating .mesh-node { opacity: 0.22; transition: opacity 90ms linear; }
.mesh-relating .mesh-edge { opacity: 0.08; }
.mesh-relating .mesh-related,
.mesh-relating .mesh-relating-self { opacity: 1; }
.mesh-relating .mesh-related-edge { opacity: 1; stroke: var(--accent); stroke-width: 2.5; }
.mesh-relating .mesh-related .mesh-label-ink,
.mesh-relating .mesh-relating-self .mesh-label-ink { opacity: 1; }
/* Colour on the neighbours as well as on the edges, because lifting them out of the
   fade only says "these are still here" — a ring in the accent says "these are the
   ones". It is a ring outside the circle rather than a recolouring of it: the body's
   own stroke is carrying the severity (ADR-0211 §4), and painting over a red node to
   say "connected" would trade a finding for a hint. The node in hand takes the
   stronger ring, so it is never in doubt which one is being asked about. */
/* non-scaling-stroke for the same reason the edges have it: this ring is drawn in
   world units, and the opening view of a large landscape scales those by about a
   half — which took a 2.5-unit ring to a little over a pixel, exactly where the
   landscape is busiest and the ring is doing the most work. */
.mesh-halo {
  fill: none; stroke: var(--accent); stroke-width: 0; opacity: 0;
  vector-effect: non-scaling-stroke;
}
/* And every node's own outline, for the same reason and not only the ones carrying a
   finding. The outline is what a process *is* on this canvas — the fill is a tint and
   the shape is a rounded square — and drawn in world units it thinned out with the
   estate: 2.2 units came to 1.4 device pixels at 36 nodes and 0.64 at 160, so the
   commonest node in the picture faded as the picture grew. It is the same rule the
   edges, the halo and the severity ring already follow: positions and radii are the
   picture and scale with it, a stroke is a channel and holds its weight. */
.mesh-body { vector-effect: non-scaling-stroke; }
/* Interior line work on a shape whose silhouette does not carry the whole drawing —
   the two edges that make ArchiMate's Node read as a box rather than as a notched
   rectangle. Thinner than the outline and not selectable: it is detail, and a fold
   that answered to the severity and hover rules would be an outline. */
.mesh-body-detail {
  vector-effect: non-scaling-stroke; pointer-events: none;
  stroke: var(--mesh-ink); stroke-width: 1.2; opacity: 0.75;
}
.mesh-relating .mesh-related .mesh-halo {
  stroke-width: 2.5; opacity: 0.7; transition: opacity 90ms linear;
}
.mesh-relating .mesh-relating-self .mesh-halo { stroke-width: 3.5; opacity: 1; }
.mesh-relating .mesh-relating-self .mesh-body { stroke: var(--accent); stroke-width: 3; }
/* The hovered node paints over its neighbours, so its name is legible even where
   the graph is at its densest. SVG has no z-index, so this is document order. */
.mesh-node:hover, .mesh-node:focus-visible { transform-box: fill-box; }
.mesh-node:hover .mesh-body, .mesh-node:focus-visible .mesh-body { stroke-width: 3; }
.mesh-count { font-size: 12px; font-weight: 600; fill: var(--accent); }
/* A heartbeat on the nodes that have a finding.
   Motion is the one channel a landscape has left once colour, size, shape and a
   glyph are all carrying something — and it is the channel the eye finds without
   being pointed at it, which is exactly what a view somebody glances at needs.

   The worse the state, the *less* pulse, which is the metaphor read honestly rather
   than backwards: a process that is degraded is still working and beats quickly and
   twice, like something under strain; one that cannot do work at all beats once,
   slowly and heavily. A landscape of quick pulses is busy and coping; a landscape of
   slow ones is in trouble. Each keeps its own severity colour (§4) instead of both
   going red, because "it is broken" and "something inside it went wrong" are the two
   findings this view exists to tell apart.

   The ring is a circle of its own under the node, so the body's stroke goes on
   carrying the severity and the fill goes on carrying the kind, and the beat is
   added to the picture rather than taken out of it.

   The ring is drawn in world units and the beat is a stroke, so it needs
   non-scaling-stroke for the same reason the severity outline above does — more
   sharply, in fact, because it is thinner to begin with: measured on the fitted view
   at a 1400px window it is 2.4 device pixels at 12 nodes, 0.73 at 160 and 0.52 at
   320. A heartbeat half a pixel wide is not a heartbeat; the motion is still there
   in the DOM and nobody can see it. */
.mesh-beat {
  fill: none; stroke-width: 2.5; opacity: 0; pointer-events: none;
  vector-effect: non-scaling-stroke;
}
.mesh-sev-attention .mesh-beat { stroke: var(--mesh-attention); }
.mesh-sev-critical .mesh-beat { stroke: var(--danger); }
@keyframes mesh-beat-quick {
  0% { transform: scale(1); opacity: 0.6; }
  9% { transform: scale(1.5); opacity: 0; }
  14% { transform: scale(1); opacity: 0.5; }
  23% { transform: scale(1.4); opacity: 0; }
  100% { transform: scale(1); opacity: 0; }
}
@keyframes mesh-beat-slow {
  0% { transform: scale(1); opacity: 0.75; }
  18% { transform: scale(1.85); opacity: 0; }
  100% { transform: scale(1); opacity: 0; }
}
.mesh-beating .mesh-sev-attention .mesh-beat {
  animation: mesh-beat-quick 1.15s ease-out infinite;
}
.mesh-beating .mesh-sev-critical .mesh-beat {
  animation: mesh-beat-slow 3s ease-out infinite;
}
/* Past the budget, and for anybody who has asked their system for less movement, the
   ring stays and stops moving. The finding is still marked; it has simply stopped
   competing for attention it no longer needs to win. */
.mesh-canvas:not(.mesh-beating) .mesh-beat { opacity: 0.35; }
@media (prefers-reduced-motion: reduce) {
  .mesh-beating .mesh-beat { animation: none; opacity: 0.35; }
}
/* Dragging a node. The cursor says the bubbles are things you can pick up before
   anybody tries, and the mark says which ones somebody already has: a node that is
   not where the layout put it is a fact about the picture, and hiding it would make
   the arrangement look like the simulation's own answer. */
.mesh-node { cursor: grab; }
.mesh-node:active { cursor: grabbing; }
.mesh-pin { fill: var(--accent); stroke: var(--surface); stroke-width: 1.5; opacity: 0; }
.mesh-pinned .mesh-pin { opacity: 1; }
.mesh-zoom button:disabled { opacity: 0.45; cursor: default; }
a .mesh-node { cursor: pointer; }
a:hover .mesh-node .mesh-body { stroke-width: 3; }
a:focus-visible .mesh-node .mesh-body { stroke: var(--accent); stroke-width: 3; }
/* A floor, not just a share. It was flex:1 with min-width:0, so once the head
   carried a notation picker and three export buttons the search collapsed to an
   empty box a few pixels wide — a control with no visible purpose, which is worse
   than an absent one. */
.mesh-search {
  /* A width of its own rather than a share of the row: growing to fill pushed the
     notation picker and the export buttons onto lines of their own, and a header in
     three stacked rows is not a header. It shrinks under pressure, down to a floor
     where the placeholder is still readable, and wraps only when the window really
     is too narrow for one line. */
  flex: 0 1 260px; min-width: 140px; max-width: 320px; padding: 6px 10px;
  border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); color: var(--text); font: inherit; font-size: 13px;
}
.mesh-search:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mesh-empty-filter { margin: 0; padding: 32px; text-align: center; color: var(--muted); }
/* Where you are standing, and how you got there. A drilldown is a place rather than
   a setting, so it is stated in the header where the picture is described — and once
   going in is repeatable it is a *path*, so the header carries the path rather than
   one chip with a cross on it.
   The row shrinks before the controls beside it do and scrolls rather than wrapping:
   a walk six nodes deep is a long string, and a header that grows a second line
   every time somebody follows a dependency is a header that moves the picture. */
.mesh-trail {
  display: flex; align-items: center; gap: 4px; flex: 0 1 auto; min-width: 0;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: thin;
}
.mesh-crumb {
  flex: none; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 5px 10px; font: inherit; font-size: 12px; font-weight: 600;
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--bg); color: var(--text);
}
button.mesh-crumb { cursor: pointer; }
button.mesh-crumb:hover { border-color: var(--accent); }
button.mesh-crumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The station you are on is not a button: there is nowhere for it to go, and a
   control that does nothing is worse than a label. It carries the accent instead,
   because "you are here" is the one thing this row has to say at a glance. */
.mesh-crumb-here {
  border-color: var(--accent); background: var(--accent-soft);
}
.mesh-crumb-sep { flex: none; color: var(--muted); font-size: 12px; }
.mesh-unpin {
  align-self: flex-start; padding: 4px 8px; font: inherit; font-size: 12px; cursor: pointer;
  border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--bg); color: var(--text);
}
.mesh-unpin:hover { border-color: var(--accent); }
/* The engine's tally for the selected process: live now, finished ever, last seen.
   Wrapped rather than truncated — three short facts, and the one that gets cut is
   the one somebody was reading. */
.mesh-runtime { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12px; }
.mesh-runtime b { font-size: 13px; }
.mesh-runtime-now b { color: var(--accent); }
/* The maintenance window's members. A row each rather than inline chips: every one
   carries what it costs on its own, which is the number that says which of them is
   driving the window — and that number has nowhere to go on a chip. */
.mesh-window-list {
  list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px;
}
.mesh-window-item { display: flex; align-items: stretch; gap: 6px; }
/* The same two-line button the findings and the impact list use, for the same
   reason: the name is what a reader scans for, so it gets the first line to itself
   and what the node costs goes underneath rather than competing for the width. */
.mesh-window-who {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px;
  text-align: left; padding: 5px 8px; font: inherit; font-size: 12px; cursor: pointer;
  border: 1px solid var(--border); border-left-width: 3px; border-radius: 6px;
  background: var(--surface); color: var(--text);
}
.mesh-window-who:hover { border-color: var(--accent); }
.mesh-window-who.mesh-sev-critical { border-left-color: var(--danger); }
.mesh-window-who.mesh-sev-attention { border-left-color: var(--mesh-attention); }
.mesh-window-name {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* What this member costs on its own, and whether the rest of the window already
   takes it down. Said in words rather than by fading the row: it is still in the
   window and still going down — it just costs nothing extra — and a greyed row
   would read as "not counted". */
.mesh-window-cost { font-size: 11px; color: var(--muted); }
.mesh-window-drop {
  flex: none; width: 22px; height: 22px; line-height: 1; padding: 0; cursor: pointer;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); color: var(--muted); font: inherit; font-size: 13px;
}
.mesh-window-drop:hover { border-color: var(--danger); color: var(--text); }
.mesh-window-close {
  align-self: flex-start; padding: 4px 8px; font: inherit; font-size: 12px; cursor: pointer;
  border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--bg); color: var(--text);
}
.mesh-window-close:hover { border-color: var(--accent); }
.mesh-window-list :focus-visible, .mesh-window-close:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
/* A filtered node on its own answers "does this exist" and nothing else, so the
   filter keeps one hop of neighbourhood around every match. Those are context rather
   than results, and are drawn as such: a search that presented things which do not
   match the search as matches would be a worse answer than the empty field. */
/* Context, not a result: this node is on screen because something next to it
   matched. Faint enough that the answer still reads as the answer, and no fainter —
   context nobody can make out is not context, it is a smudge that makes the picture
   look broken. */
.mesh-context { opacity: 0.75; }

/* Impact analysis (ADR-0211 §6). The canvas and the panel are one answer: the
   highlight says which, the panel says how many and whether the count is a floor. */
.mesh-body { display: flex; gap: 12px; align-items: flex-start; }
.mesh-body .mesh-plot { flex: 1; min-width: 0; }
/* Narrower than it was: the canvas is the view, and 260px of permanent sidebar on
   a 1280px screen took a fifth of the picture for a panel that is empty until
   somebody selects something. */
.mesh-side { width: 210px; flex: none; display: flex; flex-direction: column; gap: 10px; }
.mesh-controls { display: flex; flex-direction: column; gap: 8px; font-size: 13px; }
.mesh-controls label { color: var(--muted); }
.mesh-controls select {
  padding: 5px 8px; border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); color: var(--text); font: inherit; font-size: 13px;
}
/* Depth: a number somebody types, or the whole graph. Two controls on one row
   because there are two kinds of answer — a distance, and "however far it goes" —
   and the field goes inert under "all" rather than disappearing, so the number the
   reader had is still there when they come back to it. */
.mesh-depth { display: flex; align-items: center; gap: 8px; }
.mesh-depth input[type="number"] {
  width: 68px; padding: 5px 8px; border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); color: var(--text); font: inherit; font-size: 13px;
}
/* Element types, product state and approval: one box per value on the picture,
   stacked rather than wrapped into a paragraph of words. A reader ticks these while
   looking at the canvas, so what matters is that the list can be run down with the
   eye and that each row is a target big enough to hit — not that it is compact. The
   headings take the same muted weight as Show and Depth, because they are the same
   kind of control.

   The three lists share one class on purpose. They are one instrument with three
   scales — tick to show, untick to put down — and giving the facet lists their own
   look would suggest they answer to a different rule than the boxes above them. */
.mesh-kinds-head { color: var(--muted); }
/* Air above a heading that follows a list, so the three groups read as three and
   not as one long column with words in it. Only between them: the first heading
   sits under Depth and takes the panel's own spacing. */
.mesh-kinds + .mesh-kinds-head { margin-top: 8px; }
.mesh-kinds { display: flex; flex-direction: column; gap: 2px; }
.mesh-kind {
  display: flex; align-items: center; gap: 6px;
  /* Not muted, unlike the labels above it. Those name a control; these name the
     things on the canvas, and a list of kinds greyed out reads as a list of kinds
     that are switched off. */
  color: var(--text); cursor: pointer;
  /* A long ArchiMate word — "Application Collaboration" — must not widen the column
     the canvas is sharing. */
  overflow-wrap: anywhere;
}
.mesh-kind input { margin: 0; flex: 0 0 auto; }
.mesh-depth input[type="number"]:disabled { color: var(--muted); background: var(--bg); }
.mesh-depth-all {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; color: var(--text); cursor: pointer;
}
.mesh-depth-all input { margin: 0; cursor: pointer; }
.mesh-panel {
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); font-size: 13px; display: flex; flex-direction: column; gap: 8px;
}
.mesh-panel-empty { color: var(--muted); }
.mesh-panel-empty p, .mesh-panel p { margin: 0; }
.mesh-panel-head { display: flex; flex-direction: column; gap: 2px; }
.mesh-impact-count { font-size: 13px; }
.mesh-impact-count b { font-size: 18px; }
/* A truncated answer is a warning, not a footnote: the number above it is a floor. */
.mesh-truncated { color: var(--warn); }
.mesh-drill { font-size: 13px; }
/* The severity mix of a blast radius. Chips rather than a sentence, because the
   thing being read is a comparison between three numbers — and they carry the class
   name beside the colour, since the colour is never the only channel (§4). */
.mesh-impact-mix { display: flex; flex-wrap: wrap; gap: 6px; }
.mesh-impact-chip {
  display: inline-flex; align-items: baseline; gap: 4px; font-size: 11px;
  padding: 2px 7px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg); color: var(--muted);
}
.mesh-impact-chip b { font-size: 13px; color: var(--text); }
.mesh-impact-chip.mesh-sev-critical { border-color: var(--danger); }
.mesh-impact-chip.mesh-sev-attention { border-color: var(--mesh-attention); }
/* The impacted nodes themselves. Same shape as the findings list, because it is the
   same gesture — read a name, go to it — and two lists in one column that behaved
   differently would be two things to learn. */
.mesh-impact-list {
  list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px;
  max-height: 190px; overflow-y: auto;
}
.mesh-impact-go, .mesh-rank-go {
  display: flex; flex-direction: column; gap: 1px; width: 100%; text-align: left;
  padding: 5px 8px; font: inherit; font-size: 12px; cursor: pointer;
  border: 1px solid var(--border); border-left-width: 3px; border-radius: 6px;
  background: var(--surface); color: var(--text);
}
.mesh-impact-go:hover, .mesh-rank-go:hover { border-color: var(--accent); }
.mesh-impact-go.mesh-sev-critical, .mesh-rank-go.mesh-sev-critical { border-left-color: var(--danger); }
.mesh-impact-go.mesh-sev-attention, .mesh-rank-go.mesh-sev-attention { border-left-color: var(--mesh-attention); }
.mesh-impact-who, .mesh-rank-who {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mesh-impact-hop, .mesh-rank-count { color: var(--muted); font-size: 11px; }
.mesh-rank-count b { font-size: 13px; color: var(--text); }
/* The ranking: the impact question asked of the whole landscape rather than of one
   node, so it sits under the findings and reads the same way. */
.mesh-rank { display: flex; flex-direction: column; gap: 6px; font-size: 13px; }
/* Stacked rather than justified: the heading and what it measures are both long,
   and side by side in a sidebar they squeeze each other into columns of one word. */
.mesh-rank-head { display: flex; flex-direction: column; gap: 1px; }
.mesh-rank-head .muted { font-size: 11px; }
.mesh-rank-list {
  list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px;
}
.mesh-rank :focus-visible, .mesh-impact-list :focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
/* Findings: the same nodes the picture marks, as an index. On four hundred circles,
   spotting three red dots is hunting, and hunting is what somebody does instead of
   noticing — so the findings are also a list, worst first, and clicking one goes
   there. */
.mesh-findings { display: flex; flex-direction: column; gap: 6px; font-size: 13px; }
.mesh-findings-head { display: flex; align-items: baseline; gap: 6px; justify-content: space-between; }
.mesh-findings-head .muted { font-size: 11px; }
.mesh-findings-list {
  list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px;
  /* Bounded, because the sidebar is not the page: a hundred findings must not push
     the saved views off the bottom of a column somebody is trying to use. */
  max-height: 220px; overflow-y: auto;
}
.mesh-finding-go {
  display: flex; flex-direction: column; gap: 1px; width: 100%; text-align: left;
  padding: 5px 8px; font: inherit; font-size: 12px; cursor: pointer;
  border: 1px solid var(--border); border-left-width: 3px; border-radius: 6px;
  background: var(--surface); color: var(--text);
}
.mesh-finding-go:hover { border-color: var(--accent); }
.mesh-finding-go.mesh-sev-critical { border-left-color: var(--danger); }
.mesh-finding-go.mesh-sev-attention { border-left-color: var(--mesh-attention); }
.mesh-finding-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mesh-finding-state { color: var(--muted); font-size: 11px; }
/* The sentence behind the finding, clamped: an operator needs to see there is one
   and read it on the node itself, and three lines of reason each would turn the list
   back into the thing it replaced. */
.mesh-finding-why {
  color: var(--muted); font-size: 11px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.mesh-findings :focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Where in the process the parked work is. Indented under the finding it belongs to,
   because an element id is only meaningful with the process it is in — the same
   reason a collapsed application carries no sites at all. */
.mesh-sites {
  list-style: none; margin: 2px 0 0; padding: 0 0 0 10px;
  border-left: 2px solid var(--border); display: flex; flex-direction: column; gap: 3px;
}
.mesh-sites li { display: flex; flex-direction: column; gap: 1px; font-size: 11px; }
.mesh-site-where code { font-size: 11px; }
.mesh-site-count { color: var(--warn); font-weight: 600; }
.mesh-site-why {
  color: var(--muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* Saved views. A landscape somebody watches is a setup — a filter, a zoom, an
   arrangement — and without this it has to be rebuilt on every visit. The list is
   names because opening one changes five controls at once; a name is the only handle
   small enough to keep in a column this narrow. */
.mesh-views { display: flex; flex-direction: column; gap: 6px; font-size: 13px; }
.mesh-views-head { display: flex; align-items: baseline; gap: 6px; justify-content: space-between; }
.mesh-views-head .muted { font-size: 11px; }
.mesh-view-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.mesh-view-list li { display: flex; gap: 4px; align-items: stretch; }
.mesh-view-empty { color: var(--muted); font-size: 12px; padding: 2px 0; }
.mesh-view-open {
  flex: 1; min-width: 0; text-align: left; padding: 5px 8px; font: inherit; font-size: 13px;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface);
  color: var(--text); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mesh-view-open:hover { border-color: var(--accent); }
.mesh-view-drop {
  flex: none; width: 26px; padding: 0; font: inherit; font-size: 15px; line-height: 1;
  border: 1px solid var(--border); border-radius: 6px; background: var(--bg);
  color: var(--muted); cursor: pointer;
}
.mesh-view-drop:hover { color: var(--danger); border-color: var(--danger); }
.mesh-view-save { display: flex; gap: 4px; }
.mesh-view-save input {
  flex: 1; min-width: 0; padding: 5px 8px; font: inherit; font-size: 13px;
  border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); color: var(--text);
}
.mesh-view-save button {
  flex: none; padding: 5px 10px; font: inherit; font-size: 13px;
  border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--bg); color: var(--text); cursor: pointer;
}
.mesh-view-save button:hover { border-color: var(--accent); }
.mesh-views :focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mesh-node:focus-visible .mesh-body { stroke: var(--accent); stroke-width: 3; }
.mesh-in-impact .mesh-body { stroke-width: 3; }
line.mesh-in-impact { stroke: var(--accent); stroke-width: 2.5; }
/* The nodes the question is *about*, as against the ones that come with them. With
   one selected the distinction is idle — the answer is obviously about the node you
   clicked. With a maintenance window of three it is the whole picture: a lit blob
   that does not say which of its nodes are the ones going down cannot be checked
   against the set the reader assembled. A second ring outside the outline rather
   than a colour, so it survives beside a severity that already owns the outline. */
.mesh-picked .mesh-halo { stroke-width: 2; opacity: 1; }
/* Dimmed rather than hidden: the rest of the landscape stays visible as context,
   so an impact set reads as a part of the whole rather than as the whole. */
/* Outside the impact set. It was 0.22, which on white is nearly gone: the answer
   stood out because everything else had been erased, and a reader could no longer
   see what the highlighted nodes were highlighted *against*. An impact set is a
   part of a whole, and the whole has to stay legible for the part to mean
   anything. */
.mesh-dimmed { opacity: 0.42; }
@media (max-width: 1180px) {
  /* Narrow enough that one line would squeeze the search past reading. Wrapping puts
     the notation and the exports on a line of their own, which is a header of two
     rows rather than a control nobody can use. */
  .mesh-head { flex-wrap: wrap; }
}
@media (max-width: 900px) {
  .mesh-body { flex-direction: column; }
  .mesh-side { width: 100%; }
  .mesh-plot { width: 100%; }
}

/* Atlas bindings in the Panorama properties panel (ADR-0189 §4). A binding shows
   the server-resolved name; the id stays visible because it is what the document
   actually stores and what another tool will read. */
.panorama-binding { padding: 8px 0; border-bottom: 1px solid var(--border); }
.panorama-binding:last-child { border-bottom: 0; }
.panorama-binding-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.panorama-binding-head span { font-size: 13px; color: var(--muted); }
.panorama-binding p { margin: 4px 0 0; font-size: 13px; }
.panorama-bound-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.panorama-bound { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; font-size: 13px; }
.panorama-bound code { font-size: 12px; }
/* An unresolved binding is shown rather than hidden: removing it would make a
   broken binding look like an absent one, and the model would then look correct. */
.panorama-bound.unresolved { color: var(--warn); }
.panorama-unresolved { font-size: 12px; }
.panorama-pick { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.panorama-pick label { display: flex; align-items: baseline; gap: 8px; cursor: pointer; font-size: 13px; }

/* Provenance on the landscape mesh (ADR-0211 §2): derived, modeled, or both. Shape
   carries it as well as colour — a picture that mixed what Atlas found with what
   somebody declared, without saying which is which, is the conflation the record
   exists to prevent. */
.mesh-prov-modeled .mesh-label { font-style: italic; }
.mesh-prov-modeled circle { opacity: 0.9; }

/* Severity (ADR-0211 §4). The badge is a glyph on a filled dot, so the class is
   readable without colour perception; colour is the second channel, never the only
   one. Node fills stay as the kind and the ArchiMate layer set them (ADR-0189 §6). */
.mesh-badge-dot { fill: var(--surface-fill); stroke: var(--border-strong); stroke-width: 1.5; }
.mesh-badge-glyph { font-size: 10px; font-weight: 700; fill: var(--text); }
/* Both findings are drawn as a *filled* badge, and that is the correction.
   Critical always was; attention was a pale wash inside a thin ochre outline, so
   the two classes differed in form as well as in colour — and the form was doing
   most of the work. A tinted outline beside a solid disc does not read as "less
   urgent", it reads as "not really a finding", which is not what attention means.
   Each keeps its own hue, and each takes the glyph colour that is legible on it:
   white on the red, near-black on the amber. */
.mesh-sev-critical .mesh-badge-dot { fill: var(--danger); stroke: var(--danger); }
.mesh-sev-critical .mesh-badge-glyph { fill: #fff; }
.mesh-sev-attention .mesh-badge-dot { fill: var(--mesh-attention); stroke: var(--mesh-attention); }
.mesh-sev-attention .mesh-badge-glyph { fill: #241a05; }
/* A node with a finding wears a heavier ring than one without. Colour alone had to
   carry the whole difference before, at the same weight as every other outline on
   the canvas; weight is the channel that survives being small, being zoomed out,
   and being looked at by somebody who does not separate red from amber.

   Weight only survives being zoomed out if it is not itself zoomed out, which it was:
   the outline is drawn in world units, and the opening view of a large estate scales
   those hard. Measured on the fitted view at a 1400px window: at 12 nodes the red
   ring is 3.3 device pixels, at 160 it is 1.0, and at 320 — inside the 400-node
   budget ADR-0211 §7 sets — it is 0.73, which antialiases to a grey smudge. So the
   channel that was supposed to survive being small disappeared exactly where the
   landscape got big enough to need it. non-scaling-stroke, for the same reason the
   edges and the hover halo already have it: positions and radii are the picture and
   scale with it; a stroke is a channel and has to stay legible at every scale. */
.mesh-sev-attention .mesh-body, .mesh-sev-critical .mesh-body { stroke-width: 3.5; }
.mesh-node.mesh-sev-attention:hover .mesh-body,
.mesh-node.mesh-sev-critical:hover .mesh-body { stroke-width: 4.5; }
.mesh-sev-unknown .mesh-badge-dot { fill: var(--canvas-fill); stroke: var(--muted); }
.mesh-sev-unknown .mesh-badge-glyph { fill: var(--muted); }
.mesh-sev-glyph { font-size: 10px; font-weight: 700; fill: var(--text); }
.mesh-finding {
  display: flex; flex-direction: column; gap: 2px; font-size: 13px;
  border-left: 3px solid var(--border-strong); padding-left: 8px;
}
.mesh-finding p { margin: 0; color: var(--muted); }
.mesh-finding.mesh-sev-critical { border-left-color: var(--danger); }
.mesh-finding.mesh-sev-attention { border-left-color: var(--warn); }
.mesh-finding.mesh-sev-ok { border-left-color: var(--ok); }

/* The C4 projection (ADR-0211 §8). Structure, relationships, and — never optional —
   what the mapping could not express. */
.c4-panel { padding: 16px 20px; overflow: auto; height: 100%; background: var(--surface); }
.c4-head h2 { margin: 0 0 4px; font-size: 16px; }
.c4-head p { margin: 0 0 12px; font-size: 13px; }
.c4-tree, .c4-list { list-style: none; margin: 0; padding-left: 0; font-size: 13px; }
.c4-tree .c4-tree { padding-left: 18px; border-left: 1px solid var(--border); margin-top: 6px; }
.c4-tree > li, .c4-list > li { padding: 5px 0; }
.c4-tree p { margin: 2px 0 0; font-size: 12px; }
.c4-type {
  display: inline-block; padding: 1px 6px; margin-right: 6px; border-radius: 4px;
  background: var(--accent-soft); color: var(--accent); font-size: 11px;
}
.c4-reason { color: var(--warn); font-size: 12px; }
.c4-loss h3 { color: var(--warn); }

/* ---------- Playground tab (ADR-0215) ----------
   The Playground runs the diagram on a throwaway sandbox of the real engine. Its
   control strip borrows .sim-bar from the Design view's token simulation, and its
   side panel borrows .vars-panel, so the two play modes read as siblings; only the
   panel's own rows are styled here. The canvas needs nothing new: a playground run
   reuses the runtime view's .atlas-visited / .atlas-active markers and token
   badges, which is the point — a sandbox run should look like what it simulates. */
.pg-sec {
  display: flex; align-items: baseline; gap: 6px;
  margin: 12px 8px 4px; padding-top: 10px; border-top: 1px solid var(--border);
  font-size: 12px;
}
.pg-sec:first-child { border-top: 0; padding-top: 0; }

/* The setup is three numbered steps, each with a one-line readout of what it will
   contribute to the run — or of what is wrong with it. The number is filled once
   the step has something to give, but the state is never carried by that colour
   alone: the note beside it says it in words, which is also the only form of it
   that says *what*. */
.pg-step-n {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 16px; height: 16px; border-radius: 999px;
  font-size: 10px; font-weight: 700; line-height: 1;
  background: var(--border); color: var(--muted);
}
.pg-step-n.done { background: var(--accent); color: var(--accent-ink); }
.pg-step-note {
  margin-left: auto; min-width: 0; font-size: 11px; color: var(--muted);
  text-align: right; white-space: normal;
}
.pg-step-note.bad { color: var(--danger); }
.pg-task {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px; border-radius: 6px; font-size: 12px;
}
.pg-task:hover { background: var(--bg); }
.pg-task > div { min-width: 0; flex: 1; word-break: break-word; }
.pg-task b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.pg-result { padding: 4px 8px; font-size: 12px; display: flex; flex-direction: column; gap: 6px; }
.pg-result .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-word; }
.pg-inc {
  display: inline-block; margin-left: 6px; padding: 0 6px; border-radius: 999px;
  font-size: 11px; font-weight: 600; color: var(--danger); background: var(--danger-soft);
}
.pg-vars { width: 100%; border-collapse: collapse; font-size: 12px; }
.pg-vars td { padding: 3px 4px; border-bottom: 1px solid var(--border); vertical-align: top; }
.pg-vars td:first-child { color: var(--muted); white-space: nowrap; }
.pg-vars td.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-word; }
#pg-panel .field { margin: 10px 8px 4px; display: flex; flex-direction: column; gap: 4px; font-size: 12px; }
#pg-panel .field span { color: var(--muted); }
#pg-panel textarea {
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px;
  color: var(--text); background: var(--bg); resize: vertical;
}
#pg-panel textarea:focus { outline: none; border-color: var(--accent); background: var(--surface); }
#pg-body > p.muted { padding: 10px 8px; font-size: 12px; line-height: 1.5; }
/* The Playground lays the editor body out as a grid: setup on the left, the diagram
   in the middle with the cases under it, the analysis on the right.

   Three columns rather than one panel because the mode asks three questions at once
   and they are not the same question — what the run will be, what it did, and which
   case it was. Stacked into one 300 px column, as this began, a finished run pushed
   the dataset off the screen, and changing one figure meant scrolling past everything
   the last run produced.

   A grid rather than nested elements: #canvas is shared with the Design and Implement
   views and with bpmn-js, so it keeps its place in the DOM and is only *placed*
   differently here. */
.editor.pg-active .editor-body {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr) 330px;
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "setup overlay  analysis"
    "setup canvas   analysis"
    "setup results  analysis";
}
.editor.pg-active #pg-setup { grid-area: setup; border-left: 0; border-right: 1px solid var(--border); }
.editor.pg-active #pg-overlay { grid-area: overlay; }
.editor.pg-active #canvas { grid-area: canvas; height: auto; min-width: 0; min-height: 0; }
.editor.pg-active #pg-results { grid-area: results; }
.editor.pg-active #pg-panel { grid-area: analysis; }
/* The properties panel steps aside: the mode already owns two side panels, and a
   third would leave the diagram — the thing being watched — a sliver. */
.editor.pg-active .props, .editor.pg-active #props-resizer { display: none; }
/* Narrower windows give the columns back to the diagram before they give up a
   column: two 250 px columns still read, a 700 px canvas with three panels does not. */
@media (max-width: 1400px) {
  .editor.pg-active .editor-body { grid-template-columns: 252px minmax(0, 1fr) 280px; }
}

/* The overlay switcher, over the diagram: which of the run's four measures shades
   it, and the scale that shading means. On the canvas rather than in a column
   because it is a control for the canvas — and a legend has to sit beside the thing
   it explains, or it is a key to a map on another page. */
.pg-overlay {
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  padding: 5px 10px; border-bottom: 1px solid var(--border); background: var(--surface);
  font-size: 12px;
}
.pg-overlay > div { display: contents; }
.pg-overlay button {
  padding: 3px 9px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); color: var(--muted); font: 500 11.5px/1.4 inherit; cursor: pointer;
}
.pg-overlay button:hover { color: var(--text); }
.pg-overlay button.active { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }

/* The scale, in the same five shades the diagram uses. A colour means nothing until
   it says what its darkest end is worth. */
.pg-scale { display: inline-flex; align-items: center; gap: 3px; }
.pg-scale i { display: inline-block; width: 16px; height: 8px; border-radius: 2px; }
.pg-heat-key-1 { background: #7aa7ff; }
.pg-heat-key-2 { background: #4d86ff; }
.pg-heat-key-3 { background: #c07ad6; }
.pg-heat-key-4 { background: #e8783c; }
.pg-heat-key-5 { background: #d93025; }

/* A per-case rule is two lines — which cases, and what they have to show — because
   both halves are expressions and neither fits beside the other in a column this
   narrow. The end-event picker sits with the "then" half: it writes the assertion
   rather than being it. */
.pg-rule { margin: 6px 8px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
.pg-rule:last-of-type { border-bottom: 0; }
.pg-rule-head { display: flex; align-items: center; gap: 4px; margin-bottom: 4px; }
.pg-rule-head > .muted { flex: none; width: 30px; font-size: 11px; }
.pg-rule-head input, .pg-rule-head select {
  font: 12px/1.4 inherit; padding: 4px 6px; border: 1px solid var(--border);
  border-radius: 6px; background: var(--bg); color: var(--text); min-width: 0; flex: 1;
}
.pg-rule-head input { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.pg-rule-head select { flex: 0 0 92px; }
.pg-rule-head input:focus, .pg-rule-head select:focus {
  outline: none; border-color: var(--accent); background: var(--surface);
}
.pg-rule-head .icon-btn { flex: none; }

/* How a rule went: the split a single check line cannot carry, and the cases that
   broke it. A count sends somebody looking; case numbers send them to the rows. */
.pg-rule-result {
  margin: 6px 8px; padding: 6px 8px; border-radius: 6px;
  background: var(--bg); font-size: 12px;
}
.pg-rule-result.bad { background: var(--danger-soft); }
.pg-rule-name { font-weight: 600; word-break: break-word; }
.pg-rule-split { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 3px; }
.pg-rule-split .bad { color: var(--danger); }
.pg-rule-cases { margin-top: 3px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }

/* The cases, under the diagram, because a table is the one shape a 300 px column
   cannot hold. It is a band rather than a pane: tall enough to read a dozen rows,
   never tall enough to take the diagram's place. */
.pg-results {
  display: flex; flex-direction: column; min-height: 0; max-height: 30vh;
  border-top: 1px solid var(--border); background: var(--surface);
}
.pg-results-head {
  display: flex; align-items: center; gap: 8px; flex: none;
  padding: 8px 12px; border-bottom: 1px solid var(--border); font-size: 12px;
}
/* How many cases in the whole run are worth looking at, beside the page the strip
   is showing. It sits with the count rather than in the analysis column because it
   is the question this strip is asked: is there anything here to page towards? The
   mark and the colour are both redundant — the sentence says it. */
.pg-dev { color: var(--muted); }
.pg-dev.bad { color: var(--danger); }
.pg-results-scroll { overflow: auto; min-height: 0; }
.pg-cases { min-width: 100%; }
.pg-cases th { position: sticky; top: 0; z-index: 1; background: var(--surface); }
/* A row is a way into its own case, so it looks like one. The open row stays marked
   while the diagram shows it: the number in the strip and the row under it have to
   be visibly the same case. */
.pg-cases tbody tr { cursor: pointer; }
.pg-cases tbody tr.pg-open { background: var(--accent-soft); }
.pg-cases tbody tr.pg-open td:first-child {
  font-weight: 600; color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent);
}

/* While a case is on the diagram the strip names it instead of offering measures. */
.pg-overlay b { font-weight: 600; }
/* And so do the modelling affordances. The Playground *runs* the diagram — the
   sandbox compiled the model as it stood when it started, so a shape added here
   would not be in what is running, and the palette offering it is the panel
   promising something it cannot do. The token simulation hides them for the same
   reason; this mode was left out. */
.editor.pg-active .djs-palette,
.editor.pg-active .djs-context-pad,
.editor.pg-active .djs-direct-editing-parent { display: none !important; }

/* Step and Batch are two ways of driving one sandbox, so they are tabs inside the
   panel rather than two panels or two tabs in the editor's own bar: the sandbox,
   the seed and the policy are shared, and only the question changes. */
.pg-modes { display: flex; gap: 4px; margin: 8px; }
.pg-modes button {
  flex: 1; padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); color: var(--muted); font: 600 12px/1.4 inherit; cursor: pointer;
}
.pg-modes button:hover { color: var(--text); }
.pg-modes button.active { background: var(--surface); color: var(--text); border-color: var(--border-strong); }

/* A pool per task, named and sized where the author can see which task they mean.
   Two tasks given the same pool name share one, which is how a shared team is
   modelled without a second screen. */
.pg-pool { display: grid; grid-template-columns: 1fr 76px 60px; gap: 6px; align-items: center; margin: 4px 8px; }
.pg-pool label { font-size: 12px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pg-pool input, .pg-timing input, .pg-timing select {
  font: 12px/1.4 inherit; padding: 4px 6px; border: 1px solid var(--border);
  border-radius: 6px; background: var(--bg); color: var(--text); min-width: 0;
}
.pg-pool input:focus, .pg-timing input:focus, .pg-timing select:focus {
  outline: none; border-color: var(--accent); background: var(--surface);
}
.pg-check { display: flex; align-items: center; gap: 6px; margin: 8px; font-size: 12px; color: var(--muted); }
.pg-timing { display: flex; align-items: center; gap: 6px; margin: 4px 8px; font-size: 12px; }
.pg-timing select { flex: 1; }
.pg-timing input { width: 64px; }
.pg-file { display: flex; align-items: center; gap: 8px; margin: 6px 8px; font-size: 12px; flex-wrap: wrap; }
.pg-file input[type="file"] { font-size: 11px; max-width: 100%; }

/* Three ways in, one at a time. Only one of them drives the run, so showing all
   three at once would invite filling in two and wondering which won. */
.pg-source { display: flex; gap: 4px; margin: 6px 8px; }
.pg-source button {
  flex: 1; padding: 4px 8px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); color: var(--muted); font: 500 11.5px/1.4 inherit; cursor: pointer;
}
.pg-source button:hover { color: var(--text); }
.pg-source button.active { background: var(--surface); color: var(--text); border-color: var(--border-strong); }

/* A described field takes two lines: what it is called and how it is drawn, then
   that kind's parameters. One line does not fit a panel this narrow — a name beside
   three boxes shows "amount" as "amour", and a date field with two offsets and a
   checkbox is unreadable. The parameters share the second line, so the block keeps
   its shape as the kind changes under it. */
.pg-fields { display: flex; flex-direction: column; gap: 8px; margin: 6px 8px; }
.pg-field { display: flex; flex-direction: column; gap: 4px; }
.pg-field-head, .pg-field-args { display: flex; align-items: center; gap: 4px; }
/* The kind is the longer label of the two on its line, so it gets the wider share
   — "whole number" reading as "whole numbe" is the panel lying about the choice. */
.pg-field-head select { flex: 1.25; }
.pg-field input[type="text"], .pg-field input[type="number"], .pg-field select {
  font: 12px/1.4 inherit; padding: 4px 6px; border: 1px solid var(--border);
  border-radius: 6px; background: var(--bg); color: var(--text); min-width: 0; flex: 1;
}
.pg-field input:focus, .pg-field select:focus {
  outline: none; border-color: var(--accent); background: var(--surface);
}
.pg-field input[type="checkbox"] { flex: none; }
.pg-field .pg-check { margin: 0; flex: none; white-space: nowrap; font-size: 11px; }
.pg-field .icon-btn { flex: none; }

/* The preview is small on purpose: it is read to check that an amount looks like
   an amount, not to inspect the dataset. */
.pg-preview { margin: 4px 8px; overflow-x: auto; }
.pg-preview table { width: 100%; border-collapse: collapse; font-size: 11.5px; }
.pg-preview th {
  text-align: left; padding: 3px 6px; color: var(--muted); font-weight: 600;
  border-bottom: 1px solid var(--border-strong); white-space: nowrap;
}
.pg-preview td {
  padding: 3px 6px; border-bottom: 1px solid var(--border); white-space: nowrap;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* A batch reports its progress from the O(1) status endpoint, so the bar moves
   even on a run of fifty thousand cases. */
.pg-progress { height: 6px; margin: 6px 8px; border-radius: 999px; background: var(--bg); overflow: hidden; }
.pg-progress-fill { height: 100%; background: var(--accent); transition: width .3s ease; }
.pg-run-line { display: flex; justify-content: space-between; gap: 8px; margin: 0 8px; font-size: 12px; }

/* The report's headline numbers, four to a row and large enough to read without
   hunting for them in a table. */
.pg-facts { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 8px; }
.pg-facts > div {
  flex: 1 1 60px; padding: 6px 8px; border: 1px solid var(--border);
  border-radius: 6px; background: var(--bg); min-width: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.pg-facts b { display: block; font-size: 15px; color: var(--text); }
.pg-facts b.bad { color: var(--danger); }
.pg-facts span { font-size: 11px; color: var(--muted); }
.pg-table { width: 100%; border-collapse: collapse; font-size: 11.5px; margin: 2px 0 4px; }
/* The headers wrap and the numbers do not: the panel is narrow, and a column
   header cut off mid-word ("longes") is a worse trade than a two-line heading. */
.pg-table th {
  text-align: left; font-weight: 600; color: var(--muted); padding: 3px 6px;
  border-bottom: 1px solid var(--border); white-space: normal; line-height: 1.25;
}
.pg-table td { padding: 3px 6px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.pg-table td.mono { max-width: 90px; overflow: hidden; text-overflow: ellipsis; }
.pg-table td.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.pg-table tbody tr:hover { background: var(--bg); }

/* The run over simulated time: bars for arrivals and completions, a line for the
   work in progress. Drawn as one SVG so it needs no chart library — the buildless
   UI (ADR-0012) pays for itself here. */
.pg-chart { display: block; width: calc(100% - 16px); height: 56px; margin: 4px 8px; }
.pg-bar-in { fill: var(--accent); opacity: .35; }
.pg-bar-out { fill: var(--ok); opacity: .45; }
.pg-line { fill: none; stroke: var(--warn); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.pg-base { stroke: var(--border-strong); stroke-width: 1; vector-effect: non-scaling-stroke; }
.pg-legend { display: flex; flex-wrap: wrap; gap: 10px; margin: 2px 8px; font-size: 11px; color: var(--muted); }
.pg-legend i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 4px; vertical-align: -1px; }
.pg-key-in { background: var(--accent); opacity: .35; }
.pg-key-out { background: var(--ok); opacity: .45; }
.pg-key-flight { background: var(--warn); }
.pg-axis { display: flex; justify-content: space-between; gap: 6px; margin: 0 8px 4px; font-size: 10.5px; color: var(--muted); }

/* The arrival stream before it runs: an area under a line, one hue, scaled to its
   own fullest slice. It is drawn small on purpose — it sits between the timing
   boxes and the expectations, and its job is to show the *shape* the numbers make
   (a Poisson burst is not a takt) while the exact counts stay in the line under it
   and in the per-slice tooltips. */
.pg-spark { display: block; width: calc(100% - 16px); height: 34px; margin: 6px 8px 2px; }
.pg-spark-fill { fill: var(--accent); opacity: .12; }
.pg-spark-line { fill: none; stroke: var(--accent); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.pg-spark-hit { fill: transparent; }
.pg-spark-note { margin: 0 8px 6px; font-size: 11px; }

/* A number with its own magnitude under it: a track along the foot of the cell or
   the tile, filled to the value's share of the column's scale.
   The empty part of the track is what makes it a gauge. Without it a full value
   underlines itself and a small one reads as a stray mark; with it, one glance down
   the column says which row is the big one. The number stays in text ink beside it,
   so nothing here is carried by a length alone. */
.pg-track {
  display: block; height: 3px; margin-top: 4px; border-radius: 999px;
  background: var(--border); overflow: hidden;
}
.pg-track i { display: block; height: 100%; border-radius: 999px; background: var(--accent); opacity: .55; }
/* In a table the track is lifted out of the flow into a strip every row reserves,
   so one number's gauge does not sit its own cell's text higher than the cells
   beside it. */
.pg-bottlenecks td, .pg-pools td, .pg-cases td { padding-bottom: 9px; }
.pg-meter { position: relative; min-width: 52px; }
.pg-meter .pg-track { position: absolute; left: 6px; right: 6px; bottom: 3px; margin: 0; }
/* On a tile the gauges are compared with each other, so they sit on one line at the
   foot of their tiles however many lines the number above them wrapped to. */
.pg-facts .pg-track { margin-top: auto; }

/* Where the cases came out. The end event carries the name somebody drew, which is
   a phrase rather than an id, so it wraps where the counts beside it do not. An end
   event the run never reached keeps its row and says so by being recessive — the
   branch nobody took is a finding, and a row that looked like the others would let
   a zero pass for a number. */
.pg-ends td { padding-bottom: 9px; }
.pg-ends td:first-child { white-space: normal; width: 99%; }
.pg-ends tr.pg-unreached td { color: var(--muted); }

/* The paths the data never took, listed rather than only shaded: on a large
   diagram a cold branch is easier to read from a list than to find by colour. */
.pg-cold { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 8px; }
.pg-cold span {
  padding: 1px 6px; border-radius: 4px; font-size: 11px;
  color: var(--muted); background: var(--bg); border: 1px dashed var(--border-strong);
}
.pg-actions { display: flex; gap: 6px; margin: 12px 8px; }

/* A verdict is the one thing on this panel somebody reads first, so it is a badge
   beside the heading rather than a line inside a table they have to find. */
.pg-verdict {
  padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 600;
}
.pg-verdict.ok { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); }
.pg-verdict.bad { color: var(--danger); background: var(--danger-soft); }
.pg-table tr.pg-bad td { color: var(--danger); }
.pg-table td:first-child { width: 14px; }
/* The check and delta tables carry a label and two numbers in a narrow panel. The
   label wraps and the numbers do not: a "got" column cut off mid-value is the one
   thing on this table nobody can do without. */
.pg-checks td:nth-child(2), .pg-deltas td:first-child { white-space: normal; width: 99%; }
.pg-checks td:nth-child(3), .pg-checks td:nth-child(4),
.pg-deltas td:nth-child(2), .pg-deltas td:nth-child(3) { text-align: right; }
/* A comparison colours only the side that moved, and only where moving has a
   direction: utilisation falls for two opposite reasons and gets neither colour. */
.pg-better { color: var(--ok); font-weight: 600; }
.pg-worse { color: var(--danger); font-weight: 600; }

/* ---- Heat map on the canvas ----
   Six levels: pg-heat-0 is "no token ever came here", which is a different
   statement from "came here least" and has to look different — dashed and pale,
   the way an unbuilt path looks on a map. The five above it ramp from the app's
   own accent to a warm red, so the busiest part of the process is the one the eye
   lands on first. Both shapes and connections take the same classes, because a
   sequence flow carrying five thousand tokens is as much of a finding as a task
   doing so. */
.djs-shape.pg-heat-0 .djs-visual > :nth-child(1) {
  stroke: var(--border-strong) !important; stroke-width: 1.5px !important;
  stroke-dasharray: 3 4 !important; opacity: .65 !important;
}
.djs-shape.pg-heat-1 .djs-visual > :nth-child(1) { stroke: #7aa7ff !important; stroke-width: 2px !important; }
.djs-shape.pg-heat-2 .djs-visual > :nth-child(1) { stroke: #4d86ff !important; stroke-width: 2.5px !important; }
.djs-shape.pg-heat-3 .djs-visual > :nth-child(1) { stroke: #c07ad6 !important; stroke-width: 3px !important; }
.djs-shape.pg-heat-4 .djs-visual > :nth-child(1) { stroke: #e8783c !important; stroke-width: 3.5px !important; }
.djs-shape.pg-heat-5 .djs-visual > :nth-child(1) { stroke: #d93025 !important; stroke-width: 4px !important; }

/* A connection is selected by element, not by position: the first child of its
   visual is a <defs> holding the arrowhead, so :nth-child(1) paints the
   definitions and leaves the line itself black — which is exactly what it did.
   The arrowhead is styled through that marker, or a hot flow ends in a black tip. */
.djs-connection.pg-heat-0 .djs-visual > path {
  stroke: var(--border-strong) !important; stroke-width: 1.5px !important;
  stroke-dasharray: 3 4 !important; opacity: .65 !important;
}
.djs-connection.pg-heat-1 .djs-visual > path { stroke: #7aa7ff !important; stroke-width: 2px !important; }
.djs-connection.pg-heat-2 .djs-visual > path { stroke: #4d86ff !important; stroke-width: 2.5px !important; }
.djs-connection.pg-heat-3 .djs-visual > path { stroke: #c07ad6 !important; stroke-width: 3px !important; }
.djs-connection.pg-heat-4 .djs-visual > path { stroke: #e8783c !important; stroke-width: 3.5px !important; }
.djs-connection.pg-heat-5 .djs-visual > path { stroke: #d93025 !important; stroke-width: 4px !important; }
.djs-connection.pg-heat-0 .djs-visual marker path { fill: var(--border-strong) !important; stroke: var(--border-strong) !important; }
.djs-connection.pg-heat-1 .djs-visual marker path { fill: #7aa7ff !important; stroke: #7aa7ff !important; }
.djs-connection.pg-heat-2 .djs-visual marker path { fill: #4d86ff !important; stroke: #4d86ff !important; }
.djs-connection.pg-heat-3 .djs-visual marker path { fill: #c07ad6 !important; stroke: #c07ad6 !important; }
.djs-connection.pg-heat-4 .djs-visual marker path { fill: #e8783c !important; stroke: #e8783c !important; }
.djs-connection.pg-heat-5 .djs-visual marker path { fill: #d93025 !important; stroke: #d93025 !important; }

.token-badge.heat { background: var(--muted); box-shadow: 0 1px 3px rgba(0,0,0,.25); }

/* ---- Data objects tab (ADR-0230) -----------------
   The instance's data objects: what each datum is, what state it is in, what it
   holds, and which element put it there. It reuses .vt so a data object and a
   variable read alike in the same inspector — they are the same shape of thing,
   and the columns a variable does not have (class, state, written by) are what
   this tab exists to add. */
.vt.do-table .c-name { width: 26%; }
.vt.do-table .do-c-class, .vt.do-table .do-c-state, .vt.do-table .do-c-by { width: 1%; white-space: nowrap; }
.vt.do-table .do-c-at { width: 1%; white-space: nowrap; color: var(--muted); font: 12px/1.5 ui-monospace, monospace; }

.do-toggle {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%; min-width: 0;
  font: inherit; cursor: pointer; text-align: left; padding: 0; border: 0; background: none; color: inherit;
}
.do-toggle:hover .do-name { color: var(--accent); }
.do-toggle .v-chev { color: var(--muted); flex: none; font-size: 10px; }
.do-toggle:hover .v-chev { color: var(--accent); }

/* The declared class is the model's word for what this datum *is* — the type slot BPMN
   leaves opaque. It reads as a type, not as a value, so it is set apart like a tag. */
.do-class {
  font: 600 11px/1.5 system-ui, sans-serif; color: var(--accent);
  border: 1px solid var(--accent-soft); border-radius: 5px; padding: 1px 6px; white-space: nowrap;
}
.do-class.none { color: var(--muted); border-color: var(--border); font-weight: 500; font-style: italic; }
.do-coll {
  font: 600 10px/1.5 system-ui, sans-serif; color: var(--muted); text-transform: uppercase; letter-spacing: .04em;
  border: 1px solid var(--border); border-radius: 4px; padding: 0 4px; margin-left: 6px;
}
/* The data state is the per-datum lifecycle — the one thing a variable has no
   equivalent for — so it carries the strongest mark in the row. */
.do-state {
  font: 600 11px/1.5 system-ui, sans-serif; color: var(--text);
  background: var(--accent-soft); border-radius: 5px; padding: 2px 8px; white-space: nowrap;
}
.do-state.none { background: none; color: var(--muted); font-weight: 500; }
.do-by { font: 12px/1.5 system-ui, sans-serif; color: var(--text); }
.do-by.none { color: var(--muted); font-style: italic; }
/* A structured value is a button: it opens the same JSON window the Variables tab
   opens. Styled as a link rather than as a button, because it sits in a table cell
   among plain values and a chrome button there would read as an action on the row. */
.c-val.do-json {
  /* A button inherits no font, and this one has to keep reading as a value: the same
     monospace .vt .c-val sets, in the trail table as well as in the row. */
  font: 13px/1.5 ui-monospace, monospace; color: var(--accent);
  background: none; border: 0; padding: 0;
  cursor: pointer; text-align: left; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.do-trail-table .c-val.do-json { font-size: 12.5px; }
.c-val.do-json:hover { text-decoration: underline; }
.c-val.do-json:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* The state trail: the object's own biography, opened under its row. Indented and
   quieted so the table above stays the thing being scanned. */
.vt .do-trail > td { padding: 0 12px 10px; background: var(--bg); }
.vt .do-row.open { border-bottom: none; }
.do-trail-box { border: 1px solid var(--border); border-radius: 7px; overflow: hidden; }
.do-trail-h {
  font: 11px/1.6 system-ui, sans-serif; color: var(--muted);
  padding: 5px 10px; border-bottom: 1px solid var(--border); background: var(--panel, var(--bg));
}
.do-trail-h b { color: var(--text); font-weight: 600; }
table.do-trail-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.do-trail-table td { padding: 5px 10px; vertical-align: top; border-bottom: 1px solid var(--border); }
.do-trail-table tr:last-child td { border-bottom: none; }
.do-trail-table .do-t-n { width: 1%; color: var(--muted); font: 11px/1.6 ui-monospace, monospace; }
.do-trail-table .do-t-state { width: 1%; white-space: nowrap; font: 600 12px/1.5 system-ui, sans-serif; }
.do-trail-table .do-t-by { width: 1%; white-space: nowrap; color: var(--text); }
.do-trail-table .do-t-at { width: 1%; white-space: nowrap; color: var(--muted); font: 11.5px/1.6 ui-monospace, monospace; }

/* ---- Information model: the UML class canvas -------------------------------
   (ADR-0230, slice 2)

   The diagram is the point of this screen, so it gets the room: a toolbar, the
   canvas, a properties panel beside it, and the problems bar underneath — the
   same arrangement the Modeler uses, for the same reason. A modeler moves between
   drawing and correcting constantly, and a problem list behind a tab is a problem
   list nobody reads. */
.im-editor { display: flex; flex-direction: column; height: calc(100vh - var(--topbar-h, 96px) - 24px); min-height: 480px; }
.im-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px 8px 0 0;
  background: var(--bg); border-bottom: none;
}
.im-title { font-size: 14px; }
.im-rev { font: 12px/1.5 ui-monospace, monospace; }

/* Finding something in a model that outgrew its window — a class among thirty on the
   sheet, a member among forty in a panel. Both are view state and neither touches the
   document, so both are styled as quiet fields rather than as controls. */
.im-search { position: relative; display: inline-flex; }
.im-search > input {
  width: 190px; font: 12px/1.5 system-ui, sans-serif; color: var(--text);
  border: 1px solid var(--border); border-radius: 6px; padding: 3px 8px; background: var(--bg);
}
.im-search-results {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 12; min-width: 260px; max-width: 340px;
  max-height: 300px; overflow: auto; padding: 3px;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface);
  box-shadow: 0 4px 14px rgba(0,0,0,.12);
}
.im-search-hit {
  display: flex; gap: 8px; align-items: baseline; justify-content: space-between; width: 100%;
  text-align: left; font: 12px/1.5 system-ui, sans-serif; color: var(--text);
  border: 0; border-radius: 4px; padding: 4px 7px; background: none; cursor: pointer;
}
.im-search-hit:hover, .im-search-hit:focus-visible { background: var(--accent-soft); }
.im-search-hit .muted { font-size: 11px; white-space: nowrap; }
.im-search-none { margin: 0; padding: 5px 7px; font-size: 12px; }

.im-filter { display: flex; align-items: center; gap: 8px; margin: 0 0 6px; }
.im-filter > input {
  flex: 1; min-width: 0; font: 12px/1.4 system-ui, sans-serif; color: var(--text);
  border: 1px solid var(--border); border-radius: 5px; padding: 3px 6px; background: var(--bg);
}
.im-filter-count { font-size: 11px; white-space: nowrap; }
.im-filter-count.none { color: var(--danger, #b42318); }
.im-filter-empty { margin: 6px 0 0; font-size: 12px; }
/* The class canvas's palette is diagram-js's, the same one bpmn-js and dmn-js put
   down their left edge — its frame, its groups and its separators all come from
   vendor/canvas/diagram-js.css, so there is nothing to restate here. What is Atlas's
   is which mark stands for which entry, and that the armed relationship is lit.

   Every mark is a miniature of the shape the click produces, drawn from the OMG
   notation. There is no official UML icon set to vendor — the standard fixes the
   shapes on the diagram and says nothing about a toolbar — so they are drawn here, as
   inline SVG in the stylesheet. No font, no image files, nothing to fetch: the same
   reasoning that keeps the canvases buildless (ADR-0012).

   The entries split in two, and the split is what each entry *is* rather than a
   preference. A classifier is a button: one click adds one, it has no state, so it
   carries its kind in colour. A relationship is a *mode*: one of them is armed while
   the next two clicks draw that line, and the armed entry is recoloured to say so —
   which a baked-in colour cannot do. So the modes are masks in currentColor and
   follow hover and the armed state; the classifiers are painted. */
.uml-pi::before {
  content: ""; display: block; height: 100%; width: 100%;
  background-repeat: no-repeat; background-position: center; background-size: 19px 19px;
}
/* The modes: the mark is a stencil and the colour is the palette's, so hovering and
   arming reach it. */
.uml-pi-association::before, .uml-pi-aggregation::before, .uml-pi-composition::before,
.uml-pi-generalization::before, .uml-pi-transition::before, .uml-pi-lasso::before {
  background-color: var(--text);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: 19px 19px; mask-size: 19px 19px;
}
/* The classifiers, each in the colour of its kind. */
.uml-pi-businessObject::before { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%221.5%22%20y%3D%222.5%22%20width%3D%2213%22%20height%3D%2211%22%20rx%3D%221%22%20fill%3D%22none%22%20stroke%3D%22%232d7d9a%22%20stroke-width%3D%221.2%22%2F%3E%3Cpath%20fill-rule%3D%22evenodd%22%20fill%3D%22%232d7d9a%22%20d%3D%22M2.1%203.1h11.8v3.1H2.1z%20M4.6%203.55a1.1%201.1%200%201%200%200%202.2%201.1%201.1%200%201%200%200-2.2z%20M5.75%204.25h3.4v0.8h-1.2v0.75h-0.85v-0.75h-1.35z%22%2F%3E%3Cpath%20d%3D%22M4%209h8M4%2011.4h5.5%22%20stroke%3D%22%232d7d9a9c%22%20stroke-width%3D%221.1%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-valueType::before { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%221.5%22%20y%3D%222.5%22%20width%3D%2213%22%20height%3D%2211%22%20rx%3D%221%22%20fill%3D%22none%22%20stroke%3D%22%237a5ea8%22%20stroke-width%3D%221.2%22%2F%3E%3Cpath%20d%3D%22M2.1%206.2h11.8%22%20stroke%3D%22%237a5ea8%22%20stroke-width%3D%221.2%22%2F%3E%3Cpath%20d%3D%22M4%209h8M4%2011.4h5.5%22%20stroke%3D%22%237a5ea89c%22%20stroke-width%3D%221.1%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-enumeration::before { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%221.5%22%20y%3D%222.5%22%20width%3D%2213%22%20height%3D%2211%22%20rx%3D%221%22%20fill%3D%22none%22%20stroke%3D%22%234a8c3f%22%20stroke-width%3D%221.2%22%2F%3E%3Cpath%20d%3D%22M2.1%203.1h11.8v3.1H2.1z%22%20fill%3D%22%234a8c3f%22%2F%3E%3Ccircle%20cx%3D%224.3%22%20cy%3D%229%22%20r%3D%221.05%22%20fill%3D%22%234a8c3f%22%2F%3E%3Cpath%20d%3D%22M6.5%209h5.8%22%20stroke%3D%22%234a8c3f9c%22%20stroke-width%3D%221.1%22%20stroke-linecap%3D%22round%22%2F%3E%3Ccircle%20cx%3D%224.3%22%20cy%3D%2211.7%22%20r%3D%221.05%22%20fill%3D%22%234a8c3f%22%2F%3E%3Cpath%20d%3D%22M6.5%2011.7h3.9%22%20stroke%3D%22%234a8c3f9c%22%20stroke-width%3D%221.1%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-store::before { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M2.5%204.2c0-1%202.4-1.8%205.5-1.8s5.5.8%205.5%201.8v7.6c0%201-2.4%201.8-5.5%201.8s-5.5-.8-5.5-1.8z%22%20fill%3D%22none%22%20stroke%3D%22%23a36a2c%22%20stroke-width%3D%221.2%22%2F%3E%3Cpath%20d%3D%22M2.5%204.2c0%201%202.4%201.8%205.5%201.8s5.5-.8%205.5-1.8%22%20fill%3D%22none%22%20stroke%3D%22%23a36a2c%22%20stroke-width%3D%221.2%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-state::before { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%221.8%22%20y%3D%224%22%20width%3D%2212.4%22%20height%3D%228%22%20rx%3D%223.2%22%20fill%3D%22none%22%20stroke%3D%22%232d7d9a%22%20stroke-width%3D%221.3%22%2F%3E%3C%2Fsvg%3E"); }
/* The context pad's bin. A stencil like the relationship marks, so it follows the
   entry's colour — and turns red on hover, which is the one place in either canvas
   where a click destroys something and the only warning it gets. */
.uml-pi-trash::before {
  background-color: var(--text);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: 19px 19px; mask-size: 19px 19px;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M6%202.6h4M2.6%204.6h10.8M4.5%204.6l.5%208.2a1%201%200%200%200%201%20.95h4a1%201%200%200%200%201-.95l.5-8.2M6.6%207v4.2M9.4%207v4.2%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.2%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M6%202.6h4M2.6%204.6h10.8M4.5%204.6l.5%208.2a1%201%200%200%200%201%20.95h4a1%201%200%200%200%201-.95l.5-8.2M6.6%207v4.2M9.4%207v4.2%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.2%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
}
.djs-context-pad .entry.uml-pi-trash:hover::before { background-color: #c0392b; }

/* The pad's entries are the palette's marks, at the pad's size. The palette paints a
   frame around each entry and the pad paints its own, so only the mark is shared. */
.djs-context-pad .entry.uml-pi { display: inline-block; }
.djs-context-pad .entry.uml-pi::before { background-size: 17px 17px; -webkit-mask-size: 17px 17px; mask-size: 17px 17px; }

/* The four relationships and the two tools, as stencils. */
.uml-pi-association::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M2%208h12%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M2%208h12%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-aggregation::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M9.2%208H14.3%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M1.6%208l3.9-3%203.9%203-3.9%203z%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.25%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M9.2%208H14.3%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M1.6%208l3.9-3%203.9%203-3.9%203z%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.25%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-composition::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M9.2%208H14.3%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M1.6%208l3.9-3%203.9%203-3.9%203z%22%20fill%3D%22%23000%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.25%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M9.2%208H14.3%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M1.6%208l3.9-3%203.9%203-3.9%203z%22%20fill%3D%22%23000%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.25%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-generalization::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M1.7%208h6.6%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M8.1%203.7L14.4%208l-6.3%204.3z%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.25%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M1.7%208h6.6%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M8.1%203.7L14.4%208l-6.3%204.3z%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.25%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-transition::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M2%208h9.6%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M9.6%205.2L14%208l-4.4%202.8%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M2%208h9.6%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M9.6%205.2L14%208l-4.4%202.8%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.uml-pi-lasso::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%222.2%22%20y%3D%223.2%22%20width%3D%2211.6%22%20height%3D%229.6%22%20rx%3D%221%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.2%22%20stroke-dasharray%3D%222.6%202%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%3E%3Crect%20x%3D%222.2%22%20y%3D%223.2%22%20width%3D%2211.6%22%20height%3D%229.6%22%20rx%3D%221%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.2%22%20stroke-dasharray%3D%222.6%202%22%2F%3E%3C%2Fsvg%3E"); }
.djs-palette .uml-pi:hover::before { background-color: var(--accent); }
/* An armed relationship is a mode, and a mode has to be visible or it is a trap: the
   entry stays lit for exactly as long as the next two clicks draw that line. */
.djs-palette .uml-pi.active { background: var(--accent); border-radius: 4px; }
.djs-palette .uml-pi.active::before { background-color: var(--accent-ink); }

.im-dirty { font: 11px/1.5 system-ui, sans-serif; color: var(--muted); font-style: italic; }

.im-body { display: flex; flex: 1; min-height: 0; border: 1px solid var(--border); }
.im-canvas { flex: 1; overflow: hidden; background: var(--bg); min-width: 0; position: relative; }
/* Over the drawing, not on it: diagram-js fits the viewport to its content, and a
   sentence drawn on the sheet would be zoomed to fill the window. */
.im-empty-hint { position: absolute; z-index: 1; inset: 40px 40px auto 40px; margin: 0;
  max-width: 44ch; color: var(--muted); font-size: 13px; line-height: 1.5; pointer-events: none; }
/* The legend for the shading, over the drawing for the same reason the hint is: a
   sentence on the sheet would be zoomed with it. It is on screen only while the
   shading is, because faint and bright mean nothing without the sentence that says
   what they were read from and what that reading cannot see. */
.im-usage-legend { position: absolute; z-index: 1; inset: auto 12px 12px 12px; margin: 0;
  padding: 8px 10px; max-width: 78ch; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--muted); font-size: 12px; line-height: 1.5;
  /* Over the drawing, so it must not take the drawing's clicks: a class under the
     sentence has to stay selectable. */
  pointer-events: none; }
.im-usage-legend b { color: var(--text); font-weight: 600; }
.im-canvas.connecting { cursor: crosshair; }
.im-side { width: 340px; flex: none; border-left: 1px solid var(--border); overflow: auto; background: var(--bg); }

/* The class canvas. The drawing is diagram-js now
   (ADR-0237), so these style what the Atlas renderer in
   api/web/vendor/canvas/src/uml.js emits; the selection outline and the hit layer are the
   library's own. */
.im-canvas .djs-container { background: var(--bg); }

/* A class box. The three kinds are told apart by the colour of their header strip
   rather than by shape, because shape is what the notation already uses. */
.uml-class .uml-box { fill: var(--canvas-fill); stroke: var(--border); stroke-width: 1; }
.uml-class .uml-sep { stroke: var(--border); }
.uml-class .uml-stereo { fill: var(--muted); font: 10px/1.4 system-ui, sans-serif; letter-spacing: .02em; }
.uml-class .uml-cname { fill: var(--text); font: 600 13px/1.4 system-ui, sans-serif; }
.uml-class .uml-attr { font: 12px/1.4 ui-monospace, monospace; }
.uml-class .uml-attr-name { fill: var(--text); }
.uml-class .uml-attr-type { fill: var(--muted); }
.uml-class .uml-attr-mult { fill: var(--muted); }
.uml-class .uml-attr.key .uml-attr-name { font-weight: 700; }
/* Shaded by use (ADR-0338, read onto the drawing). What a deployed process names comes
   forward; what nothing names recedes but stays readable — a member somebody is about to
   decide the fate of has to be legible while they decide it. Both classes are absent
   unless the host has a reading to hand, so an unshaded diagram is drawn as it always
   was. `unreachable` follows below and therefore wins: while a relationship is being
   drawn, what it cannot land on is the more urgent thing to see. */
.uml-class .uml-attr.used .uml-attr-name { font-weight: 600; }
.uml-class .uml-attr.unused { opacity: .42; }
.uml-class .uml-literal.used { font-weight: 600; }
.uml-class .uml-literal.unused { opacity: .42; }
.uml-class.unused { opacity: .55; }
.uml-class .uml-literal { fill: var(--text); font: 12px/1.4 ui-monospace, monospace; }
.uml-class .uml-empty { fill: var(--muted); font: italic 11.5px/1.4 system-ui, sans-serif; }
.uml-class.invalid .uml-box { stroke: #d93025; stroke-dasharray: 4 3; }
/* While a relationship is being drawn, a class the matrix would refuse fades: the
   canvas offers only what the server would accept. */
.uml-class.unreachable { opacity: .35; }
.uml-class.businessObject .uml-box { fill: var(--class-fill); }
.uml-class.valueType .uml-box { fill: var(--canvas-fill); stroke-dasharray: 0; }
.uml-class.enumeration .uml-box { fill: var(--enum-fill); }

.uml-edge .uml-edge-line { stroke: var(--text); stroke-width: 1.4; fill: none; }
.uml-edge.generalization .uml-edge-line { stroke-width: 1.2; }
.uml-edge-label { fill: var(--text); font: 600 11px/1.4 system-ui, sans-serif; text-anchor: middle; }

/* The state canvas: a class's lifecycle (ADR-0259). The third notation on the shared
   bundle, and the run-up to the one BPMN slot the class diagram does not answer.

   Every state carries a name here, where UML's initial and final pseudostates carry
   none — the name is the string a process writes into the data state, so a state
   nothing can name is a state nothing can reach. The disc and the ring are therefore
   marks on a named box rather than shapes of their own. */
.uml-state .uml-state-box { fill: var(--class-fill); stroke: var(--border); stroke-width: 1; }
.uml-state .uml-state-name { fill: var(--text); font: 600 13px/1.4 system-ui, sans-serif; }
/* Nothing leaves a final state, which UML says with a second ring. */
.uml-state .uml-state-final { fill: none; stroke: var(--border); stroke-width: 1; }
/* Where an instance is created: UML's filled disc and its arrow, at the left edge. */
.uml-state .uml-state-start { fill: var(--text); stroke: none; }
.uml-state .uml-state-start-arrow { stroke: var(--text); stroke-width: 1.4; fill: none; }
.uml-state.invalid .uml-state-box { stroke: #d93025; stroke-dasharray: 4 3; }

/* A transition is directed and an association is not, which is the whole reason one
   carries an arrowhead and the other does not. */
.uml-transition-line { stroke: var(--text); stroke-width: 1.3; }
.uml-transition-label { fill: var(--text); font: 600 11px/1.4 system-ui, sans-serif; }

/* The derived model — what an application's processes imply, read without anyone
   having modelled anything (ADR-0301). It is
   evidence rather than a document, so it is deliberately quieter than the authored
   model's canvas: no palette, no accent, and its qualifications above the drawing
   rather than under it, because a derived picture mistaken for a complete one is
   worse than no picture. */
.dm-root { padding: 18px 22px 26px; }
.dm-link { margin-left: 8px; font-size: 12px; white-space: nowrap; }
.dm-gaps {
  margin: 14px 0 10px; padding: 10px 14px; font-size: 13px;
  border: 1px solid var(--warn); border-radius: 8px; background: var(--warn-soft); color: var(--text);
}
.dm-gaps ul { margin: 6px 0 0; padding-left: 18px; }
.dm-gaps li { margin: 3px 0; }
.dm-stage { position: relative; height: 460px; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.dm-stage.small { height: 300px; margin-top: 10px; }
.dm-side { margin-top: 14px; }
.dm-card { border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; }
.dm-card-head { display: flex; align-items: baseline; gap: 10px; font-size: 14px; }
.dm-gaps-inline { margin: 8px 0 0; padding-left: 18px; font-size: 12px; color: var(--muted); }

/* Planned against built — the difference reading
   (ADR-0310).

   A list rather than a drawing, because a difference painted onto one of the two
   pictures is exactly the confusion ADR-0301 names as the cost of having two. The two
   sides sit as columns and are never blended: a reader acts on them differently, so the
   layout has to make "which document is this about" answerable at a glance. */
.md-root { padding: 18px 22px 26px; }
.md-root .between .btn { white-space: nowrap; }
.md-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 16px; }
@media (max-width: 900px) { .md-cols { grid-template-columns: 1fr; } }
.md-col { border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; min-width: 0; }
.md-col h2 { font-size: 15px; margin: 0 0 4px; display: flex; align-items: center; gap: 8px; }
/* Shared with the class catalogue's section headings: it is the same statement — how
   many rows are under this heading — so it is the same pill rather than a second one
   that drifts. */
.md-count, .bo-count {
  font: 600 11px/1 system-ui, sans-serif; padding: 3px 7px; border-radius: 999px;
  background: var(--bg); color: var(--muted); border: 1px solid var(--border);
}
/* The backlog side carries the accent: it is the direction the pair exists for. The
   other is information, not work, and is left neutral so it does not compete. */
.md-col.planned { border-left: 3px solid var(--accent); }
.md-meaning { color: var(--muted); font-size: 12.5px; line-height: 1.5; margin: 0 0 12px; }
.md-class { border-top: 1px solid var(--border); padding: 10px 0 2px; }
.md-class-head { display: flex; align-items: baseline; gap: 8px; font-size: 13.5px; }
.md-rows { list-style: none; margin: 6px 0 0; padding: 0; }
.md-rows li {
  display: grid; grid-template-columns: 74px 1fr; gap: 2px 10px;
  padding: 6px 0; border-top: 1px dashed var(--border); font-size: 12.5px;
}
.md-rows li:first-child { border-top: none; }
.md-what { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; padding-top: 1px; }
.md-where { font-weight: 600; }
.md-note { grid-column: 2; color: var(--muted); line-height: 1.45; }
.md-none { color: var(--muted); font-size: 12.5px; font-style: italic; margin: 0; }
.md-excluded {
  margin-top: 18px; padding: 12px 16px; border: 1px solid var(--border);
  border-radius: 10px; background: var(--bg); font-size: 12.5px;
}
.md-excluded ul { margin: 8px 0 0; padding-left: 18px; color: var(--muted); line-height: 1.5; }
.md-excluded li { margin: 4px 0; }

/* The class catalogue and the where-used page
   (ADR-0338).

   The list is a plain list — the shared table, no layout of its own — because it is
   read the way every other list in the console is read (ADR-0286),
   and because the interesting work happens one click in. The detail page is a
   sequence of statements rather than a dashboard: what this class *is*, then where
   processes use it, then where the model does. Nothing here is a drawing; the drawing
   is one link away and says a different thing. */
.bo-root { padding: 18px 22px 26px; }
.bo-doc { font-size: 12px; color: var(--muted); margin-top: 2px; }
/* An absent fact reads as absent, not as an empty cell: the difference between "no
   member is written" and "this column did not render" is worth one class. */
.bo-none { color: var(--muted); font-style: italic; }
/* The three parts of "used by" stack rather than wrap: they are three different
   statements, and a wrapped one reads as a broken line. */
.bo-used { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; font-size: 12px; }
.bo-kind.businessObject { border-color: var(--accent); color: var(--accent); }
.bo-kind.enumeration { border-style: dashed; }
.bo-scope { margin: 10px 2px 0; font-size: 12px; color: var(--muted); max-width: 90ch; line-height: 1.5; }

.bo-crumb { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.bo-title { display: flex; align-items: center; gap: 10px; margin: 0; }
.bo-summary { margin-top: 16px; }
.bo-touched { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.bo-touched-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 4px 0; font-size: 12.5px; }
.bo-touched-label { min-width: 130px; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.bo-sec { margin-top: 22px; }
.bo-sec h2 { font-size: 15px; margin: 0 0 4px; display: flex; align-items: center; gap: 8px; }
.bo-meaning { color: var(--muted); font-size: 12.5px; line-height: 1.5; margin: 0 0 10px; max-width: 90ch; }

/* How something is used. The kinds are not good or bad, so they are not coloured as
   if they were: a write carries the accent because it is the one that changes the
   datum, and everything else stays quiet. */
.bo-how { text-transform: lowercase; }
.bo-how.write, .bo-how.attribute { border-color: var(--accent); color: var(--accent); }
.bo-how.declare, .bo-how.association, .bo-how.states { color: var(--muted); }
.bo-how.store { border-style: dashed; }

.bo-state { background: var(--accent-soft); }
.bo-state.initial { border-left: 3px solid var(--accent); }
.bo-state.final { border-right: 3px solid var(--muted); }
.bo-life { margin-top: 14px; }
.bo-life h3 { font-size: 13px; margin: 0 0 8px; }
.bo-states { display: flex; gap: 6px; flex-wrap: wrap; }
.bo-moves { list-style: none; margin: 10px 0 0; padding: 0; font-size: 12.5px; color: var(--muted); }
.bo-moves li { padding: 3px 0; }
.bo-lit td { width: 100%; }

/* The bar above the run-time overlay: which object is drawn, where it has got to,
   and — only when more than one object has a machine — which one to draw. */
.lc-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 8px 12px 0; font-size: 13px; }
.lc-of { color: var(--muted); }
.lc-now { color: var(--muted); }
.lc-now.none { font-style: italic; }
.og-pick { display: flex; align-items: center; gap: 6px; margin-left: auto; color: var(--muted); }
.og-pick select { font: inherit; padding: 3px 6px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); color: var(--text); max-width: 260px; }

/* The run-time overlay (ADR-0259 §4): the same machine with one instance's own life
   drawn on it. Marks on the declared notation rather than a notation of their own —
   a state this datum has been through is still that state, and reading the two on top
   of each other is the entire point. So: what was never reached recedes, what was
   reached is filled, and where the datum is now is ringed in the accent, which is the
   one question somebody opens this to answer. */
.uml-state.visited .uml-state-box { fill: var(--accent-soft); stroke: var(--accent); }
.uml-state.current .uml-state-box { stroke: var(--accent); stroke-width: 2.5; }
.uml-state.current .uml-state-name { fill: var(--accent); }
.uml-state .uml-state-count { fill: var(--muted); font: 600 10px/1 ui-monospace, monospace; }
/* An edge the datum actually travelled, against the ones it did not. */
.uml-transition.taken .uml-transition-line { stroke: var(--accent); stroke-width: 2.2; }
.uml-transition.taken .uml-transition-label { fill: var(--accent); }
.uml-transition-by { fill: var(--muted); font: 500 10px/1.3 ui-monospace, monospace; }
.uml-transition.taken .uml-transition-by { fill: var(--accent); }
/* A move that was made and never declared. Dashed, because it is what happened and
   not what was modelled — the run-time twin of the data.illegal-transition check. */
.uml-transition.undeclared .uml-transition-line {
  stroke: var(--danger); stroke-width: 1.8; stroke-dasharray: 6 4;
}
.uml-transition.undeclared .uml-transition-by { fill: var(--danger); }

.uml-end-label { fill: var(--muted); font: 11px/1.4 ui-monospace, monospace; text-anchor: middle; }

/* A store is kept visibly unlike a class, because it is not one: a cylinder, joined
   to the class it holds by a dashed annotation rather than by an association. */
.uml-store .uml-store-body { fill: var(--uml-store-fill); stroke: var(--border); }
.uml-store .uml-store-lip { stroke: var(--border); }
.uml-store .uml-store-name { fill: var(--text); font: 600 12px/1.4 system-ui, sans-serif; }
.uml-store .uml-store-sub { fill: var(--muted); font: 11px/1.4 system-ui, sans-serif; }
.uml-store.invalid .uml-store-body { stroke: #d93025; stroke-dasharray: 4 3; }
.uml-store.unreachable { opacity: .35; }
/* A store's line to its class is an annotation, not a relationship: a store and its
   class do not relate, one *is kept in* the other (ADR-0230 §7). Dashed says so. */
.uml-store-link .uml-edge-line { stroke: var(--muted); stroke-width: 1.2; fill: none; stroke-dasharray: 4 3; }
/* And a class's line to the enumeration its states come from, which is the same kind
   of statement: the class does not relate to the enumeration, it *takes its states
   from* one (ADR-0306). Dashed
   for the same reason; the «lifecycle» keyword on it is what says which dependency
   this is, since UML allows many and draws them all the same way. */
.uml-lifecycle-link .uml-edge-line,
.uml-type-link .uml-edge-line { stroke: var(--muted); stroke-width: 1.2; fill: none; stroke-dasharray: 4 3; }
.uml-lifecycle-link .uml-edge-label,
.uml-type-link .uml-edge-label { fill: var(--muted); font-size: 10.5px; }

/* diagram-js draws the selection, so the box does not have to. A line gets no
   outline from the library, so which relationship is being edited is said here. */
.djs-element.selected .uml-box,
.djs-element.selected .uml-store-body { stroke: var(--accent); stroke-width: 2; }
.djs-element.selected .uml-edge-line { stroke: var(--accent); stroke-width: 2.2; }

/* The properties panel. Its chrome — the element header, the property body and the
   collapsible groups — is the Modeler's, styled above and in the .pgroup block, and
   its fields are .field like the Modeler's. What is left here is the part that is
   this panel's own: the notation's sentences, the attribute table and the ends.

   .field dresses text inputs only, so a select or a textarea beside one sits tight
   against its label. Fixed here rather than on .field itself: that class is also the
   login and user-management form, and this is a 5px change nobody asked for there. */
.im-side .field select,
.im-side .field textarea { display: block; margin-top: 5px; }
/* Every textarea in this panel holds documentation — there is no FEEL and no script
   here — so they all want the UI font rather than the monospace one a textarea
   defaults to. The Modeler met this first and answered it the same way for its own
   documentation field (.doc-field above); a paragraph set in code face reads as
   something to be executed rather than something to be read. */
.im-side .field textarea { resize: vertical; font: inherit; min-height: 56px; }
/* The sentence that tells a modeler which kind to pick. It is the most consequential
   choice in this metamodel and a three-word palette makes it by accident. */
.im-meaning { margin: -4px 0 12px; font: 12px/1.5 system-ui, sans-serif; color: var(--muted); }
/* Louder than a group body's other paragraphs on purpose: it is what the
   relationship says, not a note about it. */
.im-side .im-reading { margin: 0 0 12px; font: 13px/1.5 system-ui, sans-serif; color: var(--text); }
.im-hint-text { margin: 10px 0 0; font: 12px/1.55 system-ui, sans-serif; color: var(--muted); }
.im-hint-text code { font-size: 11.5px; }

/* The name is the column that has to grow. A class with a hundred attributes is told
   apart by the ends of its names, and a fixed layout is what stops the two selects
   from taking the room: they carry class names as options, so left to size themselves
   they grow to the longest one and squeeze the name to nothing. */
table.im-attrs { width: 100%; border-collapse: collapse; font-size: 12px; table-layout: fixed; }
.im-attrs th:nth-child(1), .im-attrs td:nth-child(1) { width: 16px; }
.im-attrs th:nth-child(3), .im-attrs td:nth-child(3) { width: 88px; }
.im-attrs th:nth-child(4), .im-attrs td:nth-child(4) { width: 56px; }
.im-attrs th:nth-child(5), .im-attrs td:nth-child(5) { width: 24px; }
.im-attrs th:nth-child(6), .im-attrs td:nth-child(6) { width: 28px; }
/* The literals table has three columns and no header; only its grip and its ✕ are fixed. */
.im-attrs tbody tr[data-lit] td:nth-child(3) { width: 28px; }
.im-attrs th { text-align: left; font: 600 10px/1.6 system-ui, sans-serif; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; padding: 2px 4px; }
.im-attrs td { padding: 2px 4px; vertical-align: middle; }
.im-attrs input[type="text"], .im-attrs input:not([type]), .im-attrs select {
  width: 100%; box-sizing: border-box; font: 12px/1.4 system-ui, sans-serif;
  border: 1px solid var(--border); border-radius: 5px; padding: 3px 5px;
  background: var(--bg); color: var(--text);
}
.im-attrs .im-key-cell { text-align: center; width: 24px; }

/* Reordering an attribute or a literal. The grip is the only thing that starts a
   drag, so the text inputs beside it keep their own selection; the drop line marks
   the edge the row will land against. */
.im-attrs .im-grip {
  width: 16px; padding: 2px 0 2px 2px; cursor: grab; user-select: none;
  color: var(--muted); font-size: 11px; line-height: 1; text-align: center;
}
.im-attrs .im-grip:active { cursor: grabbing; }
/* A narrowed list does not reorder: a row dragged past rows that are not on screen
   would land somewhere nobody chose, so the grip says so rather than lying. */
.im-attrs .im-grip.disabled { cursor: not-allowed; opacity: .35; }
.im-attrs tr:hover .im-grip { color: var(--text); }
.im-attrs tr.im-dragging { opacity: .45; }
.im-attrs tr.im-drop-before td { box-shadow: inset 0 2px 0 0 var(--accent); }
.im-attrs tr.im-drop-after td { box-shadow: inset 0 -2px 0 0 var(--accent); }
.im-end { border: 1px solid var(--border); border-radius: 7px; padding: 8px 10px 2px; margin-bottom: 10px; }
.im-end legend { font: 600 11px/1.5 system-ui, sans-serif; color: var(--muted); padding: 0 4px; }
.im-note { font: 12px/1.55 system-ui, sans-serif; color: var(--muted); }
.im-note ul { margin: 6px 0 0; padding-left: 18px; }
.im-note li { margin-bottom: 6px; }
.im-schema { max-height: 320px; overflow: auto; font: 11.5px/1.5 ui-monospace, monospace; background: var(--accent-soft); border-radius: 6px; padding: 8px 10px; }
.im-loss { font: 12px/1.55 system-ui, sans-serif; color: var(--muted); margin-bottom: 8px; }
.im-loss b { color: var(--text); }

/* The problems bar. The tag is the distinction the whole validation rests on: "not
   authored" is a limit of this build, "invalid" is a modeling mistake, and a
   modeler acts on them differently. */
.im-problems {
  border: 1px solid var(--border); border-top: none; border-radius: 0 0 8px 8px;
  padding: 8px 10px; max-height: 132px; overflow: auto; background: var(--bg);
}
.im-ok { font: 12px/1.6 system-ui, sans-serif; color: var(--muted); }
.im-problem-head { font: 600 11px/1.6 system-ui, sans-serif; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 4px; }
button.im-problem { display: block; width: 100%; text-align: left; border: 0; background: none; cursor: pointer; }
.im-problem {
  font: 12px/1.6 system-ui, sans-serif; color: var(--text);
  padding: 3px 6px; border-radius: 5px; margin-bottom: 2px;
}
button.im-problem:hover { background: var(--accent-soft); }
.im-problem-tag {
  display: inline-block; font: 600 10px/1.5 system-ui, sans-serif; text-transform: uppercase;
  letter-spacing: .04em; border-radius: 4px; padding: 0 5px; margin-right: 6px;
}
.im-problem.notation .im-problem-tag { background: #d93025; color: #fff; }
.im-problem.out-of-subset .im-problem-tag { background: var(--muted); color: var(--bg); }
.im-panel-problems { display: flex; flex-direction: column; gap: 6px; }
.im-panel-problems .im-problem { padding-left: 0; }

/* Data › Instances: the same subject one altitude down. */
.im-group-head {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-bottom: 1px solid var(--border);
}
.im-resolved, .im-unresolved {
  font: 600 10px/1.5 system-ui, sans-serif; text-transform: uppercase; letter-spacing: .04em;
  border-radius: 4px; padding: 1px 6px;
}
.im-resolved { color: #0a7c5a; border: 1px solid color-mix(in srgb, #0a7c5a 35%, transparent); }
.im-unresolved { color: var(--muted); border: 1px solid var(--border); }
.im-group-head .muted { margin-left: auto; font-size: 12px; }

/* ---- The object diagram: the Data tab's second reading ---------------------
   (ADR-0230, slice 4)

   The class diagram under Data › Model says what an Order is; this says which
   orders are here. UML draws those as two diagrams, and the shapes echo each
   other on purpose — a header with the object's reading, a rule, then its
   members — so a person moving between the two is looking at the same thing
   twice rather than learning a second notation. */
.do-views { display: inline-flex; gap: 2px; margin-left: auto; }
.do-views button {
  font: 500 11.5px/1.5 system-ui, sans-serif; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg); color: var(--muted);
  padding: 2px 9px;
}
.do-views button:first-child { border-radius: 5px 0 0 5px; }
.do-views button:last-child { border-radius: 0 5px 5px 0; border-left: none; }
.do-views button:hover { color: var(--accent); border-color: var(--accent); }
.do-views button.active { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* The object diagram's stage. The drawing is diagram-js (ADR-0237), which fits its
   viewport to the box it is given — where the SVG this replaced carried its own
   computed width and height and simply grew. So the stage states a size, and the
   diagram fits itself into it; the notes below stay outside it, because they are
   prose about the picture rather than part of it. */
.og-stage {
  position: relative; height: min(58vh, 620px); min-height: 320px;
  border: 1px solid var(--border); border-radius: 6px; overflow: hidden;
}
.og-canvas { position: absolute; inset: 0; }
.og-canvas .djs-container { background: var(--bg); }
.og-node .og-box { fill: var(--canvas-fill); stroke: var(--border); stroke-width: 1; }
.og-node .og-sep { stroke: var(--border); }
/* A root object is a thing the instance carries; a nested one exists only inside
   its whole. The weight says which is which without a legend. */
.og-node .og-box { fill: var(--object-fill); }
.og-node.nested .og-box { fill: var(--canvas-fill); }
.og-node.unset .og-box { stroke-dasharray: 4 3; }
/* UML underlines an object's name — that is the notation's way of saying "this is
   an instance, not a type", and it is the one mark that tells the two diagrams
   apart at a glance. */
.og-node .og-label {
  fill: var(--text); font: 600 12px/1.4 system-ui, sans-serif;
  text-decoration: underline; text-underline-offset: 2px;
}
.og-node .og-state {
  fill: var(--text); font: 600 10px/1.4 system-ui, sans-serif;
}
.og-node .og-attr { font: 11.5px/1.4 ui-monospace, monospace; }
.og-node .og-attr-name { fill: var(--muted); }
.og-node .og-attr-name.key { fill: var(--text); font-weight: 700; }
.og-node .og-eq { fill: var(--border); }
.og-node .og-val { fill: var(--text); }
.og-node .og-absent { fill: var(--muted); font-style: italic; }

.og-line { stroke: var(--text); stroke-width: 1.3; }
/* A key-resolved line is an inference from two values agreeing, not a fact read
   out of one value — dashed, because the difference is worth seeing. */
.og-line.association, .og-line.aggregation { stroke-dasharray: 5 3; }
.og-mark { fill: var(--text); stroke: var(--text); stroke-width: 1.2; }
.og-line-label { fill: var(--muted); font: 10.5px/1.4 ui-monospace, monospace; }

.og-notes {
  border-top: 1px solid var(--border); padding: 8px 12px;
  font: 12px/1.6 system-ui, sans-serif; color: var(--muted);
}
.og-notes p { margin: 0 0 6px; }
.og-notes p:last-child { margin-bottom: 0; }
.og-notes b { color: var(--text); }

/* ---- Data › Instances: the data-centric index ------------------------------
   (ADR-0230, slice 5a)

   The question BPMN structurally cannot express — which instances, across which
   processes, are carrying *this* order — so the layout leads with identity: the
   class, then the business key, then the instances under it. */
.di-filters {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  margin-top: 16px; padding: 12px 14px;
}
.di-field { display: block; }
.di-field > span {
  display: block; font: 600 11px/1.6 system-ui, sans-serif; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.di-field input {
  font: 13px/1.5 system-ui, sans-serif; border: 1px solid var(--border); border-radius: 6px;
  padding: 5px 8px; background: var(--bg); color: var(--text); min-width: 180px;
}
.di-toggle { display: flex; align-items: center; gap: 6px; font: 13px/1.5 system-ui, sans-serif; padding-bottom: 5px; }
.di-note { margin-top: 14px; font-size: 12px; }
.di-note b { color: var(--text); }

/* One business key, and the instances carrying it. This heading is the sentence
   the whole information model exists to make sayable, so it reads as one. */
.im-key-head {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--accent-soft) 40%, var(--bg));
}
.im-key {
  font: 600 12.5px/1.5 ui-monospace, monospace; color: var(--text);
}
.im-key.none { font-weight: 500; font-style: italic; }
.im-key-head .muted { font: 12px/1.5 system-ui, sans-serif; }
.im-key-head .btn { margin-left: auto; }
.im-groups > table { border-top: none; }
.im-groups > .im-key-head:first-child { border-top: none; }
/* A finished instance is still carrying its data — it is just not running. */
.im-past {
  font: 600 10px/1.5 system-ui, sans-serif; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; border: 1px solid var(--border); border-radius: 4px; padding: 0 4px;
}

/* ---- Data stores on the class canvas ---------------------------------------
   (ADR-0230, slice 5b)

   A store is drawn the way the notation draws a persistent thing — an open-ended
   cylinder — because the one mistake to prevent here is reading it as a class. A
   class says what an Order is; a store says where Orders are kept, and only the
   store is about storage at all. */
.im-store { cursor: grab; }
.im-store .im-store-body {
  fill: var(--im-store-fill);
  stroke: var(--border); stroke-width: 1;
}
.im-store .im-store-lip { stroke: var(--border); stroke-width: 1; }
.im-store .im-store-name { fill: var(--text); font: 600 13px/1.4 system-ui, sans-serif; }
.im-store .im-store-sub { fill: var(--muted); font: 10.5px/1.4 ui-monospace, monospace; }
.im-store.selected .im-store-body, .im-store.selected .im-store-lip { stroke: var(--accent); stroke-width: 2; }
.im-store.invalid .im-store-body { stroke: #d93025; stroke-dasharray: 4 3; }
.im-store.unreachable { opacity: .35; }
/* The line from a store to the class it keeps is an annotation, not an
   association — nothing in the model *relates* those two — so it is drawn as one. */
.im-store-line { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 2 3; }

/* ---------- Task folders: saved filters in the inbox sidebar ---------- */
/* (ADR-0268)

   The sidebar now carries two kinds of entry that must not read as one list. The
   built-in folders are the app's; the ones below them are the person's own. A
   section header rather than a divider is what says so — a rule between two
   groups of identical buttons says "there is a gap here", not "these are yours". */
.tasks-folder-head {
  font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
  padding: 0 8px; margin: 12px 0 4px; display: flex; align-items: center; gap: 6px;
}
.tasks-folder-head::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.tasks-folder { display: flex; align-items: center; gap: 8px; }
.tasks-folder-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tasks-folder-ico { width: 14px; height: 14px; flex: 0 0 auto; opacity: .75; }
.tasks-folder.active .tasks-folder-ico { opacity: 1; }
/* Dashed, because it makes a thing rather than opening one — the same distinction
   the modeller's "add" affordances draw. */
.tasks-folder-new {
  display: flex; align-items: center; gap: 8px; width: 100%; margin-top: 4px;
  padding: 6px 8px; font: inherit; font-size: 13px; text-align: left; cursor: pointer;
  color: var(--muted); background: none; border: 1px dashed var(--border-strong); border-radius: 6px;
}
.tasks-folder-new:hover { color: var(--accent); border-color: var(--accent); }
/* Shown only when the counting scan hit its budget: the badges above are then a
   floor, and saying so is cheaper than a number quietly being wrong. */
.tasks-folder-note { margin: 10px 8px 0; font-size: 11.5px; line-height: 1.45; }

/* The selected folder's rule, above its rows: the folder explaining itself. Two
   folders with similar names are otherwise indistinguishable without opening the
   editor. One line, ellipsised — the full expression is in the title and in the
   dialog. */
.tasks-rule {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 7px 14px; font-size: 12px; color: var(--muted);
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.tasks-rule[hidden] { display: none; }
.tasks-rule-feel {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px;
  color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1 1 auto;
}
.tasks-rule .btn { flex: 0 0 auto; padding: 2px 8px; font-size: 12px; }

/* The row's own key. A queue of identical task names is unreadable without it:
   nothing else on the row differs, so there is no way to say which one you mean
   or to notice that one of them is gone. */
.tasks-item-meta { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.tasks-item-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
  color: var(--muted); font-variant-numeric: tabular-nums; flex: 0 0 auto;
}
.tasks-item.selected .tasks-item-id { color: var(--accent); }
/* When the task was opened: after the key, before the holder. */
.tasks-item-opened { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; flex: 0 0 auto; white-space: nowrap; }

/* ---------- The folder editor ---------- */
/* Wider than the stock modal: a condition is four controls on one line, and
   wrapping them turns a sentence a person reads into a form they have to parse. */
.modal.tf-modal { width: min(720px, 100%); }
.tf-body { display: flex; flex-direction: column; gap: 18px; padding-top: 14px; }
.tf-top { display: grid; grid-template-columns: 1fr 220px; gap: 14px; }
.tf-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.tf-label {
  font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.tf-in, .tf-sel {
  font: inherit; font-size: 13px; padding: 5px 8px; max-width: 100%;
  border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); color: var(--text);
}
.tf-in:focus, .tf-sel:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.tf-match { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; color: var(--muted); }
.tf-match .tf-sel { flex: 0 0 auto; width: auto; }
.tf-conds { display: flex; flex-direction: column; gap: 8px; }
/* Field · operator · value · remove. The first two are fixed so the rows line up
   into columns a person can read down; the value takes what is left, because it is
   the only one whose control changes shape. */
.tf-cond {
  display: grid; grid-template-columns: 132px 172px 1fr 26px; gap: 8px; align-items: start;
  padding: 9px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
}
.tf-cond .tf-val { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.tf-cond .tf-val > * { min-width: 0; }
.tf-grow { flex: 1 1 130px; width: 100%; }
.tf-narrow { width: 92px; flex: 0 0 auto; }
.tf-cond .tf-val select.tf-narrow { width: 124px; }
.tf-cond select[multiple] { height: auto; padding: 0; }
.tf-cond select[multiple] option { padding: 1px 6px; }
.tf-hint { font-size: 12px; color: var(--muted); }
.tf-del {
  border: 0; background: none; color: var(--muted); cursor: pointer;
  font-size: 16px; line-height: 1; padding: 5px 2px; border-radius: 5px;
}
.tf-del:hover { color: var(--danger); background: var(--danger-soft); }
.tf-joiner {
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); text-align: center;
}
/* The generated expression, always visible. It is what makes the dialog teach:
   the person sees their listbox choices become the thing the engine evaluates. */
.tf-preview { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.tf-preview-head {
  display: flex; align-items: center; gap: 8px; padding: 8px 12px; font-size: 12px;
  color: var(--muted); background: var(--bg); border-bottom: 1px solid var(--border);
}
.tf-preview-head b { color: var(--text); font-variant-numeric: tabular-nums; }
.tf-preview-head b.tf-incomplete { color: var(--warn); font-weight: 500; }
.tf-preview-tag { margin-left: auto; }
.tf-feel {
  margin: 0; padding: 12px; white-space: pre; overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.65;
  color: var(--text); min-height: 42px;
}

@media (max-width: 720px) {
  .tf-top { grid-template-columns: 1fr; }
  .tf-cond { grid-template-columns: 1fr 1fr 26px; }
  .tf-cond .tf-val { grid-column: 1 / -1; }
}

/* ---------- The decision graph: one case, drawn on the decision that decided it
   (decision-graph.js). Opened by double-clicking a business rule task in the
   Operations replay and live viewers. It is deliberately a wide window: the DRG is
   the picture a DMN author works in, and it is being shown here to somebody who
   has to account for a decision, so it gets the room a diagram needs. ---------- */
.drg-ov {
  position: fixed; inset: 0; z-index: 70; padding: 24px;
  background: rgba(20, 22, 26, 0.5); display: flex; align-items: center; justify-content: center;
}
.drg-modal {
  width: min(1100px, 100%); max-height: 88vh; display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow);
}
.drg-head {
  display: flex; align-items: flex-start; gap: 12px; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.drg-titles h2 { margin: 0; font: 700 17px/1.3 system-ui, sans-serif; color: var(--text); }
.drg-sub { margin: 3px 0 0; font: 12px/1.5 system-ui, sans-serif; color: var(--muted); }
.drg-sub b { color: var(--text); font-weight: 600; }
.drg-tag {
  display: inline-block; font: 600 10px/1.6 system-ui, sans-serif; text-transform: uppercase;
  letter-spacing: 0.04em; color: #6d28d9; background: #f5f3ff; border-radius: 999px; padding: 0 8px;
}
.drg-x {
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  border-radius: 8px; width: 30px; height: 30px; font-size: 19px; line-height: 1; cursor: pointer; flex: none;
}
.drg-x:hover { border-color: var(--accent); color: var(--accent); }
.drg-body { padding: 14px 16px 18px; overflow: auto; }
.drg-canvas {
  overflow: auto; border: 1px solid var(--border); border-radius: 10px;
  background: var(--diagram-bg); padding: 10px;
}
/* The graph is half the answer and the rules are the other half, so the diagram
   takes the room it needs and no more: a tall DRG scales down to fit rather than
   pushing the matrix that explains it below the fold. */
.drg-canvas svg { max-height: min(46vh, 430px); width: auto; max-width: 100%; margin: 0 auto; }
/* A node the case did not touch fades back rather than disappearing: "this played
   no part" is part of the account, and a hole in the graph would not say it. */
.drg-node.is-idle { opacity: 0.55; }

.drg-legend { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 10px 2px 0; }
.drg-key { display: inline-flex; align-items: center; gap: 6px; font: 11.5px/1.6 system-ui, sans-serif; color: var(--muted); }
.drg-key .sw { width: 13px; height: 13px; border-radius: 4px; border: 1.5px solid; display: inline-block; }
.drg-key .sw.given { background: #eff6ff; border-color: #3b82f6; }
.drg-key .sw.derived { background: #ffffff; border-color: #111827; }
.drg-key .sw.result { background: #dcfce7; border-color: #15803d; }
.drg-key .sw.idle { background: #f8fafc; border-color: #cbd5e1; }

/* Rules on the left, the answer on the right: the reading order of the question. */
.drg-split { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: 20px; margin-top: 16px; }
.drg-split h3 {
  margin: 0 0 8px; font: 600 11px/1.6 system-ui, sans-serif; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--muted);
}
.drg-rules { min-width: 0; overflow-x: auto; }
.drg-rules .mtable:first-of-type { margin-top: 0; }
.drg-result { min-width: 0; }
.drg-foot { font-size: 12px; line-height: 1.6; margin: 12px 0 0; }
/* A decision table is read here, not glanced at on hover, so the matrix is set at
   a size somebody can actually read it at. */
.drg-rules .mgrid { font-size: 12px; }
.drg-rules .mgrid th, .drg-rules .mgrid td { padding: 3px 8px; }

@media (max-width: 900px) {
  .drg-split { grid-template-columns: minmax(0, 1fr); }
  .drg-ov { padding: 10px; }
}

/* The estate altitude (ADR-0402): one node per domain, drawn on its own
   canvas while the altitude is new. These rules are additive — nothing the Starmap uses is
   touched — so a defect here cannot reach the shipped landscape. */
.estate-root { max-width: 1100px; }
/* The two controls keep their own line rather than being squeezed by the sentence beside
   them: a flex header shrinks whichever side it can, and a button that has wrapped into
   three lines reads as an unfinished screen rather than as a control. */
.estate-actions { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.estate-actions .btn { white-space: nowrap; }
.estate-picture { margin-top: 16px; padding: 8px; display: flex; justify-content: center; }
/* The picture is square, because a ring is. It is capped rather than stretched: an estate of
   three domains spread across a wide screen reads as three unrelated things. */
.estate-canvas { width: 100%; max-width: 560px; height: auto; }
.estate-node polygon {
  fill: var(--surface); stroke: var(--accent); stroke-width: 2;
}
/* The domain the reader is standing in is the one they can open, and it says so before it is
   hovered: the ink the console uses for what is yours. */
.estate-node[data-local="true"] polygon { stroke: var(--accent-hover); stroke-width: 3; }
.estate-open { cursor: pointer; }
.estate-node:focus { outline: none; }
.estate-node:focus polygon { stroke: var(--accent-hover); stroke-width: 4; }
/* Severity colours the outline and never the fill, the way the Starmap's do: a fill that
   changed with a finding would make the kind of thing unreadable at a glance. And unknown is
   neutral rather than a fourth level of badness — a peer on an older build is an ordinary
   state of affairs (ADR-0211 §4). */
.estate-node[data-tone="attention"] polygon { stroke: var(--mesh-attention); }
.estate-node[data-tone="unknown"] polygon { stroke: var(--muted); stroke-dasharray: 6 4; }
.estate-holds { font: 600 20px/1 system-ui, sans-serif; fill: var(--text); }
.estate-name { font: 600 12px/1 system-ui, sans-serif; fill: var(--text); }
.estate-badge circle { fill: var(--surface); stroke: var(--muted); }
.estate-badge text { font: 700 11px/1 system-ui, sans-serif; fill: var(--muted); }
.estate-edge line { stroke: var(--mesh-ink, var(--muted)); stroke-width: 2; }
.estate-edge-count { font: 600 11px/1 system-ui, sans-serif; fill: var(--muted); }
.estate-legend { margin-top: 16px; font-size: 13px; line-height: 1.7; }
.estate-legend p { margin: 0 0 8px; }
.estate-rows { margin-top: 16px; overflow-x: auto; }
.estate-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.estate-table th, .estate-table td {
  text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.estate-table th { font-weight: 600; color: var(--muted); white-space: nowrap; }
.estate-table .estate-num { text-align: right; }
/* The state column carries the same three tones as the picture, in text: colour is never the
   only channel, and this column is the channel that survives a printout. */
.estate-table td[data-tone="attention"] { color: var(--mesh-attention); }
.estate-table td[data-tone="unknown"] { color: var(--muted); }
.estate-reason, .estate-runtime { font-size: 12px; line-height: 1.5; margin-top: 2px; }

/* ---------- Tasks and the top bar on a narrow screen ----------
   (ADR-0417) The shop's breakpoint,
   860px, so a phone reads the shop and the inbox the same way: one column.

   The back button is the narrow screen's way from a task to the list, and a wide
   screen has the list beside the task, so it is hidden there. */
.tasks-back { display: none; }

/* A tablet: the three panes still fit, but not at their desktop widths. The list
   keeps a width somebody can scan; the task takes the rest. !important because the
   list's resizer writes the column widths inline — a width dragged on a desktop
   is not a width for this screen. */
@media (max-width: 1180px) and (min-width: 861px) {
  .tasks { grid-template-columns: 180px 300px 1fr !important; }
  .tasks-detail { padding: 16px; }
  .tasks-field { grid-template-columns: 120px 1fr; }
}

@media (max-width: 860px) {
  /* The top bar: the view names scroll sideways within it rather than pushing
     the icons off the screen, and the organisation's name, which the page does
     not need to be used, gives its place up. */
  .topbar { gap: 2px; padding: 0 6px; }
  .topbar .org, .topbar .divider { display: none; }
  .topnav { flex: 1 1 auto; }
  .topnav > a, .topnav-group-btn { padding: 0 8px; }
  .topbar .spacer { display: none; }
  .icon-btn { width: 36px; height: 36px; flex: 0 0 auto; }
  main { padding: 12px; }

  /* The inbox and Start: the list, or the task — never both. The grid carries
     has-selection while something is open. */
  .tasks, #start-grid { grid-template-columns: 1fr !important; min-height: 0; }
  /* A grid item is as wide as its content by default, and a row of chips that
     never wraps is wide: without this the chips widened the whole pane past the
     screen instead of scrolling inside it. */
  .tasks > * { min-width: 0; }
  .tasks-col-resizer { display: none; }
  .tasks.has-selection > .tasks-folders,
  .tasks.has-selection > .tasks-list-pane { display: none; }
  .tasks:not(.has-selection) > .tasks-detail { display: none; }
  .tasks-back { display: inline-flex; margin: 0 0 12px; }

  /* The folders become one row of chips above the list. */
  .tasks-folders { padding: 10px; gap: 8px; }
  .tasks-folders nav { flex-direction: row; flex-wrap: nowrap; overflow-x: auto; gap: 6px; }
  .tasks-folder { flex: 0 0 auto; white-space: nowrap; }
  .tasks-list { max-height: none; }
  .tasks-item { padding: 12px; }

  /* The task: its actions under its name, and each field's label above its value
     rather than in a 150px column beside it. */
  .tasks-detail { padding: 14px; }
  .tasks-detail-head { flex-direction: column; align-items: stretch; gap: 10px; }
  .tasks-detail-head h1 { font-size: 18px; overflow-wrap: anywhere; }
  .tasks-detail-actions { flex-wrap: wrap; flex-shrink: 1; }
  .tasks-fields { max-width: none; }
  .tasks-field, .tasks-approval .tasks-field { grid-template-columns: 1fr; gap: 2px; }
  .appr-buttons { flex-wrap: wrap; }
  .tasks-detail-tabs { overflow-x: auto; }
  .tasks-detail-tabs button { white-space: nowrap; }

  /* Access review: a row is a card, each cell named by its data-label, the answer
     buttons under it at a size a thumb finds. The column heads, and the filter row
     the table enhancer puts under them, go with the columns they stood above. */
  .rct-filters { flex-wrap: wrap; gap: 8px; }
  .rct-filters .field.inline { margin: 0 !important; }
  .rct-table thead { display: none; }
  .rct-table, .rct-table tbody, .rct-table tr, .rct-table td { display: block; width: 100%; }
  .rct-table tr { padding: 10px 12px; border-bottom: 1px solid var(--border); }
  .rct-table td { border: 0; padding: 3px 0; overflow-wrap: anywhere; }
  /* The label is set in the cell's left margin rather than as a grid column, so a
     cell's own content — a pill and the order beside it — keeps flowing as text. */
  .rct-table td[data-label] { position: relative; padding-left: 132px; }
  .rct-table td[data-label]::before {
    content: attr(data-label); position: absolute; left: 0; top: 4px; width: 124px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    color: var(--muted); font-size: 12px;
  }
  .rct-table td.row-actions { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 8px; }
  .rct-table td.row-actions:empty { display: none; }
  .rct-table td.row-actions .btn { min-height: 40px; }
}

/* ---------- FEEL assistant (feel-assistant.js, ADR-0445) ---------- */
/* A conversation on the left, the expression and its test on the right, the history
   and favourites under them. It looks like the Developer View on purpose: it is the
   same kind of tool, for the same fields. */
.fa-overlay {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(20, 22, 26, 0.45);
  display: grid; place-items: center; padding: 24px;
}
.fa-modal {
  width: min(1180px, 96vw); height: min(780px, 92vh);
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border-radius: 10px;
  box-shadow: 0 18px 60px rgba(20, 22, 26, 0.35);
}
.fa-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-bottom: 1px solid var(--border);
}
.fa-mark { display: inline-flex; color: var(--accent); }
.fa-mark svg { width: 16px; height: 16px; }
.fa-title { margin: 0; font-size: 15px; }
.fa-target {
  font-size: 12px; color: var(--muted); background: var(--bg);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px;
  max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fa-sp { flex: 1 1 auto; }
.fa-worker { font-size: 12px; color: var(--muted); }
.fa-worker select { font: inherit; margin-left: 4px; }
.fa-body { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(300px, 5fr) 6fr; }
.fa-chat {
  min-height: 0; display: flex; flex-direction: column;
  border-right: 1px solid var(--border); background: var(--bg);
}
.fa-log { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.fa-intro { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.55; }
.fa-msg { border-radius: 10px; padding: 8px 10px; font-size: 13px; line-height: 1.5; max-width: 92%; }
.fa-msg.user { align-self: flex-end; background: var(--accent-soft); white-space: pre-wrap; }
.fa-msg.ai { align-self: flex-start; background: var(--surface); border: 1px solid var(--border); display: grid; gap: 6px; }
.fa-expl { white-space: pre-wrap; }
.fa-code {
  margin: 0; padding: 6px 8px; border-radius: 6px; background: var(--bg); border: 1px solid var(--border);
  font: 12px/1.5 ui-monospace, monospace; white-space: pre-wrap; overflow-wrap: anywhere;
}
.fa-check { font: 12px/1.4 ui-monospace, monospace; }
.fa-check.ok, .fa-out.ok { color: var(--ok); }
.fa-check.warn { color: var(--warn); }
.fa-check.err, .fa-out.err { color: var(--danger); }
.fa-msg-foot { display: flex; gap: 10px; align-items: center; justify-content: space-between; }
.fa-meta { font-size: 11px; color: var(--muted); }
.fa-status { margin: 0; padding: 6px 12px; font-size: 12px; color: var(--muted); }
.fa-status.err { color: var(--danger); }
.fa-status.busy::before {
  content: ""; display: inline-block; width: 10px; height: 10px; margin-right: 6px; vertical-align: -1px;
  border: 2px solid var(--border-strong); border-top-color: var(--accent); border-radius: 50%;
  animation: fa-spin 0.8s linear infinite;
}
@keyframes fa-spin { to { transform: rotate(360deg); } }
.fa-ask { display: flex; gap: 8px; align-items: flex-end; padding: 10px 12px; border-top: 1px solid var(--border); }
.fa-ask textarea {
  flex: 1 1 auto; resize: vertical; min-height: 3.2em; max-height: 30vh;
  font: inherit; font-size: 13px; line-height: 1.5; padding: 6px 8px; border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); color: var(--text);
}
.fa-work { min-height: 0; overflow: auto; padding: 12px; display: flex; flex-direction: column; gap: 6px; }
.fa-label { font-size: 12px; color: var(--muted); }
.fa-vars {
  width: 100%; font: 12px/1.5 ui-monospace, monospace; padding: 6px 8px;
  border: 1px solid var(--border-strong); border-radius: 6px; background: var(--bg); color: var(--text);
  resize: vertical; max-height: 26vh;
}
.fa-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.fa-out { font: 12px/1.5 ui-monospace, monospace; color: var(--muted); overflow-wrap: anywhere; }
.fa-star { font-size: 16px; color: var(--muted); }
.fa-star.on { color: var(--warn); }
.fa-note { margin: 0; font-size: 12px; }
.fa-note.warn { color: var(--warn); }
.fa-lists { margin-top: 10px; border: 1px solid var(--border); border-radius: 8px; display: flex; flex-direction: column; min-height: 140px; }
.fa-tabs { display: flex; align-items: center; gap: 2px; padding: 4px 6px; border-bottom: 1px solid var(--border); }
.fa-tabs [role="tab"] {
  border: 0; background: none; padding: 5px 8px; border-radius: 6px; cursor: pointer;
  font-size: 12px; color: var(--muted);
}
.fa-tabs [role="tab"].on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.fa-local { font-size: 11px; color: var(--muted); }
.fa-list { list-style: none; margin: 0; padding: 4px; overflow: auto; max-height: 26vh; }
.fa-list li { display: flex; align-items: center; gap: 4px; }
.fa-list li + li { border-top: 1px solid var(--border); }
.fa-pick {
  flex: 1 1 auto; min-width: 0; display: grid; gap: 2px; text-align: left;
  border: 0; background: none; padding: 6px; border-radius: 6px; cursor: pointer; color: var(--text);
}
.fa-pick:hover { background: var(--bg); }
.fa-pick-name { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fa-pick-expr {
  font: 11px/1.4 ui-monospace, monospace; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fa-rename { flex: 1 1 auto; font: inherit; font-size: 12px; padding: 2px 4px; }
.fa-empty { padding: 8px; font-size: 12px; color: var(--muted); }
.fa-list-foot { justify-content: flex-end; padding: 4px 6px; }
@media (max-width: 760px) {
  .fa-overlay { padding: 0; }
  .fa-modal { width: 100vw; height: 100vh; border-radius: 0; }
  .fa-body { grid-template-columns: 1fr; grid-template-rows: minmax(240px, 1fr) auto; overflow: auto; }
  .fa-chat { border-right: 0; border-bottom: 1px solid var(--border); }
}
/* The spark beside a focused dmn-js cell: one button, moved to whichever cell has the
   focus, because dmn-js re-renders the cells themselves on every change. */
.feel-ai-float {
  position: fixed; z-index: 65; width: 20px; height: 18px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--accent); border-radius: 5px; background: var(--surface);
  color: var(--accent); cursor: pointer; box-shadow: 0 1px 3px rgba(20, 22, 26, 0.2);
}
.feel-ai-float[hidden] { display: none; }
#feel-assistant-btn svg { width: 18px; height: 18px; }

/* The business architecture (ADR-0305, roadmap B8). The landing view is the gap report,
   grouped by kind and never ranked: the report has no severity, so nothing here draws
   one — no red for one kind and grey for another, one card per kind in the server's
   order. The list is the shared table (ADR-0286); the detail page is a sequence of
   statements, like the business-object page beside it. */
.ba-root { padding: 18px 22px 26px; }
.ba-root .between > .btn { flex: none; white-space: nowrap; }
.ba-tabs { display: flex; gap: 4px; margin: 16px 0 0; border-bottom: 1px solid var(--border); }
.ba-tabs button { background: none; border: 0; border-bottom: 2px solid transparent; padding: 8px 12px; font: inherit; color: var(--muted); cursor: pointer; }
.ba-tabs button[aria-selected="true"] { color: var(--text); border-bottom-color: var(--accent); }
.ba-count { font-size: 12px; color: var(--muted); }
.ba-checked { margin: 14px 2px 0; }
.ba-note { margin: 8px 2px 12px; font-size: 12px; color: var(--muted); max-width: 90ch; }
.ba-kind { margin-top: 12px; }
.ba-kind h2, .ba-section h2 { font-size: 15px; margin: 0 0 4px; display: flex; align-items: center; gap: 8px; }
.ba-findings, .ba-list { margin: 8px 0 0; padding-left: 18px; line-height: 1.7; }
.ba-clean { margin: 14px 2px 0; font-size: 12px; }
.ba-filters { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 14px 0 10px; font-size: 13px; }
.ba-summary { font-size: 12px; color: var(--muted); margin-top: 2px; max-width: 60ch; }
.ba-tag { margin-right: 2px; }
.ba-state.proposed { border-style: dashed; }
.ba-state.active { border-color: var(--accent); color: var(--accent); }
.ba-state.deprecated { color: var(--muted); text-decoration: line-through; }
.ba-head { margin-top: 10px; }
.ba-head h1 { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ba-section { margin-top: 14px; }
.ba-figures { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); margin: 2px 0 6px; }
.ba-stages { margin: 8px 0 0; padding-left: 0; list-style: none; display: flex; flex-direction: column; gap: 10px; }
.ba-stage { border-left: 3px solid var(--border); padding-left: 10px; }
.ba-confirm { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 8px; }
.ba-confirm input { flex: 1 1 220px; min-width: 0; }
.ba-scope { margin: 10px 2px 0; font-size: 12px; color: var(--muted); max-width: 90ch; line-height: 1.5; }

/* Favourite and recent diagrams (diagram-shortcuts.js). The star sits beside a
   diagram's name in a list and in the top-bar menu; it is a toggle, so it reads as
   text rather than as a button until it is pointed at or on. */
.dg-star {
  background: none; border: 0; padding: 0 4px; cursor: pointer; font-size: 15px; line-height: 1;
  color: var(--muted); opacity: .55;
}
.dg-star:hover, .dg-star:focus-visible { opacity: 1; color: var(--warn); }
.dg-star.on { opacity: 1; color: var(--warn); }
.dropdown-menu .dg-star { width: auto; flex: 0 0 auto; padding: 6px 8px; }
.dg-menu { min-width: 300px; max-width: 420px; max-height: 70vh; overflow-y: auto; }
.dg-row { display: flex; align-items: center; }
.dropdown-menu .dg-row a { flex: 1 1 auto; min-width: 0; }
.dg-row .dg-name { overflow: hidden; text-overflow: ellipsis; }
.dg-row .dg-sub { font-size: 12px; }
.dg-row.dg-gone { padding: 0 0 0 10px; gap: 9px; }
.dg-chip { font-size: 10px; flex: 0 0 auto; }

/* ---------- Display settings (#/console/settings) ----------
   How this person reads: number format, time zone, language
   (ADR-0459). The screen is mostly one list of
   options, each carrying its own sample — the sample is what somebody is really
   choosing, so it is given the same weight as the name beside it. */
.ds-preview {
  margin-top: 14px; padding: 12px 14px; border: 1px solid var(--border);
  border-radius: 8px; background: var(--bg);
}
.ds-preview h2 { margin: 0 0 10px; font-size: 13px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.ds-label { font-size: 11px; color: var(--muted); margin-bottom: 2px; }
.ds-options { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.ds-option {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 10px;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
}
.ds-option:hover { background: var(--bg); }
/* The sample is monospaced because the thing being compared between two rows is
   where the separators fall, and a proportional font moves them. */
.ds-option-sample { font-family: ui-monospace, monospace; font-size: 13px; color: var(--muted); }
.ds-option input:checked ~ .ds-option-name { font-weight: 600; }

/* The plan an import shows before it replaces anything
   (ADR-0467): one box per artifact that differs. */
.import-choices .import-changed { list-style: none; padding: 0; margin: 10px 0 0; max-height: 40vh; overflow: auto; }
.import-choices .import-changed li { padding: 4px 0; }
.import-choices .import-changed label { display: flex; gap: 8px; align-items: baseline; cursor: pointer; }
.import-choices .import-bulk { display: flex; gap: 6px; }
.import-choices .import-new { margin-top: 12px; }
.import-choices .import-new ul { margin: 6px 0 0; max-height: 25vh; overflow: auto; }
.import-choices .import-group { margin-top: 14px; }
.import-choices .import-group h3 { font-size: 13px; margin: 0 0 2px; }

/* What an application's processes need from the installation and no source download
   carries, before the download and after an import (ADR-0467). */
.source-download .source-constants { display: flex; gap: 8px; align-items: baseline; margin-top: 10px; cursor: pointer; }
.source-download .needs-title { font-size: 14px; margin: 16px 0 0; }
.source-download .need-group, .import-needs .need-group { margin-top: 12px; }
.source-download .need-group h3, .import-needs .need-group h3 { font-size: 13px; margin: 0 0 2px; }
.source-download .need-group ul, .import-needs .need-group ul { margin: 6px 0 0; padding-left: 18px; max-height: 30vh; overflow: auto; }
.source-download .need-group li, .import-needs .need-group li { padding: 2px 0; }
.need-state { margin-left: 6px; font-size: 12px; color: var(--warn); }
.need-state[data-state="set"], .need-state[data-state="configured"] { color: var(--ok); }
.need-state[data-state="fallback"] { color: var(--muted); }
.need-carried { margin-left: 6px; font-size: 12px; color: var(--muted); }
.source-download .package-catalogs { list-style: none; padding: 0; margin: 6px 0 0; max-height: 25vh; overflow: auto; }
.source-download .package-catalogs li { padding: 3px 0; }
.source-download .package-catalogs label { display: flex; gap: 8px; align-items: baseline; cursor: pointer; }
.source-download .package-warnings { margin-top: 12px; }
.cat-mappings .mapping-add { display: grid; grid-template-columns: minmax(140px, 1fr) 2fr 2fr auto; gap: 10px; align-items: end; margin-top: 14px; }
.cat-mappings .mapping-add .field { margin: 0; }
@media (max-width: 720px) { .cat-mappings .mapping-add { grid-template-columns: 1fr; } }
.package-answers .package-question { margin-top: 12px; }
.package-answers .package-question label { display: grid; gap: 4px; }
.package-answers .package-answer { max-width: 100%; }

/* A select's remote options source in the form editor's properties panel
   (vendor/form-js/src/atlas/editor.jsx, ADR-0473). The entries are the panel's own
   markup; only what the panel has no rule for — a list of checkboxes under one label,
   a filter's block of entries, a plain button — is laid out here, in the panel's own
   variables. */
.atlas-remote-checks > .bio-properties-panel-label { display: block; margin-bottom: 4px; }
/* A long checkbox label wraps beside its box, not below it. */
.atlas-remote-checkbox > .bio-properties-panel-checkbox { display: flex; align-items: flex-start; }
.atlas-remote-checkbox > .bio-properties-panel-checkbox > .bio-properties-panel-input { margin-top: 3px; flex: 0 0 auto; }
.atlas-remote-checkbox > .bio-properties-panel-checkbox > .bio-properties-panel-label { margin-top: 0; }
.atlas-remote-checks > .bio-properties-panel-checkbox { margin: 2px 0; }
.atlas-remote-filter { border: 1px solid var(--color-borders-group, var(--border)); border-radius: 2px; padding: 4px 0; margin: 0 0 8px; }
.atlas-remote-filter .bio-properties-panel-entry { margin-right: 12px; }
.atlas-remote-button { font: inherit; font-size: var(--text-size-small, 13px); padding: 4px 10px; margin: 4px 0; border: 1px solid var(--input-border-color, var(--border-strong)); border-radius: 2px; background: var(--input-background-color, var(--surface)); color: var(--color-text, var(--text)); cursor: pointer; }
.atlas-remote-button:hover:not(:disabled) { border-color: var(--input-focus-border-color, var(--accent)); }
.atlas-remote-button:disabled { opacity: 0.6; cursor: default; }
.atlas-remote-filter .atlas-remote-remove { margin-left: 12px; }
/* The directory field's own warn states (form-theme.css lays the field out): the
   preview's "example data" note and a chosen id the directory no longer resolves. */
.fjs-container .atlas-rs-preview { background: var(--warn-soft); color: var(--warn); }
.fjs-container .atlas-rs-selected.atlas-rs-unknown { border-color: var(--warn); background: var(--warn-soft); }
/* The event log (web/eventlog.js): a reception, start or keeping sits under the send it
   shares a cause with, so the send reads as the head of its group. */
.el-row.el-child td { border-top-color: transparent; }
.el-row.el-child td:nth-child(4) { padding-left: 22px; }
/* A send's mark (nobody received it, kept, expired unreceived) stands a size below the
   row's text, so it reads as a note on the verb rather than as a second headline. */
.el-row .pill { font-size: 11px; line-height: 16px; padding: 1px 7px; }
