/* ── Figures, Equations, Tables ─────────────────────────────── */

/* Image figures: framed as a white plate so the image reads as an exhibit on the
   papyrus page. Export is unaffected (exporters don't load this stylesheet). */
.doc-figure {
    margin: 0 auto;
    text-align: center;
}
.doc-figure-plate {
    display: inline-block;
    max-width: 100%;
    padding: 16px;
    background: var(--doc-figure-bg);
    border: 1px solid var(--doc-border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--doc-shadow);
    /* Width travels: a size pick is a change of size, so it is watched rather
       than discovered. The plate keeps its node across the write (see
       Changes.load), which is what lets the glide finish. */
    transition: box-shadow var(--dur), width var(--dur-smooth) var(--ease-out);
}
.doc-figure-plate:hover {
    box-shadow: var(--doc-shadow), 0 0 0 1px var(--doc-border);
}
.doc-figure-img {
    display: block;
    margin: 0 auto;
    max-width: 100%;
    max-height: 480px;
    border-radius: var(--radius-xs);
    cursor: pointer;
    transition: opacity var(--dur);
}
/* A picture the user has chosen but the server does not have yet, the same
   half-there ink a math render wears while it is out (.math-rendering). */
.doc-figure-img.fig-img-uploading { opacity: .5; }
/* A dropped or pasted picture standing in its gap while it uploads
   (FiguresInsert._landing): not yet a figure, so nothing on it answers. */
.figure-block.fig-landing { pointer-events: none; }
.fig-landing .fig-menu-btn { visibility: hidden; }
/* The source credit CLOSES the caption, the way a citation closes a sentence
   (server bytes, see Figures._attrHtml). Brick, from the citation family; NOT a
   .cite-marker, so it takes no reader number, but it opens the same card. */
.fig-attribution {
    font-size: 0.9em;   /* the export sheet's own ratio to its caption */
    color: var(--doc-accent);
}
/* Only a credit that resolved to sources is a door: the export preview's own
   credit is server bytes with no ids, and stays plain caption text. The hover
   is the marker's, to the letter — same ink, same lift, same underline. */
/* In a numbered style the credit IS a reader number, so it wears the prose
   marker's face: .cite-marker's own declaration, relative to the caption it
   sits in the way a marker is to its sentence. */
.fig-attribution--num {
    font: 600 0.75em/1.2 var(--sans);
    vertical-align: super;
    padding: 0 2px;
}
/* IEEE's credit is the caption's own text at the caption's size, the
   marker's prose face (.cite-marker--inline) without its lead-in space. */
.fig-attribution.fig-attribution--inline { font-size: 1em; }
.fig-attribution[data-fig-sids] { cursor: pointer; transition: color var(--dur); }
.fig-attribution[data-fig-sids]:hover { color: var(--doc-ochre-hover); text-decoration: underline; }

.doc-figure-caption,
.eq-caption {
    font: 14px/1.6 var(--serif);
    color: var(--td3);
    text-align: center;
    margin-top: 6px;
    max-width: 60ch;
    margin-left: auto;
    margin-right: auto;
}
.fig-label, .tbl-label {
    font-weight: 600;
    color: var(--td2);
}

/* Equations */
.doc-equation {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 10px 0;
    gap: 16px;
}
.eq-content {
    flex: 1;
    text-align: center;
    overflow-x: auto;
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: background var(--dur);
}
/* Held while the formula window is open on it: the page marks what the window
   edits, in the mark it already wears under the cursor. */
.eq-content:hover,
.eq-content.is-editing {
    background: var(--doc-hover-subtle);
}
/* Display-equation SVG (server-rendered, byte-identical to the PDF). The seam
   sizes it in em so it scales with the doc font; reserve a line so the async
   fill doesn't shift layout under the pending-change reveal. */
.eq-content { min-height: 1.6em; }
.eq-content svg { overflow: visible; max-width: 100%; }
.eq-number {
    font: 16px/1 var(--serif);
    color: var(--td3);
    flex-shrink: 0;
    min-width: 40px;
    text-align: right;
}
.eq-caption {
    margin-top: 2px;
    font-style: italic;
}
/* The credit is roman inside an italic caption, exactly as the export sets it. */
.eq-caption .fig-attribution { font-style: normal; }
/* Untitled equation: the caption row carries only the menu affordance, since an
   equation is numbered rather than titled by default. */
.eq-caption--empty { margin-top: 0; font-style: normal; }
/* Tighter than figures/tables: consecutive equations are usually one derivation.
   The caption row stays visible; hiding it until hover jumped the layout. */
.figure-block:has(> .doc-equation) { margin: 4px 0; }
.eq-error {
    color: var(--doc-accent);   /* brick: renders on the papyrus/plate ground */
    font-family: var(--mono);
    font-size: 13px;
}
/* Neutral, not red: a render with no verdict yet self-heals on the next pass. */
.eq-pending {
    color: var(--td3);
    font-family: var(--mono);
    font-size: 13px;
}

/* Code listings and algorithms: one block, two faces. A listing's lines sit on
   the paper's lighter stock in the fixed-width face, exactly as typed; an
   algorithm is the page's own serif, numbered, each line at its depth. Both
   open as their text where they stand (.code-edit), in the same metrics. */
.doc-code { margin: 0; }
.code-view,
.code-edit {
    display: block;
    width: 100%;
    box-sizing: border-box;
    margin: 0;
    padding: 12px 16px;
    border: none;
    border-radius: var(--radius-sm);
    background: rgba(var(--doc-sheet-rgb), 0.7);
    color: var(--td1);
    font: 13px/1.55 var(--mono);
    tab-size: 4;
    text-align: left;
}
.code-view { overflow-x: auto; cursor: text; transition: background var(--dur); }
.code-view:hover { background: rgba(var(--doc-sheet-rgb), 0.95); }
.code-line { display: block; min-height: 1.55em; white-space: pre; }
.code-view--empty::before { content: 'Type or paste code'; color: var(--td3); opacity: 0.7; }
/* Typed as text, an algorithm keeps the fixed-width face, where its depth of
   two spaces a level can be seen. */
.code-view--algorithm { font: 16px/1.6 var(--serif); }
.code-view--algorithm .code-line { white-space: normal; min-height: 1.6em; }
.code-view .alg-num {
    display: inline-block;
    width: 1.6em;
    margin-right: 0.9em;
    text-align: right;
    font: 12px var(--sans);
    font-variant-numeric: tabular-nums;
    color: var(--td3);
}
.code-view .alg-text { display: inline; }
/* A pending edit of the lines, whole lines on the prose diff's bands. */
.code-view del.diff-del,
.code-view ins.diff-add { display: block; text-decoration: none; }
.code-edit { resize: none; outline: none; overflow: hidden; white-space: pre; caret-color: var(--doc-accent); }
.code-edit--algorithm { white-space: pre-wrap; }
.code-edit::placeholder { color: var(--td3); opacity: 0.7; }
.code-edit:focus { box-shadow: inset 0 0 0 1px var(--doc-ochre); }
.fig-face-menu-wrap { position: relative; display: inline-flex; }
.fig-face-menu.dropdown-menu { left: auto; right: 0; min-width: 132px; }
/* The peek at a listing: its first lines, cut through a line. */
.fig-tip-code { max-height: 200px; overflow: hidden; }
.fig-tip-code .code-view { font-size: 11px; padding: 8px 10px; cursor: default; }

/* Tables */
.doc-table {
    position: relative;
    margin: 12px 0;
}
.doc-table-caption {
    font: 14px/1.6 var(--serif);
    color: var(--td3);
    text-align: center;
    margin-top: 6px;
    margin-bottom: 0;
}
/* Its own alignment, so a table inside a centred wrapper (a delete's ghost)
   reads as the live grid does. */
.doc-table-content {
    overflow-x: auto;
    text-align: left;
}
.doc-table-content table {
    width: 100%;
    border-collapse: collapse;
    font: 14px/1.5 var(--serif);
    color: var(--td2);
}
.doc-table-content th,
.doc-table-content td {
    border: 1px solid var(--doc-border);
    padding: 6px 12px;
    min-height: 32px;
}
.doc-table-content th {
    font-weight: 600;
    color: var(--td1);
    background: var(--doc-hover-subtle);
    text-align: left;
    border-bottom: 1.5px solid var(--doc-border);
}
/* Remove the outer border, keep the inner grid */
.doc-table-content tr:first-child th,
.doc-table-content tr:first-child td { border-top: none; }
.doc-table-content tbody tr:last-child td { border-bottom: none; }
.doc-table-content th:first-child,
.doc-table-content td:first-child { border-left: none; }
.doc-table-content th:last-child,
.doc-table-content td:last-child { border-right: none; }

/* A figure added or removed wears its box's colour in a lighter tone on its own
   ground: a table's cells, a listing's sheet, a picture's plate (a new section
   and a deleted one are the box). A picture blends onto its plate with darken,
   so its white takes the tone and everything darker keeps its colour. */
:is(.inline-diff-body .figure-block.diff-fig-add, .pending-new .figure-block)
    :is(.doc-table-content, .code-view, .doc-figure-plate),
.fig-pane-new .doc-figure-plate { background: var(--fig-add-tone); }
:is(.inline-diff-body .figure-block.diff-fig-del, .deleted-ghost-body .figure-block)
    :is(.doc-table-content, .code-view, .doc-figure-plate),
.fig-pane-old .doc-figure-plate { background: var(--fig-del-tone); }
:is(.inline-diff-body .figure-block.diff-fig, .pending-new .figure-block, .deleted-ghost-body .figure-block, .fig-pane)
    .doc-figure-plate { isolation: isolate; }
:is(.inline-diff-body .figure-block.diff-fig, .pending-new .figure-block, .deleted-ghost-body .figure-block, .fig-pane)
    .doc-figure-img { mix-blend-mode: darken; }
:is(.inline-diff-body .figure-block.diff-fig, .pending-new .figure-block, .deleted-ghost-body .figure-block)
    .doc-table-content { border-radius: var(--radius); }

/* Cross-references: a figure's, and a section's number (the noun before it
   is the author's prose) */
.fig-ref,
.sec-ref {
    color: var(--doc-accent);
    cursor: pointer;
    transition: color var(--dur);
}
.fig-ref:hover,
.sec-ref:hover {
    color: var(--doc-ochre-hover);
    text-decoration: underline;
}

/* Inline math: server-rendered SVG (byte-identical to the PDF), em-sized to
   scale with the prose. JS seats it on the baseline via vertical-align. */
.inline-math {
    display: inline-block;
    line-height: 0;
    position: relative;
    /* Seated by the depth its glyphs hang below the baseline (math-render.js
       writes --math-depth); the band below is anchored off the same value. */
    vertical-align: calc(-1 * var(--math-depth, 0em));
}
.inline-math svg { overflow: visible; position: relative; z-index: 1; }
/* A prose span is inline by grammar (`\(...\)`); display math is a figure. */

/* The source layer MathRender lays under the glyphs (math-render.js). It is
   what the browser selects, copies and marks, so a formula's band is a word's
   band: at least a line tall, never touching layout, clipped to the FORMULA. */
.inline-math .math-alt {
    /* In flow at zero size, so it changes nothing: the engine seats its baseline on
       the chip's, and --math-depth lands it on the prose baseline. Its line box is
       then the band, snapped to whole pixels by the code that snaps the prose band. */
    display: inline-block;
    width: 0;
    height: 0;
    position: relative;
    top: calc(-1 * var(--math-depth, 0em));
    line-height: max(var(--math-band, calc(var(--doc-lh) * 1em)), var(--math-h, 0em));
    /* Trims the source's overrun to the formula's width without touching the band's
       height (which `overflow` would clip back to a box of our own). The half pixel
       is the join: a clip edge ending exactly at the chip leaves a hairline of paper. */
    clip-path: inset(-2em calc(-1 * var(--math-w, 0em) - 0.5px) -2em 0);
    color: transparent;
    white-space: pre;
    letter-spacing: 1em;
    /* Square at rest: its corners are where this band butts against the prose
       band, and a radius nicks a notch of paper out of each one. */
    transition: background-color var(--dur), box-shadow var(--dur);
}

/* At rest a formula is prose ink, exactly as it prints; the atom announces
   itself only under the cursor, everywhere math is live. Only the body and the
   review overlay's kept text open the edit popover, so only they take the
   pointer. Drawn on the band's own origin. */
.inline-math .math-alt::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: var(--math-w, 0px);
    height: max(var(--math-band, calc(var(--doc-lh) * 1em)), var(--math-h, 0em));
    border-radius: var(--radius-2xs);
    pointer-events: none;
    transition: background-color var(--dur), box-shadow var(--dur);
}
.inline-math:hover .math-alt::before,
.inline-math.is-editing .math-alt::before {
    background: var(--doc-hover);
    box-shadow: inset 0 -1px 0 var(--doc-border);
}
.section-body .inline-math:hover,
.inline-diff-body .inline-math:hover { cursor: pointer; }
/* Removed text is not in the content: nothing to announce there either. */
.inline-diff-body :is(del, .diff-del, .diff-stacked-prev) .inline-math:hover { cursor: default; }
.inline-diff-body :is(del, .diff-del, .diff-stacked-prev) .inline-math:hover .math-alt::before { background: none; box-shadow: none; }
/* A formula being made (Figures.openNewFormula): the empty box stands where
   the formula will, ringed like a hole in the boxes, until the first render
   fills it. A DOM placeholder, never stored. */
.inline-math.is-new { line-height: normal; }
.inline-math.is-new:empty::before {
    content: ''; display: inline-block; width: 0.85em; height: 1.05em;
    vertical-align: -0.18em; border-radius: var(--radius-2xs);
    background: var(--doc-hover); box-shadow: inset 0 0 0 1px var(--doc-accent);
}
.eq-content.is-new:empty::before {
    content: ''; display: inline-block; width: 1.1em; height: 1.3em;
    vertical-align: middle; border-radius: var(--radius-2xs);
    background: var(--doc-hover); box-shadow: inset 0 0 0 1px var(--doc-accent);
}
.eq-number--dim { opacity: 0.55; }
/* The history preview is read-only (see preview.css), so nothing to announce. */
.preview-section .inline-math:hover .math-alt::before { background: none; box-shadow: none; }

/* A figure moved by the row's arrows HOLDS ITS PLACE on screen and the page
   travels around it: the thing you are looking at is the thing you are moving.
   The transforms are set per block by Figures._flipUnits; this owns the trip. */
.fig-sliding { transition: transform var(--dur-travel) var(--ease-travel); }
/* For the length of the trip the scroller does exactly what it is told: the
   figure is pinned by setting scrollTop, and this scroller is SMOOTH by default,
   so the nudge was animated on a second curve. Scroll anchoring goes off with it. */
#document-scroll.doc-travelling { overflow-anchor: none; scroll-behavior: auto; }
/* A figure placed where nothing stood opens its own space: the blocks below
   travel down (DocumentView._playTrip) and it is uncovered from its top edge on
   the same clock, so no line of text is ever drawn across it. */
.fig-arriving { animation: fig-arrive var(--dur-travel) var(--ease-travel) both; }
@keyframes fig-arrive { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0); } }

/* Missing / placeholder */
.fig-missing,
.fig-no-image {
    padding: 24px;
    text-align: center;
    color: var(--td3);
    font: var(--font-ui);
    border: 1px dashed var(--doc-border);
    border-radius: var(--radius-sm);
}

/* Pending-delete ghost: the real figure (from the figure_delete snapshot) in the
   removed box, its Accept/Reject hung under the box rather than inside it. */
.fig-deleted-ghost {
    background: var(--red-bg);
    border-radius: var(--radius);
    box-shadow: inset 3px 0 0 var(--red);
    padding: 8px 8px 8px 11px;
    margin: 0;
}
.fig-deleted-ghost + .figure-change-bar { margin-top: 10px; }
.fig-deleted-label {
    color: var(--red);
}
/* The ghost of a MOVED figure: one caption line, the cross-reference to where
   it stands now and where that is, instead of a second copy of the figure. */
.fig-moved-ghost {
    font: 14px/1.6 var(--serif);
    color: var(--td3);
    text-align: center;
    padding: 4px 0;
}
/* The ghost is the box, so the removed-content block around it draws none; in a
   deleted section's body the section is the box, and red never stacks on red. */
.inline-diff-body .figure-block.diff-fig-del:has(> .fig-deleted-ghost) {
    background: none;
    box-shadow: none;
    padding: 0;
}
.deleted-ghost-body .fig-deleted-ghost {
    background: transparent;
    box-shadow: none;
    padding: 0;
}

/* Cross-reference peek: a scrap of the PAGE, lifted. Papyrus a half-step
   lighter than the sheet it floats over, so every kind renders as it prints;
   chrome is the CONTROL material. No label, no name: the link already says it. */
.fig-tip {
    position: fixed;
    z-index: var(--z-tooltip);
    background: var(--doc-bg-light);
    border: 1px solid var(--doc-border-strong);
    border-radius: var(--radius-lg);
    padding: 10px;
    max-width: 300px;
    pointer-events: none;
    box-shadow: var(--shadow-sm);
    /* JS sets left/top after measuring, so only opacity and a translate
       animate (a translate doesn't change the measured size). */
    animation: fig-tip-in var(--dur) var(--ease-out);
}
@keyframes fig-tip-in {
    from { opacity: 0; transform: translateY(-2px); }
    to   { opacity: 1; transform: translateY(0); }
}
/* The plate the page gives a picture, miniaturised: page → plate → image is
   the document's own construction. */
.fig-tip-img {
    display: block;
    max-width: 100%;
    max-height: 190px;
    padding: 6px;
    box-sizing: border-box;
    background: var(--doc-figure-bg);
    border-radius: var(--radius-xs);
}
.fig-tip-eq {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 1.6em;
    padding: 2px 4px;
}
.fig-tip-eq svg { max-width: 100%; max-height: 3.2em; }
.fig-tip-eq .eq-error, .fig-tip-eq .eq-pending {
    font: 12px/1.4 var(--mono); color: var(--td3); overflow-wrap: anywhere;
}
/* A long table is clipped, not scrolled: the card takes no pointer, so a
   scrollbar in it would be furniture nobody can reach. The cut runs THROUGH a
   row (Figures._clipTableRows measures it), which is what says it continues. */
.fig-tip-table {
    max-height: 220px;
    overflow: hidden;
}
/* Fixed layout: the columns divide the card between them and each cell
   ellipsizes, so a wide table is summarised rather than cut off at the edge. */
.fig-tip-table table {
    border-collapse: collapse;
    table-layout: fixed;
    width: 100%;
    font: 11px/1.4 var(--serif);
    color: var(--td2);
}
.fig-tip-table th, .fig-tip-table td {
    padding: 3px 7px;
    border: 1px solid var(--doc-border);
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.fig-tip-table th { font-weight: 600; background: var(--doc-hover-subtle); }
.fig-tip-text {
    font: 12px/1.45 var(--serif);
    color: var(--td3);
    max-width: 34ch;
}

/* Figure block hover state in document */
.figure-block {
    position: relative;
    cursor: default;
    white-space: normal; /* override section-body pre-wrap */
    margin: 20px 0;
}
.doc-figure,
.doc-equation {
    position: relative;
}
.doc-table:hover {
    opacity: 1;
}

/* ── Figure menu button, visual from .doc-icon-btn ───────────────── */
.fig-menu-btn { vertical-align: middle; }
.fig-menu-btn svg { display: block; position: relative; top: -1px; }

/* Figure meta panel; collapse (grid-rows) from components.css, so the row
   layout lives on the inner wrapper, never the panel itself. */
.fig-meta-panel { padding: 4px 0 2px; }
/* Every figure kind's panel is the picture's control row (.fig-tools below),
   so the collapse wrapper only clips; the row owns the layout. */
.fig-meta-panel .meta-panel-inner { display: block; }
/* Move is one paired control, not two full-width words. It stretches to the
   row's own height: an icon button has no line box, so left to itself it sits
   5px shorter than every word beside it. */
/* Arrows + Delete wrap as ONE unit; the gap matches the row's own. */
.fig-tools-tail { display: inline-flex; align-items: stretch; gap: 8px; }
.fig-move-pair { display: inline-flex; align-items: stretch; align-self: stretch; gap: 2px; }
.fig-move-pair .src-add-btn { padding: 6px 9px; }
.fig-panel-btn {
    background: none;
    border: none;
    font: var(--font-ui-sm);
    color: var(--td3);
    cursor: pointer;
    padding: 4px 10px;
    border-radius: var(--radius-sm);
    transition: background var(--dur), color var(--dur);
    white-space: nowrap;
}
.fig-panel-btn:hover {
    background: var(--doc-hover);
    color: var(--td2);
}
.fig-panel-btn:disabled { opacity: 0.45; cursor: default; }
.fig-panel-btn:disabled:hover { background: none; color: var(--td3); }
/* One red per ground: a destructive verb on papyrus is brick. At rest it is the
   same raised paper object as every control beside it, the two-tap confirm and
   the undo toast carrying the protection; the ARMED state spends the contrast. */
.fig-del-btn { color: var(--doc-accent); gap: 0; }
/* Armed, the pill keeps its paper: the label alone becomes the question, in the
   house confirm's own brick and weight (components.css). The pill is sized once
   on open to its ARMED self (Figures._pinDeleteWidth), so the row never shifts. */
.fig-del-btn { justify-content: center; }

/* The source-credit pill: it names the credited source; the picking happens
   on the citation card it opens. */
.fig-src-label { max-width: 18ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fig-src-more { color: var(--td3); flex-shrink: 0; }
.fig-chev { opacity: 0.75; flex-shrink: 0; }
/* Same escape the section level picker makes: the collapse wrapper clips, and
   while a menu is open its panel is already fully expanded. Any of the row's
   dropdowns (a picture's Size, a code block's Type). */
.fig-meta-panel:has(.dropdown-menu:not(.hidden)) .meta-panel-inner { overflow: visible; }

/* ── Replace-image dialog ─────────────────────────────────── */
/* Upload zone fills available space */
.modal-panel-content .fig-upload-zone {
    flex: 1;
    min-height: 0;
}
.modal-panel-content .fig-upload-preview {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
.modal-panel-content .fig-upload-preview img {
    max-height: 100%;
    object-fit: contain;
}

/* Upload zone */
.fig-upload-zone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 32px 16px;
    border: 2px dashed var(--chrome-border);
    border-radius: var(--radius);
    cursor: pointer;
    color: var(--t3);
    font: var(--font-ui);
    transition: border-color var(--dur), color var(--dur);
}
.fig-upload-zone:hover,
.fig-upload-zone.dragover {
    border-color: var(--accent);
    color: var(--t1);
}
.fig-upload-preview {
    text-align: center;
}
.fig-upload-preview img {
    max-width: 100%;
    max-height: 180px;
    border-radius: var(--radius-sm);
}
.fig-upload-name {
    font: var(--font-ui-sm);
    color: var(--t3);
    margin-top: 6px;
}
/* Server render in flight: keep the last SVG, dimmed. No keystroke flicker. */
.math-rendering { opacity: .5; transition: opacity var(--dur); }
/* Source→SVG swap: the new SVG fades up from the dimmed rendering state.
   MathRender.fillInto adds the class and strips it on animationend. */
.math-swap-in { animation: math-swap-in 120ms var(--ease-out); }
@keyframes math-swap-in { from { opacity: .25; } }

/* Grid table editor. The manuscript's live table IS this editor
   (Figures._mountGridTable): the read render's metrics exactly, the 22px control
   gutter outside the data columns, and every control hover-revealed. */
.fig-grid-wrap { position: relative; }
.fig-grid-scroll { overflow-x: auto; }
/* The rail hangs outside the data, so the host must not clip it. */
.doc-table-content:has(.fig-grid-wrap) { overflow-x: visible; }
.fig-grid-table {
    width: 100%;
    border-collapse: collapse;
    font: 14px/1.5 var(--serif);
    color: var(--td2);
}
.fig-grid-table th,
.fig-grid-table td {
    border: 1px solid var(--doc-border);
    padding: 6px 12px;
    min-height: 32px;
    outline: none;
    cursor: text;
    caret-color: var(--doc-accent);
}
.fig-grid-table th:empty::before,
.fig-grid-table td:empty::before {
    content: attr(data-placeholder);
    color: var(--td3);
    opacity: 0.35;
    pointer-events: none;
}
.fig-grid-table th {
    font-weight: 600;
    color: var(--td1);
    background: var(--doc-hover-subtle);
    border-bottom: 1.5px solid var(--doc-border);
}
.fig-grid-table tr:first-child th,
.fig-grid-table tr:first-child td { border-top: none; }
.fig-grid-table tbody tr:last-child td { border-bottom: none; }
.fig-grid-table th:first-child,
.fig-grid-table td:first-child { border-left: none; }
.fig-grid-table th:last-child,
.fig-grid-table td:last-child { border-right: none; }
/* Editing a cell is marked by the brick caret alone, like every prose field:
   an outline + wash painted a rectangle into the manuscript on every click. */
.fig-grid-table th:focus,
.fig-grid-table td:focus { outline: none; }
/* Empty-cell ghosts ("—") are edit chrome: invisible until the reader is
   actually at the table. */
.doc-table-content .fig-grid-table th:empty::before,
.doc-table-content .fig-grid-table td:empty::before { opacity: 0; transition: opacity var(--dur); }
.doc-table:hover .fig-grid-table th:empty::before,
.doc-table:hover .fig-grid-table td:empty::before,
.doc-table:focus-within .fig-grid-table th:empty::before,
.doc-table:focus-within .fig-grid-table td:empty::before { opacity: 0.35; }

/* The edge rail: one strip per column along the top edge, one per row down the
   left, sized from the real cells. Extent carries the scope, so nothing points
   (an arrow would read as sort); nothing shows until the caret is in the table. */
.grid-rail {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-smooth);
}
/* A strip is an 18px hit box drawn as a 5px bar; the box reaches 8px past the
   bar away from the table and stops at its edge, so it never sits over a cell. */
.grid-rail-top { left: 0; right: 0; top: -18px; overflow: hidden; }
.grid-rail-slide { display: flex; gap: 2px; }
.grid-rail-left { left: -18px; width: 18px; display: flex; flex-direction: column; gap: 2px; }
.fig-grid-wrap.grid-awake .grid-rail,
.fig-grid-wrap:focus-within .grid-rail { opacity: 1; pointer-events: auto; }
.grid-strip {
    flex: none;
    padding: 0;
    border: none;
    background: none;
    color: var(--doc-accent);
    cursor: pointer;
    position: relative;
}
.grid-rail-top .grid-strip { height: 18px; }
.grid-rail-left .grid-strip { width: 18px; }
.grid-strip::before {
    content: "";
    position: absolute;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.13);
    transition: background var(--dur);
}
.grid-rail-top .grid-strip::before { left: 0; right: 0; top: 8px; height: 5px; }
.grid-rail-left .grid-strip::before { top: 0; bottom: 0; left: 7px; width: 5px; }
.grid-strip:hover::before,
.grid-strip.is-open::before { background: var(--doc-accent); }
.grid-strip:focus-visible { outline: 2px solid var(--doc-accent); outline-offset: 2px; }
/* The nub is the tie between an open menu and its target: it says "this one"
   without claiming a direction. Hover and open only. */
.grid-strip::after {
    content: "";
    position: absolute;
    opacity: 0;
    transition: opacity var(--dur);
}
.grid-rail-top .grid-strip::after {
    left: 50%; top: 13px; transform: translateX(-50%);
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 4px solid currentColor;
}
.grid-rail-left .grid-strip::after {
    top: 50%; left: 12px; transform: translateY(-50%);
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    border-left: 4px solid currentColor;
}
.grid-strip:hover::after,
.grid-strip.is-open::after { opacity: 1; }

/* What a verb will do, painted before the click. */
.fig-grid-table th.grid-hot,
.fig-grid-table td.grid-hot { background: var(--doc-accent-dim); }
.fig-grid-table th.grid-hot { background: linear-gradient(var(--doc-accent-dim), var(--doc-accent-dim)), var(--doc-hover-subtle); }
.fig-grid-table th.grid-seam-l,
.fig-grid-table td.grid-seam-l { box-shadow: inset 2px 0 0 var(--doc-ochre-hover); }
.fig-grid-table th.grid-seam-r,
.fig-grid-table td.grid-seam-r { box-shadow: inset -2px 0 0 var(--doc-ochre-hover); }
.fig-grid-table th.grid-seam-t,
.fig-grid-table td.grid-seam-t { box-shadow: inset 0 2px 0 var(--doc-ochre-hover); }
.fig-grid-table th.grid-seam-b,
.fig-grid-table td.grid-seam-b { box-shadow: inset 0 -2px 0 var(--doc-ochre-hover); }

/* A merge moves a row's first or last cell off the table's edge, so that
   cell keeps its inner line; a cell reaching the foot from a row above is on
   the foot's edge. */
.doc-table-content :is(th, td).grid-inner-l { border-left: 1px solid var(--doc-border); }
.doc-table-content :is(th, td).grid-inner-r { border-right: 1px solid var(--doc-border); }
.doc-table-content td.grid-edge-b { border-bottom: none; }

/* While the caret is in the table a border between two cells is a verb: the
   cell a merge would make washes brick, a split's seam dashes brick. */
.grid-hint { position: absolute; pointer-events: none; box-sizing: border-box; }
.grid-hint--merge { background: var(--doc-accent-dim); box-shadow: inset 0 0 0 2px var(--doc-accent); }
.grid-hint--seam-v { border-left: 2px dashed var(--doc-accent); }
.grid-hint--seam-h { border-top: 2px dashed var(--doc-accent); }
.fig-grid-wrap.grid-border-hot .fig-grid-table :is(th, td) { cursor: pointer; }

/* The menu is the app's own dropdown, the object the sources sort and the
   reference-style picker already open on this ground. */
.grid-rail-menu.dropdown-menu {
    position: absolute;
    top: auto; left: auto;
    min-width: 190px;
    white-space: nowrap;
}
/* The items are buttons so a keyboard can reach them; the reset keeps them
   wearing .dropdown-item's material rather than the browser's. */
.grid-rail-menu .dropdown-item {
    appearance: none;
    width: 100%;
    background: none;
    border: 0;
    font: var(--font-ui-sm);
    font-size: 12px;
    text-align: left;
    justify-content: space-between;
    gap: 16px;
}
.grid-rail-menu .dropdown-item:disabled { color: var(--t3); background: none; cursor: default; }
.grid-rail-menu .grid-verb-del { color: var(--red); }
.grid-rail-menu .grid-verb-del:hover { color: var(--red); background: var(--red-bg); }
.grid-size-now { color: var(--t3); font: 11px/1 var(--sans); }
.grid-rail-menu--picker { min-width: 0; padding: 8px; }

/* The one control row every figure kind wears, borrowed whole from the sources
   tools row: raised paper pills, and a dropdown carrying its current value. The
   picture's row and the ⋯ panel are the same row, in the same panel. */
.fig-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 2px 2px;
    text-align: left;
}
/* Seven pills in a row cast seven drop shadows that merge into one dark band
   ruled across the manuscript, so on paper the row wears the small lift. */
.fig-tools .src-add-btn { box-shadow: var(--shadow-paper-pill-sm); }
.fig-tools .src-add-btn:hover { box-shadow: var(--shadow-paper-pill); }
.fig-size-menu-wrap { position: relative; display: inline-flex; }
.fig-size-menu.dropdown-menu { left: auto; right: 0; min-width: 148px; }

/* The picture at its own size, on a dimmed page. */
.fig-viewer {
    position: fixed; inset: 0; z-index: var(--z-modal, 400);
    background: rgba(28, 22, 12, 0.78);
    display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 16px;
    padding: 32px;
}
.fig-viewer-plate { width: auto; max-width: min(1100px, 92vw); }
.fig-viewer-plate img { max-width: 100%; max-height: 78vh; width: auto; height: auto; }
/* A picture under review: its before beside its after on a scrap of the page,
   so the bands and their labels read on paper as they do in the document. */
.fig-viewer .fig-viewer-pair {
    width: min(1600px, 94vw);
    box-sizing: border-box;
    padding: 22px 24px 16px;
    background: var(--doc-bg);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}
.fig-viewer-pair .fig-viewer-plate { max-width: none; }
.fig-viewer-pair .doc-figure-img { cursor: default; }
.fig-viewer-pair .fig-viewer-plate img { max-height: 70vh; }

/* Width presets on the papyrus plate, mirrored by the exporters via
   metadata.display_width (app/exporters/figures.py). */
.doc-figure-plate.img-w-small { width: min(300px, 100%); }
.doc-figure-plate.img-w-medium { width: min(480px, 100%); }
.doc-figure-plate.img-w-full { width: 100%; }
.doc-figure-plate.img-w-small .doc-figure-img,
.doc-figure-plate.img-w-medium .doc-figure-img,
.doc-figure-plate.img-w-full .doc-figure-img {
    width: 100%; max-height: none; height: auto;
}

/* Insert figure button in section meta */
.insert-fig-btn svg {
    vertical-align: middle;
}

/* ── Inline figure editing ────────────────────────────────── */
/* The block lifts off the page as a lighter papyrus card: in-place editing stays
   page material, since chrome belongs to floating popovers. */
/* No plate on the editing block: entering edit must not repaint the figure.
   The grid controls and the caption caret are what say "editing". */
.fig-inline-edit {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.fig-ie-img {
    text-align: center;
}
/* Inline figure-edit field: visual from FIELDS, structural rules here */
.fig-ie-field { width: 100%; box-sizing: border-box; resize: none; }
.fig-ie-caption-row {
    display: flex;
    align-items: center;
    gap: 6px;
    justify-content: center;
}
/* Borderless like the caption text it edits: the placeholder and the brick
   caret are the edit cues, so entering edit mode doesn't draw a rule the
   read-mode caption never had. */
.fig-ie-caption {
    flex: 1;
    max-width: 50ch;
    background: transparent;
    border: none;
    color: var(--td3);
    font: 14px/1.6 var(--serif);
    padding: 2px 0;
    outline: none;
    caret-color: var(--doc-accent);
    text-align: center;
}
.fig-ie-caption::placeholder { color: var(--td3); opacity: 0.6; font-style: italic; }
.fig-ie-caption:focus { color: var(--td2); }

/* ── Direct cell & caption editing ───────────────────────── */
.doc-table-content td:focus,
.doc-table-content th:focus { outline: none; }
.doc-table-content td,
.doc-table-content th { caret-color: var(--doc-accent); }
.fig-caption-text {
    cursor: text;
}
.fig-caption-text.fig-caption-placeholder {
    font-style: italic;
    opacity: 0.45;
}
/* NO rule under an engaged caption: one span serves every figure kind, and the
   caret plus the darkened ink already say the words are live. */
.fig-caption-text.caption-editing {
    outline: none;
    color: var(--td2);
    caret-color: var(--doc-accent);
}

/* A figure under review paints its diff where the content is (figures-diff.js):
   cells, caption, credit and alt text wear the line wash (diff.css), text keeping
   its ink. A removed value is an island the cell reader and caption skip. */
.figure-block ins.diff-add,
.figure-block del.diff-del {
    text-decoration: none;
    color: inherit;
}
/* A listing's changed lines are whole blocks, washed as blocks. */
.code-view ins.diff-add { background: var(--green-bg); }
.code-view del.diff-del { background: var(--red-bg); }
/* A credit is short and reads as one word: it never breaks inside its band. */
.fig-attribution del.diff-del, .fig-attribution ins.diff-add { white-space: nowrap; }
.fig-attribution del.diff-del + ins.diff-add::before,
.fig-caption-diffed del.diff-del + ins.diff-add::before,
.fig-caption-diffed ins.diff-add + del.diff-del::before,
.fig-alt-diff del.diff-del + ins.diff-add::before,
.fig-alt-diff ins.diff-add + del.diff-del::before { content: ' '; }
.fig-alt-diff {
    font: 13px/1.6 var(--serif);
    color: var(--td3);
    text-align: center;
    margin-top: 2px;
}
.fig-alt-label {
    font: 600 11px var(--sans);
    color: var(--td3);
    letter-spacing: 0.02em;
    margin-right: 6px;
}

/* Table: the cell's old value beside its new; added rows and columns green;
   removed rows and columns stay in place as red ghosts (no caret, no rail). */
.fig-grid-table th.grid-added,
.fig-grid-table td.grid-added,
.fig-grid-table tr.grid-added td { background: var(--green-bg); }
.fig-grid-table th.grid-ghost,
.fig-grid-table td.grid-ghost,
.fig-grid-table tr.grid-ghost td {
    background: var(--red-bg);
    color: var(--td3);
    cursor: default;
}
.fig-grid-table td .grid-old { margin-right: 2px; }

/* Picture: the whole old picture on the red band beside the whole new one on
   the green, the two at one size, half the column each (a click opens both at
   full size, Figures.openViewer). The phone stacks them (mobile.css). */
.fig-panes {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    gap: 26px 28px;
    padding: 8px 0 2px;
}
/* The band is a spread, not padding, so the plate in a pane is the plate the
   figure keeps; the settle scales it to its own size (Figures.slideFrom). */
.fig-pane {
    display: block;
    flex: 1 1 0;
    min-width: 0;
    border-radius: var(--radius);
    text-align: center;
}
.fig-pane .doc-figure-plate { display: block; margin: 0 auto; width: 100%; box-sizing: border-box; }
.fig-pane .doc-figure-plate.img-w-small { max-width: 300px; }
.fig-pane .doc-figure-img { cursor: zoom-in; }
.fig-pane-old { background: var(--red-bg); box-shadow: 0 0 0 8px var(--red-bg); }
.fig-pane-new { background: var(--green-bg); box-shadow: 0 0 0 8px var(--green-bg); }
.fig-split-tag {
    display: block;
    margin-top: 6px;
    font: var(--font-microcap);
    text-transform: uppercase;
    letter-spacing: 0.14em;
}
.fig-split-tag-old { color: var(--diff-del-body); }
.fig-split-tag-new { color: var(--diff-add-body); }

/* Equation: the whole old line on the red band above the whole new line on
   the green, each the clean render (the band hugs the formula, on the SVG). */
.figure-block .doc-equation { transition: opacity var(--dur) var(--ease); }
.eq-old-line { opacity: 0.6; margin-bottom: 0; }
.eq-old-line .eq-content { cursor: default; }
.eq-old-line .eq-content:hover { background: none; }
.eq-old-line .eq-number { visibility: hidden; }
.eq-content.eq-band-add > svg,
.eq-content.eq-band-del > svg {
    padding: 0.35em 0.6em;
    box-sizing: content-box;
    border-radius: var(--radius-2xs);
    transition: background-color var(--dur-reveal) var(--ease);
}
.eq-content.eq-band-add > svg { background: var(--green-bg); }
.eq-content.eq-band-del > svg { background: var(--red-bg); }

/* The decision, while its request is out (Figures.armDecided): what leaves
   fades, the wash under what stays drains. The settle then takes the leaving
   parts out inside one glide (Figures.settleDecided). */
.figure-block.fig-settle-accept :is(.eq-old-line, .fig-pane-old, .fig-alt-diff, del.diff-del, .grid-ghost),
.figure-block.fig-settle-reject :is(.doc-equation:not(.eq-old-line), .fig-pane-new, .fig-alt-diff, ins.diff-add, .grid-added) {
    opacity: 0;
    transition: opacity var(--dur-reveal) var(--ease);
}
.figure-block.fig-settle-accept :is(.eq-band-add > svg, .fig-pane-new, ins.diff-add, .grid-added, .grid-added td),
.figure-block.fig-settle-reject :is(.eq-band-del > svg, .fig-pane-old, del.diff-del, .grid-ghost, .grid-ghost td) {
    background-color: transparent;
    background-size: 100% 0;
    transition: background-color var(--dur-reveal) var(--ease), background-size var(--dur-reveal) var(--ease),
        box-shadow var(--dur-reveal) var(--ease);
}
.figure-block.fig-settle-accept .fig-pane-new,
.figure-block.fig-settle-reject .fig-pane-old { box-shadow: 0 0 0 8px transparent; }
.figure-block.fig-settle-accept .fig-pane-new .doc-figure-plate,
.figure-block.fig-settle-reject .fig-pane-old .doc-figure-plate {
    background-color: var(--doc-figure-bg);
    transition: background-color var(--dur-reveal) var(--ease);
}
.figure-block.fig-settle-accept .fig-split-tag-new,
.figure-block.fig-settle-reject .fig-split-tag-old { opacity: 0; transition: opacity var(--dur-reveal) var(--ease); }
.figure-block.fig-settle-reject .eq-old-line { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .fig-settle-accept *, .fig-settle-reject * { transition: none !important; }
}

/* ── Insert figure popover ────────────────────────────────── */
/* The formula window (Figures._openMathWindow) wears the insert popover's own
   stock and width, so the Math tile's menu never widens into it. */
/* No padding of its own: the popover's rim (its 5px and the picker's) is the
   rim every lane's rows sit on, so the well and the case stand where they do. */
.mi-pane { display: flex; flex-direction: column; gap: 10px; }
/* Which line the formula lands on: a two-value control at the head of the pane,
   because a display equation and an inline formula are one object stored twice. */
.mi-mode { display: flex; gap: 2px; }
.mi-mode-opt {
    flex: 1;
    white-space: nowrap;
    padding: 5px 8px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    font: 500 var(--fs-sm)/1.3 var(--sans);
    color: var(--t3);
    cursor: pointer;
    transition: background var(--dur), color var(--dur);
}
.mi-mode-opt:hover { color: var(--t2); }
.mi-mode-opt.active { background: var(--chrome-hover); color: var(--t1); }
/* The door's way back to the insert menu: the lanes' own `‹ Math` at the
   segment's head, its own width only, so the two halves keep their line. */
.mi-mode-back.fig-pane-back { flex: none; padding: 2px 10px 2px 2px; margin-right: 4px; }
.eq-error { overflow-wrap: anywhere; }

/* The well: the LaTeX field over its own drawing (.mi-ink, MathInput.inkHtml).
   The field's glyphs are transparent and the drawing sets the same characters
   under them, so the two share every metric declared here and nowhere else. */
.mi-pane--edit { gap: 8px; }
.mi-well {
    position: relative; padding: 6px 10px;
    background: var(--chrome-surface);
    border: 1px solid var(--chrome-border); border-radius: var(--radius-sm);
}
.mi-well:focus-within { border-color: var(--accent-border); }
.mi-src { position: relative; }
.mi-field, .mi-ink {
    box-sizing: border-box; margin: 0; border: 0;
    padding: 10px 36px 10px 0;   /* the right pad keeps the first line clear of Copy */
    font: 13px/1.5 var(--mono); letter-spacing: normal; tab-size: 4;
    white-space: pre-wrap; overflow-wrap: anywhere;
}
.mi-ink { position: absolute; inset: 0; color: var(--t1); pointer-events: none; overflow: hidden; }
.mi-field {
    position: relative; display: block; width: 100%; min-height: 0;
    resize: none; overflow: hidden; background: none; outline: none;
    color: transparent; -webkit-text-fill-color: transparent;
    caret-color: var(--accent);
}
.mi-field::placeholder { color: var(--t4); -webkit-text-fill-color: var(--t4); opacity: 1; }
.mi-field::selection { background: rgba(var(--accent-rgb), 0.28); }
/* What only shapes the formula steps back: a command that takes a group, and
   the script marks quieter still, so the student's own letters carry the ink. */
.mi-ink .mi-k { color: var(--t3); }
.mi-ink .mi-p { color: var(--t4); }
/* A brace pair is a box: its braces are the box's edges, so nothing moves. The
   box the caret is in is lit, the other arguments of its structure dotted. */
/* The edges' hairline of the well's own ground parts two boxes that touch
   (\frac{a}{b}) without moving a character. */
.mi-ink .mi-g {
    border-radius: 2px; background: rgba(255,255,255,0.055);
    box-shadow: inset 1px 0 0 var(--chrome-surface), inset -1px 0 0 var(--chrome-surface);
}
.mi-ink .mi-b { color: transparent; }
.mi-ink .mi-g.is-empty,
.mi-ink .mi-g.is-near { background: none; outline: 1px dashed var(--t4); outline-offset: -1px; }
.mi-ink .mi-g.is-here {
    background: rgba(var(--accent-rgb), 0.12); outline: none;
    box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), 0.8);
}
/* A script's argument without braces has no edges to draw on: a tint only. */
.mi-ink .mi-t { border-radius: 2px; background: rgba(255,255,255,0.055); }
.mi-copy {
    position: absolute; top: 8px; right: 8px; padding: 2px;
    font: 500 var(--fs-xs)/1 var(--sans); color: var(--t4);
    background: none; border: none; cursor: pointer;
    transition: color var(--dur);
}
.mi-copy:hover { color: var(--t2); }
.mi-copy:disabled { opacity: 0.45; cursor: default; }
.mi-copy:disabled:hover { color: var(--t4); }

/* Friendly error line: plain language under the source field, never a modal. */
.mi-error { font: var(--fs-xs)/1.5 var(--sans); color: var(--red); }
/* The agent line closes the surface, above the foot. */
.mi-ask { margin-top: 2px; }

.fig-insert-popover {
    z-index: var(--z-dropdown);
    background: var(--chrome-bg2);
    border: 1px solid var(--chrome-border);
    border-radius: var(--radius);
    padding: 5px;
    box-shadow: var(--shadow-md);
    width: var(--insert-pop-w);
    box-sizing: border-box;
    /* place() caps max-height at the anchored side's room: the popover grows
       with panes and previews but scrolls instead of leaving the viewport. */
    overflow-y: auto;
    overscroll-behavior: contain;
}
/* Settle out of the plus: place() sets transform-origin to the anchored
   corner, so the menu visibly grows FROM the trigger. */
.fig-pop-in { animation: fig-pop-settle var(--dur-reveal) var(--ease-out); }
/* A line swap hands the formula window on: the same element slides to the
   formula's new place instead of popping in again. */
.fig-insert-popover--math.fig-pop-move { transition: top var(--dur-reveal) var(--ease-out), left var(--dur-reveal) var(--ease-out); }
@keyframes fig-pop-settle {
    from { opacity: 0; transform: scale(0.95); }
    to   { opacity: 1; transform: scale(1); }
}
/* Menu ↔ pane morph: the incoming face rises in. */
.fig-pane-in { animation: fig-pane-rise var(--dur-reveal) var(--ease-out); }
@keyframes fig-pane-rise {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}
/* Four makers as tiles, icon over label: at 14px beside the word the glyphs
   were decoration, at 18px over it they are the thing you aim at, and the
   four fit the popover's own width in one row. */
.fig-popover-tiles {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 2px;
}
.fig-popover-tile {
    flex-direction: column;
    gap: 6px;
    padding: 11px 6px;
    text-align: center;
    justify-content: center;
}
/* The two lanes that are not makers sit as full-width rows under the tiles. */
.fig-popover-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2px;
}
.fig-popover-item {
    display: flex;
    align-items: center;
    gap: 7px;
    width: 100%;
    padding: 7px 8px;
    font: var(--fs-sm)/1.2 var(--sans);
    color: var(--t2);
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--dur), color var(--dur);
    text-align: left;
    white-space: nowrap;
}
.fig-popover-item:hover {
    background: var(--chrome-hover);
    color: var(--t1);
}
/* The Reference lane points at something the document already has, rather than
   creating a new object, so it sits on its own full-width row under the four
   makers. */
.fig-popover-item--wide { grid-column: 1 / -1; }
.fig-popover-item svg {
    flex-shrink: 0;
    opacity: 0.7;
}
.fig-popover-item:hover svg {
    opacity: 1;
}
/* Lane pane: ONE bar carrying the way back on the left and the way forward on
   the right, so every lane is that bar plus a body, and a lane that commits on
   the pick leaves the slot empty. Sticky, so the way out never scrolls away. */
.fig-pane-bar {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 5px 6px;
    margin-bottom: 2px;
    background: var(--chrome-bg2);
    border-bottom: 1px solid var(--chrome-border);
}
.fig-pane-back {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 1;
    min-width: 0;
    padding: 2px 3px;
    font: 600 var(--fs-sm)/1.2 var(--sans);
    color: var(--t2);
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    transition: color var(--dur);
}
.fig-pane-back:hover { color: var(--t1); }
.fig-pane-back svg { flex-shrink: 0; opacity: 0.7; }
.fig-pane-do { display: flex; flex: none; }
.fig-popover-divider {
    height: 1px;
    background: var(--chrome-border);
    margin: 3px 0;
}
/* Reference lane: one row per placed figure, wearing the menu's own item
   recipe. The name carries the reader's number; the caption tells the two
   "Figure 3"s of a long paper apart. */
.fig-ref-list { display: flex; flex-direction: column; gap: 1px; }
.fig-ref-item {
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    padding: 6px 8px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    text-align: left;
    transition: background var(--dur), color var(--dur);
}
.fig-ref-item:hover { background: var(--chrome-hover); }
.fig-ref-name {
    flex-shrink: 0;
    font: 600 var(--fs-sm)/1.3 var(--sans);
    color: var(--t2);
}
.fig-ref-item:hover .fig-ref-name { color: var(--t1); }
.fig-ref-cap {
    flex: 1;
    min-width: 0;
    font: var(--fs-xs)/1.35 var(--sans);
    color: var(--t3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.fig-ref-empty {
    font: var(--fs-sm)/1.5 var(--sans);
    color: var(--t3);
    padding: 8px 8px 10px;
    max-width: 34ch;
}

/* The agent lane: ONE line wearing the composer's own underline, growing as you
   write, with the send beside what it sends. */
.fig-ask { display: flex; align-items: flex-end; gap: 8px; width: 100%; padding: 2px 2px 0; }
.fig-ask-input {
    flex: 1; min-width: 0; box-sizing: border-box; resize: none; overflow: hidden;
    background: none; border: none; outline: none;
    border-bottom: 1px solid rgba(var(--accent-rgb),0.30);
    font: 13px/1.55 var(--sans); color: var(--t1);
    padding: 5px 0 7px; caret-color: var(--accent);
    transition: border-color var(--dur);
}
.fig-ask-input:hover,
.fig-ask-input:focus { border-bottom-color: rgba(var(--accent-rgb),0.55); }
.fig-ask-input::placeholder { color: var(--t3); opacity: 0.7; }
/* Visible at rest, dimmed until there is something to send:
   the composer's own square, sized for the popover. */
.fig-ask-send {
    display: flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; flex: none;
    border: none; border-radius: var(--radius);
    background: var(--grad-gold-pill); color: var(--on-gold);
    box-shadow: var(--shadow-pill); cursor: pointer;
    transition: filter var(--dur), box-shadow var(--dur), opacity var(--dur);
}
.fig-ask-send:hover:not(:disabled) { filter: brightness(1.06); box-shadow: var(--shadow-pill-hover); }
.fig-ask-send:disabled { opacity: 0.45; box-shadow: none; cursor: default; }
/* In a window with its own commit (Save, Insert) the send never fills gold: the
   panel's pill, a dim arrow at rest, and the gold in the ARROW once there are
   words, so the commit stays the one gold object. The Ask lane's send IS its commit. */
.fig-ask--calm .fig-ask-send,
.fig-ask--calm .fig-ask-send:disabled {
    background: var(--grad-neutral-btn); color: var(--t3);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.05); opacity: 1;
}
.fig-ask--calm .fig-ask-send:not(:disabled) { color: var(--accent); }
.fig-ask--calm .fig-ask-send:hover:not(:disabled) { filter: none; background: var(--chrome-active); box-shadow: inset 0 1px 0 rgba(255,255,255,0.05); }
.fig-popover-picker {
    padding: 6px 4px 4px;
}

/* ── Inline image insert (the popover's Image lane) ── */
/* Hairlines, not a box: the popover's own divider is the only structure, and
   the picture row is the taller one because it is what you aim at. Its verb
   sits at the right edge, where a row's action goes. */
.fig-img-inline { display: flex; flex-direction: column; gap: 2px; width: 100%; }
/* Table pane: center the size grid under the back header. */
.fig-popover-picker .table-size-picker {
    display: flex; flex-direction: column; align-items: center;
}
.fig-img-row {
    display: flex; align-items: center; gap: 9px; width: 100%;
    padding: 14px 8px; background: none; border: none; border-radius: var(--radius-sm);
    font: var(--font-ui-sm); color: var(--t2); cursor: pointer; text-align: left;
    transition: background var(--dur), color var(--dur);
}
.fig-img-row:hover,
.fig-img-row:focus-visible { background: var(--chrome-hover); color: var(--t1); outline: none; }
.fig-img-row svg { flex: none; opacity: 0.7; }
.fig-img-row:hover svg { opacity: 1; }
.fig-img-row-lead { flex: 1; min-width: 0; }
.fig-img-row-verb {
    flex: none; font: var(--fs-xs)/1.4 var(--sans); color: var(--t3);
    text-decoration: underline; text-underline-offset: 2px;
    text-decoration-color: var(--chrome-border);
}
.fig-img-row:hover .fig-img-row-verb { color: var(--t2); }
/* A drag is answered by a wash over the whole pane, the way the sources panel
   answers one: the target is the surface, never a drawn box. */
.fig-img-inline.dragover {
    background: var(--accent-subtle);
    box-shadow: inset 0 0 0 1px var(--accent-border);
    border-radius: var(--radius-sm);
}
.fig-img-inline.dragover .fig-img-row,
.fig-img-inline.dragover .fig-popover-item { color: var(--accent); }
/* Under the wash the rows keep no ground of their own, or the pane reads as a
   card sitting inside the target instead of being it. */
.fig-img-inline.dragover .fig-img-row,
.fig-img-inline.dragover .fig-popover-item { background: none; }
.fig-img-inline.dragover .fig-img-row-verb { color: var(--accent); }
.fig-img-wait { font: var(--font-ui-sm); color: var(--t3); padding: 4px 2px 2px; }
/* The pick IS the commit, so the pane shows its work: the hairline runs until
   the figure exists. Indeterminate on purpose, the upload goes through fetch
   and reports no progress, so a drawn percentage would be invented. */
.fig-img-bar { height: 3px; border-radius: 999px; background: var(--chrome-border); overflow: hidden; }
.fig-img-bar > span {
    display: block; width: 40%; height: 100%; border-radius: 999px;
    background: var(--accent);
    animation: fig-img-run 1.1s var(--ease) infinite;
}
@keyframes fig-img-run {
    from { transform: translateX(-100%); }
    to   { transform: translateX(250%); }
}
/* .fig-ie-field is a papyrus input; recolor it for the chrome popover surface. */
.fig-img-inline .fig-ie-field {
    background: var(--chrome-surface); color: var(--t1);
    border: 1px solid var(--chrome-border); border-radius: var(--radius-sm);
    padding: 7px 10px; font: var(--font-ui); caret-color: var(--accent);
}
.fig-img-inline .fig-ie-field::placeholder { color: var(--t3); opacity: 0.6; }
.fig-img-inline .fig-ie-field:focus { color: var(--t1); border-color: var(--accent-border); }
.fig-img-err { font: var(--fs-xs)/1.4 var(--sans); color: var(--red); }

/* ── Google Docs-style table size picker ──────────────────── */
.table-size-picker {
    padding: 4px;
}
.picker-grid {
    display: grid;
    grid-template-columns: repeat(var(--picker-cols, 10), 18px);
    grid-template-rows: repeat(var(--picker-rows, 10), 18px);
    gap: 2px;
    padding: 1px;
}
.picker-cell {
    width: 18px;
    height: 18px;
    border: 1.5px solid var(--chrome-border);
    border-radius: var(--radius-xs);
    cursor: pointer;
    transition: background 60ms, border-color 60ms;  /* bespoke: near-instant grid tracking under the cursor */
}
/* Resizing keeps the table's present shape on the grid in the quiet tint,
   so the hovered shape reads against what it replaces. */
.picker-cell.current {
    background: var(--accent-subtle);
    border-color: var(--accent-border);
}
.picker-cell.highlight {
    background: var(--accent-dim);
    border-color: var(--accent);
}
.picker-label {
    font: var(--font-ui-sm);
    color: var(--t3);
    text-align: center;
    margin-top: 6px;
    min-height: 16px;
}
/* Table-resize popover: the grid picker anchored to its Resize button. */
.fig-resize-popover {
    position: fixed;
    z-index: var(--z-dropdown);
    background: var(--chrome-bg2);
    border: 1px solid var(--chrome-border);
    border-radius: var(--radius);
    padding: 8px;
    box-shadow: var(--shadow-md);
    animation: pop-rise var(--dur-reveal) var(--ease-out);
}
