/* ============================================================================
 * biography-modal.css — The Biographer's Study (FFV Modal Renaissance, Track A1).
 *
 * forging-modal-renaissance S1 (2026-07-21). The universal FFV BiographyModal
 * (`biography-modal.js`, ModalManager id 'biographyModal') dressed as a
 * biographer's writing desk at night: warm lamplight pooled on handmade paper,
 * sepia ink, gold leaf at the rim, the family's orchid only glinting there.
 * The two panes are the two pages of an open folio — the living manuscript
 * (AI) on the left, the family's page (Authorized) on the right.
 *
 * The deckle-edged folio silhouette + lamplight + dust motes are stamped by
 * the chassis kit (js/core/ffv-chassis.js, theme 'biographer'; tokens in
 * css/ffv-modal-theme.css). Without the chassis (mobile sheet, fallback) the
 * shell wears the same warm glass with a rectangular gold rim.
 *
 * FUNCTION BYTE-IDENTICAL with the pre-renaissance modal: every class, the
 * data-bio-action contract, panes/tabs/editor, ModalManager registration and
 * the mobile tab-toggle sheet are unchanged — this file only re-skins them.
 *
 * MODAL_STANDARD §4 compliance unchanged: visibility/animation via
 * `.ffa-modal`/`.ffa-modal__panel` (modal-foundation.css), z via the
 * `--tier-curator` token, single `.ffa-exit-btn` close.
 * ========================================================================== */

/* The overlay: the study at night — a warm dark room, lamplight vignette. */
.bio-modal {
    background:
        radial-gradient(ellipse 90% 70% at 44% 30%,
            rgba(64, 42, 16, 0.30), rgba(10, 8, 16, 0.78) 78%),
        rgba(10, 8, 16, 0.56);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    padding: 24px;
}

/* ── the shell: the desk ─────────────────────────────────────────────────
 * Base = warm umber-navy glass with a rectangular gold rim (the fallback).
 * Once the chassis stamps (.ffvc-framed) the SVG folio takes over painting
 * the body: the shell goes transparent, overflow opens so the deckle edges
 * breathe past the box, and content lifts above the chassis layer. */
.bio-modal-shell {
    position: relative;
    background: var(--ffv-glass-bg, linear-gradient(155deg, #2b1d12, #141322 70%, #241407));
    color: #f0e6cd;
    width: min(1100px, 100%);
    max-height: calc(100vh - 48px);
    border-radius: 18px 23px 16px 25px / 23px 16px 25px 18px;
    box-shadow:
        0 24px 70px rgba(6, 4, 10, 0.6),
        0 0 44px rgba(217, 154, 61, 0.10);
    display: flex;
    flex-direction: column;
    /* clips content only — resize chrome sits at positive insets inside this
       edge by contract (spatial-manager.css), so it can never be clipped */
    overflow: hidden;
}
/* rectangular gold-rim fallback (mask-composite — the family rim technique) */
.bio-modal-shell::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 2px;
    background: var(--ffv-rim-gradient, linear-gradient(125deg,
        rgba(240, 198, 106, 0.62), rgba(217, 154, 61, 0.5) 30%,
        rgba(138, 90, 46, 0.55) 56%, rgba(239, 63, 165, 0.4) 78%,
        rgba(240, 198, 106, 0.55)));
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    pointer-events: none;
    opacity: 0.75;
    z-index: 2;
}
/* chassis-framed: the SVG folio paints the desk; the box disappears */
.bio-modal-shell.ffvc-framed {
    background: transparent;
    box-shadow: none;
    overflow: visible;
    border-radius: 0;
    padding: 12px 16px 14px;
}
.bio-modal-shell.ffvc-framed::before { display: none; }
/* content lifts above the chassis layer — FFASpatial's .ffs-handle band is
   excluded: it must keep its own absolute edge placement (fleet resize-chrome
   contract, spatial-manager.css).
   ⛔ THE EXCLUSION NOW ALSO CARRIES THE ARMED BRACKETS (armed-bracket-perimeter
   S2). The corner cues are `.ffs-handle::after`, so this `:not()` is the only
   thing keeping them above the lifted content — drop it here and the brackets
   go dark in THIS room while staying lit everywhere else, which reads as
   "fixed in some rooms". There are FIVE content-lift sites in app/css
   (biography-modal, dressing-room, ffv-modal-theme, unified-media-modal,
   stories); they are one edit, and window-manager.smoke.cjs sweeps all of
   them so the set cannot drift by hand. */
.bio-modal-shell > :not(.ffv-chassis-layer):not(.ffs-handle) {
    position: relative;
    z-index: 1;
}

/* ── header: the nameplate under the lamp ───────────────────────────────── */
.bio-modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding: 22px 24px 14px;
    border-bottom: 1px solid rgba(240, 198, 106, 0.18);
    background: none;
}
.bio-modal-title {
    margin: 0;
    font-family: var(--ffv-display-font, 'Playfair Display', Georgia, serif);
    font-size: calc(1.58rem * var(--type-scale, 1));
    font-weight: 600;
    line-height: 1.18;
    color: #f3e2b8;
    letter-spacing: 0.012em;
    text-shadow: 0 0 22px rgba(240, 198, 106, 0.22);
}
.bio-modal-subtitle {
    margin-top: 3px;
    font-size: calc(0.74rem * var(--type-scale, 1));
    color: rgba(240, 198, 106, 0.72);
    text-transform: uppercase;
    letter-spacing: 0.17em;
}
.bio-modal-close,
.bio-modal-minimize { flex: 0 0 auto; }

/* the ink line that draws itself under the subject's name (once per open) */
.bio-ink-line {
    display: block;
    width: 214px;
    max-width: 100%;
    height: 12px;
    margin-top: 5px;
    overflow: visible;
}
.bio-ink-line path {
    fill: none;
    stroke: #d99a3d;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-dasharray: 240;
    stroke-dashoffset: 0;
    opacity: 0.85;
    filter: drop-shadow(0 0 4px rgba(217, 154, 61, 0.5));
}
.bio-ink-line.bio-ink-animate path {
    stroke-dashoffset: 240;
    animation: bio-ink-draw 1.2s 0.25s cubic-bezier(0.3, 0, 0.2, 1) forwards;
}
@keyframes bio-ink-draw { to { stroke-dashoffset: 0; } }

/* ── Watch Bio Story CTA (kept primary; now gold leaf) ──────────────────── */
.bio-modal-watch-cta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px 14px;
    padding: 12px 20px 0;
}
.bio-watch-cta-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    border: 1px solid rgba(185, 138, 55, 0.8);
    border-radius: 9px 12px 8px 13px;
    background: linear-gradient(160deg, #f0c66a, #d99a3d);
    color: #3a2607;
    font-size: calc(0.95rem * var(--type-scale, 1));
    font-weight: 700;
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(217, 154, 61, 0.35);
    transition: filter 0.15s ease;
}
.bio-watch-cta-btn:hover { filter: brightness(1.07); }
.bio-watch-cta-icon { font-size: calc(1.05rem * var(--type-scale, 1)); line-height: 1; }
.bio-watch-cta-hint {
    font-size: calc(0.8rem * var(--type-scale, 1));
    color: rgba(240, 230, 205, 0.72);
    font-style: italic;
    flex: 1 1 220px;
    min-width: 0;
}

/* ── tabs — the panel switch (S7: ONE panel at a time on EVERY width) ───── */
.bio-modal-tabs {
    display: flex;
    gap: 4px;
    padding: 10px 16px 0;
}
.bio-modal-tab {
    flex: 1 1 0;
    border: none;
    background: transparent;
    padding: 10px 8px;
    font-family: var(--ffv-body-font, 'Source Sans 3', sans-serif);
    font-size: calc(0.95rem * var(--type-scale, 1));
    font-weight: 600;
    color: rgba(240, 230, 205, 0.66);
    border-bottom: 2.5px solid transparent;
    cursor: pointer;
}
.bio-modal-tab.is-active {
    color: #f0c66a;
    border-bottom-color: #d99a3d;
}

/* ── body / the two pages of the folio ──────────────────────────────────────
 * LAYOUT CONTRACT (hardening-vault-faculties S8 / D8.1, Issue 13a).
 * The shell is a strict flex column from `.bio-modal-shell` all the way down
 * to `.bio-pane-doc`, and EVERY link in that chain carries `min-height: 0` —
 * without it a flex item refuses to shrink below its content and the overflow
 * pops out the bottom of the shell instead of scrolling inside it.
 *
 *   .bio-modal-shell (flex column, max-height: calc(100vh - 48px), overflow hidden)
 *     └ .bio-modal-body      flex: 1 1 auto · min-height: 0 · flex column
 *         ├ .bio-modal-panes flex: 1 1 auto · min-height: 0   ← the two pages
 *         │   └ .bio-pane    flex column · min-height: 0
 *         │       ├ .bio-pane-body     flex: 1 1 auto · min-height: 0
 *         │       │   └ .bio-pane-doc  flex: 1 1 auto · min-height: 0 · SCROLLS
 *         │       └ .bio-pane-controls flex: 0 0 auto · sticky footer  ← ALWAYS SEEN
 *         └ .bio-modal-changelog  flex: 0 0 auto · own max-height · scrolls
 *
 * The invariant this buys: the text column absorbs ALL the variable height, so
 * the control row's position is a function of the shell, never of the biography's
 * length. The old `.bio-pane-doc { max-height: 46vh }` cap did the opposite — it
 * bounded the text but let the pane keep growing, which is what pushed the
 * controls below the fold on a long biography (Joe's /listen screenshot).
 * The body no longer scrolls as a whole; the doc and the changelog scroll. */
.bio-modal-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
    overflow: hidden;
    -webkit-overflow-scrolling: touch;
    padding: 16px 20px 20px;
}
.bio-modal-panes {
    display: grid;
    grid-template-columns: 1fr;
    gap: 22px;
    flex: 1 1 auto;
    min-height: 0;
    /* D8.2: the edit takeover animates this track list to a single column */
    transition: grid-template-columns 260ms cubic-bezier(0.32, 0.72, 0.24, 1);
}
/* a page of handmade paper: warm parchment, feathered edge-shadow, faintly
 * irregular corners (the same irregularity degree as the family's chassis) */
.bio-pane {
    position: relative;
    border: 1px solid rgba(138, 90, 46, 0.4);
    border-radius: 5px 11px 6px 13px / 11px 5px 13px 7px;
    padding: 18px 18px 16px;
    display: flex;
    flex-direction: column;
    min-width: 0;
    /* D8.1 — the pane must be allowed to shrink; its doc child does the scrolling */
    min-height: 0;
    overflow: hidden;
    /* D8.2 — the departing/returning page fades rather than blinking */
    transition: opacity 220ms ease;
    background: linear-gradient(168deg, #f8f1e2, #f2e6c9 58%, #eadbba);
    box-shadow:
        inset 0 0 36px rgba(138, 90, 46, 0.14),
        inset 0 0 3px rgba(255, 252, 240, 0.9),
        0 8px 26px rgba(6, 4, 2, 0.42);
    color: var(--ffv-ink, #35291a);
}
.bio-pane-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 8px;
    border-bottom: 1px solid rgba(138, 90, 46, 0.22);
    padding-bottom: 8px;
}
.bio-pane-title {
    margin: 0;
    font-family: var(--ffv-display-font, 'Playfair Display', Georgia, serif);
    font-size: calc(1.14rem * var(--type-scale, 1));
    font-weight: 700;
    color: #4a3618;
    letter-spacing: 0.01em;
}
.bio-pane-tagline {
    font-size: calc(0.78rem * var(--type-scale, 1));
    font-style: italic;
    color: var(--ffv-ink-soft, #6d5a3e);
}

.bio-pane-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
    margin-bottom: 10px;
    min-height: 22px;
}
.bio-meta-item {
    font-size: calc(0.74rem * var(--type-scale, 1));
    color: var(--ffv-ink-soft, #6d5a3e);
}
.bio-meta-source { font-style: italic; }
.bio-canonical-tag {
    font-size: calc(0.7rem * var(--type-scale, 1));
    font-weight: 700;
    color: #f6eedd;
    background: #8a5a2e;
    border-radius: 999px;
    padding: 2px 9px;
    letter-spacing: 0.03em;
    box-shadow: inset 0 1px 0 rgba(255, 240, 210, 0.25);
}

/* freshness badges — warm study tints */
.bio-fresh {
    font-size: calc(0.7rem * var(--type-scale, 1));
    font-weight: 700;
    border-radius: 999px;
    padding: 2px 9px;
}
.bio-fresh--current { background: #e7ecd6; color: #4a641c; }
.bio-fresh--stale   { background: #f8e4ba; color: #8a5a10; }
.bio-fresh--unknown { background: #efe7d4; color: #8d7c5c; }
/* "Revised" is Fergi's hand on the page — the one orchid accent inside */
.bio-fresh--revised { background: #f7dcec; color: #a91e77; animation: bio-revised-pulse 1.4s ease-out 2; }

@keyframes bio-revised-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(239, 63, 165, 0.32); }
    70%  { box-shadow: 0 0 0 7px rgba(239, 63, 165, 0); }
    100% { box-shadow: 0 0 0 0 rgba(239, 63, 165, 0); }
}

/* D5 live-morph: "revising…" status + the focal-bio morph transition
 *
 * directing-canvas-situations S1 / Issue 4 — REVISING IS ORCHID, RENDERING IS
 * SEPIA. The revision state used to wear the same warm-sepia pill and the same
 * ring spinner as every other working indicator in this folio, and Joe read it
 * as a render. The two states are now separated on colour and iconography as
 * well as wording: orchid is Fergi's hand on the page (the same accent as the
 * ✨ Revised badge this state resolves into, .bio-fresh--revised), the nib
 * strokes rather than spins, and the render keeps the production chrome.
 * Do not re-tint this pill to the sepia family. */
.bio-morph-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: calc(0.7rem * var(--type-scale, 1));
    font-weight: 700;
    color: #a91e77;
    background: #f7dcec;
    border: 1px solid rgba(169, 30, 119, 0.28);
    border-radius: 999px;
    padding: 2px 9px;
}
/* the nib writes: a short stroke left-to-right, tipping as it goes — legible
   as WRITING at a glance, where a ring reads as WAITING */
.bio-morph-nib {
    display: inline-block;
    font-size: 1.05em;
    line-height: 1;
    color: #a91e77;
    animation: bio-nib-stroke 1.15s ease-in-out infinite;
}
@keyframes bio-nib-stroke {
    0%   { transform: translateX(-1.5px) rotate(-12deg); opacity: 0.55; }
    45%  { transform: translateX(1.5px)  rotate(4deg);   opacity: 1; }
    70%  { transform: translateX(1.5px)  rotate(4deg);   opacity: 1; }
    100% { transform: translateX(-1.5px) rotate(-12deg); opacity: 0.55; }
}
/* the AI page is lit from Fergi's side while the revision is in flight — the
   whole-pane cue that distinguishes this from a render (which dims the pane
   entirely behind the lamp overlay) */
.bio-pane--ai.is-morphing {
    box-shadow: inset 3px 0 0 0 rgba(169, 30, 119, 0.55),
                inset 0 0 34px rgba(169, 30, 119, 0.07);
}

/* Issue 3 — Approve & Render, held while a revision is in flight. It reads as
   a stated WAIT (paused glyph, orchid-tinted to name what it is waiting for),
   never as the render's own in-flight state. */
.bio-ctl--render-held {
    color: #8d5f7c;
    background: #f6e6f0;
    border-color: rgba(169, 30, 119, 0.3);
    cursor: not-allowed;
}
.bio-ctl--render-held .bio-ctl-icon { color: #a91e77; }
/* the focal AI body cross-fades as it morphs to the revised draft */
.bio-pane--ai .bio-pane-doc {
    transition: opacity 0.35s ease, filter 0.35s ease;
}
.bio-pane--ai.is-morphing .bio-pane-doc {
    opacity: 0.45;
    filter: blur(0.4px);
}
.bio-pane--ai.is-morphed .bio-pane-doc {
    animation: bio-morph-in 0.5s ease-out;
}
@keyframes bio-morph-in {
    from { opacity: 0.45; transform: translateY(3px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* directing-canvas-situations S1 / Issue 7 — the durable-draft offer.
   Unsaved words held on this device, surfaced in the read view with both doors.
   Deliberately quiet: a notice, not an alarm — nothing is wrong, something is
   simply waiting. It sits between the meta row and the page so it reads as a
   note pinned to the manuscript rather than as an error banner. */
.bio-draft-notice {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 9px;
    padding: 7px 11px;
    border-radius: 7px 10px 8px 11px;
    background: #fbf1d8;
    border: 1px dashed rgba(138, 90, 46, 0.5);
    color: #6d5a3e;
    font-size: calc(0.78rem * var(--type-scale, 1));
}
.bio-draft-notice-icon { font-size: 1em; line-height: 1; }
.bio-draft-notice-text { flex: 1 1 180px; }
.bio-draft-notice .bio-ctl { padding: 3px 9px; font-size: calc(0.74rem * var(--type-scale, 1)); }

/* document body — sepia ink on paper */
.bio-pane-body {
    flex: 1 1 auto;
    /* D8.1: `min-height: 120px` (the old value) was a FLOOR, and a floor on a
       flex item is exactly what stops it shrinking — it re-created the same
       overflow the 46vh cap did. The floor moves to the empty/editor states,
       which are the only ones that actually need one. */
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.bio-pane-doc {
    font-family: var(--ffv-body-font, 'Source Sans 3', 'Inter', sans-serif);
    font-size: calc(0.96rem * var(--type-scale, 1));
    line-height: 1.66;
    color: var(--ffv-ink, #35291a);
    /* D8.1: the text column absorbs the variable height and scrolls internally
       — replacing the fixed 46vh cap that pushed the controls below the fold */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: rgba(138, 90, 46, 0.45) transparent;
}
.bio-pane-doc::-webkit-scrollbar { width: 8px; }
.bio-pane-doc::-webkit-scrollbar-thumb {
    background: rgba(138, 90, 46, 0.4);
    border-radius: 4px;
}
.bio-pane-doc h2, .bio-pane-doc h3, .bio-pane-doc h4, .bio-pane-doc h5 {
    margin: 0.9em 0 0.35em;
    line-height: 1.25;
    font-family: var(--ffv-display-font, 'Playfair Display', Georgia, serif);
    color: #4a3618;
}
.bio-pane-doc p { margin: 0 0 0.85em; }

.bio-pane-editor {
    width: 100%;
    /* D8.1/D8.2: the editor fills the pane's remaining height rather than
       claiming a viewport-relative slab. `resize` is retired on desktop — the
       size ladder (− / +) and freeform drag-resize own the modal's geometry,
       and a draggable textarea corner inside a resizable window fights it. */
    flex: 1 1 auto;
    min-height: 180px;
    resize: none;
    border: 1px solid rgba(138, 90, 46, 0.45);
    border-radius: 6px 9px 7px 10px;
    padding: 12px;
    font-family: var(--ffv-body-font, 'Source Sans 3', 'Inter', sans-serif);
    font-size: calc(0.93rem * var(--type-scale, 1));
    line-height: 1.58;
    color: var(--ffv-ink, #35291a);
    background: #fbf6ea;
    box-shadow: inset 0 1px 6px rgba(138, 90, 46, 0.12);
}
.bio-pane-editor:focus {
    outline: none;
    border-color: #d99a3d;
    box-shadow: inset 0 1px 6px rgba(138, 90, 46, 0.12), 0 0 0 3px rgba(217, 154, 61, 0.22);
}

.bio-pane-empty {
    text-align: center;
    color: var(--ffv-ink-soft, #6d5a3e);
    font-style: italic;
    padding: 28px 12px;
    /* the one state that genuinely wants a floor (D8.1 moved it off .bio-pane-body) */
    min-height: 120px;
    flex: 1 1 auto;
    overflow-y: auto;
}
.bio-pane-empty-icon { font-size: calc(1.9rem * var(--type-scale, 1)); margin-bottom: 8px; }

/* ════════════════════════════════════════════════════════════════════════
 * D8.2 — THE EDIT TAKEOVER (Issue 13b, Joe: "Use good design sense in this.")
 *
 * Writing gets the whole desk. When a pane enters edit mode the folio turns
 * from two facing pages to ONE open page: the editing pane animates to the
 * full modal width and the other page steps aside (fades + slides out of the
 * reading light) rather than being yanked from the DOM.
 *
 * The state machine lives in biography-modal.js; CSS owns only the two
 * classes it sets:
 *   .bio-modal-panes.is-editfull  → single-column track, other page departing
 *   .bio-pane.is-editing          → the page being written on
 *
 * Why a class and not a re-render: `_render()` replaces innerHTML wholesale,
 * which kills any in-flight transition. So the JS renders the two-column
 * grid first (editor already swapped in, still narrow) and adds the class on
 * the NEXT frame — the browser then has a before-state to animate FROM. The
 * grid-template-columns transition is declared up at .bio-modal-panes.
 *
 * Kinship with is-morphing/is-morphed: the same discipline (opacity + a short
 * translate, both suppressed under prefers-reduced-motion) — the ready-made
 * transition vocabulary of this modal, applied to a second axis.
 * ════════════════════════════════════════════════════════════════════════ */
.bio-modal-panes.is-editfull {
    grid-template-columns: 1fr;
}
/* the page that steps aside — collapsed out of the grid flow, faded, and
   held non-interactive so a stray tab-stop can't land in a hidden pane */
.bio-modal-panes.is-editfull .bio-pane:not(.is-editing) {
    position: absolute;
    left: -9999px;
    top: 0;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
    overflow: hidden;
}
.bio-modal-panes.is-editfull { position: relative; }

/* the page being written on: a touch more light on the paper, and the
   deliberate widening reads as intent rather than as a layout jump */
.bio-pane.is-editing {
    animation: bio-editfull-open 260ms cubic-bezier(0.32, 0.72, 0.24, 1);
    box-shadow:
        inset 0 0 44px rgba(138, 90, 46, 0.12),
        inset 0 0 3px rgba(255, 252, 240, 0.95),
        0 10px 34px rgba(6, 4, 2, 0.5),
        0 0 0 1px rgba(217, 154, 61, 0.35);
}
@keyframes bio-editfull-open {
    from { opacity: 0.72; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* the editing pane's head gains a quiet mode line — "you are writing" */
.bio-editing-hint {
    font-size: calc(0.74rem * var(--type-scale, 1));
    font-style: italic;
    color: #8a5a2e;
    letter-spacing: 0.02em;
}
/* Save/Cancel are the ONLY controls in edit mode; the row is already the
   sticky pane footer from D8.1, so they are always reachable no matter how
   long the text runs. Right-align the pair so the primary act sits where the
   writing ends. */
.bio-pane.is-editing .bio-pane-controls { justify-content: flex-end; }
.bio-pane.is-editing .bio-pane-controls .bio-ctl { min-width: 104px; justify-content: center; }

@media (prefers-reduced-motion: reduce) {
    .bio-modal-panes { transition: none; }
    .bio-pane.is-editing { animation: none; }
}

/* ── controls: quill-and-ink buttons; gold leaf for the primary act ─────── */
/* D8.1 — the control row is a PANE FOOTER, not the tail of a scrolling column.
   `flex: 0 0 auto` keeps it out of the height negotiation (the doc absorbs all
   of it), and `position: sticky; bottom` pins it for the one case where the
   pane itself scrolls (mobile, where the single active pane is the scroller).
   The paper-coloured background is load-bearing: sticky content overlaps the
   text scrolling beneath it, so it must be opaque. */
.bio-pane-controls {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    flex: 0 0 auto;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid rgba(138, 90, 46, 0.25);
    position: sticky;
    bottom: 0;
    z-index: 2;
    /* Greg 2026-10-08: OPAQUE, as the comment above requires — the 0.86-alpha top let the phone's
       scrolled biography show through the composer ("Saturdays, Gate Harness…" under "You wrote"). */
    background: linear-gradient(180deg, #f2e6c9, #eadbba 42%);
    /* bleed to the pane's padding edge so scrolling text never shows beside it */
    margin-left: -18px;
    margin-right: -18px;
    margin-bottom: -16px;
    padding-left: 18px;
    padding-right: 18px;
    padding-bottom: 12px;
}
.bio-ctl {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border: 1px solid rgba(112, 78, 38, 0.42);
    background: rgba(251, 246, 234, 0.75);
    color: var(--ffv-ink, #35291a);
    border-radius: 7px 10px 8px 11px;
    padding: 7px 11px;
    font-family: var(--ffv-body-font, 'Source Sans 3', 'Inter', sans-serif);
    font-size: calc(0.82rem * var(--type-scale, 1));
    font-weight: 600;
    cursor: pointer;
    min-height: 36px;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.bio-ctl:hover:not(:disabled) {
    border-color: #d99a3d;
    background: rgba(240, 198, 106, 0.18);
}
.bio-ctl:disabled { opacity: 0.45; cursor: not-allowed; }
.bio-ctl--primary {
    background: linear-gradient(160deg, #f0c66a, #d99a3d);
    border-color: rgba(185, 138, 55, 0.9);
    color: #3a2607;
    box-shadow: 0 1px 6px rgba(217, 154, 61, 0.35);
}
.bio-ctl--primary:hover:not(:disabled) {
    background: linear-gradient(160deg, #f4d07e, #e0a447);
}
.bio-ctl-icon { font-size: calc(0.95rem * var(--type-scale, 1)); line-height: 1; }

/* Pin carries the wax: a faint seal shadow behind the control */
.bio-ctl--pin { position: relative; }

.bio-rendered-note {
    display: inline-flex;
    align-items: center;
    font-size: calc(0.78rem * var(--type-scale, 1));
    font-style: italic;
    color: var(--ffv-ink-soft, #6d5a3e);
    padding: 7px 4px;
}
.bio-production-badge {
    font-size: calc(0.72rem * var(--type-scale, 1));
    font-weight: 700;
    color: #6d4218;
    background: rgba(240, 198, 106, 0.35);
    border: 1px solid rgba(185, 138, 55, 0.5);
    border-radius: 999px;
    padding: 2px 9px;
}
.bio-spinner-inline {
    display: inline-block;
    width: 12px;
    height: 12px;
    border: 2px solid rgba(58, 38, 7, 0.3);
    border-top-color: #3a2607;
    border-radius: 50%;
    animation: bio-spin 0.7s linear infinite;
}

/* ── the wax seal (stamped on Pin as Authorized) ────────────────────────── */
.bio-wax-seal {
    position: absolute;
    top: 14px;
    right: 20px;
    width: 62px;
    height: 62px;
    border-radius: 47% 53% 51% 49% / 52% 48% 52% 48%;
    background: radial-gradient(circle at 38% 30%, #c05a48, #a33b2e 46%, #6f241a 86%);
    box-shadow:
        inset 0 2px 6px rgba(255, 220, 200, 0.35),
        inset 0 -4px 10px rgba(60, 12, 6, 0.55),
        0 4px 14px rgba(20, 4, 2, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--ffv-display-font, 'Playfair Display', Georgia, serif);
    font-size: calc(1.7rem * var(--type-scale, 1));
    font-weight: 700;
    color: #f2d8c2;
    text-shadow: 0 -1px 2px rgba(70, 16, 8, 0.8);
    pointer-events: none;
    z-index: 3;
    opacity: 0;
    animation:
        bio-seal-stamp 0.4s cubic-bezier(0.2, 1.4, 0.4, 1) forwards,
        bio-seal-fade 0.7s ease 1.15s forwards;
}
.bio-wax-seal::after {
    content: '';
    position: absolute;
    inset: 6px;
    border-radius: inherit;
    border: 1.5px solid rgba(242, 216, 194, 0.5);
}
@keyframes bio-seal-stamp {
    0%   { opacity: 0; transform: scale(1.9) rotate(-14deg); }
    60%  { opacity: 1; transform: scale(0.94) rotate(2deg); }
    100% { opacity: 1; transform: scale(1) rotate(0deg); }
}
@keyframes bio-seal-fade {
    to { opacity: 0; transform: scale(0.92); }
}

/* ── Marginalia (the change log): notes in the margin ───────────────────── */
.bio-modal-changelog {
    /* D8.1: a sibling of the panes inside the flex-column body — it takes its
       own bounded slice and scrolls internally rather than pushing the panes
       (and with them the control rows) out of the shell. */
    flex: 0 0 auto;
    max-height: 34%;
    overflow-y: auto;
    margin-top: 18px;
    border: 1px solid rgba(138, 90, 46, 0.38);
    border-left: 3px solid rgba(217, 154, 61, 0.7);
    border-radius: 4px 10px 6px 11px;
    padding: 14px 16px;
    background: linear-gradient(172deg, #f6eedd, #efe3c8);
    color: var(--ffv-ink, #35291a);
    box-shadow: inset 0 0 24px rgba(138, 90, 46, 0.1), 0 6px 20px rgba(6, 4, 2, 0.35);
}
.bio-changelog-head h4 {
    margin: 0 0 8px;
    font-family: var(--ffv-display-font, 'Playfair Display', Georgia, serif);
    font-style: italic;
    font-size: calc(1rem * var(--type-scale, 1));
    color: #4a3618;
}
.bio-changelog-list { list-style: none; margin: 0; padding: 0; }
.bio-changelog-row {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    padding: 7px 0;
    border-bottom: 1px dashed rgba(138, 90, 46, 0.3);
    font-size: calc(0.82rem * var(--type-scale, 1));
}
.bio-changelog-row:last-child { border-bottom: none; }
.bio-changelog-when { color: var(--ffv-ink-soft, #6d5a3e); min-width: 96px; font-style: italic; }
.bio-changelog-what { font-weight: 600; }
.bio-changelog-who { color: var(--ffv-ink-soft, #6d5a3e); margin-left: auto; }
.bio-changelog-empty, .bio-changelog-loading {
    color: var(--ffv-ink-soft, #6d5a3e);
    font-style: italic;
    font-size: calc(0.85rem * var(--type-scale, 1));
    margin: 0;
}

/* ── loading / error (on the dark desk, before the pages open) ──────────── */
.bio-modal-loading {
    padding: 56px 24px;
    text-align: center;
    color: rgba(240, 230, 205, 0.8);
    font-style: italic;
}
.bio-modal-spinner {
    width: 32px; height: 32px;
    margin: 0 auto 14px;
    border: 3px solid rgba(240, 198, 106, 0.22);
    border-top-color: #d99a3d;
    border-radius: 50%;
    animation: bio-spin 0.8s linear infinite;
}
@keyframes bio-spin { to { transform: rotate(360deg); } }
.bio-modal-error {
    padding: 40px 20px;
    text-align: center;
    color: rgba(240, 230, 205, 0.8);
}
.bio-modal-error-icon { font-size: calc(2rem * var(--type-scale, 1)); margin-bottom: 10px; }

/* ── toast ──────────────────────────────────────────────────────────────── */
.bio-modal-toast {
    position: fixed;
    left: 50%;
    bottom: 32px;
    transform: translateX(-50%) translateY(10px);
    background: linear-gradient(160deg, #2b1d12, #1c1526);
    border: 1px solid rgba(240, 198, 106, 0.4);
    color: #f3e2b8;
    padding: 11px 18px;
    border-radius: 9px 12px 8px 13px;
    font-size: calc(0.88rem * var(--type-scale, 1));
    z-index: var(--z-modal-critical, 100001);
    opacity: 0;
    transition: opacity 200ms ease, transform 200ms ease;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
    pointer-events: none;
}
.bio-modal-toast.is-visible { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ── motion discipline ──────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    .bio-pane--ai .bio-pane-doc,
    .bio-pane--ai.is-morphed .bio-pane-doc,
    .bio-fresh--revised { transition: none; animation: none; }
    /* the nib holds its written position — the state stays legible by colour,
       glyph and wording without the stroke (Issue 4's other three axes) */
    .bio-morph-nib { animation: none; transform: none; opacity: 1; }
    .bio-ink-line.bio-ink-animate path { animation: none; stroke-dashoffset: 0; }
    .bio-wax-seal { animation: none; opacity: 0.92; }
}

/* ════════════════════════════════════════════════════════════════════════
 * Desktop: both pages side-by-side, tabs hidden.
 * Mobile (≤767px): single active page, tab-toggle visible, full-screen sheet
 * (warm glass fallback — the chassis never stamps on mobile).
 *
 * U7 (2026-08-14): was 769/768, which classified a 768px-wide viewport as MOBILE
 * here and as desktop in all 36 other mobile stylesheets. Now the canonical
 * 767/768 pair. (The 1025px gate in biography-modal.js:1974 is NOT this drift —
 * it is the bio-duo scene's own deliberate large-desktop gate and is left alone.)
 * ════════════════════════════════════════════════════════════════════════ */
/* S7 (reforging-biography-folio): ONE panel at a time on every width — Joe's model,
   2026-09-26: "One panel at a time (AI or Authorized)". The folio builds only the active
   page, so the switch is visible everywhere and the page takes the whole desk. */
@media (min-width: 768px) {
    .bio-modal-tabs { display: flex; max-width: 560px; padding: 10px 20px 0; }
    .bio-modal-panes { grid-template-columns: 1fr; }
}

@media (max-width: 767px) {
    /* S8 (reforging-biography-folio): the folio sits BETWEEN the phone's two chrome bands — the
       app's chrome contract (mobile.css § `.ffa-mobile-modal-chrome`), stated here with the same
       tokens rather than by class because the folio's overlay is also its backdrop. Measured on
       the served origin at 390×844 before S8, the overlay ran 0→844 UNDER both bands:
         · the fixed top nav (0–56, z 11500) covered the folio's own ✕ at y 22, so a tap on
           "Close" landed on the header's Fergi AI button — the member could not close the
           biography, and opened Fergi instead;
         · the control panel (796–844, z 11000) covered the sticky composer and controls.
       The folio now OWNS the control panel while it is open (Say It on its own surface —
       biography-modal.js `_takeTheBar`), so it must stop at it, never paint over it. Both tokens
       already carry their safe-area insets, so the shell adds none of its own. */
    .ffa-modal.bio-modal {
        top: var(--mobile-top-bar-total-height, calc(56px + env(safe-area-inset-top, 0px)));
        bottom: var(--mobile-tab-bar-total-height, calc(48px + env(safe-area-inset-bottom, 0px)));
        padding: 0;
    }
    .bio-modal-shell {
        width: 100%;
        max-width: 100%;
        height: 100%;
        max-height: 100%;
        border-radius: 0;
        padding-top: 0;
        padding-bottom: 0;
    }
    .bio-modal-panes { grid-template-columns: 1fr; }
    /* Tab-toggle drives which pane is visible. */
    .bio-modal-shell--active-ai .bio-pane--authorized { display: none; }
    .bio-modal-shell--active-authorized .bio-pane--ai { display: none; }
    /* D8.1 — on mobile the single visible pane IS the scroller: the whole pane
       scrolls (head + meta + text) and the sticky control footer rides at the
       bottom of it. That is why `.bio-pane-controls` is sticky rather than
       merely a flex-end child; on desktop the doc scrolls and the footer is
       already fixed by the flex chain. */
    .bio-pane { overflow-y: auto; }
    .bio-pane-doc { overflow-y: visible; flex: 0 0 auto; }
    /* S8: the body must NOT shrink here. With the desktop's `flex: 1 1 auto; min-height: 0` it
       collapsed to 0 px on the phone (measured on the served origin, a 1,913-word biography:
       body 0 px, doc 8,149 px spilling out of it), so the text overflowed its box and the
       sticky footer sat right after the empty body — at the TOP of the content — painting
       over the words instead of riding the bottom. The body now takes the text's height, the
       pane scrolls it, and the footer pins to the bottom as D8.1 intended. */
    .bio-pane-body { min-height: 0; flex: 0 0 auto; }
    .bio-pane-editor { min-height: 46vh; }
    /* the changelog gets real room on a phone rather than a third of the sheet */
    .bio-modal-changelog { max-height: 50%; }
}


/* ════════════════════════════════════════════════════════════════════════
 * reforging-biography-folio S7 — the folio rebuilt around the Biography model.
 * One panel, one composer, the record line, the highlighted change, Batch ↔ Apply,
 * the exit sheet, Version History. Motion is compositor-only and off under
 * prefers-reduced-motion; every state is legible by glyph + wording, not colour alone.
 * ════════════════════════════════════════════════════════════════════════ */

.bio-tab-published {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: calc(0.66rem * var(--type-scale, 1));
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #3a2607;
    background: linear-gradient(160deg, #f0c66a, #d99a3d);
    vertical-align: 1px;
}

/* One panel: the switch above already names the page, so the page's own heading gives its
   height back to the text — the tagline rides inline, small, beside where the title was. */
@media (min-width: 768px) {
    .bio-modal-panes--one .bio-pane-head {
        flex-direction: row;
        align-items: baseline;
        margin-bottom: 4px;
        padding-bottom: 4px;
    }
    .bio-modal-panes--one .bio-pane-head .bio-pane-title {
        position: absolute;
        width: 1px; height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }
}

/* the page takes the desk: a readable measure, centred, on wide canvases (the phone + tablet shapes; from
   1025 px the full-width rule just below takes over) */
.bio-modal-panes--one .bio-pane-doc {
    max-width: 74ch;
    width: 100%;
    margin: 0 auto;
}

/* freeing-biography-folio S3 — THE WHOLE DISPLAY (#1). Joe 2026-10-15, walking his biography in desktop Chrome:
   use the full display width so as much of the biography as possible shows at once. Joe's directive SUPERSEDES
   the 10-14 folio redesign's readable-measure choice (the 74ch cap above, and the shell's min(1100px, 100%)).
   Long lines stay readable by setting the text in COLUMNS of ~40 characters that fill the page's height and
   run sideways: 1440 px shows three columns of the biography at once, 1920 px four, where the measure showed
   one. (A bigger type step was the alternative; at 1920 it still gives ~150-character lines, or shows less.)
   A mouse wheel reads across the columns (biography-modal.js `_dfWireColumns`); a change is brought into view
   sideways (`_revealChange`). Only from 1025 px — where the bio-duo scene gives the folio the whole canvas —
   so the phone (390), tablet (768) and iPad (1024) shapes are unchanged. The direct editor and Version
   History are other elements and keep their own width. */
@media (min-width: 1025px) {
    .bio-modal-shell { width: 100%; height: calc(100vh - 48px); }
    .bio-modal-panes--one .bio-pane-doc {
        max-width: none;
        margin: 0;
        column-width: 40ch;
        column-gap: 3em;
        column-rule: 1px solid rgba(138, 90, 46, 0.22);
        column-fill: auto;
        overflow-x: auto;
        overflow-y: hidden;
        overscroll-behavior-x: contain;
        padding-bottom: 6px;               /* the sideways scrollbar sits under the text, not on it */
    }
    .bio-modal-panes--one .bio-pane-doc::-webkit-scrollbar { height: 8px; }
    /* a heading never ends a column on its own */
    .bio-modal-panes--one .bio-pane-doc h2, .bio-modal-panes--one .bio-pane-doc h3,
    .bio-modal-panes--one .bio-pane-doc h4, .bio-modal-panes--one .bio-pane-doc h5 { break-after: avoid; }
    .bio-modal-panes--one .bio-pane-doc > :first-child { margin-top: 0; }
    /* more columns off-screen → the edge fades (biography-modal.js toggles these as the reader scrolls) */
    .bio-modal-panes--one .bio-pane-doc.bio-doc-more-right {
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 64px), rgba(0, 0, 0, 0.12));
                mask-image: linear-gradient(to right, #000 calc(100% - 64px), rgba(0, 0, 0, 0.12));
    }
    .bio-modal-panes--one .bio-pane-doc.bio-doc-more-left {
        -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 64px), rgba(0, 0, 0, 0.12));
                mask-image: linear-gradient(to left, #000 calc(100% - 64px), rgba(0, 0, 0, 0.12));
    }
    .bio-modal-panes--one .bio-pane-doc.bio-doc-more-left.bio-doc-more-right {
        -webkit-mask-image: linear-gradient(to right, rgba(0, 0, 0, 0.12), #000 64px, #000 calc(100% - 64px), rgba(0, 0, 0, 0.12));
                mask-image: linear-gradient(to right, rgba(0, 0, 0, 0.12), #000 64px, #000 calc(100% - 64px), rgba(0, 0, 0, 0.12));
    }
}

/* the change the last comment made — ink that settles into a margin mark */
.bio-pane-doc .bio-changed {
    position: relative;
    border-radius: 2px 6px 6px 2px;
    border-left: 3px solid #d99a3d;
    margin-left: -11px;
    padding-left: 8px;
    background: linear-gradient(90deg, rgba(240, 198, 106, 0.46), rgba(240, 198, 106, 0.16));
    animation: bio-changed-settle 2.4s ease-out 1;
}
@keyframes bio-changed-settle {
    from { opacity: 0.35; }
    to   { opacity: 1; }
}
/* committing-biography-drafts S3: the draft face highlights EVERY paragraph its draft changed and repaints
   often (status lines, the bar), so the settle would replay on all of them at each repaint. Only the
   paragraph the latest change touched settles in; the others simply stay highlighted. */
.bio-pane--draft .bio-pane-doc .bio-changed { animation: none; }
.bio-pane--draft .bio-pane-doc .bio-changed[data-bio-changed-focus] { animation: bio-changed-settle 2.4s ease-out 1; }

.bio-session-line,
.bio-held-batch {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0 0 6px;
    padding: 5px 12px;
    border-radius: 6px 10px 7px 11px;
    border: 1px solid rgba(185, 138, 55, 0.55);
    background: rgba(240, 198, 106, 0.18);
    color: var(--ffv-ink, #35291a);
    font-size: calc(0.84rem * var(--type-scale, 1));
}
.bio-session-icon { font-weight: 700; color: #8a5a1e; }

.bio-rendered-note--pending { color: #7a4f14; font-style: normal; font-weight: 600; }

/* ── the composer's record lines ────────────────────────────────────────── */
.bio-composer .bio-ap-undo-chip { margin-top: 4px; }
.bio-composer .bio-ap-earlier {
    font-size: calc(0.76rem * var(--type-scale, 1));
    font-style: italic;
    color: var(--ffv-ink-soft, #6d5a3e);
}
.bio-composer .bio-ap-entry {
    padding: 6px 0 8px;
    border-bottom: 1px dashed rgba(138, 90, 46, 0.28);
}
.bio-composer .bio-ap-entry:last-of-type { border-bottom: none; }
.bio-composer .bio-ap-mode { font-weight: 700; color: #6d4218; }
.bio-composer .bio-ap-line--member {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.bio-composer .bio-ap-line--record {
    margin-top: 3px;
    display: flex;
    gap: 6px;
    align-items: baseline;
    font-size: calc(0.84rem * var(--type-scale, 1));
}
.bio-composer .bio-ap-entry--landed .bio-ap-line--record::before { content: '✓'; color: #2f7d4f; font-weight: 700; }
.bio-composer .bio-ap-entry--held .bio-ap-line--record::before { content: '⏸'; color: #8a5a1e; }
.bio-composer .bio-ap-entry--batched .bio-ap-line--record::before { content: '⧗'; color: #8a5a1e; }
.bio-composer .bio-ap-entry--failed .bio-ap-line--record { color: #8e2b1f; }
.bio-composer .bio-ap-entry--failed .bio-ap-line--record::before { content: '!'; font-weight: 700; }
.bio-composer .bio-ap-held,
.bio-composer .bio-ap-refused {
    margin: 4px 0 0 18px;
    padding: 0;
    font-size: calc(0.8rem * var(--type-scale, 1));
    color: var(--ffv-ink-soft, #6d5a3e);
}
.bio-composer .bio-ap-directive {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
    font-size: calc(0.82rem * var(--type-scale, 1));
}
.bio-composer .bio-ap-directive.is-kept { color: #2f7d4f; font-weight: 600; }
.bio-composer .bio-ap-batch {
    border: 1px solid rgba(112, 78, 38, 0.42);
    background: rgba(251, 246, 234, 0.75);
    color: var(--ffv-ink, #35291a);
    border-radius: 999px;
    padding: 4px 11px;
    font-size: calc(0.78rem * var(--type-scale, 1));
    font-weight: 600;
    cursor: pointer;
}
.bio-composer .bio-ap-batch.is-on {
    background: linear-gradient(160deg, #f0c66a, #d99a3d);
    border-color: rgba(185, 138, 55, 0.9);
    color: #3a2607;
}

/* ── Version History — it turns the page (takes the panel's place, whole height) ── */
.bio-versions-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-right: 4px;
}
.bio-versions-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
    padding-bottom: 8px;
    border-bottom: 1px solid rgba(138, 90, 46, 0.22);
}
.bio-versions-head h4 {
    margin: 0;
    flex: 1 1 auto;
    font-family: var(--ffv-display-font, 'Playfair Display', Georgia, serif);
    font-style: italic;
    font-size: calc(1rem * var(--type-scale, 1));
    color: #4a3618;
}
.bio-versions-close { min-height: 30px; padding: 3px 9px; }
.bio-versions-sub {
    margin: 12px 0 4px;
    font-size: calc(0.78rem * var(--type-scale, 1));
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #8a5a1e;
}
.bio-version-list { list-style: none; margin: 8px 0 0; padding: 0; }
.bio-version-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 6px 0;
    border-bottom: 1px dashed rgba(138, 90, 46, 0.3);
    font-size: calc(0.84rem * var(--type-scale, 1));
}
.bio-version-row .bio-ctl { min-height: 30px; padding: 3px 9px; }
.bio-version-current { font-weight: 700; color: #2f7d4f; }
.bio-changelog-detail { color: var(--ffv-ink-soft, #6d5a3e); flex: 1 1 200px; min-width: 0; }
/* Greg 2026-10-08: in the version list the comment that made a version sits UNDER its label, lighter —
   it used to run straight on from the label ("…from a comment“Gate Harness…"). */
.bio-version-row .bio-changelog-what .bio-changelog-detail { display: block; font-weight: 400; font-style: italic; margin-top: 2px; }
.bio-directive-list { margin: 4px 0; padding-left: 18px; font-size: calc(0.84rem * var(--type-scale, 1)); }
.bio-diff {
    flex: 1 1 100%;
    margin: 6px 0 2px;
    padding: 8px 10px;
    background: rgba(255, 252, 240, 0.7);
    border-radius: 6px;
    font-size: calc(0.86rem * var(--type-scale, 1));
    line-height: 1.5;
}
.bio-diff p { margin: 0 0 0.6em; }
.bio-diff .bio-diff-same { color: var(--ffv-ink-soft, #6d5a3e); }
.bio-diff ins { background: rgba(47, 157, 111, 0.2); text-decoration: none; border-bottom: 2px solid #2f7d4f; }
.bio-diff del { background: rgba(163, 59, 46, 0.14); color: #8e2b1f; }

/* ── Before you go: Apply and save · Discard · Keep working ─────────────── */
#biographyModalOverlay .bio-modal-shell > .bio-exit-sheet {
    position: absolute;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(12, 8, 4, 0.55);
    padding: 20px;
}
.bio-exit-card {
    max-width: 520px;
    width: 100%;
    padding: 20px 22px 18px;
    border-radius: 7px 12px 8px 13px;
    background: linear-gradient(168deg, #f8f1e2, #f2e6c9 58%, #eadbba);
    border: 1px solid rgba(138, 90, 46, 0.45);
    color: var(--ffv-ink, #35291a);
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
}
.bio-exit-card h3 {
    margin: 0 0 8px;
    font-family: var(--ffv-display-font, 'Playfair Display', Georgia, serif);
    font-size: calc(1.15rem * var(--type-scale, 1));
}
.bio-exit-card p { margin: 0 0 8px; font-size: calc(0.9rem * var(--type-scale, 1)); }
.bio-exit-explain { color: var(--ffv-ink-soft, #6d5a3e); }
.bio-exit-working { display: flex; gap: 8px; align-items: center; font-weight: 600; color: #6d4218; }
.bio-exit-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

@media (prefers-reduced-motion: reduce) {
    .bio-pane-doc .bio-changed,
    .bio-pane--draft .bio-pane-doc .bio-changed[data-bio-changed-focus] { animation: none; }
}
@media (forced-colors: active) {
    .bio-pane-doc .bio-changed { border-left: 3px solid Highlight; box-shadow: none; }
    .bio-tab-published { border: 1px solid CanvasText; }
}


/* ════════════════════════════════════════════════════════════════════════
 * reforging-biography-folio S8 — the folio on the PHONE: the text stays readable while the
 * composer is open. Measured on the served origin at 390×844 once the composer mounted there:
 * header 132 px + switch 76 px + the sticky controls 520 px left 31 px of biography on screen,
 * and the paragraph a comment had just changed was not visible at all (the text scrolled
 * BEHIND the opaque footer — its tagline and "Watch" ghosted through). Joe's model says the
 * change is "shown immediately"; on a phone that needs the text to be there to show.
 * So on the phone: a one-line header, a slim switch, the disclosure only until the member
 * engages, the comment log capped (the latest entry; every earlier one is in Version History),
 * the chips on one scrolling row, and the pane controls compact. Every control keeps a ≥44 px
 * target (census C6). The desktop folio is untouched (S7's layout).
 * ════════════════════════════════════════════════════════════════════════ */
@media (max-width: 767px) {
    .bio-modal-header { padding: 6px 10px 6px 12px; gap: 8px; align-items: center; }
    .bio-modal-title {
        font-size: calc(1.15rem * var(--type-scale, 1));
        line-height: 1.2;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .bio-modal-title-block { min-width: 0; flex: 1 1 auto; }
    .bio-modal-subtitle,
    .bio-modal-title-block .bio-ink-line { display: none; }
    .bio-modal-tabs { padding: 2px 10px 0; }
    /* the switch names the page (as on desktop ≥768): the page's own title and tagline give
       their height back to the text; the session note reads small */
    .bio-modal-panes--one .bio-pane-head { margin-bottom: 2px; padding-bottom: 2px; border-bottom: none; }
    .bio-modal-panes--one .bio-pane-head .bio-pane-title,
    .bio-modal-panes--one .bio-pane-head .bio-pane-tagline {
        position: absolute;
        width: 1px; height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }
    .bio-session-line { font-size: calc(0.78rem * var(--type-scale, 1)); padding: 6px 10px; margin: 4px 0; }
    .bio-modal-tabs [data-bio-tab] { min-height: 44px; padding-top: 4px; padding-bottom: 4px; }

    .bio-pane-controls { gap: 5px; margin-top: 8px; padding-top: 8px; padding-bottom: 8px; }
    .bio-pane-controls > .bio-ctl {
        min-height: 44px;
        padding: 4px 10px;
        font-size: calc(0.78rem * var(--type-scale, 1));
    }
    .bio-pane-aperture { flex: 0 0 100%; }
    .bio-composer .bio-ap-disclosure { display: block; line-height: 1.35; }
    .bio-composer.is-engaged .bio-ap-disclosure { display: none; }
    .bio-composer .bio-ap-log { max-height: 88px; overflow-y: auto; overscroll-behavior: contain; }
    .bio-composer .bio-ap-chips {
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    .bio-composer .bio-ap-chips > * { flex: 0 0 auto; min-height: 44px; }
    /* Batch Comments is Joe's model, not a stem: it leads the row so it is never scrolled off */
    .bio-composer .bio-ap-chips > .bio-ap-batch { order: -1; }
}

/* S8 — the TABLET band (768–1024 px: the iPad, a phone on its side). The bio-duo scene only
   takes the folio from 1025 px, so here FFASpatial seats it at the shell's NATURAL size, and the
   natural height was whatever was left after the text column shrank to nothing: measured on the
   served origin at 820×1180, a 610 px window in mid-screen with the header, switch and composer
   filling it and 0 px of biography (`.bio-pane-doc` clientHeight 0). The shell now asks for
   the viewport's height, so the placement reads a window a member can read in; FFASpatial
   still owns where it sits and how it is resized. */
@media (min-width: 768px) and (max-width: 1024px) {
    .bio-modal-shell { height: calc(100vh - 48px); }
    /* Greg 2026-10-08 — measured on the physical iPad (820×1061, Safari): after ONE comment the
       composer's disclosure + "You wrote" + "Recorded…" + take-back + two rows of chips grew until
       the biography showed ~3 lines and its newest sentence was cut mid-line. The phone's compact
       composer now applies to the tablet band too: the disclosure steps aside once the member is
       engaged, the log scrolls inside ~7.5 lines, and the chips keep to one scrolling row. */
    .bio-composer.is-engaged .bio-ap-disclosure { display: none; }
    /* the disclosure's flex row became three squeezed, wrapping columns on the iPad ("Tell Fergi |
       about" · "Trudi C | Ferguson" · "— say it or…") — it reads as one sentence, as on a phone */
    .bio-composer .bio-ap-disclosure { display: block; line-height: 1.35; }
    .bio-composer .bio-ap-log { max-height: 7.5em; overflow-y: auto; overscroll-behavior: contain; }
    .bio-composer .bio-ap-chips {
        flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch; scrollbar-width: none;
    }
    .bio-composer .bio-ap-chips > * { flex: 0 0 auto; min-height: 44px; }
    .bio-composer .bio-ap-chips > .bio-ap-batch { order: -1; }
    .bio-session-line { padding: 6px 10px; margin: 4px 0; }
    /* the window's own −/+ size steps were 30×30 — under the 44 px touch floor */
    .bio-modal-shell .fv-step-btn { min-width: 44px; min-height: 44px; }
}

/* Greg 2026-10-08 — on a tablet or phone the session banner ("Saved as a new version … re-rendering …
   Version History can take it back") repeats what the composer's landed line ("… now shows it",
   ↩︎ Take that change back) and the footer's render chip already say; it took ~90 px from the text.
   Measured on the iPad: 15 lines of biography before a comment, 8 after. It steps aside once a landed
   comment is on screen. And the take-back chip meets the touch floor (was 150×33). */
@media (max-width: 767px) {
    /* Greg 2026-10-08 — READING ON A PHONE. Measured on the physical iPhone (402×660) mid-way through
       Trudi's 29,661-char biography: ~3 lines of text between the Watch row + tagline + tabs above and
       the composer (two-line disclosure, chips, render chip) below. Nothing a member needs is removed:
       the disclosure teaches only on an EMPTY biography (the field's placeholder says the rest), the
       Watch button stays without its tagline, and the render chip shows only while a render is pending. */
    .bio-watch-cta-hint { display: none; }
    .bio-pane:has(.bio-pane-doc) .bio-composer .bio-ap-disclosure { display: none; }
    .bio-rendered-note:not(.bio-rendered-note--pending) { display: none; }
    /* the folio is a full sheet on a phone: a window-size ladder resizes nothing useful and squeezed
       the person's name to "Browser Gate H…" (Greg 2026-10-08, iPhone glass) */
    .bio-modal-header .fv-size-ladder { display: none; }
    /* one line on a phone — the two-line pill cost the text ~30 px */
    .bio-rendered-note { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
                         font-size: calc(0.74rem * var(--type-scale, 1)); }
}
@media (max-width: 1024px) {
    .bio-modal-shell:has(.bio-ap-entry--landed) .bio-session-line { display: none; }
    .bio-composer .bio-ap-undo-chip { min-height: 44px; }
    /* the window's −/+ size steps were 30×30 on the phone as well */
    .bio-modal-shell .fv-step-btn { min-width: 44px; min-height: 44px; }
}

/* S8 — a phone ON ITS SIDE (844×390: `FFAMobile.isPocket()` true, yet the desktop path — the
   census's finding F7). The window is ~330 px tall and the composer footer alone is ~290 px, so
   a sticky footer left 31 px of text (measured). Here the pane scrolls as a whole with the
   footer IN FLOW: the biography first, the composer after it — nothing painted over the words.
   Same query `mobile-detect.js` uses for the pocket-landscape case. */
@media (orientation: landscape) and (max-height: 480px) and (pointer: coarse) {
    .bio-modal-header { padding: 6px 12px; align-items: center; }
    .bio-modal-subtitle,
    .bio-modal-title-block .bio-ink-line { display: none; }
    .bio-modal-title { font-size: calc(1.1rem * var(--type-scale, 1)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .bio-modal-tabs { padding-top: 0; }
    .bio-pane { overflow-y: auto; }
    .bio-pane-body { min-height: 0; flex: 0 0 auto; }
    .bio-pane-doc { overflow-y: visible; flex: 0 0 auto; }
    .bio-pane-controls { position: static; }
}

/* ══════════════════════════════════════════════════════════════════════════
 * THIN — the folio born thin (reforging-biography-folio S9; slim-down D2
 * extended to the folio as the 13th dressable room).
 *
 * The shared thin frame (rim, sill, colophon, sweep, forced colours) is
 * room-windows.css's — this block only gives the folio its palette and its
 * ribbon. Palette: the Biographer's Study, cut thin — gold leaf at the rim,
 * a bronze return, the lamplight as the inner glow, and the family's orchid
 * ONLY in the glint that crosses the rim (the study's own rule: "the family's
 * orchid only glinting there"). The signature motif is the folio's own glyph
 * (launcher-biography, the open folio), mounted by RoomWindows.applyDress.
 *
 * The ribbon: the lamp-lit nameplate (105 px: a 1.58 rem name, the ink line,
 * the BIOGRAPHIES subtitle) folds onto one line — the name, then "Biographies"
 * inline in small caps — and the panel switch loses its air. The ink line is
 * the Bespoke study's flourish and stands down here.
 * ========================================================================== */
.bio-modal-shell {
    --thin-rim-a: #f0c66a;
    --thin-rim-b: #a8732c;
    --thin-glow: rgba(217, 154, 61, 0.40);
    --thin-surface: #17121a;
    --thin-glint: rgba(247, 128, 196, 0.9);
}
.bio-modal-shell.ffrw-stripped.ffrw-thin .bio-modal-header {
    align-items: center;
    gap: 12px;
    padding: 4px 12px 4px 14px;
    border-bottom: 1px solid transparent;
    border-image: linear-gradient(90deg, transparent 0,
        var(--thin-rim-a) 16%, var(--thin-rim-b) 62%, transparent 100%) 1;
}
.bio-modal-shell.ffrw-stripped.ffrw-thin .bio-modal-title-block {
    display: flex;
    align-items: baseline;
    gap: 12px;
    min-width: 0;
    flex: 1 1 auto;
}
.bio-modal-shell.ffrw-stripped.ffrw-thin .bio-modal-title {
    font-size: calc(1.15rem * var(--type-scale, 1));
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 0 1 auto;
}
.bio-modal-shell.ffrw-stripped.ffrw-thin .bio-ink-line { display: none; }
.bio-modal-shell.ffrw-stripped.ffrw-thin .bio-modal-subtitle {
    margin-top: 0;
    font-size: calc(0.86rem * var(--type-scale, 1));
    text-transform: none;
    font-variant-caps: all-small-caps;
    letter-spacing: 0.14em;
    white-space: nowrap;
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}
.bio-modal-shell.ffrw-stripped.ffrw-thin .bio-modal-tabs { padding: 4px 12px 0; }
.bio-modal-shell.ffrw-stripped.ffrw-thin .bio-modal-tab { padding: 6px 8px; }
@media (forced-colors: active) {
    .bio-modal-shell.ffrw-stripped.ffrw-thin .bio-modal-header {
        border-image: none;
        border-bottom-color: CanvasText;
    }
}


/* ═══ The phone reading room (Greg, 2026-10-02) ═══
   Joe: members reading and correcting their own and their family's biographies is "the most important factor in family
   recruitment". On a 390×844 phone the sticky footer (invitation + composer + chips + five tools in two rows) covered
   293 of the pane's 501 px: ~208 px of biography visible. In portrait on a phone the tools now sit behind "More ⋯"
   in the chip row (bio-aperture.js); the composer and Say It stay pinned. Hidden only where the More chip exists, so a
   panel without the chip row keeps its tools. Landscape phones already moved the footer into the flow (S8 F7). */
.bio-ap-more { display: none; }
@media (max-width: 767px) and (orientation: portrait) {
    /* G-053 (Greg, 2026-10-02): a member with no biography yet (most of the family) read "…and the
       biography is written from the" and lost the end of the sentence under the composer. The empty
       state's top padding and icon give back the room. */
    .bio-pane-empty { padding: 6px 12px 10px; min-height: 0; }
    .bio-pane-empty-icon { font-size: calc(1.35rem * var(--type-scale, 1)); margin-bottom: 2px; }
    .bio-composer .bio-ap-chiprow > .bio-ap-more { display: inline-flex; align-items: center; }
    /* G-054 (Greg, 2026-10-02): the chip row scrolls sideways on a phone, and "More ⋯" is its last
       child, so it sat at x≈609 on a 390 px screen: the five tools it hides (G-049) were reachable
       only by a swipe nobody would think to make. Same rule as Batch Comments ("never scrolled
       off"), the other way round: More is pinned to the row's right edge while the chips scroll
       beneath it. */
    /* Greg 2026-10-08 (v1.1.344): More is no longer inside the scrolling row. Sticky over the row it sat on a
       passing chip's pill border ("A( More"); absolutely positioned out of it, iPhone WebKit's touch scroller
       clipped it away entirely (v1.1.343, measured on FergiPhone). Now it is the row's sibling in
       .bio-ap-chiprow, so the chips scroll and stop at a clean edge beside it. */
    .bio-composer .bio-ap-chiprow > .bio-ap-more { flex: 0 0 auto; min-height: 44px; }
    .bio-pane:not(.is-editing) .bio-pane-controls:has(.bio-ap-more):not(.show-tools) > .bio-ctl { display: none; }
}

/* ═══ The folio on a touch desktop shell (the iPad), Greg G-055, 2026-10-02 ═══
   Measured in the family-device matrix (iPad Mini, the desktop shell under a finger): the composer's chips
   (Correct a fact · Add a memory · Change the tone · Batch Comments · Open full conversation) and the
   tool line (Edit · Version History …) were 26–36 px tall. min-width 768, not 769: the iPad Mini is
   exactly 768 wide and gets the desktop shell (the phone block is max-width 767). Text buttons simply get the 44 px floor; the
   phone block above already gives the phone theirs. */
@media (pointer: coarse) and (min-width: 768px) {
    html .bio-composer .bio-ap-chip,
    html .bio-composer .bio-ap-batch,
    html .bio-composer .bio-ap-expand,
    html .bio-pane-controls > .bio-ctl { min-height: 44px; }
}

/* ════════════════════════════════════════════════════════════════════════
 * committing-biography-drafts S1 — THE DRAFT FACE (behind a flag; spec
 * docs/biographical-revision/BIOGRAPHY_FOLIO_REDESIGN_2026-10-14.md § 4).
 * Three zones: the text (draft banner + highlighted changes) · the typing input · the control panel
 * (Discard · Say It · Commit Changes · More). Every rule is scoped to the draft face's own classes, so
 * the classic face is untouched while the flag is off.
 * ════════════════════════════════════════════════════════════════════════ */

/* the tab's dot: uncommitted changes on that panel */
.bio-tab-dot {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-left: 6px;
    padding: 0 6px;
    border-radius: 999px;
    font-size: calc(0.7rem * var(--type-scale, 1));
    font-weight: 700;
    color: #3a2607;
    background: #f0c66a;
    vertical-align: 1px;
}
.bio-tab-dot > span { color: #a3471e; font-size: 0.8em; }

/* ✎ Draft — N changes not yet committed */
.bio-df-banner {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 8px;
    padding: 7px 12px;
    border-radius: 6px;
    background: rgba(240, 198, 106, 0.32);
    border: 1px dashed rgba(185, 138, 55, 0.85);
    color: #5a3a10;
    font-weight: 700;
    font-size: calc(0.88rem * var(--type-scale, 1));
}
.bio-df-banner-icon { font-size: 1.05em; }
.bio-df-meta { flex: 0 0 auto; margin: 0 0 6px; }
.bio-df-narr { font-size: calc(0.78rem * var(--type-scale, 1)); font-style: italic; color: var(--ffv-ink-soft, #6d5a3e); }

/* the dock: status line, typing input, control panel — the pane's footer, always seen */
.bio-df-dock {
    flex: 0 0 auto;
    position: sticky;
    /* = the negative bottom margin. On a phone the PANE scrolls, and sticky sticks to its CONTENT edge, so `bottom: 0`
       parked the dock 16 px above the pane's padding edge and the text scrolled visibly beneath it (Greg GP-6, S4). */
    bottom: -16px;
    z-index: 2;
    margin: 10px -18px -16px;
    padding: 8px 18px 10px;
    border-top: 1px solid rgba(138, 90, 46, 0.25);
    background: linear-gradient(180deg, #f2e6c9, #eadbba 42%);
}
.bio-df-status {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 6px;
    font-size: calc(0.86rem * var(--type-scale, 1));
    color: #4a3618;
}
.bio-df-status--done { color: #2f6b3f; }
.bio-df-status--done::before { content: '✓'; font-weight: 700; }
.bio-df-status--held { color: #7a4d14; }
.bio-df-status--held::before { content: '⏸'; }
.bio-df-status--failed { color: #8e2b1f; }
.bio-df-status--failed::before { content: '!'; font-weight: 700; }

.bio-df-input { display: flex; align-items: flex-end; gap: 6px; margin: 0 0 8px; }
.bio-df-field {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 44px;
    max-height: 9em;
    resize: vertical;
    padding: 10px 12px;
    border: 1px solid rgba(112, 78, 38, 0.45);
    border-radius: 9px;
    background: #fffaf0;
    color: var(--ffv-ink, #35291a);
    font: inherit;
    font-size: calc(0.95rem * var(--type-scale, 1));
    line-height: 1.35;
}
.bio-df-field:focus-visible { outline: 2px solid #d99a3d; outline-offset: 1px; }
.bio-df-send {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid rgba(185, 138, 55, 0.9);
    background: linear-gradient(160deg, #f0c66a, #d99a3d);
    color: #3a2607;
    font-size: 1.1rem;
    cursor: pointer;
}
.bio-df-send:disabled { opacity: 0.45; cursor: not-allowed; }

/* the control panel: Discard · SAY IT · Commit Changes · More */
.bio-df-bar {
    display: grid;
    grid-template-columns: 1fr auto 1fr auto;
    align-items: center;
    gap: 10px;
}
.bio-df-btn {
    min-height: 44px;
    padding: 8px 14px;
    border-radius: 9px;
    border: 1px solid rgba(112, 78, 38, 0.42);
    background: rgba(251, 246, 234, 0.85);
    color: var(--ffv-ink, #35291a);
    font: inherit;
    font-weight: 700;
    font-size: calc(0.9rem * var(--type-scale, 1));
    cursor: pointer;
}
.bio-df-btn:disabled { opacity: 0.42; cursor: not-allowed; }
.bio-df-discard { justify-self: start; }
.bio-df-commit { justify-self: end; }
.bio-df-commit:not(:disabled) {
    background: linear-gradient(160deg, #f0c66a, #d99a3d);
    border-color: rgba(185, 138, 55, 0.9);
    color: #3a2607;
    box-shadow: 0 1px 6px rgba(217, 154, 61, 0.35);
}
.bio-df-morebtn { min-width: 44px; padding: 8px 10px; }
/* Say It is the largest control: centre, the first thing the eye finds (the PR-1 coach mark's target) */
.bio-df-sayit-slot { display: flex; justify-content: center; }
.bio-df-sayit-slot .sayit-btn.bio-df-sayit {
    width: 68px;
    height: 68px;
    border-radius: 50%;
    padding: 0;
    background: radial-gradient(circle at 50% 38%, #fff6dc, #f0c66a 70%, #d99a3d);
    box-shadow: 0 2px 12px rgba(160, 104, 28, 0.45), inset 0 0 0 1px rgba(185, 138, 55, 0.9);
}
.bio-df-sayit-slot .sayit-btn.bio-df-sayit .sayit-glyph { width: 42px; height: 42px; }

/* ⋯ More, by panel — a popover that FLOATS up from the control panel, sized to its items (Greg GI-3/GP-2, S3):
   the zero-height anchor sits just above the bar, so opening More never moves the input or the text. */
.bio-df-more-anchor { position: relative; height: 0; }
.bio-df-more {
    position: absolute;
    right: 0;
    bottom: 8px;
    z-index: 12;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    width: max-content;
    min-width: 200px;
    max-width: min(320px, calc(100vw - 32px));
    padding: 6px;
    border-radius: 9px;
    border: 1px solid rgba(138, 90, 46, 0.45);
    background: #fbf3e0;
    box-shadow: 0 8px 24px rgba(40, 24, 6, 0.22);
}
.bio-df-more-item {
    min-height: 44px;
    padding: 8px 12px;
    text-align: left;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--ffv-ink, #35291a);
    font: inherit;
    font-size: calc(0.92rem * var(--type-scale, 1));
    cursor: pointer;
}
.bio-df-more-item:hover:not(:disabled), .bio-df-more-item:focus-visible { background: rgba(240, 198, 106, 0.3); }
.bio-df-more-item:disabled { opacity: 0.42; cursor: not-allowed; }
/* freeing-biography-folio S3 (#4): unavailable, but pressable — it opens a sheet that says why. Dimmed like a
   disabled item, with a help cursor and a small "why" mark so it does not read as broken. */
.bio-df-more-item.is-unavailable { opacity: 0.6; cursor: help; }
.bio-df-more-item.is-unavailable::after { content: ' ⓘ'; font-size: 0.85em; opacity: 0.8; }

/* Authorized "Edit the text directly": the editor IS the draft */
.bio-df-editbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 0 0 6px;
    font-size: calc(0.84rem * var(--type-scale, 1));
    color: var(--ffv-ink-soft, #6d5a3e);
}
.bio-df-editdone { min-height: 36px; padding: 4px 12px; }

/* a guest reads, listens and exports — no input, no draft controls (R4) */
.bio-df-readrow { display: flex; flex-wrap: wrap; gap: 8px; }

/* PR-1: an animated graphic that pops up and points directly at the Say It icon (placed by _dfPlaceCoach) */
#biographyModalOverlay .bio-modal-shell > .bio-df-coach {
    position: absolute;
    z-index: 11;
    left: 8px;
    bottom: 120px;
    width: min(272px, calc(100% - 16px));
    padding: 10px 12px 10px;
    border-radius: 12px;
    background: #2a1d0b;
    color: #fbf3e0;
    border: 2px solid #f0c66a;
    box-shadow: 0 12px 36px rgba(0, 0, 0, 0.45);
    font-size: calc(0.9rem * var(--type-scale, 1));
    opacity: 0;
    transform: translateY(8px) scale(0.96);
    animation: bio-df-coach-pop 360ms cubic-bezier(0.32, 0.72, 0.24, 1.2) 400ms forwards;
}
.bio-df-coach p { margin: 0 0 6px; }
.bio-df-coach .bio-ctl { margin-top: 2px; min-height: 44px; min-width: 88px; }   /* Greg GI-4: the 44 px touch floor */
/* The pointer: a gold stem from the card (which sits ABOVE the typing input — Greg GI-1/GP-1) down past the input to
   Say It, ending in a head. `_dfPlaceCoach` sets --bio-coach-stem (the measured gap) and --bio-coach-arrow-x. */
.bio-df-coach-arrow {
    position: absolute;
    left: var(--bio-coach-arrow-x, 50%);
    top: 100%;
    width: 3px;
    height: var(--bio-coach-stem, 10px);
    margin-left: -1.5px;
    border-radius: 2px;
    /* gold, so it reads on the parchment AND on the phone's dark bar — and NOT drawn across the typing field (Greg GP-7):
       `_dfPlaceCoach` sets the gap; without it the gap sits at 100% (zero length) and the stem is whole. The outline is a
       drop-shadow, which follows the painted gold, so the gap carries no outline either. */
    background: linear-gradient(to bottom,
        #f0c66a 0, #f0c66a var(--bio-coach-gap-top, 100%),
        transparent var(--bio-coach-gap-top, 100%), transparent var(--bio-coach-gap-bot, 100%),
        #f0c66a var(--bio-coach-gap-bot, 100%));
    filter: drop-shadow(0 0 1px rgba(42, 29, 11, 0.85));
    animation: bio-df-coach-nudge 1.1s ease-in-out 900ms infinite;
}
.bio-df-coach-arrow::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: -13px;
    margin-left: -9px;
    border-left: 9px solid transparent;
    border-right: 9px solid transparent;
    border-top: 14px solid #f0c66a;
}
@keyframes bio-df-coach-pop { to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes bio-df-coach-nudge { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(5px); } }

/* the close sheet reuses the exit card; a tap outside the card means keep editing */
.bio-df-sheet { cursor: default; }

/* the phone bar's inert slots (Discard / Commit before a draft exists) — inert but LEGIBLE (Greg GP-4, S3: at 0.38
   opacity they vanished, purple on purple, so a member could not see that Commit existed until they made a change). */
.mobile-tab-bar .mcp-slot.mcp-slot-disabled { opacity: 1; pointer-events: none; color: rgba(253, 246, 230, 0.58); }
.mobile-tab-bar .mcp-slot.mcp-slot-disabled .mobile-tab-icon,
.mobile-tab-bar .mcp-slot.mcp-slot-disabled .mobile-tab-label { color: inherit; opacity: 1; }

/* Where the app's bottom bar exists, the folio's control panel IS that bar (Discard · Say It · Commit · More,
   biography-modal.js `_dfBarSlots`) — the in-folio copy steps aside so there is one, never two. The typing
   input stays directly above it. `display:none` on the class, never [hidden] (a display rule beats [hidden]). */
body.has-mobile-tab-bar .bio-df-bar { display: none; }
/* …and in that bar Say It is the DOMINANT control (body.bio-df-open is set while the draft face is open): a raised
   gold disc, 60 px, above the 48 px bar — the phone's twin of the desktop's 68 px centre disc (Greg GP-3, S3: it was an
   80×37 slot, the smallest in the bar, below the 44 px floor, and the same weight as Discard). */
body.bio-df-open .mobile-tab-bar .mcp-slot[data-slot-id="say-it"] {
    flex: 0 0 60px;
    width: 60px;
    height: 60px;
    min-height: 60px;
    margin: -18px 6px 0;
    padding: 0;
    align-self: flex-start;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: radial-gradient(circle at 50% 38%, #fff6dc, #f0c66a 70%, #d99a3d);
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5), inset 0 0 0 2px rgba(185, 138, 55, 0.95);
}
body.bio-df-open .mobile-tab-bar .mcp-slot[data-slot-id="say-it"] .mobile-tab-icon { margin: 0; }
body.bio-df-open .mobile-tab-bar .mcp-slot[data-slot-id="say-it"] .sayit-glyph { width: 40px; height: 40px; }
/* the raised disc rises 18 px into the folio: the dock keeps the typing input clear of it */
body.has-mobile-tab-bar .bio-df-dock { padding-bottom: 8px; }
body.bio-df-open.has-mobile-tab-bar .bio-df-dock { padding-bottom: 24px; }

@media (max-width: 767px) {
    .bio-df-dock { margin-left: -12px; margin-right: -12px; padding-left: 12px; padding-right: 12px; }
    .bio-df-more { min-width: 0; max-width: calc(100vw - 24px); }
}
@media (prefers-reduced-motion: reduce) {
    #biographyModalOverlay .bio-modal-shell > .bio-df-coach { animation: none; opacity: 1; transform: none; }
    .bio-df-coach-arrow { animation: none; }
}
@media (forced-colors: active) {
    .bio-df-banner, .bio-df-coach { border: 1px solid CanvasText; }
    .bio-tab-dot { border: 1px solid CanvasText; }
}

/* ════════════════════════════════════════════════════════════════════════
 * freeing-biography-folio S2 — THE styled confirm (js/features/biography-modal/bio-confirm.js, window.FFVConfirm).
 * The folio's own sheet card (.bio-exit-card), in a fixed layer above every surface (the folio, the phone tab
 * bar at 11000, Fergi): a confirm that something will be replaced must never be painted over.
 * ════════════════════════════════════════════════════════════════════════ */
.ffv-confirm {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal-critical, 100001);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(12, 8, 4, 0.55);
}
.ffv-confirm .ffv-confirm-card { max-width: 540px; }
.ffv-confirm .ffv-confirm-text { white-space: pre-line; }
.ffv-confirm .ffv-confirm-actions { justify-content: flex-end; }
.ffv-confirm .ffv-confirm-danger {
    background: linear-gradient(160deg, #c8553f, #9e3524);
    border-color: rgba(120, 36, 22, 0.9);
    color: #fff6ec;
    box-shadow: 0 1px 6px rgba(158, 53, 36, 0.35);
}
.ffv-confirm .ffv-confirm-danger:hover:not(:disabled) { background: linear-gradient(160deg, #d2654e, #a83c2a); }
.ffv-confirm .bio-ctl:focus-visible { outline: 3px solid #2f6fb0; outline-offset: 2px; }
@media (forced-colors: active) {
    .ffv-confirm .bio-ctl:focus-visible { outline-color: Highlight; }
}

/* R17 (settling-prerelease-rulings S4): on the desktop #biographyModalOverlay is clipped to the App Panel so the
   Control Panel stays live. The rule lives in ONE place, css/modal-foundation.css § "Surfaces that live in the
   App Panel"; do not restate it here. */
