/* OrbitraSCOUT concept: DESIGN SYSTEM V2, the rules applied to real selectors.
   Ruled in DESIGN-SYSTEM-V2.md (the numbers, with the Stripe measurement behind each).
   Reasoning in ../research/09-DESIGN-RULES-V2-WHY.md. Values in tokens.css.

   This file is linked LAST in every screen's head, after tokens.css, so a rule written
   here reaches every screen in the clickthrough on the next regenerate. Nothing in here
   is a colour literal: every value is a token from tokens.css or a dimension.

   HOW TO READ IT. Each section below is one section of DESIGN-SYSTEM-V2.md, in the same
   order. Under each, the selectors are grouped by where they come from:
     platform  = the design system's own classes, copied unchanged into screens/shared/
     shell     = written by this build (build/shell.py), shared by every blank screen
     screen    = a class one screen's own author named locally, doing the same job

   ============================ THE SELECTOR MAP ============================
   Found by grepping every generated screen in screens/ for the classes that draw each
   thing. Where two names draw the same thing, both are listed and both are ruled.

   CARDS
     platform  .o-card                       every card, pre-login and in-page
     platform  .o-prelogin .formcol .o-card  the pre-login card (sign in, the flow, set
                                             password, terms, the ID-issued screen)
     platform  .o-card.vp-card               the post-signup verification prompt's card
     shell     .scout-card                   a blank pre-login screen's card (E4)
     screen    .cf .o-card, .r11card         the desk's board cards
     screen    .frame .o-card                S1 and V2b, which already remove the border

   MODALS, DIALOGS, THE SHEET AND THE DRAWER
     platform  .o-modal                      the platform modal: E0 (.booking-modal),
                                             E5a (.vp-modal), X1 (.ie-panel), S1's share ask
     platform  .o-welcome                    the welcome pop-up (E7), inside .welcome-scrim
     shell     .scout-modal                  the four dialogs this build draws:
                                             D6, T3, P2 and the verification-call sheet (E8)
     screen    .dm-panel                     the desk's own review pop-up (D1), which
                                             becomes the review drawer
     screen    .ns-panel                     the conversation panel (D4), the surface the
                                             drawer is matched to

   OVERLAYS
     shell     .scout-dim                    behind D6, T3, P2, E8
     screen    .dm-scrim                     behind the desk's review (D1, D4)
     screen    .scrim                        behind E0, E5a and X1's own modals
     screen    .welcome-scrim                behind the welcome pop-up (E7)
     screen    .share-scrim                  behind S1's share ask
     platform  .o-drawer-scrim               the narrow-window navigation drawer, below 1390px

   INPUTS
     platform  .o-input, .o-input-wrap, .o-input--reveal, .o-check, .o-check-row
     platform  .o-field (the label-and-input group), .fields / .tq-group (groups of them)

   BUTTONS
     platform  .o-btn, .o-btn--primary, .o-btn--secondary, .o-btn--quiet, .o-btn--block
     shell     .scout-act, .scout-act--primary, .scout-act--secondary, .scout-act--quiet
     screen    .o-welcome__dismiss, .ie-act, .dead-act, .actbar__acts (act rows)

   LABELS, HEADINGS, CAPTIONS, LINKS
     platform  .o-label                      a field's label
     platform  .page-title / .o-prelogin .page-title    a card's heading
     platform  .page-sub                     the line under it
     platform  .modal-h, .modal-p            a platform modal's heading and body
     platform  .o-welcome__h                 the welcome pop-up's heading
     platform  .o-help                       a field's caption
     shell     .scout-page__h, .scout-modal__h          a blank screen's and a dialog's heading
     shell     .scout-page__p, .scout-modal__p          the line under it
     shell     .scout-page__eyebrow, .scout-lock, .scout-note   captions
     screen    .dm-panel__h                  the review's heading
     screen    .signin                       the line that becomes the card footer strip
     screen    .tq-link, .forgot             links inside a card

   CHIPS
     operable  .dm-chip (the desk's suggested asks), .scout-slot (the sheet's times)
     label     .scout-chip, .scout-pill, .cf__chip, .tag, .o-avatar  stay fully round,
               see the judgement call recorded in BUILD-REPORT.md
   ========================================================================= */


/* ============================ 1. SURFACES AND ELEVATION ============================
   Card and modal surface = the page coming forward. No border on any of them. Elevation
   is a soft teal glow plus a deep black drop, because a grey shadow is invisible on
   near-black. Radius 12 on cards and modals, 6 on controls. */

/* ---- Cards. No border, page surface, 12px radius. Hairline rows INSIDE a card stay:
   nothing here touches .r10arow, .cf__trow, .o-ledger or any other row hairline. ---- */
.o-card,
.scout-card,
.o-prelogin .formcol .o-card {
  background: var(--bg-modal);
  border: none;
  border-radius: var(--radius-card);
}

/* The one card that needs lift: the pre-login card sits over the blurred backdrop, so
   without a shadow it reads as a rectangle drawn onto a photograph. Every other card in
   this product sits on flat colour and is separated by air, which is the whole point of
   principle 1, so no other card takes a shadow. */
.o-prelogin .formcol .o-card,
.o-prelogin .scout-card {
  box-shadow: var(--elev-card);
}

/* ---- Modals, dialogs and the sheet. ONE STEP ABOVE THE PAGE, no border, the drop. ----
   CHANGED 14 September 2026 (Shane, after the teal glow came off: "now the edge of the
   card is invisible, but we want to avoid using a border if possible").

   WHY IT WENT INVISIBLE. These surfaces were --bg-modal, which resolves to the canvas
   colour, so a dialog was the same tone as the page it covered and the only thing marking
   its edge was the teal ring in --elev-modal. Take the ring off and there is nothing left
   to mark it: a drop needs something lighter behind it to darken, and behind a dialog is a
   scrim over a near-black page.

   THE PRODUCT ALREADY ANSWERED THIS, TWICE. The rulings register, 12 September, on the top
   bar's menu: "A menu has no border and no shadow. It separates by sitting one surface step
   above the page (--bg-raised); --bg-modal resolves to the canvas colour and cannot." And
   ruling 5j, on a card inside a card: once the fill is half a step up, "the fill and the
   drop do the whole job and the ring is a third mark saying the same thing". The menu was
   moved off --bg-modal then; the dialogs were not. They are now.

   --bg-modal ITSELF IS LEFT ALONE, deliberately. The pre-login card reads it too, and the
   footer strip on that card has a fill computed against it to match Stripe's own
   strip-to-card contrast (see the working on --bg-footer in tokens.css). Moving the token
   would invert that relationship and make the strip darker than the card it sits in.

   THE HALF STEP, NOT THE FULL ONE (Shane, 14 September: "background is too light now").
   Drawn at --bg-raised first, which is a full step up and measures 1.112 against the page.
   That is the same value the 12 September pass rejected on a nested card, where it "read as
   a different KIND of object" rather than as the same surface lifted; the resolution then
   was --bg-nested, the midpoint, created as a token for this exact job. It measures 1.079,
   which is enough to draw the edge with no ring and not so much that the dialog stops
   looking like part of the product. ---- */
.o-modal,
.scout-modal,
.welcome-scrim .o-welcome,
.dm-panel {
  background: var(--bg-nested);
  border: none;
  border-radius: var(--radius-card);
  box-shadow: var(--elev-modal);
}

/* The welcome component is drawn twice in this product: as the pop-up over the desk (E7)
   and as an in-page card at the top of the Orbitra ID page (S1). It carried a teal border
   of its own in both. As the pop-up it is a modal and takes the modal rules above; as the
   card it loses the border too and keeps a surface one step above the page, the same
   tonal container the waiting band and the footer strip use. */
.o-welcome {
  border: none;
  border-radius: var(--radius-card);
  background: var(--bg-surface);
}
.welcome-scrim .o-welcome { background: var(--bg-nested); }

/* ---- Tonal containers. The desk's waiting band is a card by every test except its
   class name, so it loses its border and takes the card radius. It keeps its own surface
   one step above the page, which is the same device the card footer strip uses and the
   one principle 1 endorses by name: a tone, not a box. ---- */
.r10bar {
  border: none;
  border-radius: var(--radius-card);
}


/* ================================== 2. SPACE ==================================
   The padding scale, and the spacings between a line and the thing under it. */

/* --pad-card is declared at 64px in tokens.css. The two smaller steps are here, because
   a media query cannot live in a token file. */
@media (max-width: 1023px) {
  :root { --pad-card: 32px; }
}
@media (max-width: 767px) {
  :root { --pad-card: 16px; }
}

/* Interior padding, on the surfaces the scale was measured on: the pre-login card, and
   every modal, dialog and sheet. In-page content cards keep their own padding; the
   reason is recorded in BUILD-REPORT.md as a judgement call. */
.o-prelogin .formcol .o-card,
.o-prelogin .scout-card,
.o-modal,
.scout-modal,
.welcome-scrim .o-welcome {
  padding: var(--pad-card);
}

/* Label to its input, and between field groups. */
.o-field { gap: var(--space-label); }
.fields,
.tq-fields { gap: var(--space-group); }
/* Heading to the first field group, and a paragraph or caption to the control below it. */
.fields { margin-top: var(--space-group); }
.o-prelogin .actions,
.actions { margin-top: var(--space-text-control); }
.o-prelogin .card-alert { margin-top: var(--space-text-control); }
/* Between stacked buttons, and along an act row. */
.scout-acts,
.modal-acts,
.o-welcome__foot,
.vp-acts,
.ie-acts,
.public-actions { gap: var(--space-buttons); }


/* THE TOP BAR GOES FULL WIDTH BELOW THE DRAWER BREAKPOINT. Ruled by Shane, 12
   September 2026. The platform bar carries the page's 48px gutter at every width,
   so on a narrow desktop, a tablet or a phone it reads as a bar floating inside
   the window rather than the window's own edge, at the exact moment the hamburger
   appears and the bar becomes the navigation. Below the platform's 1390 breakpoint
   the side padding drops to 24, and below 768 to 16, the same steps --pad-card
   takes. The page keeps its own gutter; only the bar tightens. */
@media (max-width: 1390px) {
  .o-topbar { padding-left: var(--sp-24); padding-right: var(--sp-24); }
}
@media (max-width: 767px) {
  .o-topbar { padding-left: var(--sp-16); padding-right: var(--sp-16); }
}


/* ================================= 3. CONTROLS =================================
   Inputs 44 tall with a 1px ring drawn as a shadow, so the ring costs no height.
   Primary buttons 44, secondary 40, both at radius 6. */

.o-input {
  height: var(--control-h);
  padding: 8px var(--control-pad-x);
  border: none;
  box-shadow: 0 0 0 1px var(--border-input);
  border-radius: var(--radius-control);
  background: var(--bg-modal);
  font: var(--type-body);
  color: var(--text-primary);
}
.o-input:hover:not(:disabled):not(.is-error) {
  box-shadow: 0 0 0 1px var(--border-default);
}
.o-input:focus,
.o-input:focus-visible {
  outline: none;
  box-shadow: var(--focus-halo);
}
.o-input.is-error { box-shadow: 0 0 0 1px var(--status-danger-fill); }
.o-input:disabled { background: var(--bg-surface); color: var(--text-disabled); }
/* The reveal control sits inside the field, so it follows the field's new height. */
.o-input--reveal { padding-right: var(--sp-40); }

/* The checkbox and its label. */
.o-check { border-radius: var(--radius-control); }
.o-check-row { font: var(--type-label); color: var(--text-primary); }

/* Buttons. The class alone is 0,1,0 and the platform's own quiet button is 0,2,0, so the
   two heights below are written at 0,2,0 to reach past it without touching the quiet
   button, which is a 32px control by its own screen's ruling. */
.o-btn { border-radius: var(--radius-control); font: var(--type-button); }
.o-btn.o-btn--primary { height: var(--control-h); }
.o-btn.o-btn--secondary {
  height: var(--control-h-secondary);
  background: transparent;
  box-shadow: 0 0 0 1px var(--border-input);
  color: var(--text-primary);
}
.o-btn.o-btn--secondary:hover:not(:disabled) {
  background: var(--bg-surface);
  box-shadow: 0 0 0 1px var(--border-default);
}

/* The index page's own primary button, which is neither a platform button nor a blank
   screen's act, but is the first control Maher presses. */
.start {
  height: var(--control-h);
  padding: 0 var(--sp-20);
  border-radius: var(--radius-control);
  font: var(--type-button);
}

/* The blank screens' own act buttons, same two heights, same radius. */
.scout-act {
  border-radius: var(--radius-control);
  font: var(--type-button);
  height: var(--control-h-secondary);
  padding: 0 var(--sp-16);
}
.scout-act--primary { height: var(--control-h); }
.scout-act--secondary,
.scout-act--quiet {
  background: transparent;
  border-color: var(--border-input);
}
.scout-act--secondary { color: var(--text-primary); }

/* Chips a person operates take the control radius. Chips that are labels (the status
   chip, the Needs you pill, a trade's state, the index's BUILT and BLANK tags) stay
   fully round: a pill is a shape rather than a step on the radius scale, and taking
   them to 6px would restyle the top bar the architecture ruling fixed. */
.dm-chip,
.scout-slot { border-radius: var(--radius-control); }


/* ================================== 4. TYPE ==================================
   Stripe's sizes and line-heights, weight 400 where Stripe uses 300, because weight 300
   on near-black loses its edges. Secondary text is neutral now; the token already
   changed, so nothing below re-states a colour that the token carries. */

/* Card, modal and page headings. The desk's own greeting and the index page's title are
   display type, the one large element on their page, and are left alone. */
.page-title,
.o-prelogin .page-title,
.scout-page__h,
.scout-modal__h,
.modal-h,
.dm-panel__h,
.o-welcome__h {
  font: var(--type-heading);
  letter-spacing: var(--tracking-heading);
  color: var(--text-primary);
}

/* ONE TITLE RULE, EVERYWHERE, finished 10 September 2026 (corrections ruling A2).
   The qualification flow's own steps were the one family the rule above could not
   reach. The flow's stylesheet steps every heading after step 1 down a stop of its
   own ("so a twelve-screen walk does not shout twelve times"), written as
   `.wizard > section[data-step]:not([data-step="1"]) .page-title`, which is
   specificity (0,4,1) and outranks both arms above. Measured, not guessed: those
   headings were setting 26px/36px at weight 500 in Satoshi while every other title
   in the build set 22px/36px at weight 400 in Inter.

   The arm below is (0,5,1), one class higher, so it wins on specificity rather than
   on source order. Every class in it is really on the page: the flow is a
   .o-prelogin frame, its steps are sections inside .wizard, and every step's title
   sits inside that step's own .o-card.

   WHAT THIS FIXES ON THE SCREEN: "Are you the manager on your licence?" set on two
   lines at 26px in a 412px column. At 22px it sets on one. */
.o-prelogin .wizard > section[data-step] .o-card .page-title {
  font: var(--type-heading);
  letter-spacing: var(--tracking-heading);
  color: var(--text-primary);
}

/* THE BODY LINE. Its values are the desk's status line, measured in a real browser
   and written into tokens.css as --type-body-secondary and --text-body (ruling A3;
   the measurement is on the token). Every modal body line, every card sentence and
   every standard body line on every screen takes them, the document screen's "Read
   from bill-of-lading... Check each detail before you sign" included, because that
   line is a .page-sub.

   .centre .voice is the desk's own status line, named here at the (0,2,0) its own
   file uses so the token is what draws it rather than the desk file's own copy of
   the same two numbers. .ie-p is the interruption set's body line, which was one
   step smaller again at 14/20. */
.page-sub,
.o-prelogin .page-sub,
.scout-page__p,
.scout-modal__p,
.modal-p,
.voice,
.centre .voice,
.o-welcome__p,
.ie-p {
  font: var(--type-body-secondary);
  color: var(--text-body);
}

/* Labels. */
.o-label { font: var(--type-label); color: var(--text-secondary); }

/* Captions. */
.o-help,
.scout-page__eyebrow,
.scout-lock,
.scout-note,
.o-progress-caption,
.dm-panel__conseq {
  font: var(--type-caption);
  color: var(--text-secondary);
}
.o-help--error { color: var(--status-danger-text); }

/* Links inside a card or a dialog. The rail, the doors and the top bar keep their own
   type: they are chrome, not the reading surface this scale was measured on. */
.o-prelogin .o-card a:not(.o-btn):not(.scout-act),
.scout-card a:not(.scout-act),
.forgot,
.tq-link {
  font: var(--type-link);
  color: var(--link-default);
}


/* ========================== 5. THE CARD FOOTER STRIP ==========================
   The line a card carries at its foot ("No account yet? Sign up", "Already have an
   account? Log in") becomes a strip. The wrapper is added by build/02_copy_built.py,
   which moves the existing line into it and rewrites none of its words.

   RE-RULED 10 September 2026 (Shane), to match Stripe: the strip is INSET from the card
   on all four sides rather than running the full card width, rounded on all four corners
   at the control radius, and carries NO hairline. Its fill is --bg-footer, which is now a
   computed value: the tone that sits at Stripe's own strip-to-card contrast, 1.05 to 1
   (the arithmetic is written out on the token in tokens.css). The result is a tonal
   container that is barely there, which is the point.

   THE SIDE AND BOTTOM INSET IS 24px FROM THE CARD'S EDGE, not from its content box. The
   card's padding is --pad-card (64px at 1024 and up, 32 from 768 to 1023, 16 below), so
   the strip pulls back out through that padding by calc(var(--sp-24) - var(--pad-card))
   and lands 24px inside the card at every one of the three widths. Below 768 the card
   itself goes edge to edge with no radius, so calc gives +8px there and the strip sits
   24px in from the window's own edge, which is the same reading.

   THE TOP GAP IS LEFT AT --pad-card, unchanged from the rule this replaces: it is the gap
   between the last control and the strip, measured from content, not an inset from the
   card's edge, so the 24px rule does not reach it. */

/* Left in place from the full-bleed strip. It no longer clips anything (the strip is
   inset now and rounds its own corners), and it is not removed because it is on every
   .o-card in the build and taking it off is a change with a blast radius this pass has
   not measured. Reverse by deleting this rule. */
.o-card { overflow: hidden; }

.v2-cardfoot {
  display: grid;
  place-items: center;
  height: 64px;
  margin: var(--pad-card) calc(var(--sp-24) - var(--pad-card)) calc(var(--sp-24) - var(--pad-card));
  padding: 0 var(--sp-16);
  background: var(--bg-footer);
  border: none;
  border-radius: var(--radius-control);
  text-align: center;
}
.v2-cardfoot > * { margin: 0; font: var(--type-caption); color: var(--text-secondary); }
/* AN ESCAPE LINE AT THE FOOT OF A CARD IS A STRIP, 10 September 2026 (ruling A4).
   The rule is no longer only about "No account yet? Sign up": ANY line at the foot of
   a card that leaves the card's main path takes this component. In the qualification
   flow those lines are drawn as .tq-linkrow with a .tq-link button inside, so the two
   below bring that pair into the strip: the row loses the margin it carries when it
   stands on its own, and the button inside it sets at the strip's own 13px link
   rather than the flow's 15px one.

   WHERE THE LINE IS NOT AN ESCAPE, it stays a verb. A link that answers the card's
   own question sits inside the question's group, above the card's act row, and joins
   the verb row instead (section 15). The two the flow has are "I am not sure" and
   "I cannot check right now"; the two that are escapes are "Skip for now" and "I do
   not know who the manager is yet", and both sit AFTER their card's act row. */
.v2-cardfoot .tq-linkrow { margin: 0; text-align: center; }
.v2-cardfoot .tq-link {
  font: var(--type-caption);
  color: var(--link-default);
}
.v2-cardfoot .tq-link:hover { color: var(--link-hover); }
/* Written one step above the card-link rule in section 4, and after it, so the strip's own
   13px link wins over the card's 14px one. The two :not() arms are what carry that step:
   each contributes a class's worth of specificity, exactly as they do in section 4. */
.v2-cardfoot a,
.o-prelogin .o-card .v2-cardfoot a:not(.o-btn):not(.scout-act) {
  font: var(--type-caption);
  color: var(--link-default);
  text-decoration: none;
}
.v2-cardfoot a:hover { color: var(--link-hover); }

/* Below 768 the strip is already a detached rounded box, because the rule above detaches
   it at every width now. Nothing left to say here; the block is kept, empty of its old
   contents, so the responsive story reads in one place. The card goes edge to edge below
   768 (section 6), and the calc above puts the strip 24px inside that edge. */


/* ====================== 6. RESPONSIVE RULES, PRE-LOGIN ======================
   540 at 1024 and up; radius and elevation down to 768; edge to edge with 16px margins
   and neither radius nor elevation below 768. */

.o-prelogin .formcol { width: min(var(--card-w), 100%); }
.o-prelogin .scout-card { width: min(var(--card-w), 100%); }

@media (max-width: 767px) {
  .o-prelogin .wizard { padding-left: var(--sp-16); padding-right: var(--sp-16); }
  .o-prelogin .formcol { width: 100%; }
  .o-prelogin .formcol .o-card,
  .o-prelogin .scout-card {
    width: 100%;
    border-radius: 0;
    box-shadow: none;
  }
}


/* ============ 7. WHAT EVERY MODAL AND POP-UP INHERITS: THE OVERLAY ============
   A scrim plus a blur, and the page behind held still while the dialog is open. The blur
   does the separating on a near-black canvas; a plain dark scrim on a dark page adds
   almost nothing. */

.scout-dim,
.dm-scrim,
.welcome-scrim,
.share-scrim,
.o-prelogin .scrim,
.frame .scrim,
.o-drawer-scrim {
  background: var(--overlay-scrim);
  backdrop-filter: blur(var(--overlay-blur));
  -webkit-backdrop-filter: blur(var(--overlay-blur));
}

/* The page behind a dialog does not move.
   - The five dialog screens this build draws (D6, T3, P2 and the verification-call
     sheet's two doors) carry the page behind them as real markup, built in at build time
     (build/03_blanks.py), inside .scout-modal-host. The rule below fixes that backdrop to
     the window, clips it, and makes it inert, so it cannot scroll, cannot be clicked and
     cannot take focus while the dialog is open. CHANGED 10 September 2026: it used to be
     an iframe, and pointer-events was set on that iframe; the backdrop is inlined markup
     now, so the rule moves onto the host itself.
   - The desk's own review drawer puts data-dm-panel="open" on the body when it opens, so
     the lock is written against that attribute and needs no change to the file's script.
   - The welcome pop-up and the share ask sit on a scrim that is fixed to the window
     (section 10), so the page under them cannot move either. */
.scout-modal-host {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
}

/* The desk scrolls on the document element, not on the body, so the lock is written
   against both: the attribute the file's own script sets lives on the body. */
html:has(body[data-dm-panel="open"]),
body[data-dm-panel="open"] { overflow: hidden; }
body[data-dm-panel="open"] .content { overflow: hidden; }


/* ====================== 8. THE REVIEW DRAWER ======================
   Decision D-182, ruling 4, and 08-CHAT-PLACEMENT-SYNTHESIS section 3: anything reviewed
   with evidence gets a drawer with the page still visible; a modal is for a single
   confirmation. The desk's review pop-up (.dm-panel) was a centred modal; it becomes a
   right-side drawer, at the conversation panel's own width, on the same surface, full
   height, sliding in from the right, with the desk behind it under the overlay.

   The file's own DOM and script are untouched: everything below is position, size and
   layout, and the file's script only ever sets data-dm-panel on the body, which is what
   the open state reads. The one exception is the verbs, which gained behaviour; that is a
   per-file edit in build/02_copy_built.py, recorded in BUILD-REPORT.md.

   Wrapped at 641 and up so the file's own phone behaviour (a sheet rising from the foot,
   its own @media (max-width: 640px)) is left exactly as it was. */

@media (max-width: 1359px) {
  :root { --drawer-w: 340px; }
}

@media (min-width: 641px) {
  .dm-panel {
    top: 0;
    right: 0;
    bottom: 0;
    left: auto;
    width: min(var(--drawer-w), 100%);
    max-width: none;
    max-height: none;
    height: 100%;
    border-radius: var(--radius-card) 0 0 var(--radius-card);
    transform: translateX(100%);
  }
  body[data-dm-panel="open"] .dm-panel { transform: translateX(0); }

  /* The way back to the index is fixed to the window's bottom right on every screen in
     this build, which is where the drawer now is. It steps left of the drawer while the
     drawer is open, the same move the conversation panel screen already makes. */
  body[data-dm-panel="open"] .scout-home {
    right: calc(var(--drawer-w) + var(--sp-16));
  }
}

/* Top to bottom inside the drawer: the header, the context line, the evidence rows, the
   draft, what approving does, and the verbs pinned to the foot. The body is the only part
   that scrolls, so the verbs are never a line a reader has to go looking for. */
.dm-panel__head { padding: var(--pad-drawer) var(--pad-drawer) var(--sp-8); }
.dm-panel__body { flex: 1 1 auto; padding: 0 var(--pad-drawer); }
.dm-panel__conseq { padding: var(--sp-16) var(--pad-drawer) 0; }
.dm-panel__foot {
  padding: var(--space-text-control) var(--pad-drawer) var(--pad-drawer);
  gap: var(--space-buttons);
}
.dm-panel__foot .o-btn { height: var(--control-h); }

/* Edit makes the draft editable where it sits. The reply variant's draft is already a
   field; the other variants' is a paragraph, which the verb turns editable. Both take the
   focus halo, so an engaged draft reads the same as an engaged field anywhere else. */
.dm-panel__q.is-editing,
.dm-panel__edit.is-editing {
  background: var(--bg-modal);
  box-shadow: var(--focus-halo);
  border-radius: var(--radius-control);
  padding: var(--sp-12);
  outline: none;
}
p.dm-panel__q.is-editing { padding: var(--sp-12); }

/* The drafted letter is a field, so it takes the field's own ring, radius and text size
   rather than the box the file drew it in. */
.dm-panel__edit {
  border: none;
  box-shadow: 0 0 0 1px var(--border-input);
  border-radius: var(--radius-control);
  background: var(--bg-modal);
  font: var(--type-body);
  color: var(--text-primary);
  padding: var(--sp-12);
}
.dm-panel__edit:focus,
.dm-panel__edit:focus-visible {
  outline: none;
  box-shadow: var(--focus-halo);
}

/* Discard asks one question in place, under the verbs. Never a second modal. The hidden
   state is written explicitly: a display rule on the class outranks the browser's own
   [hidden] rule, so without this the question would be drawn before it is asked. */
.dm-panel__why[hidden] { display: none; }
.dm-panel__why {
  display: flex;
  align-items: center;
  gap: var(--space-buttons);
  padding: 0 var(--pad-drawer) var(--pad-drawer);
}
.dm-panel__why .o-input { flex: 1 1 auto; min-width: 0; }
.dm-panel__why .o-btn { flex: none; height: var(--control-h); }

/* The waiting-band row the drawer is about, ringed while the drawer is open. Which row
   that is comes from the panel's own approve control, which the file's script stamps with
   the variant name every time it opens, so this needs no script of its own. */
body[data-dm-panel="open"]:has(#dm-approve[data-panel="reply"]) #dm-wait-khalid,
body[data-dm-panel="open"]:has(#dm-approve[data-panel="cert"]) #dm-wait-cert {
  box-shadow: inset 0 0 0 1px var(--teal-400);
  border-radius: var(--radius-control);
}


/* =============== 9. THE DESK, RESHAPED TO VERSION ZERO ===============
   The composer is the hero at the top and the waiting band sits under it, both unchanged.
   Below them the board is three cards instead of six: the trade tracker at full width,
   then the assistants and the vault side by side. Each asks one question and offers one
   door. The markup edits are in build/02_copy_built.py (ELEMENT_EDITS and the desk's own
   copy edits); the placing is here.

   Scoped by :has() to the board that actually carries the reshaped tracker, so the
   conversation panel screen's own board, which is the north star's drawing and is not
   reshaped, is left alone. Above 900 only: below that the desk file's own rule stacks the
   board into one column and that behaviour is kept. */

@media (min-width: 901px) {
  .r11cols:has(.r11card--tracker) {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
  .r11cols:has(.r11card--tracker) > .r11main,
  .r11cols:has(.r11card--tracker) > .r11rail { display: contents; }
  .r11card--tracker { grid-column: 1 / -1; grid-row: 1; }
  .r11card--assistants { grid-column: 1; grid-row: 2; }
  .r11card--vault { grid-column: 2; grid-row: 2; }
}
/* ONE GAP BETWEEN THE DESK'S CARDS, AND IT IS 40 (Shane, 12 September 2026: "the gap
   between the Needs you and Trade tracker cards is the correct vertical spacing. Please
   apply this vertical spacing between cards to the entire desk"). The desk had two
   numbers: 40 above the board, because that came from the board block's own top margin,
   and 24 between the cards inside it and under the setup card, because that came from
   each card's own. 40 everywhere now. It sits here rather than in the desk's per-file
   block because the desk carries its own stylesheet in its body, which beats anything in
   its head; this sheet is linked again at the foot of that file, so it wins. */
.r11cols { gap: var(--sp-40); }

/* A door is a link now where it leads somewhere, so it needs the link reset every other
   link in this build carries. */
a.cf__door { text-decoration: none; }

/* The vault's find bar, brought to the v2 field spec with every other field: 44 tall, the
   ring drawn as a shadow so the height stays exact, radius 6, body text. */
.cf__find {
  height: var(--control-h);
  padding: 0 var(--control-pad-x);
  border: none;
  box-shadow: 0 0 0 1px var(--border-input);
  border-radius: var(--radius-control);
  background: var(--bg-modal);
  font: var(--type-body);
  color: var(--text-tertiary);
}
.r11card--vault .cf__find { margin: var(--sp-8) 0 var(--sp-16); }
.r11card--vault svg { width: 15px; height: 15px; stroke-width: 1.6; flex: none; }


/* ============ 10. NO DIALOG EVER SCROLLS INSIDE ITSELF ============
   THE RULING (Shane, 10 September 2026), global, every dialog in the build:

     1. A dialog never scrolls internally.
     2. When a dialog is taller than the window, the whole page scrolls, and the
        backdrop behind it stays put.
     3. A dialog sits no closer to the top of the window than a tenth of the window's
        height, and never closer than 72 pixels: var(--dialog-inset-y).
     4. When it fits, it is centred vertically.

   THE REVIEW DRAWER IS EXEMPT. A drawer is full height by nature (section 8), so
   .dm-panel and .dm-scrim are not touched by anything below.

   HOW 3 AND 4 HOLD AT THE SAME TIME, without a media query or a script. The scrim gets
   var(--dialog-inset-y) as its padding on BOTH the top and the bottom, and the dialog
   gets margin: auto. In a flex container an auto margin eats the free space before
   alignment does, so:
     - dialog shorter than the window: the two auto margins split the free space evenly
       and, because the padding is symmetrical, the dialog lands on the window's true
       vertical centre;
     - dialog taller: there is no free space, both auto margins collapse to zero, and the
       dialog starts exactly var(--dialog-inset-y) down.
   align-items: center would look the same in the first case and cut the top off in the
   second, which is the flexbox trap this avoids. */

/* ---- A. The five dialog pages this build stamps: D6, T3, P2 and the two doors of the
   verification-call sheet. Their whole page is backdrop, scrim and dialog, so the ruling
   is taken literally here: the backdrop is fixed to the window (section 7), the scrim
   joins normal flow, and THE PAGE is what scrolls. The page templates in
   build/03_blanks.py no longer set overflow: hidden on their body, which is what lets it.

   shell.css draws this scrim fixed and centred with a grid; every property that fights
   the flow layout is restated here, including inset and align-items, because
   place-items: center in shell.css also sets align-items and would otherwise survive. */
.scout-dim {
  position: relative;
  inset: auto;
  z-index: 50;
  min-height: 100vh;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--dialog-inset-y) var(--sp-24);
}
.scout-dim > .scout-modal {
  margin: auto;
  max-height: none;
  overflow: visible;
  flex: none;
}

/* ---- B. The platform's own modals, which live inside a real application page: the
   booking modal (E0), the verification prompt (E5a), the interruption set's panel (X1),
   the welcome pop-up (E7) and the Orbitra ID page's share ask (S1).

   JUDGEMENT CALL, stated so it can be reversed. These scrims are drawn position: absolute
   over their screen's own .frame, with the application's content under them. Putting them
   into normal flow, the way the stamped dialog pages do, would push that content down the
   page and break five designed screens. So here the scrim is fixed to the window and the
   SCRIM is the scroll container. What a person sees is the ruling exactly: the dialog
   never scrolls inside itself, the page behind it does not move, and the top gap is the
   same var(--dialog-inset-y). It is also the more faithful reading of "the backdrop stays
   fixed" for a dialog over a real page, because scrolling that page IS moving the
   backdrop. Reverse by moving these selectors into block A above.

   Fixed also fixes a second thing on the longer pages: absolute over a .frame taller than
   the window centred the dialog on the DOCUMENT, so on the Orbitra ID page the share ask
   sat below the fold. It is centred on the window now. */
.o-prelogin .scrim,
.frame .scrim,
.welcome-scrim,
.share-scrim {
  position: fixed;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--dialog-inset-y) var(--sp-24);
}

/* The internal scroller each of these dialogs carried is cancelled: the platform gave
   .booking-modal, .vp-modal, .ie-panel and the share ask max-height: 100% with
   overflow: auto, which is rule 1 broken in four places. Written at two classes so it
   lands after the screens' own .share-scrim .o-modal rule, which is the highest of them
   and sets both properties. */
.o-prelogin .scrim > .o-modal,
.frame .scrim > .o-modal,
.share-scrim > .o-modal,
.welcome-scrim > .o-welcome {
  margin: auto;
  max-height: none;
  overflow: visible;
  flex: none;
}

/* The two scrims that open on a state keep the platform's own hidden rule, which is
   written at a class plus an attribute and so already outranks the display above. Stated
   here because a display rule that drew a hidden dialog is a defect this build has
   already made once (BUILD-REPORT.md, the review drawer's discard question). */
.welcome-scrim[hidden],
.share-scrim[hidden] { display: none; }


/* ============ 11. THE PROFILE MENU ============
   THE RULING: decision D-185, ruling 6, in ../research/12-CHAT-AS-A-MODE-RULING.md.
   The signed-in person, top right, is a menu control on every logged-in screen. Its
   menu carries the person's name and role, the company's name with its Registered or
   Verified chip, a hairline, Settings and Sign out. The company name leaves the top
   bar's left slot for it, because a company name is not a working control and the slot
   is wanted for the way back to the desk.

   THE SURFACE IS THE DIALOG SURFACE, section 1 exactly: the page colour coming forward
   (var(--bg-modal)), no border, var(--elev-modal), var(--radius-card). The padding is
   var(--sp-16) rather than var(--pad-card), because 64 pixels each side of a menu is
   not a menu; the ruling names 16 and this is where that number lands.

   THE ROWS take var(--radius-row), the universal list-row radius the same ruling adds
   (section 5 of it): 36 tall, 10 pixels of side padding, pulled out through the card's
   own padding by a negative inline margin so the row's TEXT still begins on the card's
   16 pixel edge while its hover fill runs wider than the text.

   The markup is written in build/shell.py (profile()), used by the blank screens and,
   through dress_topbar, by every copied one. The open state is deep linked as ?menu=1.
*/
.scout-profile { position: relative; display: inline-flex; align-items: center; }

/* The control. It was a span with the avatar and the name in it; it is a button now,
   so every one of the button's own defaults has to be taken back off. */
.scout-who--btn {
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.scout-who--btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }

.scout-menu {
  position: absolute; top: calc(100% + var(--sp-8)); right: 0; z-index: 80;
  display: block; min-width: 248px;
  background: var(--bg-modal);
  border: none;
  border-radius: var(--radius-card);
  box-shadow: var(--elev-modal);
  padding: var(--sp-16);
}
/* Written out, because a display rule on a class outranks the browser's own rule for
   the hidden attribute. This build has drawn a hidden thing open twice already (the
   drawer's discard question, and the sheet's day rows); it does not do it a third time. */
.scout-menu[hidden] { display: none; }

/* THE NAME IS A LINK NOW, 10 September 2026 (corrections ruling D13): it opens
   "Your profile". It keeps the identity block's look exactly, so the menu still
   reads as a name over a company rather than as three rows; only the pointer and
   the hover say it is a door. */
.scout-menu__who {
  display: block; font: var(--type-label); color: var(--text-primary);
  white-space: nowrap; text-decoration: none;
}
a.scout-menu__who:hover { color: var(--link-hover); }
a.scout-menu__who:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.scout-menu__co {
  display: flex; align-items: center; gap: var(--sp-8); margin-top: var(--sp-4);
  font: var(--type-caption); color: var(--text-secondary); white-space: nowrap;
}
.scout-menu__rule {
  display: block; height: 1px; background: var(--border-subtle);
  margin: var(--sp-12) calc(var(--sp-16) * -1);
}
.scout-menu__row {
  display: flex; align-items: center; height: 36px;
  padding: 0 10px; margin: 0 -10px;
  border-radius: var(--radius-row);
  font: var(--type-label); color: var(--text-secondary);
  text-decoration: none; white-space: nowrap;
}
.scout-menu__row:hover { background: var(--bg-surface); color: var(--text-primary); }
.scout-menu__row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }


/* ============ 12. THE CONVERSATION, A MODE OF THE DESK ============
   THE RULING: decision D-185, in ../research/12-CHAT-AS-A-MODE-RULING.md. The composer
   on the desk opens the conversation as a MODE: the whole screen given over to it, the
   rail collapsed to a recents sidebar, "Back to desk" top left in both the sidebar and
   the content area, the conversation in a centred column, the composer pinned at its
   foot, and the objects the conversation is about (a trade, the documents it was read
   from, a drafted reply) carried inside it as cards rather than on a desk beside it.

   THE NUMBERS ARE THE RULING'S SECTION 5, which measured ChatGPT live in Shane's own
   Chrome on 10 September 2026: the sidebar at var(--sidebar-w) (260), a row 36 tall at
   6 by 10 with 6 of side margin and var(--radius-row) (10), the section label small and
   muted, the column at var(--conversation-w) (720). All three are tokens in tokens.css.

   THE THREAD'S IDIOM IS THE DESK'S OWN, copied value for value out of the desk file's
   own demo layer (scout-demo.html, "THE THREAD") so the two read as one product: the
   question right aligned in a teal-tinted bubble at radius 18, the answer left aligned
   directly on the canvas with no bubble and no card, the evidence as hairline rows with
   a document icon. Every rule below is scoped under .scout-conv, so the desk's own copy
   of those classes is untouched.

   Only one screen uses this section today, screens/D4-conversation.html, stamped by
   build/03_blanks.py. It is written here rather than in that file because the recents
   row and the column width are design-system decisions the ruling made universal. */

/* ---- A. The shell and the recents sidebar.
   overflow: clip replaces the platform shell's own overflow: hidden, for the same reason
   the six-item rail needed it (build/shell.py): hidden makes the shell a scroll container,
   and a scroll container between a sticky element and the window is what stops the
   sidebar and the composer sticking to the WINDOW. clip crops without scrolling, so both
   stick where they are meant to. Found by looking at the first render, where the composer
   sat below the fold instead of pinned at the foot. */
.scout-conv-shell { --rail-w: var(--sidebar-w); }
.o-shell:has(> .scout-side) { overflow: clip; }
.o-shell > .scout-side {
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh; align-self: start;
  padding: var(--sp-16) 10px; overflow-y: auto;
}
.scout-side .rail-brand { padding: var(--sp-8) 10px var(--sp-24); }
.scout-side .rail-brand a { display: block; line-height: 0; }
/* The same 1.3 factor the rail's mark takes, for the same reason: a square mark at the
   old lockup's rendered height reads too small. See build/shell.py. */
.scout-side .rail-brand img { height: 29.25px; width: auto; display: block; }

.scout-side__row {
  display: flex; align-items: center; gap: var(--sp-12);
  height: 36px; padding: 6px 10px; margin: 0 6px;
  border-radius: var(--radius-row);
  font: var(--type-label); color: var(--text-secondary);
  text-decoration: none; white-space: nowrap; overflow: hidden;
}
.scout-side__row > span { overflow: hidden; text-overflow: ellipsis; }
.scout-side__row svg { width: 16px; height: 16px; stroke-width: 1.6; flex: none; }
.scout-side__row:hover { color: var(--text-primary); background: var(--bg-surface); }
.scout-side__row.is-active { color: var(--text-primary); background: var(--bg-surface); }
.scout-side__row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.scout-side__label {
  margin: var(--sp-24) 0 var(--sp-12); padding: 0 var(--sp-16);
  font: var(--type-caption); color: var(--text-tertiary);
}
/* The day a group of conversations belongs to, under the section label. The ruling has
   both: "Recents" names the list, the day groups it. */
.scout-side__day {
  margin: 0 0 var(--sp-8); padding: 0 var(--sp-16);
  font: var(--type-caption); color: var(--text-disabled);
}

/* ---- B. "Back to desk" in the top bar's left slot, where the company name sat. */
.scout-back {
  display: inline-flex; align-items: center; gap: var(--sp-8);
  font: var(--type-label); color: var(--text-secondary); text-decoration: none;
}
.scout-back svg { width: 16px; height: 16px; stroke-width: 1.6; flex: none; }
.scout-back:hover { color: var(--text-primary); }
.scout-back:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }

/* ---- C. The column, and the composer pinned at its foot.
   THE COLUMN IS THE WINDOW, AND THE THREAD IS WHAT SCROLLS. The mode takes the whole
   screen: the shell is exactly one window tall, the composer sits at the foot of the
   column and never moves, and the conversation scrolls under it. That is the ruling's
   own shape ("the conversation fills the centre with the composer at the foot") and it
   is the only one where nothing overlaps: the first attempt pinned the composer with
   position: sticky and let the page scroll, and the render showed the composer's own
   opaque bar sitting over the approval card's three verbs. Found by looking.
   The composer is not position: fixed, deliberately. The desk file's own note says why
   and it applies here: a fixed element gets its own compositing layer, and on a
   wide-gamut display that layer paints this near-black a hair away from the identical
   canvas behind it, so the bar reads as a lighter rectangle. Shane isolated that with
   strip tests on 27 August. */
.o-main.scout-conv {
  padding: 0 var(--gutter); max-width: none;
  height: calc(100vh - var(--size-top-bar));
}
.scout-conv__col {
  width: 100%; max-width: var(--conversation-w); margin-inline: auto;
  height: 100%; min-height: 0;
  display: flex; flex-direction: column;
}
.scout-conv__thread {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: var(--sp-24) 0 var(--sp-32);
}
.scout-conv__foot {
  flex: none; background: var(--bg); padding: var(--sp-16) 0 var(--sp-32);
}

/* ---- D. The thread, the desk's own idiom. */
.scout-conv .dm-turn { padding-top: var(--sp-32); }
.scout-conv .dm-user { display: flex; justify-content: flex-end; }
.scout-conv .dm-user__b {
  max-width: 70%; padding: var(--sp-12) var(--sp-20);
  background: var(--teal-900);
  border-radius: 18px;
  font: var(--type-body); color: var(--text-primary); text-align: left;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.scout-conv .dm-asst { margin-top: var(--sp-24); }
.scout-conv .dm-a { font: var(--type-body); color: var(--text-primary); max-width: 62ch; margin: 0; }
.scout-conv .dm-evs { margin-top: var(--sp-20); }
.scout-conv .dm-ev {
  display: flex; align-items: center; gap: var(--sp-12);
  padding: var(--sp-12) 0; border-top: 1px solid var(--border-subtle);
  font: var(--type-label); color: var(--text-secondary);
}
.scout-conv .dm-ev:last-child { border-bottom: 1px solid var(--border-subtle); }
.scout-conv .dm-ev svg { width: 15px; height: 15px; stroke-width: 1.6; flex: none; color: var(--text-tertiary); }
.scout-conv .dm-ev b { color: var(--text-primary); font-weight: var(--fw-medium); }
.scout-conv .dm-ev__dim { color: var(--text-tertiary); }

/* ---- E. The trade as a card inside the conversation, with one door.
   The reference, the parties and the state chip are the desk's own tracker row, value
   for value (.cf__ref, .cf__who, .cf__chip in the desk file), so the same trade reads
   the same in both places. The surface is the tonal container the waiting band uses. */
.scout-tradecard {
  margin-top: var(--sp-24);
  display: flex; align-items: center; gap: var(--sp-16); flex-wrap: wrap;
  background: var(--bg-surface); border-radius: var(--radius-card);
  padding: var(--sp-16) var(--sp-20);
}
.scout-tradecard__ref {
  font: var(--fw-medium) var(--fs-sm)/var(--lh-sm) var(--font-mono);
  color: var(--teal-400); letter-spacing: .02em; flex: none;
}
.scout-tradecard__who {
  flex: 1; min-width: 0; font: var(--type-label); color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scout-tradecard__chip {
  flex: none; font: var(--fw-medium) var(--fs-caption)/var(--lh-caption) var(--font-ui);
  color: var(--text-tertiary);
  border: 1px solid var(--border-subtle); border-radius: var(--r-full);
  padding: 2px var(--sp-8);
}
/* The trade's state, in the desk's own colours for it (.cf__chip--you on the tracker). */
.scout-tradecard__chip--you { color: var(--mint-550); border-color: var(--mint-700); }
.scout-tradecard__door {
  flex: none; font: var(--type-link); color: var(--link-default); text-decoration: none;
}
.scout-tradecard__door:hover { color: var(--link-hover); }

/* ---- F. The approval card. It is what the review drawer was: the same three verbs, the
   same drafted letter as a field, the same line saying what approving does. It sits on
   the page's own colour with the teal glow, like every other dialog surface in this
   build, because inside the conversation it IS the dialog. */
.scout-approve {
  margin-top: var(--sp-32);
  background: var(--bg-modal); border: none;
  border-radius: var(--radius-card); box-shadow: var(--elev-modal);
  padding: var(--sp-24);
}
.scout-approve__h { font: var(--type-label); color: var(--text-primary); margin: 0 0 var(--sp-12); }
.scout-approve__draft {
  display: block; width: 100%; resize: none;
  padding: var(--sp-12) var(--control-pad-x);
  background: var(--bg-modal); border: none;
  box-shadow: 0 0 0 1px var(--border-input);
  border-radius: var(--radius-control);
  font: var(--type-body); color: var(--text-primary);
}
.scout-approve__draft:focus-visible { outline: none; box-shadow: var(--focus-halo); }
.scout-approve__conseq {
  font: var(--type-caption); color: var(--text-secondary);
  margin: var(--sp-16) 0 var(--sp-24);
}
.scout-approve__verbs { display: flex; align-items: center; gap: var(--sp-12); }
.scout-approve__verbs .o-btn { height: var(--control-h); }

/* ---- G. The composer, the desk's own shape and its own placeholder words, copied out of
   the desk file's own rules (.cp and its three parts). It is static here: a still of the
   product, like every other control this clickthrough does not wire. */
.scout-conv .cp {
  display: flex; align-items: center; gap: var(--sp-12); width: 100%;
  background: var(--bg-surface); border: 1px solid var(--border-default);
  border-radius: var(--r-full);
  padding: var(--sp-8) var(--sp-8) var(--sp-8) var(--sp-16);
}
.scout-conv .cp__aff {
  display: grid; place-items: center; width: 32px; height: 32px; flex: none;
  background: none; border: 0; border-radius: var(--r-full);
  color: var(--text-tertiary); padding: 0;
}
.scout-conv .cp__aff svg { width: 16px; height: 16px; }
.scout-conv .cp__ph {
  flex: 1; text-align: left; font: var(--type-body); color: var(--text-tertiary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.scout-conv .cp__send {
  display: grid; place-items: center; width: 36px; height: 36px; flex: none;
  background: var(--bg-inset); border: 1px solid var(--border-subtle);
  border-radius: var(--r-full); color: var(--text-secondary); padding: 0;
}
.scout-conv .cp__send svg { width: 16px; height: 16px; }

/* ---- H. The two doors on the desk that lead into the mode.
   "Review" on the waiting band's AL-4471 row, in the band's own door idiom, and the
   answer card the certificate chip draws in place (decision D-185, ruling 2). The card
   is the dialog surface again, at 24 of padding rather than 64: it sits in the flow of
   the desk under the composer, and the desk below it moves down by its height and by
   nothing else. */
a.r10item__door { flex: none; margin-left: auto; text-decoration: none; }
a.r10item__door + .r10item__when { margin-left: var(--sp-16); }

.scout-answer {
  width: 100%; max-width: 768px; margin: var(--sp-32) auto 0; text-align: left;
  background: var(--bg-modal); border: none;
  border-radius: var(--radius-card); box-shadow: var(--elev-modal);
  padding: var(--sp-24);
}
.scout-answer[hidden] { display: none; }
.scout-answer .dm-a { margin: 0; }
.scout-answer .scout-tradecard { margin-top: var(--sp-20); }
.scout-answer__more {
  display: inline-block; margin-top: var(--sp-20);
  font: var(--type-link); color: var(--link-default); text-decoration: none;
}
.scout-answer__more:hover { color: var(--link-hover); }
/* The trade's state on the certificate's own trade, in the desk's own colours for it
   (.cf__chip--warn on the tracker). */
.scout-tradecard__chip--warn { color: var(--status-warning-text); border-color: var(--status-warning-border); }


/* ============ 13. THE SETUP CARD ON THE DESK ============
   THE RULING: decision D-186, ../research/11-MOMENTS-AND-PROFILES-PROCESS.md section 5.
   "Get your desk working", the five-row checklist from the 24 August hand-off desk,
   revived as one card under the composer and above the waiting band. On day one the
   band is empty and this card is what populates the desk, which is decision D-109.

   THE COMPONENT IS THE PLATFORM'S OWN .o-guide, the setup guide it already ships, with
   its head, meter, count, list and .g-row family. It was drawn for a floating 336-pixel
   panel pinned in a screen's corner. Everything below is only what has to change to make
   it a card in the flow of the board, plus the four small parts the 24 August card had
   that the platform never promoted (a row that is two lines tall, its note, the
   identifier and its copy control).

   THE SURFACE IS THE WAITING BAND'S, var(--bg-surface) at var(--radius-card) with no
   border and no shadow, not the dialog surface. Stated as a judgement call in
   BUILD-REPORT.md: the card sits in the board beside the waiting band, both are tonal
   containers rather than boxes, and giving this one a glow would make a standing task
   the loudest thing on the desk. */
.o-guide.scout-setup {
  position: static; right: auto; bottom: auto;
  width: 100%; margin: 0 0 var(--sp-24);
  background: var(--bg-surface);
  border: none;
  border-radius: var(--radius-card);
  box-shadow: none;
}
/* .o-guide sets no display of its own, so [hidden] already works on the card. .g-row
   sets display: flex, which outranks the browser's rule for the attribute, so the row
   that is only shown in the staff variant needs its guard written out. This build has
   drawn a hidden thing open twice before; it does not do it again. */
.o-guide.scout-setup[hidden] { display: none; }
.scout-setup .g-row[hidden] { display: none; }

.scout-setup .o-guide__head { padding: var(--sp-16) var(--sp-24) var(--sp-12); }
.scout-setup .o-guide__meter { margin: 0 var(--sp-24); }
.scout-setup .o-guide__count { padding: var(--sp-8) var(--sp-24) 0; margin: 0; }
.scout-setup .o-guide__list { padding: var(--sp-8) var(--sp-24) var(--sp-16); }
.scout-setup__x { margin-left: auto; flex: none; }

/* The identifier and its copy control, in row 1. The identifier is set in the mono face
   every Orbitra ID in this product is set in. */
.scout-setup__id {
  flex: none; font: var(--fw-regular) var(--fs-caption)/var(--lh-caption) var(--font-mono);
  color: var(--text-secondary); letter-spacing: .02em;
}
.scout-setup__copy {
  flex: none; display: grid; place-items: center; width: var(--sp-20); height: var(--sp-20);
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--text-tertiary);
}
.scout-setup__copy svg { width: 15px; height: 15px; stroke-width: 1.6; }
.scout-setup__copy:hover { color: var(--text-primary); }
.scout-setup__copy:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* A row whose label carries a second line, and that second line. Both are the 24 August
   card's own names for them; the platform promoted the rest of the family and not these
   two, so they are written here. */
.scout-setup .g-row--tall { align-items: flex-start; }
.scout-setup .g-row__note {
  display: block; margin-top: 2px;
  font: var(--type-caption); color: var(--text-tertiary);
}
.scout-setup a.g-row__cta { text-decoration: none; }
.scout-setup .g-row__label { flex: 1; min-width: 0; }

/* THE METER IS A DOOR, demo v3, 10 September 2026. Need 4 of Maher's launch-alignment
   document, team adoption, gets no rail item (the rail stays the ruled six), so its
   progress view is reached from the two places a person already looks: this meter and
   the profile menu. The meter and its count are wrapped in one link, so the whole
   progress line is the door rather than a new control being added to the card. Both
   were a div and a p and are spans inside the link now, so both are told to be blocks
   here; nothing else about them changes. */
.scout-setup__meterlink { display: block; text-decoration: none; color: inherit; }
.scout-setup .o-guide__meter { display: block; }
.scout-setup .o-guide__count { display: block; }
.scout-setup__meterlink:hover .o-guide__count { color: var(--text-primary); }
.scout-setup__meterlink:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* THE PATH RECOMMENDATION, ?setup=path. Need 5 of the same document, "path-recommendation
   logic at SCOUT Desk activation". It is a STATE OF THIS CARD, not a screen before the
   desk: the walk stays the flow, the terms and the desk. One line at the top of the card
   and two doors, both in the card's own row-door idiom, so nothing new is drawn. */
/* The bottom padding is the meter's own separation: without it the teal doors and the
   teal progress bar sit one on top of the other and the bar reads as an underline of
   the second door. Found by looking at the render. */
.scout-setup__path { padding: 0 var(--sp-24) var(--sp-20); }
.scout-setup__path[hidden] { display: none; }
.scout-setup__path-h { margin: 0 0 var(--sp-8); font: var(--type-label); color: var(--text-primary); }
.scout-setup__path-doors { display: flex; flex-wrap: wrap; gap: var(--sp-24); }

/* The Verified card, the same card for one visit when the company becomes Verified
   (research/11 section 3). Same surface, same head, two lines and one optional door. */
.scout-verified__p { margin: 0 0 var(--sp-4); font: var(--type-body); color: var(--text-primary); }
.scout-verified__sub { margin: 0 0 var(--sp-16); font: var(--type-label); color: var(--text-secondary); }

/* ---- THE SAME CARD, DOCKED. Ruled 10 September 2026 (corrections ruling D16).

   THE RULE, and it is the point of the whole thing: ONE COMPONENT, TWO POSITIONS,
   PICKED BY STATE. Day one the rows sit IN THE FLOW of the desk under the
   composer, because on day one they are what stops the desk being empty (decision
   D-109). From the second visit, or the moment the person collapses the card, the
   SAME rows are docked bottom right of the window and stay there while the desk is
   open, until every row is done. Deep links: ?setup=1 in flow, ?setup=docked, and
   ?setup=docked&collapsed=1 for the single line.

   THE FORM IS THE PLATFORM'S OWN CORNER PANEL, which is where this component came
   from: components.css draws .o-guide as a 336px panel pinned to a corner and
   .o-guide-strip as the same object minimised to one line, and its own comment
   says so ("ONE COMPONENT IN TWO FORMS, NEVER TWO COMPONENTS"). Section 13 above
   is what takes that panel OUT of its corner and puts it in the flow of the board.
   The block below simply gives the corner back, so nothing new is drawn either
   way.

   FIXED, NOT ABSOLUTE. The platform pins the panel with position: absolute, which
   on the desk would anchor it to the page and let it scroll away. The ruling says
   fixed while the desk is open, so it is fixed to the window. */
.scout-setup--docked.o-guide.scout-setup {
  position: fixed;
  right: var(--sp-32);
  bottom: var(--sp-32);
  left: auto;
  z-index: 20;
  width: min(336px, calc(100% - var(--sp-40)));
  margin: 0;
  background: var(--bg-raised);
  border: 1px solid var(--border-default);
  border-radius: var(--r-lg);
  box-shadow: var(--elev-3);
}
/* Docked, the panel is 336 wide rather than the board's full width, so the two
   parts written for the wide card come back to the platform's own 20px gutter. */
.scout-setup--docked .o-guide__head { padding: var(--sp-16) var(--sp-20) var(--sp-12); }
.scout-setup--docked .o-guide__meter { margin: 0 var(--sp-20); }
.scout-setup--docked .o-guide__count { padding: var(--sp-8) var(--sp-20) 0; }
.scout-setup--docked .o-guide__list { padding: var(--sp-8) var(--sp-20) var(--sp-16); }
/* A ROW AT 336 IS NOT A ROW AT 1100. The card in the flow of the board is over a
   thousand pixels wide, so a row holds its mark, its label, its value and its door
   on one line. Docked, the same row has 336 and the platform's own .g-row makes the
   value and the door inflexible, so the LABEL is what gets squeezed: "Your Orbitra
   ID" set on three lines and "Add your documents" ran under its own value. Found by
   looking at the render. Docked, the row wraps: the label takes the first line
   beside its mark, and the value and the door sit on the line under it, the door
   pushed to the right edge. Nothing about the row changes in the flow of the
   board. */
.scout-setup--docked .g-row { flex-wrap: wrap; align-items: flex-start; row-gap: var(--sp-4); }
.scout-setup--docked .g-row__label { flex: 1 1 calc(100% - var(--icon-sm) - var(--sp-12)); }
.scout-setup--docked .g-row__mark { margin-top: 2px; }
.scout-setup--docked .scout-setup__id,
.scout-setup--docked .g-row__meta { margin-left: calc(var(--icon-sm) + var(--sp-12)); }
.scout-setup--docked .scout-setup__id + .scout-setup__copy + .g-row__meta,
.scout-setup--docked .scout-setup__id ~ .g-row__meta { margin-left: 0; }
.scout-setup--docked .g-row__cta { margin-left: auto; }

/* THE ONE CONTROL, TWO GLYPHS. In the flow it dismisses and draws a cross; docked
   it minimises to the strip and draws the platform's own minimise chevron, which is
   the rule that component came with: a standing task that is not finished may be
   put away, never closed. The markup carries both glyphs (build/02_copy_built.py,
   GUIDE_X) and this picks one. */
.scout-setup__x-min { display: none; }
.scout-setup--docked .scout-setup__x-close { display: none; }
.scout-setup--docked .scout-setup__x-min { display: block; }

/* The collapsed form is the platform's .o-guide-strip, which already draws
   itself in the same corner; the same two lines fix it to the window. */
.scout-setup-strip.o-guide-strip {
  position: fixed;
  right: var(--sp-32);
  bottom: var(--sp-32);
}


/* ============ 14. THE STRETCH TAG ============
   ADDED 10 September 2026, demo v3. Maher's launch-alignment document of 10 September
   splits the 21 September scope into a locked "Need" section and a "Stretch, ship if
   bandwidth allows" section, and gives each stretch item a written fallback. Every
   surface in this concept that belongs to a stretch item carries one caption line
   saying so, in the document's own fallback words, at the top of the screen or the card.

   IT IS A CAPTION AND NOTHING MORE. One line at var(--type-caption), secondary rather
   than tertiary so it does not read as a second "Not designed yet" eyebrow, and no box,
   no colour of its own and no icon: it is a note about scope, not a state of the
   product, and it must never outrank the thing it sits above. The strings themselves
   are in build/lib.py. */
.scout-stretch {
  font: var(--type-caption); color: var(--text-secondary);
  margin: 0 0 var(--sp-12); max-width: 78ch;
}
/* On a blank screen it follows the "Not designed yet" eyebrow, and without air between
   them the two captions read as one paragraph. Found by looking at the render. */
.scout-page__eyebrow + .scout-stretch { margin-top: var(--sp-12); }

/* A LOCKED CONTROL AND ITS LABEL ARE ONE ROW. Added in the same pass. Data sources (N1)
   is the first blank screen to carry five locked controls stacked in a column, and at
   the act row's shared 12px gap a "Not connected" label sat as far from the control it
   belongs to as from the next one down. The gap between controls opens to 20 and the
   label is pulled back to 12 under its own control, so a label always reads as belonging
   to the thing above it. Nothing else moves: on every other blank screen the locked
   control is the last one on the page and there is nothing under it. */
/* SCOPED 10 September 2026 to rows that actually carry a locked label (corrections
   ruling A1). The verb-row rule puts 12 pixels between buttons, along the row and
   between its lines; this 20 exists only so a "Not connected" caption reads as
   belonging to the control above it rather than to the next one down, so it binds
   only where such a caption exists. On a blank screen with no locked control (One
   trade, whose five doors wrap onto two lines) the row is back to 12. */
.scout-page .scout-acts:has(.scout-lock) { row-gap: var(--sp-20); }
.scout-page .scout-lock { margin-top: calc(var(--sp-8) * -1); }
/* On the desk, where the caption sits inside a tonal container rather than at the top of
   a page. The waiting band draws its own 24 of inline padding and its head sits above the
   caption, so the caption needs no inset of its own: it starts on the same left edge as
   "Waiting on you". A little air above it, because the head has 12 under it already. */
.r10bar > .scout-stretch { margin: var(--sp-4) 0 var(--sp-12); }
/* On the assistants card the caption sits under the card head and above the card's own
   line, which carries a negative top margin of its own, so the caption takes the space. */
.r11card > .scout-stretch { margin-bottom: var(--sp-16); }
/* On the approval card inside the conversation. */
.scout-approve > .scout-stretch { margin-bottom: var(--sp-16); }


/* ============ 15. A VERB ROW NEVER SQUASHES ITS BUTTONS ============
   THE RULING: corrections of 10 September 2026, A1, in ../research/13-SHANE-CORRECTIONS-10-SEP-RULINGS.md.

     A verb row never squashes its buttons. If the buttons do not fit the row at
     natural width, the row wraps to stacked full-width buttons, in order, 12px
     apart. Modal width and padding do not change to fit buttons. Every button in
     a verb row is --control-h (44px).

   A VERB ROW IS A ROW OF BUTTONS THAT ACT ON THE THING IN FRONT OF THE PERSON. The
   selectors below were found by measuring, not by guessing: every screen in the build
   was loaded at 1440 by 900 and every element whose own children are two or more
   buttons was listed with its class, its buttons' rendered widths and their widths
   with the label held on one line. That sweep found ten row classes and exactly ONE
   squashed row in the whole build, the licence question's answers, which is the case
   the ruling names.

   WHAT "NEVER SQUASHES" IS, IN CSS. min-width: max-content floors a button at the
   width its own label needs on one line, and white-space: nowrap stops the label
   wrapping inside it; flex-shrink: 0 stops the row taking width back off it. So a
   button either sits at its natural width or wider, never narrower, at every window
   size, with no measurement in a script anywhere.

   WHAT WRAPS, AND WHAT IT LOOKS LIKE. flex-wrap: wrap moves a button that will not
   fit onto the next line. On .tq-choices, the flow's answer rows, the buttons also
   share the row equally (flex: 1 1 0, floored by the same min-width), which is what
   that row already did as a two-column grid: so a row that fits reads exactly as it
   did, and a button that has to wrap takes the full width of its own line.

   THE HONEST LIMIT, stated rather than hidden. CSS cannot ask "did this row fit?" and
   then restyle every button in it, so "the row wraps to STACKED full-width buttons"
   is true button by button (a wrapped button fills its line) and not all-or-nothing
   (two buttons that do fit still share the first line). Nothing in the build hits the
   difference today. Reverse or replace by rewriting this block.

   HEIGHT. Every button in a verb row is 44. This deliberately overrides the 40px
   secondary and the 38px quiet button INSIDE A VERB ROW only: mixed heights in one
   row of verbs read as a mistake. Outside a verb row (the pre-login card's stacked
   alternates, which is where Stripe's 40 was measured) section 3's heights stand. */

.scout-acts,
.modal-acts,
.vp-acts,
.ie-acts,
.o-welcome__foot,
.public-actions,
.dm-panel__foot,
.scout-approve__verbs,
.tq-choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-buttons);
}

.scout-acts > .scout-act,
.scout-acts > .o-btn,
.modal-acts > .o-btn,
.vp-acts > .o-btn,
.ie-acts > .ie-act,
.ie-acts > .o-btn,
.o-welcome__foot > .o-btn,
.o-welcome__foot > .o-welcome__dismiss,
.public-actions > .o-btn,
.dm-panel__foot > .o-btn,
.scout-approve__verbs > .o-btn,
.tq-choices > .tq-choice {
  height: var(--control-h);
  min-height: var(--control-h);
  min-width: max-content;
  white-space: nowrap;
  flex-shrink: 0;
}

/* The answer row shares itself out equally, floored at each button's own natural
   width, which is what the two-column grid it replaces did. A button that cannot fit
   the line takes the whole of the next one. */
.tq-choices > .tq-choice { flex: 1 1 0; }


/* ============ 16. THE TOP BAR, LOCKED, AND THE RAIL'S ONE LABEL ============
   THE RULING: corrections of 10 September 2026, B6 to B9.

   B6. THE REGISTERED CHIP LEAVES THE TOP BAR. It is a fact that changes once, in a
   bar that is seen every minute. It is drawn instead where it is being used: the
   profile menu's company line, the setup card's first row, the Organisation page and
   the public page. Nothing here draws it; the removal is in build/shell.py (topbar)
   and build/02_copy_built.py (dress_topbar), because the chip was written into the
   markup rather than styled into it.

   B7. NEEDS YOU IS RED, AND ABSENT AT ZERO. One state, not two: a two-state badge
   (amber then red) needs a definition of urgent that the product does not have yet.
   The red is the design system's own danger fill, --danger-fill, with --text-onFill
   on it; it is the same red on the top bar's pill and on the desk section's own
   count, so the pill and the section read as one thing. The pill's own fill is set
   where the pill is drawn (build/shell.py); the desk section's count is a class of
   the desk file, so it is set here. Absence at zero is a state, not a style, and it
   is in the scripts.

   B8. THE GLOBE IS ONE STEP SMALLER AND OPENS THE PLATFORM'S OWN LANGUAGE MENU.
   Measured before it was changed: .o-icon-btn svg draws at var(--icon-lg), which is
   24px with a 1.6 stroke, on a 40px disc. One step down the platform's own icon scale
   (components.css, "Icon scale": xs 14/1.15, sm 16/1.3, md 20/1.65, lg 24/1.6) is
   --icon-md, 20px with a 1.65 stroke. So 24px goes to 20px and the stroke goes from
   1.6 to 1.65, which is the scale's own pairing at that size, not a second decision.
   The menu itself is the platform's .o-menu with .o-menu__item rows and .o-menu__tick
   ticks, exactly as the document screens already draw it; only where it hangs is
   written here, which is what the platform's own comment says a screen owns.

   B9. THE RAIL'S COLLAPSE LABEL SHOWS ON HOVER ONLY. The platform draws it on hover
   AND on focus-within, with an opacity transition. Focus stays on the control after a
   press, so the label lingered after the pointer had gone; the transition is a second
   lingering. Both are cancelled here for this one label. */

/* B7. The desk section's own count, the same red as the pill. */
.r10bar__n {
  background: var(--danger-fill);
  color: var(--text-onFill);
}

/* B8. Where the language menu hangs. The platform's own rule for this
   (.o-prelogin .lang / .lang-menu in components.css) is scoped to the pre-login
   header, so the top bar's own copy is written here, with the same numbers. */
.scout-lang { position: relative; display: inline-flex; }
.scout-lang__menu {
  position: absolute; top: calc(100% + var(--sp-8)); right: 0; z-index: 80;
  display: block;
}
/* Written out, because a display rule on a class outranks the browser's own rule for
   the hidden attribute. This build has drawn a hidden thing open twice already. */
.scout-lang__menu[hidden] { display: none; }
/* One step down the icon scale, on this control only. */
.scout-lang__btn svg {
  width: var(--icon-md); height: var(--icon-md); stroke-width: var(--icon-md-stroke);
}

/* B9. The collapse label, hover only, no fade and no focus state. Four classes, so it
   lands above the platform's own two-class :hover and :focus-within rules without
   touching any other tooltip in the product. */
.o-tip.o-rail-collapse-tip .o-tip__label { transition: none; }
.o-tip.o-rail-collapse-tip:focus-within .o-tip__label { opacity: 0; }
.o-tip.o-rail-collapse-tip:hover .o-tip__label { opacity: 1; }


/* ============ 17. YOUR ORGANISATION ============
   THE RULING: Shane's corrections of 10 September 2026, D12 and D15. "Your
   people" becomes "Your organisation", everything about the company in one
   place, and it absorbs S1, the old post-login home, which is retired as a
   destination.

   NOTHING IS DRAWN HERE THAT S1 DID NOT ALREADY DRAW. The page's three real
   blocks are lifted out of the generated S1 file at build time
   (build/03_blanks.py, build_organisation), and the rules those blocks need are
   lifted with them into a style block on the page itself, which is where a
   screen's own styles belong. All this section does is space the three blocks
   apart, which is the one thing S1's own stylesheet cannot say about a page it
   does not know exists. */
.scout-org { margin: 0 0 var(--sp-64); max-width: 720px; }
.scout-org__block { margin: var(--sp-40) 0 0; }
/* The first block's heading sits under the placeholder note above it, so it
   takes the same air a section head takes anywhere else on a blank screen. */
.scout-org__block > .proof-head { margin-top: 0; }
/* The placeholder block above drops its own bottom padding when the real blocks
   follow it, or the two are 104 pixels apart: 64 of page padding under one and 40
   of block margin over the other. */
.scout-page:has(+ .scout-org) { padding-bottom: 0; }
/* THE PAGE'S THREE SECTION HEADS ARE ONE SIZE. S1 drew "When a new buyer checks
   you" at display 2xl under a 3xl page title; here the page title is
   --type-heading at 22, so that head would outrank the title of its own page. It
   takes .proof-head's size, which is the size the other two section heads on this
   page already are. Written out rather than swapped in the markup, because the
   markup is lifted out of S1 verbatim and rewriting a class in it would make the
   two pages disagree. */
.scout-org .act-head {
  font: var(--fw-medium) var(--fs-md)/var(--lh-md) var(--font-display);
  margin: 0 0 var(--sp-12);
}


/* ============================ CONGRUENCE PASS, 12 September 2026 ============================
   Shane: "make sure everything is in congruence globally." Every rule here reaches every
   screen through this one file. The reasoning is in research/18-DESIGN-RULINGS-REGISTER.md.

   1. THE PAGE COLUMN IS THE DESK'S, ON EVERY SCREEN WITH A RAIL. The platform caps .o-main
   at --content-max INCLUDING its --gutter padding, so the content inside is 1104 wide; the
   desk overrides that in its own sheet, the other screens never did, and every page was
   132px narrower than the desk with different side padding. Pre-login screens do not use
   .o-main and are untouched; conversation mode keeps .o-main.scout-conv. */
.o-main { max-width: none; padding: var(--sp-40) var(--gutter) var(--sp-64); }
/* The bar runs the full width beside the rail, as the desk's does. The platform caps
   it at --content-max and centres it, which put the profile and the count pill 146
   pixels further in on every screen except the desk (measured 12 September: the desk's
   right-hand group ends at 1680 on a 1728 window, every other screen's at 1534). The
   page block keeps its own cap; only the bar goes wide. */
.o-topbar { max-width: none; }
.cf, .pg, .scout-page { max-width: var(--content-max); margin-inline: auto; }
/* 2. The page heading sits 88 below the bar: 40 on the column, 48 on the page block. */
.pg, .scout-page { padding-top: var(--sp-48); }
/* 3. ONE CANVAS. The public page, the ID-issued screen and the tamper check re-set
   --bg-canvas on their frame to an old surface step, so their page was a different
   black. The frame is also the drawer's containing block, so it is positioned here. */
.frame { --bg-canvas: var(--bg); background: var(--bg-canvas); position: relative; }
body { background: var(--bg-canvas); }
/* 4. NARROW-WINDOW NAVIGATION ON EVERY SCREEN. The platform hides the rail and uncaps the
   bar and page below 1390 only on shells carrying its own modifier; the shell's screens
   never carried it and kept their rail at every width. The hamburger and the drawer
   themselves come from build/shell.py. */
@media (max-width: 1390px) {
  .o-shell > .scout-rail { display: none; }
  .o-shell { grid-template-columns: 1fr; }
  .o-topbar, .o-main { max-width: none; }
}
/* 5. THE BODY LINE REACHES ROWS AND LINES, not only the paragraphs section 4 lists:
   the desk's band text, tracker text, board-card lines and setup rows. Leads inside
   them stay primary medium. */
.r10item__txt, .cf__who, .r10line, .r10arow__txt, .r12vault__m, .g-row__t, .o-guide__t {
  font: var(--type-body-secondary); color: var(--text-body);
}
.r10item__txt b, .cf__who b, .r10arow__txt b { color: var(--text-primary); font-weight: var(--fw-medium); }
/* 6. STATUS ON THE STATUS RAMP, NEVER THE BRAND TEAL (Shane, 11 September). Green is
   working, ready or on track; orange needs something from you; red is at risk or stopped.
   Counts are not statuses and keep the mint pill. The desk's tracker chips, its band's
   accent bars and its live dot all used mint for a state. */
.cf__trow .cf__chip { color: var(--status-success-text); border-color: var(--status-success-border); }
.cf__chip--you, .scout-tradecard__chip--you { color: var(--status-warning-text); border-color: var(--status-warning-border); }
.cf__chip--warn, .scout-tradecard__chip--warn { color: var(--status-danger-text); border-color: var(--status-danger-border); }
.r10item--mint::before { background: var(--status-warning-fill); }
.r10item--warn::before { background: var(--status-danger-fill); }
.cf__livedot { background: var(--status-success-fill); }
/* 7. IN-PAGE CARDS TAKE 32 INSIDE (Shane, 12 September), and the rows that bleed to a
   card's inner edge follow the same number, or every hairline stops 8px short. */
.r10bar, .r11card, .cf__tile, .cf__tile--trades { padding: var(--sp-32); }
.r10item, .r11card .cf__trow, .r10arow {
  padding-left: var(--sp-32); padding-right: var(--sp-32);
  margin-inline: calc(var(--sp-32) * -1);
}
/* 8. ROWS TAKE THE TRADES SCREEN'S VERTICAL PADDING, 24 ABOVE AND BELOW (Shane, 12
   September: "top and bottom padding on row items ... in alignment globally with the
   trades screen"). The desk's rows were 12, the assistants' 16, the vault's 16, the
   platform's lists 12 to 14. Side padding is a separate open question (bleed to the
   card edge, as the desk and assistants do, or inset with the row radius, as trades
   does) and is not changed here. */
.r10item, .r11card .cf__trow, .r10arow, .qrows li.q, .vr__row,
.o-ledger__row, .o-checklist__row, .o-filerow, .tr__row, .stub__row {
  padding-top: var(--sp-24); padding-bottom: var(--sp-24);
}
/* 9. IN-PAGE CARDS ON THE DESIGNED SCREENS TAKE THE SAME 32 as the desk's and the
   assistants' (rule 7): the kit's .card and the console's panel were 24. */
.card, .sopp-panel { padding: var(--sp-32); }
/* 10. THE VERTICAL RHYTHM INSIDE A CARD (Shane, 12 September: "fix all vertical spacing,
   the cards are messing it up"). One set of numbers: 32 above the head; head to the first
   row 12 (the row's own 24 makes 36 to its text); head to prose 20; rows 24 and 24; a card
   that ENDS in rows keeps 8 below, so the last row's 24 makes 32 to the edge instead of
   56; a card that ends in prose or a door keeps 32. Stacked cards sit 24 apart, and so
   does a search field above them. */
.r11card--tracker, .o-card.qcard, .lcard { padding-bottom: var(--sp-8); }
/* Ruled 12 September: a card head sits 16 above its first row, everywhere. It was 12,
   16, 20 and 8 across the four screen families. */
.r10bar__head, .cf__thead, .dhead, .lcard .lcard__h, .trl .tr__head,
.sopc__head, .ch__head { margin-bottom: var(--sp-16); padding-bottom: 0; }
.lcard .tr__head { padding-bottom: 0; }
.lcard { margin-bottom: var(--sp-24); }
/* A SEARCH OR FILTER ROW IS PAGE FURNITURE, and what follows it is a new region, so
   it takes 40 below rather than the 24 that separates two cards (Shane, 12 September:
   "I don't think there's enough spacing below the search bar"). It had 32 above and
   24 below on the vault, which grouped it with the list it filters rather than with
   the page, and 16 below on the assistants screen, which was a third number for one
   relationship. */
.vs, .ctl { margin-bottom: var(--sp-40); }
.lcard + .lcard { margin-top: 0; }
/* 11. A ROW'S TWO LINES, THE VAULT'S PAIR, EVERYWHERE (Shane, 12 September: "vault is
   optimal for the row main line and secondary line, but the vertical spacing between the
   two should be slightly larger"). Measured off the vault: the subject line is 500 at
   15/22 in --text-primary; the line under it is 400 at 13/18 in --text-tertiary. The two
   line boxes were touching, so the second line now takes 4 above it. Context appended to
   a subject line on the SAME line stays regular in --text-secondary, which is how the
   assistants row and the desk's band draw a compound line. The trades screen was the
   outlier at 16/24; its other columns are attributes, not the row's subject, so they take
   the size and colour but not the weight. */
.vr__what, .vg__who, .tr__who, .cf__who {
  font: var(--fw-medium) var(--fs-base)/var(--lh-base) var(--font-ui);
  color: var(--text-primary);
}
.tr__latest, .tr__lands {
  font: var(--fw-regular) var(--fs-base)/var(--lh-base) var(--font-ui);
  color: var(--text-primary);
}
.vr__what small, .vg__who small, .tr__row small, .cf__tsub, .q__s {
  display: block; margin-top: var(--sp-4);
  font: var(--type-caption); color: var(--text-tertiary);
}
/* 12. NO STROKE ON TOP OF A CARD, AND A LIST LIVES IN ONE (Shane, 12 September: "the
   lists on a card work better, but remove the stroke/border on top").

   THE STROKE. --elev-2 is "0 1px 0 0 var(--neutral-700) inset" plus a drop, so every
   surface drawn with it carries a 1px light line across its top edge: a raised-button
   highlight, on a card. The drop stays, the highlight goes. Ruled here rather than on the
   token, because --elev-2 is also the recipe for controls that should keep their highlight.

   THE LIST. A list sits in a card, not on the canvas: the trades table and the vault's
   sections join the desk's band, the tracker and the approvals queue. The card is the page
   column, so its edges line up with the page title and the page's action button; the rows
   bleed to its inner edge and are separated by hairlines, with no hairline above the first
   one, which would read as a second line under the card head. */
.r10bar, .r11card, .cf__tile, .o-card.dcard, .card, .sopp-panel, .trl, .vl {
  box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
}
.trl, .vl {
  background: var(--bg-surface); border-radius: var(--radius-card);
  padding: var(--sp-32) var(--sp-32) var(--sp-8); margin: 0 0 var(--sp-24);
}
.trl .tr__row {
  border-radius: 0; margin-inline: calc(var(--sp-32) * -1);
  padding-left: var(--sp-32); padding-right: var(--sp-32);
  border-top: 1px solid var(--border-subtle);
}
/* The vault hangs its separator on the list ITEM, so the item is what bleeds to the
   card's inner edge; the row inside it keeps its own padding and takes none of its own. */
.vl > li { margin-inline: calc(var(--sp-32) * -1); padding-inline: var(--sp-32); }
.vl .vr__row { border-radius: 0; }
/* The vault draws its separators as a bottom border on each item and a top border on
   the flat list, so it needs the opposite treatment: the list's own top rule goes, and
   so does the last item's, which would otherwise sit a hairline above the card's foot. */
.vl--flat { border-top: 0; }
.vl > li:last-child { border-bottom: 0; }
.trl .tr__head { padding: 0 0 var(--sp-12); margin: 0; }
.trl .tr__head::after { display: none; }
/* No hairline above the first row of a list, in any card. */
.trl ul > li:first-child .tr__row,
.r11card .cf__trow:first-of-type, .qrows li.q:first-child,
.r10bar__items > .r10item:first-child { border-top: 0; }
/* 13. THE LAST FOUR RULINGS OF 12 SEPTEMBER.
   THE PAGE'S ACTION BUTTON IS SECONDARY, on every screen: outlined, not filled. The page
   already has one primary act in the reading order, which is the work in front of you;
   a filled button at the top right competes with it.
   A ROW'S DOOR. Where the row opens the object itself, the row is the door and carries a
   chevron alone. Where it leads to an ACT on the object, it carries a named door with the
   verb, at medium weight, because a door is a control and regular reads as body text.
   The console's doors were regular. */
.pg__head .btn--p, .ph .btn--p {
  background: transparent; color: var(--text-primary);
  box-shadow: 0 0 0 1px var(--border-input); height: var(--control-h-secondary);
}
.pg__head .btn--p:hover, .ph .btn--p:hover { background: var(--bg-hover); }

/* THE PAGE'S ACTION CARRIES A PLUS (Shane, 12 September). It keeps its border, which
   is what says it can be pressed now that the fill is off it, and the plus says what
   kind of pressing: on a page whose top right also holds filter chips and a view
   switch, all outlined, the symbol is what tells the action apart from the state.
   Drawn as a mask rather than as markup so it reaches all three pages from here and
   takes the button's own colour, hover included. */
.pg__head .btn::before, .ph .btn::before {
  content: ""; width: 16px; height: 16px; flex: none; margin-right: var(--sp-8);
  background: currentColor;
  -webkit-mask: var(--icon-plus) center / contain no-repeat;
  mask: var(--icon-plus) center / contain no-repeat;
}
.sopc__door, .sopp__act, .sop__bar-quiet a {
  font: var(--fw-medium) var(--fs-sm)/var(--lh-sm) var(--font-ui);
}
/* 14. NO STATUS PILLS. STATE IS WRITTEN, NOT ENCLOSED (Shane, 12 September).
   Every state the product shows is now the words themselves in the state's colour:
   green in good order, orange needs something, red wrong or stopped. The pill shape
   is gone from the trades tracker, the desk's board cards, the trade card and the
   assistants grid. The count pill is not a status and keeps its fill.
   WHY: a pill promises a small closed set, and half this product's states are
   sentences with numbers in them ("Expires in 7 days", "A copy did not match"),
   which a pill cannot hold without going ragged. One form for all of them. */
.cf__chip, .tr__chip, .scout-tradecard__chip, .chip, .tag, .vr__flag {
  border: none; border-radius: 0; padding: 0; background: none;
  font: var(--type-caption); white-space: nowrap;
}
/* A state with no modifier IS the good state on the tracker and the grid, so those
   default to green. The vault's flag is not a state: unmodified it is a plain date
   ("Expires 2 October"), which was reading as green, that is, as good news about a
   certificate running out. Unmodified, it is tertiary. */
.cf__chip, .tr__chip, .scout-tradecard__chip, .chip, .tag {
  color: var(--status-success-text);
}
.vr__flag { color: var(--text-tertiary); }
/* ONE SIZE FOR A STATE, AND IT IS THE CAPTION STEP. The same statement was two
   sizes across two pages: the trades screen at the body line, the vault's flags at
   --type-caption, 13 on 18. Shane took the smaller one (12 September), which is the
   design system's own caption token, so a state now reads as an attribute of the row
   rather than competing with the row's subject. The vault's empty flag cell keeps no
   colour of its own. */
.vr__flag--warning { color: var(--status-warning-text); }
.vr__flag--danger { color: var(--status-danger-text); }
.cf__trow .cf__chip { color: var(--status-success-text); }
.cf__chip--you, .tr__chip--you, .scout-tradecard__chip--you, .chip--warn {
  color: var(--status-warning-text);
}
.cf__chip--warn, .tr__chip--warn, .scout-tradecard__chip--warn, .chip--off {
  color: var(--status-danger-text);
}

/* 15. A CARD TITLE HAS TWO SIZES, AND WHICH ONE IS A QUESTION OF WHAT IT IS
   (Shane, 12 September). A title that NAMES a thing (an assistant, a shipment, a
   module) takes 19 on 28. A title that LABELS a list ("Ready for your approval",
   "Needs you", "Trade tracker") takes 15 on 22. Same distinction the register
   already draws between an object and a queue. */
.gc__t, .sopc__h {
  font: var(--fw-medium) var(--fs-lg)/var(--lh-lg) var(--font-display);
  color: var(--text-primary); letter-spacing: var(--tracking-heading);
}
.dk, .cf__k, .r10bar__t, .lcard__h, .ch__who {
  font: var(--fw-medium) var(--fs-base)/var(--lh-base) var(--font-display);
  color: var(--text-primary);
}
/* ============ 16. THE TOP BAR'S CONTROLS AND ITS TWO MENUS ============
   Ruled by Shane on 12 September, walking the product. Six changes, each one a
   thing that was too loud or too quiet.

   1. THE NEEDS YOU PILL'S EDGE GOES QUIETER. It carried --border-default, the
   strongest hairline in the system, around a control whose job is to be noticed
   by its red count, not by its outline. The raised fill already separates it. */
.scout-pill { border-color: var(--border-subtle); }
.scout-pill:hover { border-color: var(--border-default); background: var(--bg-raised); }

/* 2. THE GLOBE'S HOVER BECOMES VISIBLE. --bg-surface against the canvas is a step
   of about two per cent; the control looked dead on hover. --bg-raised is the step
   the pill beside it already uses at rest, so the two controls agree. */
.o-icon-btn:hover { background: var(--bg-raised); }

/* 3. THE NAME AND AVATAR BECOME VISIBLY A CONTROL. They had no hover at all, so the
   one thing in the bar that opens a menu looked like a label. It takes the globe's
   treatment, so both controls in the bar behave identically, plus the role lifting
   one step, which is the cheapest possible second signal. */
/* No fill behind it: the avatar's edge lighting up is enough, and hovering either
   the icon or the name lights the same edge (Shane, 12 September). A rectangle
   appearing behind a name that is already sitting on the bar was the louder of the
   two ways to say the same thing. */
.scout-topbar .o-avatar {
  transition: border-color var(--motion-fast) var(--motion-ease);
}
.scout-who--btn:hover .o-avatar { border-color: var(--border-hover); }

/* 4. THE LANGUAGE MENU LOSES ITS BORDER. The platform draws .o-menu with
   --border-default, which on this canvas is a hard rectangle around two words. It
   takes the same treatment as the profile menu below: a surface step, no edge. */
.scout-lang__menu.o-menu {
  border: none; background: var(--bg-raised); border-radius: var(--radius-card);
  box-shadow: none; padding: var(--sp-8);
}
.scout-lang__menu .o-menu__item { border-radius: var(--radius-row); }
.scout-lang__menu .o-menu__item:hover { background: var(--bg-surface); }

/* 5. THE PROFILE MENU IS LIGHTER THAN THE PAGE, AND HAS NOTHING DRAWN AROUND IT.
   It was --bg-modal, which resolves to the canvas colour, so a menu floating over
   the page was the same black as the page and only a shadow said otherwise. The
   surface step does that job now, and the shadow and the border both go. */
.scout-menu {
  background: var(--bg-raised);
  border: none;
  box-shadow: none;
}

/* 6. THE IDENTITY BLOCK: THREE LINES, THEN THE REGISTERED COMPONENT. The name and
   the role shared a line and the state was a teal pill of its own invention. The
   name is its own line, the role is under it, the company under that, and the state
   is the product's own registered block, compact: its icon, the state, the
   qualifier. No pill anywhere. */
.scout-menu__who { font: var(--type-label); color: var(--text-primary); }
.scout-menu__role {
  display: block; margin-top: 2px;
  font: var(--type-caption); color: var(--text-tertiary); white-space: nowrap;
}
.scout-menu__co {
  display: block; margin-top: var(--sp-8);
  font: var(--type-caption); color: var(--text-secondary); white-space: nowrap;
}
.scout-seal { margin-top: var(--sp-8); gap: var(--sp-8); align-items: center; }
/* REGISTERED AND VERIFIED ARE TEAL WITH A WHITE MARK (Shane, 13 September 2026).
   They were both green, on the status ramp. This is the one place the ramp does not
   apply, and the reason is worth keeping: the ramp colours the STATE OF A PIECE OF
   WORK, green working, orange wanting something, red failed. Registered and Verified
   are not the state of any work; they are who the company is, which is the thing this
   product's accent is for. The mark beside it goes white so the line reads as an
   identity rather than as a second green signal. */
.scout-seal .o-seal__ring { display: inline-flex; color: var(--text-primary); }
.scout-seal .o-seal__ring svg { width: 18px; height: 18px; stroke-width: 1.6; }
.scout-seal .o-seal__txt strong {
  font: var(--fw-medium) var(--fs-caption)/var(--lh-caption) var(--font-ui);
  color: var(--brand-accent);
}
.scout-seal .seal-qual { font-weight: var(--fw-regular); color: var(--text-tertiary); }
.scout-menu__rule { margin-top: var(--sp-16); }
/* ============ 17. THE DATA RULES CONSOLE JOINS THE CARD RULES ============
   Its module cards were 20/24/52 inside while every other card in the product is 32,
   and the lines describing what each module is, and what setting it switches on, were
   written at the caption step when they are running text. Both corrected here rather
   than in the screen, so the console follows the same rules as everything else. */
.sopc { padding: var(--sp-32); }
.sopc__sets, .sopc__on, .sopc__later { font: var(--type-body-secondary); }
.sopc__sets { color: var(--text-body); }
.sopc__on { color: var(--text-body); }
.sopc__later { color: var(--text-tertiary); }
.sopc__on span, .sopc__later span { font: var(--type-caption); color: var(--text-tertiary); }
/* ============ 18. THE SEARCH FIELD, ONE THING EVERYWHERE ============
   Ruled by Shane, 12 September, walking the product: the vault's search had an icon
   and the system's full height, the assistants' had neither because this session
   shrank it. The vault's is the correct one, so it becomes the rule rather than the
   exception: the system's input, at --control-h with --type-body, its placeholder in
   --text-tertiary (already the platform's own rule), an icon at the left and the text
   cleared past it.

   THE ONE DEPARTURE, AND IT IS DELIBERATE. The ends are round, --radius-search, where
   every other control in the system is --radius-control at 6. The system already has
   a precedent for this: --radius-row is 10 rather than 6, "deliberately NOT
   --radius-control, which the ruling measured as reading too tight for a 36px row".
   A search field is a different control from a field you fill in, and the round end
   is what says so. Recorded as a named token rather than typed onto a screen. */
.o-search { position: relative; display: block; }
.o-search__ico { position: absolute; left: var(--sp-16); top: 50%;
  transform: translateY(-50%); width: 16px; height: 16px; stroke-width: 1.6;
  color: var(--text-tertiary); pointer-events: none; }
.o-search .o-input, input[type="search"].o-input {
  border-radius: var(--radius-search);
  height: var(--control-h);
  font: var(--type-body);
  padding-left: var(--sp-40);
}
.vs .o-input { border-radius: var(--radius-search); }
/* ============ 19. A CARD'S TOP AND BOTTOM ARE EQUAL, IN WHAT YOU SEE ============
   Shane, 12 September: "why do the cards have more top padding than bottom padding?"
   The vault's list card was 20 top and 8 bottom, which is not the same, and the 8 was
   not arbitrary: a row carries its own 24, so 8 of card padding plus the row's 24 is
   the 32 every card is supposed to show. The 20 was this session's own mistake.

   THE RULE, stated as what the reader sees rather than as a padding value:
   the space from a card's edge to the first thing inside it is 32, and to the last
   thing inside it is 32. Where that thing is a ROW, the row already owns 24 of it, so
   the card's own padding is 8. Where it is a head, prose or a door, which own nothing,
   the card's padding is the full 32.

     card that starts with a head, ends with rows   ->  32 top, 8 bottom
     card that starts and ends with rows            ->   8 top, 8 bottom
     card of prose only                             ->  32 top, 32 bottom          */
.trl { padding: var(--sp-32) var(--sp-32) var(--sp-8); }
.vl { padding: var(--sp-8) var(--sp-32); }
.r10bar, .o-card.qcard { padding: var(--sp-32) var(--sp-32) var(--sp-8); }
/* ============ 20. THE GROUP ROW IS A ROW ============
   Shane, 12 September: the "Needs attention" card was right and every card below it
   was short at the top. The cause: those cards begin with a shipment's group line,
   which carried 12 of its own padding where every other row carries 24, so the card's
   8 plus the row's 12 came to 20 instead of 32. A group line is a row. */
.vl > li.vgl .vg {
  padding-top: var(--sp-24); padding-bottom: var(--sp-24);
}

/* ============ 21. WHAT A FIELD DOES WHEN YOU TOUCH IT ============
   Ruled by Shane, 12 September, from the desk's composer, which is the one input in
   the product whose states he likes. The composer carries no hover at all and its
   active state is the quietest possible: the ring lifts from --border-default to
   --border-hover, and nothing else moves. No teal, no halo.

   THE TEAL GOES. --focus-halo is a 1px teal ring plus a 4px teal glow at 25 percent.
   On a dark field it reads as an alarm rather than as a cursor landing.

   THE RING STAYS A SHADOW, not a border, because design system v2 draws it that way
   so it costs no height. Only the colours change.

   ACCESSIBILITY, checked rather than assumed: --border-hover (#526675) against the
   field's own surface (#050B10) is 3.3 to 1, which clears the 3 to 1 minimum a
   non-text interface component needs. It is a smaller margin than the teal had, and
   it is the reason the ring is not taken any quieter than this. */

/* Every field: no hover, and the composer's active state.
   THE :not(:focus) IS LOAD-BEARING. The platform's hover rule carries three classes
   of specificity and the focus rule two, so without it a field you are typing in
   loses its active ring the moment the pointer happens to rest over it. Caught by
   measuring a focused field and finding the resting colour. */
.o-input:hover:not(:disabled):not(.is-error):not(:focus) {
  box-shadow: 0 0 0 1px var(--border-input);
}
/* THE FOCUS RULE HAS TO OUTRANK THE PLATFORM'S HOVER RULE, not just this file's.
   The platform draws hover at three classes of specificity and focus at two, so a
   two-class focus rule loses to it and a field clicked while the pointer is still
   over it stays in its hover state until the pointer leaves. Adding :not(:focus) to
   the hover overrides in this file was not enough, because the platform's own hover
   rule is still there and still matches. This one matches its weight and, being
   later, wins. (Shane, 12 September: "it should become active when I click it first,
   not when I click it and my mouse is no longer hovering".) */
.o-input:focus:not(:disabled):not(.is-error),
.o-input:focus-visible:not(:disabled):not(.is-error) {
  outline: none;
  box-shadow: 0 0 0 1px var(--border-hover);
}

/* A search field keeps a hover, because it is a control you hunt for rather than one
   you tab into: the ring lifts one step on the way, exactly as it does today. */
.o-search .o-input:hover:not(:disabled):not(:focus),
.vs .o-input:hover:not(:disabled):not(:focus) {
  box-shadow: 0 0 0 1px var(--border-default);
}
/* ============ 22. THE PAGE CONTROL BAR ============
   One bar for every list page: search on the left, the filters centred in what is
   left between it and the controls on the right, and sort and the view switch on the
   right. Ruled by Shane on 12 September, from the assistants screen, and applied to
   the vault and the trades list so all three read as one product.

   THE FILTER CHIPS ARE CONTROLS, so they are sized as controls, 36 tall on the
   label's type, not as the 12px labels they were. The selected one takes the brand
   accent: the design system reserves teal for "links, the accent and the active
   state", and a chosen filter is an active state, not a status, so this is the one
   place on a list page where teal is still right. */
.pgctl { display: flex; align-items: center; gap: var(--sp-16);
  margin: 0 0 var(--sp-40); }
.pgctl__search { flex: 0 0 320px; }
/* A search that sits inside the bar has no margin of its own; the bar owns the gap.
   The vault's form keeps its own class because its live search script holds on to
   it, and that class carries a 40 bottom margin for when it stands alone. */
.pgctl .vs { margin: 0; }
.pgctl__filters { flex: 1 1 auto; display: flex; justify-content: center;
  flex-wrap: wrap; gap: var(--sp-8); }
.pgctl__right { flex: 0 0 auto; display: flex; align-items: center; gap: var(--sp-8); }

.pgchip { display: inline-flex; align-items: center; gap: 6px; height: 36px;
  padding: 0 var(--sp-16); border-radius: var(--r-full);
  border: 1px solid var(--border-subtle);
  font: var(--type-label); color: var(--text-secondary);
  text-decoration: none; white-space: nowrap;
  transition: border-color var(--motion-fast) var(--motion-ease),
              color var(--motion-fast) var(--motion-ease); }
.pgchip:hover { border-color: var(--border-default); color: var(--text-primary); }
.pgchip b { font-weight: var(--fw-regular); color: var(--text-tertiary); }
.pgchip--on { border-color: var(--teal-700); color: var(--teal-400); }
.pgchip--on b { color: var(--teal-400); }
.pgchip:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

.pgsort { display: inline-flex; align-items: center; gap: var(--sp-4); height: 36px;
  padding: 0 var(--sp-12); border-radius: var(--radius-control);
  font: var(--type-label); color: var(--text-secondary); }
.pgsort b { color: var(--text-primary); font-weight: var(--fw-medium); }
.pgview { display: inline-grid; place-items: center; width: 36px; height: 36px;
  border-radius: var(--radius-control); color: var(--text-tertiary); }
/* A SELECTED VIEW IS A STATE, NOT A HOVER, so it keeps the full step. When the
   hover token was halved on 12 September this would otherwise have gone quiet with
   it, and a chosen view has to stay chosen when the pointer is elsewhere. */
.pgview--on { background: var(--bg-raised); color: var(--text-primary); }
.pgview svg { width: 16px; height: 16px; stroke-width: 1.6; }
/* ============ 23. THE PAGE HEADING TO ITS SUBTITLE ============
   It was three numbers for one relationship: 16 on the trades list, 8 on the vault,
   10 on the assistants screen, because two of them wrap the heading and its action in
   a row and one does not, and each carried its own margin. Measured 12 September.
   It is --space-label, 12, everywhere: a heading and the line under it are a label
   and the thing it labels, which is the relationship that token names. Everything
   below is already common at 32 to the control bar and 40 from the bar to the
   content. */
.pg__h { margin-bottom: var(--sp-12); }
.pg__head, .ph { margin-bottom: 0; }
.pg__head .pg__h, .ph .pg__h { margin-bottom: 0; }
.pg__head + .pg__sub, .ph + .pg__sub { margin-top: var(--sp-12); }
/* THE ACTION BUTTON MUST NOT SET THE HEADING ROW'S HEIGHT. It is 44 tall and the
   heading's line box is 36, so a row holding both is 44, and 12 measured from the
   row's foot puts 20 between the two lines of text rather than 12. The button comes
   out of the flow and is centred on the heading instead, so the row is exactly as
   tall as the heading and the 12 is 12. The heading reserves room on its right so a
   long title cannot run under the button. */
.pg__head, .ph { position: relative; align-items: center; }
.pg__head > .btn, .ph > .btn {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
}
.pg__head > .pg__h, .ph > .pg__h { padding-right: 220px; }


/* ============ 24. THE CONVERSATION LIVES IN THE RAIL ============
   Ruled by Shane, 12 September 2026: the session history and New conversation belong
   in the product's own sidebar, on every screen, not in a sidebar that only exists
   inside a conversation. The markup is in build/shell.py; the shapes are here.

   The rows are the ChatGPT measurement decision D-185 already took (36 tall, 14 on 20,
   regular, a 10px radius, no see-more), which is why those numbers are not re-derived.
   The rail's own item spacing and hover come from the platform; a recent is a quieter
   thing than a destination, so it starts at secondary and comes up on hover. */
.scout-rail__rule {
  height: 1px; background: var(--border-subtle);
  margin: var(--sp-16) var(--sp-12);
}
.scout-rail__label {
  /* 8 BELOW, NOT 4 (Shane, 13 September: "not enough spacing between Recents and
     the list below"). The rows under it are 36 tall on a 20 line box, so 8 of box
     lands as 16 of ink between the label and the first title, which is the gap a
     label and the thing it labels take everywhere else in this product. */
  margin: var(--sp-16) 0 var(--sp-8); padding: 0 var(--sp-12);
  font: var(--type-caption); color: var(--text-tertiary);
}
.scout-rail__recent {
  /* CENTRED IN ITS OWN BOX (Shane, 13 September 2026: on hover "the text appears at
     the top of the background, the text should be vertically centered"). The row set
     a 36px line-height and then the font shorthand on the next line reset it to 20,
     so the 36px box kept its height while its text went back to sitting on a 20px
     line at the top of it. Nothing showed until the hover painted the box. Centring
     the row rather than restoring the line-height, so the fix holds whatever the
     title's own line box turns out to be. */
  display: flex; align-items: center; height: 36px; padding: 0 var(--sp-12);
  border-radius: var(--radius-row);
  font: var(--fw-regular) var(--fs-sm)/var(--lh-sm) var(--font-ui);
  color: var(--text-secondary); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.scout-rail__recent > span {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scout-rail__recent:hover { color: var(--text-primary); background: var(--bg-hover); }
.scout-rail__recent.is-active { color: var(--text-primary); background: var(--bg-surface); }
.scout-rail__recent:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
/* Collapsed, the rail is 64 wide and a conversation title cannot live there. The
   block goes with the labels, exactly as the platform hides an item's own label. */
.o-shell.is-collapsed .scout-rail__label,
.o-shell.is-collapsed .scout-rail__recent,
.o-shell.is-collapsed .scout-rail__rule { display: none; }

/* ============ 25. THE DRAFT IS PINNED ABOVE THE COMPOSER ============
   Ruled by Shane, 12 September 2026, against Claude Code's own multiple-choice card:
   "if there is a draft reply generated, it should appear in a card fixed to the chat
   box below it... apply standard card design rules and tokens... use smaller buttons
   and make them somewhat subtle."

   It was the last block IN the thread, so it scrolled with the conversation and the
   screen had to be opened pre-scrolled to keep the verbs in reach. Pinned above the
   composer it is always in reach and the thread is free to scroll behind it.

   The card takes the register's own card rules: --bg-surface, radius 12, no border,
   the board's drop, 24 inside (a card inside a column, like the setup steps). Its head
   is the card-head size, its consequence line the caption, and its three verbs are
   small quiet controls rather than the full-height button set: a standing draft must
   not out-shout the composer under it. */
/* CORRECTED 12 September 2026, after Shane: "match the background colour to that of
   the cards on the desk... assess font size, weight and colour... assess padding,
   spacing. Apply the same rules that we have applied to other cards."

   The card's own fill was already --bg-surface, the same as every card on the desk.
   What made it read wrong was the FIELD inside it: it was --bg-canvas, DARKER than the
   card and the same colour as the page behind, so the card read as a frame around a
   hole. A field inside a card goes one step UP, not down, which is the same
   separate-by-one-surface-step rule the nested setup cards and the menus already use.

   The rest is brought to the register's own card numbers: 32 inside, not 24; 20 from
   the head to the field, 24 from the field to the foot; and the verbs at the caption
   step, 13 on 18, rather than the 12 they were, because 12 was a size this product
   does not otherwise use on a control. */
.scout-conv__foot .scout-approve {
  background: var(--bg-surface); border: none; border-radius: var(--radius-card);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
  padding: var(--sp-32); margin: 0 0 var(--sp-16);
}
.scout-approve__head { display: flex; align-items: flex-start; gap: var(--sp-12); }
.scout-conv__foot .scout-approve__h {
  flex: 1; min-width: 0; margin: 0;
  font: var(--fw-medium) var(--fs-base)/var(--lh-base) var(--font-display);
  color: var(--text-primary);
}
.scout-approve__x {
  flex: none; display: grid; place-items: center; width: 24px; height: 24px;
  padding: 0; border: 0; background: none; cursor: pointer;
  border-radius: var(--radius-control); color: var(--text-tertiary);
}
.scout-approve__x svg { width: 15px; height: 15px; stroke-width: 1.6; }
.scout-approve__x:hover { color: var(--text-primary); background: var(--bg-hover); }
.scout-approve__x:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* THE DRAFT IS A FIELD, AND A FIELD IN THIS PRODUCT LOOKS ONE WAY (Shane, 12
   September). Measured across the build: the trades search, the vault search and every
   field on Add a trade are all --bg-canvas with a 1px --border-input ring, not inset.
   This one was --bg-raised with an inset ring, the only field in the build that did not
   match. It reads darker than the card it sits in now, which is what a field should do,
   and the reason that looked wrong on the first attempt was the card around it, not the
   field: the card was 24 inside with no head or foot, so the field filled it and the
   pair read as a hole. The card is 32 with both now. */
.scout-conv__foot .scout-approve__draft {
  display: block; width: 100%; margin: var(--sp-20) 0 0; resize: none;
  padding: var(--sp-16);
  background: var(--bg-canvas); border: none;
  box-shadow: 0 0 0 1px var(--border-input);
  border-radius: var(--radius-control);
  font: var(--type-body-secondary); color: var(--text-primary);
}
/* The focus ring lifts one step and nothing else moves, which is the composer's own
   behaviour and the rule in the register (section 5d). */
.scout-conv__foot .scout-approve__draft:focus {
  outline: none; box-shadow: 0 0 0 1px var(--border-hover);
}
.scout-approve__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-24); margin-top: var(--sp-24);
}
.scout-conv__foot .scout-approve__conseq {
  margin: 0; min-width: 0; font: var(--type-caption); color: var(--text-tertiary);
}
.scout-approve__verbs { display: flex; gap: var(--sp-8); flex: none; }
/* SMALL AND SUBTLE. 30 tall against the system's 44, the label at the caption step,
   a quiet ring rather than a fill; the one that commits comes up to primary text and
   a brighter ring, and still carries no fill. */
.scout-approve__v {
  height: 30px; padding: 0 var(--sp-12); border: 0; cursor: pointer;
  border-radius: var(--radius-control); background: none;
  box-shadow: inset 0 0 0 1px var(--border-subtle);
  font: var(--fw-medium) 13px/18px var(--font-ui);
  color: var(--text-secondary);
}
.scout-approve__v:hover { color: var(--text-primary); box-shadow: inset 0 0 0 1px var(--border-default); }
.scout-approve__v:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.scout-approve__v--go { color: var(--text-primary); box-shadow: inset 0 0 0 1px var(--border-default); }
.scout-approve__v--go:hover { box-shadow: inset 0 0 0 1px var(--border-hover); }

/* ============ 26. THE COMPOSER OPENS THE CONVERSATION, SMOOTHLY ============
   Ruled by Shane, 12 September 2026: "a prompt entered into the composer on the desk
   should automatically trigger full screen. The transition to full screen should be
   animated and smooth."

   Two halves of one move. On the desk, pressing the composer fades the board away and
   lifts the composer as the page leaves. On the conversation, the column arrives by
   rising the same distance. Both are 200ms on the platform's own easing, and both are
   off for a reader who has asked for less motion. */
.scout-leaving .cf, .scout-leaving .r10bar, .scout-leaving .r11cols,
.scout-leaving .o-guide, .scout-leaving .r2-hello, .scout-leaving #scout-voice {
  opacity: 0; transition: opacity 180ms var(--motion-ease);
}
.scout-leaving .dm-dock, .scout-leaving .cp {
  transform: translateY(-12px);
  transition: transform 200ms var(--motion-ease);
}
@keyframes scout-conv-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.scout-conv__col { animation: scout-conv-in 200ms var(--motion-ease) both; }
@media (prefers-reduced-motion: reduce) {
  .scout-leaving .cf, .scout-leaving .r10bar, .scout-leaving .r11cols,
  .scout-leaving .o-guide, .scout-leaving .r2-hello, .scout-leaving #scout-voice,
  .scout-leaving .dm-dock, .scout-leaving .cp { transition: none; transform: none; }
  .scout-conv__col { animation: none; }
}


/* ============ 27. THE RAIL RESIZES BY DRAGGING ============
   Shane, 12 September 2026, once the rail took the session history: a sidebar holding
   conversation titles has to be able to get wider. The markup and the behaviour are in
   build/shell.py, which also records that this reverses the 5 August retirement of the
   old splitter and how the two reasons behind that retirement are answered.

   WHAT THE EXISTING MOTION DID TO IT, and this is the part worth keeping written down:
   the shell animates its grid columns over --motion-slow on --motion-easeBack, a curve
   that deliberately overshoots. That is right for a click landing on a named width and
   wrong for a drag, where it makes the rail lag the pointer and wobble past it. And the
   collapse control nudges the rail 5px sideways on hover, at the very edge the handle
   lives on. `is-resizing` turns both off for the length of the drag. */
.o-rail__resize {
  position: absolute; top: 0; bottom: 0; right: -12px; width: 24px;
  cursor: col-resize; z-index: 6; touch-action: none;
}
/* IT IS A SHORT PILL, NOT A LINE DOWN THE WHOLE EDGE (Shane, 12 September, pointing
   at how Claude's own sidebar does it). A full-height rule lit up the entire border and
   read as the page changing rather than as a grip you can take hold of. 4 by 40, fully
   round, centred on the edge, and it only exists on hover.

   AND THE CHEVRON STANDS DOWN WHILE IT SHOWS, because the grip does its job while it
   is up: a press that does not travel collapses, a press that travels resizes, and the
   label says both on two lines. That is Claude's own model and it is where Shane landed
   on 12 September, after trying a version where the border only resized. The chevron's
   hover nudge is untouched: reaching for the border does not reach the chevron, so the
   nudge only ever fires on the icon itself. */
.o-rail__resize::after {
  content: ""; position: absolute; top: 50%; left: 10px; width: 4px; height: 40px;
  transform: translateY(-50%); border-radius: var(--r-full);
  background: var(--border-hover); opacity: 0;
  transition: opacity var(--motion-fast) var(--motion-ease);
}
.o-rail__resize:hover::after,
.o-rail__resize:focus-visible::after,
.o-shell.is-resizing .o-rail__resize::after { opacity: 1; }
.o-rail__resize:focus-visible { outline: none; }
.o-rail__resize:focus-visible::after { background: var(--focus-ring); }
/* The chevron and its own tooltip step aside while the grip is up. */
.o-rail__resize:hover ~ .o-tip.o-rail-collapse-tip,
.o-rail__resize:focus-visible ~ .o-tip.o-rail-collapse-tip,
.o-shell.is-resizing .o-tip.o-rail-collapse-tip {
  opacity: 0; pointer-events: none;
  transition: opacity var(--motion-fast) var(--motion-ease);
}
/* The grip's own label, in the platform's tooltip idiom: the act, then what a drag
   does, because one control now does both. */
.o-rail__resize-tip small {
  display: block; margin-top: 2px;
  font: var(--type-caption); color: var(--text-tertiary);
}
.o-rail__resize-tip {
  position: absolute; left: 22px; top: 50%; transform: translateY(-50%);
  display: none; white-space: nowrap; pointer-events: none;
  padding: var(--sp-8) var(--sp-12); border-radius: var(--radius-control);
  background: var(--bg-raised);
  font: var(--fw-medium) var(--fs-sm)/var(--lh-sm) var(--font-ui);
  color: var(--text-primary); z-index: 7;
}
.o-rail__resize:hover .o-rail__resize-tip,
.o-rail__resize:focus-visible .o-rail__resize-tip { display: block; }
.o-shell.is-resizing .o-rail__resize-tip { display: none; }
/* The drag has to track the pointer exactly: no easing, and no hover nudge. */
.o-shell.is-resizing { transition: none; }
.o-shell.is-resizing > .o-rail { transition: none; margin-right: 0 !important; }
.o-shell.is-resizing, .o-shell.is-resizing * { user-select: none; }
/* Collapsed there is nothing to resize, and the drawer is neither resized nor
   collapsed: it closes with its own control. Belt and braces on top of the markup fix
   in build/shell.py, so a drawer can never paint a second chevron inside the sidebar. */
.o-shell.is-collapsed .o-rail__resize, .o-drawer .o-rail__resize { display: none; }
.o-drawer .o-tip.o-rail-collapse-tip { display: none; }


/* ============ 28. A CARD THAT ENDS IN A DOOR ============
   Shane, 13 September 2026, on the desk: "4 more See everything, doesn't have enough
   spacing above or below it", and "Open your trades, doesn't have enough spacing
   above it".

   MEASURED BEFORE CHANGING, at 1440. Both doors had 24 of ink above them, which is
   the last row's own bottom padding and nothing else. Two adjacent rows put 48
   between their texts, so a door at 24 reads as HALF a row gap from the last row:
   attached to that row rather than standing as the card's own foot. Below, the
   tracker's door had the 32 a card ending in a door is ruled to take, and the band's
   had 8, because rule 10 lists the band among the cards that END IN ROWS and the band
   ends in a door now. The band has left that list, above.

   THE RULE: a door at the foot of a card takes 32 of ink above it and 32 below, so it
   sits in a region of its own, equal on both sides, on the card's own inside number.
   The last row already owns 24 of that, so the foot adds 8. Rule 10 is unchanged for
   every card that really does end in rows. */
.r10bar { padding-bottom: var(--sp-32); }
.r10bar__foot, .r11card--tracker.trl > .cf__door, .o-card.dcard > .dfoot {
  padding-top: var(--sp-8);
}


/* ============ 29b. A MENU YOU CAN ACTUALLY REACH ============
   Shane, 14 September 2026: "I cannot keep it open. As soon as my mouse leaves the
   username and icon, the drop down disappears."

   THE FAULT, MEASURED. Both bar menus sit at `top: calc(100% + 8px)`, so there are
   eight pixels between the bottom of the control and the top of the menu. The wrap
   is `position: relative` around the control alone, and the menu is absolutely
   positioned, so it is OUT OF FLOW and does not grow the wrap's box. Measured on the
   profile menu at 1440: the control runs 15 to 47 and the menu starts at 55. Those
   eight pixels belong to neither box, so moving the pointer down to the menu leaves
   the wrap, `mouseleave` fires, and the menu closes before it can be reached.

   THE RULING IT BREAKS IS ITS OWN. Section 5j of the rulings register says the
   listener is on the wrap "which holds the control and the menu together, so crossing
   the gap between them does not count as leaving". Hovering the menu does keep the
   wrap hovered, because the menu is a descendant; the GAP is what nobody owns.

   THE FIX IS A BRIDGE, not a smaller gap: a transparent strip on the menu itself,
   spanning the eight pixels up to the control. It is a descendant of the wrap, so the
   pointer never leaves, and it changes nothing that is drawn. Both menus, because
   they are the same control twice. The visual separation is kept. */
.scout-menu::before,
.scout-lang__menu::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc(var(--sp-8) * -1);
  height: var(--sp-8);
}

/* ============ 29. ONE MENU AT A TIME IN THE TOP BAR ============
   Shane, 13 September 2026: "when the user profile dropdown is open, and I click the
   language dropdown, the language dropdown appears behind it. Only one should open at
   a time."

   TWO FAULTS, one visible and one underneath it. The visible one is stacking: neither
   menu sets a z-index, so they paint in the order the bar draws them, and the bar
   draws the language control before the person, which puts the profile menu on top of
   whatever the language menu does. The one underneath is that both could be open at
   all: each menu's script closes itself when a press lands outside it, but a press on
   the OTHER menu's button calls stopPropagation, so that press never reaches the first
   menu's listener. Closing the other one on open is the fix, in build/shell.py; this
   is the stacking half, so an open menu is on top even mid-animation. */
.scout-menu, .scout-lang__menu { z-index: 40; }
.scout-profile[data-scout-menu="open"] .scout-menu,
.scout-lang[data-scout-lang="open"] .scout-lang__menu { z-index: 41; }


/* ============ 30. THE RAIL STAYS PUT ON EVERY SCREEN ============
   Shane, 13 September 2026, on the bill-of-lading screen: "the sidebar is not
   behaving correctly when I scroll down".

   MEASURED, not guessed. Scrolled 600 on that screen, the rail's top sat at -600 and
   its foot at 300: it had scrolled away with the page, leaving the canvas bare below
   it. On the desk the same scroll leaves the rail at 0. The two screens do it
   differently, which is the actual fault: the desk's rail is FIXED, from its own
   copied stylesheet, and every other screen's is STICKY, from the platform's.

   WHY STICKY WAS FAILING, found by walking the rail's ancestors: the copied mockups
   carry a device-frame rule, `.frame { width: 1440px; height: 900px; overflow:
   hidden }`, from the days when a screen was a picture of a screen. A sticky element
   sticks inside its nearest scrollport, and an ancestor that clips becomes one, so
   the rail was sticking inside a box that never scrolls while the page scrolled
   underneath it. The desk escapes it only because a fixed element ignores a clipping
   ancestor.

   THE FIX IS THE FRAME, not the rail, because the frame is the thing that is wrong: a
   full-window screen has no device frame to clip against. With it gone the platform's
   own sticky rail works as written, on every screen, which is the register's rule that
   the shell behaves identically everywhere and the desk is not special. Verified after
   the change on all four: rail top 0 at a 600 scroll. */
.frame { overflow: visible; }


/* ============ 31. NOTHING BEHIND THE PAGE ============
   Shane, 13 September 2026: "remove overflow so when the user scrolls hard up or down
   they cannot see past the edge of the screen".

   What he is seeing is the overscroll bounce: pull past the top or the foot and the
   window rubber-bands, showing whatever is behind the page. It is not a stray
   scrollbar, so hiding overflow is the wrong tool and would cost the sticky rail its
   scrollport again (section 30). `overscroll-behavior: none` is the right one: it
   stops the bounce and the chaining without touching how the page scrolls. On the
   root because the page itself is the scroller on every screen in this build. */
html, body { overscroll-behavior: none; }


/* ============ 32. A PROMPT BUBBLE HAS NO BORDER ============
   Shane, 13 September 2026, on the conversation: remove the border from the user
   prompt bubbles. It was a 1px teal ring on a teal fill, which on this ground reads as
   an outline drawn around a shape that already has an edge.

   UNSCOPED, DELIBERATELY. The same bubble with the same ring is written a second time
   in the desk's own copied stylesheet, and reaches three more files through the
   backdrops that carry the desk as real markup. One class here, loaded after all of
   them, is what makes the bubble one thing everywhere rather than one thing on the
   screen Shane happened to be looking at. */
.dm-user__b { border: 0; }


/* ============ 33. A SURFACE THAT IS NOT OPEN YET ============
   Session P, 14 September 2026. One component, two variants, replacing the
   "Not designed yet" eyebrow on thirteen screens.

   WHAT WAS WRONG WITH THE EYEBROW, measured before it was changed. It sat above
   the page title, in the slot a breadcrumb takes, and said the screen was
   broken. Under it came a title, a sentence, and a row of buttons of which most
   did nothing: seven of the thirteen screens drew a filled primary button whose
   only behaviour was a browser tooltip reading "Not built in this pass". A
   control that does nothing is the most fault-reading object a clickthrough can
   carry, and there were nineteen of them.

   WHAT REPLACES IT. The page head is a real page head: the title, and the
   surface's own ruled sentence under it, on the same rhythm every other page
   keeps. Below that, two blocks, the second of which most screens do not have:

     the notice    the state of the surface, and one way onward. A PANEL WITH NO
                   FILL, a hairline ring, and its contents centred in a generous
                   band of space
     the contents  the surface's own named parts, where it has any, in a normal
                   filled card 24 below the notice. Row grammar is the vault's,
                   unchanged: a part reachable today is a live row and the row is
                   the door; a part that is not goes quiet and says why on its
                   right

   THE NOTICE IS UNFILLED, AND THAT IS THE WHOLE POINT (Shane, 14 September:
   "'Coming' looks like part of the interface. Make it more obvious.") The first
   build put the state line at the top left of an ordinary product card, in the
   card-title type, which is the exact shape of a label belonging to a working
   component: same fill, same radius, same drop, same corner. It read as a
   heading for a list that had failed to load rather than as a statement about
   the page. Three things now say it is not a component, and none of them is a
   new colour, a glyph or a status pill:

     no fill        every working card in this product is filled. A ring with
                    nothing behind it is a space reserved, not a thing built
     no drop        a drop is what lifts a card off the canvas. This does not lift
     centred        every title in this product is top left. Nothing centred in the
                    middle of a panel can be read as one

   THE TWO VARIANTS ARE THE SAME SHAPE AND DIFFERENT WORDS, which is what makes
   this one component rather than two. --unlocks names when the surface opens,
   because Maher's pitch is a product that opens up over thirty days and a surface
   that says so is the product working. --coming says one word, for the utility
   surfaces nobody judges a demonstration for. The modifiers carry no declarations
   today; they exist so a difference, if Shane wants one, lands in one place.

   NOTHING IN THE CONTENTS CARD IS A NEW NUMBER. It is rule 12's card, rule 19's
   padding, rule 8's rows (the selector joins that rule rather than restating 24),
   and the door is the trades card's own. */

/* THE PAGE HEAD ON A STAMPED SCREEN IS THE PAGE HEAD ON A DESIGNED ONE. The
   split the register records in section 5j: a page title is 26 on 36 medium in
   the display face on a designed screen and 22 on 36 regular in the UI face on a
   stamped one, because section 4 above puts .scout-page__h on the platform's
   heading token together with every card and modal title. Scoped to the main
   area, so it reaches a stamped page inside the product and leaves the pre-login
   card's own title and every dialog exactly as they are. Measured on N1 before:
   22px/36px Inter 400 against the vault's 26px/36px Satoshi 500 at the same
   x-position. */
.o-main .scout-page__h {
  font: var(--fw-medium) var(--fs-2xl)/var(--lh-2xl) var(--font-display);
  letter-spacing: var(--tracking-heading);
}

/* The block itself is nothing: it carries no fill, no padding and no edge, so
   the notice and the contents card below it are two objects on the page column
   rather than one object with two halves. */
.stub { margin: 0; }
/* A page with no sentence under its title (Notifications, Forgot password) puts
   the notice straight under the heading, and takes the 32 the page's rhythm gives
   the slot the sentence would have used. It is the whole 32 rather than the 20
   that would top up the heading's own 12, because two adjacent margins collapse
   to the larger of the two rather than adding: built at 20 first and measured at
   20, not 32. */
.scout-page__h + .stub { margin-top: var(--sp-32); }

/* THE RING IS THE WARNING RAMP'S (Shane, 14 September). It is the one place in
   this build where the ramp colours something that is not the state of a piece of
   work, and the reason is that nothing else says "provisional" as fast: on
   Settings the list below the notice reads as a finished design, so a neutral
   ring left the word above it ambiguous about which of the two it was describing.

   THE COLLISION, NAMED RATHER THAN IGNORED. Section 3 gives orange one meaning,
   "needs something from you", and the Needs you band owns it. Nothing collides in
   view: none of the thirteen placeholder screens carries any other orange, and
   the band lives on the desk and the Needs you screen, neither of which is one of
   them. The rule to keep is that the ramp reaches the RING of a whole panel here
   and never a row, a chip or a word, so no reader has to decide whether an orange
   thing on a list is asking them for something.

   WHICH ORANGE, measured rather than picked. Four were drawn and looked at on
   the near-black canvas at 1440. --status-warning-border (#593C08) is the
   system's own edge for a warning surface and is the semantically obvious
   choice, but it is designed to sit on that surface's own subtle fill, not on
   #000305, and against the canvas it reads as a dim brown line rather than as
   orange at all. --status-warning-text (#D9951D) reads as an alarm around a
   1106-wide panel, and the panel is not an alarm. The system's warning surface
   entire, #2D1D05 behind #593C08, is a filled block again and undoes the reason
   the notice is unfilled. --status-warning-fill (#B37900) is unmistakably the
   colour Shane asked for at a 1px weight and does not shout, so the ring takes
   it. A 6 per cent wash of it behind the ring was drawn too and is invisible at
   this size; there is no fill. */
.stub__notice {
  border: 1px solid var(--status-warning-fill);
  border-radius: var(--radius-card);
  background: none; box-shadow: none;
  padding: var(--sp-48) var(--sp-32);
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
/* Two cards on a page sit 24 apart (rule 10), and a notice and the card under it
   are two cards for that purpose. */
.stub__notice + .stub__list { margin-top: var(--sp-24); }

/* THE STATE LINE, at the size a card title takes when it names a thing: 19 on 28,
   the display face, medium, which is the assistants card's own and the console
   module's. It is the biggest thing inside the notice and the only thing most
   notices hold. */
.stub__state {
  font: var(--fw-medium) var(--fs-lg)/var(--lh-lg) var(--font-display);
  color: var(--text-primary); margin: 0; max-width: 52ch;
}
/* Inside the pre-login card the heading is the platform's 22 on 36 rather than
   the page column's 26 on 36, and a 19 under it competes with it instead of
   sitting below it. There the state line is the card's one line and takes the
   line a pre-login card gives its own: the body line, in the body colour. */
.o-prelogin .stub__state, .scout-card .stub__state {
  font: var(--type-body-secondary); color: var(--text-body);
}
/* THE BODY LINE SAYS WHAT THE DEAL IS (Shane, 14 September: "actually explain in
   body text what the deal is"). One word cannot carry the difference between a
   surface that is not built and a list under it that works, so a sentence does:
   what this surface is for, and what is true today. It is the product's own body
   line, and it takes a measure, because a centred sentence running the full 1106
   of the page column is unreadable however correct its colour is. */
.stub__body {
  font: var(--type-body-secondary); color: var(--text-body);
  margin: var(--sp-12) 0 0; max-width: 62ch;
}
/* The one way onward sits under the statement, on the group gap, centred with it.
   It is the notice's own foot rather than the contents card's, so a screen with
   parts and a way back still has exactly one door and it is in one place. */
.stub__foot { margin-top: var(--sp-24); }
.stub__door {
  font: var(--type-link); color: var(--link-default);
  text-decoration: none; white-space: nowrap;
}
.stub__door:hover { color: var(--link-hover); }

/* THE CONTENTS CARD is rule 12's list card, to the letter: the surface colour,
   radius 12, the system's drop, no border, and rule 19's 8 at top and foot
   because it both starts and ends in rows. */
.stub__list {
  list-style: none; margin: 0;
  background: var(--bg-surface); border-radius: var(--radius-card);
  box-shadow: var(--elev-drop);
  padding: var(--sp-8) var(--sp-32);
}
/* The row bleeds to the card's inner edge and hangs its own hairline, which is
   the trades list's device rather than the vault's, so a hover paints the full
   width of the card instead of stopping at the text. Its 24 above and below comes
   from rule 8, which this selector joins. */
.stub__row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto 20px;
  column-gap: var(--sp-16); align-items: center;
  margin-inline: calc(var(--sp-32) * -1); padding-inline: var(--sp-32);
  border-top: 1px solid var(--border-subtle);
  text-decoration: none; color: inherit;
}
/* No hairline above the first row: it would read as a line under the card's top. */
.stub__list > li:first-child .stub__row { border-top: 0; }
a.stub__row:hover { background: var(--bg-hover); }

.stub__what {
  font: var(--type-body-secondary); font-weight: var(--fw-medium);
  color: var(--text-primary); min-width: 0;
}
/* A part that is not reachable yet is not a door, so it does not look like one. */
.stub__row--wait .stub__what {
  font-weight: var(--fw-regular); color: var(--text-secondary);
}
.stub__flag {
  font: var(--type-caption); color: var(--text-tertiary);
  white-space: nowrap; text-align: right;
}
.stub__chev {
  color: var(--text-tertiary); font-size: 18px; line-height: 1; text-align: right;
}

/* The pre-login card is 64 inside already (section 2) and holds a 540px column,
   so a ringed panel inside it is a box in a box: the ring and the padding come
   off. The centring comes off with them, because that card's own heading is left
   aligned and so is every other pre-login card in this build, and a centred line
   under a left-aligned heading reads as a mistake rather than as a state. Nothing
   is lost by it: the reason the in-product notice is centred is that every title
   inside the product sits top left, and on the way in there is no product around
   it to be mistaken for. */
.o-prelogin .stub__notice, .scout-card .stub__notice {
  border: 0; padding: 0; align-items: flex-start; text-align: left;
}

/* ============ 34. THE COMPLETED SETUP STEP, REDRAWN (Shane, 14 September 2026) ============
   Five corrections in one sitting, on seeing the card at full size: "the background in
   the completed state is too dark ... remove the drop shadow ... the issued first of
   August or any sort of success message should be displayed in green ... more spacing
   between the success message and the button ... the border must also be removed. Make
   sure that there is enough contrast between the background and the card behind it,
   because there was no border on it."

   WHAT IT WAS AND WHY IT READ AS TOO DARK. The done step took --bg-canvas, the desk's
   own floor, which is DARKER than the --bg-surface card holding it, and carried a 1px
   inset ring to keep its edge. A card darker than its container reads as a hole punched
   in the container, which is the fault the 12 September redraw had already been through
   once; the ring was what stopped it reading as a gap.

   THREE PASSES, AND THE THIRD IS THE RULING. Raising it a step above the container
   (--bg-raised) fixed the darkness and bought a new fault, which Shane named on sight:
   lighter than the three steps still to do, it STOOD OUT, and a finished thing should
   recede. Dropping it to --bg-nested, the same fill as the other three, still read as
   lighter than them, because those three carry a drop and this one does not and the
   filled mark lifts the card's apparent brightness. It now sits, in his words,
   "somewhere in between the colour of the normal state card and the card behind it".

   THE RAMP HAS NO STEP THERE. The nested card is neutral-875 and the container is
   neutral-900, adjacent rungs with nothing between them, so the fill is MIXED FROM THE
   TWO TOKENS rather than written as a new colour. That keeps the rule that no colour is
   defined outside the token file, and it keeps the value tied to whatever those two
   tokens become. Halfway lands at roughly 7, 14, 20: darker than the three steps still
   to do and still clear of the card behind it, with no ring to help it. What it also
   loses is the drop, and losing the drop is what makes it settle while the others float.
   The state is carried by three things: the mark, the green line, and the card sitting
   flat and a shade back.

   THE FACT THE CARD CARRIES GOES GREEN, on the status ramp, because it is the one line
   that says the step is done; the tick in the ring above it is the only other mark, and
   that ruling (a green glyph in a neutral ring) is untouched. The line stands 16 clear
   of whatever follows it rather than the 8 a state line takes when it is one of a pair.

   SCOPED AWAY FROM THE DOCKED PANEL, where these are rows and not cards and the fills
   and gaps are the panel's own. Written here rather than in 02_copy_built.py because
   that step regenerates 21 screens and other sessions were mid-flight; a rule in this
   file reaches D1, D6 and E8 with no build run at all. THE NEED IS STILL RECORDED for
   whoever owns that step: the per-file rules it writes now have a shared-file override
   sitting on top of them. */
.scout-setup__step--done {
  background: color-mix(in srgb, var(--bg-nested) 50%, var(--bg-surface));
  box-shadow: none;
}
.scout-setup .scout-setup__step--done .g-row__meta { color: var(--status-success-text); }
.scout-setup:not(.scout-setup--docked) .scout-setup__step--done .scout-setup__foot { gap: var(--sp-16); }

/* THE MARK CARRIES THE STATE, AND THIS REVERSES THE 12 SEPTEMBER RULING that the circle
   stays neutral (Shane then: "replace the number in the circle with a green tick, but do
   not make the circle green"). Shane, 14 September, twice: the completed step "still
   doesn't register as completed in comparison to the other three".

   THE PREMISE OF THE OLD RULING IS GONE, which is why this is a reversal and not a
   contradiction. It was made when the done step ALSO carried a fill of its own, so the
   ring could stay neutral and the card still read as finished. Two corrections since
   have ruled the fill may be neither darker than the card behind it (a hole) nor lighter
   than the three steps still to do (it advances), which leaves it identical to them in
   fill, padding, radius and structure. Of what then still differed, two things are all
   but invisible on this ground: the drop it loses has, in the words written a few rules
   above this one, "almost no colour to fall on", and the title's step from primary to
   secondary is one notch at 15px.

   SO THE STATE HAD NOTHING STRONG LEFT TO RIDE ON. The mark is the one element in the
   card whose only job is the state, and it sits top left where the eye already goes to
   read the other three numbers. Filled, it is the universal completed mark and it
   registers across the row at a glance. Chosen by looking rather than by argument: the
   card was drawn as built, with a green ring, and filled, one under the other, at
   ideation/sop-flow/compare-done.html; the ringed one is better than what was built and
   still quiet. The tick thickens to 3 so it holds its shape on the fill. */
/* THE COMPLETED MARK IS A SOLID DISC AT 24, WHERE THE THREE NUMBERED RINGS ARE 28, AND
   THAT IS HOW THEY MATCH. Shane, through three passes: the tick looked too big beside the
   numbers; reducing the glyph changed nothing visible; turning the disc into a ring fixed
   the weight but not the thing he meant. He meant the mark, and the mark is the disc.

   A FILLED CIRCLE READS LARGER THAN A RING OF THE SAME DIAMETER, so equal numbers give
   unequal marks and the disc has to come down for the row to look level. This is optical
   compensation, the same reason a solid glyph is drawn smaller than an outlined one at the
   same nominal size. Chosen by drawing the disc at 28, 26, 24 and 22 against the numbered
   rings and looking at the four stacked: 26 still reads bigger, 22 begins to read smaller,
   24 sits level. The stack is kept at ideation/sop-flow/renders/L24-disc-sizes.png.

   WHAT THE GLYPH IS NOT. Measured in the browser, the tick's ink is 7 by 4.5 and a
   numeral's is 9.2 by 18, so the tick was already smaller and thinner than the numbers
   beside it. Taking it from 14 to 12 moved its ink by about one pixel, which is below the
   threshold of visible difference: the reduction was real and invisible. The tick stays at
   12, half the mark, the proportion it had before any of this. */
.scout-setup__step--done .scout-setup__n {
  width: 24px;
  height: 24px;
  background: var(--status-success-fill);
  color: var(--text-onFill);
  box-shadow: none;
}
/* The tick a shade smaller inside the filled mark (Shane, 14 September), its stroke
   eased with it so it does not read as chunky at the smaller size. */
.scout-setup__step--done .scout-setup__n svg { width: 12px; height: 12px; stroke-width: 2.5; }

/* THE MARK, GROWN AND BRIGHTENED (Shane, 14 September): the numbered marks should match
   the tick and be the same size, and "they are currently grayed out, make them lighter".

   BOTH MARKS WERE ALREADY 24 SQUARE, which is the whole point: a filled disc reads
   heavier, and therefore larger, than a 1px ring of the same diameter, so matching them
   by measurement was never going to match them by eye. Both grow together to 28, which
   keeps them equal, and the numbered one gains the weight it was missing: the numeral
   steps from the caption size in tertiary grey to the body size in the primary text
   colour, and the ring from --border-default to --border-strong. THE TICK DOES NOT GROW
   WITH THE CIRCLE. It was taken to 14 to hold the proportion it had at 24, and Shane read
   it as too big beside the numerals: a glyph carries more ink than a numeral of the same
   height, so the same box is not the same weight. It sits at 12 inside the 28 mark.

   SCOPED TO THE FOUR-CARD VARIANT AND AWAY FROM THE DOCKED PANEL, deliberately. Docked,
   these are rows, and the note and foot under each one hang on an indent computed from
   the mark: calc(--sp-24 + --sp-12). Growing the mark there without moving that indent
   would leave every docked row four pixels out of true. */
.scout-setup--steps:not(.scout-setup--docked) .scout-setup__n {
  width: 28px; height: 28px;
  box-shadow: inset 0 0 0 1px var(--border-strong);
  font: var(--fw-medium) var(--fs-base)/var(--lh-base) var(--font-ui);
  color: var(--text-primary);
}
.scout-setup--steps:not(.scout-setup--docked) .scout-setup__step--done .scout-setup__n {
  width: 24px; height: 24px; box-shadow: none;
}

/* ============ 35. THE SWITCH, ON, IS TEAL (Shane, 15 September 2026) ============
   On the assistants page the knob of a running assistant took the status green
   (build/03c_assistants.py, line 309). Shane: when active it should be teal, not
   green. Green is the status ramp, for facts about the world (a paper filed, a step
   done); a control that is on is the product's own accent, the same teal the chosen
   option on the data-rules picker takes. Written here rather than in the script that
   draws the page because that script regenerates the screen and other sessions are
   mid-flight; a rule in this file reaches the live screen with no build run. THE NEED
   IS RECORDED for whoever owns that script: line 309 should take --teal-300 and this
   section should then be deleted. Scoped to the on state; the off knob stays grey.
   THE WHOLE CONTROL, NOT ONLY THE KNOB (Shane, 15 September, second pass): the ring
   and the ground go teal with it. Still a ring on a ground with a knob inside, not a
   filled badge, which the 12 September ruling on this control rules out: the ground is
   the darkest step of the accent ramp, the same ground the chosen option on the
   data-rules picker takes; the ring the mid step; the knob the bright one. Lines 304
   to 312 of the script are the ones to change when it is folded back. */
.sw[aria-checked="true"] { background: var(--teal-900); box-shadow: inset 0 0 0 1px var(--teal-500); }
.sw[aria-checked="true"]::after { background: var(--teal-300); }


/* ============ 36. THE RAIL STAYS PUT ON THE ORGANISATION SCREENS, AND THE CHECKED
   CARD'S LAST ROW ============
   Shane, 15 September 2026, late: "when I scroll down, Settings is not fixed at the
   bottom of the screen, and I can scroll down past the sidebar border", plainly on the
   Vault and Public page tabs of the Organisation screen.

   MEASURED at 1440 by 900 before the change, scrolled 157 on the Vault tab: the rail
   `position: relative`, its top at -157, Settings' foot at 727. Section 30 removed the
   device frame's clipping and relies on the platform's sticky rail, `.o-shell >
   .scout-rail`, two classes. Four screens (P1, P2, E8, S1) still carry the device-frame
   rule in their own first style block, `.frame .o-shell > .o-rail { position: relative }`,
   three classes, and the rail carries both class names, so the page's rule wins and the
   rail scrolls with the page. This section states the sticky rail at a heavier weight,
   SCOPED TO THE ORGANISATION FAMILY, the five screens whose main carries `idpage` (P1,
   P2, E8 from settings, S1, S7). It must not reach the desks (D1, D6, E8 from the desk),
   whose own layout lays a FIXED rail over the content: a first draft of this section
   reached them, an in-flow rail of 100vh took the first grid row, and the whole desk
   dropped below the fold on any window wide enough to show the rail (the hero measured
   at 1080 from the top). Measured after the scope, on the Organisation screen's Public
   page tab scrolled 698: sticky, top 0, foot 900, Settings' foot at 884; the desk's
   hero back at the top with its fixed rail.

   THE NEED IS RECORDED for whoever owns those four screens' scripts: delete the
   device-frame rule from the page's own block and this section can go with it.

   THE CHECKED CARD ("Every time you have been checked", the Public page tab's last card)
   drew the row rule under its last row, from the shared components sheet's
   `.o-seen__row { border-bottom }`. The last row carries none. */
.o-shell:has(.o-main.idpage) > .o-rail, .o-shell:has(.o-main.idpage) > .scout-rail { position: sticky; top: 0; height: 100vh; align-self: start; }
.idpage .o-seen__list .o-seen__row:last-child { border-bottom: 0; }
