/* Document -- the meeting workflow's "what do the minutes say?" step.
 *
 * The third and last of these files. discussions.css and votes.css were extractions: their
 * rules had been JS string arrays injected into a <style> tag at runtime, unreachable by
 * /design/ and by the design-system checks, which is how 77 and 142 hardcoded colours
 * survived in them (issue #364). This one is not an extraction -- the Document tab's rules
 * were in styles.css all along, reachable and checked. It is a MOVE, made for a different
 * reason: the tab is being rebuilt onto the two-column stage Discussions uses (#448), and
 * a surface with its own layout is easier to read as its own file than as 150 lines in the
 * middle of the app's general stylesheet.
 *
 * Two groups of rules live here:
 *
 *   .mbdoc-*  new, for the stage, the video column and the side panel. Modelled on
 *             discussions.css so the two screens resolve identically -- a clerk moving
 *             between them should not be able to tell they were built separately.
 *   .mb-*     moved from styles.css: the item body box, the action line, the format bar.
 *             Same markup, same behaviour; the values are on tokens now, which is what
 *             moving them was worth doing for.
 *
 * The literals that came with the .mb-* rules resolve to SEMANTIC roles, not to whichever
 * ramp step happens to match. Following the mapping discussions.css already documents:
 *
 *   #374151  body text        -> --gv-ink-700
 *   #e5e7eb  hairline         -> --gv-border
 *   #94a3b8  gutter label     -> --gv-text-subtle   (NOT --gv-ink-500: 4.39:1, fails AA)
 *   #cbd5e1  placeholder      -> --gv-text-disabled
 *   #6366f1 / #c7d2fe / #eef2ff  indigo focus and "on" states -> the brand ramp. There is
 *            no indigo in this product; it was the same borrowed hue the AI buttons wore
 *            before DESIGN-SYSTEM.md 4 settled that question, and it sat directly beside a
 *            brand-green-and-gold nib on this very screen.
 *
 * .pm-md-body p.mb-action stays in styles.css. It draws the same ACTION label on published
 * minutes -- a page a resident reaches without signing in -- and belongs with the portal's
 * rules, not with the editor's.
 */

/* Palette scope, the shape discussions.css and votes.css use, so a rule can be read here
 * without holding the whole token list in your head. */
.mbdoc {
    --line: var(--gv-border);
    --ink: var(--gv-text);
    --ink2: var(--gv-ink-700);
    --mut: var(--gv-text-subtle);
    --mut2: var(--gv-text-disabled);
    --g: var(--gv-brand-700);
    --gd: var(--gv-brand-900);
    --gl: var(--gv-brand-50);
    --gb: var(--gv-brand-200);
    /* How wide "Writing this item from the transcript…" renders in the body box, and so
       where the typewriter reveal stops and the caret comes to rest. MEASURED, not chosen:
       215.79px at the box's 13px, which is 16.6em. Short and the ellipsis is clipped; long
       and the caret floats in space past the last character.
       RE-MEASURE IF THE STRING OR THE TYPE CHANGES, and move the steps(38) in
       mbDocTyping with it — that is the character count of the same sentence. */
    --mbdoc-typing-w: 16.6em;
    color: var(--gv-text);
}
.mbdoc .tnum { font-variant-numeric: tabular-nums; }

/* ── the stage ──────────────────────────────────────────────────────────────
 * Gone to components.css as .gv-stage (#466), and it went as THE reference: this tab's
 * numbers are the ones all five steps now share -- clamp(260px, 36%, 420px), an 18px
 * gutter, calc(100vh - 164px), 520px min-height. .mbdoc-left and .mbdoc-right went with
 * it as .gv-stage__main and .gv-stage__side, containment wall included.
 *
 * That this tab won was not a coin toss. It was already where the other three had been
 * converging -- it borrows Discussions' player and the Motions tab's transport and panel,
 * and the notes further down this file record two earlier attempts to build its own
 * instead, both reverted. The only thing it lacked was somewhere to live that was not one
 * screen's stylesheet.
 *
 * ONE RULE OF THIS TAB'S DID NOT SURVIVE. The comment here used to argue that the
 * recording must never drop below the document, which is why the column shrank to 260px
 * rather than collapsing, and why this was the one stage with no breakpoint. The shared
 * stage stacks at 1180px like the other four. What changed the answer is that the rule
 * was only ever weighed against THIS tab: on Motions, holding two columns down to 1180
 * leaves the filter bar 722px of the 760px it needs on a busy meeting, so it wraps to
 * three lines (votes.css measures this). A recording the clerk has to scroll to costs
 * less than a control bar that has come apart, and five steps behaving alike costs less
 * again than either. The reasoning is kept here rather than deleted because it is still
 * the right reasoning about this tab in isolation -- it just is not about this tab any
 * more. */

/* The bar and the format rail sit INSIDE the left column, so they are exactly as wide as
 * the document under them rather than running on past it beneath the video. The 2px is the
 * gutter .mbdoc-doc keeps clear for its scrollbar -- without it the bar and the document
 * miss their right edges by exactly that much. */
#mbdoc-bar { flex: 0 0 auto; padding-right: 2px; }

/* The document itself, and the only thing in this column that scrolls. */
.mbdoc-doc {
    overflow-y: auto;
    min-height: 0;
    flex: 1 1 auto;
    padding-right: 2px;
}

/* Stable wrappers so switching what the panel shows can swap innerHTML without the video
 * between them ever being re-parented -- moving an <iframe> in the DOM reloads it, and the
 * clerk loses their place in the recording. discussions.css says the same thing about
 * #mbtv-above / #mbtv-below; this is the third surface to need it. */
/* The order down this column is fixed: recording, then its transport, then the
 * Agenda/Transcript panel -- the Motions tab's arrangement.
 *
 * Every one of these carries an explicit `flex`, and the two above the panel are
 * `0 0 auto` (never shrink). The default is `0 1 auto`, which means a short viewport
 * squeezes them: #mbdoc-tr-w had no rule at all, so the transport could be compressed
 * toward zero height and -- because .mbdoc-right clips -- disappear silently instead of
 * overflowing where it could be seen. The panel is the only thing here that may give up
 * space, because it is the only thing that can scroll its own content. */
#mbdoc-above { display: flex; flex-direction: column; min-height: 0; flex: 0 0 auto; }
#mbdoc-tr-w  { flex: 0 0 auto; }
#mbdoc-below { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* The two rules that made the borrowed panel scroll internally rather than push the column
 * past its bottom were here, scoped to #mbdoc-below. They are in votes.css now, keyed on
 * .gv-stage__side (#466) -- because the Recording and Speakers steps borrow the same panel
 * and are not inside #mbdoc-below, and because the panel is votes.css's to size. */

/* ── the video ──────────────────────────────────────────────────────────────
 * .gv-stage__vid and .gv-stage__noplay in components.css now (#466), taken from here
 * whole -- the 16:9 frame, the 32vh cap, and the long argument for laying the player out
 * in normal flow instead of position:absolute;inset:0. That argument is why THIS box was
 * the one promoted: .mbtv-vid and .mbmv-vid both used the absolute recipe, which is the
 * one way a child of this column can escape it and lay itself out against the viewport. */

/* The transport's slot, under the video and above the panel. */
#mbdoc-tr-w { flex: 0 0 auto; }

/* ── the right column's panel and transport ───────────────────────────────
 * Neither is here. Both are the Motions tab's, from votes.css: .mbmv-tr for the transport
 * and .mbmv-pan / .mbmv-ph / .mbmv-flip / .mbmv-pb for the Agenda ⟷ Transcript panel.
 *
 * This file briefly carried its own — .mbdoc-tp with a Jump / AI / Verbatim head, a jump
 * list and a per-item transcript slice. Three views nobody else in the workflow offered,
 * beside a screen that already had the two that matter. Deleting them left #mbdoc-below
 * holding the borrowed panel directly, which is all it ever needed to do.
 */

/* The legacy-minutes note on the empty state. A statement, not a warning: nothing is at
 * risk, and the old data is not deleted by building new minutes. */
.mbdoc-legacy {
    display: flex;
    align-items: flex-start;
    gap: var(--gv-space-2);
    margin: 0 0 var(--gv-space-3);
    padding: var(--gv-space-3);
    border-radius: var(--gv-radius-md);
    background: var(--gv-warning-bg);
    border: var(--gv-border-width) solid var(--gv-warning-border);
    color: var(--gv-warning-fg);
    font-size: var(--gv-text-sm);
    line-height: var(--gv-leading-snug);
}

/* ── an item that was never discussed ─────────────────────────────────────── */
/* The clerk answered this on the Discussions step, and the generator has always honoured
 * it. What the document did with that answer was render an empty box captioned "Write what
 * happened on this item...", which is the one thing that did not happen.
 *
 * One dotted row: the item, and the one thing you can do with it. The same shape the
 * Motions tab gives an agenda item with no motions on it (.mbmv-i.none and its + Motion) --
 * a row on the agenda with nothing recorded against it and a single standing action.
 *
 * Outlined rather than tinted: the row is not an alert and nothing is wrong with it, it
 * simply holds nothing yet. It stays full height and full width, so a clerk scanning the
 * document still sees the item in its place on the agenda.
 *
 * DOTTED, not dashed. Motions draws its equivalent row dashed and this followed it, which
 * is why it shipped that way -- but the three states on this tab each want their own mark
 * and dotted is the quietest of the three outlines. Asked for directly; the two screens
 * differ here on purpose.
 *
 * The standing action is plainly visible, never revealed on :hover -- a tablet has no hover
 * to give, and a tablet is what a clerk runs a meeting on (DESIGN-SYSTEM.md). */
.mbdoc-nd {
    display: flex;
    align-items: center;
    gap: var(--gv-space-3);
    padding: var(--gv-space-2) var(--gv-space-3);
    /* An ordinary card with a DOTTED LEFT EDGE — the same 3px rail the other two states
     * wear, so all three are read in one place down the left margin rather than one of
     * them being a differently-shaped box:
     *
     *   written        3px solid green
     *   still owed     3px solid amber
     *   needs none     3px dotted, this
     *
     * It was a dotted outline all the way round, which said "this row is different" but
     * not "this row is the third of three". Motions draws its equivalent the same way
     * (.mbmv-i.none: a normal border with border-left:3px dashed) — dotted rather than
     * dashed here, which is the one difference between the two screens and was asked for. */
    border: var(--gv-border-width) solid var(--gv-border);
    border-left: 3px dotted var(--gv-border-strong);
    border-radius: var(--gv-radius-md);
    background: var(--gv-surface-sunk);
    min-height: var(--gv-control-lg);
    /* Pulled left by the same amount .mbdoc-item is, so the three rails sit in ONE column
     * down the margin. Without it this row's rail was 8px inboard of the other two —
     * measured — and three marks that do not line up read as three unrelated decorations
     * rather than one scale. */
    margin-left: calc(var(--gv-space-2) * -1);
}
.mbdoc-nd .n {
    flex: 0 0 auto;
    min-width: 1.25rem;
    font-family: var(--gv-font-mono);
    font-size: var(--gv-text-sm);
    font-weight: var(--gv-weight-bold);
    color: var(--gv-text-disabled);
}
/* Muted, because nothing has been written about it -- but not struck through and not
 * hidden. The item is on the agenda and has to stay readable. */
.mbdoc-nd .t {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--gv-text-sm);
    line-height: var(--gv-leading-snug);
    color: var(--gv-text-subtle);
}
.mbdoc-nd .gv-btn { flex: 0 0 auto; margin-left: auto; }

/* ── moved from styles.css: the item body ─────────────────────────────────── */
/* One text box per item. It reads as a surface you can write on -- a whole clickable
 * rectangle with room to breathe -- rather than as the row of one-line fields it used
 * to be. */
.mb-body-wrap { position: relative; }
.mb-body-box {
    display: block;
    cursor: text;
    min-height: 3.25rem;
    padding: 9px 12px;
    border: var(--gv-border-width) solid transparent;
    border-radius: var(--gv-radius-md);
    font-size: var(--gv-text-sm);
    line-height: var(--gv-leading-relaxed);
    color: var(--gv-ink-700);
    background: var(--gv-surface-sunk);
    transition: background-color var(--gv-dur-fast), border-color var(--gv-dur-fast), box-shadow var(--gv-dur-fast);
}
.mb-body-box:hover { border-color: var(--gv-border); background: var(--gv-surface); }
.mb-body-box:focus {
    outline: none;
    background: var(--gv-surface);
    border-color: var(--gv-brand-300);
    box-shadow: 0 0 0 3px var(--gv-brand-100);
}
/* Even spacing between lines, whichever tag the browser reached for. */
.mb-body-box > p,
.mb-body-box > div { margin: 0 0 0.5em 0; }
.mb-body-box > p:last-child,
.mb-body-box > div:last-child { margin-bottom: 0; }
.mb-body-box > ul,
.mb-body-box > ol { margin: 0.35em 0 0.5em 0; }
/* The action taken. Ruled off from the narrative above it, labelled in the gutter, and
 * bold -- the same three things the PDF does, so the editor and the printed page say the
 * same thing. The label is DRAWN, not typed, so it cannot be half-deleted and it never
 * lands in the saved text. Each action gets its own rule, so two or three of them read as
 * separate actions. */
.mb-body-box > p.mb-action,
.mb-body-box > div.mb-action {
    position: relative;
    margin-top: 0.6em;
    padding-top: 0.55em;
    padding-left: 3.4rem;
    border-top: var(--gv-border-width) solid var(--gv-border);
    font-weight: var(--gv-weight-semibold);
    color: var(--gv-ink-800);
}
.mb-body-box > p.mb-action::before,
.mb-body-box > div.mb-action::before {
    content: 'Action';
    position: absolute;
    left: 0;
    top: 0.55em;
    font-size: var(--gv-text-3xs);
    font-weight: var(--gv-weight-bold);
    letter-spacing: var(--gv-tracking-wide);
    text-transform: uppercase;
    color: var(--gv-text-subtle);
    /* Sit the small label on the same baseline as the bigger text beside it — which the
     * three tuned numbers that used to be here did not: measured, the label's baseline sat
     * 4.65px ABOVE the paragraph's.
     *
     * The label is absolutely positioned, so it can never share the paragraph's line box,
     * and that is where the error came from. A baseline sits at half-leading + ascent from
     * the top of its line box, so two boxes of DIFFERENT heights put their baselines in
     * different places even when the boxes start at the same y — 10px of leading against
     * 22.1px is a 6px discrepancy before anything else is counted.
     *
     * Giving the label the paragraph's line box removes it: line-height in absolute px,
     * built from the same two tokens the paragraph uses, so the two stay in step if either
     * token moves. `em` cannot do this — inside the pseudo-element it means the LABEL's
     * 10px, not the paragraph's 13px, which is precisely the trap the old 0.42em fell into.
     *
     * What is left after that is the ascent: a baseline also sits lower for a bigger font,
     * and the two sizes differ by 3px. ONE nudge closes it, and the number is measured
     * rather than derived — a calculated 0.3 ascent ratio left it 2.35px out, because the
     * ratio is the typeface's and not a constant worth pretending to know. Measured in the
     * browser against this stylesheet: 0.25 of the paragraph's own size lands it on the
     * baseline. Scaled to --gv-text-sm so it follows if that token moves; if the type
     * changes, re-measure rather than trusting the 0.25. */
    line-height: calc(var(--gv-text-sm) * var(--gv-leading-relaxed));
    padding-top: calc(0.25 * var(--gv-text-sm));
    pointer-events: none;
}
/* "+ Add action". It used to be revealed on :hover, which is a control that does not
 * exist on the device a clerk runs a meeting on -- a tablet has no hover to give. Visible
 * at rest and quiet, the way DESIGN-SYSTEM.md settled it for the Votes tab's Add motion. */
/* The controls under the body box. Usually one button; two when the item has an action and
 * no narrative yet, where "+ Add text" is the named way to start some (#470) -- the line
 * mbBodyBoxHtml leaves above the action is the one you get by just clicking. The ROW
 * carries the margin the button used to, so a lone button sits exactly where it always
 * has. */
.mb-add-row {
    display: flex;
    align-items: center;
    gap: var(--gv-space-4);
    margin: 5px 0 0 1px;
}
.mb-add-action {
    display: flex;
    align-items: center;
    gap: var(--gv-space-1);
    margin: 0;
    padding: 2px;
    border: none;
    background: none;
    cursor: pointer;
    font-size: var(--gv-text-3xs);
    font-weight: var(--gv-weight-semibold);
    letter-spacing: var(--gv-tracking-wide);
    text-transform: uppercase;
    color: var(--gv-text-subtle);
    transition: color var(--gv-dur-fast);
}
.mb-add-action:hover { color: var(--gv-brand-700); }

.mb-body-box.is-empty::before {
    content: attr(data-placeholder);
    color: var(--gv-text-disabled);
    pointer-events: none;
    position: absolute;
    padding: 1px 0;
}
.mb-story-item-group { padding: 0; }

/* ── the box while the AI is writing into it ───────────────────────────────
 * A sentence typing itself out, character by character, behind a caret. "Someone is
 * typing" is unambiguous in a way a bare blinking cursor is not — a cursor sitting still
 * is what an IDLE field looks like — and it is the only treatment that can also say WHY
 * the field went blank and stopped taking input.
 *
 * DRAWN AS ::after, and that is a correctness point rather than a styling one. The text
 * must never be able to reach Firestore, and a pseudo-element is not part of innerHTML or
 * innerText: mbSaveBodyBox cannot see it even if a save that was already debounced fires
 * while the box is in this state. A <span> in the box would have been one badly-timed
 * 800ms timer away from writing "Writing this item from the transcript…" into the minutes.
 *
 * steps(38) is the character count of the string. Re-word it and that number moves with
 * it, or the reveal stops part-way and restarts.
 *
 * The box also holds the height its prose had (set inline by mbDocWritingStart) so the
 * document does not jump while it is empty. */
.mb-body-box.is-writing {
    caret-color: transparent;   /* it is not editable; a blinking system caret would lie */
}
.mb-body-box.is-writing::after {
    content: 'Writing this item from the transcript…';
    display: inline-block;
    overflow: hidden;
    white-space: nowrap;
    vertical-align: bottom;
    width: 0;
    color: var(--gv-text-disabled);
    border-right: 2px solid var(--gv-ai-fg);
    animation: mbDocTyping 3.4s steps(38) infinite,
               mbDocTypingCaret 1.06s steps(1) infinite;
}
/* Types for 70% of the loop, then rests on the finished sentence before starting again —
 * a reveal that restarts the instant it lands reads as a glitch rather than a rhythm. */
@keyframes mbDocTyping {
    0%        { width: 0; }
    70%, 100% { width: var(--mbdoc-typing-w); }
}
/* The caret keeps blinking while the words appear, which is what a cursor does. */
@keyframes mbDocTypingCaret {
    0%, 66%   { border-right-color: var(--gv-ai-fg); }
    67%, 100% { border-right-color: transparent; }
}
/* Reduced motion: the sentence is simply there, whole and still. It still says what is
 * happening and why the field is blank — it just does not perform it. */
@media (prefers-reduced-motion: reduce) {
    .mb-body-box.is-writing::after {
        animation: none;
        width: var(--mbdoc-typing-w);
        border-right-color: transparent;
    }
}

/* ── the item's state, down its left edge ───────────────────────────────────
 * Three states, three treatments, and between them they cover every item on the agenda:
 *
 *   needs no minutes   .mbdoc-nd  — the dotted row with + Minutes, above
 *   written            green
 *   still owed         amber, the same --gv-warning the Incomplete chip wears
 *
 * The edge is always drawn and only its colour changes, so nothing moves when an item goes
 * from owed to written — a border appearing on save would shift the paragraph the clerk
 * has just finished typing into.
 *
 * A NOTE ON THE GREEN, because the file should carry the disagreement rather than pretend
 * there wasn't one. Motions marks only the exception: .mbmv-i.incomplete is the only
 * coloured edge in votes.css and a finished motion gets nothing, and the August step-status
 * work rejected per-row marks as "busy", putting green at the step level instead. The
 * argument against green here is that a finished thirty-item document becomes a wall of it,
 * making DONE the loudest thing on a page whose done items are the ones you never need to
 * look at. That was raised and overruled: the product owner wants written items marked, and
 * the amber is easier to find with something to contrast against. --gv-brand-300 rather
 * than a full-strength green keeps it quiet enough to scan past. */
.mbdoc-item {
    border-left: 3px solid transparent;   /* .mbmv-i.incomplete draws 3px too */
    padding-left: var(--gv-space-2);
    margin-left: calc(var(--gv-space-2) * -1);
    border-radius: var(--gv-radius-sm) 0 0 var(--gv-radius-sm);
}
.mbdoc-item.is-written { border-left-color: var(--gv-brand-300); }
.mbdoc-item.is-owed    { border-left-color: var(--gv-warning); }

/* The item's title line, and the meeting header and free-text blocks, which are the same
 * kind of field since Quill left this tab (#448). */
/* The item's title row. Positioned so Delete can come out of the flow: while it sat IN the
 * row, the title field ended a button's width short of the prose box beneath it, and an
 * item's two fields did not share a right edge. */
.mb-story-head { position: relative; }
.mb-story-del {
    position: absolute;
    right: 0;
    top: 0;
    line-height: var(--gv-leading-none);
}

/* The item's actions trigger — on the title row and on the dotted not-discussed row.
 *
 * VISIBLE AT REST. What it replaces was a bare × carrying Tailwind's
 * `opacity-0 group-hover:opacity-100`, so on a tablet — the device a clerk runs a meeting
 * on — the only way to delete an item was a control that never appeared. Quiet until
 * hovered, present always: the same treatment .gv-filterbar__more gets, and the rule
 * DESIGN-SYSTEM.md settled for the Votes tab's Add motion.
 *
 * Styled here rather than in Tailwind classes on the button because it is drawn from two
 * different renderers and they must not drift. */
.mbdoc-item-menu {
    flex: 0 0 auto;
    border: var(--gv-border-width) solid transparent;
    background: transparent;
    color: var(--gv-text-disabled);
    border-radius: var(--gv-radius-sm);
    padding: var(--gv-space-1);
    cursor: pointer;
    line-height: var(--gv-leading-none);
    transition: color var(--gv-dur-fast) var(--gv-ease),
                border-color var(--gv-dur-fast) var(--gv-ease);
}
.mbdoc-item-menu:hover {
    color: var(--gv-ink-700);
    border-color: var(--gv-border);
    background: var(--gv-surface);
}
.mbdoc-item-menu:focus-visible {
    outline: var(--gv-focus-width) solid var(--gv-focus-color);
    outline-offset: 1px;
}
/* On the dotted row it sits in the flex flow after + Minutes, so it needs no lifting —
 * only a little air, since that row's gap is sized for text and a button. */
.mbdoc-nd-menu { margin-left: var(--gv-space-1); }

.mb-story-header-edit {
    display: block;
    min-height: 1.25rem;
    cursor: text;
    /* Right padding clears the absolutely-placed Delete: the BOX runs the full width, so it
     * shares a right edge with the prose box below it, while the text stops before the
     * button rather than running under it. */
    padding: 2px 26px 2px 6px;
    margin-left: -6px;
    border-radius: var(--gv-radius-sm);
    transition: background-color var(--gv-dur-fast), box-shadow var(--gv-dur-fast);
}
.mb-story-header-edit:hover { background: var(--gv-surface-sunk); }
.mb-story-header-edit:focus {
    outline: none;
    background: var(--gv-surface);
    box-shadow: 0 0 0 2px var(--gv-brand-200);
}

/* ── moved from styles.css: the formatting toolbar ────────────────────────── */
/* It is parked in ONE place -- the top-right corner of the document column -- and stays
 * there whatever field has the caret. mbPositionFormatBar computes that corner. A docked
 * rail in the flow was built and rejected (it reserved 38px whether or not anything was
 * being edited), as were two placements that followed the focused box: controls that move
 * depending on which paragraph you are in are controls you have to go and find.
 *
 * It overlays what is beneath it, including the section's Rewrite and delete on the top
 * row. Deliberate. The shadow is what separates it from the document under it, so this is
 * one of the few things on the tab that earns an elevation.
 *
 * Hidden with [hidden], never opacity. An opacity:0 toolbar still takes clicks, and this
 * one would park seven invisible buttons directly over the line being written. */
#mb-format-bar {
    /* ── the two knobs ──────────────────────────────────────────────────────
     * Where the bar sits relative to the box it is editing. mbPositionFormatBar
     * writes only the box's top-right CORNER into top/left; everything below turns
     * that into an alignment, so these two are the whole adjustment surface and
     * they take any unit.
     *
     *   --mb-fmt-gap       clear air between the bar's bottom and the box's top.
     *                      Raise it to lift the bar further off the field.
     *   --mb-fmt-nudge-x   pull the bar left of the box's right edge. 0 aligns the
     *                      two right edges exactly.
     */
    --mb-fmt-gap: 6px;
    --mb-fmt-nudge-x: 0px;

    position: fixed;
    z-index: var(--gv-z-dropdown);
    display: flex;
    align-items: center;
    gap: 1px;
    padding: var(--gv-space-1);
    border-radius: var(--gv-radius-md);
    background: var(--gv-surface);
    border: var(--gv-border-width) solid var(--gv-border);
    box-shadow: var(--gv-shadow-md);
    animation: mbFmtIn var(--gv-dur-fast) var(--gv-ease);

    /* The alignment itself, and the reason JS never measures the bar: -100% is the
     * bar's OWN width and height, so it is pulled left by its width and up by its
     * height from the corner it was given. Bar-right lands on box-right, bar-bottom
     * on box-top. It stays true if the bar's contents ever change size, because the
     * percentage is resolved at paint rather than by a number JS wrote earlier. */
    transform: translate(calc(-100% - var(--mb-fmt-nudge-x)),
                         calc(-100% - var(--mb-fmt-gap)));
    transform-origin: 100% 100%;
}
/* No room above the box -- the first item in a scrolled document. Flip to just inside its
 * top edge rather than drawing over the step bar. Same right alignment either way. */
#mb-format-bar.is-below {
    transform: translate(calc(-100% - var(--mb-fmt-nudge-x)), var(--mb-fmt-gap));
}
#mb-format-bar[hidden] { display: none; }
/* Opacity only. This used to rise 2px on entry, which meant the keyframes set `transform`
 * -- and `transform` is now what ALIGNS the bar, so the animation overrode the alignment
 * for its whole duration and the bar snapped into place when it ended. Measured: during
 * the animation it sat a full bar-width right and a bar-height low. A fade needs no
 * transform, so the two no longer compete for the one property. */
@keyframes mbFmtIn { from { opacity: 0; } to { opacity: 1; } }
.mb-fmt-btn {
    width: var(--gv-control-sm);
    height: var(--gv-control-sm);
    border: none;
    border-radius: var(--gv-radius-sm);
    cursor: pointer;
    background: transparent;
    color: var(--gv-text-subtle);
    font-size: var(--gv-text-xs);
    line-height: var(--gv-leading-none);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color var(--gv-dur-fast), color var(--gv-dur-fast);
}
.mb-fmt-btn:hover:not(:disabled) { background: var(--gv-surface-sunk); color: var(--gv-text); }
/* A control that does not apply to the field in hand -- lists and alignment on a one-line
 * title. It stays in place rather than disappearing: the bar sits in one fixed spot, and a
 * bar that changes width under the caret is not in one fixed spot. */
.mb-fmt-btn:disabled { color: var(--gv-text-disabled); cursor: default; }
.mb-fmt-btn.is-on { background: var(--gv-brand-50); color: var(--gv-brand-900); }
.mb-fmt-sep { width: 1px; height: 12px; background: var(--gv-border); margin: 0 2px; }

/* ── the bar's item actions ───────────────────────────────────────────────── */
/* Right of the bar's one separator. Everything left of it changes how the text LOOKS;
 * these two do something to the ITEM -- rewrite its prose, file an exhibit against it.
 * Clear formatting moved back in with the formatting when this arrived: it had a divider
 * to itself, which left it sitting beside these, and "clear the formatting" next to
 * "replace the paragraph" is two consequential controls with nothing between them.
 *
 * Disabled, never hidden, when the focused field is not part of an item -- the meeting
 * header and the free-text blocks are blocks, not agenda items. Same reason the list and
 * alignment controls are disabled rather than hidden: the bar must not change width as the
 * caret moves, because it is right-aligned to the box and every button would shift. */
/* position:relative so the Rewrite popover, which appends itself to this element, hangs
 * off the actions group rather than off whatever happens to be positioned above it. */
.mb-fmt-actions { display: inline-flex; align-items: center; gap: var(--gv-space-1); position: relative; }
/* `hidden` is an attribute the UA stylesheet turns into display:none — and ANY display
   declaration of our own outbids it, because a class beats a UA rule. Both of these set
   display, so both need saying explicitly or mbShowFormatBar's act.hidden = true does
   nothing at all and the buttons stay on screen looking enabled.
   #mb-format-bar[hidden] above needs the same line for the same reason. */
.mb-fmt-actions[hidden],
.mb-fmt-sep[hidden] { display: none; }
.mb-fmt-act {
    display: inline-flex;
    align-items: center;
    gap: var(--gv-space-1);
    height: var(--gv-control-sm);
    padding: 0 var(--gv-space-2);
    border-radius: var(--gv-radius-sm);
    border: var(--gv-border-width) solid var(--gv-border-strong);
    background: var(--gv-surface);
    color: var(--gv-text-subtle);
    font-size: var(--gv-text-xs);
    font-weight: var(--gv-weight-medium);
    line-height: var(--gv-leading-none);
    cursor: pointer;
    transition: background-color var(--gv-dur-fast), color var(--gv-dur-fast), border-color var(--gv-dur-fast);
}
.mb-fmt-act:hover { background: var(--gv-surface-sunk); color: var(--gv-text); }
/* Attach is icon-only and that is allowed here: a paperclip is one of the few marks
 * DESIGN-SYSTEM.md's "universally understood" test actually passes. Rewrite is not, so it
 * keeps its word. */
.mb-fmt-att { width: var(--gv-control-sm); padding: 0; justify-content: center; }
/* The AI register, from the token the product ships rather than the gold the design doc
 * still describes. Outlined, not filled -- this sits among eleven quiet icons. */
.mb-fmt-ai { border-color: var(--gv-ai-fg); color: var(--gv-ai-fg); }
.mb-fmt-ai:hover:not(:disabled) { background: var(--gv-ai-bg); color: var(--gv-ai-fg-hover); border-color: var(--gv-ai-fg-hover); }
.mb-fmt-act:disabled,
.mb-fmt-act[aria-disabled="true"] {
    color: var(--gv-text-disabled);
    border-color: var(--gv-border);
    background: var(--gv-surface);
    cursor: default;
    pointer-events: none;
}

/* ── narrow and short ─────────────────────────────────────────────────────── */
/* Under 1040px the stage is one column and the panel goes, exactly as Discussions does it.
 * The video and its transport stay: on a narrow screen the recording is still the reason
 * this column exists, and the panel's contents are all reachable from the document itself.
 *
 * The stage also gives up its viewport lock here. A single column that is still
 * height-locked would put the document in a short scrolling box under the video with the
 * rest of the page empty below it -- the lock only earns its keep while there are two
 * columns to keep level. */
/* Both of this tab's remaining viewport rules are the shared stage's now (#466).
 *
 * The 1040px gap tweak is simply dead: the stage stacks at 1180px, so nothing reaches a
 * two-column layout at 1040 to tighten the gutter of.
 *
 * The short-viewport video cap moved to components.css and kept THIS file's number. All
 * three tabs disagreed about it -- 26vh here, 34vh on Discussions, nothing at all on
 * Motions -- and 26vh is the careful one: on a 768px-tall laptop it is the difference
 * between the transport and the panel being on screen under the player and being under
 * the fold. The tab that thought hardest about a short window is the one whose answer the
 * other four now get.
 *
 * What did NOT survive is the argument that used to sit here: that this stage must never
 * collapse to one column, because a full-width 16/9 box stops being a player beside the
 * document and becomes a banner across it. That reasoning is still sound about this tab
 * alone -- see the note at the head of this file for why it lost anyway. */

/* ── the bar's sentence ───────────────────────────────────────────────────── */
/* The live references in the line and its second line are components.css's now:
 * .gv-barlink and .gv-subline (#448). They started as .mbtv-bar-link / -l2 on
 * Discussions, were briefly copied here as .mbdoc-bar-*, and were promoted the moment
 * a second surface wanted them rather than left as two copies drifting apart. */

/* ── the facts row under the step bar ─────────────────────────────────────── */
/* The two things on this tab that are statements rather than actions: which version of
 * the minutes the public has (#103, formerly the portal posting pill), and the
 * minute-order counter. GV.stepBar's `meta` is escaped plain text and cannot hold a pill
 * or an input, so they get their own quiet line under the bar instead of being forced
 * into a slot that will not take them. */
.mbdoc-facts {
    display: flex;
    align-items: center;
    gap: var(--gv-space-2);
    flex-wrap: wrap;
    margin: calc(var(--gv-space-2) * -1) 0 var(--gv-space-3);
    padding: 0 var(--gv-space-1);
    min-height: var(--gv-control-sm);
}
/* :empty was never going to fire. The row used to ALWAYS contain #mb-portal-status, which
   renderMinPortalStatus filled asynchronously so the element had to exist before the answer
   did, and an element with a child is not :empty
   however little that child says. So on every meeting without minute orders the row stood
   28px tall (its own min-height) plus 12px of margin, showing nothing: 48px of gap between
   the toolbar and the filter row, measured.

   :has() asks the question that was actually meant — is the one thing in here still empty?
   One child and that child empty means nothing has arrived, so the row goes. It comes back
   on its own the moment the portal status lands (the child stops being :empty) or minute
   orders are switched on (there is more than one child). Where :has() is unsupported the
   row simply stays, which is the behaviour this replaced.

   Still load-bearing under #103: the version state moved up onto the bar, so on a board
   with no minute orders this row now has nothing in it at all and this rule is what keeps
   it from reserving 48px between the toolbar and the filter row. */
.mbdoc-facts:empty,
.mbdoc-facts:has(> :only-child:empty) { display: none; }
.mbdoc-facts__lbl {
    font-size: var(--gv-text-xs);
    font-weight: var(--gv-weight-medium);
    color: var(--gv-text-subtle);
    white-space: nowrap;
}
.mbdoc-facts__num { width: 6rem; }
.mbdoc-facts__hint {
    font-size: var(--gv-text-xs);
    color: var(--gv-text-subtle);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.mbdoc-facts__hint b { color: var(--gv-text); font-weight: var(--gv-weight-semibold); }

/* The bar's left half: the status pill, then the sentence about it (#103).
 *
 * A row of its own, and it has to be — the card's text column is a plain span, so the two
 * were INLINE siblings inside it. That broke both ways: the column's flex gap never
 * reached between them, so the pill read as the first word of the sentence; and the
 * sub-line, being a block, broke out to the column's left edge and started under the pill
 * instead of under the text it belongs to. A flex row fixes both at once, because the
 * sentence and its sub-line are then one box that the pill sits beside.
 *
 * align-items:center, so the pill is centred against the sentence however many lines it
 * runs to. That is deliberately NOT what the card does with the buttons on the right —
 * #512 baseline-aligns those to the FIRST line — and the difference is what each one is:
 * the buttons are read alongside the sentence's opening words, the pill labels the whole
 * block. */
.mbdoc-bar__state {
    display: flex;
    align-items: center;
    gap: var(--gv-space-3);
    min-width: 0;
}
/* The sentence and its sub-line: one column, so the sub-line sits under the text rather
   than under the pill. min-width:0 is what lets it wrap instead of pushing the buttons
   off the row. */
.mbdoc-bar__say { min-width: 0; }

/* Approved minutes: a document, not a workspace (#103).
 *
 * The controls are already absent from the markup — handles, item menus, Add rows, Rewrite
 * and the section lock are not rendered while the minutes are approved. What is left is
 * the CHROME around the text: grey fills that mean "type here", hover borders that promise
 * a box will accept a caret, a placeholder inviting prose into an empty item. Each of those
 * is an affordance for editing, and on an approved record every one of them is a lie.
 *
 * So the fills go flat and the hovers stop. Nothing is dimmed: this is the finished
 * document and it should read as clearly as it prints, not as a greyed-out version of a
 * screen the clerk cannot use. Dimming says "not available to you"; flattening says "there
 * is nothing to do here", which is the true one. */
.mbdoc--locked .mb-body-box,
.mbdoc--locked .mb-rich,
.mbdoc--locked .mb-bullet-content {
    background: transparent;
    border-color: transparent;
    cursor: default;
}
.mbdoc--locked .mb-body-box:hover,
.mbdoc--locked .mb-rich:hover,
.mbdoc--locked .mb-bullet-content:hover {
    background: transparent;
    border-color: transparent;
}
/* The empty-item invitation. An item nobody wrote on stays empty on an approved record,
   and asking for prose that cannot be typed is the plainest false promise on the screen.
 *
 * TWO rules, because the body box does not use the shared placeholder. styles.css draws
 * that one off [contenteditable="true"][data-placeholder]:empty, which cannot fire on a
 * locked document anyway — the attribute is not there. The body box has its own, keyed on
 * an is-empty class that mbBodyBoxEmptyState sets from JS, and that one fires regardless
 * of whether anything can be typed into it. It was the one still showing. */
.mbdoc--locked .mb-body-box.is-empty::before,
.mbdoc--locked [data-placeholder]:empty::before { content: none; }
/* Reordering is off at the source — Sortable is never wired to a frozen section — but the
   grab cursor lives on the row, so it goes too rather than surviving its handle. */
.mbdoc--locked .cursor-grab { cursor: default; }

/* ── the Rewrite popover ──────────────────────────────────────────────────── */
/* Anchored to the button's own wrapper. The popover existed before this change and was
 * unreachable (see mbOpenFillOptions); what is new is that it is on the design system
 * rather than in the indigo it was written in. */
.mb-fill-anchor { position: relative; display: inline-flex; }
.mb-fill-popover {
    position: absolute;
    right: 0;
    top: 100%;
    margin-top: var(--gv-space-1);
    z-index: var(--gv-z-dropdown);
    min-width: 20rem;
    padding: var(--gv-space-3);
    background: var(--gv-surface);
    border: var(--gv-border-width) solid var(--gv-border);
    border-radius: var(--gv-radius-lg);
    box-shadow: var(--gv-shadow-lg);
    text-align: left;
}
.mb-fill-row { margin-bottom: var(--gv-space-3); }
.mb-fill-row:last-of-type { margin-bottom: var(--gv-space-2); }
.mb-fill-lbl {
    display: block;
    margin-bottom: var(--gv-space-2);
    font-size: var(--gv-text-xs);
    font-weight: var(--gv-weight-medium);
    color: var(--gv-text-subtle);
}
.mb-fill-lbl .opt { font-weight: var(--gv-weight-normal); color: var(--gv-text-disabled); }
.mb-fill-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--gv-space-2);
}
/* The one thing a clerk has to be told before pressing Rewrite on an item they wrote
 * themselves: this is the press that overrides the protection, and it must not be quiet
 * about it. */
.mb-fill-warn {
    display: flex;
    align-items: flex-start;
    gap: var(--gv-space-2);
    margin: 0 0 var(--gv-space-3);
    padding: var(--gv-space-2);
    border-radius: var(--gv-radius-sm);
    background: var(--gv-warning-bg);
    border: var(--gv-border-width) solid var(--gv-warning-border);
    color: var(--gv-warning-fg);
    font-size: var(--gv-text-xs);
    line-height: var(--gv-leading-snug);
}
