/*
 * Stetworth /app — the task detail page family: the detail skeleton,
 * deliverables, context files, the failure banner, the
 * publish-approval banner, the budget-gate panel, and the preview
 * bodies — each section cut from style.css unchanged and in the
 * original order.  The rules that must beat style.css's shared
 * vocabulary on source order at equal specificity (the
 * approval-confirmation badge, the two approval badges, the
 * needs-approval card modifier, the reject-reason height, the budget
 * decision error) stay behind in style.css instead.
 */
/*
 * Load order is load-bearing: the /app shell links every split sheet
 * BEFORE style.css.  That keeps both source-order contracts intact —
 * a rule kept behind in style.css still beats its shared group there
 * by file order, and the responsive tail at the end of style.css
 * still overrides this sheet's base rules at equal specificity.
 * This sheet declares no design-token block of its own and carries
 * no media block of any kind — every width, motion and paged
 * override for these rules lives in style.css's responsive tail
 * (docs/APP.md#the-split-stylesheets).
 */

/* ============================================================
 * Task detail page
 *
 * The detail view is a centered, narrow column rendered when the
 * route is `#/tasks/<id>` — server-backed on this surface.  The page
 * is tiered — state first, product next, audit material behind
 * section-level disclosures.  Layout sections, top to bottom:
 *   - .detail-header (back link + title + the brief: one paragraph
 *     at 280 characters or fewer, else the truncated preview plus
 *     the .detail-brief disclosure holding the full text)
 *   - .detail-summary (the letterhead summary card — a raised sheet
 *     under a 3px oxblood head rule, holding the status row with the
 *     live progress chip over a double-hairline letterhead, the two
 *     always-present gate mounts, the failure banner, and the
 *     plain-text output fact line):
 *       - .detail-cancel (the always-present Cancel mount — zero-height
 *         while empty, holds the Cancel control while the task is
 *         queued or in progress; first among the mounts because it is
 *         the state row's own action)
 *       - .detail-approval (the publish-approval mount; zero-height
 *         while empty, holds the .approval-banner while the task
 *         waits at the publish gate — see the approval section below)
 *       - .detail-budget (the budget-gate mount, same
 *         zero-height-while-empty contract; holds the .budget-banner
 *         Continue / Stop panel while the task is parked at its
 *         budget cap — see the budget-gate section below)
 *       - .failure-banner (when status === 'failed'; carries the
 *         inline resume error)
 *       - .detail-summary-fact ("Output is ready." — terminal tasks
 *         with output; a ledger row, deliberately no anchor)
 *   - .detail-section-output (the deliverable cards + "Download all"
 *     when the payload carries `deliverables[]` entries, else the
 *     single "Download output" control when the task has output)
 *   - .detail-section-pull-request (the single .detail-pr-link, when
 *     the payload's `pr_url` passes the SPA's http/https guard;
 *     hidden entirely otherwise, which is every task today)
 *   - .detail-section-context-files (one muted .context-file-row per
 *     `context_files[]` entry — the user's own uploads; hidden while
 *     the array is empty, present at any status)
 *   - the Steps disclosure (.detail-section-disclosure, default open
 *     unless the task is complete) holding .timeline (ordered list
 *     of steps + inline corrections; updated in place while the task
 *     is live, open or closed)
 *   - the task-scoped criteria section ("Criteria enforced on this
 *     task"; omitted for tasks without criteria) with its
 *     .review-disclosure fold, then the two short plain sections
 *     (target repository, integrations snapshot), then the three
 *     default-closed audit disclosures (best practices, assumptions,
 *     improvements) — .review-panel rows inside every folded body;
 *     rows are all still in the DOM — see the disclosure section
 *     below
 *   - modal scaffolding for the add / edit preference dialog and the
 *     deliverable preview (type-aware `.preview-*` bodies)
 *
 * The `.deliverable-*` and `.preview-*` rules further down are live
 * again: the cards render from the detail payload's real per-file
 * metadata, adapted from the mockup's demo-only treatment (see the
 * section comments below for what was trimmed in the port).
 *
 * The container caps at ~880px so prose and review rows stay
 * comfortably scannable on wide displays.
 * ============================================================ */

.detail {
    max-width: 880px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--s-7);
}

.detail-header {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
    padding-bottom: var(--s-5);
    border-bottom: 1px solid var(--c-rule);
}

.detail-back {
    font-size: var(--fs-ui);
    color: var(--c-ink-muted);
    border-bottom-color: transparent;
}

.detail-back:hover {
    color: var(--c-ink);
    border-bottom-color: var(--c-ink);
}

.back-link {
    border-bottom-color: transparent;
    color: var(--c-ink-muted);
}

.back-link:hover {
    color: var(--c-ink);
    border-bottom-color: var(--c-ink);
}

.detail-title {
    margin: 0;
    font-size: var(--fs-hero);
    line-height: 1.15;
}

.detail-description {
    color: var(--c-ink-muted);
    font-size: var(--fs-lede);
    max-width: 760px;
}

.detail-status-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-3);
    margin-top: var(--s-2);
}

.detail-status-meta {
    font-size: var(--fs-ui);
    color: var(--c-ink-muted);
    font-variant-numeric: tabular-nums;
}

/* The summary card: the raised-sheet treatment extended to the
   detail's state tier — paper on the parchment ground under the 3px
   oxblood head rule (the letterhead device, byte-pinned as a brand
   carve-out in tests/test_app_design_tokens.py alongside the
   .assistant-thread head rule and the .sc-card spine), with the
   settle shadow.  Internal rows separate on --c-rule hairlines. */
.detail-summary {
    background: var(--c-paper);
    border: 1px solid var(--c-rule-strong);
    border-top: 3px solid var(--c-oxblood);
    border-radius: var(--radius);
    box-shadow: var(--sh-sheet);
    padding: var(--s-4) var(--s-5);
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
}

/* The card leads with the status row, so the header's top offset is
   redundant inside it. */
.detail-summary > .detail-status-row {
    margin-top: 0;
}

/* Letterhead under the status row: the double hairline drawn with
   borders — the .assistant-thread::before device.  The row is a
   wrapping flex line, so a full-width pseudo-item lands on its own
   line beneath the chips. */
.detail-summary > .detail-status-row::after {
    content: '';
    width: 100%;
    height: 5px;
    border-top: 1px solid var(--c-rule);
    border-bottom: 1px solid var(--c-rule);
}

/* The output fact line: a plain-text ledger row (no anchor — an
   in-page fragment would fall through the hash router; the Output
   section sits directly beneath the card), closed by the card's
   internal hairline. */
.detail-summary-fact {
    margin: 0;
    padding-bottom: var(--s-3);
    font-size: var(--fs-ui);
    color: var(--c-ink);
    border-bottom: 1px solid var(--c-rule);
}

/* The long-brief disclosure under the truncated preview: a quiet
   textual control in the muted ui register, native affordance only. */
.detail-brief-summary {
    cursor: pointer;
    font-size: var(--fs-ui);
    color: var(--c-ink-muted);
}

.detail-brief-summary:hover {
    color: var(--c-ink);
}

.detail-brief[open] > .detail-brief-summary {
    margin-bottom: var(--s-2);
}

.detail-section {
    display: flex;
    flex-direction: column;
    gap: var(--s-4);
}

.detail-section-title {
    font-family: var(--font-accent);
    font-weight: 400;
    font-size: var(--fs-section);
    color: var(--c-ink);
    margin: 0;
    letter-spacing: -0.005em;
}

.detail-section-lede {
    color: var(--c-ink-muted);
    font-size: var(--fs-body);
    margin: 0;
    max-width: 720px;
}

/* Section-level disclosures (Steps and the audit trail): the section
   heading and its row count ARE the summary row, led by the house
   state glyph.  The first cut shipped marker-less (list-style: none
   with nothing in its place), and the rendered review found a closed
   "Steps" reading as a plain heading while the sibling folds on the
   same page — the long-brief preview, the criteria fold — keep their
   native triangles: two disclosure vocabularies on one screen, one of
   them invisible.  So the summary takes the account menu's own glyph
   treatment (the ::after caret at style.css's Account menu rule — an
   informational UI glyph under WCAG 1.4.11, muted ink for the 3:1
   floor): a small triangle pointing right closed and down open,
   matching the direction language of the native markers beside it.
   The two-rule native-marker removal below is the account menu's
   pattern too — flex display already suppresses the list-item marker
   in Chrome/Firefox, but WebKit builds through Safari 18.3 draw the
   legacy marker regardless of list-style, which would double the
   glyph. */
.detail-section-disclosure > summary {
    display: flex;
    align-items: baseline;
    gap: var(--s-3);
    cursor: pointer;
    list-style: none;
}

.detail-section-disclosure > summary::-webkit-details-marker {
    display: none;
}

.detail-section-disclosure > summary::before {
    content: "\25B8";
    color: var(--c-ink-muted);
}

.detail-section-disclosure[open] > summary::before {
    content: "\25BE";
}

/* The count's color delta on the shared micro-label type treatment
   (the grouped .tag rule, which stayed in style.css — a sheet linked
   AFTER this one, so the delta holds only by staying property-disjoint
   from that rule; no source-order tie-break exists across files). */
.detail-section-count {
    color: var(--c-ink-muted);
}

.detail-empty {
    color: var(--c-ink-muted);
    font-size: var(--fs-body);
    font-style: italic;
}

/* The task detail page's pull-request link — the .task-card-pr-link
 * treatment at body size, kept off the full column width so it reads
 * as one link rather than a bar. */
.detail-pr-link {
    align-self: flex-start;
    color: var(--c-oxblood);
    text-decoration: none;
    border-bottom: 1px solid transparent;
}

.detail-pr-link:hover { border-bottom-color: var(--c-oxblood); }

/* The always-present mount `_reconcileCancelControl` (web/app/js/app.js)
 * adds/removes the detail header's Cancel control in.  `.detail-header`
 * is a gapped column, so the mount must
 * take no space while it holds nothing — the `.detail-approval:empty`
 * rule applied to the third such mount on this surface.  The control's
 * own layout class (`.task-cancel`, shared with the gallery card) is
 * styled in web/app/css/style.css beside its other render site. */
.detail-cancel:empty {
    display: none;
}

/* ----------------------------------------------------------------
 * Deliverables — the typed per-file cards on the completed-task
 * detail (one .deliverable-row per `deliverables[]` entry inside
 * .review-panel-deliverables, with the "Download all" control in
 * the section head reusing .detail-section-head).  Icon badge +
 * name/size meta + Preview/Download actions.
 * ---------------------------------------------------------------- */

.deliverable-row {
    display: grid;
    grid-template-columns: 56px 1fr auto;
    gap: var(--s-4);
    align-items: center;
    padding: var(--s-4) var(--s-5);
    border-bottom: 1px solid var(--c-rule);
}

.deliverable-row:last-child {
    border-bottom: 0;
}

.deliverable-icon {
    width: 48px;
    height: 48px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--c-paper-soft);
    border: 1px solid var(--c-rule);
    border-radius: var(--radius);
    color: var(--c-ink-muted);
}

.deliverable-icon-text {
    font-family: var(--font-body);
    font-size: var(--fs-micro);
    font-weight: 600;
    letter-spacing: 0.08em;
}

.deliverable-meta {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.deliverable-name {
    margin: 0;
    font-size: var(--fs-body);
    font-weight: 500;
    color: var(--c-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.deliverable-info {
    margin: 0;
    font-size: var(--fs-meta);
    color: var(--c-ink-muted);
    font-variant-numeric: tabular-nums;
}

.deliverable-actions {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    flex-shrink: 0;
}

/* ----------------------------------------------------------------
 * Context files — the user's own uploads on the task detail (one
 * .context-file-row per `context_files[]` entry inside
 * .context-file-list).  Deliberately quieter than the deliverable
 * cards so provided-by-you inputs never read as produced output:
 * plain hairline-ruled list rows sitting directly on the page
 * parchment (no card cream fill), no icon badge, no type
 * vocabulary, no preview affordance — just name, size, and a ghost
 * Download.  All colors are existing palette tokens.
 * ---------------------------------------------------------------- */

.context-file-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid var(--c-rule);
    border-radius: var(--radius);
}

.context-file-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    padding: var(--s-3) var(--s-4);
    border-bottom: 1px solid var(--c-rule);
}

.context-file-row:last-child {
    border-bottom: 0;
}

.context-file-meta {
    display: flex;
    align-items: baseline;
    gap: var(--s-3);
    min-width: 0;
}

.context-file-name {
    margin: 0;
    font-size: var(--fs-body);
    color: var(--c-ink-soft);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.context-file-info {
    margin: 0;
    font-size: var(--fs-meta);
    color: var(--c-ink-muted);
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
}

.context-file-download-btn {
    flex-shrink: 0;
}

/* ----------------------------------------------------------------
 * Failure banner (status === 'failed')
 * ---------------------------------------------------------------- */

.failure-banner {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--s-4);
    align-items: center;
    padding: var(--s-4) var(--s-5);
    background: var(--c-paper);
    border: 1px solid var(--c-status-failed);
    border-left-width: 3px;
    border-radius: var(--radius);
}

.failure-banner-body {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
    min-width: 0;
}

.failure-banner-title {
    margin: 0;
    font-size: var(--fs-body);
    font-weight: 500;
    color: var(--c-status-failed);
}

.failure-banner-reason {
    margin: 0;
    font-size: var(--fs-body);
    line-height: 1.5;
    color: var(--c-ink-soft);
}

.failure-banner-actions {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    flex-shrink: 0;
}

/* Supplementary line under the failure reason: the session-local
 * echo of the user's own publish-rejection note (the server's
 * failure_reason stays fixed copy by design). */
.failure-banner-note {
    margin: 0;
    font-size: var(--fs-body);
    line-height: 1.5;
    font-style: italic;
    color: var(--c-ink-muted);
}

/* ----------------------------------------------------------------
 * Publish-approval banner + gallery badge (task.approval non-null)
 *
 * The /app rendering of the publish-confirmation gate — an /app
 * extension beyond the mockup, modeled on the console's
 * .publish-gate treatment but composed from this surface's
 * vocabulary: the failure-banner's accented-box structure with the
 * burnished "needs attention but alive" accent (the task still
 * reads In progress at the gate), .modal-field wells for the
 * reject-reason textarea, and the preview text in the recessed
 * monospace well the deliverable previews use.
 *
 * The per-call bodies inside `.approval-preview-call` are produced by
 * the renderer registry rather than by this surface's own markup;
 * their vocabulary is styled in the following section.
 * ---------------------------------------------------------------- */

/* The always-present mount `_reconcileApprovalBanner` adds/removes
 * the banner in.  `.detail-summary` is a gapped column, so the mount
 * must take no space while it holds nothing. */
.detail-approval:empty {
    display: none;
}

.approval-banner {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
    padding: var(--s-4) var(--s-5);
    background: var(--c-paper);
    border: 1px solid var(--c-burnished-text);
    border-left-width: 3px;
    border-radius: var(--radius);
}

/* The finished-but-unpublished variant.  The live gate is burnished
 * (something is pending and it will lapse); this one is moss, because
 * the build succeeded and nothing is counting down — the border is the
 * quickest way for a returning user to tell "act by Thursday" from
 * "ready when you are". */
.approval-banner-unpublished {
    border-color: var(--c-status-complete);
}

.approval-banner-title {
    margin: 0;
    font-size: var(--fs-body);
    font-weight: 600;
    color: var(--c-ink);
}

.approval-banner-lede {
    margin: 0;
    font-size: var(--fs-body);
    line-height: 1.5;
    color: var(--c-ink-soft);
}

.approval-banner-deadline {
    margin: 0;
    font-size: var(--fs-body);
    color: var(--c-ink);
}

.approval-preview {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
}

.approval-preview-loading,
.approval-preview-empty,
.approval-preview-error {
    margin: 0;
    /* var(--fs-body), not --fs-ui.  This sheet is Instrument Sans, not EB Garamond —
       the floor is a house convention for small italic, and NOTHING asserts
       it: the italic size test was deleted as unsound.  Keep it by hand. */
    font-size: var(--fs-body);
    font-style: italic;
    color: var(--c-ink-muted);
}

.approval-preview-group {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    padding: var(--s-3) var(--s-4);
    background: var(--c-paper);
    border: 1px solid var(--c-rule);
    border-radius: var(--radius-sm);
}

/* Group header: the eyebrow name on the left, the Edit control on the
 * right.  Edit is a ghost button on purpose — it is the third choice
 * behind Approve and Reject, and giving it any weight would read as a
 * recommendation to fiddle with what the task already staged. */
.approval-preview-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
}


.approval-preview-group-name {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--fs-micro);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--c-ink-muted);
}

.approval-preview-call {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
}

.approval-call-description {
    margin: 0;
    font-size: var(--fs-body);
    line-height: 1.5;
    color: var(--c-ink-soft);
}

/* The inlined draft artifact (`preview_text`) — the same recessed
 * monospace well as .preview-md-text, capped shorter so a long
 * draft doesn't push the decision controls off screen. */
.approval-call-preview-text {
    margin: 0;
    padding: var(--s-3) var(--s-4);
    font-family: var(--font-mono);
    font-size: var(--fs-ui);
    line-height: 1.55;
    color: var(--c-ink-soft);
    background: var(--c-paper);
    border: 1px solid var(--c-rule-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--sh-well);
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 30vh;
    overflow-y: auto;
}

.approval-banner-actions {
    display: flex;
    align-items: center;
    gap: var(--s-2);
}

/* ----------------------------------------------------------------
 * Budget-gate panel (task parked by the per-build budget backstop)
 *
 * Ported from the console when /console was retired.  It shares the
 * approval banner's structure — an accented aside in the detail
 * column, hydrated into an always-present mount — and, under the edge
 * law, its burnished waiting dress too: both gates park the task
 * waiting on the person, and oxblood belongs to the top letterhead
 * device.  The banners' own words tell the two gates apart — the
 * publish gate asks "is this right?", the budget gate asks "do you
 * want to keep paying?" — never the accent alone.
 * ---------------------------------------------------------------- */

/* Always-present mount; `.detail-summary` is a gapped column, so it
 * must take no space while it holds nothing. */
.detail-budget:empty {
    display: none;
}

.budget-banner {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
    padding: var(--s-4) var(--s-5);
    background: var(--c-paper);
    border: 1px solid var(--c-burnished-text);
    border-left-width: 3px;
    border-radius: var(--radius);
}

.budget-banner-title {
    margin: 0;
    font-size: var(--fs-body);
    font-weight: 600;
    color: var(--c-ink);
}

.budget-banner-lede,
.budget-banner-note {
    margin: 0;
    font-size: var(--fs-body);
    line-height: 1.5;
    color: var(--c-ink-soft);
}

.budget-banner-note { color: var(--c-ink-muted); }

.budget-banner-loading {
    margin: 0;
    font-size: var(--fs-ui);
    font-style: italic;
    color: var(--c-ink-muted);
}

.budget-banner-decided { font-style: italic; }

/* Decision context as a definition list: label column fixed so the
 * four values line up and the spend figure is easy to find. */
.budget-banner-stats {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
    margin: 0;
}

.budget-banner-stat {
    display: flex;
    gap: var(--s-3);
    font-size: var(--fs-body);
}

.budget-banner-stat dt {
    flex: 0 0 8.5rem;
    font-size: var(--fs-meta);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--c-ink-muted);
    padding-top: 0.15rem;
}

.budget-banner-stat dd {
    margin: 0;
    color: var(--c-ink);
    font-variant-numeric: tabular-nums;
    min-width: 0;
    word-break: break-word;
}

/* Same treatment as .account-ledger-link — the global `a` rule already
 * supplies the hairline underline, so this only recolors it. */
.budget-banner-pr-link {
    color: var(--c-oxblood);
    text-decoration: none;
    border-bottom: 1px solid transparent;
}

.budget-banner-pr-link:hover { border-bottom-color: var(--c-oxblood); }

.budget-banner-no-pr {
    color: var(--c-ink-muted);
    font-style: italic;
}

.budget-banner-actions {
    display: flex;
    align-items: center;
    gap: var(--s-2);
}

/* ----------------------------------------------------------------
 * Preview bodies (pdf/pptx, xlsx, md, csv, docx)
 *
 * Adapted from the mockup in the port to real data: the pdf/pptx
 * body is the extracted page/slide count alone (the mockup's
 * stylised fake page list stayed demo-only), and the xlsx sheet
 * tabs are static chips (spans, not buttons) because they summarize
 * workbook structure rather than switch anything.
 * ---------------------------------------------------------------- */

.preview-pdf {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
}

/* The paged-doc body's sole content: "N pages total" / "N slides
   total" — sized to read as the answer, not a caption. */
.preview-pdf-meta {
    margin: 0;
    font-size: var(--fs-body);
    color: var(--c-ink-soft);
}

.preview-sheet {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
}

.preview-sheet-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-1);
    padding-bottom: var(--s-2);
    border-bottom: 1px solid var(--c-rule);
}

/* Static chips (spans): a summary of the workbook's sheet names,
   not interactive tabs — so no cursor or hover affordances. */
.preview-sheet-tab {
    display: inline-block;
    padding: 0.35rem 0.7rem;
    font-family: var(--font-body);
    font-size: var(--fs-ui);
    color: var(--c-ink-muted);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
}

.preview-sheet-tab-active {
    color: var(--c-ink);
    background: var(--c-paper-soft);
    border-color: var(--c-rule);
}

.preview-sheet-text {
    margin: 0;
    padding: var(--s-3) var(--s-4);
    font-family: var(--font-mono);
    font-size: var(--fs-ui);
    line-height: 1.5;
    color: var(--c-ink-soft);
    background: var(--c-paper);
    border: 1px solid var(--c-rule-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--sh-well);
    white-space: pre-wrap;
    word-break: break-word;
}

.preview-md {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
}

.preview-md-text {
    margin: 0;
    padding: var(--s-3) var(--s-4);
    font-family: var(--font-mono);
    font-size: var(--fs-ui);
    line-height: 1.55;
    color: var(--c-ink-soft);
    background: var(--c-paper);
    border: 1px solid var(--c-rule-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--sh-well);
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 50vh;
    overflow-y: auto;
}

.preview-csv {
    overflow-x: auto;
}

.preview-csv-table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--font-body);
    font-size: var(--fs-ui);
}

.preview-csv-table th,
.preview-csv-table td {
    padding: 0.4rem 0.7rem;
    text-align: left;
    border-bottom: 1px solid var(--c-rule);
    color: var(--c-ink-soft);
}

.preview-csv-table th {
    font-weight: 500;
    color: var(--c-ink);
    background: var(--c-paper-soft);
    border-bottom: 1px solid var(--c-rule-strong);
}

/* Word documents: structure, not content — the first heading as a
   title line above the heading/paragraph counts.  There is no
   rendered page here on purpose (the payload carries counts, never
   body text), so the block reads as a summary, like the paged-doc
   body it sits beside. */
.preview-docx {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
}

.preview-docx-title {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--fs-lede);
    font-weight: 500;
    color: var(--c-ink);
    word-break: break-word;
}

.preview-docx-meta {
    margin: 0;
    font-size: var(--fs-body);
    color: var(--c-ink-soft);
}

.preview-empty {
    margin: 0;
    color: var(--c-ink-muted);
    font-style: italic;
    font-size: var(--fs-body);
}

