/* THE FRAME MENU (P1-D3): a body portal above the sticky head and drawers (91), below the confirm scrim (94).
   Layer 93 is the existing looking layer; committing stays on 94/95. */
.frame-menu { position: absolute; z-index: 93; width: max-content; min-width: 180px;
  max-width: calc(100vw - 2 * var(--space-3)); overflow-y: auto; overscroll-behavior: contain;
  scrollbar-gutter: stable; padding: var(--space-1); border: 1px solid var(--line);
  border-radius: var(--radius-md); background: var(--surface); color: var(--ink); box-shadow: var(--shadow-float); }
.frame-menu .pm-item { position: relative; display: grid; grid-template-columns: var(--space-4) minmax(0, 1fr);
  width: 100%; height: auto; padding: var(--space-2); gap: 0 var(--space-1); border: 0;
  border-radius: var(--radius-sm); background: transparent; color: var(--ink); text-align: left;
  font: inherit; font-size: var(--type-small-size); cursor: pointer; }
.frame-menu .pm-item::before { content: ""; grid-column: 1; grid-row: 1; }
.frame-menu .pm-item[aria-checked="true"]::before { content: "\2713"; }
.frame-menu .pm-l, .frame-menu .pm-sub { grid-column: 2; overflow-wrap: anywhere; }
.frame-menu .pm-sub { font-size: var(--type-meta-size); color: var(--muted); }
.frame-menu .pm-item:hover, .frame-menu .pm-item:focus-visible { background: var(--hover); }
.frame-menu .pm-item:focus-visible { outline: var(--focus-width) solid var(--focus-color); outline-offset: -2px; }
.frame-menu .pm-item.danger { color: var(--neg); }
.frame-menu .pm-item[aria-disabled="true"] { color: var(--faint); cursor: default; }
.frame-menu .pm-head { padding: var(--space-2); color: var(--muted); font-size: var(--type-meta-size); }
.frame-menu .pm-rule { margin: var(--space-1); border-top: 1px solid var(--line); }

/* THE ONE CONFIRM AND THE DIALOG PRIMITIVE (P1-B) ============================================================ */

/* THE DIALOG PRIMITIVE's foot. The bespoke dialogs that are not confirms (a rule, a reference line, a compose form,
   the share sheet, the page preview, feedback, removing a product) still wear it until their screens' lanes rebuild
   them; the one confirm below has its own foot. */
.rm-foot { flex: none; display: flex; align-items: center; gap: 12px;
  padding: 14px 24px; border-top: 1px solid var(--line-soft); background: var(--canvas); }

/* THE ONE CONFIRM (P1 · Frame, item 10; FrameSpec's "Ask now · the confirm").
   One host, #v2-modal, drawn by confirm() in shell-ui-dialogs.js: the title, the body, the facts as a two-column grid,
   fields, a typed id, then a ruled foot with Cancel and the primary. Every ask(), askFields() and v2ModalOpen() draws
   here too, through their adapters. A spend's figures sit in the grid's last two rows and never on a button. */
.v2-modal { --scroll-cover: var(--surface); }
/* A commit floats over looking (V12, 2026-09-24): the confirm and its scrim sit above every surface a confirm is asked
   from, the drawer (91) and its overlay (90), the centred dialogs and the palette (93), the fact dock, the frames and
   the workbench. ask()'s old host sat at 93; v2ModalOpen's sat at 35, under an open drawer. modal-layers.test.mjs. */
.v2-modal-scrim { position: fixed; inset: 0; z-index: 94; background: var(--scrim); }
.v2-modal-scrim.hidden, .v2-modal.hidden { display: none; }
/* One wrapper per open period, the focus trap's root (confirm(), shell-ui-dialogs.js). It takes no box of its own, so
   the card lays out its parts as if it were not there. */
.v2-modal .mperiod { display: contents; }
.v2-modal { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 95;
  width: 440px; max-width: calc(100vw - 2 * var(--space-4)); max-height: 86vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--space-3);
  background: var(--surface); border-radius: var(--radius-card); box-shadow: var(--shadow-float);
  padding: var(--space-5) var(--space-6); animation: v2popInCentre .14s ease both; }
/* The kind of decision, only where a caller names one ("Remove", "Edit fact"). The board draws none by default. */
.v2-modal .eye { font-size: var(--type-meta-size); line-height: 1.5; font-weight: 500; letter-spacing: 0; color: var(--faint); }
.v2-modal.danger .eye { color: var(--neg); }
.v2-modal h3 { margin: 0; font-family: var(--type-card-title-family); font-size: var(--type-card-title-size);
  font-weight: var(--type-card-title-weight); line-height: var(--type-card-title-leading);
  letter-spacing: var(--type-card-title-tracking); color: var(--ink); text-wrap: balance; }
.v2-modal .mbody { font-size: var(--text-base); line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-modal .mbody[hidden] { display: none; }
.v2-modal .mtext { margin: 0; }
/* The label column fits its longest label on one line ("Chat limit left this month", RD's NIT), never under the old
   130px and never past 60% of the card; past that a label wraps, and the value column gives way first. */
.v2-modal .mrows { display: grid; grid-template-columns: fit-content(60%) minmax(0, 1fr); gap: var(--space-2) var(--space-3);
  font-size: var(--text-base); line-height: 1.45; }
.v2-modal .mrows .k { min-width: 130px; color: var(--muted); }
.v2-modal .mrows .v { min-width: 0; color: var(--ink); overflow-wrap: anywhere; }
/* R1: mono is for identifiers (a provider id, a slug), marked by the call site, which is the only thing that knows. */
.v2-modal .mrows .v.ident { font-family: var(--font-mono); font-size: var(--type-meta-size); }
/* A figure, as the board draws it: the same typeface, numerals that line up. The spend's two rows are figures. */
.v2-modal .mrows .v.num { font-variant-numeric: tabular-nums; }
.v2-modal .mnote { font-size: var(--type-small-size); line-height: 1.5; color: var(--muted); }
.v2-modal .mtyped { font-size: var(--type-small-size); color: var(--body); }
.v2-modal .mbad { font-size: var(--type-small-size); color: var(--neg); }
.v2-modal .mbad[hidden] { display: none; }
/* The chat limit (RD's ruling (ii)): a tick, then the sentence with its figure field inline, sized for a figure. Its
   lines take a row of their own. On a touch screen the field keeps console.css's 16 px field rule. */
.v2-modal .mlimit { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2);
  padding-top: var(--space-3); border-top: 1px solid var(--line-soft); font-size: var(--type-small-size); line-height: 1.5; color: var(--body); }
/* console.css gives every input the full width: the tick and the figure field size themselves. */
.v2-modal .mlimit .mlimit-on { flex: none; width: 15px; height: 15px; margin: 0; }
.v2-modal .mlimit .mlimit-n { flex: none; width: 9ch; height: 30px; padding: 0 8px;
  font-size: var(--type-small-size); font-variant-numeric: tabular-nums; }
/* The sentence reads as one: its two halves are labels of the tick, not field captions (console.css's label rule). */
.v2-modal .mlimit .mlimit-t { display: inline; margin: 0; font-size: inherit; font-weight: 400; color: inherit; }
.v2-modal .mlimit .mbad, .v2-modal .mlimit .mlimit-off { flex-basis: 100%; }
.v2-modal .mlimit .mlimit-off[hidden] { display: none; }
.v2-modal .mfoot { display: flex; justify-content: flex-end; gap: var(--space-2);
  padding-top: var(--space-3); border-top: 1px solid var(--line-soft); }

/* A field inside the dialog. Not scoped under `.v2-modal`: a brief fact and the record-a-change note wear it too. */
.v2-modal-field { width: 100%; }
.v2-modal input.v2-typed { width: 100%; height: 30px; padding: 0 10px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  font: 400 12px/1 var(--font-mono); color: var(--ink); }

/* What ask()'s callers draw in a confirm's body: the price workings, a list being consented to, tick-to-add picks.
   Each keeps the rhythm it had in the old host, now under the one host. */
.v2-modal .mbody .row { padding: 8px 0; border-top: 1px solid var(--line-soft); }
/* A picker dropped into the middle of a sentence must not break the sentence, nor make its line taller. */
.v2-modal .mbody .pm-sel { width: auto; max-width: none; vertical-align: middle; }
.v2-modal .mbody .pm-sel .pm-selbtn { height: 24px; padding: 0 8px; gap: 5px; }
.v2-modal .cf-price { margin: var(--sp-1) 0 var(--space-1); }
.v2-modal .cf-price .calc { margin-top: var(--sp-3); font-size: var(--type-small-size); line-height: 1.9; color: var(--muted); }
.v2-modal .cf-price .calc .rw { position: relative; border-bottom: 1px dotted var(--edge-hover); cursor: help; }
.v2-modal .cf-price .hint { margin-top: var(--sp-3); font-size: var(--type-meta-size); line-height: 1.5; color: var(--faint); }
/* The list of things being consented to is never its own scroller here: it grows, and the dialog scrolls if it must. */
.v2-modal .dlist { max-height: none; overflow: visible; margin: 4px 0 0; }
/* The questions and the price are two different claims, so the price starts below a rule. */
.v2-modal .dlist + .dcalc { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line); }
.v2-modal .dcalc + .dnote { margin-top: 12px; }
.v2-modal .cf-picks { margin-top: 14px; }
.v2-modal .cf-pick { display: flex; align-items: flex-start; gap: 12px;
  margin: 0; padding: 11px 2px; border-top: 1px solid var(--line-soft);
  font-weight: 400; letter-spacing: 0; color: var(--body); cursor: pointer; }
.v2-modal .cf-pick:last-child { border-bottom: 1px solid var(--line-soft); }
.v2-modal .cf-pick input[type="checkbox"] { flex: none; width: 15px; height: 15px;
  margin: 2px 0 0; accent-color: var(--heat); cursor: pointer; }
.v2-modal .cf-pick .nm { display: block; font-family: inherit;
  font-size: var(--text-base); font-weight: 600; line-height: 1.4; color: var(--ink); }
.v2-modal .cf-pick .ds { display: block; margin-top: 3px;
  font-size: var(--type-small-size); font-weight: 400; line-height: 1.55; color: var(--muted); }

/* THE READING DRAWER (P1-B2; the board is ReadingDrawer.dc.html, design e69f93bae). What the reading on screen is:
   its qualifying lines, then label-and-value rows at the confirm's 130px label measure, the doors, and what a reading
   is. On the scale throughout (RD, 10-02: sizes stay on the ladder); the board's 10px row and note padding is 8, its
   9px note corner the 8px radius. v2ReadingDrill in shell-dock.js; reading-drawer.test.mjs. */
.v2-rdg { display: flex; flex-direction: column; padding-top: var(--space-3); }
.v2-rdg-note { margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  background: var(--rail); color: var(--body); line-height: 1.45; text-wrap: pretty; }
.v2-rdg-note.warn { background: var(--warn-soft); }
.v2-rdg-row { display: grid; grid-template-columns: 130px minmax(0, 1fr); gap: var(--space-4); align-items: baseline;
  padding: var(--space-2) 0; border-top: 1px solid var(--line-soft); }
.v2-rdg-row .k { font-size: var(--type-small-size); color: var(--muted); }
.v2-rdg-row .v { min-width: 0; color: var(--ink); line-height: 1.45; }
.v2-rdg-engs { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
.v2-rdg-eng { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--type-small-size); color: var(--ink); }
/* An engine that couldn't be checked: listed, never dropped, in the faint ink, its mark ghosted. */
.v2-rdg-eng.off { color: var(--muted); }
.v2-rdg-eng.off .emark { opacity: .45; }
.v2-rdg-doors { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-4); padding-top: var(--space-4);
  border-top: 1px solid var(--line-soft); }
.v2-rdg-meth { margin-top: var(--space-3); font-size: var(--type-small-size); color: var(--muted); }
.v2-rdg-methpop { display: inline-flex; align-items: center; gap: var(--space-1); }
.v2-rdg-q { position: relative; display: inline-grid; place-items: center; width: 16px; height: 16px;
  border: 1px solid var(--line); border-radius: var(--radius-full); font-size: var(--type-meta-size); line-height: 1;
  color: var(--muted); cursor: help; }
.v2-rdg-methlink { display: none; }
.v2-rdg-methlink summary { cursor: pointer; color: var(--muted); text-decoration: underline; text-underline-offset: 3px;
  list-style: none; }
.v2-rdg-methlink summary::-webkit-details-marker { display: none; }
.v2-rdg-methlink p { margin: var(--space-2) 0 0; color: var(--body); line-height: 1.5; }
/* A phone has no hover: the method is a link that opens its words in place (ReadingDrawerMobile.dc.html). */
@media (max-width: 760px) {
  .v2-rdg-methpop { display: none; }
  .v2-rdg-methlink { display: block; }
  .v2-rdg-row { grid-template-columns: 104px minmax(0, 1fr); gap: var(--space-3); }
  .v2-rdg-doors .v2-btn { flex: 1; justify-content: center; }
}
/* THE READING DRAWER ends here: its gate (reading-drawer.test.mjs) reads to this line and no further. */

/* THE ONE DRAWER HOST, part 1: the surfaces (P1-B2b). The explainer drawer (#drawer) and the fact dock (#v2-dock)
   are wrappers inside the frame's panel (drawer.js mountFrame): no position, width, scrim or head of their own. They
   fill the panel under the frame's head, and their bodies scroll. A width rule for either is a second frame, and the
   one-host census (frame-host.test.mjs) fails on it. */
.sb-panel > [data-sbsurface] { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.sb-panel > [data-sbsurface][hidden], .sb-fbody[hidden] { display: none; }
/* The dock's sticky foot and scroll covers paint the panel's ground now, not the canvas. */
.v2-dock { --scroll-cover: var(--surface); }
/* .drawer-head and .drawer-body are the centred modals' head and body too (#pv-modal, #rm-modal and seven more
   in app.html): those rules stay for them. */
.drawer-head { flex: none; display: flex; align-items: flex-start; gap: 12px; padding: 20px 28px; border-bottom: 1px solid var(--line-light); }
.drawer-head .eyebrow { font-size: var(--text-xs); font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-text); }
.drawer-head h2 { font-family: var(--display); font-size: var(--text-xl); font-weight: 600; color: var(--ink); margin: 4px 0 0; letter-spacing: -.01em; }
.drawer-body { flex: 1; overflow-y: auto; padding: 20px 28px 44px; }
.v2-dock .dbody { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 18px 24px; }
.v2-dock.v2-dock-wide .dbody { display: flex; flex-direction: column; overflow: hidden; padding: 0; }
@media (max-width: 900px) {
.v2-dock.v2-dock-wide .dbody { overflow-y: auto; }
}
body.shell .drawer-head .eyebrow { color: var(--faint); }
/* The centred modals' chrome */
body.shell .drawer-head { padding: 17px 24px 15px; align-items: flex-start; }
body.shell .drawer-head h2 { font-family: inherit; font-size: var(--text-lg); font-weight: 500;
  letter-spacing: -.01em; margin: 8px 0 0; }
body.shell .drawer-body { padding: 18px 24px 40px; }

/* THE ONE DRAWER HOST, part 2: drawer.js's frame (.sb-frame), moved byte-identical from shell.css (P1-B2b) ======== */
/* THE DRAWER FRAME (phase 3; the one host since P1-B2b). A drawer for looking, a centred modal for
   committing (frameFor). The drawer sits over a scrim at the reader's place and the screen stays
   visible; every drawer is --size-drawer wide, a reading one (a full answer, a table) --size-drawer-wide,
   whatever its kind or surface; a phone gets a full-screen sheet. The scroll owner is the body, and it
   reserves its scrollbar track. The head follows the Drawer board: the path over the title, then the
   pager, Open as a full page and Close. */
.sb-frame { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
/* The rail stays clickable beside an open drawer (FrameSticky layers it over the drawers): a rail click is "leave
   this and go" (shell-boot's [data-nav] handler, frame.leave()). The frame's own box passes the click through;
   its scrim and panel take theirs. A centred modal commits, so its scrim keeps the whole screen. */
.sb-scrim, .sb-panel { pointer-events: auto; }
@media (min-width: 901px) { .sb-frame:not([data-frame="modal"]) .sb-scrim { left: var(--rail-w); } }
.sb-frame[hidden] { display: none; }
.sb-scrim { position: absolute; inset: 0; background: var(--scrim-soft, var(--scrim)); }
.sb-panel {
  position: absolute; top: 0; right: 0; bottom: 0; width: var(--size-drawer); max-width: 100vw;
  display: flex; flex-direction: column; background: var(--surface); border-left: 1px solid var(--line);
  box-shadow: var(--shadow-3, var(--shadow-2)); outline: none;
}
.sb-frame[data-wide="1"]:not([data-frame="modal"]) .sb-panel { width: var(--size-drawer-wide); }
.sb-frame[data-frame="modal"] .sb-panel {
  top: 50%; left: 50%; right: auto; bottom: auto; transform: translate(-50%, -50%);
  width: min(var(--size-drawer), calc(100vw - 32px)); max-height: calc(100vh - 64px); border: 1px solid var(--line); border-radius: var(--r-lg, var(--r-md));
}
.sb-fhd { display: flex; align-items: flex-start; gap: var(--space-3); padding: 22px var(--space-6) 18px; border-bottom: 1px solid var(--line-soft); }
.sb-fttl { flex: 1; min-width: 0; }
.sb-feye { font-size: var(--type-small-size); line-height: 1.4; color: var(--muted); }
.sb-feye:empty { display: none; }
/* The drawer's title is the ladder's drawer-title step (design, 10-02: 20px, under the 12.5px path); a centred
   modal's head stays card-title, as the confirm's does. */
.sb-ftitle { margin: var(--space-1) 0 0; font-family: var(--type-drawer-title-family); font-size: var(--type-drawer-title-size);
  line-height: var(--type-drawer-title-leading); font-weight: var(--type-drawer-title-weight);
  letter-spacing: var(--type-drawer-title-tracking); color: var(--ink); text-wrap: balance; overflow-wrap: anywhere; }
.sb-frame[data-frame="modal"] .sb-ftitle { font-family: var(--type-card-title-family); font-size: var(--type-card-title-size);
  line-height: var(--type-card-title-leading); font-weight: var(--type-card-title-weight);
  letter-spacing: var(--type-card-title-tracking); }
/* Open as a full page: an icon button with its words on the hover and for a reader, only where the thing has a page. */
.sb-fopen {
  flex: none; display: inline-grid; place-items: center; width: var(--ctl-h-sm); height: var(--ctl-h-sm); padding: 0;
  background: transparent; border: 1px solid var(--line); border-radius: var(--r-sm); color: var(--ink); cursor: pointer;
}
.sb-fopen[hidden] { display: none; }
.sb-fopen:hover { background: var(--hover); }
.sb-fopen .ic { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.sb-fback, .sb-fclose {
  flex: none; height: var(--ctl-h-sm); padding: 0 10px; background: transparent; border: 1px solid var(--line);
  border-radius: var(--r-sm); font-weight: 400; font-size: var(--text-xs); line-height: 1; font-family: inherit; color: var(--ink); cursor: pointer;
}
.sb-fback:hover, .sb-fclose:hover { background: var(--wash-hover); }
/* The walk (W, 2026-09-25): "9 / 23" between Previous and Next, for a drawer opened from a
   list. The ends are disabled, never wrapped. */
.sb-fwalk { flex: none; display: flex; align-items: center; gap: 4px; }
.sb-fwalk[hidden] { display: none; }
.sb-fwalk-n { padding: 0 4px; font-size: var(--text-xs); font-variant-numeric: tabular-nums; color: var(--muted); white-space: nowrap; }
.sb-fstep {
  flex: none; width: var(--ctl-h-sm); height: var(--ctl-h-sm); padding: 0; background: transparent; border: 1px solid var(--line);
  border-radius: var(--r-sm); font-size: var(--text-sm); line-height: 1; font-family: inherit; color: var(--ink); cursor: pointer;
}
.sb-fstep:hover:not(:disabled) { background: var(--wash-hover); }
.sb-fstep:disabled { color: var(--faint); cursor: default; }
.sb-fconfirm { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 10px 16px; border-bottom: 1px solid var(--line); font-size: var(--text-xs); color: var(--ink); }
.sb-fconfirm[hidden] { display: none; }
.sb-fconfirm span { flex: 1; }
.sb-fconfirm button { height: var(--ctl-h-sm); padding: 0 10px; border: 1px solid var(--line); border-radius: var(--r-sm); background: transparent; font-weight: 400; font-size: var(--text-xs); line-height: 1; font-family: inherit; color: var(--ink); cursor: pointer; }
.sb-fconfirm button.danger { color: var(--danger); border-color: var(--danger); }
.sb-fbody { flex: 1; overflow-y: auto; scrollbar-gutter: stable; padding: 16px; }
.sb-ffoot { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 12px 16px; border-top: 1px solid var(--line); }
.sb-ffoot[hidden] { display: none; }
.sb-fcons { flex: 1; margin: 0; font-size: var(--text-xs); color: var(--muted); }
.sb-fprimary { margin-left: auto; height: var(--ctl-h); padding: 0 14px; background: var(--ink); color: var(--surface); border: 0; border-radius: var(--r-sm); font-weight: 500; font-size: var(--text-xs); line-height: 1; font-family: inherit; cursor: pointer; }
.sb-ffail { margin: 0; font-size: var(--text-xs); color: var(--muted); }
/* The demo badge is fixed bottom-right and always shown; a drawer, and any frame on a
   phone, keeps a strip clear for it rather than letting it sit on the primary. The
   first-run tour waits behind an open frame and returns when it closes: all of it, the
   click block and the ring with the card. A block left standing without its card took
   every click on the page, the frame's included (W, 2026-09-25). */
body:has(> .demo-badge) .sb-frame:not([data-frame="modal"]) .sb-panel { padding-bottom: 48px; }
body:has(> .sb-frame:not([hidden])) :is(.tour-block, .tour-ring, .tour-pop) { display: none; }
@media (max-width: 560px) {
body:has(> .demo-badge) .sb-frame .sb-panel { padding-bottom: 48px; }
.sb-panel, .sb-frame[data-frame="modal"] .sb-panel {
    inset: 0; width: 100vw; max-width: 100vw; max-height: none; transform: none; border: 0; border-radius: 0;
  }
}

/* THE LEAD ROW (P1-D3, plan §1 C). What a screen's header held that is not a slot (reading stamps and their tips, read
   and date wraps, tab strips, counts, a scope label) moved unchanged, ids and behaviour intact, into this row right
   after the header: "the header never carries a figure". It keeps the header's own row so nothing moves on screen
   until each P3 lane places these facts on its board. */
.frame-lead { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 8px 28px 0; }
@media (max-width: 760px) {
  .frame-lead { row-gap: 6px; padding: 6px 16px 0; }
}

/* THE FRAME'S HEADER (P1-D3). #frame-head wears .v2-head's row (console.css), so it reads as every header did; these
   rules lay out its per-view parts. A `display` rule beats the `hidden` attribute, so every part says so for itself:
   an inactive view's group stays out of the layout, the tab order and the accessibility tree (RD's gate). */
.frame-head[hidden], .frame-acts[hidden], .frame-back[hidden], .frame-fact[hidden], .frame-read[hidden], .frame-exports-held[hidden] { display: none !important; }
.frame-backs, .frame-facts, .frame-groups { display: contents; }
.frame-acts { display: flex; align-items: center; gap: 10px; flex: none; }
.frame-back { display: inline-flex; align-items: center; flex: none; }
/* An empty fact host draws nothing: no box, no width, never "0" or "–". */
.frame-fact { display: inline-flex; align-items: center; flex: none; }
.frame-fact:empty { display: none; }
/* ROWS TWO AND THREE'S HOSTS (P1-D4/D5, job P1-j5): `scopeLead` and `filters`, each a full row under the title row,
   drawn by the screen. Empty or another view's, a host has no box, so the header grows a row only where one is drawn.
   On a phone the filters row stays one line and scrolls sideways inside the header (RD 10-02, ruled again by R24). */
.frame-rows { display: contents; }
.frame-head:has(> .frame-rows > :not([hidden]):not(:empty)) { flex-wrap: wrap; }
/* ROW ONE KEEPS ITS BAND (REV-P1-d5s1 F2). A header that wraps for a drawn row would pack row one against its top edge
   (.v2-head has no block padding), so at desktop the title holds the first line at the header's 48px rung, less its 1px
   rule: row one's acts sit exactly where a one-row header puts them, and the drawn row follows 8px under it with 8px to
   the header's foot. A phone's header has its own block padding and row gap (console.css), and keeps them. */
@media (min-width: 761px) {
  .frame-head:has(> .frame-rows > :not([hidden]):not(:empty)) { row-gap: var(--space-2); padding-block-end: var(--space-2); }
  .frame-head:has(> .frame-rows > :not([hidden]):not(:empty)) > .ttl { min-height: calc(var(--space-12) - 1px); display: inline-flex; align-items: center; }
}
.frame-scope-lead, .frame-filters { flex: 0 0 100%; display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
/* Above 760 px the filters host is never a scroll box (REV-P1-drivers F2: overflow-x: auto computes overflow-y to auto too and cuts
   what opens inside the row). On a phone it is one, and what opens inside it leaves it: the Filter panel is lifted by
   frame-drivers.js wireFilterEscape (position: fixed against the viewport) and, under 760 px, drawn as the bottom sheet. */
@media (max-width: 760px) {
  /* padding-block with the matching negative margin-block keeps a focus ring from being cut by the scroller's clip box (an
     overflow-x: auto box clips on both axes) and leaves the row's height unchanged. The inline pair does the same sideways:
     the row bleeds to the screen's edges, so a scrolled chip leaves the screen at its edge and not at the header's gutter. */
  .frame-head .frame-filters { overflow-x: auto; scrollbar-width: none; padding-block: var(--space-1); margin-block: calc(-1 * var(--space-1));
    padding-inline: var(--space-4); margin-inline: calc(-1 * var(--space-4)); flex-basis: calc(100% + 2 * var(--space-4)); }
  .frame-head .frame-filters::-webkit-scrollbar { display: none; }
  .frame-head .frame-filters > * { flex: none; flex-wrap: nowrap; }
  .frame-head .frame-filters [data-loc-chip], .frame-head .frame-filters .ui-fp-btn { flex: none; white-space: nowrap; }
  .frame-head .frame-filters .ui-fp[data-frame-lift] { position: fixed !important; left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important;
    width: auto !important; min-width: 0 !important; max-width: none !important; max-height: min(70dvh, 640px) !important; z-index: 93;
    border-radius: var(--radius-card) var(--radius-card) 0 0; border-bottom: 0; overflow-y: auto; overscroll-behavior: contain;
    padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px)); }
  /* REV-P1-j9 F1: the lifted panel is still a descendant of the sticky header (z-index 6), so its own z-index only orders it inside
     that stacking context and page content at 7 or more (a tip at 12, a selection bar at 12-18) paints over the sheet and takes
     the tap. While a sheet is open the header takes the sheet's level. */
  .frame-head:has(.ui-fp[data-frame-lift]) { z-index: 93; }
}
.frame-scope-lead[hidden], .frame-filters[hidden] { display: none !important; }
.frame-scope-lead:empty, .frame-filters:empty { display: none; }
/* A tip the tip driver opened (frame-drivers.js wireTips: a tap, or focus and hover where CSS alone does not reach). */
[data-row][data-tip-open] > .v2-tip { display: block; animation: v2tipIn .12s ease .35s both; }
/* The Notice (frame-drivers.js notice), as the design system's Notice preview draws it (P1-D5; the preview is the spec, REV-P1-d5
   D3, not the States board's cards): a failed region on
   the hover ground with its "!" mark, its words in ink and a quiet "· Try again"; stale counts on the warn ground in warn
   words, with the same act; set aside, one plain line under the row, no box. */
.frame-notice { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0; padding: var(--space-3) 14px; border: 0;
  border-radius: var(--radius-lg); color: var(--body); font: inherit; }
.frame-notice-t { min-width: 0; }
.frame-notice[data-notice="failed"] { background: var(--hover); }
.frame-notice[data-notice="failed"]::before { content: "!"; flex: none; margin-inline-end: 10px; color: var(--neg); font-weight: 600; }
.frame-notice[data-notice="failed"] .frame-notice-t { color: var(--ink); font-weight: 500; }
.frame-notice[data-notice="stale"] { padding-block: 10px; background: var(--warn-soft); color: var(--warn); font-weight: 500; }
.frame-notice[data-notice="aside"] { display: block; background: none; border: 0; padding: 0; font-size: var(--type-small-size); color: var(--body); }
.frame-notice-act { flex: none; margin-inline-start: .3em; padding: 0; border: 0; background: none; color: inherit; font: inherit; font-weight: 400; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px; }
.frame-notice-act::before { content: "· "; display: inline-block; white-space: pre; }
.frame-notice-act:hover { text-decoration-thickness: 2px; }
.frame-notice-act:focus-visible { outline: 2px solid var(--heat); outline-offset: 2px; border-radius: 2px; }
/* THE READ (P1-slots, RD 10-05): one served read beside the title, in the fact's place, as quiet words. Empty, it has
   no box. On a phone it takes a row of its own under the title row (the header wraps there), never More: a fact stays
   on the face. */
.frame-read { flex: none; color: var(--muted); font-size: var(--type-small-size); white-space: nowrap; }
.frame-read:empty { display: none; }
@media (max-width: 760px) {
  .frame-read { order: 1; flex: 1 1 100%; min-width: 0; white-space: normal; }
}
/* The phone header: menu, title, Ask agent and More (⋯); Share, the actions and the exports sit in More. */
.frame-more { display: none; }
@media (max-width: 760px) {
  .frame-more { display: inline-flex; }
  .frame-acts > :not([data-frame-ask]):not(.v2-btn-primary):not(.frame-more):not([data-frame-keep]) { display: none; }
}
/* A block report's header controls that act on the generated document (report.css hides them in the section; these are
   the frame's copies of that list, the ones it moved): Share, its note and print. */
.frame-acts[data-blk] :is(#v2rep-share, #v2rep-sharewhy, #rep-print) { display: none; }
/* THE PAGE GUTTER OF A SWITCHED SCREEN (FRAME-1: the owner's flag, RD 10-05). A switched screen draws into a fresh
   section (shell-rail.js › switchedSection), which console.css's `.app > .main .view.active` leaves flush against the
   rail. The frame pads it, and the header beside it, at the new system's page gutter: space-10 at desktop, space-4 at
   760px and below, as the boards draw it. So the header's title, the screen's own header and its cards share one left
   edge, and the screen adds no side padding of its own. Keyed to a switched screen being the active one: under a
   legacy screen the header keeps its 28px and --gutter (a legacy token) stays where it is.
   ONE EXCEPTION (RD 10-05): a switched screen whose root is its own scroller (.v2-scroll) keeps its own gutter, since a
   padded section would hold its scrollbar 40px off the window's edge. That is Locations alone, until D5 makes the main
   column the scroller and P3-04 drops v2-body and v2-scroll; drive-frame-gutter.mjs fails any other screen it matches.
   (Locations' header is its own, drawn at run time, so the frame's stays hidden there.) */
.app > .main .appmain:has(> .view.active[data-screen-next]) { --frame-gutter: var(--space-10); }
@media (max-width: 760px) {
  .app > .main .appmain:has(> .view.active[data-screen-next]) { --frame-gutter: var(--space-4); }
}
.app > .main .appmain > .view.active[data-screen-next]:not(:has(> .v2-scroll)),
.app > .main .appmain:has(> .view.active[data-screen-next]) > .frame-head { padding-inline: var(--frame-gutter); }
/* THE PAGE RHYTHM (RD-a4, D-277). The boards' page container reads 32px top, 20px between its blocks and 48px foot on 82
   of 92 pages; the phone reads 16, 14 and 96 (the foot clears the tab bar). The frame pads the section's block axis as it
   pads the inline axis (D-80), with the scroller below: a screen's root sets its gap, `gap: var(--frame-gap)`, and no top
   or foot space of its own, so the first box sits --frame-top under the header and the last --frame-foot clear of the end.
   The three are declared beside --frame-gutter, under the same condition, so a legacy screen declares none. */
.app > .main .appmain:has(> .view.active[data-screen-next]) { --frame-top: var(--space-8); --frame-gap: var(--space-5); --frame-foot: var(--space-12); }
@media (max-width: 760px) {
  .app > .main .appmain:has(> .view.active[data-screen-next]) { --frame-top: var(--space-4); --frame-gap: 14px; --frame-foot: calc(var(--space-12) * 2); }
}
/* The lead row (reading stamps, date controls) opens the page in a switched section: its own 8px top (6px on a phone) was
   the gap under the header, which the frame's --frame-top is now. */
.app > .main .appmain > .view.active[data-screen-next] > .frame-lead { padding-top: 0; }
/* THE PAGE SCROLLER (P1-D5; RD-a4, D-276). The main column is the page's scroller: one sticky header (#frame-head, a
   .v2-head: position sticky, top 0) inside it, never on the page and never inside a screen's own scroll (P1-plan "The
   header"). Under a switched screen the main column scrolls with a stable gutter, so its bar sits on the window's edge
   and a page that grows past the fold does not shift; the section grows with its page (it was held to the window and
   scrolled itself until D5) and keeps the rhythm's top and foot inside it. A legacy screen keeps its own inner scroller
   and fits the column (console.css keeps the column overflow hidden there), so no gutter is kept. A switched section whose root is still its own
   scroller (.v2-scroll: Locations' place page, until P3-04 L3b) keeps the old way, and drive-frame-gutter.mjs fails any
   root scroller on a screen's first page. A pane page (below) keeps a window-high section. shell-rail.js applyView
   starts the main column at its top on a change of view. */
.app > .main .appmain:has(> .view.active[data-screen-next]):not(:has(> .view.active > :is(.v2-scroll, [data-fixedvh]))) {
  overflow-y: auto;
  scrollbar-gutter: stable;
}
.app > .main .appmain > .view.active[data-screen-next]:not(:has(> .v2-scroll)) {
  flex: 1 0 auto;
  padding-block: var(--frame-top) var(--frame-foot);
}
/* A PANE PAGE (REV-P1-d5s1 F3): a switched screen whose root is declared [data-fixedvh] lays its panes out in a split held
   to the window, each pane scrolling inside (Agent mode's Canvas: its document and its agent panel, canvas.css .cv-split;
   the panel sticks to its newest line and the document follows its end, which a page that grows instead would break). Its
   section keeps the window's height and scrolls itself, the old way (D-276's interim), with the rhythm's padding; the main
   column above has nothing to scroll and keeps no gutter. drive-frame-gutter.mjs declares each such page (PANE). */
.app > .main .appmain > .view.active[data-screen-next]:has(> [data-fixedvh]) {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  scrollbar-gutter: stable;
}
/* A view drawn without the app's chrome (Flows' editor) keeps its own way out; the frame's burger would open nothing. */
html[data-chromeless] #frame-head [data-burger] { display: none; }
/* A date control alone in a lead row (Answers, Missed) takes the row on a phone and wraps inside it, as it did in the
   header (shell.css's rule for `.v2-head > .v2-src-facetwrap`, which no longer matches it). */
@media (max-width: 760px) {
  .frame-lead > .v2-src-facetwrap:has(> .sb-date) { flex: 1 1 100%; min-width: 0; max-width: 100%; }
}
/* THE NOT-FOUND PAGE (P1-D5; boards NotFound and GuidesMobileNotFound, NF-1 and NF-2): screen-notfound.js draws it into
   the notfound section, which wears the frame's rhythm. The title sits 96px under the header (48px on a phone): the
   frame's --frame-top and the page's own margin. The path is mono on the track's paint, and each segment is one unbroken
   run: a line ends only at a slash (the <wbr> before it), never inside a segment. The desktop names the rail and the
   palette's key, the phone its menu and search. */
.nf-page { max-width: 620px; margin: calc(var(--space-8) * 2) auto 0; display: flex; flex-direction: column; gap: 14px; }
.nf-title { margin: 0; font-size: var(--type-finding-size); line-height: 1.2; font-weight: 600; letter-spacing: -0.022em; color: var(--ink); }
.nf-path { font-family: var(--font-mono); font-size: .86em; font-weight: 600; background: var(--track); border-radius: var(--radius-sm); padding: 0 6px; }
.nf-seg { white-space: nowrap; }
.nf-why { margin: 0; color: var(--body); line-height: 1.55; font-size: var(--type-section-title-size); text-wrap: pretty; }
.nf-phone { display: none; }
.nf-doors { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: 6px; }
@media (max-width: 760px) {
  .nf-page { margin-top: var(--space-8); }
  .nf-title { font-size: var(--type-drawer-title-size); }
  .nf-desk { display: none; }
  .nf-phone { display: inline; }
  /* The doors stack, Open Visibility (filled) above Open Next moves, each the column's full width (GuidesMobileNotFound). */
  .nf-doors { flex-direction: column; align-items: stretch; }
  .nf-doors .v2-btn { width: 100%; justify-content: center; }
}

/* THE AGENCY BAR (P1-D5; the design system's AgencyBar): one dark bar, 40px, above both header rows while the session is
   scoped to a client. Ink ground and canvas text in both themes. It sits above the main column's scroller, so it never
   scrolls away; shell-rail.js paintAgencyBar draws it on every identity load (frame-drivers.js agencyBarHtml). */
.agency-bar { flex: none; display: flex; align-items: center; gap: var(--space-3); min-height: 40px; padding: 0 var(--space-5);
  background: var(--ink); color: var(--canvas); font-size: var(--type-small-size); line-height: 1.3; }
.agency-bar[hidden], html.embed .agency-bar { display: none; }
.agency-bar .ab-k { flex: none; font-weight: 600; }
.agency-bar .ab-dot, .agency-bar .ab-who { color: color-mix(in srgb, var(--canvas) 80%, var(--ink)); }
.agency-bar .ab-who { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agency-bar .ab-who + button { margin-left: auto; }
.agency-bar button { flex: none; font: inherit; color: inherit; background: none; cursor: pointer; white-space: nowrap; }
.agency-bar .ab-switch { border: 1px solid color-mix(in srgb, var(--canvas) 25%, transparent); border-radius: var(--radius-sm);
  padding: var(--space-1) 10px; }
.agency-bar .ab-switch:hover, .agency-bar .ab-switch[aria-expanded="true"] { background: color-mix(in srgb, var(--canvas) 12%, transparent); }
.agency-bar .ab-back { border: 0; padding: var(--space-1) 0; text-decoration: underline; text-underline-offset: 2px; }
.agency-bar .ab-back:hover { text-decoration-thickness: 2px; }
.agency-bar button:focus-visible { outline: 2px solid var(--canvas); outline-offset: 2px; }
/* The phone keeps the client's name whole beside both doors on one line: the label, its dot and " · managed by {agency}"
   step out (Back to agency says whose it is), and only the client's name ellipsizes, when it cannot fit (REV-P1-d5 D1). */
@media (max-width: 760px) {
  .agency-bar { gap: var(--space-2); padding: 0 var(--space-4); }
  .agency-bar .ab-k, .agency-bar .ab-dot, .agency-bar .ab-by { display: none; }
}
