/* Data Product Factory — one stylesheet, no build step, Blueprint's design language.
 *
 * Palantir's Blueprint is a React library whose styling is SCSS compiled to a single
 * sheet. This application is server-rendered Jinja with no build step and no React,
 * so what is adopted here is the *design language* — the palette, the 10px grid, the
 * 2px corner, the elevation recipe, the control geometry — expressed as the custom
 * properties the app already used. That is also where Blueprint itself is going: v6
 * on develop moves off SCSS variables to CSS custom properties and `color-mix` in
 * OKLCH, which is exactly what a no-build-step app can consume directly.
 *
 * The values below come from Blueprint's published tokens. Which version each
 * Foundry surface ships, and what Palantir overrides internally, is not verifiable
 * from outside — their product code is closed — so nothing here is guessed at from
 * screenshots of Foundry.
 *
 * Light theme only, deliberately.
 *
 * What Blueprint changes about the previous sheet, in one line each:
 *
 * **Corners are 2px everywhere.** Blueprint has one radius and uses it for buttons,
 * cards, tags and inputs alike. Rounded 10px cards read as consumer software; this
 * is an instrument.
 *
 * **Hairlines are shadows, not borders.** A surface is `0 0 0 1px rgba(black,.1)`
 * plus a drop shadow, which is why Blueprint cards sit *on* the background rather
 * than being drawn onto it. Borders remain for dividers inside a surface.
 *
 * **Controls have fixed heights** — 30px standard, 24px small — so a button, a
 * select and an input line up on a row without anybody nudging padding.
 *
 * **Weight is 400 or 600.** Blueprint has no 550s and no negative tracking.
 *
 * What is kept from before, because it is ours rather than Blueprint's absence of
 * an opinion: colour means one thing (an amber tag is always "somebody must
 * decide"), density is a feature, and every page has one primary action in
 * `.page-head`.
 */

/* --- the namespace, and why it needs a rule ---------------------------------
 *
 * Five sheets load on every page: this one, then spine, phase1, build, govern.
 * There is one global namespace across all five, no build step and no scoping, so
 * a class defined for one component applies to every element anywhere that happens
 * to share the name. Two of those have already cost real time:
 *
 *   `.trunc` was a phase1 rule with `display: block`, put on a `<td>` on the
 *   connections page. A `<td>` with `display: block` leaves table layout, so the
 *   cell rendered short of its own row and drew its bottom border in the wrong
 *   place.
 *
 *   `.req` here is the requirement *card* — bordered, padded. An
 *   `<i class="req">optional</i>` in a form label picked up the border and the
 *   padding and rendered in a box it never asked for. It is `.optional` now.
 *
 * Neither was caught by the test suite and neither ever will be: no assertion can
 * see a border. So the guard has to be the naming, and it is two rules.
 *
 * **A lane sheet's private classes carry the lane's prefix.** `b-` in build.css,
 * `g-` in govern.css. A name with the prefix is either defined in that sheet or it
 * is a typo, and a typo is visible — which is exactly why build.css adopted it,
 * after seven undefined class names on its pages rendered as browser defaults.
 * spine.css and phase1.css predate the convention and are not prefixed; they are
 * not being renamed wholesale, but anything *new* in them takes `s-` and `p-`, and
 * anything touched for another reason takes it on the way past.
 *
 * **A class used by more than one lane is a shared class and lives here.** Not
 * copied here — moved. Two sheets defining the same bare `.foo` means the later
 * link order silently wins, which is a coin toss decided by base.html rather than
 * by anybody's intent.
 *
 * The corollary, and the one worth saying out loud: **never fix a collision by
 * adding a counter-rule.** `tr.typecard` in govern.css spent four declarations
 * putting a row back into table layout after a card rule here took it out. Rename
 * the narrower use instead — the row became `.g-typerow` and both the card and the
 * counter-rules were deleted. A counter-rule leaves two components still sharing
 * one name and adds a third place to look.
 *
 * tests/test_css_namespace.py enforces the parts of this that are mechanical.
 */

/* --- tokens ---------------------------------------------------------------- */

:root {
  /* Blueprint's greyscale. `--ink` is $pt-text-color ($dark-gray1), `--ink-3` is
     $pt-text-color-muted ($gray1); the two dividers are $pt-divider-black at the
     two alphas Blueprint actually uses. */
  --black:  #111418;
  --ink:    #1c2127;
  --ink-2:  #404854;
  --ink-3:  #5f6b7c;
  --line:   rgba(17, 20, 24, .15);
  --line-2: rgba(17, 20, 24, .08);
  --bg:     #f6f7f9;   /* $pt-app-background-color, $light-gray5 */
  --panel:  #ffffff;
  --side:   #ffffff;
  --hover:  #edeff2;   /* $light-gray4 */
  --active: #dce0e5;   /* $light-gray2 */

  /* The four intents. Each is a foreground dark enough to read on white, the fill
     its solid form uses, the tint it sits on, and a hairline.

     The tints are mixed rather than hand-picked hexes: one number changes the
     strength of all four, and they stay in step. `color-mix` in OKLCH is the v6
     direction and mixes perceptually, so a 12% green and a 12% orange carry the
     same weight — which is the whole reason an amber tag and a green one can mean
     "decide" and "done" without one shouting. The hex above each is the fallback a
     browser without `color-mix` keeps. */
  --good: #1c6e42;  --good-fill: #238551;  --good-bg: #e8f2ed;  --good-line: #cfe3d8;
  --warn: #935610;  --warn-fill: #c87619;  --warn-bg: #fbf0e3;  --warn-line: #eddcc4;
  --bad:  #ac2f33;  --bad-fill:  #cd4246;  --bad-bg:  #fbeaea;  --bad-line:  #f0d2d2;
  --info: #215db0;  --info-fill: #2d72d2;  --info-bg: #e8eefa;  --info-line: #cfdcf3;
  --muted-bg: rgba(143, 153, 168, .15);   /* $gray3 at Blueprint's minimal-tag alpha */
}

@supports (color: color-mix(in oklch, white, black)) {
  :root {
    --good-bg: color-mix(in oklch, var(--good-fill) 12%, #fff);
    --warn-bg: color-mix(in oklch, var(--warn-fill) 12%, #fff);
    --bad-bg:  color-mix(in oklch, var(--bad-fill)  12%, #fff);
    --info-bg: color-mix(in oklch, var(--info-fill) 12%, #fff);
    --good-line: color-mix(in oklch, var(--good-fill) 26%, #fff);
    --warn-line: color-mix(in oklch, var(--warn-fill) 26%, #fff);
    --bad-line:  color-mix(in oklch, var(--bad-fill)  26%, #fff);
    --info-line: color-mix(in oklch, var(--info-fill) 26%, #fff);
  }
}

:root {
  /* Blueprint's type scale, trimmed to the five sizes this application has pages
     for. 14px body, 12px small; headings are its h2/h4 rather than its h1, which at
     36px is a marketing size. */
  --t-micro: 11px;
  --t-small: 12px;
  --t-body:  14px;
  --t-lead:  16px;
  --t-h2:    18px;
  --t-h1:    28px;
  /* Display numerals — a count on a tile, read at a glance rather than in a
     sentence. Two sizes, because a page has at most two orders of them. */
  --t-num:   20px;
  --t-num-l: 26px;

  /* v6's transition, on everything that has one. 100ms is short enough that a
     hover reads as a response rather than an animation. */
  --ease: cubic-bezier(.4, 1, .75, .9);
  --quick: 100ms;

  /* A hairline is one device pixel, not one CSS pixel. On a retina screen a 1px
     border is two physical pixels and reads as a drawn line; half of one reads as
     an edge. It is the cheapest craft detail there is — one media query — and it
     is most of why a dense table looks sharp rather than ruled. */
  --hair: 1px;

  /* $pt-grid-size is 10px and everything is a multiple or a half of it. */
  /* Two half-steps, because four people independently reached for something
     between 5 and 10: the gap between a badge and the word beside it, and the
     padding inside a chip. Declared rather than left as stray literals — a scale
     with undeclared exceptions is a scale nobody follows. */
  --s0: 3px;  --sh: 7px;
  --s1: 5px;  --s2: 10px; --s3: 15px; --s4: 20px;
  --s5: 30px; --s6: 40px; --s7: 60px;

  /* One radius. Blueprint v6 moved this to 4px — 2px is the value from v3, and it
     is the single cheapest thing in this file that dates the whole interface. */
  --r: 4px; --r-2: 4px; --r-3: 4px;

  --mono: "SF Mono", Monaco, Inconsolata, "Fira Mono", "Source Code Pro", monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
          Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
  --side-w: 240px;

  /* Blueprint v6's elevation ladder, as published: a hairline ring standing in for
     the border, then a drop that gets softer and taller rather than darker. The
     older ladder in this file used .2 alpha at every level, which reads heavy at
     rest and is why panels looked stamped onto the page rather than lying on it. */
  --e0:     0 0 0 1px rgba(0, 0, 0, .15), 0 0 5px 0 rgba(0, 0, 0, .02);
  --shadow: 0 0 0 1px rgba(0, 0, 0, .1), 0 1px 3px 0 rgba(0, 0, 0, .1),
            0 1px 2px -1px rgba(0, 0, 0, .1);
  --e2:     0 0 0 1px rgba(0, 0, 0, .1), 0 4px 6px -4px rgba(0, 0, 0, .1),
            0 10px 15px -3px rgba(0, 0, 0, .1);
  --lift:   0 0 0 1px rgba(0, 0, 0, .1), 0 20px 25px -5px rgba(0, 0, 0, .1),
            0 10px 15px -3px rgba(0, 0, 0, .1);

  /* Its two border weights, which are one colour at two alphas rather than two
     greys — so a divider inside a tinted panel stays the same divider. */
  --line-soft:   rgba(95, 107, 124, .12);
  --line-strong: rgba(95, 107, 124, .25);

  /* The two shadows every control is built from. */
  --ctl:       inset 0 0 0 1px rgba(17, 20, 24, .2), 0 1px 2px rgba(17, 20, 24, .1);
  --ctl-inset: inset 0 0 0 1px rgba(17, 20, 24, .2), inset 0 1px 1px rgba(17, 20, 24, .1);
  /* v6's focus ring: 2px of the intent, offset by 2px, and nothing else. */
  --ctl-focus: inset 0 0 0 1px var(--info-fill),
               0 0 0 2px #fff, 0 0 0 4px var(--info-fill);
}

@media (min-resolution: 192dpi), (-webkit-min-device-pixel-ratio: 2) {
  :root { --hair: .5px; }
}

* { box-sizing: border-box; }
html, body { height: 100%; }

/* Blueprint's body line-height is 1.28581 — tight, because interface text is
   labels and single lines. Running text gets 1.5 back where it is actually read. */
body { margin: 0; font: var(--t-body)/1.28581 var(--sans); color: var(--ink);
       background: var(--bg); -webkit-font-smoothing: antialiased; }

a { color: var(--info); text-decoration: none; }
a:hover { color: var(--info); text-decoration: underline; }
p { margin: 0 0 var(--s2); line-height: 1.5; }
li, td, .lede, .panel-note, .small { line-height: 1.5; }

h1 { font-size: var(--t-h1); font-weight: 600; line-height: 1.15; margin: 0 0 var(--s1); }
h2 { font-size: var(--t-h2); font-weight: 600; line-height: 1.2; margin: 0 0 var(--s2); }
h3 { font-size: var(--t-body); font-weight: 600; margin: 0 0 var(--s1); }

/* One eyebrow style, shared by section labels and stat captions, so those read as
   the same rank on every page. Table headers are no longer part of it — Blueprint
   sets them in the body size at 600 and in the heading colour, and a column header
   that shouts in 11px caps competes with the data underneath it. */
.pfield-k, .side-label, .stat-label, .listhead h2 {
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 600; color: var(--ink-3);
}

.lede  { font-size: var(--t-lead); color: var(--ink-2); max-width: 76ch; margin: 0 0 var(--s4); }
.small { font-size: var(--t-small); }
.dim   { color: var(--ink-3); }
.tight { white-space: nowrap; }
.scroll { overflow-x: auto; }

code, .mono, pre { font-family: var(--mono); }
/* Blueprint's inline code: a ring rather than a fill, so a run of them in a
   sentence does not stripe the line. */
code { font-size: var(--t-small); background: rgba(255, 255, 255, .7); color: var(--ink-3);
       box-shadow: inset 0 0 0 1px rgba(17, 20, 24, .2); border-radius: var(--r);
       padding: 2px 5px; }

/* --- shell ----------------------------------------------------------------- */

.shell { display: flex; min-height: 100%; }

/* Blueprint's navbar is 50px and lifts off the page with elevation rather than a
   border. The sidebar is the same surface stood on its end. */
.side { width: var(--side-w); flex: 0 0 var(--side-w); background: var(--side);
        border-right: 1px solid var(--line); display: flex; flex-direction: column;
        position: sticky; top: 0; height: 100vh; }
.side-top { height: 50px; display: flex; align-items: center; gap: var(--s2);
            padding: 0 var(--s3); border-bottom: 1px solid var(--line); }
.logo { width: 22px; height: 22px; border-radius: var(--r); background: var(--ink);
        color: #fff; display: grid; place-items: center; font-size: var(--t-small); flex: 0 0 22px; }
.side-title { font-weight: 600; font-size: var(--t-body); color: var(--ink); overflow: hidden;
              text-overflow: ellipsis; white-space: nowrap; }
.side-title:hover { text-decoration: none; }

.side-nav { flex: 1; overflow-y: auto; padding: var(--s2) var(--s2) var(--s5); }
.side-group { margin-bottom: var(--s4); }
.side-label { padding: 0 var(--s2) var(--s1); }
/* Blueprint's menu item: 30px tall, 2px corner, filled on selection with the
   primary intent rather than a raised white card. */
.side-nav a { display: flex; align-items: center; justify-content: space-between;
              gap: var(--s2); min-height: 30px; padding: 5px var(--s2);
              border-radius: var(--r); color: var(--ink-2); font-size: var(--t-body); }
.side-nav a:hover { background: var(--muted-bg); color: var(--ink); text-decoration: none; }
.side-nav a.on { background: var(--info-fill); color: #fff; font-weight: 400; }
.side-nav a.on:hover { background: var(--info); color: #fff; }
.side-nav a.on .n { background: rgba(255, 255, 255, .3); color: #fff; }
/* A nav row that carries its own action. The plus is Blueprint's minimal button:
   no fill until you reach for it, so the sidebar still reads as navigation rather
   than as a toolbar. */
.navrow { display: flex; align-items: center; gap: 2px; }
.navrow > a:first-child { flex: 1 1 auto; min-width: 0; }
.navadd { display: grid; place-items: center; flex: 0 0 26px; width: 26px; height: 26px;
          border-radius: var(--r); color: var(--ink-3); font-size: var(--t-lead); line-height: 1; }
.navadd:hover { background: var(--muted-bg); color: var(--ink); text-decoration: none; }
.navadd:active { background: var(--active); }

.side-nav .n { font-size: var(--t-small); font-weight: 400; color: var(--ink-3);
               font-variant-numeric: tabular-nums; background: var(--muted-bg);
               border-radius: var(--r); padding: 1px 6px; }
.side-nav .n.warn { background: var(--warn-bg); color: var(--warn); }

.side-foot { border-top: 1px solid var(--line); padding: var(--s3) var(--s4);
             display: flex; align-items: center; gap: var(--s2);
             font-size: var(--t-small); color: var(--ink-2); }
/* The version, under the person: small, and a link to what changed in it. */
.side-version { display: block; margin-top: var(--s2); font-size: var(--t-micro);
                color: var(--ink-3); font-variant-numeric: tabular-nums; }
.side-version:hover { color: var(--ink); }
.side-foot:has(.side-version) { flex-wrap: wrap; }
.avatar { width: 22px; height: 22px; border-radius: 50%; background: var(--info-bg);
          color: var(--info); display: grid; place-items: center;
          font: 600 10px var(--sans); flex: 0 0 22px; }

.body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar { height: 50px; display: flex; align-items: center; justify-content: space-between;
          padding: 0 var(--s5); background: var(--panel); box-shadow: var(--e0);
          position: sticky; top: 0; z-index: 10; }
/* One line, truncated. It wrapped inside a 50px bar and pushed itself into the
   actions beside it at 1024. */
.crumbs { display: block; font-size: var(--t-body); line-height: 20px;
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
          color: var(--ink-3); flex: 1 1 auto; min-width: 0; }
.crumbs b { color: var(--ink); font-weight: 600; }
.crumbs a { color: var(--ink-2); }
.topbar-actions { display: flex; gap: var(--s2); align-items: center; flex-shrink: 0;
                  margin-left: var(--s3); }

main { padding: var(--s5) var(--s5) var(--s7); max-width: 1180px; width: 100%; }
main.wide { max-width: 1460px; }

/* --- the page head, one shape everywhere ----------------------------------- */

.page-head { display: flex; align-items: flex-start; justify-content: space-between;
             gap: var(--s5); margin-bottom: var(--s5); }
.page-head .lede { margin-bottom: 0; }
.page-head-actions { display: flex; align-items: center; gap: var(--s2); flex-shrink: 0; padding-top: var(--s1); }

/* A section heading: 30px above it — a section break, the same gap the page head
   leaves — and 10px to the panel it heads, so it reads as that panel's title and
   not as the last line of the one before. It was 40 above and 15 below, and a
   `:first-of-type` rule that only fired when the heading happened to be the first
   <div> of its parent. */
.listhead { display: flex; align-items: baseline; justify-content: space-between;
            gap: var(--s4); margin: var(--s5) 0 var(--s2); padding-bottom: var(--s2);
            border-bottom: 1px solid var(--line); }
.page-head + .listhead, .listhead:first-child { margin-top: 0; }
.listhead h2 { margin: 0; }

/* A block, not inline-block: inline, it sat in a line box 2px below the top every
   other page's first line starts at. */
.backlink { display: block; width: fit-content; font-size: var(--t-small); color: var(--ink-3);
            line-height: 18px; margin-bottom: var(--s2); }
.backlink:hover { color: var(--ink); }

/* --- surfaces -------------------------------------------------------------- */

/* Blueprint's card: white, 2px, 20px of padding, and a shadow that includes its own
   1px ring — so the surface sits on the background rather than being outlined on
   it. Every raised thing in this sheet is built from this. */
.panel { background: var(--panel); border-radius: var(--r); --pad: var(--s4);
         padding: var(--pad); margin-bottom: var(--s4); box-shadow: var(--e0); }
.panel.flush { padding: 0; overflow: hidden; }
/* A panel's head: the title and what acts on it, on one centred line, at one of
   two heights — 44px as the bar across the top of a flush panel, 34px as the head
   inside a padded one. It was top-aligned, so an 18px title next to a 24px button
   sat 3px high, and its height followed whatever it held: twelve different
   heights across the application for one component. The minimum is a small
   button's height; a head holding a two-line block still grows, because that is
   content and not drift. */
.panel-head { display: flex; align-items: center; justify-content: space-between;
              gap: var(--s4); min-height: 24px; box-sizing: content-box; }
.panel.flush > .panel-head { padding: var(--s2) var(--s4);
                             border-bottom: 1px solid var(--line-2); }
.panel:not(.flush) > .panel-head { padding: 0 0 var(--s2); margin-bottom: var(--s3);
                                   border-bottom: 1px solid var(--line-2); }
/* One title size in a head. "The data" was an h2 at 18px beside h3s at 14px. */
.panel-head h2, .panel-head h3 { margin: 0; font-size: var(--t-body); font-weight: 600;
                                 line-height: 20px; }
.panel-note { color: var(--ink-3); font-size: var(--t-small); max-width: 84ch;
              margin: 0 0 var(--s3); }
.panel.flush .panel-note { padding: var(--s2) var(--s4) 0; }
.empty { padding: var(--s5) var(--s4); text-align: center; color: var(--ink-3);
         font-size: var(--t-body); }

.grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); align-items: start; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s4); }
@media (max-width: 1000px) { .grid, .grid-3 { grid-template-columns: 1fr; } }

/* --- facts: the property strip under a title --------------------------------
 *
 * The stat tiles are gone. A row of cards with a 28px number in each is how a BI
 * dashboard says "KPIs"; an object page says the same facts the way the phase page
 * says "Ends with": a caps label and a value on one baseline. That is `.factbar`
 * (spine.css), and `templates/_facts.html` is the one way a page writes it, so the
 * counts on every page line up with each other and with the phase facts. What
 * follows is only what a tile carried that a phase fact never needed: a dim
 * sub-fact after the value, and the tile's colour on the value itself.
 */
.factbar.facts { margin-top: var(--s2); }
.factbar small { font-size: var(--t-small); color: var(--ink-3); white-space: nowrap; }
.factbar i.good { color: var(--good); }
.factbar i.warn { color: var(--warn); }
.factbar i.bad  { color: var(--bad); }

/* --- tables: one system -----------------------------------------------------
 *
 * The owner's words were "all the tables look slightly different", and he was
 * right. There were four base classes doing one job — `compact`, `gtable`,
 * `ptable`, and no class at all — and two fixed-layout helpers, `fixed` in
 * phase1.css and `b-fixed` in build.css, whose declarations were the same line
 * written twice in two sheets.
 *
 * Density was the worst of it. Ten templates set it from the data:
 *
 *     <table class="{{ 'compact' if releases | length > 8 }} b-fixed">
 *
 * so the same table drew a 34px row on one product and a 61px row on another, and
 * nothing a reader could see explained the difference. Density is a decision about
 * the type, not a function of how many rows the query happened to return. It is
 * gone; tests/test_table_system.py fails if anybody writes it again.
 *
 * One base and four modifiers now. **Every `<table>` in the application wears
 * `.dtable`** — the test fails the build if one does not, which is also how a new
 * page finds out that the system exists.
 *
 *   .dtable      the table. Dense — 6px of vertical padding, always, whatever the
 *                row count. Fixed layout everywhere, including inside a flush
 *                panel, so a `<colgroup>` is honoured rather than advisory. Cells
 *                align to the top; long tokens break rather than force a column.
 *   .t-mid       cells centred vertically. For a register whose every row is one
 *                line, where a chip beside a word reads high against a top edge.
 *                Was `.gtable`'s `vertical-align: middle`.
 *   .t-inset     a table nested inside a card or a form rather than being the
 *                panel's whole content: small type, tighter gutters. Was `.ptable`.
 *   .t-inset.kv  …and its first column is a 170px label. Was `.ptable.kv`. The
 *                width is shared rather than per-page, which is why it is here and
 *                not in a colgroup.
 *   .t-auto      columns sized to content instead of to the colgroup. The one
 *                escape from fixed layout, for a table whose cells are samples of
 *                unknown width and whose right answer to being too wide is to
 *                scroll. One table uses it: the sample rows on a source page.
 *   tr.tgroup    the grouped variant — a band across the table that starts a
 *                group. Two sheets drew this strip: phase1's `.grouprow` and
 *                govern's `.g-group`. Same strip, two type treatments, so the
 *                chrome is here once and only the type stays with the page.
 *
 * Merged into the base and deleted: `compact` (density), `fixed` and `b-fixed`
 * (layout — `b-fixed`'s `overflow-wrap: anywhere` came along with it), and
 * `gtable`, which was those two plus the middle alignment now called `.t-mid`.
 * `gtable`'s per-table `min-width` floors moved out of govern.css and into the
 * templates as an inline `min-width` on the table: the number is the sum of that
 * table's own columns, so it belongs beside the `<colgroup>` it is derived from
 * rather than in a stylesheet nobody reads while editing the columns.
 *
 * What a table still says for itself is its column budget, and it says it in a
 * `<colgroup>` — never as a `width` on a `<th>`. A fixed table reads cell widths
 * from the first row only, which puts one page's layout in two places and makes it
 * depend on which row Jinja emitted first. The test enforces that too.
 *
 * A per-page table class survives only where it styles that page's *contents* —
 * `.logtable`'s sticky header and its timestamp and verb cells, `.typetable`'s
 * name-and-relation cell, `.peopletable`'s inline role selects. Where the class was
 * only a column budget it is gone, and the budget is in the colgroup.
 */

/* Blueprint's html-table: headers in the heading colour at 600, rows separated by
   the light divider. No zebra — striping is for tables you scan across, and these
   are read down. */
table { width: 100%; border-collapse: collapse; font-size: var(--t-body);
        table-layout: fixed; }
/* A table with no class of its own is one inside rendered prose: no colgroup to
   honour, so it sizes to its content. `.dtable` says otherwise, below. */
.panel.flush > table, .scroll > table { table-layout: auto; }
/* Any table wide enough to overflow scrolls rather than clipping. */
.panel.flush { overflow-x: auto; }
th { text-align: left; padding: 11px var(--s4); border-bottom: var(--hair) solid var(--line);
     white-space: nowrap; font-weight: 600; color: var(--ink); font-size: var(--t-small); }
td { padding: 11px var(--s4); border-bottom: var(--hair) solid var(--line-2);
     vertical-align: top; }
tr:last-child td { border-bottom: 0; }

/* The base. Fixed, and it stays fixed inside the two containers that relax it —
   a table that sizes its columns to its content starves the one carrying sentences
   and overflows the one carrying chips. On the traceability page that pushed the
   Verdict column off the panel entirely, so the one thing that page exists to say
   could not be read at all. */
table.dtable { table-layout: fixed; }
.panel.flush > table.dtable, .scroll > table.dtable { table-layout: fixed; }
/* Dense, always. This is Blueprint v6's compact table — the vertical padding is
   6px and the horizontal is not touched, so a dense table stays readable rather
   than cramped. It used to be opt-in per page and per row count; it is the house
   density now, because a reader moving between two pages should not have to notice
   that one of them holds nine rows and the other holds seven. */
.dtable th, .dtable td { padding-top: 6px; padding-bottom: 6px; }
/* Sentences wrap; identifiers break rather than forcing the column open. Under
   fixed layout a 60-character qualified name has nowhere to go, so say what it may
   do. Was `.b-fixed td, .b-fixed th` in build.css. */
.dtable td, .dtable th { overflow-wrap: anywhere; }
/* One row height. A single-line row was 30.5, 33.3, 34.5 or 36.5px depending on
   whether it carried plain text, a badge, a code chip or an icon link — the inline
   blocks sat on the text baseline and pushed the line box down. Centred, they fit
   the line; the minimum is the height a row with a chip needed anyway, so every
   one-line row in the application is the same 36px and taller ones are taller
   only because they hold more than a line. */
.dtable tbody td { height: 36px; }
.dtable td .badge, .dtable td .pill, .dtable td code, .dtable td .btn,
.dtable td .name { vertical-align: middle; }
/* Inside a phase step the table sits in a column beside the record panel, so it
   takes Blueprint's compact horizontal padding rather than the page's 20px: forty
   pixels a column was the difference between "consumer" and "consume / r". */
.s-step-body .dtable th, .s-step-body .dtable td { padding-left: var(--s2); padding-right: var(--s2); }

/* Middle rather than top, for the lists where every row is one line. A one-line
   row aligned to the top of its padding reads as sitting slightly high next to a
   chip. Top is the base because most tables here carry at least one column of
   prose, and prose starts at the top of its cell. */
.dtable.t-mid td { vertical-align: middle; }

/* Content-sized, and scrolling when that makes it too wide. */
table.dtable.t-auto, .panel.flush > table.dtable.t-auto,
.scroll > table.dtable.t-auto { table-layout: auto; }

/* A sortable header — `table[data-sort]`, wired in site.js. The header text
   becomes a button, so it is a tab stop and Enter works without a key handler of
   our own; it keeps the header's type exactly, because a header that turns blue
   and underlined reads as a link to somewhere else. The caret is the only chrome:
   faint until hovered or focused on an unsorted column, solid on the sorted one,
   which is the one fact a reader needs about the order. `aria-sort` on the <th>
   is what a screen reader hears; the caret only draws it. */
/* The caret hangs in the cell's padding rather than taking width from the label:
   in an 80px column "Version" plus a caret truncated to "Vers…", and a header
   that loses its word to make room for the sort mark has its priorities reversed. */
.th-sort { position: relative; font: inherit; color: inherit; background: none;
           border: 0; padding: 0; margin: 0; cursor: pointer; text-align: inherit;
           white-space: nowrap; }
.th-sort:focus-visible { outline: 0; box-shadow: var(--ctl-focus); border-radius: 2px; }
.th-sort-ind { position: absolute; left: 100%; top: 50%; transform: translateY(-50%);
               width: 12px; text-align: center; color: var(--ink-3); opacity: 0;
               font-size: 11px; line-height: 1; }
.th-sort:hover .th-sort-ind, .th-sort:focus-visible .th-sort-ind { opacity: .6; }
th[aria-sort] .th-sort-ind { opacity: 1; color: var(--ink-2); }
/* The find box over a table. Same control as the filter bar's, and the bar's
   width when it is in one; in a panel head it is narrower than the base input's
   100%, because it sits beside a title rather than in a row of its own. */
input[type=text].t-find { width: 220px; flex: 0 0 auto; }
/* In the filter bar it is the fourth control on the row, and at the bar's 260px
   the count it sits beside was pushed onto a line of its own. */
.g-filters input[type=text].t-find { width: 150px; }

tbody tr:hover { background: rgba(143, 153, 168, .07); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Every count, version and identifier. A number that changes width when it ticks
   drags the column next to it, and the eye reads the movement as a change in the
   wrong thing. */
.badge, .n, .stat, .stamp, code, .steprow-title, time { font-variant-numeric: tabular-nums; }
.panel:not(.flush) > table th, .panel:not(.flush) > table td,
.panel:not(.flush) > form table th, .panel:not(.flush) > form table td {
  padding-left: 0; padding-right: var(--s3); }

/* The inset table. Small type and a 10px gutter: it is a detail inside something
   else, and at body size in a 20px gutter it competes with the panel around it. */
.t-inset { font-size: var(--t-small); margin-top: var(--s1); }
/* `var(--hair)`, not 1px: a hairline is one device pixel, and this table used to
   draw a full one while the rows above and below it drew half. Same rule, one
   sheet, one weight. */
.t-inset th, .t-inset td { padding: 6px var(--s2); text-align: left; vertical-align: top;
                           border-bottom: var(--hair) solid var(--line-2);
                           font-size: var(--t-small); }
.t-inset thead th { border-bottom: var(--hair) solid var(--line); }
.t-inset.kv th { width: 170px; color: var(--ink-3); font-weight: 400; }
.t-inset ul { margin: 0; }

/* The band that starts a group, as chrome. A band is one strip across the table,
   and its `colspan` has to equal the column count exactly — spanning wider makes
   the browser invent the missing column, which then takes a share of the slack and
   paints nothing in it. Where a band is written as several cells instead, the
   trailing ones are padding-free so there is no seam where the content cell ends.

   The type is the page's: phase1's `.grouprow` names the group at body size,
   govern's `.g-group` sets it as an eyebrow in caps. Both are legitimate and both
   sit on this strip, which is what stops the two from drifting apart again — they
   were a `#f6f7f9` band and a `#fafbfd` band before, one hairline apart. */
tr.tgroup > th, tr.tgroup > td {
  background: var(--bg); white-space: normal; font-weight: 400;
  border-top: var(--hair) solid var(--line);
  border-bottom: var(--hair) solid var(--line); }
tr.tgroup:first-child > th, tr.tgroup:first-child > td { border-top: 0; }
tr.tgroup > th + th, tr.tgroup > td + td { padding-left: 0; padding-right: 0; }
/* A band is not a row you can hover; it is a heading. */
tbody tr.tgroup:hover > th, tbody tr.tgroup:hover > td { background: var(--bg); }

/* A rule above the first row of a group, where the group needs a break but not a
   band — the workflow definition, where the phase name is already in the row. */
tr.phasestart td { border-top: var(--hair) solid var(--line); }
tr.phasestart:first-child td { border-top: 0; }

/* --- badges and chips ------------------------------------------------------ */

/* Blueprint's minimal tag: a square-cornered 12px label on a 10%-alpha wash of its
   intent. Not a pill — a pill reads as a count badge, and these are statuses. */
.badge { display: inline-block; padding: 2px 6px; border-radius: var(--r);
         font: 400 var(--t-small)/16px var(--sans);
         white-space: nowrap; background: var(--muted-bg); color: var(--ink-2); }
.badge.good  { background: var(--good-bg); color: var(--good); }
.badge.warn  { background: var(--warn-bg); color: var(--warn); }
.badge.bad   { background: var(--bad-bg);  color: var(--bad); }
.badge.info  { background: var(--info-bg); color: var(--info); }
.badge.muted { background: var(--muted-bg); color: var(--ink-2); }
.pill { display: inline-block; padding: 1px 6px; border-radius: var(--r);
        font: 400 11px/18px var(--mono); background: var(--muted-bg); color: var(--ink-2);
        /* An identifier breaks at a space or not at all. A table cell's
           `overflow-wrap: anywhere` reached in here and printed "bronz / e". */
        overflow-wrap: normal; }
.chip { display: inline-block; background: var(--muted-bg); border-radius: var(--r);
        padding: 1px 5px; font-size: var(--t-micro); margin: 0 2px 2px 0; }

/* --- controls -------------------------------------------------------------- */

/* Blueprint's button: 30px tall, weight 400, and a border drawn as an inset shadow
   so pressing it can sink the surface rather than move a border. The gradient is
   the light-theme recipe — a white wash falling off over $light-gray5. */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
       min-height: 30px; padding: 5px var(--s2); border-radius: var(--r); border: 0;
       background: linear-gradient(to bottom, rgba(255, 255, 255, .8),
                   rgba(255, 255, 255, 0)) left no-repeat, #f6f7f9;
       box-shadow: var(--ctl); color: var(--ink);
       font: 400 var(--t-body)/1.28581 var(--sans); cursor: pointer; white-space: nowrap; }
.btn:hover { background: var(--hover); box-shadow: inset 0 0 0 1px rgba(17, 20, 24, .2),
             0 1px 2px rgba(17, 20, 24, .2); color: var(--ink); text-decoration: none; }
.btn:active { background: var(--active); box-shadow: inset 0 1px 2px rgba(17, 20, 24, .2); }
.btn.primary { background: var(--info-fill); color: #fff;
               box-shadow: inset 0 0 0 1px rgba(17, 20, 24, .2), 0 1px 2px rgba(17, 20, 24, .2); }
.btn.primary:hover  { background: var(--info); color: #fff; }
.btn.primary:active { background: #184a90; box-shadow: inset 0 1px 2px rgba(17, 20, 24, .2); }
.btn.small { min-height: 24px; padding: 0 var(--sh); font-size: var(--t-small); }
/* A disabled control still has to look like a control. Half-opacity with the ring
   removed produces grey text on grey with no edge, which reads as a rendering
   fault rather than as a refusal — and the refusal is the information. Keep the
   ring, drop the contrast. */
.btn:disabled { cursor: not-allowed; opacity: 1; color: var(--ink-3);
                background: var(--muted-bg); box-shadow: var(--ctl); }
.btn.primary:disabled { color: var(--info);
                        background: color-mix(in oklch, var(--info-fill) 22%, #fff);
                        box-shadow: inset 0 0 0 1px var(--info-line); }
@supports not (color: color-mix(in oklch, white, black)) {
  .btn.primary:disabled { background: var(--info-bg); }
}

/* And its input: the same ring, pressed inward. Fixed 30px so a control row lines
   up without anybody counting padding. */
input[type=text], input[type=email], input[type=password], input[type=file], textarea, select {
  width: 100%; height: 30px; padding: 0 var(--s2); border: 0; border-radius: var(--r);
  box-shadow: var(--ctl-inset); appearance: none;
  font: var(--t-body)/1.28581 var(--sans); color: var(--ink); background: var(--panel); }
select { padding-right: var(--s5);
         background: var(--panel) url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%235f6b7c'%3E%3Cpath d='M12 6H4l4 5z'/%3E%3C/svg%3E")
                     right var(--s2) center no-repeat; }
input[type=text]:focus, input[type=email]:focus, input[type=password]:focus,
textarea:focus, select:focus {
  outline: 0; box-shadow: var(--ctl-focus); }
input::placeholder, textarea::placeholder { color: var(--ink-3); }
textarea { height: auto; min-height: 64px; padding: var(--sh) var(--s2); resize: vertical;
           line-height: 1.5; }
label { display: block; font-size: var(--t-small); color: var(--ink-2);
        margin: 0 0 var(--s1); font-weight: 400; }
.field { margin-bottom: var(--s3); }
/* The one-line hint under a field. It replaces a paragraph, and if it needs more
   than a line it wants to be behind the `i` instead.

   It lived in phase1.css, which is where the forms that first needed it are. But
   the Documents page uses it too, and that page is nowhere near phase1's lane —
   so the rule that governs it was one a phase1 refactor could have changed
   without ever opening the template it would break. A helper used by more than
   one lane is a shared helper and belongs here. */
.hint { font-size: var(--t-small); color: var(--ink-3); margin: 5px 0 0; }

/* Blueprint tints the control itself rather than drawing a custom box; `accent-color`
   is the one-line version of that and keeps the native hit target. */
input[type=checkbox], input[type=radio] { accent-color: var(--info-fill); }

.checkline { display: flex; align-items: baseline; gap: var(--s2); cursor: pointer;
             font-weight: 400; color: var(--ink); }
.checkline input { margin: 0; flex-shrink: 0; }
.selectall { padding-bottom: 6px; margin-bottom: var(--s1);
             border-bottom: 1px dashed var(--line); }
.tinysel { width: auto !important; display: inline-block; height: 24px; padding: 0 var(--s4) 0 6px;
           font-size: var(--t-small); margin: 0 !important; }

/* --- asides ---------------------------------------------------------------- */

/* Three kinds, and they are different things: what is yours to decide, what the
   system suggests, and what it warns. One colour each, always. */
.reason   { border-left: 2px solid var(--line); padding-left: var(--s3);
            color: var(--ink-2); font-size: var(--t-small); margin: var(--s2) 0; }
.suggests { border-left: 2px solid var(--info-line); padding-left: var(--s3);
            color: var(--ink-2); font-size: var(--t-small); margin: var(--s2) 0; }
.warn-note { color: var(--warn); }

.alarm { display: flex; gap: var(--s3); padding: var(--s3) var(--s4); margin: 0 0 var(--s5);
         border: 1px solid var(--bad-line); background: var(--bad-bg); border-radius: var(--r); }
.alarm b { color: var(--bad); }

.plist { margin: var(--s1) 0 0; padding-left: 18px; }
.plist li { margin-bottom: 2px; }
.plist.tight { margin: 2px 0 0; font-size: var(--t-small); }
.plist.bad li  { color: var(--bad); }
.plist.warn li { color: var(--warn); }

.pfield { margin: var(--s3) 0 0; }
.pfield-k { margin: var(--s4) 0 var(--s1); }
.pfield p { margin: 0; }

details { margin-top: var(--s2); }
summary { cursor: pointer; font-size: var(--t-small); color: var(--info); }
details[open] summary { margin-bottom: var(--s2); }

/* Flashes float. Every mutating route redirects to an anchor, and an anchor jump
   scrolls a top-of-page message out of view — which is how a POST that worked
   perfectly reads as a button that does nothing. */
.flashes { position: fixed; top: var(--s4); right: var(--s5); z-index: 90;
           width: min(420px, calc(100vw - 40px)); display: flex; flex-direction: column;
           gap: var(--s2); pointer-events: none; }
.flash { pointer-events: auto; padding: var(--s2) var(--s3); border-radius: var(--r-2);
         font-size: var(--t-small); background: var(--panel); border: 1px solid var(--line);
         box-shadow: var(--lift); animation: flashin .2s ease-out; }
.flash.good { background: var(--good-bg); border-color: var(--good-line); color: var(--good); }
.flash.bad  { background: var(--bad-bg);  border-color: var(--bad-line);  color: var(--bad); }
.flash.info { background: var(--info-bg); border-color: var(--info-line); color: var(--info); }
@keyframes flashin { from { opacity: 0; transform: translateY(-6px); } }

.clamp2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
          overflow: hidden; }

/* --- the three phases ------------------------------------------------------ */

.nextup { display: flex; justify-content: space-between; align-items: center;
          gap: var(--s5); padding: var(--s3); border-radius: var(--r);
          background: var(--panel); margin-bottom: var(--s4);
          box-shadow: inset 0 0 0 1px var(--info-fill), var(--e0); }
.nextup-title { font-size: var(--t-h2); font-weight: 600; }

.phase { border-radius: var(--r); background: var(--panel);
         margin-bottom: var(--s4); overflow: hidden; box-shadow: var(--e0); }
.phase-head { display: flex; align-items: flex-start; gap: var(--s3);
              padding: var(--s4) var(--s5) var(--s3); }
.phase-no { flex-shrink: 0; width: 24px; height: 24px; border-radius: 50%;
            background: var(--ink); color: #fff; display: grid; place-items: center;
            font: 600 var(--t-small) var(--sans); }
.phase.is-complete .phase-no { background: var(--good); }
.phase-count { flex-shrink: 0; text-align: right; }
.phase-count b { display: block; font-size: var(--t-h2); font-variant-numeric: tabular-nums; }
.phase-steps { border-top: 1px solid var(--line); }
.phase-gate { display: flex; justify-content: space-between; gap: var(--s5);
              padding: var(--s3) var(--s5); background: #fafbfc;
              border-top: 1px solid var(--line); font-size: var(--t-small); }
.phase-gate.open { background: var(--warn-bg); }
.raci { display: flex; flex-direction: column; gap: 1px; text-align: right; flex-shrink: 0; }
.raci b { color: var(--ink-2); display: inline-block; width: 13px; }

.steprow { display: flex; align-items: center; gap: var(--s3); padding: 9px var(--s5);
           border-bottom: 1px solid var(--line-2); color: var(--ink); }
.steprow:last-child { border-bottom: 0; }
.steprow:hover { background: #f7f8fa; text-decoration: none; }
.steprow-title { flex: 1 1 auto; font-size: var(--t-body); font-weight: 400; }
.steprow-meta { display: flex; gap: var(--s1); align-items: center; flex-shrink: 0; }
.steprow.blocked { opacity: .55; }

.step-dot { width: 20px; height: 20px; border-radius: 50%; display: grid;
            place-items: center; font: 600 var(--t-micro) var(--mono);
            background: var(--muted-bg); color: var(--ink-3); flex-shrink: 0; }
.step-dot.done  { background: var(--good-fill); color: #fff; }
.step-dot.ready { background: var(--info-fill); color: #fff; }

/* Three segments, one per phase. A single bar would say 9/18 and hide that all
   nine are in the first phase. */
.phasebar { display: flex; gap: 6px; }
.phaseseg { position: relative; flex: 1 1 0; min-width: 0; padding: 6px 10px;
            border: 1px solid var(--line); border-radius: var(--r-2); background: #fbfbfd;
            overflow: hidden; font-size: var(--t-small); display: flex;
            align-items: baseline; justify-content: space-between; gap: var(--s2); }
.phaseseg span, .phaseseg b { position: relative; z-index: 1; }
.phaseseg span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.phaseseg b { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.phaseseg-fill { position: absolute; inset: 0 auto 0 0; background: var(--good-bg); }
.phaseseg.is-complete { border-color: var(--good-line); }
.phaseseg.is-current  { border-color: var(--ink); }

.flowbar { display: flex; gap: var(--s2); align-items: center; margin: 0 0 var(--s3); }

/* --- a step page ----------------------------------------------------------- */

.stephead { display: flex; justify-content: space-between; align-items: flex-start;
            gap: var(--s5); margin-bottom: var(--s2); }
.stepnav { display: flex; justify-content: space-between; gap: var(--s3);
           margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--line); }
.step-actions { display: flex; gap: var(--s2); align-items: center; margin: var(--s3) 0;
                flex-wrap: wrap; }
.step-actions form { margin: 0; }
.drawer[hidden] { display: none; }
.drawer { border-top: 1px solid var(--line); padding-top: var(--s3); }
.qform { margin-top: var(--s3); padding: var(--s3) var(--s4); border: 1px solid var(--line);
         border-radius: var(--r-2); background: #fbfbfd; }

/* --- products list --------------------------------------------------------- */

/* One row per product: what it is, who it is for, what version, how far through
   each phase, and what to do next. */
.prow { display: flex; align-items: center; gap: var(--s4); padding: var(--s3) var(--s5);
        border-bottom: 1px solid var(--line-2); color: var(--ink); }
.prow:last-child { border-bottom: 0; }
.prow:hover { background: #f7f8fa; text-decoration: none; }

.prow-id { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.prow-name { font-weight: 600; font-size: var(--t-body); overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; }
.prow-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prow-ver { flex: 0 0 70px; }

/* Three segments, one per phase. A single "4 of 9" hides that all four are in the
   first phase, and which phase you are stuck in is the interesting part. */
.prow-phases { flex: 0 0 330px; display: flex; gap: 4px; }
.pseg { position: relative; flex: 1 1 0; min-width: 0; overflow: hidden;
        border: 1px solid var(--line); border-radius: var(--r-3);
        padding: var(--s0) var(--sh); background: #fbfbfd; display: flex;
        align-items: baseline; justify-content: space-between; gap: var(--s1); }
.pseg i { position: absolute; inset: 0 auto 0 0; background: var(--good-bg); }
.pseg b, .pseg em { position: relative; z-index: 1; font-style: normal; }
.pseg b { font-size: var(--t-small); font-weight: 400; color: var(--ink-2);
          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pseg em { font-size: var(--t-micro); color: var(--ink-3);
           font-variant-numeric: tabular-nums; }
.pseg.is-complete { border-color: var(--good-line); }
.pseg.is-complete i { background: #dcefe4; }
.pseg.is-current { border-color: var(--ink); }

.prow-next { flex: 0 0 230px; font-size: var(--t-small); color: var(--info); font-weight: 400;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 1100px) {
  .prow { flex-wrap: wrap; gap: var(--s2); }
  .prow-id { flex: 1 1 60%; }
  .prow-phases { flex: 1 1 100%; order: 3; }
  .prow-next { flex: 0 0 auto; order: 2; }
}

/* --- source systems -------------------------------------------------------- */

.sysrow { display: flex; align-items: flex-start; gap: var(--s4); padding: var(--s2) 0;
          border-bottom: 1px solid var(--line-2); }
.sysrow:last-of-type { border-bottom: 0; }
.sysrow-name { flex: 1 1 auto; min-width: 0; font-size: var(--t-body); }
.sysrow .picker { flex: 0 0 260px; margin-top: 0; }
.syshead { display: flex; justify-content: space-between; align-items: flex-end;
           gap: var(--s4); margin: var(--s5) 0 var(--s3); padding-bottom: var(--s2);
           border-bottom: 2px solid var(--ink); }
.syshead:first-of-type { margin-top: var(--s1); }
.sysblock { border: 1px solid var(--line); border-radius: var(--r-2);
            padding: var(--s3) var(--s4); margin-bottom: var(--s2); background: var(--panel); }
.sysblock-head { display: flex; justify-content: space-between; align-items: baseline;
                 gap: var(--s3); }

.picker { position: relative; margin-top: var(--s2); }
.pickerbtn { display: flex; align-items: center; justify-content: space-between; width: 100%;
             gap: var(--s2); min-height: 30px; padding: 5px var(--s2); border: 0;
             border-radius: var(--r); background: linear-gradient(to bottom,
             rgba(255, 255, 255, .8), rgba(255, 255, 255, 0)) left no-repeat, #f6f7f9;
             box-shadow: var(--ctl); font: var(--t-body) var(--sans); color: var(--ink);
             cursor: pointer; text-align: left; }
.pickerbtn:hover { background: var(--hover); }
.picker.is-open .pickerbtn { background: var(--active);
                             box-shadow: inset 0 1px 2px rgba(17, 20, 24, .2); }
.pickerbtn .caret { color: var(--ink-3); font-size: var(--t-micro); }
.picker.is-open .caret { transform: rotate(180deg); }
.pickermenu { position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 4px);
              background: var(--panel); border-radius: var(--r);
              padding: var(--s1); box-shadow: var(--lift);
              max-height: 260px; overflow: auto; }
.pickermenu[hidden] { display: none; }
.pickermenu .checkline { padding: var(--s1) 2px; border-radius: var(--r-3); }
.pickermenu .checkline:hover { background: var(--line-2); }

/* --- proposals ------------------------------------------------------------- */

.proposal { border: 1px solid var(--line); border-radius: var(--r); margin-top: var(--s3);
            background: var(--panel); }
.proposal-head { display: flex; justify-content: space-between; align-items: baseline;
                 gap: var(--s3); padding: var(--s2) var(--s4);
                 border-bottom: 1px solid var(--line); }
.proposal-body { padding: var(--s3) var(--s4); }
.proposal-foot { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
                 padding: var(--s2) var(--s4); border-top: 1px solid var(--line);
                 background: #fbfbfc; border-radius: 0 0 var(--r) var(--r); }
.proposal-foot form { margin: 0; }
.maxbtn { padding: 2px 8px; margin-left: var(--s2); line-height: 1.2; }

pre { margin: 0; padding: var(--s3); overflow-x: auto; background: #16181c;
      color: #dfe3e8; border-radius: var(--r-2); font: 12px/1.5 var(--mono); tab-size: 2; }
pre.plain { background: var(--muted-bg); color: var(--ink); }
pre.sql { background: #1e2126; color: #e6e8eb; max-height: 380px; white-space: pre;
          margin: var(--s1) 0 0; }
.rawjson { margin-top: var(--s3); }
.rawjson summary { color: var(--ink-3); }
.rawjson pre, .proposal-body > pre { border: 1px solid #2a2e35; white-space: pre-wrap;
                                     word-break: break-word; max-height: 320px; }

.req { border: 1px solid var(--line); border-radius: var(--r-2); padding: var(--s2) var(--s3);
       margin-top: var(--s2); background: var(--panel); }
.req-head { display: flex; justify-content: space-between; gap: var(--s3);
            align-items: baseline; margin-bottom: var(--s1); }
.req p { margin: var(--s0) 0; font-size: var(--t-small); }
.reqedit input[type=text], .reqedit textarea, .reqedit select { width: 100%; margin: 0 0 var(--s1); }
.reqedit .reqtitle { font-weight: 600; }
.reqedit textarea { min-height: 46px; font-size: var(--t-small); line-height: 1.5; }
.reqedit textarea.mono { font-size: var(--t-small); }
.reqedit .req-head { margin-bottom: var(--s2); }
.reqform { margin: 0; }

/* Red / amber / green. A verdict readable across the room — a paragraph saying
   "servable with caveats" gets skimmed; a dot does not. */
.rag { display: inline-block; width: 10px; height: 10px; border-radius: 50%;
       flex-shrink: 0; vertical-align: middle; }
.rag-green { background: var(--good-fill); }
.rag-amber { background: var(--warn-fill); }
.rag-red   { background: var(--bad-fill); }

.verdict { display: flex; gap: var(--s3); align-items: flex-start; padding: var(--s3) var(--s4);
           border-radius: var(--r-2); border: 1px solid var(--line); }
.verdict .rag { margin-top: 5px; width: 12px; height: 12px; }
.verdict b { text-transform: capitalize; }
.verdict p { margin: var(--s1) 0 0; color: var(--ink-2); }
.verdict-green { background: var(--good-bg); border-color: var(--good-line); }
.verdict-amber { background: var(--warn-bg); border-color: var(--warn-line); }
.verdict-red   { background: var(--bad-bg);  border-color: var(--bad-line); }

/* --- the vault, drawn ------------------------------------------------------ */

.vault { display: flex; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s1); }
.vnode { border: 1px solid var(--line); border-radius: var(--r-2); padding: var(--s2) var(--s3);
         background: var(--panel); flex: 1 1 300px; min-width: 280px; }
.vhub { border-left: 3px solid var(--info); }
.vnode-head { display: flex; align-items: baseline; gap: var(--s2); margin-bottom: var(--s0); }
.vsat { border: 1px solid var(--line); border-left: 3px solid var(--ink-3);
        border-radius: var(--r-2); padding: var(--s2) var(--s3); margin-top: var(--s2);
        background: #fafbfc; }
.attrs { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
         gap: 6px; margin-top: 6px; }
.attr { font-size: var(--t-small); }
.attr .expr { color: var(--ink-3); font-family: var(--mono); font-size: var(--t-micro);
              margin-top: 1px; word-break: break-all; }

.reuse { display: flex; gap: var(--s3); align-items: flex-start; margin-top: var(--s3);
         padding: var(--s3) var(--s4); border: 1px solid var(--good-line);
         background: var(--good-bg); border-radius: var(--r-2); }
.reusebar { flex-shrink: 0; width: 74px; height: 6px; border-radius: var(--r-3);
            background: #d9e8df; overflow: hidden; margin-top: 6px; }
.reusebar i { display: block; height: 100%; background: var(--good-fill); border-radius: var(--r-3); }

/* --- permission ------------------------------------------------------------ */

/* Bleeds to the edges of whatever panel it is in, rather than to the edges of the
   one it was written for. The hard-coded `--s5` overhung every `--s4` panel by ten
   pixels, which is visible on any answered demand document. */
.grantbox { border-top: var(--hair) solid var(--line);
            margin: var(--s4) calc(var(--pad, var(--s4)) * -1) calc(var(--s4) * -1);
            padding: var(--s3) var(--pad, var(--s4)); background: #fafbfc;
            display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap;
            border-radius: 0 0 var(--r) var(--r); }
.panel.flush .grantbox { margin: 0; }
.grantform { width: 100%; }
.grantgrid { display: grid; grid-template-columns: 1fr 2fr 2fr; gap: var(--s2); }
.grantgrid input { margin: 0; }
.feedback { border-left: 3px solid var(--warn-fill); background: var(--warn-bg);
            padding: var(--s2) var(--s3); border-radius: 0 var(--r-2) var(--r-2) 0;
            margin: var(--s2) 0; font-size: var(--t-small); }

.waitform { width: 100%; margin-bottom: var(--s3); }
.waitstrip { display: flex; align-items: center; gap: var(--s3); padding: 9px var(--s3);
             border: 1px solid var(--warn-line); background: var(--warn-bg);
             border-radius: var(--r-2); }
.waitstrip .runline { flex: 1 1 auto; font-size: var(--t-small); color: var(--ink-2); }

/* --- an agent is running --------------------------------------------------- */

.runstrip { display: flex; align-items: center; gap: var(--s3); margin-top: var(--s2);
            padding: 9px var(--s3); border: 1px solid var(--line); border-radius: var(--r-2);
            background: #fbfbfd; }
.runstrip[hidden] { display: none; }
.runorb { display: inline-flex; gap: 5px; flex-shrink: 0; }
.runorb span { width: 6px; height: 6px; border-radius: 50%; background: var(--ink);
               animation: bob 1.05s ease-in-out infinite; }
.runorb span:nth-child(2) { animation-delay: .14s; opacity: .72; }
.runorb span:nth-child(3) { animation-delay: .28s; opacity: .48; }
@keyframes bob { 0%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-5px); } }
.runstrip .runline { font-size: var(--t-small); color: var(--ink-2); flex: 1 1 auto; }
.runstrip .runclock { font-size: var(--t-small); color: var(--ink-3);
                      font-variant-numeric: tabular-nums; flex-shrink: 0; }
.runbar { width: 90px; height: 3px; background: var(--line); border-radius: var(--r-3);
          overflow: hidden; flex-shrink: 0; }
.runbar i { display: block; height: 100%; width: 34%; background: var(--ink);
            border-radius: var(--r-3); animation: sweep 1.5s ease-in-out infinite; }
@keyframes sweep { 0% { transform: translateX(-100%); } 100% { transform: translateX(320%); } }

/* --- maximise -------------------------------------------------------------- */

/* A class on the card itself rather than a clone into a new node: no DOM is moved,
   so there is nothing to rebuild and no way for the content to be lost. */
.maximizable.is-max { position: fixed; inset: var(--s5); z-index: 60; margin: 0;
    background: var(--panel); overflow: auto; box-shadow: 0 24px 70px rgba(0,0,0,.28);
    border-radius: var(--r); }
.maximizable.is-max .rawjson pre, .maximizable.is-max .proposal-body > pre,
.maximizable.is-max pre.sql { max-height: none; }
.maximizable.is-max .attrs { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
body.has-max { overflow: hidden; }
body.has-max::after { content: ''; position: fixed; inset: 0; z-index: 55;
                      background: rgba(20, 22, 26, .45); }

@media (prefers-reduced-motion: reduce) {
  .runorb span, .runbar i, .flash { animation: none; }
}

/* --- utilities -------------------------------------------------------------
 *
 * A deliberately short list. These exist so a template never needs `style="..."`
 * — there were a hundred and seventy of those, each one a spacing value nobody
 * chose on purpose. If a layout needs something that is not here, it needs a name
 * of its own above, not another inline declaration.
 */

.row      { display: flex; align-items: flex-start; justify-content: space-between;
            gap: var(--s5); }
.row-tight { display: flex; align-items: center; gap: var(--s2); }
.row-end  { display: flex; gap: var(--s2); flex-shrink: 0; align-items: center; }
.grow     { flex: 1 1 auto; min-width: 0; }
.nowrap   { flex-shrink: 0; }

.stack    { display: flex; flex-direction: column; gap: var(--s2); }
.mt0 { margin-top: 0; }      .mt1 { margin-top: var(--s1); }
.mt2 { margin-top: var(--s2); } .mt3 { margin-top: var(--s3); }
.mt4 { margin-top: var(--s4); } .mt6 { margin-top: var(--s6); }
.mb0 { margin-bottom: 0; }   .mb1 { margin-bottom: var(--s1); }
.mb2 { margin-bottom: var(--s2); } .mb3 { margin-bottom: var(--s3); }
.mb4 { margin-bottom: var(--s4); }
.m0  { margin: 0; }

.w-narrow { max-width: 640px; }
.w-form   { max-width: 820px; }
.prewrap  { white-space: pre-wrap; }
.p0 { padding: 0; }
.w-full { width: 100%; }
.ta-right { text-align: right; }
.tnum { font-variant-numeric: tabular-nums; }
.inline { display: inline; }
.col-320 { flex: 0 0 320px; }
.ta-tall { min-height: 140px; }
.ta-mid  { min-height: 110px; }
.drawer-plain { border: 0; padding-top: 0; }
/* A card footer that bleeds to the panel edge from inside a form. */
.foot-bleed { margin: var(--s3) calc(var(--s4) * -1) calc(var(--s3) * -1); }

/* What a phase ends with and what it leaves behind — the two things somebody
   outside the team asks about it. */
.phase-ends { display: flex; gap: var(--s5); flex-wrap: wrap; margin-top: var(--s2);
              font-size: var(--t-small); color: var(--ink-2); }
.phase-ends b { color: var(--ink-3); font-size: var(--t-micro); text-transform: uppercase;
                letter-spacing: 0.07em; font-weight: 700; margin-right: var(--s1); }

/* What a phase produced, on the same line as what it ends with. It had a column of
   its own down the right, which made one phase read as two subjects and left a
   screen of empty space beside a three-line heading. */
.phase-ends { column-gap: var(--s4); row-gap: 6px; align-items: baseline; }
.phase-ends + .phase-ends { margin-top: 6px; }
.made { display: inline-flex; align-items: baseline; gap: 6px; }
.made a { font-weight: 500; }

/* The workflow file, shown as what it is: a document you read, not a form. */
pre.yaml { background: #16181c; color: #dfe3e8; max-height: 620px; overflow: auto;
           white-space: pre; font-size: var(--t-small); line-height: 1.6; }

/* --- a document, read as a document ---------------------------------------- */

.doc { max-width: 74ch; font-size: var(--t-lead); line-height: 1.65; }
.doc h1 { font-size: var(--t-h1); margin: 0 0 var(--s3); }
.doc h2 { font-size: var(--t-h2); margin: var(--s6) 0 var(--s2);
          padding-bottom: var(--s1); border-bottom: 1px solid var(--line-2); }
.doc h3 { font-size: var(--t-body); margin: var(--s4) 0 var(--s1); }
.doc p, .doc li { color: var(--ink-2); }
.doc ul, .doc ol { padding-left: 22px; margin: 0 0 var(--s3); }
.doc li { margin-bottom: var(--s1); }
.doc table { font-size: var(--t-small); margin: var(--s2) 0 var(--s3); }
.doc table th, .doc table td { padding: 6px var(--s2); }
.doc code { font-size: var(--t-small); }
.doc strong { color: var(--ink); }

pre.diff { background: #16181c; color: #b9c0c9; max-height: 460px; overflow: auto;
           font-size: var(--t-small); line-height: 1.6; white-space: pre; }

/* The artefacts a phase produced, inside the phase that produced them. */
.phase-artefacts { padding: var(--s3) var(--s5) var(--s1);
                   border-top: 1px solid var(--line-2); background: #fcfcfd; }
.phase-artefacts .t-inset td { padding-left: 0; }

/* RACI on a step page: who is accountable for the decision this step feeds. */
.step-raci { display: flex; gap: var(--s5); flex-wrap: wrap; padding: var(--s3) var(--s4);
             border: 1px solid var(--line); border-radius: var(--r-2);
             background: #fbfbfd; margin: var(--s3) 0; font-size: var(--t-small); }
.step-raci div { display: flex; flex-direction: column; gap: 1px; }
.step-raci b { font-size: var(--t-micro); text-transform: uppercase;
               letter-spacing: 0.07em; color: var(--ink-3); }

/* Where the estate is: one cell per phase, plus live. They add up to the total,
   which is the only reason to put them next to each other. */
/* One strip, cells divided by hairlines, the same shape as the phase bar on a
   product: four separate cards with big numerals read as a dashboard, and these
   are one count split four ways. */
.phasecounts { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0;
               margin-bottom: var(--s4); background: var(--panel);
               border-radius: var(--r); box-shadow: var(--e0); overflow: hidden; }
.pcount { display: flex; align-items: center; gap: var(--s2);
          padding: var(--s2) var(--s3); background: var(--panel); }
.phasecounts button.pcount + button.pcount { border-left: var(--hair) solid var(--line-2); }
.pcount-n { font-size: var(--t-num); font-weight: 600;
            font-variant-numeric: tabular-nums; min-width: 22px; }
.pcount.is-empty { opacity: .5; }
.pcount.is-empty .pcount-n { color: var(--ink-3); }
@media (max-width: 900px) { .phasecounts { grid-template-columns: repeat(2, 1fr); } }

/* The designer: a table of attributes you edit in place. Inputs sit flush in the
   cell so the grid reads as a table rather than as a stack of forms. */
.designtable td { vertical-align: top; padding: 6px var(--s2); }
.designtable input[type=text], .designtable select { height: 24px; padding: 0 var(--sh);
    font-size: var(--t-small); }
.designtable select { padding-right: var(--s4); }
.designtable .checkline { gap: var(--s1); }

/* A sketch, pushed to the bottom and marked as one. Better than deleting it: the
   shape is worth showing, and pretending it is finished is worse than saying it.

   Dimmed by colour rather than by opacity: opacity fades the whole subtree, so the
   selected item's fill washed out to a pastel and stopped reading as selected. */
.side-mock { margin-top: auto; }
.side-mock .side-label, .side-mock a:not(.on) { color: var(--ink-3); }
.side-mock a:not(.on):hover { color: var(--ink); }

/* A definition list used as a key/value block: label above value in a fixed
   column, so four of them stack into a corner of a card without a table. */
dl.kv { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 2px var(--s2);
        align-items: baseline; font-size: var(--t-small); }
dl.kv.wide { grid-template-columns: 110px 1fr; gap: var(--s1) var(--s3);
             font-size: var(--t-body); }
dl.kv dt { color: var(--ink-3); }
dl.kv dd { margin: 0; color: var(--ink); }

/* The one line under a heading that says what the thing is. */
.sub { color: var(--ink-2); font-size: var(--t-small); margin: 0; max-width: 84ch; }

/* --- the ontology, browsed as things ----------------------------------------
 *
 * The card grid that used to live here — `.typegrid`, `.typecard`, `.typecard-head`,
 * `.typecard-n`, `.typelinks` — became the table on the ontology page and no
 * template has referred to any of it since. It is deleted rather than kept, because
 * the one thing that still used the name was a `<tr class="typecard">`, which
 * inherited `display: block` from a card and had to be argued back into table
 * layout by four rules in govern.css. A component nobody renders is not free: it is
 * a name that reads as available, on a sheet that loads on every page.
 */

/* An object's properties and its links, side by side. */
.objgrid { display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--s4);
           align-items: start; }
@media (max-width: 1000px) { .objgrid { grid-template-columns: 1fr; } }
.linkgroup { margin-bottom: var(--s3); }
.linkgroup .pfield-k { margin: 0 0 var(--s1); }
.linkrow { display: flex; align-items: baseline; gap: var(--s2); padding: 5px 0;
           border-bottom: 1px solid var(--line-2); font-size: var(--t-small); }
.linkrow:last-child { border-bottom: 0; }
.coverage { display: inline-block; width: 42px; height: 4px; border-radius: var(--r-3);
            background: var(--line); overflow: hidden; vertical-align: middle; }
.coverage i { display: block; height: 100%; background: var(--ink-3); }

/* A config block somebody is going to select and copy, so it wraps rather than
   scrolls sideways — a horizontal scrollbar is how you paste half a path. */
pre.snippet { white-space: pre-wrap; word-break: break-word; margin: var(--s1) 0 0;
              border: 1px solid #2a2e35; }

/* A flush panel that needs its contents inset after all — a code block reads worse
   bleeding to the edge than a table does. */
.panel-pad { padding: var(--s3) var(--s4) var(--s4); }

/* --- the chain, and the ontology graph --------------------------------------
 *
 * Both are drawn server-side as plain SVG. No library, no client-side layout: the
 * positions are computed in Python from the same data the tables read, so the
 * picture cannot disagree with the numbers next to it — which is the only reason
 * a diagram in an audit tool is worth having.
 */

.chain { display: block; margin: var(--s2) 0; }
.chain-box { fill: var(--panel); stroke: var(--info-line); stroke-width: 1; }
.chain-box.is-empty { fill: var(--bg); stroke: var(--line); }
.chain-n { font: 600 16px var(--sans); fill: var(--ink); }
.chain-box.is-empty + .chain-n { fill: var(--ink-3); }
.chain-t { font: 400 11px var(--mono); fill: var(--ink-3); }
.chain-arrow { stroke: var(--line); stroke-width: 1; }
.chain-head { fill: var(--ink-3); }

.w-auto { width: auto !important; }

/* The ontology, drawn. Edges under nodes, both muted until you point at one —
   a graph this dense is a shape first and a set of labels second. */
.ograph { display: block; margin: var(--s2) auto; max-width: 1000px; }
.ograph-edge line { stroke: var(--line); fill: none; }
.ograph-tip { fill: rgba(17, 20, 24, .25); }
.ograph-edge:hover line { stroke: var(--info-fill); }
.ograph-node circle { fill: var(--info-bg); stroke: var(--info-fill); stroke-width: 1.5; }
.ograph-node:hover circle { fill: var(--info-fill); }
.ograph-n { font: 600 13px var(--sans); fill: var(--info); text-anchor: middle; }
.ograph-node:hover .ograph-n { fill: #fff; }
.ograph-l { font: 400 11px var(--sans); fill: var(--ink-2); text-anchor: middle; }


/* --- search in the sidebar, live ---------------------------------------------
 *
 * It moved out of the topbar because search here is mostly navigation, and the
 * place you navigate from is the nav. The panel opens over the page rather than
 * replacing it: answering "where is the churn product" should cost one keystroke,
 * not a page load.
 */
.topsearch { flex: 0 1 420px; margin: 0 var(--s4) 0 0; position: relative; }
.topsearch input { height: 26px; font-size: var(--t-small); }
@media (max-width: 900px) { .topsearch { display: none; } }

/* Wider than the box that opens it, and set in the body size rather than the small
   one. A result list is *read*, not glanced at — the whole reason it exists is to
   tell two things apart that have similar names, and 12px truncated to one line is
   how you end up opening both anyway. Three lines per hit: what it is called, which
   product it belongs to, and what it is. */
.livepanel { position: absolute; z-index: 60; top: calc(100% + 6px); left: 0;
             width: min(680px, calc(100vw - 300px)); max-height: 72vh; overflow: auto;
             background: var(--panel); border-radius: var(--r); box-shadow: var(--lift);
             padding: var(--s1) 0 0; }
.livepanel[hidden] { display: none; }

.livegroup { padding: var(--s1) 0; border-bottom: 1px solid var(--line-2); }
.livegroup:last-of-type { border-bottom: 0; }
.livegroup-k { display: flex; justify-content: space-between; padding: 4px var(--s3);
               font-size: var(--t-micro); text-transform: uppercase;
               letter-spacing: 0.06em; font-weight: 600; color: var(--ink-3); }

.livehit { display: grid; grid-template-columns: 1fr auto; gap: 0 var(--s2);
           padding: var(--sh) var(--s3); color: var(--ink); }
.livehit-name { font-size: var(--t-body); font-weight: 500; overflow: hidden;
                text-overflow: ellipsis; white-space: nowrap; }
.livehit-in { font-size: var(--t-small); color: var(--ink-3); justify-self: end;
              align-self: center; white-space: nowrap; }
.livehit-detail { grid-column: 1 / -1; font-size: var(--t-small); color: var(--ink-3);
                  line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2;
                  -webkit-box-orient: vertical; overflow: hidden; }
.livehit:hover, .livehit.is-on { background: var(--info-fill); color: #fff;
                                 text-decoration: none; }
.livehit:hover .livehit-in, .livehit.is-on .livehit-in,
.livehit:hover .livehit-detail, .livehit.is-on .livehit-detail {
  color: rgba(255, 255, 255, .85); }

.livemore { padding: var(--s0) var(--s3) 6px; font-size: var(--t-small); color: var(--ink-3); }
.liveempty { padding: var(--s3); font-size: var(--t-body); color: var(--ink-3); }
.livefoot { display: flex; justify-content: space-between; gap: var(--s3);
            border-top: 1px solid var(--line); padding: var(--s2) var(--s3);
            font-size: var(--t-small); color: var(--ink-3);
            position: sticky; bottom: 0; background: var(--panel); }

/* Who you are, and switching to somebody else. In the footer because it is
   context rather than navigation — you check it, you rarely change it. */
.whoami { margin: 0; position: relative; }
.whoami summary { list-style: none; display: flex; align-items: center; gap: var(--s2);
                  padding: var(--s1); border-radius: var(--r); color: var(--ink); }
.whoami summary::-webkit-details-marker { display: none; }
.whoami summary:hover { background: var(--muted-bg); }
.whoami summary b { display: block; font-size: var(--t-small); }
.whoami-menu { position: absolute; bottom: calc(100% + 6px); left: 0; width: 280px;
               background: var(--panel); border-radius: var(--r); box-shadow: var(--lift);
               padding: var(--s2); z-index: 60; }
.whoami-row { display: flex; flex-direction: column; align-items: flex-start; width: 100%;
              gap: 0; padding: 5px var(--s2); border: 0; background: none; cursor: pointer;
              border-radius: var(--r); text-align: left; font: inherit; }
.whoami-row:hover { background: var(--muted-bg); }
.whoami-row.is-on { background: var(--info-bg); }
.whoami-row b { font-size: var(--t-small); font-weight: 600; }

/* An explanation that is worth keeping and not worth reading twice. */
.info { margin: 0; display: inline-block; }
.info summary { list-style: none; width: 18px; height: 18px; border-radius: 50%;
                display: grid; place-items: center; background: var(--muted-bg);
                color: var(--ink-3); font: 600 11px var(--sans); cursor: pointer; }
.info summary::-webkit-details-marker { display: none; }
.info summary:hover { background: var(--info-bg); color: var(--info); }
/* One place, every time: a panel pinned to the right of the window rather than a
   bubble beside whichever `i` you happened to click. A popover that appears in a
   different spot each time makes you hunt for the thing you just asked for, and
   next to the button it covers the field you were reading about. */
.info-body { position: fixed; z-index: 70; top: 66px; right: var(--s4); width: 320px;
             background: var(--panel); box-shadow: var(--lift); border-radius: var(--r);
             padding: var(--s3); font-size: var(--t-small); line-height: 1.55;
             color: var(--ink-2); }
.info-body::before { content: 'Why'; display: block; font-size: var(--t-micro);
                     text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600;
                     color: var(--ink-3); margin-bottom: var(--s1); }
.info[open] summary { background: var(--info-fill); color: #fff; }
@media (max-width: 900px) { .info-body { position: absolute; right: 0; top: auto; } }

/* The tutorial. */
.tourgrid { display: grid; grid-template-columns: 1fr 320px; gap: var(--s4);
            align-items: start; }
@media (max-width: 1000px) { .tourgrid { grid-template-columns: 1fr; } }
.tourstep { display: flex; align-items: center; gap: var(--s2); padding: 5px var(--s1);
            border-radius: var(--r); color: var(--ink-2); font-size: var(--t-small); }
.tourstep:hover { background: var(--muted-bg); text-decoration: none; }
.tourstep.is-on { background: var(--info-fill); color: #fff; }
.tourstep.is-on .dim { color: rgba(255, 255, 255, .8); }
.tourstep-n { flex: 0 0 18px; height: 18px; border-radius: 50%; display: grid;
              place-items: center; background: var(--muted-bg); color: var(--ink-3);
              font: 600 10px var(--mono); }
.tourstep.is-on .tourstep-n { background: rgba(255, 255, 255, .25); color: #fff; }
.tourstep.is-done .tourstep-n { background: var(--good-fill); color: #fff; }

/* The new-product form: two columns, because the three questions are independent
   and stacking them made the page feel like an interview. */
.formgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4);
            align-items: start; }
@media (max-width: 1000px) { .formgrid { grid-template-columns: 1fr; } }

/* A source system is one button, because the decision is nearly always at that
   level — "we need orders" — and twelve checkboxes across three open panels was
   most of the page for the choice people spend the least time on. */
.syspick { display: flex; align-items: stretch; flex-wrap: wrap; gap: 0;
           border-radius: var(--r); box-shadow: var(--e0); margin-bottom: var(--s2);
           background: var(--panel); }
.syspick.is-on { box-shadow: inset 0 0 0 1px var(--info-fill), var(--e0); }
.syspick-main { display: flex; align-items: center; gap: var(--s2); flex: 1 1 auto;
                min-width: 0; padding: var(--s2) var(--s3); border: 0; background: none;
                font: inherit; text-align: left; cursor: pointer; color: var(--ink);
                border-radius: var(--r); }
.syspick-main:hover { background: var(--muted-bg); }
.syspick-main b { display: block; font-size: var(--t-body); }
.syspick-check { flex: 0 0 14px; width: 14px; height: 14px; border-radius: var(--r);
                 box-shadow: inset 0 0 0 1px rgba(17, 20, 24, .3); }
.syspick.is-on .syspick-check, .syspick-check.is-on { background: var(--info-fill);
                                box-shadow: inset 0 0 0 1px var(--info-fill); }
.syspick.is-some .syspick-check { background: var(--info-bg);
                                  box-shadow: inset 0 0 0 1px var(--info-fill); }
.syspick-more { border: 0; background: none; padding: 0 var(--s3); cursor: pointer;
                font: 400 var(--t-small) var(--sans); color: var(--ink-3); }
.syspick-more:hover { color: var(--ink); }
.syspick-tables { flex: 1 1 100%; padding: var(--s2) var(--s3) var(--s3) var(--s5);
                  border-top: 1px solid var(--line-2); }
.syspick-tables[hidden] { display: none; }

label.row { justify-content: flex-start; gap: var(--s2); align-items: center; }

/* Blueprint's icons in the nav. The row becomes icon + label + count, and the
   label takes the leftover width so a long name truncates rather than pushing the
   count off the edge. `currentColor` means the selected row's white text carries
   its icon with it. */
.icon { flex: 0 0 16px; opacity: .75; }
.side-nav a { gap: var(--s2); }
.side-nav a > span:not(.n) { flex: 1 1 auto; min-width: 0; overflow: hidden;
                             text-overflow: ellipsis; white-space: nowrap; }
.side-nav a:hover .icon, .side-nav a.on .icon { opacity: 1; }

/* Connections, as apps. A tile answers "is this set up, and is it real" faster
   than a row of four columns does — and the two questions are different, so the
   badge and the amber rule carry one each. */
/* The tile grid that used to be the Connections page is gone with it: cards that
   clipped their own sentences, a coloured stripe down each side standing in for
   what the badge already said, and a third of a wide screen left empty. */

/* Icons in the results. The row becomes icon · name · product, with the detail
   spanning underneath — the icon is what lets somebody skip a whole group without
   reading its header. */
.spritesheet { position: absolute; width: 0; height: 0; overflow: hidden; }
.livehit { grid-template-columns: 16px 1fr auto; align-items: baseline; }
.livehit-icon { align-self: center; opacity: .55; }
.livehit:hover .livehit-icon, .livehit.is-on .livehit-icon { opacity: 1; }
.livehit-detail { grid-column: 2 / -1; }
.livegroup-k > span:first-child { display: inline-flex; align-items: center; gap: 6px; }
.livegroup-k .icon { opacity: .6; }
.kindcell { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-3);
            font-size: var(--t-small); }

/* --- the walkthrough ---------------------------------------------------------
 *
 * One element lit, everything else dimmed. The dimming is a single enormous
 * box-shadow on the spotlight rather than four overlay panels: it is one element,
 * it cannot leave a seam, and `pointer-events: none` means the page underneath
 * stays clickable — a tour that blocks the thing it is pointing at teaches you
 * nothing about using it.
 */
.tour-spot { position: absolute; z-index: 200; border-radius: var(--r);
             box-shadow: 0 0 0 2px var(--info-fill), 0 0 0 9999px rgba(17, 20, 24, .55);
             pointer-events: none; transition: all .18s ease; }
/* Narrower than the window, always. A fixed 360 on a window with a sidebar in it
   put the card half off the right edge, and a tutorial you have to scroll
   sideways to read is worse than no tutorial. */
/* Dark, as Blueprint draws a popover over a light page: the card is the guide, not
   part of the page it is pointing at, and a white box over a white panel read as
   one more panel. Its own palette, so nothing on the page beneath can bleed in. */
.tour-card { position: absolute; z-index: 201; width: 360px;
             max-width: calc(100vw - 24px); background: #1c2127; color: #f6f7f9;
             border-radius: var(--r); padding: var(--s3);
             box-shadow: 0 0 0 1px rgba(255, 255, 255, .08), 0 18px 40px -8px rgba(0, 0, 0, .5); }
.tour-card.is-centred { position: fixed; top: 50%; left: 50%;
                        transform: translate(-50%, -50%); }
.tour-card h3 { margin: var(--s2) 0 var(--s1); font-size: var(--t-h2); color: #fff; }
.tour-card p { margin: 0 0 var(--s3); color: #c5cbd3; font-size: var(--t-body); line-height: 1.5; }
.tour-top { display: flex; justify-content: space-between; align-items: baseline; }
.tour-k { font-size: var(--t-micro); text-transform: uppercase; letter-spacing: 0.06em;
          font-weight: 600; color: #8f99a8; }
.tour-n { font-family: var(--mono); font-size: var(--t-micro); color: #8f99a8;
          font-variant-numeric: tabular-nums; }
.tour-bar { display: flex; gap: 3px; margin-top: var(--s2); }
.tour-bar i { flex: 1 1 0; height: 3px; border-radius: 1px; background: rgba(255, 255, 255, .14); }
.tour-bar i.is-done { background: rgba(76, 144, 240, .55); }
.tour-bar i.is-on { background: #4c90f0; }
.tour-role { font-size: var(--t-small); color: #8f99a8; margin: calc(-1 * var(--s1)) 0 var(--s3); }
.tour-role b { color: #f6f7f9; font-weight: 600; }
.tour-buttons { display: flex; align-items: center; gap: var(--s2); }
.tour-card .btn { background: #2f343c; color: #f6f7f9;
                  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12); }
.tour-card .btn:hover { background: #383e47; }
.tour-card .btn.primary { background: var(--info-fill); color: #fff; box-shadow: none; }
.tour-card .btn.primary:hover { background: var(--info); }
.tour-keys { display: inline-flex; gap: 3px; }
.tour-keys kbd { font-family: var(--mono); font-size: 10px; line-height: 1; padding: 3px 4px;
                 border-radius: 2px; color: #8f99a8; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .14); }
body.has-tour { overflow-x: hidden; }
@media (prefers-reduced-motion: reduce) { .tour-spot { transition: none; } }

/* Beta, on the two things that are shapes rather than finished. Quieter than a
   state badge, because it is a fact about the feature rather than about the work. */
.side-nav .n.beta { background: var(--warn-bg); color: var(--warn); font-size: var(--t-micro);
                    text-transform: uppercase; letter-spacing: .04em; }
.whoami summary .badge { font-size: var(--t-micro); }

/* What already exists, shown where the decision is made. The accent rule is the
   ontology's colour wherever it appears — a hint that looks like a warning gets
   dismissed, and this is not a warning, it is somebody else's work. */
.reuse-hints { margin: var(--s2) 0 0; }
.reuse-hint { border-left: 2px solid var(--info-line); padding: 2px 0 2px var(--s3);
              font-size: var(--t-small); margin-bottom: var(--s1); }
.syspick-known { flex: 1 1 100%; padding: 0 var(--s3) var(--s2) var(--s5);
                 color: var(--info); }

/* The RACI, in the roles the workspace has. The colour is the role's own — the
   same one it wears on the Users page — so "whose decision is this" is answered by
   looking rather than by reading four lines and remembering which team is which. */
.raci { display: flex; flex-direction: column; gap: var(--s0); align-items: flex-end; }
.raci-row { display: inline-flex; align-items: center; gap: 6px; }
.raci-row b { color: var(--ink-3); font-size: var(--t-micro); font-weight: 700;
              width: 10px; text-align: right; }
.raci.is-inline { flex-direction: row; flex-wrap: wrap; gap: var(--s2); }

/* One line per step: the dot, what it is, what it produced, where it stands.
   A grid with a stacking fallback looked fine at 1400px and fell apart at 1000,
   which is where people actually have their windows — so it is a flex row that
   wraps only the artefacts, and only when they genuinely do not fit. */
.steprow { display: flex; align-items: baseline; gap: var(--s3);
           padding: 8px var(--s4); }
.steprow .step-dot { align-self: center; }
.steprow-title { flex: 0 0 auto; min-width: 190px; max-width: 220px; }
/* One artefact per line, each intact. Wrapping them into a paragraph broke a
   name across two lines and left its badge stranded on a third, which is how three
   documents took six. A long name truncates instead. */
.steprow-made { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
                align-items: flex-start; gap: 2px;
                font-size: var(--t-small); color: var(--ink-3); }
.made { display: inline-flex; flex-wrap: nowrap; align-items: baseline;
        gap: 6px; max-width: 100%; white-space: nowrap; }
.made a { display: inline-block; max-width: 40ch; overflow: hidden;
          text-overflow: ellipsis; vertical-align: bottom; }
.steprow-meta { flex: 0 0 auto; margin-left: auto; }

/* The step row is a div so the artefacts under it can be links — an anchor cannot
   contain an anchor, which is the whole reason they were plain text. The title
   keeps the row's colour and the hover stays on the row, so it still behaves like
   one thing you can click. */
.steprow-link { color: inherit; font-weight: 500; }
.steprow-link:hover { text-decoration: underline; }
.steprow-made a { color: var(--info); }
.steprow-made a:hover { text-decoration: underline; }

/* The draft scope: one row per suggested system, with the sentence that argues
   for it. Suggestions, never selections — nothing here ticks a box. */
.scoperow { display: flex; align-items: flex-start; gap: var(--s3);
            padding: var(--s2) 0; border-bottom: 1px solid var(--line-2); }
.scoperow:last-of-type { border-bottom: 0; }

/* The scope step. Seven systems, a hundred and forty tables: collapsed by default,
   opened by what the brief points at or by what is already in scope, and every row
   carries the line that says why it is wanted. */
.sysblock { border-radius: var(--r); box-shadow: var(--e0); margin-bottom: var(--s2);
            background: var(--panel); }
.sysblock[hidden] { display: none; }
.sysblock-head { display: flex; align-items: flex-start; gap: var(--s2);
                 padding: var(--s2) var(--s3); cursor: pointer; list-style: none; }
.sysblock-head::-webkit-details-marker { display: none; }
.sysblock-head:hover { background: var(--muted-bg); }
.sysblock-body { padding: 0 var(--s3) var(--s3) var(--s5); }
.sysblock-body textarea { min-height: 46px; }
.tablelist { margin-top: var(--s2); max-height: 320px; overflow-y: auto;
             padding-right: var(--s1); }
.tablerow { display: flex; align-items: center; gap: var(--s2); padding: 2px 0; }
.tablerow[hidden] { display: none; }
.tablerow .checkline { flex: 0 0 46%; min-width: 0; align-items: center; }
/* One line each. A table's description is a sentence, and twenty sentences at full
   height is a list nobody scrolls to the end of. */
.tablerow .checkline > span { min-width: 0; overflow: hidden; text-overflow: ellipsis;
                              white-space: nowrap; }
.tablenote { flex: 1 1 auto; height: 24px; font-size: var(--t-small); }

/* --- the sign-in page ------------------------------------------------------- */
/* Its own page rather than the application chrome around a form. Somebody who has
   not been let in should not be shown the sidebar: it lists what this workspace
   contains, and that list is itself information about the organisation. */
body.plain { background: var(--bg); }
.signin { max-width: 52ch; margin: 12vh auto var(--s6); padding: var(--s5);
          background: var(--panel); border-radius: var(--r); box-shadow: var(--e2); }
.signin h1 { font-size: var(--t-h2); margin: 0 0 var(--s3); }
.signin .lede { margin-bottom: var(--s3); }
.signin p:last-child { margin-bottom: 0; }
.signin { max-width: 400px; }
.account-error { color: var(--bad); font-size: var(--t-small); margin: 0 0 var(--s3); }

/* --- users, when a directory owns them -------------------------------------- */
/* A group that is in the map reads as a fact; one that is not is noise the person
   happens to carry, and greying it is the difference between a page that explains a
   role and a page that lists twelve GUIDs. */
.grouptag { display: inline-block; font-family: var(--mono); font-size: var(--t-small);
            padding: 0 5px; border-radius: var(--r); margin: 0 var(--s0) var(--s0) 0;
            background: var(--muted-bg); color: var(--ink-2); }
.grouptag.on { background: var(--info-bg); color: var(--info); }


/* --- people -------------------------------------------------------------- */

/* The role select carries the role's colour rather than sitting beside a badge
   that repeats it. Same control metrics as everywhere else — 30px, 2px radius —
   so a column of them lines up with a column of anything else. */
/* `.people td { vertical-align: top }` used to sit here and had never applied:
   govern.css links after this sheet, so its `.gtable td { vertical-align: middle }`
   won at equal specificity and the rows were centred all along. The list is
   `.t-mid` now, which says out loud what it was already doing. */
.people select { max-width: 190px; }
.people select.role-info  { box-shadow: inset 0 0 0 1px var(--info),  0 0 0 1px rgba(45,114,210,.1); }
.people select.role-warn  { box-shadow: inset 0 0 0 1px var(--warn),  0 0 0 1px rgba(200,118,25,.1); }
.people select.role-good  { box-shadow: inset 0 0 0 1px var(--good),  0 0 0 1px rgba(29,125,79,.1); }
.people select.role-muted { box-shadow: inset 0 0 0 1px rgba(143,153,168,.5); }

/* Always the same width, so a row nobody can switch to does not read as a row
   with a control missing from it. */
.rowacts { display: flex; gap: var(--s2); justify-content: flex-end;
           align-items: center; min-height: 30px; }

/* One card per role, people assigned inside it. */
.rolecard { margin-bottom: var(--s4); }
.rolecard .empty { padding: var(--s3) var(--s4); }
/* The assign row sits under the people it adds to, at the same width as the
   controls above it — the four-different-widths look is what read as assembled
   rather than designed. */
.assign { display: grid; gap: var(--s2); align-items: center;
          grid-template-columns: 1fr 1.4fr auto; padding: var(--s3) var(--s4);
          border-top: 1px solid var(--line); }
.assign:has(select) { grid-template-columns: 1fr 1.4fr 1fr auto; }
@media (max-width: 760px) { .assign { grid-template-columns: 1fr; } }


/* --- connections --------------------------------------------------------- */

/* A grid of labelled fields at one width, rather than a row of controls each
   sized to its own placeholder. */
.connform { display: grid; gap: var(--s3); padding: var(--s3) var(--s4);
            grid-template-columns: repeat(3, 1fr); align-items: end; }
.connform .field { display: flex; flex-direction: column; gap: 4px; }
.connform .field > span { font-size: var(--t-small); color: var(--ink-3);
                          text-transform: uppercase; letter-spacing: .04em; }
.connform .tick { justify-content: flex-end; }
.connact { grid-column: 1 / -1; display: flex; gap: var(--s2);
           border-top: 1px solid var(--line); padding-top: var(--s3); }
.connresult { margin: 0 var(--s4) var(--s3); padding: var(--s2) var(--s3);
              border-radius: var(--r); background: var(--good-bg);
              box-shadow: inset 0 0 0 1px var(--good-line);
              font-size: var(--t-small); }
.connresult.bad { background: var(--bad-bg); box-shadow: inset 0 0 0 1px var(--bad-line); }
.good-text { color: var(--good); }
@media (max-width: 900px) { .connform { grid-template-columns: 1fr 1fr; } }


/* A number and its unit are two things, and `35.0days` is neither. */
.stat .unit { margin-left: 4px; font-size: var(--t-small); color: var(--ink-3);
              font-weight: 400; }


/* A native file input beside a styled 30px control undoes the whole system in one
   glance. Blueprint's approach: the button is ours, the input is invisible and
   still does the work — so keyboard and drop behaviour are the browser's. */
input[type=file] { width: 100%; height: 30px; font-size: var(--t-small);
                   color: var(--ink-2); }
input[type=file]::file-selector-button {
  height: 24px; margin-right: var(--s2); padding: 0 8px;
  border: 0; border-radius: var(--r); cursor: pointer;
  font: 600 var(--t-small)/1 var(--sans); color: var(--ink);
  background: var(--panel); box-shadow: var(--ctl);
  transition: background var(--quick) var(--ease);
}
input[type=file]::file-selector-button:hover { background: var(--hover); }

/* A non-ideal state is a sentence and a way out, not a centred shrug. */
.empty b { display: block; color: var(--ink); margin-bottom: var(--s1); }
.empty .btn { margin-top: var(--s3); }


/* --- who, as opposed to what --------------------------------------------- */

/* The three parties get their own palette, kept out of the four state intents.
   Green meant "completed" and "Data Hub" in the same viewport; amber meant
   "warning" and "Source System". A reader who learns that amber means attention
   and then meets an amber chip that means a department has learned nothing.

   Low-chroma on purpose: a party is an identity to recognise, not a condition to
   act on, so it should be quieter than any state next to it. */
.badge.party-ask   { color: #5642a6; background: #eeebfa; box-shadow: inset 0 0 0 1px #ddd8f2; }
.badge.party-own   { color: #0c5174; background: #e6f1f7; box-shadow: inset 0 0 0 1px #cfe2ec; }
.badge.party-build { color: #4a4a52; background: #eeeef1; box-shadow: inset 0 0 0 1px #e0e0e6; }
.badge.party-none  { color: var(--ink-2); background: var(--muted-bg); }
@supports (color: color-mix(in oklch, white, black)) {
  .badge.party-ask   { background: color-mix(in oklch, #5642a6 9%, #fff); }
  .badge.party-own   { background: color-mix(in oklch, #0c5174 9%, #fff); }
  .badge.party-build { background: color-mix(in oklch, #4a4a52 8%, #fff); }
}


/* --- connections, as tiles with a sheet ---------------------------------- */

/* Nine rows of four short values. Left to fill the page, the flexible column takes
   six hundred pixels for four words and the button ends up somewhere else entirely
   — a full-width table is only honest when the content is wide. */
.conns-panel { max-width: 980px; }
/* Clicking the row still opens the sheet, but the row is not a tab stop and has no
   focus style, because it never was a control: the only listener on it is `click`,
   which a keyboard cannot fire on a `<tr>`. It used to carry `tabindex="0"` and an
   outline here, which told a keyboard user this was the control and then did
   nothing when they pressed Enter. The button in the last cell is the real one. */
.conn-row { cursor: pointer; }
.conn-row:hover { background: var(--bg); }

/* The button is the affordance, so it has to look like one before the pointer
   arrives — a control that only appears on hover is a control a scanner never
   learns exists. Hovering the row promotes it rather than revealing it. */
.conns td .btn { min-width: 74px; }
.conn-row:hover td .btn { background: var(--hover);
                          box-shadow: inset 0 0 0 1px rgba(17, 20, 24, .2),
                                      0 1px 2px rgba(17, 20, 24, .2); }

/* The vendor's mark when `static/logos/<key>.svg` exists, and a monogram in the
   vendor's colour when it does not — so the page is complete before anybody has
   licensed a single logo, and complete in a different way afterwards. */
/* The tint is the vendor's colour and the letter is a darkened version of it, so
   a pale brand (DuckDB's yellow) still produces a readable mark rather than a
   yellow letter on a yellow tile. */
/* The mark on its own, at the size the vendor's own listings use it. No tinted
   tile behind it: a coloured square around every logo is chrome the logos already
   have opinions about. */
.conn-logo { flex: 0 0 auto; width: 28px; height: 28px; display: grid;
             place-items: center; overflow: hidden; }
.conn-logo img { width: 20px; height: 20px; object-fit: contain; }
/* The fallback is quiet on purpose. A saturated tile in the vendor's colour beside
   four real marks does not read as "we used their brand" — it reads as an asset
   that failed to load. ServiceNow has no permissively-licensed mark to ship, so
   this is the honest state and it should look deliberate rather than broken. */
.conn-logo i { font-style: normal; font-weight: 600; font-size: var(--t-micro);
               letter-spacing: -.02em; width: 20px; height: 20px;
               display: grid; place-items: center; border-radius: var(--r);
               color: var(--ink-2); background: var(--muted-bg);
               box-shadow: inset 0 0 0 var(--hair) var(--line); }

dialog.sheet { width: min(560px, calc(100vw - 40px)); padding: 0; border: 0;
               border-radius: var(--r-2); box-shadow: var(--lift);
               background: var(--panel); color: var(--ink); }
dialog.sheet::backdrop { background: rgba(17, 20, 24, .4); }
.sheet-form { display: flex; flex-direction: column; }
.sheet-head { display: flex; align-items: center; gap: var(--s3);
              padding: var(--s3) var(--s4); border-bottom: var(--hair) solid var(--line); }
.sheet-what { margin: 0; padding: var(--s3) var(--s4); font-size: var(--t-small);
              color: var(--ink-2); }
/* One line when shut, so a sheet somebody opened to type a token starts at the
   fields. The paragraph inside keeps its own padding, so opening it looks the same
   as the sheets that show it outright. */
.sheet-what-more > summary { padding: var(--s2) var(--s4); font-size: var(--t-small);
                             color: var(--ink-3); cursor: pointer; }
.sheet-what-more > summary:hover { color: var(--ink); }
.sheet-what-more[open] > summary { color: var(--ink-2); }
.sheet-note { margin: 0 var(--s4) var(--s3); padding: var(--s2) var(--s3);
              border-radius: var(--r); background: var(--warn-bg);
              box-shadow: inset 0 0 0 1px var(--warn-line);
              font-size: var(--t-small); color: var(--warn); }
.sheet-fields { display: grid; gap: var(--s3); padding: 0 var(--s4) var(--s3); }
.sheet-fields .field { display: flex; flex-direction: column; gap: 4px; }
.sheet-fields .field > span { font-size: var(--t-small); color: var(--ink-3);
                              text-transform: uppercase; letter-spacing: .04em;
                              display: flex; gap: var(--s2); align-items: baseline; }
/* Not `.req`: that name is already the requirement card — a bordered, padded box —
   so a marker wearing it arrived inside a border it never asked for. */
.sheet-fields .optional { color: var(--ink-3); font-weight: 400; font-style: italic;
                          text-transform: none; letter-spacing: 0; }
.sheet-fields .hint { font-size: var(--t-small); color: var(--ink-3); }
.sheet-act { display: flex; gap: var(--s2); align-items: center;
             padding: var(--s3) var(--s4); border-top: var(--hair) solid var(--line);
             background: var(--bg); }

.sheet-uses { display: flex; gap: var(--s4); padding: 0 var(--s4) var(--s3);
              font-size: var(--t-small); color: var(--ink-2); }
.sheet-uses b { display: block; color: var(--ink); }


/* Reuse is good news only when there is some. The unconditional green said
   "done" over a panel reading "none of nine is modelled yet". */
.reuse.is-some { background: var(--good-bg); box-shadow: inset 0 0 0 1px var(--good-line); }


/* One line, cut with an ellipsis. Shared, because four page stylesheets load on
   every page and a helper redefined in one of them is a helper redefined for all
   of them. */
.trunc { display: block; overflow: hidden; text-overflow: ellipsis;
         white-space: nowrap; }


/* The role cannot create one. Dimmed and inert rather than removed, so the rail
   does not change shape when somebody switches person. */
.navadd.is-off { opacity: .35; pointer-events: none; }

/* A file shown as an appendix. The summary is a line, not a panel head, so a
   closed disclosure does not look like an empty card. */
.rawfile > summary { padding: var(--s2) 0; font-size: var(--t-small);
                     color: var(--ink-3); cursor: pointer; }
.rawfile > summary:hover { color: var(--ink); }

