/* Styles for rendered page content — the HTML that RenderUtils produces from
 * page markdown. Shared by every surface that renders a page: the editor
 * preview, fullscreen presentation mode, the public shared page and shared
 * notebook views, and therefore MathBB Commons.
 *
 * These rules used to live in style.css and a duplicate copy in
 * shared_page.html, which meant the shared *notebook* view — the main Commons
 * reading surface — rendered theorem environments unstyled. One file, linked
 * from all of them, instead.
 *
 * Colors come from the design tokens, so this file follows whichever theme is
 * active (see docs/theming.md).
 */

/* ── Theorem environments (::: {.theorem} … :::) ── */
.mb-theorem { margin: 1em 0; }
.mb-theorem > p:first-of-type { display: inline; }
.mb-theorem-head { font-weight: 700; font-style: normal; }
.mb-theorem-name { font-weight: 400; }
.mb-thm-plain { font-style: italic; }
.mb-thm-plain .mb-theorem-head, .mb-thm-plain .mb-theorem-name { font-style: normal; }
.mb-thm-remark .mb-theorem-head, .mb-thm-proof .mb-theorem-head { font-weight: 400; font-style: italic; }
.mb-qed { text-align: right; }

/* ── Lean formalizations (::: {.proof lean="file.lean"} … :::) ──
 *
 * The block has two faces: the written proof and a framed, scrollable view of
 * the .lean resource. See static/js/lean-proof.js for the whole picture.
 *
 * At rest only the showing face is in flow, so the swap is exactly as tall as
 * it and the surrounding page lays out as if none of this were here; the other
 * face is absolute, translated one width aside and hidden. `.is-animating`
 * lifts both out of flow and the swap carries an explicit height, so the
 * transform slide and the height change run together and the content below
 * moves to meet the new size.
 *
 * overflow: clip, not hidden — hidden would make the swap a block formatting
 * context, so the theorem's margins would stop collapsing through it and the
 * block would measure taller than it renders (same trap as the fold bodies). */
.mb-lean-swap {
    position: relative;
    overflow: clip;
    /* The block's vertical spacing lives here, not on either face, so both
       start flush with the swap's top edge and one `top` value puts the toggle
       in the corner of whichever face is showing. */
    margin: 1em 0;
}
.mb-lean-pane { width: 100%; }
.mb-lean-pane-doc > .mb-theorem,
.mb-lean-frame { margin: 0; }
/* Keep the prose clear of the toggle, which floats over that corner. The QED
   box is on its own line well below it, so pull it back out to the full width
   or it would sit short of every other proof's. On the Lean face the toggle
   lands on the frame's title bar, whose filename is left-aligned, so that side
   needs no room made for it. */
.mb-lean-pane-doc > .mb-theorem { padding-right: 3.6rem; }
.mb-lean-pane-doc > .mb-theorem > .mb-qed { margin-right: -3.6rem; }
.mb-lean-pane-code {
    position: absolute;
    top: 0;
    left: 0;
    transform: translateX(100%);
    visibility: hidden;
}
.mb-lean-swap.is-open > .mb-lean-pane-doc {
    position: absolute;
    top: 0;
    left: 0;
    transform: translateX(-100%);
    visibility: hidden;
}
.mb-lean-swap.is-open > .mb-lean-pane-code {
    position: static;
    transform: none;
    visibility: visible;
}

.mb-lean-swap.is-animating { transition: height 0.32s ease; }
.mb-lean-swap.is-animating > .mb-lean-pane {
    position: absolute;
    top: 0;
    left: 0;
    visibility: visible;
    transition: transform 0.32s ease;
}
.mb-lean-swap.is-animating > .mb-lean-pane-doc { transform: translateX(0); }
.mb-lean-swap.is-animating > .mb-lean-pane-code { transform: translateX(100%); }
.mb-lean-swap.is-animating.is-open > .mb-lean-pane-doc { transform: translateX(-100%); }
.mb-lean-swap.is-animating.is-open > .mb-lean-pane-code { transform: translateX(0); }

.mb-lean-toggle {
    position: absolute;
    top: 0.3rem;
    right: 0.4rem;
    z-index: 2;
    padding: 0.1rem 0.5rem;
    font: inherit;
    font-size: 0.72rem;
    font-style: normal;
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: 1.6;
    color: var(--text-3);
    background: var(--surface-2);
    border: 1px solid var(--border-4);
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.mb-lean-toggle:hover {
    color: var(--accent-deep);
    background: var(--accent-bg);
    border-color: var(--accent-faint);
}
.mb-lean-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
/* Once the Lean face is showing, the toggle has moved onto the frame's title
   bar and reads as one of its controls, so it drops the pill and takes the
   copy button's treatment. Closed, it is still an invitation sitting on the
   prose and keeps the outline that makes it visible there. */
.mb-lean-swap.is-open > .mb-lean-toggle {
    /* Same box as .mb-lean-copy beside it: the toggle is positioned, so it
       can't inherit the bar's flex centring and has to match by hand. */
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 0.35rem;
    font-weight: 500;
    color: var(--text-4);
    background: none;
    border-color: transparent;
    border-radius: var(--radius-sm);
}
/* Borderless on hover too — the base rule's outline belongs to the pill the
   toggle is when closed, and on the bar the tint alone is affordance enough. */
.mb-lean-swap.is-open > .mb-lean-toggle:hover {
    color: var(--text-1);
    background: var(--surface-4);
    border-color: transparent;
}

/* The Lean face is a fixed-size window rather than a block that grows with the
   file: a formalization is usually longer than the prose it replaces, and a
   proof that doubled the page height on every toggle would be unusable. */
.mb-lean-frame {
    display: flex;
    flex-direction: column;
    height: min(24rem, 55vh);
    border: 1px solid var(--border-4);
    border-radius: var(--radius-md);
    background: var(--surface-2);
    overflow: hidden;
}
.mb-lean-bar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    /* The toggle floats over this bar's right end, so leave it room. */
    padding: 0.25rem 3.1rem 0.25rem 0.7rem;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    font-style: normal;
    color: var(--text-3);
    background: var(--surface-3);
    border-bottom: 1px solid var(--border-3);
}
.mb-lean-bar .mb-lean-name {
    /* Natural width, so the count that follows reads as part of the same
       phrase rather than drifting off to the far end of the bar. It still
       gives way first when the pane is narrow. */
    flex: 0 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* How long the formalization is, written in by LeanProof._fill once the file
   is in hand. Empty until then, and for a file that failed to load, so the
   separator hangs off :not(:empty) rather than being drawn beside nothing.
   It never shrinks: a long file name ellipsizes and the count stays whole. */
.mb-lean-bar .mb-lean-size {
    /* The auto margin is what holds the copy button at the right end, whether
       or not there is a count to show. */
    flex: 0 0 auto;
    margin-right: auto;
    color: var(--text-4);
}
.mb-lean-bar .mb-lean-size:not(:empty)::before {
    content: '·';
    margin-right: 0.5rem;
}
.mb-lean-copy,
.mb-lean-download,
.mb-lean-run {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 0.35rem;
    font-weight: 500;
    color: var(--text-4);
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.mb-lean-copy:hover,
.mb-lean-download:hover {
    color: var(--text-1);
    background: var(--surface-4);
}
.mb-lean-copy:focus-visible,
.mb-lean-download:focus-visible,
.mb-lean-run:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
/* The version popup beside Run. The frame clips its overflow, so the popup
   hangs below the bar and opens leftward — the icon sits near the right end. */
.mb-lean-info {
    flex: 0 0 auto;
    position: relative;
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 0.15rem;
    color: var(--text-4);
    cursor: default;
    transition: color 0.12s;
}
.mb-lean-info:hover { color: var(--text-1); }
.mb-lean-info:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-radius: var(--radius-sm);
}
.mb-lean-info-pop {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    width: 17rem;
    padding: 0.45rem 0.6rem;
    white-space: normal;
    text-align: left;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--text-2);
    background: var(--surface-1);
    border: 1px solid var(--border-4);
    border-radius: var(--radius-md);
    box-shadow: 0 2px 10px var(--shadow-2);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease, visibility 0.12s ease;
    z-index: 3;
}
.mb-lean-info:hover .mb-lean-info-pop,
.mb-lean-info:focus .mb-lean-info-pop {
    opacity: 1;
    visibility: visible;
}
.mb-lean-info-line { display: block; }
.mb-lean-info-line + .mb-lean-info-line { margin-top: 0.35rem; }
/* The aside about upcoming performance work — deliberately quieter than the
   two facts above it. */
.mb-lean-info-note {
    display: block;
    margin-top: 0.45rem;
    font-size: 0.64rem;
    line-height: 1.35;
    color: var(--text-4);
}
.mb-lean-copy.is-copied { color: var(--success); }
.mb-lean-copy .mb-lean-copy-done,
.mb-lean-copy.is-copied .mb-lean-copy-idle { display: none; }
.mb-lean-copy.is-copied .mb-lean-copy-done { display: block; }
.mb-lean-code {
    flex: 1 1 auto;
    min-width: 0;
    /* The containing block for the highlight band, which must scroll with the
       source rather than sit still over it. */
    position: relative;
    margin: 0;
    /* No left padding: the gutter owns that edge and pins itself to it. */
    padding: 0.7rem 0.9rem 0.7rem 0;
    display: flex;
    align-items: stretch;
    overflow: auto;
    background: none;
    border-radius: 0;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-style: normal;
    line-height: 1.55;
    tab-size: 2;
    white-space: pre;
    color: var(--text-1);
}
.mb-lean-code code {
    /* `1 0 auto`, not `1 1 auto`: a long line must make the pre scroll rather
       than squeeze the code column, or `white-space: pre` would be fighting a
       shrinking box. Positioned so it paints above the highlight band. */
    flex: 1 0 auto;
    position: relative;
    z-index: 1;
    background: none;
    padding: 0;
    font: inherit;
    color: inherit;
}
/* The line-number gutter. `position: sticky; left: 0` is what holds it at the
   left edge while the code scrolls sideways underneath — it is inside the
   scroll container, so it still scrolls *vertically* with the source, which is
   exactly the pairing wanted. It is a sibling of <code>, so nothing here can
   reach the clipboard, and `user-select: none` keeps a mouse selection across
   the source from picking the numbers up either. */
.mb-lean-gutter {
    position: sticky;
    left: 0;
    z-index: 1;
    flex: 0 0 auto;
    /* em, not rem: the padding belongs to the code's type size. Trimmed to
       almost nothing — the numbers sit where the code's own left padding used
       to be, so the gutter costs the source about 7px rather than the ~21px
       it started at. The width itself is the content's, so a short file
       reserves no room for line numbers it doesn't have. */
    /* +2px a side on top of that: with the frame's own padding gone the
       numbers ended up all but touching its edge. */
    padding: 0 calc(0.4em + 2px) 0 calc(0.1em + 2px);
    font: inherit;
    text-align: right;
    color: var(--text-5);
    /* Transparent, and no dividing rule: a fill and a border made it read as a
       separate column, and an opaque one also cut the highlight band in half,
       leaving a lit line with an unlit number. `is-scrolled` puts the fill
       back only when there is something to hide behind it — see below. */
    background: transparent;
    user-select: none;
    -webkit-user-select: none;
}
/* Sticky and transparent is a contradiction the moment the source scrolls
   sideways: the code would slide *under* the numbers and show through them.
   So the fill comes back exactly then, and only then. */
.mb-lean-code.is-scrolled .mb-lean-gutter {
    background: var(--surface-2);
    box-shadow: 1px 0 0 var(--border-4);
}
.mb-lean-gutter:empty { display: none; }
/* One block per line, inheriting the code's line box so the two columns stay
   in step; it is also the geometry a clicked line number is measured from. */
.mb-lean-gutter > div { line-height: inherit; }

/* The band a clicked message lights up. Positioned rather than a class on the
   line: a token can span lines (a nesting block comment does), so the
   highlighted source has no one-element-per-line to mark. It sits under the
   code (which is `position: relative`) and under the gutter (sticky, z-index
   1), so it lights the line without covering either. */
.mb-lean-hl {
    position: absolute;
    left: 0;
    z-index: 0;
    pointer-events: none;
    /* No left accent bar: `left: 0` puts it in content coordinates, where the
       sticky gutter sits on top of it. The tint alone carries the line. */
    background: var(--accent-bg);
}
.mb-lean-hl[hidden] { display: none; }
.mb-lean-hl.is-on { animation: mb-lean-flash 1.1s ease-out; }
@keyframes mb-lean-flash {
    0%, 40% { background: var(--accent-pale); }
    100% { background: var(--accent-bg); }
}
.mb-lean-pane-code.is-error .mb-lean-code { color: var(--danger); }

@media (prefers-reduced-motion: reduce) {
    .mb-lean-swap.is-animating,
    .mb-lean-swap.is-animating > .mb-lean-pane { transition: none; }
}

/* Run is the one control on the bar that *does* something, so it carries the
   accent tint the others don't; the play glyph sits half a space off the word.
   Hidden in the emitted markup and revealed by LeanProof.apply — [hidden] alone
   loses to the display above it. */
.mb-lean-run {
    gap: 0.28rem;
    color: var(--accent-deep);
    background: var(--accent-bg);
    border-color: var(--accent-faint);
    font-family: var(--font-ui);
    font-size: 0.7rem;
    letter-spacing: 0.01em;
}
.mb-lean-run[hidden] { display: none; }
.mb-lean-run:hover {
    color: var(--accent-deep);
    background: var(--accent-faint);
    border-color: var(--accent-light);
}
/* Disabled while this file's check is in flight: a second Run would spend the
   sandbox twice for one answer, and Abort in the panel below is the control
   that makes sense at that moment. */
.mb-lean-run:disabled {
    color: var(--text-4);
    background: var(--surface-4);
    border-color: transparent;
    cursor: default;
}

/* ── Lean syntax highlighting ──────────────────────────────────────────────
 *
 * Tokens come from the small lexer in lean-proof.js. The colours are the
 * app's existing --syn-* palette rather than new tokens: it is derived per
 * theme (tools/build_themes.py) and already carries a legible spread in both
 * light and dark, so a formalization is highlighted in whatever design the
 * reader is being served. */
.mb-lean-cm { color: var(--syn-marker); font-style: italic; }
.mb-lean-str { color: var(--syn-code); }
.mb-lean-num { color: var(--syn-link-url); }
.mb-lean-kw { color: var(--syn-header); font-weight: 600; }
.mb-lean-tac { color: var(--syn-list); }
.mb-lean-attr { color: var(--syn-fence); }
.mb-lean-decl { color: var(--syn-link-text); font-weight: 600; }
/* A `sorry` is the one token whose colour is a warning rather than a
   classification: it is what separates a file that looks proved from one that
   is. */
.mb-lean-sorry {
    color: var(--danger);
    font-weight: 600;
    background: var(--danger-wash);
    border-radius: var(--radius-xs);
    padding: 0 0.15em;
}

/* The source and the check panel share one box, so the panel can slide in
   over the source without leaving the frame. */
.mb-lean-body {
    position: relative;
    flex: 1 1 auto;
    display: flex;
    min-height: 0;
    overflow: hidden;
}

/* ── The check panel ───────────────────────────────────────────────────────
 *
 * Clicking Run posts the file to the Lean backend and reports the verdict
 * here: a pane that slides in from the right *inside the frame*, over the
 * source it is about. It stays within the frame deliberately — a check belongs
 * to the block that asked for it, and nothing else on the page should be
 * covered by one (which also means presentation mode and a narrow editor pane
 * need no special case). */
.mb-lean-run-panel {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    /* A sidebar over the source, not a cover for it: the reader is meant to
       read the errors *against* the code they are about, so the file stays on
       screen beside them. 35% of the frame, but never below a width a goal
       state can be read in, and never wider than the frame itself (which a
       17rem floor would otherwise exceed in a narrow editor pane). */
    width: min(100%, max(17rem, 35%));
    z-index: 3;
    display: flex;
    flex-direction: column;
    background: var(--surface-1);
    border-left: 1px solid var(--border-3);
    box-shadow: var(--shadow-3);
    font-family: var(--font-ui);
    font-size: 0.78rem;
    font-style: normal;
    line-height: 1.5;
    color: var(--text-1);
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.28s ease, visibility 0.28s;
}
.mb-lean-run-panel.is-open { transform: none; visibility: visible; }
.mb-lean-run-head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0.4rem 0.25rem 0.7rem;
    border-bottom: 1px solid var(--border-3);
    background: var(--surface-3);
}
.mb-lean-run-title {
    /* Gives way first: in a narrow panel the clock and the buttons are what
       the reader needs, and the panel's own heading is the least of it. */
    flex: 0 1 auto;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    color: var(--text-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* The clock: live while the check runs, then the total it took. The auto
   margin holds it (and the buttons) at the right end however short the title
   has been squeezed. */
.mb-lean-run-elapsed {
    flex: 0 0 auto;
    margin-left: auto;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-4);
}
/* Abort is offered only while there is something to abort. It stops the wait,
   not the container — the panel says so rather than claiming a cancellation
   the server never heard about. */
.mb-lean-run-abort {
    flex: 0 0 auto;
    display: none;
    align-items: center;
    height: 20px;
    padding: 0 0.4rem;
    font-family: var(--font-ui);
    font-size: 0.7rem;
    font-weight: 500;
    color: var(--danger-deep);
    background: none;
    border: 1px solid var(--danger-border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: color 0.12s, background 0.12s;
}
.mb-lean-run-panel.is-running .mb-lean-run-abort { display: inline-flex; }
.mb-lean-run-abort:hover { background: var(--danger-bg); }
.mb-lean-run-close {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    padding: 0.15rem;
    color: var(--text-4);
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.mb-lean-run-close:hover { color: var(--text-1); background: var(--surface-4); }
.mb-lean-run-abort:focus-visible,
.mb-lean-run-close:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
.mb-lean-run-body {
    flex: 1 1 auto;
    overflow: auto;
    padding: 0.6rem 0.75rem 1.2rem;
}

/* Running. Elapsed time is the only honest progress the checker offers, so it
   is the largest thing here; the bar beside it is indeterminate and says only
   that the request is still open. */
.mb-lean-run-status {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: 0.45rem 0.55rem;
}
.mb-lean-run-spinner {
    width: 15px;
    height: 15px;
    border: 2px solid var(--border-4);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: mb-lean-spin 0.8s linear infinite;
}
@keyframes mb-lean-spin { to { transform: rotate(360deg); } }
.mb-lean-run-running { font-weight: 600; }
.mb-lean-run-elapsed {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--text-3);
}
.mb-lean-run-track {
    grid-column: 1 / -1;
    height: 3px;
    border-radius: var(--radius-pill);
    background: var(--surface-4);
    overflow: hidden;
}
.mb-lean-run-track > i {
    display: block;
    width: 35%;
    height: 100%;
    border-radius: inherit;
    background: var(--accent);
    animation: mb-lean-slide 1.4s ease-in-out infinite;
}
@keyframes mb-lean-slide {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(300%); }
}
.mb-lean-run-note {
    grid-column: 1 / -1;
    margin: 0.2rem 0 0;
    font-size: 0.76rem;
    color: var(--text-3);
}

/* The verdict. Five outcomes, three colours: proved is the only good one,
   incomplete is the honest middle, and everything else is a failure of one
   kind or another. */
.mb-lean-run-verdict {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.3rem;
    margin-bottom: 0.5rem;
}
.mb-lean-run-badge {
    flex: 0 0 auto;
    padding: 0.1rem 0.5rem;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    font-weight: 600;
    border-radius: var(--radius-pill);
    color: var(--text-2);
    background: var(--surface-4);
}
.mb-lean-run-verdict.is-proved .mb-lean-run-badge {
    color: var(--success-deep);
    background: var(--success-bg);
}
.mb-lean-run-verdict.is-incomplete .mb-lean-run-badge {
    color: var(--warning-deep);
    background: var(--warning-bg);
}
.mb-lean-run-verdict.is-failed .mb-lean-run-badge,
.mb-lean-run-verdict.is-error .mb-lean-run-badge {
    color: var(--danger-deep);
    background: var(--danger-bg);
}
.mb-lean-run-gloss { color: var(--text-2); }
.mb-lean-run-failed {
    margin-top: 0.7rem;
    padding: 0.5rem 0.65rem;
    color: var(--danger-deep);
    background: var(--danger-bg);
    border: 1px solid var(--danger-border);
    border-radius: var(--radius-sm);
}

.mb-lean-run-section { margin-top: 0.9rem; }
/* The whole header is the control, so the hit target is the line rather than
   the chevron alone. */
.mb-lean-run-sectionhead {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    width: 100%;
    padding: 0.2rem 0;
    font-family: inherit;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-4);
    background: none;
    border: none;
    cursor: pointer;
}
.mb-lean-run-sectionhead:hover { color: var(--text-2); }
.mb-lean-run-sectionhead:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-radius: var(--radius-xs);
}
.mb-lean-run-chevron {
    flex: 0 0 auto;
    transition: transform 0.22s ease;
}
.mb-lean-run-section.is-open .mb-lean-run-chevron { transform: rotate(90deg); }
.mb-lean-run-sectiontitle { flex: 0 1 auto; min-width: 0; }
/* The count is the point of a collapsed section: it says how much is behind
   it, so it is never dimmed away or truncated. */
.mb-lean-run-count {
    flex: 0 0 auto;
    font-weight: 500;
    letter-spacing: 0.02em;
    text-transform: none;
    color: var(--text-5);
}
/* A Lean keyword in a heading is a literal, not a label: `sorry` is spelled
   lowercase in the language and uppercasing it would be quoting it wrongly. */
.mb-lean-run-sectiontitle code {
    font-family: var(--font-mono);
    font-size: 0.72rem;
    letter-spacing: 0;
    text-transform: none;
    background: none;
    padding: 0;
    color: inherit;
}
/* The 0fr↔1fr grid track is what animates: it needs no measured height, so a
   section holding forty declarations opens as smoothly as one holding one. */
.mb-lean-run-wrap {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.24s ease;
}
.mb-lean-run-section.is-open > .mb-lean-run-wrap { grid-template-rows: 1fr; }
.mb-lean-run-inner { overflow: hidden; }
.mb-lean-run-section ul { margin: 0; padding: 0; list-style: none; }
.mb-lean-run-section li {
    padding: 0.4rem 0;
    border-top: 1px solid var(--border-4);
}
.mb-lean-run-section li:first-child { border-top: none; padding-top: 0; }
.mb-lean-run-section code,
.mb-lean-run-gloss code {
    font-family: var(--font-mono);
    font-size: 0.76rem;
    background: none;
    padding: 0;
    color: var(--text-1);
}
/* Lean's own output, preformatted. A *goal state* must not wrap — its
   alignment is the information — so it scrolls sideways instead. A diagnostic
   is mostly prose (a hint, a suggestion) and wraps, because in a panel this
   narrow the alternative is scrolling every message horizontally to read the
   end of an English sentence. Hence `pre-wrap` by default and `pre` for the
   goals section. */
.mb-lean-run-section pre {
    margin: 0.25rem 0 0;
    padding: 0.4rem 0.55rem;
    font-family: var(--font-mono);
    font-size: 0.74rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    overflow-x: auto;
    background: var(--surface-2);
    border: 1px solid var(--border-4);
    border-radius: var(--radius-sm);
    color: var(--text-2);
}
.mb-lean-run-section[data-section="sorries"] pre {
    white-space: pre;
    overflow-wrap: normal;
}
.mb-lean-run-clean { color: var(--success-deep); font-size: 0.76rem; }
.mb-lean-run-dirty { color: var(--danger-deep); font-size: 0.76rem; }
/* A message's position is a control: it scrolls the source to that line and
   lights it up, so it is styled as the link it behaves like. */
.mb-lean-run-loc {
    display: inline-block;
    padding: 0;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    color: var(--link);
    background: none;
    border: none;
    border-bottom: 1px solid transparent;
    cursor: pointer;
}
.mb-lean-run-loc:hover { border-bottom-color: currentColor; }
.mb-lean-run-loc:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
}

@media (prefers-reduced-motion: reduce) {
    .mb-lean-run-panel,
    .mb-lean-run-wrap,
    .mb-lean-run-chevron { transition: none; }
    .mb-lean-run-spinner,
    .mb-lean-hl.is-on,
    .mb-lean-run-track > i { animation: none; }
}


/* amsthm italicises a theorem's *statement text*, not the math in it. Without
   this reset the block's font-style cascades into KaTeX, and the browser fakes
   an oblique for the faces that have no italic cut — slanting blackboard-bold
   ℝ, digits, parentheses and relation symbols, and disagreeing with the
   compiled PDF. KaTeX still italicises variables itself, via its own rules. */
.mb-theorem .katex { font-style: normal; }

/* ── 3D models (.glb) ──
 * A model has no intrinsic size, so the renderer omits the height and the
 * aspect ratio is set here — that way the box scales with its column instead
 * of being pinned at one pixel height, and a theme can retune it. An explicit
 * ;size=WxH in the markdown drops the class and wins with inline styles. */
.model-viewer-container.model-auto-size {
    width: 100%;
    aspect-ratio: 4 / 3;
    height: auto;
}
/* In a column the box is already narrow; don't also let it get tall. */
.mb-column .model-viewer-container.model-auto-size,
.mb-multicol .model-viewer-container.model-auto-size { max-height: 60vh; }

/* ── Markdown tables ──
 *
 * Scoped to rendered page content, so the app's own tables (admin lists, the
 * tool-permissions matrix, keyboard shortcuts) keep their own styling.
 *
 * The look follows booktabs — horizontal rules only, no vertical lines, plenty
 * of horizontal padding — which is the convention in mathematical typesetting
 * and matches what the LaTeX export already produces (pandoc emits
 * \toprule/\midrule/\bottomrule).
 */
.preview-pane table,
#fullscreen-content table,
.shared-content table,
.snb-content-container table {
    border-collapse: collapse;
    margin: 1.3em 0;
    /* Shrink to fit the content rather than stretching across the pane: a
       three-column table spread over 900px is harder to read, not easier. */
    width: auto;
    max-width: 100%;
    /* Line up digits in numeric columns. */
    font-variant-numeric: tabular-nums;
}
.preview-pane th, .preview-pane td,
#fullscreen-content th, #fullscreen-content td,
.shared-content th, .shared-content td,
.snb-content-container th, .snb-content-container td {
    /* The horizontal padding is the whole point — markdown tables render with
       none by default, which jams the columns together. */
    padding: 0.42em 1.1em;
    vertical-align: top;
    border: none;
}
/* First and last cells sit flush with the surrounding text block. */
.preview-pane tr > :first-child, #fullscreen-content tr > :first-child,
.shared-content tr > :first-child, .snb-content-container tr > :first-child {
    padding-left: 0;
}
.preview-pane tr > :last-child, #fullscreen-content tr > :last-child,
.shared-content tr > :last-child, .snb-content-container tr > :last-child {
    padding-right: 0;
}
/* `:not([align])` because marked emits align="right"/"center" attributes for
   markdown's |---:| syntax, and a plain text-align rule would override them. */
.preview-pane th:not([align]), #fullscreen-content th:not([align]),
.shared-content th:not([align]), .snb-content-container th:not([align]) {
    text-align: left;
}
.preview-pane thead th, #fullscreen-content thead th,
.shared-content thead th, .snb-content-container thead th {
    font-weight: 700;
    border-bottom: 2px solid var(--border-6);
    padding-bottom: 0.5em;
}
.preview-pane tbody tr:not(:last-child) td,
#fullscreen-content tbody tr:not(:last-child) td,
.shared-content tbody tr:not(:last-child) td,
.snb-content-container tbody tr:not(:last-child) td {
    border-bottom: 1px solid var(--border-2);
}
.preview-pane tbody tr:last-child td, #fullscreen-content tbody tr:last-child td,
.shared-content tbody tr:last-child td, .snb-content-container tbody tr:last-child td {
    border-bottom: 2px solid var(--border-6);
}

/* ── Side-by-side columns (::: {.columns} / ::: {.column width="…"}) ── */
.mb-columns {
    display: flex;
    gap: 1.6rem;
    align-items: flex-start;
    margin: 1em 0;
}
/* Unsized columns share whatever the sized ones leave. min-width:0 stops a
   wide equation or code block from forcing the column past its share. */
.mb-column { flex: 1 1 0; min-width: 0; }
/* The first and last child of a column shouldn't add margin that breaks
   alignment with the neighbouring column's top edge. */
.mb-column > :first-child { margin-top: 0; }
.mb-column > :last-child { margin-bottom: 0; }

/* ── Flowed columns (::: {.multicol count="N"}) ── */
.mb-multicol {
    column-gap: 1.6rem;
    margin: 1em 0;
}
/* Keep the indivisible things whole across a column break — but NOT
   paragraphs, which have to be splittable or the text never flows into the
   second column at all. */
.mb-multicol .math-display,
.mb-multicol img,
.mb-multicol pre,
.mb-multicol table,
.mb-multicol .mb-theorem { break-inside: avoid; }
.mb-multicol > :first-child { margin-top: 0; }

/* Display math, figures and tables overflow a narrow column rather than
   wrapping, so let them scroll inside it instead of widening the layout. */
.mb-column .math-display, .mb-multicol .math-display { overflow-x: auto; }
.mb-column img, .mb-multicol img { max-width: 100%; height: auto; }
/* A table only scrolls if it's a block box, so switch it inside columns. */
.mb-column table, .mb-multicol table { display: block; overflow-x: auto; }

/* Collapse to a single column when there isn't room. The threshold is where
   a two-way split drops under ~230px a side, which is about the narrowest a
   column of prose-with-math stays readable.

   Viewport width is the fallback (phones, and browsers without container
   queries); the container query below is what actually matters, because the
   editor's preview pane can be narrow on a very wide screen. With all three
   panes open the preview sits under the threshold and columns stack — hide
   the chat pane (Cmd+D) to see the layout a reader will get. */
@media (max-width: 700px) {
    .mb-columns { flex-direction: column; gap: 1rem; }
    .mb-multicol { column-count: 1 !important; }
}

@supports (container-type: inline-size) {
    .preview-pane, .shared-content, .snb-content-container, #fullscreen-content {
        container-type: inline-size;
    }
    @container (max-width: 480px) {
        .mb-columns { flex-direction: column; gap: 1rem; }
        .mb-multicol { column-count: 1 !important; }
    }
}

/* Print / PDF export: keep a column row on one page where possible. */
@media print {
    .mb-columns { break-inside: avoid; }
}

/* ── Markdown tables in chat messages ──
 *
 * Same markdown renderer, very different constraints: the chat pane is a few
 * hundred pixels wide, so a table that wraps would end up with one word per
 * column. These are tuned for that instead — smaller type, tight padding,
 * cells that don't wrap, and the table itself as a scroll container so a wide
 * table slides sideways rather than blowing out the pane.
 *
 * Covers the Dora and collaborator chats (.chat-bubble) and the Relay chat
 * (.ar-text-content).
 */
.chat-bubble table,
.ar-text-content table {
    display: block;             /* a table only scrolls if it's a block box */
    overflow-x: auto;
    border-collapse: collapse;
    max-width: 100%;
    margin: 0.6em 0;
    font-size: 0.88em;
    font-variant-numeric: tabular-nums;
}
.chat-bubble th, .chat-bubble td,
.ar-text-content th, .ar-text-content td {
    padding: 0.24em 0.65em;
    border: none;
    vertical-align: top;
    /* Prefer sideways scrolling over columns collapsing to one word wide. */
    white-space: nowrap;
}
.chat-bubble tr > :first-child, .ar-text-content tr > :first-child { padding-left: 0; }
.chat-bubble tr > :last-child, .ar-text-content tr > :last-child { padding-right: 0; }
/* marked emits align="right"/"center" for |---:|; only default the rest. */
.chat-bubble th:not([align]), .ar-text-content th:not([align]) { text-align: left; }
.chat-bubble thead th, .ar-text-content thead th {
    font-weight: 700;
    border-bottom: 1px solid var(--border-5);
    padding-bottom: 0.34em;
}
.chat-bubble tbody tr:not(:last-child) td,
.ar-text-content tbody tr:not(:last-child) td {
    border-bottom: 1px solid var(--border-2);
}
/* The user's own bubble is filled with the accent color, so token greys would
   disappear into it — use the on-accent text color at low emphasis instead. */
.chat-bubble.chat-mine thead th,
.chat-bubble.chat-mine tbody tr:not(:last-child) td {
    border-bottom-color: var(--text-inverse-dim);
}

/* ── Collapsible content (static/js/content-fold.js) ──
 *
 * Headers, list-item bullets and the head region of a \collapsehere-marked
 * ::: environment keep their normal appearance until hovered, when they pick
 * up the accent color to show they're clickable. A folded span of content is
 * replaced by a "⋯" capsule that expands it again.
 */

.foldable {
    cursor: pointer;
    transition: color 0.12s ease;
}
h1.foldable:hover, h2.foldable:hover, h3.foldable:hover,
h4.foldable:hover, h5.foldable:hover, h6.foldable:hover {
    color: var(--accent);
}
li.foldable, .mb-theorem.foldable { position: relative; cursor: auto; }

/* The head region of a tail fold: everything before the marker. The inline run
   is wrapped in its own span (content-fold.js explains why), so no trigger ever
   contains a fold body and :hover is exact — hovering the visible tail does not
   light the head. Hovering any part of the region lights all of it, the way a
   header lights along its full width.

   The two-deep selector is what "all of it" costs: a head region is either a
   direct child of the owner (an environment's `.mb-theorem-head`) or one level
   in (the run wrapped inside the run-in paragraph). Bounding it at two also
   keeps a *nested* fold's trigger, which is always deeper, from lighting its
   ancestor's head. */
.fold-trigger { cursor: pointer; transition: color 0.12s ease; }
.foldable:has(> .fold-trigger:is(:hover, :focus-visible),
              > * > .fold-trigger:is(:hover, :focus-visible)) > .fold-trigger,
.foldable:has(> .fold-trigger:is(:hover, :focus-visible),
              > * > .fold-trigger:is(:hover, :focus-visible)) > * > .fold-trigger {
    color: var(--accent);
}
.fold-trigger:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 3px;
}

/* Invisible click target over the browser-drawn list marker, which can't take
   events itself. It sits just outside the item's content box, where the bullet
   or number is painted. */
.fold-handle {
    position: absolute;
    right: 100%;
    top: 0;
    width: 1.6em;
    height: 1.5em;
    cursor: pointer;
}
li.foldable:has(> .fold-handle:hover)::marker,
li.foldable:has(> .fold-handle:focus-visible)::marker {
    color: var(--accent);
}
.fold-handle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
    border-radius: 3px;
}

/* The body is the wrapper holding everything a fold hides. Height is animated
   between 0 and its measured height; `fold-hidden` is the resting collapsed
   state, so nothing carries inline styles between animations. */
.fold-body.fold-animating {
    overflow: hidden;
    transition: height 0.22s ease, opacity 0.18s ease;
}
/* A tail fold's first part stays on the head's line, so it is a span and
   heights mean nothing to it: the owner animates instead (below) and the part
   only fades. Declared before .fold-hidden, which has the same specificity and
   must win when both apply. */
.fold-body.fold-inline { display: inline; }
.fold-body.fold-fading { transition: opacity 0.18s ease; }
.fold-body.fold-hidden { display: none; }
li > .fold-body { margin: 0; }

/* The owner of a tail fold while its own height travels. Kept distinct from
   .fold-animating on the body so the two never fight — and note that the
   parts under it deliberately do *not* get overflow:hidden, which would give a
   block part a formatting context and change the height being animated to.

   `clip` rather than `hidden` for exactly that reason, one level up: `hidden`
   establishes a block formatting context on the owner, so the margins of the
   paragraphs inside stop collapsing through it and it measures taller than it
   renders — +32px on a two-paragraph list item, measured. Animating to that
   figure jolts at the start and snaps back when the inline height is cleared.
   `clip` clips without the formatting context.

   The clip margin keeps the browser-drawn list marker, which is painted
   outside the padding box and would otherwise blink out for the length of the
   animation. It lets the tail spill the same distance below, but the tail is
   fading out while that happens. Browsers without either property just don't
   clip — a 220ms cosmetic difference, nothing more. */
.fold-owner-animating {
    overflow: clip;
    overflow-clip-margin: 2em;
    transition: height 0.22s ease;
}

.fold-capsule {
    display: none;
    box-sizing: border-box;
    height: 19px;
    padding: 0 9px;
    color: var(--accent-deep);
    background: var(--accent-bg);
    border: 1px solid var(--accent-faint);
    border-radius: 999px;
    margin-left: 0.35em;
    cursor: pointer;
    /* Optical alignment. `middle` centres on the x-height, which leaves the
       capsule sitting low against a line of caps and ascenders — noticeably so
       next to a header. Nudging up by 0.093em lands it on the cap-height
       centre instead; measured at 0.095em against 16px body text and 0.092em
       against a 24px header, so one constant serves every size. font-size is
       inherited for no other reason than to make that em track the text —
       nothing in the capsule is typeset (the dots are drawn). */
    font-size: inherit;
    vertical-align: middle;
    position: relative;
    top: -0.093em;
}
/* The dots are drawn, not typeset — see content-fold.js. Flex centring puts
   them exactly on the capsule's centreline in any font. */
.fold-capsule > span {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: currentColor;
}
/* A tail fold's capsule sits where the marker was — inside the head region,
   so not necessarily a direct child of the owner. CSS can't reach it from
   `.folded >`, so its state is a class of its own, set alongside `folded`. */
.foldable.folded > .fold-capsule,
.fold-capsule.fold-shown {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 3px;
    animation: fold-capsule-in 0.16s ease-out;
}
/* In a folded list item the capsule is all that's left, so it sits directly
   against the marker rather than after a gap where text used to be. A tail
   capsule follows the item's own text and keeps the gap. */
li.foldable.folded > .fold-capsule:not(.fold-capsule-tail) { margin-left: 0; }
.fold-capsule:hover {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--text-inverse);
}
.fold-capsule:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
@keyframes fold-capsule-in {
    from { opacity: 0; transform: scale(0.8); }
    to   { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .fold-body.fold-animating,
    .fold-body.fold-fading,
    .fold-owner-animating { transition: none; }
    .foldable.folded > .fold-capsule,
    .fold-capsule.fold-shown { animation: none; }
}

/* Space reserved below the content so collapsing the bottom-most fold can't
   clamp the reader's scroll position — see ContentFold._applyScrollPad, which
   sets the class and the custom property on the scroll container itself.
   A generated block rather than padding on that container: these scrollers are
   border-box, and on one with a definite height (a flex child such as the
   editor's preview pane or a shared notebook's main column) padding past that
   height grows the box instead of the scroll range. A pseudo-element also
   isn't a DOM node, so the editor's morphdom pass can't discard it. */
.fold-scroll-pad::after {
    content: '';
    display: block;
    height: var(--fold-scroll-pad, 0px);
}

/* ── Page builds (static/js/page-build.js) ──
 *
 * A \pausehere marker becomes an empty .pause-mark sentinel on every surface,
 * but only presentation mode acts on it: PageBuild tags the reveal roots and
 * steps through the stages. Everywhere else the sentinel renders as nothing
 * and the whole page reads normally.
 */

.pause-mark { display: none; }

/* Author cue, editor preview only. The marker is invisible by design, which
   makes a build hard to see in the surface where it's being written — so the
   preview shows where the stages fall and which animation each one uses. A
   marker on its own line reads as a slide break; one sitting mid-paragraph
   stays inline so it doesn't split the line. Which it is comes from the class
   render-utils.js put on it, not from a selector — `:only-child` counts
   elements, so a marker sharing its paragraph with text would match it. */
.preview-pane .pause-mark {
    display: inline-flex;
    align-items: center;
    vertical-align: baseline;
    padding: 0 0.45em;
    border: 1px dashed var(--border-5);
    border-radius: 999px;
    font-family: inherit;
    font-size: 0.68em;
    line-height: 1.7;
    letter-spacing: 0.02em;
    color: var(--text-5);
    background: var(--surface-2);
    user-select: none;
}
.preview-pane .pause-mark::before {
    content: '⏸ ' attr(data-pause);
}
.preview-pane .pause-mark-line {
    display: flex;
    justify-content: center;
    border-width: 0 0 1px 0;
    border-radius: 0;
    padding: 0;
    background: none;
    overflow: visible;
}
.preview-pane .pause-mark-line::before {
    padding: 0 0.6em;
    background: var(--surface-1);
    transform: translateY(0.5em);
}

/* Hidden content keeps its box: nothing reflows as a build runs, the reader's
   scroll position can't shift under them, and a slide-in lands exactly where
   the content was already sitting. This is what LaTeX does — it typesets the
   whole frame and simply doesn't ink the later material.

   Nested reveal roots inherit correctly: a stage-2 root inside stage-1 content
   is hidden by its ancestor until stage 1 arrives, and by its own class after
   that. */
.pause-hidden { visibility: hidden; }

/* Geometry and timing for the animated reveals. Literals with a custom
   property in front of them rather than entries in tokens.css: that file is
   generated by tools/build_themes.py and holds what a *theme* varies, and a
   build's motion is the same in every theme. Overridable all the same. */

/* A run of text after a mid-paragraph marker. Inline, so the sentence keeps
   its line — except under a slide, where a transform needs a block box. */
.pause-run { display: inline; }
.pause-run.pause-anim-slide { display: inline-block; }

@keyframes pause-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes pause-slide {
    from { opacity: 0; transform: translateX(var(--pause-slide-distance, 4em)); }
    to   { opacity: 1; transform: none; }
}
.pause-anim-fade {
    animation: pause-fade var(--pause-fade-duration, 320ms) ease-out both;
}
.pause-anim-slide {
    animation: pause-slide var(--pause-slide-duration, 480ms)
               cubic-bezier(0.22, 0.7, 0.3, 1) both;
}

/* One character (or one indivisible subtree — a formula, an image) of a
   typewriter reveal. Hidden rather than removed, again so the layout is
   settled before the first character lands. */
.pause-tw-off { visibility: hidden; }

@media (prefers-reduced-motion: reduce) {
    .pause-anim-fade, .pause-anim-slide { animation: none; }
}


/* ── Step indicators (static/js/step-indicator.js) ──
   The dot row that reports a position in a short sequence: the build steps of
   a page (docs/page_builds.md) and the frame of an image gallery
   (docs/document_view.md). One look wherever it appears, so the shape means
   the same thing each time; the surfaces that hold one — the presentation bar,
   the shared reading view's bottom bar, either page list — size and colour it
   from here in fullscreen.css and their own stylesheets. */
.mb-steps { display: flex; align-items: center; }

/* The button is a comfortable click target; the dot the reader sees is drawn
   inside it. Sizing the button to the dot would make a 7px target. */
.mb-step-dot {
    width: 18px; height: 18px; padding: 0; border: none; background: none;
    cursor: pointer; font-size: 0; flex: none;
    display: flex; align-items: center; justify-content: center;
    --mb-dot-size: 7px;
}
.mb-step-dot::after {
    content: ''; box-sizing: border-box;
    width: var(--mb-dot-size); height: var(--mb-dot-size); border-radius: 50%;
    border: 1px solid currentColor; color: var(--scrim);
    opacity: 0.5; transition: opacity 0.15s, background-color 0.15s;
}
.mb-step-dot:hover::after { opacity: 0.9; }
.mb-step-dot.active::after { background: currentColor; opacity: 1; }
.mb-step-dot:focus-visible {
    outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 50%;
}

/* The ‹ › buttons. They flank the dots as well as standing either side of the
   counter, so a reader who never touches the keyboard can still step. */
.mb-step-arrow {
    width: 16px; height: 18px; padding: 0; border: none; background: none;
    color: var(--scrim); cursor: pointer; flex: none;
    font-size: 0.95rem; line-height: 1; opacity: 0.55;
    display: flex; align-items: center; justify-content: center;
    transition: opacity 0.15s;
}
.mb-steps-dots .mb-step-arrow:first-child { margin-right: 1px; }
.mb-steps-dots .mb-step-arrow:last-child { margin-left: 1px; }

/* Where a caller asked for arrows, they are the control a reader reaches for —
   the step bar under a building page, in the presentation and in the shared
   reading view — so they are sized to be aimed at. The compact size above is
   for the pair a long build's counter carries whether it wanted them or not,
   sitting inside a page-list row that has no width to spare. */
.mb-steps-arrows .mb-step-arrow {
    width: 26px; height: 26px;
    font-size: 1.5rem; opacity: 0.65;
}
.mb-steps-dots.mb-steps-arrows .mb-step-arrow:first-child { margin-right: 3px; }
.mb-steps-dots.mb-steps-arrows .mb-step-arrow:last-child { margin-left: 3px; }
.mb-step-arrow:hover:not(:disabled) { opacity: 1; }
.mb-step-arrow:disabled { opacity: 0.2; cursor: default; }
.mb-step-arrow:focus-visible {
    outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 3px;
}

/* The counter a long sequence gets instead of a dot row. Tabular figures so
   the width doesn't twitch as the number changes — it can sit beside a title
   in a page list, and a shifting one would drag the title with it. */
.mb-steps-counter { gap: 1px; color: var(--scrim); }
.mb-step-count {
    font-size: 0.7rem; opacity: 0.75;
    font-variant-numeric: tabular-nums;
    min-width: 2.9em; text-align: center; user-select: none;
}
/* A sequence that isn't the one on screen has no position to report, so its
   counter reads as a size rather than as a place. */
.mb-steps-idle .mb-step-count { opacity: 0.5; }


/* ── Image galleries (static/js/image-gallery.js) ──
   ![caption;size=WxH;auto=N](a.png|b.png|c.png) — several images in the space
   of one. See docs/document_view.md. */
.mb-gallery {
    margin: 0.5rem 0; max-width: 100%;
    /* Room for one arrow either side of the frame: the button plus the gap
       between it and the image. */
    --mb-gallery-nav: 28px;
    --mb-gallery-nav-gap: 7px;
}
/* A gallery given ;size= shrink-wraps its frame; an unsized one is an ordinary
   block whose frame fills whatever is left between the arrows. */
.mb-gallery-sized { width: fit-content; }
/* The frame and its two arrows. Padded rather than flexed so the frame stays a
   plain block: the editor's drag-to-resize writes an inline width straight
   onto it, which a flex item with a grow factor would ignore. */
.mb-gallery-stage {
    position: relative; max-width: 100%;
    padding: 0 calc(var(--mb-gallery-nav) + var(--mb-gallery-nav-gap));
    /* A frame with nothing in it yet — images still loading, or broken — has
       no height, and the arrows centre on this box. */
    min-height: var(--mb-gallery-nav);
}
.mb-gallery-viewport { overflow: hidden; max-width: 100%; }
/* Items are top-aligned rather than stretched, so a frame with no declared
   height settles at the tallest image and stays there — the alternative,
   sizing to whichever image is showing, makes the page jump as the reader
   steps. */
.mb-gallery-track {
    display: flex; align-items: flex-start; height: 100%;
    transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Two class names, not one: the surfaces that render page content each style
   a bare `img` (`.shared-content img` has a vertical margin and a radius), and
   a margin here would push a frame out of a viewport with a declared height
   and shift the whole track. */
.mb-gallery .mb-gallery-img {
    flex: 0 0 100%; width: 100%; max-width: 100%;
    height: auto; margin: 0; border-radius: var(--radius-sm);
}
/* With a declared height the frame is fixed and the images are fitted into
   it, so frames of different shapes all sit in the same box. */
.mb-gallery .mb-gallery-viewport[style*="height"] .mb-gallery-img {
    height: 100%; object-fit: contain;
}

/* Beside the frame, never over it: an arrow on top of the picture hides part
   of what the reader came to look at, and one that only appears on hover
   doesn't say the gallery can be stepped at all.
   A white glyph on a dark translucent disc, legible against whatever the page
   background happens to be. A bare chevron in a text colour was too quiet to
   find; `--scrim-control` rather than `--scrim`, which is tuned for covering a
   whole page and comes out as light grey behind a white glyph on a white one.
   Centred vertically on the frame: the button is absolutely positioned inside
   a stage that spans the frame alone, with the dot row outside it, so `top:
   50%` is the middle of the picture rather than of picture-plus-dots. */
.mb-gallery-nav {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: var(--mb-gallery-nav); height: var(--mb-gallery-nav);
    padding: 0; border: none; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 1.05rem; line-height: 1; cursor: pointer;
    color: var(--text-inverse); background: var(--scrim-control);
    transition: background-color 0.15s;
}
.mb-gallery-nav:hover { background: var(--scrim-control-hover); }
.mb-gallery-nav:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
}
.mb-gallery-prev { left: 0; }
.mb-gallery-next { right: 0; }

/* Padded to match the stage, so the dots centre under the frame rather than
   under the frame-plus-arrows. */
.mb-gallery-steps {
    display: flex; justify-content: center; margin-top: 4px;
    padding: 0 calc(var(--mb-gallery-nav) + var(--mb-gallery-nav-gap));
}

/* What a gallery becomes where there is nothing to click (the PDF export, and
   — via latex_export.py — the manuscript): every frame, laid out as a grid. */
.mb-gallery-grid {
    display: grid; gap: 8px; margin: 0.5rem 0; max-width: 100%;
}
.mb-gallery-grid .mb-gallery-img {
    flex: none; width: 100%; height: auto; margin: 0;
}

@media (prefers-reduced-motion: reduce) {
    .mb-gallery-track { transition: none; }
}


/* ── YouTube video embeds ──
   Written as an image embed with a YouTube URL for its destination
   (![caption;size=WxH](https://youtu.be/...)); see docs/document_view.md.
   A bare iframe, so the editor's drag-to-resize picks it up like a PDF. */
.video-embed-container {
    display: block;
    margin: 0.5rem 0;
    max-width: 100%;
    /* The band the editor's drag-to-resize grabs. A live player consumes the
       mouse over its own rect, so without a margin of container around it
       there would be nowhere left to take hold of. Matches the 8px a 3D model
       container leaves for the same reason. */
    padding: 8px;
}
.video-embed {
    display: block;
    width: 100%;
    height: 100%;
    border: none;
    border-radius: var(--radius-md);
}

/* ── Cross-references ─────────────────────────────────────────────────────
   \label{key} marks a destination; \ref / \eqref / [text](#key) /
   [text](page:Title) link to one. See docs/intralinks.md. */

/* A \ref or \eqref reads as part of the sentence — "by Theorem 2.3" — so it
   stays the colour of the prose and only announces itself on hover. A
   markdown link is a link, and looks like one.

   Both classes are named on the \ref rule, and deliberately: every surface
   colours its links with a container rule (`.shared-content a` and friends)
   that ties with `a.eq-ref` on specificity and then wins on source order,
   being in a stylesheet or a <style> block loaded after this one. The pair
   puts the "reads as prose" rule out of that tie's reach. */
a.mb-xref { color: var(--accent); text-decoration: none; cursor: pointer; }
a.mb-xref:hover { text-decoration: underline; }
a.mb-xref.eq-ref { color: inherit; text-decoration: none; }
a.mb-xref.eq-ref:hover { color: var(--accent); text-decoration: underline; }

/* A reference to a label the notebook doesn't define. Loud on purpose: the
   usual cause is a page that was retitled or a label that was deleted, and a
   quiet failure would leave the author with a link to nowhere. */
.eq-ref-unresolved { color: var(--danger); cursor: help; }

/* A \label marking a spot rather than a numbered block: a scroll destination,
   not something to look at. It must occupy no space at all — one sitting
   mid-sentence would otherwise widen the line it is in. */
.mb-anchor { display: inline-block; width: 0; height: 0; overflow: hidden; }

/* Room above a target so it doesn't land flush against the top of the pane
   (or under a sticky header) when a link scrolls to it. */
.mb-anchor, .mb-theorem, .math-display, .rendered-content h1, .rendered-content h2,
.rendered-content h3, .rendered-content h4 { scroll-margin-top: 2rem; }

/* The landing flash. A background wash rather than an outline: it can't
   reflow anything, and it reads at a glance on a block as well as on a line. */
@keyframes mb-xref-hit {
    from { background-color: var(--accent-bg); }
    to   { background-color: transparent; }
}
.mb-xref-hit {
    animation: mb-xref-hit 1.6s ease-out;
    border-radius: 3px;
}
@media (prefers-reduced-motion: reduce) {
    .mb-xref-hit { animation-duration: 0.01ms; }
}
