/* The page carries the design language the mockup donor declares: one hairline depth and no
   shadow, an ink-and-rule ledger where a measure is a record and a gate prints its word in the
   slot a number would have filled. Every value below is a member of the donor's token system
   (market-evidence-mockup/tokens.json); fonts and the tracking step are the donor's own values
   read off its style block, because the token file captured no font or tracking tier. */
:root {
  --surface-field: #F5F7FA;
  --surface-card: #FFFFFF;
  --surface-sunken: #EDF1F5;
  --text-strong: #1C2530;
  --text-body: #44505D;
  --text-quiet: #5F6B78;
  --border-hairline: #DCE3EA;
  --border-inner: #EDF1F5;
  --accent: #1A6BAE;
  --accent-strong: #14578F;
  --accent-soft: #E8F1F8;
  --state-gate: #9A5514;
  --state-ok: #1F7A4D;
  --on-accent: #FFFFFF;
  --range-danger: #B3261E;
  --range-mid: #9A5514;
  --range-good: #1F7A4D;
  --micro: 11px;
  --small: 12px;
  --body: 14px;
  --lead: 16px;
  --section: 20px;
  --title: 26px;
  --step: 8px;
  --radius-control: 6px;
  --radius-card: 12px;
  --radius-circle: 50%;
  --bar-height: 3px;
  --bar-radius: 2px;
  --bar-width: 56px;
  --target-min: 44px;
  --label-tracking: 0.1em;
  --head-tracking: 0.08em;
  --motion-swap: 120ms;
  --font-stack: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-figure: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --surface-field: #10151B;
    --surface-card: #182029;
    --surface-sunken: #222C37;
    --text-strong: #E6EAF0;
    --text-body: #B4BECB;
    --text-quiet: #9AA5B3;
    --border-hairline: #2A3542;
    --border-inner: #222C37;
    --accent: #6FB3E8;
    --accent-strong: #6FB3E8;
    --accent-soft: #182029;
    --state-gate: #E0A870;
    --state-ok: #6FC79A;
    --on-accent: #10151B;
    --range-danger: #E88C86;
    --range-mid: #E0A870;
    --range-good: #6FC79A;
  }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

* { box-sizing: border-box; }

/* Selection is inked, so it stays legible on the page and the card in either theme. */
::selection { background: var(--accent); color: var(--on-accent); }

body {
  margin: 0;
  padding: var(--step) var(--step) calc(var(--step) * 12);
  background: var(--surface-field);
  color: var(--text-body);
  font-family: var(--font-stack);
  font-size: var(--body);
  line-height: 1.45;
}

/* Hierarchy is size plus weight plus tracking together, never size alone. */
h1 {
  font-size: var(--section);
  font-weight: 600;
  color: var(--text-strong);
  letter-spacing: -0.01em;
  line-height: 1.3;
  margin: 0 0 calc(var(--step) * 2);
}

.app { max-width: 1200px; margin: 0 auto; }
/* The page opens on the title block, so the title is the page's own heading and no band sits above
   it; at the docked width the answer card leads and the title waits for the wide band, the way the
   donor draws it. Sign-out stays reachable as one quiet line under the surfaces, never a band. */
.title { display: none; }
.signout { display: block; margin: calc(var(--step) * 2) 0 0; }

button, input, textarea { font: inherit; color: inherit; }

button {
  border: 1px solid var(--border-hairline);
  background: var(--surface-card);
  border-radius: var(--radius-control);
  padding: calc(var(--step) * 0.75) calc(var(--step) * 1.5);
  min-height: var(--target-min);
  cursor: pointer;
}
button:hover { border-color: var(--accent); color: var(--accent-strong); }
button:active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); }
button:disabled {
  border-color: var(--border-hairline);
  background: var(--surface-field);
  color: var(--text-quiet);
  cursor: progress;
}
button:focus-visible, input:focus-visible, textarea:focus-visible, summary:focus-visible, a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.link { border: none; background: none; min-height: auto; text-decoration: underline; color: var(--accent-strong); }
.link:hover, .link:active { background: none; }
.link:disabled { background: none; text-decoration: none; }

.card {
  background: var(--surface-card);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  padding: calc(var(--step) * 2);
  margin-bottom: var(--step);
}

/* A label is stamped, not whispered: uppercase, tracked and weighted so it reads as a heading
   even at metadata size. A card names its section with the wider tracking and a form names each
   field with the head step, so the two stay two levels rather than one, and the donor's own label
   tier — the one its profile block draws over a value — is the tier the fields are drawn from
   instead of the body prose the operator already reads as sentence text. */
.eyebrow, label {
  font-size: var(--micro);
  font-weight: 600;
  letter-spacing: var(--head-tracking);
  color: var(--text-quiet);
}
.eyebrow { margin: 0 0 var(--step); }

/* A figure is a record: counts, keys, signed shares and terminators all print in the record face,
   so the eye reads them as one column and a gate prints its word where a number would go. */
.figure {
  margin: 0;
  font-family: var(--font-figure);
  font-size: var(--section);
  font-weight: 600;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}

.quiet { margin: var(--step) 0 0; color: var(--text-quiet); font-size: var(--small); }

/* An empty collection renders as its own quiet line, in the donor's own quiet role, because the
   donor draws no empty state of its own and an added rule would be a value it never carried. */
.empty {
  margin: 0;
  color: var(--text-quiet);
  font-size: var(--small);
}

.error { color: var(--range-danger); }
.state { color: var(--text-quiet); font-variant-numeric: tabular-nums; }

/* The profile tab's fit mark: a filled mark when the profile was scored, a hollow one when it was
   not, so the set reads at a glance without a second label. */
.tab .badge {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 12px;
  height: 12px;
  border-radius: var(--radius-circle);
  background: var(--state-ok);
  border: 2px solid var(--surface-card);
}
.tab[data-fits="false"] .badge { background: var(--surface-card); border-color: var(--text-quiet); }

/* The answer is one card of ruled rows, each led by its own mark, so the three questions read as
   one conclusion rather than three panels. */
.answers { display: grid; gap: var(--step); }
.answer { display: grid; grid-template-columns: 28px 1fr; gap: calc(var(--step) * 1.5); align-items: start; }
.answer .icon { color: var(--accent); }
.answer > div { display: grid; gap: calc(var(--step) * 0.25); min-width: 0; }

/* The detected items ride one compact rail: many items never stack into blocks, so the answer keeps
   one height at any count and the reader swipes across rather than down. */
.detected { margin: 0 0 calc(var(--step) * 0.5); }
.hot {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--step);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}
.hot li {
  flex: 0 0 auto;
  scroll-snap-align: start;
  display: inline-flex;
  align-items: center;
  gap: var(--step);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-control);
  padding: var(--step) calc(var(--step) * 1.5);
  min-height: var(--target-min);
  background: var(--surface-card);
}
.hot .figure { font-size: var(--lead); }

/* A chip names a kind or a state in one word, inked by its own tone so the rail reads at a glance. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 0.75);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-control);
  padding: calc(var(--step) * 0.75) calc(var(--step) * 1.5);
  font-size: var(--small);
  color: var(--text-body);
}
.chip.gate { color: var(--state-gate); border-color: currentColor; }
.chip.ok { color: var(--state-ok); border-color: currentColor; }
.chip.accent { color: var(--accent-strong); border-color: currentColor; }

/* One status vocabulary: a small glyph carries the state and its words live in a bubble, so no
   whole-capitalised terminator sits on the surface. The trigger is one button, so the bubble answers
   the pointer, the keyboard and the tap, and the tone inks the glyph. */
.tip { position: relative; display: inline-flex; align-items: center; }
.tip-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* WCAG 2.5.8: the glyph keeps its drawn size and the control keeps a 24 pixel target, so the
     pointer and the thumb meet the same control the keyboard does. */
  min-width: 24px;
  min-height: 24px;
  padding: calc(var(--step) * 0.5);
  margin: calc(var(--step) * -0.5);
  border: none;
  background: none;
  cursor: pointer;
}
.tip-trigger:hover, .tip-trigger:active { background: none; border: none; }
.glyph { width: 16px; height: 16px; flex: 0 0 auto; color: var(--text-quiet); }
.tip[data-tone="gate"] .glyph { color: var(--state-gate); }
.tip[data-tone="ok"] .glyph { color: var(--state-ok); }
.bubble {
  display: none;
  /* The overlay is fixed to the viewport and placed from the trigger's measured rect: any scroll
     container between a trigger and its bubble clips a box that opens past it, and the chip rail and
     the reading box both are one. The dock sits under it, so the reading a docked width opens last
     still paints. */
  position: fixed;
  z-index: 20;
  top: var(--tip-top, 0);
  left: var(--tip-left, 0);
  width: max-content;
  max-width: min(320px, 80vw);
  text-align: left;
  white-space: normal;
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-control);
  background: var(--surface-card);
  color: var(--text-body);
  font-size: var(--small);
  font-weight: 400;
  letter-spacing: normal;
}
/* The reading scrolls in its own box, not the bordered one: the pointer is drawn below the border
   and a scroll container counts its own decoration as overflow. The padding moves in with it, so the
   negative margin a glyph's control carries stays inside the box that scrolls. */
.bubble-reading {
  display: block;
  max-height: 60vh;
  overflow: auto;
  padding: var(--step) calc(var(--step) * 1.5);
}
.bubble::after {
  content: "";
  position: absolute;
  top: 100%;
  left: var(--tip-arrow, 50%);
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: var(--border-hairline);
}
/* The side the component placed the box on turns the pointer around, so it meets the trigger whether
   the bubble opened above it or below it. */
.bubble[data-side="bottom"]::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--border-hairline);
}
/* A closed bubble is drawn by one decision the component makes, so the stylesheet rules no gesture of
   its own and the pointer, the keyboard and the caller that owns the open state cannot drift apart.
   The words stay in the trigger's accessible name and in its description either way. The rule reaches
   the open tip's own bubble, never a tip nested inside it, which opens on its own decision. */
.tip[data-open="true"] > .bubble { display: block; }

/* The evidence a figure opens is a block of its own inside the bubble, so a long reading wraps and
   scrolls rather than pushing the ledger sideways. */
.evidence { display: grid; gap: calc(var(--step) * 0.5); }
.evidence-line { display: block; }
/* The family's own verification is a disclosure that opens as a full-width row, so a reader opens the
   family's figures and the deals they were measured over, and the deals read across the ledger
   instead of inside the narrow family column. */
.family-summary {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 0.5);
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: var(--text-strong);
  cursor: pointer;
  text-align: left;
}
.family-summary .disclosure-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--text-quiet);
  transition: transform var(--motion-swap);
}
.family-summary .disclosure-mark svg { display: block; width: 16px; height: 16px; }
.family-summary[aria-expanded="true"] .disclosure-mark { transform: rotate(90deg); }
.family-summary:hover { text-decoration: underline; }
tr.family-detail > td { text-align: left; padding-top: calc(var(--step) * 2); }
tr.family-detail .family-deals { margin-top: calc(var(--step) * 1.5); }
/* The row's long read is a second-level disclosure inside the family's first-level row, so its
   summary sits one tier quieter and its body indents under the row it belongs to. */
tr.family-detail .panel > summary { font-size: var(--small); color: var(--text-quiet); }
tr.family-detail .panel .panel-body { padding-left: calc(var(--step) * 2); }
/* Above the breakpoint the deal row is one line, so the second-level control sits at the row end and
   its label aligns to that edge; below it the row stacks and the control keeps the full width. An
   open panel takes its own full-width line under the row, so its body never squeezes the title. */
tr.family-detail .deal { flex-wrap: wrap; }
@media (min-width: 900px) {
  tr.family-detail .panel { flex: 0 0 auto; }
  tr.family-detail .panel > summary { text-align: right; }
  tr.family-detail .panel[open] { flex: 1 0 100%; }
}
.family-name { color: var(--text-strong); }
.status-line { display: inline-flex; align-items: center; gap: var(--step); }

/* Evidence sits behind a stamped trigger and opens on every input a details element answers. */
.panel { margin-top: var(--step); }
.panel summary {
  cursor: pointer;
  color: var(--accent-strong);
  font-size: var(--micro);
  font-weight: 600;
  letter-spacing: var(--label-tracking);
}
.panel summary:hover { text-decoration: underline; }
/* The disclosure prints a fingerprint as one unbroken token, so the body grants the break the
   value itself cannot carry; without it the token overflows every viewport narrower than it. */
.panel-body {
  padding: var(--step) 0 0;
  color: var(--text-quiet);
  font-family: var(--font-stack);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* The market is a ledger: ruled rows, a stamped header, a measured bar beside every share and no
   rank anywhere, because a sort is only a view of the same rows. */
table.market { width: 100%; border-collapse: collapse; }
table.market caption { text-align: left; }
/* A measure holds its own column and its own gutter, so no figure can reach the figure beside it: a
   column is sized to its content, and without a gutter the two boxes touch exactly where a
   right-aligned figure ends and the next one begins. The gutter is the step this sheet spaces every
   group by, and the stacked layout below the breakpoint separates the same measures by the same
   unit, so the two layouts state one decision. The ledger's own outer edges stay flush with the
   caption above and the band they sit in, so the gutter never indents the ledger itself. */
table.market th, table.market td {
  text-align: right;
  padding: calc(var(--step) * 0.75);
  border-bottom: 1px solid var(--border-inner);
  white-space: nowrap;
}
/* The family column is the ledger's one left-aligned column, so its content never reaches its own
   right edge and a trailing gutter there buys no separation at the cost of the width a family name
   needs. It keeps the gutter only where a figure starts beside it. */
table.market th:first-child, table.market td:first-child {
  text-align: left;
  white-space: normal;
  padding-left: 0;
  padding-right: 0;
}
/* The row key leads its row. The donor draws every first-column body cell in `--text-strong` at
   both the widths it delivers — each family name, and the residual reading's own name — while it
   draws the whole header row quiet, so the ink is stated here on the body cell alone. Inked from
   the body instead, as the ledger did above the breakpoint, the key fell behind the ochre
   terminators beside it and the ochre ended up the strongest ink in the row. One statement covers
   both layouts: the stacked entry below the breakpoint inherits it rather than repeating it. */
table.market td:first-child { color: var(--text-strong); }
table.market th:last-child, table.market td:last-child { padding-right: 0; }
table.market th {
  font-size: var(--micro);
  letter-spacing: var(--head-tracking);
  color: var(--text-quiet);
  font-weight: 600;
  border-bottom-color: var(--border-hairline);
}
table.market th button {
  border: none;
  background: none;
  padding: 0;
  min-height: 0;
  color: inherit;
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: flex-end;
  text-transform: inherit;
  letter-spacing: inherit;
  font-size: inherit;
  font-weight: inherit;
}
table.market th:first-child button { justify-content: flex-start; }
table.market th button:hover { color: var(--text-strong); text-decoration: underline; }
table.market th button:active { background: none; }
table.market th[aria-sort="ascending"] button, table.market th[aria-sort="descending"] button { color: var(--text-strong); }
table.market td { font-family: var(--font-figure); font-variant-numeric: tabular-nums; }
/* The stamp that marks a fit as a prior takes its own line under the figure it marks, both hanging
   on the cell's right edge in the order the cell declares them: the column is then sized by the
   wider of the two runs instead of by their sum, which is the width the gutter beside it comes
   from. The stacked layout keeps the two runs on one line, where the stamp's own margin separates
   them. */
table.market td .mark { display: block; }
/* The header row names every measure the wide ledger draws, so a cell keeps its own word out of
   the ledger here; the stacked entry below the breakpoint is the layout that prints it. */
table.market .measure-word { display: none; }

/* The ledger reads one profile at a time, so the selector shares the market surface with the column
   it drives: one tab stop for the whole set and the selected tab marked. It wraps rather than
   scrolls sideways, because a profile id is a long slug and a rail at 320 pixels would carry the
   tab a reader is looking for out of the viewport. */
.tablist { display: flex; flex-wrap: wrap; gap: calc(var(--step) * 0.5); margin-bottom: var(--step); }
.tab {
  display: inline-grid;
  gap: calc(var(--step) * 0.25);
  min-height: var(--target-min);
  padding: calc(var(--step) * 0.5) var(--step);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-control);
  background: var(--surface-card);
  color: var(--text-quiet);
  text-align: left;
  font-size: var(--small);
  overflow-wrap: anywhere;
}
/* The tab names the profile it selects, so the name reads at the control's own tier: the record
   face is the page's signature and stays, but a key at the section tier outranks the heading it
   sits under and, on a real profile slug, stands the selector 167px tall at the gate width. */
.tab .figure { color: var(--text-strong); font-size: var(--body); }
/* The hint is the tab's second run, so the container's own gap states the space between them. A
   `.quiet` run carries a paragraph margin of its own, which would stand the two runs a step apart. */
.tab .quiet { margin-top: 0; }
.tab[aria-selected="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); }
.tab[aria-selected="true"] .figure { color: var(--accent-strong); }
.tab:hover { border-color: var(--accent); }
.tab:hover .figure { color: var(--accent-strong); }
.tab:active { border-color: var(--accent); background: var(--accent-soft); }

/* The card variant is the donor's fuller tabset: the profile's own mark carries its identity, the
   count follows the name and the fit mark reads the score, so the set answers "which of my sheets
   fits" before any figure is opened. */
.tablist[data-variant="card"] { gap: var(--step); }
.tablist[data-variant="card"] .tab {
  display: inline-flex;
  align-items: center;
  gap: var(--step);
  min-height: 56px;
  padding: 0 var(--step);
  font-size: var(--body);
  font-weight: 600;
}
.tab .avatar {
  position: relative;
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  border-radius: var(--radius-circle);
  border: 1px solid var(--border-hairline);
  background: var(--surface-sunken);
  display: grid;
  place-items: center;
  font-size: var(--micro);
  font-weight: 700;
  color: var(--text-strong);
}
.tab[data-fits="true"] .avatar { border-color: var(--state-ok); border-width: 2px; }
.tab .count { font-weight: 400; color: var(--text-quiet); }
.tab[aria-selected="true"] .count { color: var(--accent-strong); }

/* A share prints as its own word above its own bar, so the eye reads the figure and the mass it
   came from together; a measure with no share prints its terminator and leaves the track empty. */
.cell { display: grid; gap: calc(var(--step) * 0.5); justify-items: end; }
.cell .value { color: var(--text-strong); }
.cell[data-gated="true"] .value { color: var(--state-gate); }
.bar {
  display: block;
  width: var(--bar-width);
  height: var(--bar-height);
  border-radius: var(--bar-radius);
  background: var(--surface-sunken);
  overflow: hidden;
}
.bar > i { display: block; height: 100%; border-radius: var(--bar-radius); background: var(--accent); }
/* A measure drawn as a partition reads as one line of bands: the bands share one track, each is as
   long as its own share, and the line names the whole they divide in its accessible name. One
   presenter draws every band line, so a new band is a data entry and never a second drawing. */
.band-line {
  display: inline-flex;
  width: var(--bar-width);
  height: var(--bar-height);
  border-radius: var(--bar-radius);
  overflow: hidden;
  background: var(--surface-sunken);
  vertical-align: middle;
}
.band { display: block; height: 100%; min-width: 1px; }

/* A prior mark is a gate, so it prints as an ochred stamp rather than as body text. */
.mark {
  display: inline-block;
  margin-left: calc(var(--step) * 0.5);
  font-family: var(--font-figure);
  font-size: var(--micro);
  letter-spacing: var(--head-tracking);
  text-transform: uppercase;
  color: var(--state-gate);
}

/* The same gate stated as the sentence the answer and the profile card both carry: it takes the
   gate ink too, so a figure no outcome can calibrate reads as gated wherever it prints, not as
   one more quiet line. */
.prior { color: var(--state-gate); }

/* The postings behind the families: each family's own disclosure holds the deals its count was
   measured over, one row per deal, ruled as the ledger rules, and inside it the seven lines the
   channel already prints, in the channel's own order. Nothing is labelled, because the order
   carries which figure is which, so the page is read exactly as the notification is read. The line
   wraps at any width rather than letting one long posting push the page sideways, and a line the
   read carried nothing for is absent from the flow rather than drawn empty. The collection takes
   the ledger's own reset: a browser list would bullet and indent the rows the ledger rules, and the
   indent would step the deals off the ledger's left edge. */
.family-deals { list-style: none; margin: 0; padding: 0; }
/* A list item is a horizontal stack of only what a reader scans the list by: the posting's own line
   and the trigger that opens the rest. The channel's remaining lines and the provenance that
   verifies them live in the row's own disclosure, so a family's list is a stack of postings. */
.deal {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--step);
  padding: calc(var(--step) * 0.75) 0;
  border-top: 1px solid var(--border-inner);
}
.deal > .deal-line { margin: 0; flex: 1 1 auto; }
/* A posting a word can still win carries the same accent surface the family row above it carries, so one
   meaning travels the ledger: the family says how many of its postings a word can win and the postings
   themselves say which ones. A family whose artefact carried no ids marks none, because a mark the page
   cannot ground is a mark the page invented. */
.family-deals .deal[data-open-to-a-word="true"] { background: var(--accent-soft); }
.deal-line {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--step) * 0.5);
  margin: 0;
}
/* The channel prints a line as one phrase and stands its segments a single space apart, so the half
   step spaces every gap inside one line. A wrap inside a line and the boundary between two lines are
   not the same event, so they do not take the same space: a line that begins under another takes the
   whole step this sheet gives every group that starts beneath one (see .panel), which is
   the step the donor's own deal block adds to a group below the first. At one spacing for both, a
   posting line wide enough to wrap reads as two lines rather than as one line that wrapped. */
.deal-line + .deal-line { margin-top: var(--step); }
/* The channel's mark between two segments is punctuation and not a value: it takes the quiet tier, so
   it recedes under the values it separates rather than reading as the divider of a table, and it
   hangs on its own line's half step, so the phrase stays a phrase. The character it prints is the one
   the row declared and never one this sheet chooses, so a line the channel joins with a space stays
   joined. */
.deal-separator { color: var(--text-quiet); }
/* The row declares three weights and this sheet carries three text tiers, so one maps to one: the
   channel's own plain text is the page's body ink, a fact the reader is not pricing the posting by
   is the quiet tier, and a figure the channel inks first is the reader's cue. Painting the plain and
   quiet weights alike would leave the row's own vocabulary half read, and every fact of a posting as
   quiet as the metadata around it. */
.deal-value { color: var(--text-body); overflow-wrap: anywhere; }
.deal-value[data-weight="quiet"] { color: var(--text-quiet); }
/* A figure the channel inks first is a figure the reader prices the posting by, so it takes the
   page's own strong ink and the record face the market cells print their figures in. */
.deal-value[data-weight="strong"] {
  color: var(--text-strong);
  font-family: var(--font-figure);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.deal-value[data-role="title"] { color: var(--text-strong); font-weight: 600; }
/* A segment the read carried a url for is a link, so it takes the page's own link language: the
   accent ink and the underline at rest, the way the sign-out control carries them. The declaration
   is the stylesheet's decision rather than an inherited one, so a title the corpus never linked stays
   plain strong text beside a title it did. It is stated inside the row's own line, one
   element-class step above every ink the row declares, because a weight or role ink out-specifies a
   bare a.deal-value and would otherwise paint a linked segment as an unlinked one. */
.deal-line a.deal-value { color: var(--accent-strong); text-decoration: underline; }
/* The clickable surface answers the pointer the way the ledger's own sort labels do: the strongest
   ink under the hand, while the underline keeps it a link. */
a.deal-value:hover { color: var(--text-strong); }

/* The captured description is the poster's own CommonMark, so the page renders the prose the poster
   wrote: paragraphs, lists, headings, quotes, code and tables take the sheet's own rhythm, the mark
   keeps the matched family phrase, and the first and last blocks carry no margin of their own so the
   body sits flush in its disclosure. */
.capture-body { overflow-wrap: anywhere; }
.capture-body > :first-child { margin-top: 0; }
.capture-body > :last-child { margin-bottom: 0; }
.capture-body p, .capture-body ul, .capture-body ol, .capture-body blockquote,
.capture-body pre, .capture-body table, .capture-body h1, .capture-body h2, .capture-body h3 {
  margin: 0 0 calc(var(--step) * 1);
}
.capture-body h1, .capture-body h2, .capture-body h3 { color: var(--text-strong); font-size: var(--body); font-weight: 600; }
.capture-body ul, .capture-body ol { padding-left: calc(var(--step) * 2.5); }
.capture-body blockquote { padding-left: var(--step); border-left: 2px solid var(--border-hairline); color: var(--text-quiet); }
.capture-body pre { padding: var(--step); border-radius: var(--radius-control); background: var(--surface-sunken); overflow-x: auto; }
.capture-body code { font-family: var(--font-figure); font-size: var(--small); }
.capture-body table { border-collapse: collapse; }
.capture-body th, .capture-body td { border: 1px solid var(--border-hairline); padding: calc(var(--step) * 0.5); text-align: left; }
mark { background: var(--accent-soft); color: var(--accent-strong); border-radius: var(--bar-radius); }

/* The operator's sheets are rows read and edited where they are read, so the bank and its editor are
   one row shape: a saved row names its sheet and its rate, and Edit turns that same row into the
   editor. One row is the whole CRUD surface, so an update costs a row of height and never a panel. */
.rows { display: grid; gap: var(--step); margin-top: calc(var(--step) * 3); padding-top: calc(var(--step) * 2); border-top: 1px solid var(--border-hairline); }
.row { display: flex; align-items: center; gap: var(--step); flex-wrap: wrap; }
/* The editor is its own bounded form, so the row's Edit and Remove never sit beside the form's Save
   and Cancel without a boundary between the list and the form. */
.row.editing {
  display: grid;
  gap: calc(var(--step) * 1.5);
  margin-top: calc(var(--step) * 1.5);
  padding: calc(var(--step) * 2);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: var(--surface-sunken);
}
.row.editing .fields { display: grid; gap: calc(var(--step) * 1.5); }
.row.editing .actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--step);
  padding-top: calc(var(--step) * 1.5);
  border-top: 1px solid var(--border-hairline);
}
.name { font-weight: 600; color: var(--text-strong); overflow-wrap: anywhere; }
.grow { flex: 1 1 auto; }
.field { display: grid; gap: calc(var(--step) * 0.25); font-size: var(--small); color: var(--text-quiet); }
.field input, .field textarea { min-height: var(--target-min); min-width: 0; width: 100%; }
.mini, .primary {
  min-height: var(--target-min);
  padding: 0 calc(var(--step) * 2);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-control);
  background: var(--surface-card);
  color: var(--text-body);
  font-size: var(--small);
  cursor: pointer;
}
.primary { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }
.danger { color: var(--state-gate); border-color: currentColor; }
.danger:hover, .danger:active { border-color: var(--state-gate); background: var(--surface-sunken); }

/* The profiles card holds the tabset and the slot the selection scores, so the reason a figure reads
   the way it does is one region beside the control that moved it. */
.deal-slot { display: grid; gap: calc(var(--step) * 0.5); padding-top: calc(var(--step) * 2); border-top: 1px solid var(--border-inner); }
.deal-slot .head { display: flex; align-items: center; gap: var(--step); flex-wrap: wrap; }

/* A profile measure is a figure the read published, so it prints in the record face with tabular
   figures, the way a market cell and a read state do, while the prose around it stays in the
   system stack. The mass line inks strong, so the counts lead the shares they are taken over. */
.measure { font-family: var(--font-figure); font-variant-numeric: tabular-nums; }
.measure.mass { color: var(--text-strong); }

form { display: flex; flex-direction: column; gap: var(--step); align-items: flex-start; }
input, textarea {
  width: 100%;
  padding: var(--step);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-control);
  background: var(--surface-field);
}
input:hover, textarea:hover { border-color: var(--accent); }
input:disabled, textarea:disabled {
  border-color: var(--border-hairline);
  color: var(--text-quiet);
  cursor: not-allowed;
}
textarea { font-family: var(--font-figure); font-size: var(--small); resize: vertical; }

/* The login is the same ledger: the statement sits above a rule and the one credential below it. */
.login { max-width: 30rem; margin: calc(var(--step) * 8) auto; padding: calc(var(--step) * 2); }
.login form { border-top: 1px solid var(--border-hairline); padding-top: calc(var(--step) * 2); }

.foot { margin-top: calc(var(--step) * 2); border-top: 1px solid var(--border-hairline); padding-top: var(--step); }
/* A fingerprint is one unbroken token, so the footer grants the break the token cannot carry; without
   it the snapshot line scrolls the page sideways at the gate width. */
.foot p { overflow-wrap: anywhere; }

/* The reading's identity sits behind its own trigger in the read region: the label names the value in
   the page's words and the value is the artefact's own token, so the two never read as one sentence.
   The panel body already grants the token the break an unbroken hash cannot carry. */
.provenance-line { margin: 0; }
.provenance-label { font-weight: 600; }

.dock { display: none; }

/* Mobile: one surface at a time behind a dock at the bottom, inside thumb reach. Desktop: the
   three surfaces sit on one page and the dock disappears. */
@media (max-width: 899px) {
  .surface[data-active="false"] { display: none; }
  .app { padding-bottom: calc(var(--step) * 10); }
  .bar { --bar-width: 40px; }

  /* The same ledger reflows into one stacked entry per family, the shape the mockup draws at
     320px: the header row is dropped, a family prints its name on its own line and its measures
     inline beneath it, so no measure needs a column the viewport cannot hold. */
  table.market, table.market tbody, table.market tr, table.market td { display: block; }
  /* A blockified row outranks the attribute a row is withheld with, so the family's closed detail row
     would paint its postings below the breakpoint. The attribute is re-stated on the ledger's own rows,
     so a family shows its own line alone at every width the page serves. */
  table.market tr[hidden] { display: none; }
  table.market thead { display: none; }
  /* A table-caption shrink-wraps once the table itself is a block, so the legend is a block here:
     it fills the stacked ledger on one line instead of collapsing into a narrow column. */
  table.market caption { display: block; }
  table.market tbody tr { padding: var(--step) 0; border-bottom: 1px solid var(--border-inner); }
  table.market tbody tr:last-child { border-bottom: none; }
  table.market td { border-bottom: none; padding: 0; text-align: left; white-space: normal; }
  table.market td:first-child { font-weight: 600; }
  table.market td .cell { display: flex; align-items: center; gap: var(--step); }
  table.market td .mark { display: inline-block; }
  table.market td:not(:first-child) {
    display: inline-flex;
    column-gap: calc(var(--step) * 0.5);
    align-items: baseline;
    margin-right: calc(var(--step) * 1.5);
  }
  /* A measure's runs are a figure, the word that names it and the share it came from, so the
     stacked cell holds them apart by the same half step a prior mark leads with and states them on
     one line, the way the wide ledger states them, and it prints the word the header row carried
     above the breakpoint. Both facts are the cell's layout rather than any run's ink: a flex item
     blockifies and loses the space the wide layout reads between its neighbours, and a run
     blockified out of `.quiet` takes a paragraph's top margin, which would carry the share a step
     under the count it came from instead of beside it. */
  table.market td .quiet { margin-top: 0; }
  table.market .measure-word { display: inline; }

  /* The dock is the mockup's floating card: a centred pill with a fade above it, so the ledger
     scrolls under it. An edge-to-edge bar would be a language the donor never drew. */
  .dock::before {
    content: "";
    position: absolute;
    left: calc(var(--step) * -1);
    right: calc(var(--step) * -1);
    top: calc(var(--step) * -3);
    height: calc(var(--step) * 3);
    pointer-events: none;
    background: linear-gradient(to bottom, transparent, var(--surface-card));
  }
  .dock {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: var(--step);
    width: min(560px, calc(100vw - calc(var(--step) * 2)));
    z-index: 10;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    background: var(--surface-card);
    border: 1px solid var(--border-hairline);
    border-radius: var(--radius-card);
    padding: calc(var(--step) * 0.75);
    gap: calc(var(--step) * 0.5);
  }
  .dock button {
    display: grid;
    place-items: center;
    gap: calc(var(--step) * 0.25);
    min-height: 48px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    background: none;
    font-size: var(--micro);
    font-weight: 600;
    letter-spacing: var(--head-tracking);
    text-transform: uppercase;
    color: var(--text-quiet);
  }
  .dock button[aria-current="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }
  .dock svg { display: block; }
  .dock button:hover { color: var(--text-strong); }
}

/* The donor's wide band: above the docked width one grid holds the same four surfaces, the answer
   and the foot across both columns and the market beside the profiles, so the fit reads next to the
   table it belongs to. One grid, the same tokens, no second layout. The ledger shares this band, so
   every cell in it has to be able to hold its own measure: the anywhere-break that let a column
   collapse to a single character is what broke it, not the band. */
@media (min-width: 900px) {
  main { display: grid; grid-template-columns: 1fr 1fr; gap: var(--step); align-items: start; }
  main > .state, main > .surface[data-surface="answer"], main > .foot, main > .signout, main > .reading-identity { grid-column: 1 / -1; }
  .title { display: block; margin: 0 0 calc(var(--step) * 2); }
}

/* The postings the screen moved out of the counts are a region of their own, never a family row and
   never a figure: the block is ruled off the readings that end the ledger above it with the boundary
   rule the sheet draws a block below a block in and the leading it gives one (see .rows), so a reader
   meets a list the ledger did not count rather than one more line of the ledger's own notes. The
   boundary takes the hairline the ledger's own outer rule takes (see table.market th), so the region
   reads as an edge and the rows inside it as rows rather than one flat crop of rules. Their content is
   still postings, so the list reads the way the ledger rules the postings inside a family's disclosure
   (see .deal): one posting a row, ruled above, the posting's own line leading and its ground hanging
   on the row's right edge. */
.screened { margin: var(--step) 0 0; padding-top: calc(var(--step) * 2); border-top: 1px solid var(--border-hairline); }
.screened-state { margin: 0; }
/* The screen's own reading closes the list it qualifies, so it takes the block's own row grammar
   rather than hanging off the last posting it does not belong to: ruled above and led the way every
   other row of the block is. A reading that stands alone — the screen that could not read the corpus,
   or one that judged part of it and left no posting out — is the block's first row, and the rule the
   block already draws above itself is the one it is read under, so it draws none of its own. */
.screened-list + .screened-state { margin-top: var(--step); padding-top: calc(var(--step) * 1.5); border-top: 1px solid var(--border-inner); }
.screened-list { list-style: none; margin: 0; padding: 0; }
.screened-list li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--step);
  padding: calc(var(--step) * 0.75) 0;
  border-top: 1px solid var(--border-inner);
}
/* The posting's own line is the row key, so it takes the ink and the weight the ledger gives a
   posting's own title line (see .deal-value[data-role="title"]). */
.screened-title { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; color: var(--text-strong); font-weight: 600; }

/* The family's own words ride the bubble the family cell opens, so the ledger keeps its own row
   rhythm: the family row is its own height however many words the pass measured, and a reader meets
   the families before the words. Inside the bubble the word wraps rather than widening it and the bar
   is unbroken, so the longest bar the page draws can never reach the figure beside it. Its spacing
   is drawn from the sheet's step scale, its two fixed sizes stay literals the way every other fixed
   size in this sheet does, and the block adds no pseudo-element. */
.phrase-list { list-style: none; margin: calc(var(--step) * 0.75) 0 0; padding: 0; }
/* One declaration names the bar's run, so a row that draws none holds the same column open and every
   figure in the list sits in one column. */
.phrase { --phrase-bar: 72px; display: flex; align-items: baseline; gap: calc(var(--step) * 0.5); }
.phrase[data-effect="none"] { padding-right: calc(var(--phrase-bar) + var(--step) * 0.5); }
.phrase-word { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; color: var(--text-body); }
/* The figure holds a column of its own and the bar a fixed run, so both are the fixed sizes this sheet
   states as literals rather than in the step scale the spacing above is drawn from: the figure column
   at the 20 pixels a signed count or a status glyph needs, the bar at the 72 its drawn marks fill. The
   bar draws one mark per row up to the page's bound, so a move longer than the bound is shortened in
   the bar alone and the figure beside it states the move whole; a phrase the pass carried no number for
   draws the status glyph in the figure column rather than a mark. */
.phrase-figure { flex: 0 0 20px; text-align: right; font-family: var(--font-figure); color: var(--text-strong); }
.phrase-bar { flex: 0 0 var(--phrase-bar); white-space: nowrap; font-family: var(--font-figure); font-size: var(--small); color: var(--accent-strong); }
.phrase[data-effect="loses"] .phrase-bar { color: var(--state-gate); }
/* A word a change the judge itself can produce would explain draws no bar, and the row keeps the bar's
   run reserved so every figure in the list holds one column whatever the row drew beside it. Its label
   takes a line of its own rather than a share of the word's, so the narrowest width the page serves
   never has to choose between reading the word and reading the reading it is in. */
.phrase[data-variation="true"] .phrase-variation { flex: 0 0 var(--phrase-bar); display: inline-flex; align-items: center; justify-content: flex-end; }
/* The list's own rule, the variation its smallest figures belong to and the offer a family's list came
   from close the bubble under the ranking: what the list left out, the sample the judge's own
   variation was measured over, and the words the family offers against the words that were judged. All
   take the sheet's quiet ink and its small size, so the ranking keeps the reader's eye. Their carrier
   is one muted control in the bubble's corner, so the reading is never crowded by its own explanation. */
.phrase-list-note, .phrase-withheld-note, .phrase-note, .phrase-offer { margin: var(--step) 0 0; color: var(--text-quiet); font-size: var(--small); }
.phrase-notes { display: flex; justify-content: flex-end; margin-top: calc(var(--step) * 0.5); }
/* The words mark is the page's own mark geometry rather than the status glyph the bubble also
   carries, so it keeps the drawn size that keeps its two lines apart inside the row's control. */
.glyph-words { width: 20px; height: 20px; }
/* The family row carries two affordances: the disclosure mark before the name and the words mark
   after it. The words mark takes the row's own half step from the name, the same step the disclosure
   mark already keeps, so it reads as the family's second control rather than as a symbol glued to the
   name it opens. Its reading is held by the table, so it opens on the hand and never on a hover the
   way a status glyph's bubble does; the ink under the hand is the feedback that hover cannot be, and
   it is stated here alone, so no status glyph gains a gesture its owner never gave it. */
table.market td[data-column="key"] .tip { margin-left: calc(var(--step) * 0.5); }
table.market td[data-column="key"] .tip-trigger:hover .glyph,
table.market td[data-column="key"] .tip-trigger:active .glyph { color: var(--accent-strong); }
/* A family a word can still win carries the accent surface the ledger already spends on the selected
   reading, so the reader meets the rows an improvement is still possible in before opening anything;
   the count behind the mark is stated on the row for a family whose words changed nothing, and in the
   bubble for a family whose marker opens one. */
table.market tbody tr[data-open-to-a-word="true"] { background: var(--accent-soft); }
/* The ledger's prose is read in its label column, which is the one column free to wrap, so a reading's
   own share and bar take a line of their own there instead of setting the width of a measure column.
   The statement takes the sheet's quiet ink and its small size. */
table.market td[data-column="key"] .reading-note { display: block; margin-top: calc(var(--step) * 0.25); color: var(--text-quiet); font-size: var(--small); font-weight: 400; }

/* One 2px bar per item of a partition: the width is the share and the ink is the table's own ramp,
   so the profile card reads its figures as bars rather than as a sentence. The bars take the sheet's
   one bar track, so a share resolves against a definite width and is drawn as a length rather than as
   a percentage of a box the flex container leaves to its own content. */
.figure-bars { display: inline-flex; flex-direction: column; gap: 1px; width: var(--bar-width); vertical-align: middle; }
.figure-bar { display: block; height: 2px; min-width: 1px; border-radius: 1px; }

/* A trigger that carries a word stacks the mark over it, so a reader meets "More" rather than a bare
   glyph, and the control sits under the reading it explains. */
.tip[data-trigger-text] .tip-trigger { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; }
.tip-word { font-size: 0.72rem; letter-spacing: 0.02em; }

/* The block a reader opens rather than one that opens itself: collapsed by default, and never
   taller than the window it sits in, so a long exclusion list scrolls inside its own box. */
.screened > summary { cursor: pointer; }
.screened .panel-body { max-height: 40vh; overflow-y: auto; }

/* The stacked trigger is a word, not a chrome control: no border, no fill, no padding, so it reads
   as part of the block it sits under rather than as a second box beside it. */
.tip[data-trigger-text] .tip-trigger { border: 0; background: none; padding: 0; box-shadow: none; color: inherit; }
