/* ============================================================================================
   STRIP BOARD — the shell's own visual world.

   Written from DESIGN.md, which was written from the built canvas (design/*.dc.html). It is a
   deliberate REPLACEMENT of the MudBlazor language, not an extension of it, and it lives in its
   own sheet for exactly that reason: during step 1 of design/PLAN.md the new shell carries the
   thirty-four existing MudBlazor pages inside it, so the two worlds have to be separable —
   `omgqa.css` still skins what is under the rack, `strip.css` owns the rack itself.

   Two rules from DESIGN.md are enforced here rather than remembered:
     · #6E7671 and #4A5350 are RULES, never ink. The ink floor is --ink-3 (#8A9490, 4.9:1).
     · Text is never dimmed with `opacity` — a colour from the table, or nothing.
   ============================================================================================ */

/* THE TOKENS ARE NOT THE BOARD, and separating them is what lets a dialog be part of this world.
   `MudDialogProvider` mounts at MainLayout line 22, OUTSIDE the `.om-board` of line 36 — so every
   custom property declared only on the board is out of scope by the time a dialog renders, and a
   dialog written in this language would have come out unstyled. Measured, not assumed: that is
   where the provider is.
   One declaration, two entry points — no duplicated palette to drift. The board keeps its own
   LAYOUT below; only the vocabulary is shared. */
.om-board,
.om-dialog {
  --board:          #16191A;
  --board-raised:   #1C201F;
  --board-recessed: #101312;
  --rule:           #2A302E;
  --rule-strong:    #4A5350;

  --ink:            #E9E3D4;
  --ink-2:          #A8B0AC;
  --ink-3:          #8A9490;

  --paper:          #E9E3D4;
  --paper-dim:      #D6D0C1;
  --ink-paper:      #23262A;
  --ink-paper-2:    #4A4E54;

  --yours:          #D2622E;
  --yours-ink-paper:#A3491B;
  --agent:          #4E7C6B;
  --agent-ink:      #7FB3A0;
  --planned:        #6E7F91;
  --broken:         #B23A2E;
  --broken-ink:     #E8705C;
  --warn:           #C4834A;

  --narrow: 'Archivo Narrow', 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --prose:  'Archivo', 'Helvetica Neue', Arial, sans-serif;
}

.om-board {
  display: flex;
  min-height: 100vh;
  background: var(--board);
  color: var(--ink);
  font-family: var(--prose);
}

/* ── THE RAIL ────────────────────────────────────────────────────────────────────────────────
   Five destinations. Every one is a real <a href>, so focus, Enter, middle-click and "open in a
   new tab" all work without a line of JavaScript — the keyboard ratchet's whole point. */

.om-rail {
  width: 92px;
  flex-shrink: 0;
  background: var(--board-recessed);
  border-right: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 18px 0 12px 0;
  gap: 4px;
  position: sticky;
  top: 0;
  height: 100vh;
}

.om-rail-mark {
  width: 30px;
  height: 30px;
  border: 1.5px solid var(--yours);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 22px;
  flex-shrink: 0;
}

.om-rail-mark span { width: 12px; height: 2px; background: var(--yours); display: block; }

.om-rail-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 11px 0;
  width: 100%;
  text-decoration: none;
  color: var(--ink-3);
  border-left: 2px solid transparent;
  position: relative;
}

.om-rail-item svg { stroke: currentColor; }

.om-rail-item:hover { color: var(--ink-2); background: rgba(255,255,255,.03); }

/* Selection is a FILL CHANGE plus the destination's own left edge — the one place a left edge is
   allowed, because here it is the rail's own selected state and not a side-tab accent on a card. */
.om-rail-item--on { color: var(--ink); background: var(--board-raised); border-left-color: var(--yours); }

.om-rail-label {
  font-family: var(--narrow);
  font-size: 11px;
  letter-spacing: .07em;
  text-transform: uppercase;
  text-align: center;
  line-height: 1.15;
}

/* THE ONLY NUMBER ON THE SCREEN. DESIGN.md: exactly one surface carries a count in a filled
   orange plate, and it is the Bay. Scarcity is what makes it mean something. */
.om-rail-count {
  position: absolute;
  top: 4px;
  right: 12px;
  background: var(--yours);
  color: var(--board);
  font-family: var(--narrow);
  font-weight: 700;
  font-size: 12px;
  line-height: 1;
  padding: 3px 5px;
  font-variant-numeric: tabular-nums;
}

.om-rail-spacer { flex-grow: 1; }

.om-rail-user {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  width: 100%;
}

.om-rail-out {
  background: none;
  border: 1px solid var(--rule);
  color: var(--ink-3);
  padding: 5px 7px;
  cursor: pointer;
  display: flex;
  align-items: center;
}

.om-rail-out:hover { color: var(--ink); border-color: var(--rule-strong); }

/* ── THE RACK: header and lens strip ─────────────────────────────────────────────────────── */

.om-rack-col { flex-grow: 1; display: flex; flex-direction: column; min-width: 0; }

.om-rack {
  min-height: 62px;
  border-bottom: 1px solid var(--rule);
  display: flex;
  align-items: center;
  padding: 0 28px;
  gap: 16px;
  flex-shrink: 0;
  flex-wrap: wrap;
}

.om-rack-title { font-family: var(--narrow); font-size: 19px; font-weight: 600; letter-spacing: .02em; }
.om-rack-sep { width: 1px; height: 22px; background: var(--rule); flex-shrink: 0; }

.om-rack-meta {
  font-family: var(--narrow);
  font-size: 13px;
  color: var(--ink-3);
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: center;
  gap: 8px;
}

.om-rack-grow { flex-grow: 1; }

/* The project selector is a native <select>: one control, real keyboard semantics, and the
   operating system's own picker on a phone. The scope label that replaces it on a workspace
   screen is the same size and in the same place, so the header never has two scope controls. */
.om-rack-project {
  font-family: var(--narrow);
  font-size: 14px;
  letter-spacing: .03em;
  background: var(--board-raised);
  color: var(--ink);
  border: 1px solid var(--rule);
  padding: 7px 11px;
  max-width: 260px;
}

.om-rack-scope {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--narrow);
  font-size: 13px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.om-rack-search {
  display: flex;
  align-items: center;
  gap: 9px;
  border: 1px solid var(--rule);
  background: none;
  color: var(--ink-3);
  padding: 7px 13px;
  min-width: 232px;
  font-family: var(--prose);
  font-size: 13px;
  cursor: pointer;
  text-align: left;
}

.om-rack-search:hover { border-color: var(--rule-strong); color: var(--ink-2); }

/* A destination is not a state: every chip wears the same graphite plate. The first build painted
   one destination in the alarm red and so promised a permanent emergency. */
.om-lenses {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 28px;
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
  flex-shrink: 0;
}

.om-lens {
  font-family: var(--narrow);
  font-size: 13px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-decoration: none;
  border: 1px solid var(--rule);
  padding: 5px 11px;
}

.om-lens:hover { color: var(--ink); border-color: var(--rule-strong); }
.om-lens--on { background: var(--rule-strong); color: var(--ink); border-color: var(--rule-strong); }

.om-board .om-page { padding: 22px 28px 28px 28px; max-width: none; }

/* ── BAYS AND STRIPS ─────────────────────────────────────────────────────────────────────── */

.om-bay { display: flex; flex-direction: column; }
.om-bay-head { display: flex; align-items: flex-end; gap: 14px; padding-bottom: 9px; }

.om-count {
  /* MEASURED at 390px: "24" wrapped to a "2" over a "4". The plate is a flex child beside a heading and a
     paragraph, and a flex item's min-width is its CONTENT by default — but the paragraph beside it is longer,
     so the plate was the one squeezed. The one number the whole product is built around cannot be the thing
     that wraps. */
  flex-shrink: 0;
  white-space: nowrap;
  background: var(--yours);
  color: var(--board);
  font-family: var(--narrow);
  font-weight: 700;
  font-size: 34px;
  line-height: 1;
  padding: 7px 13px 6px 13px;
  font-variant-numeric: tabular-nums;
}

/* Empty is never rendered as absence: an OUTLINED zero, then a sentence saying why it is empty. */
.om-count--zero { background: none; border: 2px solid var(--rule-strong); color: var(--ink-3); padding: 5px 13px 4px 13px; }

.om-bay-title {
  font-family: var(--narrow);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
}

.om-bay-sub { font-size: 13px; color: var(--ink-3); }
.om-bay-rule { height: 2px; background: var(--yours); }
.om-bay-rule--ambient { height: 1px; background: var(--rule); }

.om-strips { display: flex; flex-direction: column; gap: 4px; padding-top: 8px; }

.om-strip {
  display: flex;
  align-items: stretch;
  background: var(--paper);
  min-height: 52px;
  text-decoration: none;
  transition: transform 140ms cubic-bezier(.2,.8,.3,1);
}

.om-strip:hover { transform: translateX(3px); }
.om-strip:focus-visible { outline: 2px solid var(--yours); outline-offset: 2px; }

.om-strip-code {
  width: 62px;
  flex-shrink: 0;
  background: var(--yours);
  color: var(--board);
  font-family: var(--narrow);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .1em;
  display: flex;
  align-items: center;
  justify-content: center;
}

.om-strip-code--broken { background: var(--broken); color: #F4EFE3; }

/* THE OTHER TWO CUTS OF THE CODE BLOCK. The default block is `--yours`, which means "only a human can
   move this" — right for the Bay, wrong for a rack whose rows are a MACHINE'S standing orders. A fleet
   strip's code says which phase the server is handing out, so it takes the agent colour when work is
   flowing and the warn colour for a pause, which is degraded and not failed.

   THE AGENT FILL IS THE PLATE, NOT THE CLASS COLOUR, and getting that wrong once is why it is spelled out.
   DESIGN.md's table gives the Agent class two values — `#4E7C6B` and a plate `#35594B` — and a filled block
   is a plate. The first version used the class colour and MEASURED 4.14:1 against the paper ink, under the
   4.5:1 floor this sheet binds, while its own comment claimed it cleared it. The plate measures 6.82:1.

   Ink is the near-white paper tone in both and not `--board`: the dark ground on the agent plate is 3.1:1,
   and this block is 13px uppercase — a size WCAG does not call large, so it needs the body cut and not the
   large-text one. Measured: agent 6.82:1, warn 5.13:1, and the pre-existing broken block 5.17:1. */
.om-strip-code--agent { background: #35594B; color: #F4EFE3; }
.om-strip-code--warn { background: #8A5A22; color: #F4EFE3; }

.om-strip-body {
  flex-grow: 1;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 18px;
  min-width: 0;
}

.om-strip-key {
  font-family: var(--narrow);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-paper-2);
  letter-spacing: .05em;
  font-variant-numeric: tabular-nums;
  width: 118px;
  flex-shrink: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.om-strip-text {
  font-size: 15px;
  color: var(--ink-paper);
  font-weight: 500;
  flex-grow: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.om-strip-since {
  font-family: var(--narrow);
  font-size: 13px;
  color: var(--ink-paper-2);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.om-strip-act {
  display: flex;
  align-items: center;
  padding-right: 12px;
  flex-shrink: 0;
}

.om-strip-act span {
  background: var(--ink-paper);
  color: var(--paper);
  font-family: var(--narrow);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 9px 17px;
}

.om-strip-more {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 4px 0 4px;
  font-family: var(--narrow);
  font-size: 13px;
  color: var(--ink-3);
  letter-spacing: .04em;
}

.om-strip-more span:last-child { flex-grow: 1; height: 1px; background: var(--rule); }

/* ── THE EMPTY BAY ───────────────────────────────────────────────────────────────────────── */

.om-empty { padding: 34px 0 0 0; display: flex; gap: 44px; align-items: flex-start; flex-wrap: wrap; }
.om-empty-said { display: flex; flex-direction: column; gap: 15px; max-width: 540px; }
.om-empty-lede { margin: 0; font-size: 28px; font-weight: 500; line-height: 1.32; color: var(--ink); text-wrap: pretty; }
.om-empty-body { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink-2); text-wrap: pretty; }

.om-empty-proof {
  display: flex;
  align-items: center;
  gap: 11px;
  padding-top: 4px;
  font-family: var(--narrow);
  font-size: 14px;
  letter-spacing: .05em;
  color: var(--ink-3);
}

.om-empty-split { width: 1px; align-self: stretch; background: var(--rule); }
.om-empty-side { display: flex; flex-direction: column; gap: 13px; flex-grow: 1; min-width: 220px; }

.om-side-title {
  font-family: var(--narrow);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ── IN FLIGHT ───────────────────────────────────────────────────────────────────────────── */

.om-flight { display: flex; align-items: center; gap: 13px; padding: 9px 0; }

.om-flight-n {
  font-family: var(--narrow);
  font-size: 27px;
  font-weight: 600;
  color: var(--ink);
  width: 42px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.om-flight-body { flex-grow: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.om-flight-label {
  font-family: var(--narrow);
  font-size: 14px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
}

.om-flight-bar { height: 3px; background: var(--rule); }
.om-flight-bar span { display: block; height: 3px; background: var(--rule-strong); }
.om-flight-bar--agent span { background: var(--agent); }
.om-flight-bar--planned span { background: var(--planned); }

/* ── A SOURCE THAT DID NOT ANSWER ────────────────────────────────────────────────────────────
   The whole reason the endpoint reports per source. Warn, not alarm: the bay is degraded, not
   broken, and saying so is the difference between an empty screen and an honest one. */
.om-degraded {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  border: 1px solid var(--warn);
  padding: 11px 14px;
  margin-bottom: 16px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
}

.om-degraded strong { color: var(--warn); font-weight: 600; }

/* ── THE RECORD ──────────────────────────────────────────────────────────────────────────────
   A decision request's question is a markdown document (p50 1 532 characters, p90 3 270), and the
   screen where it gets answered prints it rather than dumping it: one column, each section's heading
   set in the GUTTER beside its text instead of stacked above it. That is what makes a 2 000-character
   record scannable — the labels form a spine you read down, and the prose keeps one measure. */

.om-record { background: var(--paper); color: var(--ink-paper); max-width: 980px; }

.om-record-head { display: flex; align-items: stretch; border-bottom: 2px solid var(--yours); }
.om-record-head .om-strip-code { width: 62px; }
.om-record-title { padding: 14px 20px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }

.om-record-meta {
  display: flex;
  align-items: baseline;
  gap: 12px;
  font-family: var(--narrow);
  font-size: 13px;
  color: var(--ink-paper-2);
  letter-spacing: .05em;
  flex-wrap: wrap;
}

.om-record-type { text-transform: uppercase; font-weight: 700; letter-spacing: .09em; }

.om-record-ask {
  margin: 0;
  font-size: 21px;
  font-weight: 500;
  line-height: 1.35;
  color: var(--ink-paper);
  text-wrap: pretty;
}

.om-record-body { padding: 4px 0 20px 0; }
.om-record-body--plain .om-record-gutter { display: none; }

.om-record-section { display: flex; gap: 20px; padding: 16px 20px 0 20px; align-items: flex-start; }

.om-record-gutter {
  width: 132px;
  flex-shrink: 0;
  font-family: var(--narrow);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--ink-paper-2);
  padding-top: 4px;
}

/* The record's prose lives on paper, so it needs its own ink — `.om-md` is styled for the dark board
   everywhere else in the app, and inheriting that here puts near-white text on cream. */
.om-record .om-md { flex-grow: 1; min-width: 0; font-size: 15px; line-height: 1.62; color: var(--ink-paper); }
.om-record .om-md p { margin: 0 0 10px 0; text-wrap: pretty; }
.om-record .om-md p:last-child { margin-bottom: 0; }
.om-record .om-md strong { font-weight: 600; }
.om-record .om-md a { color: var(--yours-ink-paper); text-decoration: underline; }
.om-record .om-md ul, .om-record .om-md ol { margin: 0 0 10px 0; padding-left: 20px; }
.om-record .om-md li { margin-bottom: 4px; }
.om-record .om-md code {
  font-family: var(--narrow);
  font-size: 14px;
  background: var(--paper-dim);
  padding: 1px 5px;
  color: var(--ink-paper-2);
  word-break: break-word;
}
.om-record .om-md pre {
  background: var(--paper-dim);
  padding: 11px 13px;
  overflow-x: auto;
  margin: 0 0 10px 0;
}
.om-record .om-md pre code { background: none; padding: 0; }

/* ── THE ANSWER ──────────────────────────────────────────────────────────────────────────────
   The only interactive thing on the screen. Selection is a fill change plus an inset outline —
   never a left border, which reads as the side-tab accent this world refuses (DESIGN.md). */

.om-answer { max-width: 980px; padding-top: 26px; display: flex; flex-direction: column; gap: 0; }
.om-answer .om-bay-rule { margin-top: 8px; }
.om-options { display: flex; flex-direction: column; gap: 3px; padding-top: 12px; }

.om-option {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  background: var(--paper-dim);
  padding: 13px 16px;
  cursor: pointer;
  outline: 2px solid transparent;
  outline-offset: -2px;
}

.om-option:hover { background: var(--paper); }
.om-option--on { background: var(--paper); outline-color: var(--yours); }
.om-option input { margin: 3px 0 0 0; accent-color: var(--yours); width: 16px; height: 16px; flex-shrink: 0; }
.om-option-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.om-option-label { font-size: 15px; line-height: 1.45; color: var(--ink-paper); text-wrap: pretty; }

.om-option-note {
  font-family: var(--narrow);
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--yours-ink-paper);
  font-weight: 700;
}

.om-custom {
  width: 100%;
  box-sizing: border-box;
  background: var(--paper);
  color: var(--ink-paper);
  border: 1px solid var(--rule-strong);
  padding: 11px 13px;
  font-family: var(--prose);
  font-size: 15px;
  line-height: 1.5;
  resize: vertical;
}

.om-consequences { display: flex; flex-direction: column; gap: 9px; padding-top: 22px; }
.om-consequence { display: flex; align-items: flex-start; gap: 11px; font-size: 14px; color: var(--ink-2); line-height: 1.5; }
.om-consequence svg { flex-shrink: 0; margin-top: 3px; }

.om-commit { display: flex; align-items: center; gap: 16px; padding-top: 24px; flex-wrap: wrap; }

.om-commit-btn {
  background: var(--yours);
  color: var(--board);
  border: none;
  font-family: var(--narrow);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: 14px 26px;
  cursor: pointer;
  min-height: 46px;
}

.om-commit-btn:hover:not(:disabled) { background: #E8763F; }
/* Disabled is a RULE colour on the board, not dimmed text: `opacity` would push the label under the
   contrast floor, which is the failure DESIGN.md bans by name. */
.om-commit-btn:disabled { background: var(--rule-strong); color: var(--board); cursor: not-allowed; }
.om-commit-note { font-size: 13px; color: var(--ink-3); }
.om-answered { padding-top: 16px; display: flex; flex-direction: column; gap: 8px; }

/* ── THE QUEUE ───────────────────────────────────────────────────────────────────────────────
   A queued agent write is not a strip: a strip is something you pick up, and these are rows in a
   group you decide about together. The signing panel sits to the right and carries what the
   approval binds (design/PLAN.md §2.2 and §8). */

.om-queue-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; padding-bottom: 9px; }
.om-queue-head h1 { margin: 0; }
.om-queue-head p { margin: 4px 0 0 0; max-width: 620px; }

.om-queue { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(300px, 1fr); gap: 26px; padding-top: 18px; align-items: start; }
.om-queue-groups { display: flex; flex-direction: column; gap: 22px; min-width: 0; }
.om-group { display: flex; flex-direction: column; gap: 3px; }

.om-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 5px;
  flex-wrap: wrap;
}

.om-group-tool {
  font-family: var(--narrow);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink);
}

.om-group-who { font-size: 13px; color: var(--ink-3); margin-left: 11px; }

.om-group-all {
  background: none;
  border: 1px solid var(--rule-strong);
  color: var(--ink);
  font-family: var(--narrow);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 7px 15px;
  cursor: pointer;
}

.om-group-all:hover:not(:disabled) { background: var(--rule-strong); }
.om-group-all:disabled { color: var(--ink-3); cursor: not-allowed; }

.om-qrow {
  display: flex;
  align-items: center;
  gap: 15px;
  width: 100%;
  text-align: left;
  background: var(--paper-dim);
  border: none;
  padding: 12px 15px;
  min-height: 44px;
  cursor: pointer;
  outline: 2px solid transparent;
  outline-offset: -2px;
  font-family: var(--prose);
}

.om-qrow:hover { background: var(--paper); }
/* Selection is a fill change plus an inset outline. Never a left border: that is the side-tab accent
   this world refuses, and the mechanical detector flags it. */
.om-qrow--on { background: var(--paper); outline-color: var(--yours); }

.om-qrow-key {
  font-family: var(--narrow);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-paper-2);
  letter-spacing: .05em;
  font-variant-numeric: tabular-nums;
  width: 132px;
  flex-shrink: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.om-qrow-text { font-size: 14px; color: var(--ink-paper); flex-grow: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.om-qrow-since { font-family: var(--narrow); font-size: 13px; color: var(--ink-paper-2); font-variant-numeric: tabular-nums; flex-shrink: 0; }
.om-queue-tune { align-self: flex-start; }

/* ── THE SIGNING PANEL ─────────────────────────────────────────────────────────────────────── */

.om-sign { position: sticky; top: 18px; display: flex; flex-direction: column; gap: 0; min-width: 0; }
.om-sign .om-bay-rule { margin: 8px 0 14px 0; }
.om-sign-what { padding-bottom: 12px; }
.om-sign-tool { font-family: var(--narrow); font-size: 15px; font-weight: 600; letter-spacing: .05em; color: var(--ink); word-break: break-word; }

.om-sign-args { display: grid; grid-template-columns: minmax(90px, auto) 1fr; gap: 7px 16px; margin: 0; }
.om-sign-args dt {
  font-family: var(--narrow);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.om-sign-args dd { margin: 0; font-size: 14px; color: var(--ink); word-break: break-word; }
.om-sign-bind { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--rule); }
.om-sign-prefix { font-size: 12px; color: var(--ink-3); margin-left: 7px; }
.om-sign-hash { font-family: var(--narrow); letter-spacing: .12em; font-variant-numeric: tabular-nums; }

.om-sign-block { padding-top: 14px; }
.om-sign-blocklabel {
  font-family: var(--narrow);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.om-sign-block p { margin: 5px 0 0 0; font-size: 14px; line-height: 1.55; color: var(--ink-2); text-wrap: pretty; }
.om-sign-raw { word-break: break-all; font-family: var(--narrow); }

.om-sign-act { display: flex; gap: 10px; padding-top: 22px; flex-wrap: wrap; }

.om-reject-btn {
  background: none;
  border: 1px solid var(--rule-strong);
  color: var(--ink);
  font-family: var(--narrow);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: 13px 22px;
  cursor: pointer;
  min-height: 46px;
}

.om-reject-btn:hover:not(:disabled) { border-color: var(--broken); color: var(--broken-ink); }
.om-reject-btn:disabled { color: var(--ink-3); cursor: not-allowed; }

/* ── DECIDED ─────────────────────────────────────────────────────────────────────────────────── */

.om-history { padding-top: 30px; display: flex; flex-direction: column; }
.om-history .om-bay-rule--ambient { margin: 8px 0 9px 0; }
.om-hrow { display: flex; align-items: center; gap: 15px; padding: 9px 2px; border-bottom: 1px solid var(--rule); flex-wrap: wrap; }
.om-hrow .om-qrow-key, .om-hrow .om-qrow-text { color: var(--ink-2); }
.om-hrow .om-qrow-since { color: var(--ink-3); }

.om-hrow-verdict {
  font-family: var(--narrow);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  width: 74px;
  flex-shrink: 0;
}

.om-hrow--ok { color: var(--agent-ink); }
.om-hrow--no { color: var(--ink-3); }

.om-hrow-run {
  background: none;
  border: 1px solid var(--rule);
  color: var(--ink-3);
  font-family: var(--narrow);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 5px 11px;
  cursor: pointer;
}

.om-hrow-run:hover:not(:disabled) { color: var(--ink); border-color: var(--rule-strong); }

/* ── THE DELIBERATE GESTURE ──────────────────────────────────────────────────────────────────
   §2.6. The fill is a CSS transition so the progress a thumb sees is the browser's own and does
   not wait on a round trip; the server still decides whether the hold was long enough. */

.om-deliberate { display: flex; flex-direction: column; gap: 12px; }
.om-deliberate-what { display: flex; flex-direction: column; gap: 7px; max-width: 520px; }
.om-deliberate-act { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

.om-signing-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 5px; }
.om-signing-list li { font-size: 14px; line-height: 1.5; color: var(--ink-2); text-wrap: pretty; }

/* An absent contract is a WARNING, not a neutral fact. It reads in the degraded colour so the eye lands
   on it before the button — which is the whole reason the gesture buys a moment. */
.om-signing-warn { color: var(--warn) !important; font-weight: 600; }

.om-hold { position: relative; overflow: hidden; min-width: 220px; }
.om-hold-label { position: relative; z-index: 1; }

.om-hold-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: #E8763F;
  transition: width 0ms linear;
}

/* 800ms is OmDeliberate.HoldMs. The two are separate declarations of one number by necessity — one is a
   stylesheet and the other is C# — so the guard reads both and fails if they drift: a fill that finishes
   before the server's threshold promises a commit that does not happen. */
.om-hold--filling .om-hold-fill { width: 100%; transition: width 800ms linear; }

/* ── THE STORY'S RECORD ──────────────────────────────────────────────────────────────────────
   Same head as the strip the Bay put it on, so opening a strip lands on the same object rather than
   on a differently-shaped screen about it. The code block takes the class's colour — the four classes
   are the whole system (DESIGN.md) and a story's class is what its state means. */

/* THE HEAD IS A STRIP, SO IT IS ON PAPER. Measured on the deployed record: both heads rendered the
   title in `ink-paper` (#23262A) directly on the board (#16191A) — 1.1:1, effectively invisible. The
   ink was right and the GROUND was missing: `.om-record-head` is paper only because the decision
   record wraps it in `.om-record`, and these two heads stand on their own. A strip is a piece of paper
   in a holder; a head with no paper under it is not a strip, it is dark text on a dark board. */
.om-story-head, .om-finding-head { background: var(--paper); }

.om-story-head--yours .om-strip-code { background: var(--yours); color: var(--board); }
.om-story-head--agent .om-strip-code { background: var(--agent); color: #F4EFE3; }
.om-story-head--planned .om-strip-code { background: var(--planned); color: var(--board); }
.om-story-head--settled .om-strip-code { background: var(--rule-strong); color: var(--paper); }
.om-story-head .om-record-ask { font-size: 21px; }

/* ── THE CRITERIA ────────────────────────────────────────────────────────────────────────────
   The rollup answers the question the gate asks — is everything in Pass? — and the sentence under
   it says the count was earned criterion by criterion. A failing criterion is not a smaller pass:
   it takes the broken colour, because "5 of 6 in Pass" hides "1 failing" and they are not the same
   situation. */

.om-criteria { margin: 18px 0 22px 0; }
.om-criteria-head { display: flex; align-items: flex-end; gap: 14px; padding-bottom: 9px; }

.om-criteria-count {
  font-family: var(--narrow);
  font-weight: 700;
  font-size: 21px;
  line-height: 1;
  padding: 8px 12px;
  border: 2px solid var(--rule-strong);
  color: var(--ink-3);
  white-space: nowrap;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/* Filled only when EVERY criterion passes. "All but one" is not nearly all — it is not done, and a
   filled plate would say it was. */
.om-criteria-count--all { background: var(--agent); border-color: var(--agent); color: #F4EFE3; }
.om-criteria-head p { margin: 3px 0 0 0; }

.om-criteria-list { list-style: none; margin: 9px 0 0 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }

.om-criterion {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  background: var(--paper-dim);
  padding: 11px 15px;
}

.om-criterion > div { flex-grow: 1; min-width: 0; }

.om-criterion-mark {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  margin-top: 3px;
  border: 2px solid var(--ink-paper-2);
}

.om-criterion--pass { background: var(--paper); }
.om-criterion--pass .om-criterion-mark { background: var(--agent); border-color: var(--agent); }
.om-criterion--fail .om-criterion-mark { background: var(--broken); border-color: var(--broken); }

.om-criterion-text { font-size: 15px; line-height: 1.45; color: var(--ink-paper); display: block; text-wrap: pretty; }
.om-criterion-evidence { font-size: 13px; color: var(--ink-paper-2); display: block; margin-top: 3px; }

.om-criterion-status {
  font-family: var(--narrow);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-paper-2);
  flex-shrink: 0;
}

.om-criterion--fail .om-criterion-status { color: var(--broken); }

/* ── THE GATES ───────────────────────────────────────────────────────────────────────────────
   Everything above the last row happened. The row at the bottom is the gate still shut, and it is
   shut because the server refuses that transition to every service account. Saying so is what makes
   the wait legible: the story is not stuck, it is waiting for a person. */

.om-gates { margin: 22px 0; }
.om-gates .om-bay-rule--ambient { margin: 8px 0 0 0; }
.om-gate-list { list-style: none; margin: 0; padding: 0; }

.om-gate {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 9px 0;
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
}

.om-gate-state {
  font-family: var(--narrow);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink);
  width: 190px;
  flex-shrink: 0;
}

.om-gate--agent .om-gate-state { color: var(--agent-ink); }
.om-gate--yours .om-gate-state { color: var(--yours); }

.om-gate-when {
  font-family: var(--narrow);
  font-size: 13px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.om-gate-who { font-size: 13px; color: var(--ink-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* The gate that is still shut: outlined rather than filled, because nothing has happened here yet. */
.om-gate--open { border-bottom: none; padding-top: 13px; }
.om-gate--open .om-gate-state { color: var(--yours); }
.om-gate-when--open { color: var(--ink-2); font-family: var(--prose); letter-spacing: 0; }

/* ── THE EVIDENCE ────────────────────────────────────────────────────────────────────────────
   What backs the story. Read far more often than edited, so it is set as a record rather than as
   a form with rows in it — the linking controls stay, they just stop being the shape of the block. */

.om-evidence { margin: 22px 0; }
.om-evidence .om-bay-rule--ambient { margin: 8px 0 0 0; }
.om-evidence > p { margin: 8px 0 0 0; }
.om-evidence-rows { display: flex; flex-direction: column; gap: 3px; padding-top: 10px; }

.om-evidence-row {
  display: flex;
  align-items: baseline;
  gap: 15px;
  background: var(--paper-dim);
  padding: 10px 15px;
  flex-wrap: wrap;
}

/* A SHA, a repository and a branch are unbreakable tokens: they wrap between characters or they push
   the row off the screen, and neither is acceptable, so they get the narrow face and no wrapping. */
.om-evidence-ref {
  font-family: var(--narrow);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--ink-paper);
  width: 96px;
  flex-shrink: 0;
  white-space: nowrap;
}

.om-evidence-ref a { color: var(--yours-ink-paper); text-decoration: underline; }
.om-evidence-what { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.om-evidence-title { font-size: 14px; color: var(--ink-paper); text-wrap: pretty; }

.om-evidence-where {
  font-family: var(--narrow);
  font-size: 13px;
  color: var(--ink-paper-2);
  letter-spacing: .03em;
  word-break: break-word;
}

/* ── THE FINDING'S HEAD AND PATH ─────────────────────────────────────────────────────────────
   Same anatomy as the story's record, with one deliberate difference: a story's block carries its
   state (who is holding it) and a finding's carries its severity (what a reader triages on). The
   tones still mean what DESIGN.md says — a critical finding IS something broken. */

.om-finding-head--broken .om-strip-code { background: var(--broken); color: #F4EFE3; }
.om-finding-head--warn .om-strip-code { background: var(--warn); color: var(--board); }
.om-finding-head--planned .om-strip-code { background: var(--planned); color: var(--board); }
.om-finding-head .om-record-ask { font-size: 21px; }

.om-path { margin: 18px 0 22px 0; }
.om-path .om-bay-rule--ambient { margin: 8px 0 0 0; }
.om-path > p { margin: 10px 0 0 0; }
.om-path-list { list-style: none; margin: 10px 0 0 0; padding: 0; }

.om-path-stop {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 9px 0;
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
}

/* The mark is the only thing that says where you are, so it carries the difference: hollow ahead,
   filled behind, and filled in the Yours colour for the stop you are standing on. */
.om-path-mark {
  width: 11px;
  height: 11px;
  flex-shrink: 0;
  border: 2px solid var(--rule-strong);
  align-self: center;
}

.om-path-stop--passed .om-path-mark { background: var(--rule-strong); }
.om-path-stop--here .om-path-mark { background: var(--yours); border-color: var(--yours); }

.om-path-name {
  font-family: var(--narrow);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-3);
  width: 172px;
  flex-shrink: 0;
}

.om-path-stop--here .om-path-name { color: var(--ink); }
.om-path-stop--passed .om-path-name { color: var(--ink-2); }
.om-path-note { font-size: 13px; color: var(--ink-3); min-width: 0; }
.om-path-stop--here .om-path-note { color: var(--yours); font-family: var(--narrow); letter-spacing: .06em; text-transform: uppercase; }

/* ── THE TRIAGE GATE ─────────────────────────────────────────────────────────────────────────
   §2.5: triage is the FIRST human gate, and it was buried in a "move to…" menu beside eleven other
   destinations. A finding sitting untriaged is not waiting for a state change, it is waiting for a
   VERDICT — so each verdict is a row that says what it does, not an item in a dropdown. */

.om-triage { margin: 0 0 18px 0; padding: 14px; border: 1px solid var(--yours); }
.om-triage > p { margin: 6px 0 0 0; }
.om-triage-acts { display: flex; flex-direction: column; gap: 3px; padding-top: 12px; }

.om-triage-act {
  display: flex;
  flex-direction: column;
  gap: 3px;
  text-align: left;
  width: 100%;
  background: var(--board-raised);
  border: 1px solid var(--rule);
  padding: 11px 14px;
  cursor: pointer;
  font-family: var(--prose);
  min-height: 46px;
}

.om-triage-act:hover:not(:disabled) { border-color: var(--rule-strong); background: var(--board); }
.om-triage-act:disabled { cursor: not-allowed; }

/* Accepting is the verdict that puts work INTO the machine's hands, so it wears the Yours colour on
   its verb — it is the one of the three that hands something over. */
.om-triage-act--accept .om-triage-verb { color: var(--yours); }

.om-triage-verb {
  font-family: var(--narrow);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink);
}

.om-triage-what { font-size: 13px; line-height: 1.45; color: var(--ink-3); text-wrap: pretty; }
.om-triage-act:disabled .om-triage-verb, .om-triage-act:disabled .om-triage-what { color: var(--ink-3); }

/* The line under a holding column: it is what tells the reader the column is the top of a pile
   rather than the whole of one. Quiet on purpose — it is a fact about the column, not an action
   competing with the cards above it. */
.om-board-more {
  display: block;
  padding: 10px 4px 2px 4px;
  font-family: var(--narrow);
  font-size: 13px;
  letter-spacing: .04em;
  color: var(--ink-3);
  text-decoration: none;
}

.om-board-more:hover { color: var(--ink); text-decoration: underline; }

/* ── WHAT A STORY IS TIED TO ─────────────────────────────────────────────────────────────────
   One block, not several. Reading the decision that gated a story three panels away from the
   stories it relates to is what made "what is this attached to?" a question you had to assemble. */

.om-ties { margin: 22px 0; }
.om-ties .om-bay-rule--ambient { margin: 8px 0 0 0; }
.om-ties > p { margin: 10px 0 0 0; }
.om-tie-link { text-decoration: none; }
.om-tie-link .om-evidence-title { color: var(--yours-ink-paper); text-decoration: underline; }

/* ── PHONE ───────────────────────────────────────────────────────────────────────────────────
   Not a narrowed desktop. The rail becomes a bottom row of five, bays stack, a strip becomes a
   two-part block with its action below, and every action is a ≥46px target. */

@media (max-width: 899px) {
  .om-board { flex-direction: column; }

  .om-rail {
    order: 2;
    position: fixed;
    /* `top: auto` is not tidiness. The desktop rule pins `top: 0` for the sticky column, and an element with
       BOTH top and bottom set stretches to the full viewport — which is exactly what happened: the bottom row
       of five rendered as a full-height bar down the left of a 390px phone, with the page behind it. */
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    width: auto;
    height: auto;
    flex-direction: row;
    justify-content: space-around;
    align-items: stretch;
    padding: 0;
    gap: 0;
    border-right: none;
    border-top: 1px solid var(--rule);
    z-index: 30;
  }

  .om-rail-mark, .om-rail-spacer, .om-rail-user { display: none; }

  .om-rail-item {
    flex: 1;
    padding: 9px 2px;
    min-height: 56px;
    justify-content: center;
    border-left: none;
    border-top: 2px solid transparent;
  }

  .om-rail-item--on { border-left-color: transparent; border-top-color: var(--yours); }
  .om-rail-count { top: 2px; right: 50%; transform: translateX(22px); }

  /* Clears the fixed bottom row (56px) with room for the board's own bottom padding. At 60px the last
     strip's ACCEPT button sat UNDER the rail — visible, and untappable, which is the worst of both. */
  .om-rack-col { padding-bottom: 76px; }
  .om-rack, .om-lenses, .om-board .om-page { padding-left: 16px; padding-right: 16px; }

  /* ⌘K is a desktop-keyboard affordance; on a phone the control keeps its icon and its accessible name and
     stops demanding horizontal space it does not have. */
  .om-rack-search { min-width: 0; }
  .om-rack-search-label { display: none; }

  .om-record-section { flex-direction: column; gap: 6px; padding: 14px 15px 0 15px; }
  .om-record-gutter { width: auto; padding-top: 0; }
  .om-record-title { padding: 12px 15px; }
  .om-record-ask { font-size: 18px; }
  .om-commit-btn { width: 100%; }

  /* The signing panel goes UNDER the queue on a phone rather than into a 150px column beside it. */
  .om-queue { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .om-sign { position: static; }
  .om-qrow { flex-wrap: wrap; gap: 4px 12px; }
  .om-qrow-key { width: auto; }
  .om-qrow-text { flex-basis: 100%; white-space: normal; overflow: visible; }
  .om-sign-act button { flex-grow: 1; }

  .om-gate-state { width: 100%; }
  .om-evidence-ref { width: auto; }
  .om-path-name { width: 100%; }

  .om-criteria-head { flex-wrap: wrap; gap: 10px; }
  .om-criterion { flex-wrap: wrap; }
  .om-criterion-status { width: 100%; padding-left: 26px; }

  .om-strip { flex-wrap: wrap; }
  .om-strip-body { flex-wrap: wrap; gap: 4px 12px; padding: 11px 14px; }
  .om-strip-text { white-space: normal; overflow: visible; flex-basis: 100%; }
  .om-strip-act { width: 100%; padding: 0 14px 12px 14px; }
  .om-strip-act span { flex-grow: 1; text-align: center; padding: 15px 17px; min-height: 46px; box-sizing: border-box; }
  .om-empty-split { display: none; }
  .om-empty-lede { font-size: 23px; }
}

/* ── THE KNOWLEDGE BASE (design/PLAN.md §17) ─────────────────────────────────────────────────
   Built from the pieces already here: the record's paper for an article, the strip's rhythm for
   the index. Nothing new is invented — a KB that looked like a different product would read as
   somebody else's documentation bolted on, which is exactly what it replaces. */

.om-kb { padding: 1.5rem 2rem 4rem; max-width: 1000px; }

.om-kb-head { display: flex; align-items: flex-start; gap: 1.5rem; justify-content: space-between; }

.om-kb-export {
  font-family: var(--narrow); text-transform: uppercase; letter-spacing: 0.09em; font-size: 0.76rem;
  color: var(--ink-2); border: 1px solid var(--rule-strong); border-radius: 2px;
  padding: 0.5rem 0.9rem; text-decoration: none; white-space: nowrap;
}
.om-kb-export:hover { color: var(--ink); border-color: var(--ink-3); }

.om-kb-search {
  width: 100%; margin: 1.5rem 0 2rem;
  background: var(--board-recessed); border: 1px solid var(--rule); border-radius: 2px;
  color: var(--ink); font-family: var(--prose); font-size: 0.95rem; padding: 0.7rem 0.9rem;
}
.om-kb-search:focus { outline: 2px solid var(--yours); outline-offset: 1px; }

.om-kb-list { display: grid; }

.om-kb-row {
  display: grid; grid-template-columns: 9rem 1fr auto; gap: 1.25rem; align-items: baseline;
  padding: 1rem 0; border-top: 1px solid var(--rule); text-decoration: none; color: inherit;
}
.om-kb-row:last-child { border-bottom: 1px solid var(--rule); }
.om-kb-row:hover { background: var(--board-raised); }

/* THE SCOPE IS ON EVERY ROW, and that is the one thing this screen adds over the API. A fact true
   only in dev and a fact true everywhere read identically in prose; confusing them is the expensive
   mistake the KB exists to stop. */
.om-kb-scope {
  font-family: var(--narrow); text-transform: uppercase; letter-spacing: 0.09em; font-size: 0.72rem;
  color: var(--agent-ink); white-space: nowrap;
}

.om-kb-row-main { display: grid; gap: 0.2rem; }
.om-kb-row-title { font-weight: 600; }
.om-kb-row-summary { color: var(--ink-2); font-size: 0.92rem; }
.om-kb-updated { font-family: var(--narrow); font-size: 0.74rem; color: var(--ink-3); white-space: nowrap; }

.om-kb-tag {
  font-family: var(--narrow); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.7rem;
  color: var(--ink-paper-2); border: 1px solid var(--paper-dim); border-radius: 2px; padding: 0.1rem 0.4rem;
}

.om-kb-article { margin: 1.5rem 2rem 4rem; }
.om-kb-scope-line { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; margin: 0 0 1rem; }
.om-kb-article .om-kb-scope { color: var(--yours-ink-paper); }
.om-kb-article .om-kb-updated { color: var(--ink-paper-2); }
.om-kb-summary { font-size: 1.05rem; color: var(--ink-paper); margin: 0 0 1.5rem; }

/* Superseded knowledge is KEPT so nobody re-derives it — and marked, so nobody acts on it. */
.om-kb-superseded {
  border-left: 3px solid var(--broken); background: var(--paper-dim);
  padding: 0.75rem 1rem; margin: 0 0 1.5rem; color: var(--ink-paper);
}

@media (max-width: 46rem) {
  .om-kb-row { grid-template-columns: 1fr; gap: 0.35rem; }
  .om-kb-updated { display: none; }
}

.om-kb-back {
  font-family: var(--narrow); text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.74rem;
  color: var(--yours-ink-paper); text-decoration: none; display: inline-block; margin-bottom: 1rem;
}
.om-kb-back:hover { text-decoration: underline; }

/* ════════════════════════════════════════════════════════════════════════════════════════════
   COMMON SURFACES — the vocabulary the ported pages need (2026-09-10).

   Everything above this line is BESPOKE: `om-criteria`, `om-gate`, `om-kb-row` exist for one screen
   each, because each was designed against its own data. That was right for six screens and it is the
   reason the other thirty-four never moved: there was no way to write an ordinary table, form or
   button in this language, so "port a page" meant "design a page", and a page with no measured defect
   never justified that.

   The operator's call on 2026-09-10 — *"lleva todo, absolutamente todo a la nueva experiencia"* —
   settles the question §14 of design/PLAN.md had deferred as "trabajo de estética con el producto
   funcionando". This section is what makes it mechanical instead of thirty-four design problems.

   IT INHERITS THE TWO RULES, and they are the reason this is not just a MudBlazor re-skin:
     · #6E7671 and #4A5350 are RULES, never ink. The ink floor is --ink-3 (#8A9490, 4.9:1).
     · Text is NEVER dimmed with `opacity`. A colour from the table, or nothing. A disabled control
       changes its BACKGROUND to a rule colour and keeps its label legible — see .om-commit-btn's
       own note, which is where that was learned.
   ════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── TYPE ────────────────────────────────────────────────────────────────────────────────────
   Four sizes, because 463 <MudText> calls across the app used Typo values that mapped to about four
   real jobs. Narrow + uppercase is the heading voice everywhere else in this sheet; prose is prose. */

.om-h1 { font-family: var(--narrow); font-size: 28px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; margin: 0; }
.om-h2 { font-family: var(--narrow); font-size: 21px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; margin: 0; }
.om-h3 { font-family: var(--narrow); font-size: 15px; font-weight: 700; letter-spacing: .10em; text-transform: uppercase; margin: 0; color: var(--ink-2); }
.om-lede { font-size: 14px; color: var(--ink-2); margin: 0; max-width: 78ch; }
.om-note { font-size: 13px; color: var(--ink-3); margin: 0; }
.om-num  { font-variant-numeric: tabular-nums; }

/* A page's masthead: title, one sentence, a hairline. Same shape as .om-bay-head so a ported page
   and the Bay do not read as two products. */
.om-page-head { display: flex; align-items: flex-end; gap: 14px; padding-bottom: 9px; flex-wrap: wrap; }
.om-page-head .om-rack-grow { flex: 1 1 240px; }
.om-page { display: flex; flex-direction: column; gap: 18px; }

/* ── PANEL ───────────────────────────────────────────────────────────────────────────────────
   The replacement for 126 <MudPaper>. Raised board, one rule, no shadow: this world separates
   surfaces with a line, never with a blur. */

.om-panel { background: var(--board-raised); border: 1px solid var(--rule); padding: 16px 18px; }
.om-panel--flush { padding: 0; }
.om-panel-head { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
.om-panel-body { padding: 16px 18px; }
.om-panel-foot { padding: 12px 16px; border-top: 1px solid var(--rule); display: flex; gap: 10px; align-items: center; }

/* ── TABLE ───────────────────────────────────────────────────────────────────────────────────
   19 <MudTable>, 102 <MudTh>, 102 <MudTd>. Rows are RULED, not striped: a zebra needs a second
   background, and a second background is a second ink floor to defend.

   `om-table-wrap` is not optional. Every wide table in this app must scroll inside its own box —
   the page body must never scroll sideways, which is the failure that made the Decisions inbox
   unreadable on a phone (commit 896c2e28). */

.om-table-wrap { overflow-x: auto; }
.om-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.om-table th {
  font-family: var(--narrow); font-size: 12px; font-weight: 700; letter-spacing: .10em;
  text-transform: uppercase; color: var(--ink-2); text-align: left;
  padding: 9px 12px; border-bottom: 1px solid var(--rule-strong); white-space: nowrap;
}
.om-table td { padding: 11px 12px; border-bottom: 1px solid var(--rule); vertical-align: top; }
.om-table tbody tr:last-child td { border-bottom: none; }
.om-table tbody tr:hover td { background: var(--board-recessed); }
.om-table td.om-num, .om-table th.om-num { text-align: right; font-variant-numeric: tabular-nums; }
/* A row that is a link: the whole row, not a word inside it. */
.om-table tbody tr.om-row-link { cursor: pointer; }
.om-table tbody tr.om-row-link:hover td { background: var(--board-recessed); }

/* ── BUTTON ──────────────────────────────────────────────────────────────────────────────────
   182 <MudButton>. Three weights and nothing else: the act, the alternative, the destructive one.
   Disabled changes the BACKGROUND, never the opacity — see the rules note at the top. */

.om-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--narrow); font-size: 14px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; padding: 10px 18px; min-height: 40px;
  border: 1px solid var(--rule-strong); background: none; color: var(--ink);
  cursor: pointer; text-decoration: none; transition: background 120ms, border-color 120ms;
}
.om-btn:hover:not(:disabled) { border-color: var(--ink-3); background: var(--board-raised); }
.om-btn:disabled { background: var(--rule); border-color: var(--rule); color: var(--ink-3); cursor: not-allowed; }

.om-btn--act { background: var(--yours); border-color: var(--yours); color: var(--board); }
.om-btn--act:hover:not(:disabled) { background: #E8763F; border-color: #E8763F; }
.om-btn--act:disabled { background: var(--rule-strong); border-color: var(--rule-strong); color: var(--board); }

.om-btn--danger { border-color: var(--broken); color: var(--broken-ink); }
.om-btn--danger:hover:not(:disabled) { background: var(--broken); border-color: var(--broken); color: var(--board); }

.om-btn--small { min-height: 32px; padding: 6px 12px; font-size: 12px; }

.om-iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border: 1px solid var(--rule); background: none;
  color: var(--ink-2); cursor: pointer;
}
.om-iconbtn:hover:not(:disabled) { border-color: var(--ink-3); color: var(--ink); }
.om-iconbtn:disabled { background: var(--rule); color: var(--ink-3); cursor: not-allowed; }

.om-acts { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* ── TAG ─────────────────────────────────────────────────────────────────────────────────────
   110 <MudChip>. A tag states a fact; it is never a button. The four meanings this product has —
   yours, an agent's, planned, broken — already have colours, and a tag borrows them rather than
   inventing a palette of severities nobody defined. */

.om-tag {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--narrow); font-size: 11px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; padding: 3px 8px; border: 1px solid var(--rule-strong); color: var(--ink-2);
  white-space: nowrap;
}
.om-tag--yours   { border-color: var(--yours);   color: var(--yours); }
.om-tag--agent   { border-color: var(--agent);   color: var(--agent-ink); }
.om-tag--planned { border-color: var(--planned); color: var(--planned); }
.om-tag--broken  { border-color: var(--broken);  color: var(--broken-ink); }
.om-tag--warn    { border-color: var(--warn);    color: var(--warn); }
/* On paper (inside a strip) the same tag needs the paper inks, or it disappears. */
.om-strip .om-tag, .om-record .om-tag { border-color: var(--ink-paper-2); color: var(--ink-paper-2); }
/* And a NOTE on paper needs the same correction, for the same reason: --ink-3 is the board's ink
   floor, measured against the board. On --paper it is a smudge. MarkdownView's "nothing written yet"
   line is the one that lands on both grounds. */
.om-strip .om-note, .om-record .om-note { color: var(--ink-paper-2); }

/* ── FIELDS ──────────────────────────────────────────────────────────────────────────────────
   63 text fields, 59 selects, 11 checkboxes, 6 switches. The label sits ABOVE the control and stays
   there: a floating label that becomes the value's neighbour is the pattern that leaves a filled
   field with no visible name. */

.om-field { display: flex; flex-direction: column; gap: 6px; }
.om-label { font-family: var(--narrow); font-size: 12px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ink-2); }
.om-input, .om-select, .om-textarea {
  background: var(--board-recessed); border: 1px solid var(--rule-strong); color: var(--ink);
  font-family: var(--prose); font-size: 14px; padding: 9px 11px; min-height: 40px; width: 100%;
}
.om-textarea { min-height: 96px; resize: vertical; }
.om-input:focus, .om-select:focus, .om-textarea:focus { outline: 2px solid var(--yours); outline-offset: -2px; }
.om-input::placeholder, .om-textarea::placeholder { color: var(--ink-3); }
.om-input:disabled, .om-select:disabled, .om-textarea:disabled { background: var(--rule); color: var(--ink-3); }
.om-field-note { font-size: 12px; color: var(--ink-3); }
.om-field-note--broken { color: var(--broken-ink); }
.om-check { display: inline-flex; align-items: center; gap: 9px; font-size: 14px; cursor: pointer; }
.om-check input { width: 16px; height: 16px; accent-color: var(--yours); }
.om-form { display: flex; flex-direction: column; gap: 16px; max-width: 640px; }
.om-form-row { display: flex; gap: 14px; flex-wrap: wrap; }
.om-form-row > .om-field { flex: 1 1 220px; }

/* ── ALERT ───────────────────────────────────────────────────────────────────────────────────
   44 <MudAlert>. A left rule carries the meaning, the way .om-strip-key does — a filled banner
   would be the only saturated block on a page and would read as more urgent than the work itself. */

.om-alert { border: 1px solid var(--rule); border-left: 3px solid var(--rule-strong); padding: 11px 14px; font-size: 14px; color: var(--ink-2); background: var(--board-raised); }
.om-alert--warn   { border-left-color: var(--warn);   color: var(--ink); }
.om-alert--broken { border-left-color: var(--broken); color: var(--ink); }
.om-alert--ok     { border-left-color: var(--agent);  color: var(--ink); }
.om-alert--yours  { border-left-color: var(--yours);  color: var(--ink); }
.om-alert-title { font-family: var(--narrow); font-weight: 700; letter-spacing: .07em; text-transform: uppercase; font-size: 13px; display: block; margin-bottom: 3px; }

/* ── TABS ────────────────────────────────────────────────────────────────────────────────────
   Same shape as .om-lens, which the rack already uses — one language for "pick a view", not two.
   The old purple <MudTabs> bar was the single loudest thing on any screen it appeared on. */

.om-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--rule); flex-wrap: wrap; }
.om-tab {
  font-family: var(--narrow); font-size: 13px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-3); background: none; border: none;
  border-bottom: 2px solid transparent; padding: 10px 16px; cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; gap: 8px;
}
.om-tab:hover { color: var(--ink-2); }
.om-tab--on { color: var(--ink); border-bottom-color: var(--yours); }
.om-tab-count { font-family: var(--narrow); font-size: 11px; font-weight: 700; background: var(--yours); color: var(--board); padding: 1px 6px; font-variant-numeric: tabular-nums; }
.om-tab--on .om-tab-count, .om-tab .om-tab-count { }

/* ── PROGRESS AND WAITING ────────────────────────────────────────────────────────────────────
   34 spinners and 26 bars. A spinner says "this is loading"; it must not be the only thing on the
   screen with no words, so `om-wait` carries a sentence. */

.om-wait { display: flex; align-items: center; gap: 12px; color: var(--ink-3); font-size: 13px; padding: 22px 0; }
.om-spin { width: 16px; height: 16px; border: 2px solid var(--rule-strong); border-top-color: var(--yours); border-radius: 50%; animation: om-spin 700ms linear infinite; flex-shrink: 0; }
@keyframes om-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .om-spin { animation-duration: 2400ms; } }
.om-bar { height: 4px; background: var(--rule); overflow: hidden; }
.om-bar-fill { height: 100%; background: var(--yours); }
.om-bar-fill--agent { background: var(--agent); }
.om-bar-fill--broken { background: var(--broken); }
/* Approaching a cap is not the same fact as being at it, and the billing bars are the place that
   distinction has to survive: `--warn` is already the colour this world uses for "not yet, but soon". */
.om-bar-fill--warn { background: var(--warn); }

/* ── LINK, RULE, GRID ────────────────────────────────────────────────────────────────────── */

.om-link { color: var(--yours); text-decoration: none; border-bottom: 1px solid transparent; }
.om-link:hover { border-bottom-color: var(--yours); }
.om-rule { height: 1px; background: var(--rule); border: none; margin: 0; }
.om-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.om-stack { display: flex; flex-direction: column; gap: 12px; }

/* A figure on a panel: the number first, its name under it. Same plate idiom as .om-count. */
.om-stat { display: flex; flex-direction: column; gap: 3px; }
.om-stat-n { font-family: var(--narrow); font-size: 30px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.om-stat-label { font-family: var(--narrow); font-size: 12px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ink-3); }

/* ── NOTHING HERE ────────────────────────────────────────────────────────────────────────────
   Absence is never a blank region. The Bay's empty state already says this; the generic one keeps
   the same promise for the ported pages: a sentence saying WHY it is empty. */

.om-nothing { border: 1px dashed var(--rule-strong); padding: 26px 20px; text-align: center; color: var(--ink-3); font-size: 14px; }
.om-nothing-title { font-family: var(--narrow); font-size: 16px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ink-2); display: block; margin-bottom: 5px; }

/* ── PHONE ───────────────────────────────────────────────────────────────────────────────── */

@media (max-width: 720px) {
  .om-h1 { font-size: 22px; }
  .om-panel, .om-panel-body { padding: 13px 14px; }
  .om-table th, .om-table td { padding: 9px 9px; }
  .om-form-row { flex-direction: column; }
  .om-tab { padding: 9px 11px; font-size: 12px; }

  /* THE 46px FLOOR, WHICH DESIGN.md BINDS AND THIS SHEET WAS NOT MEETING (measured 2026-09-12).
     `.om-btn` sat at 40, `.om-btn--small` at 32 and `.om-pbtn` at 38 — and on the fleet page the two
     SMALLEST targets on a card were Save and Discard, the ones that commit. The bespoke strip components
     hit 46 already; the ported "common surfaces" vocabulary never did, and every screen built from it
     inherited the gap.
     Raised on the PHONE only, deliberately: 32px is a reasonable dense size for a mouse, and growing
     every small button on a desk monitor to fingertip size would be paying a phone's cost everywhere.
     The floor DESIGN.md states is about touch, so it is applied where there is touch. */
  .om-btn, .om-btn--small, .om-strip-act .om-pbtn { min-height: 46px; }
  .om-input, .om-select, .om-textarea { min-height: 46px; }
  .om-check input { width: 22px; height: 22px; }
  .om-check { min-height: 46px; }
}

/* ── A STRIP THAT IS NOT A LINK ───────────────────────────────────────────────────────────────
   The Bay's strips all go somewhere, so `.om-strip` is an <a> and slides on hover. A strip that
   carries its own buttons — accept / needs-changes — is not a destination, and sliding under the
   cursor while you aim at a button is the one animation that can cost a misclick. It also grows a
   second line: the reason it cannot be accepted, or the evidence behind it. */

.om-strip--static { flex-wrap: wrap; cursor: default; }
.om-strip--static:hover { transform: none; }
.om-strip--static .om-strip-body { padding-top: 9px; padding-bottom: 9px; }

/* Buttons ON PAPER. The board's own .om-btn is drawn for a dark ground and vanishes here, so the
   paper variants restate the three weights against --paper. Same rule as everywhere: disabled
   changes the ground, never the opacity. */
.om-strip-act .om-pbtn {
  font-family: var(--narrow); font-size: 13px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; padding: 9px 15px; min-height: 38px; cursor: pointer;
  border: 1px solid var(--ink-paper-2); background: none; color: var(--ink-paper);
}
.om-strip-act .om-pbtn:hover:not(:disabled) { background: var(--paper-dim); }
.om-strip-act .om-pbtn--act { background: var(--ink-paper); border-color: var(--ink-paper); color: var(--paper); }
.om-strip-act .om-pbtn--act:hover:not(:disabled) { background: #34383E; border-color: #34383E; }
.om-strip-act .om-pbtn:disabled { background: var(--paper-dim); border-color: var(--paper-dim); color: #6F6A5E; cursor: not-allowed; }
.om-strip-act { gap: 8px; }

/* The second line. It hangs under the body, indented past the code block so the eye keeps the
   column, and it carries the ONE thing that decides whether this row can be acted on. */
.om-strip-note {
  flex-basis: 100%;
  padding: 0 18px 10px 80px;
  font-size: 13px;
  color: var(--ink-paper-2);
  display: flex; align-items: baseline; gap: 7px;
}
.om-strip-note--broken { color: #8E2A20; font-weight: 500; }
.om-strip-note--warn { color: #8A5A22; }
.om-strip-note-mark { font-family: var(--narrow); font-weight: 700; letter-spacing: .08em; text-transform: uppercase; font-size: 11px; flex-shrink: 0; }

/* A link inside a static strip's body. The strip itself is not an anchor here, so the key and the
   title carry the navigation — underlined only on hover, because two permanently underlined runs of
   text on every row is noise, and the row already reads as one object. */
.om-strip-tie { color: inherit; text-decoration: none; }
.om-strip-tie:hover { text-decoration: underline; }
.om-strip-tie:focus-visible { outline: 2px solid var(--yours); outline-offset: 2px; }

/* The key slot is 118px by default, which fits a story key. A branch name needs more and has no
   space to break at, so a strip can widen its own slot without a second class per caller. */
.om-strip-key { width: var(--om-strip-key-w, 118px); }

/* THREE COUNTS THAT MUST BE COMPARABLE DOWN A COLUMN, so they are tabular and fixed-width. Passed,
   failed, skipped — in that order everywhere. A zero failure count is INK, not a colour: painting it
   green would make "no failures" as loud as "twelve failures", and the eye should only stop on the
   second one. */
.om-runcount {
  font-family: var(--narrow); font-size: 13px; font-weight: 700;
  font-variant-numeric: tabular-nums; min-width: 40px; text-align: right; flex-shrink: 0;
  color: var(--ink-paper-2);
}
.om-runcount--pass { color: #2F5D4C; }
.om-runcount--fail { color: #8E2A20; }
.om-runcount--zero { color: var(--ink-paper-2); }
.om-runcount--skip { color: #6F6A5E; }

/* The generic empty state's parts (OmEmptyState). `.om-nothing` and `.om-nothing-title` are above;
   these finish it. The wrapper name matters: this component used to be `.om-empty`, which the Bay
   also defines in this same sheet — loaded second, the Bay's flex row was silently winning on all
   fourteen pages that use it. */
.om-nothing-mark {
  display: inline-block; font-family: var(--narrow); font-size: 11px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; padding: 3px 9px; margin-bottom: 10px;
  border: 1px solid var(--rule-strong); color: var(--ink-3);
}
/* Denied and failed are NOT empty, and the mark is where that stops being a footnote. */
.om-nothing-mark--denied { border-color: var(--warn); color: var(--warn); }
.om-nothing-mark--failed { border-color: var(--broken); color: var(--broken-ink); }
.om-nothing-body { font-size: 14px; color: var(--ink-2); margin: 0 auto 10px auto; max-width: 62ch; }
.om-nothing .om-note { margin-bottom: 12px; }
.om-nothing--compact { border: none; padding: 8px 0; text-align: left; }
.om-nothing--compact .om-nothing-body { font-size: 13px; margin: 0; }

/* ── DIALOG ──────────────────────────────────────────────────────────────────────────────────
   252 MudBlazor components live in Components/Ui/, thirteen of them dialogs, and NOBODY WAS
   COUNTING THEM. The port's own tally said "28 of 37 pages", which is true and measures the wrong
   unit: a ported page that opens an unported dialog shows both languages at once, one on top of
   the other, which is more jarring than an unported page is on its own.

   THE MECHANISM STAYS MUDBLAZOR ON PURPOSE. `DialogService.ShowAsync<T>` and
   `IMudDialogInstance` are the plumbing behind every call site in the app; focus trapping, the
   Escape key, the backdrop click and the result round-trip all already work. What was wrong was
   never the plumbing — it was the LANGUAGE, which is exactly what strip.css says it replaces at
   the top of this file. Re-skinning costs one wrapper per dialog; replacing the mechanism costs
   every call site and buys a worse Escape key.

   The selectors below reach into `.mud-*` deliberately, and that is the one place in this sheet
   that does. It is the seam between the two worlds, and a seam is better named than hidden. */

.om-dialog.mud-dialog {
  background: var(--board-raised);
  border: 1px solid var(--rule-strong);
  border-radius: 0;                 /* nothing in this world is rounded */
  box-shadow: 0 24px 64px rgba(0,0,0,.55);
  color: var(--ink);
  font-family: var(--prose);
}

/* THE TITLE IS A STRIP, like every other heading in this language: narrow, upper, tracked, and
   sitting on a rule rather than floating. */
.om-dialog .om-dialog-title {
  font-family: var(--narrow); font-size: 13px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-2);
  padding: 16px 24px; border-bottom: 1px solid var(--rule);
  display: flex; align-items: center; gap: 12px;
}

.om-dialog .om-dialog-body {
  padding: 24px; font-size: 15px; line-height: 1.6; color: var(--ink);
}

/* A dialog is a decision, so its actions are a rule away from what they act on, and the act is
   on the right where the eye finishes. */
.om-dialog .om-dialog-actions {
  display: flex; justify-content: flex-end; align-items: center; gap: 12px;
  padding: 16px 24px; border-top: 1px solid var(--rule);
}
.om-dialog .om-dialog-actions .om-dialog-aside { margin-right: auto; color: var(--ink-3); font-size: 13px; }

/* MudBlazor renders its own paddings and typography inside the dialog; these neutralise them so
   the classes above are the only thing deciding, rather than fighting a specificity war in
   whichever file loads second. */
.om-dialog .mud-dialog-title,
.om-dialog .mud-dialog-content,
.om-dialog .mud-dialog-actions { padding: 0; margin: 0; border: 0; background: none; }

/* THE SCRIM IS A DIMMER, NOT A TINT. The board behind stays legible as context — a dialog in this
   world is a strip laid ON the board, not a different screen. */
.mud-overlay .mud-overlay-scrim { background: rgba(6, 8, 8, .72); }

/* A form inside a dialog: label above field, both in the vocabulary the ported pages use. */
.om-dialog .om-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.om-dialog .om-field:last-child { margin-bottom: 0; }
.om-dialog .om-field-label {
  font-family: var(--narrow); font-size: 12px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3);
}
.om-dialog .om-field-input,
.om-dialog .om-field-input:focus {
  font-family: var(--prose); font-size: 15px; color: var(--ink);
  background: var(--board-recessed); border: 1px solid var(--rule-strong);
  padding: 10px 12px; outline: none;
}
.om-dialog .om-field-input:focus { border-color: var(--yours); }
.om-dialog .om-field-hint { font-size: 13px; color: var(--ink-3); }

/* ── TRAZABILIDAD (§16.2) ────────────────────────────────────────────────────────────────────
   Tres números que hay que poder comparar de un vistazo, así que son tabulares y de ancho fijo.
   El color NO es decoración: un hueco es un criterio en Pass que no apunta a nada —la peor clase,
   porque parece cerrado— y un huérfano es un documento del que nadie descendió un criterio, que es
   la dirección que nadie chequea. Cero de cada uno se ve neutro a propósito: lo normal no grita. */
/* Misma caja que `om-ties` —son vecinas y se leen juntas— pero clase propia: `om-ties` es una
   afirmación sobre su contenido que un guard verifica, no un estilo compartido. */
.om-trace { margin: 22px 0; }
.om-trace .om-bay-rule--ambient { margin: 8px 0 0 0; }
.om-trace > p { margin: 10px 0 0 0; }

.om-trace-counts { display: flex; gap: 20px; flex-wrap: wrap; margin: 10px 0 14px; }
.om-trace-count {
  font-family: var(--narrow); font-size: 13px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3);
}
.om-trace-count b {
  font-size: 17px; color: var(--ink-2); margin-right: 6px;
  font-variant-numeric: tabular-nums;
}
.om-trace-count--bad b  { color: var(--broken-ink); }
.om-trace-count--warn b { color: var(--warn); }
