/* ============================================================
   CiteOwl — Free tool pages (/tools/*)
   ============================================================
   ONE decision drives everything here: a tool page is a room in the
   app, not a brochure with a widget in it. So it borrows the editor's
   grammar rather than inventing a second one.

   TWO MATERIALS, NO CARDS. Dark chrome, then one papyrus sheet, and
   that is the end of the stack. Results are rows separated by hairlines
   ON the papyrus — never a white plate, never a grey sub-card floating
   inside one. The model is the app's own Sources panel (citations.css),
   which is already a list of retrieved records carrying per-item
   status, which is exactly what a checked bibliography is:

     .src-card      → .tool-row        hairline-separated, collapse/expand
     .src-summary   → .tool-row-head   20px mark / ident / verdict / chevron
     .src-title-txt → .tool-row-title  500 15px serif
     .src-byline    → .tool-row-by     Author · Year · Journal, 12px sans
     .src-facts     → .tool-facts      max-content/1fr, microcap keys
     .src-controls  → .tool-controls   plain-text toggles, brick = active
     .src-search    → the field recipe (--doc-bg-light + strong border)

   ONE ACCENT PER SURFACE. Gold (--accent) is the chrome's signal and
   never appears on papyrus; on the sheet the accent is brick
   (--doc-accent), per app/static/CLAUDE.md. The one exception is the
   .blog-cta block, which is chrome sitting on the page rather than
   document surface, and keeps its gold pill.

   ONE BUTTON SYSTEM, THREE RANKS:
     primary    .tool-btn-primary — filled brick
     secondary  .tool-btn         — bordered, the "+ Add source" recipe
     quiet      .tool-inline      — icon + word, the "Copy all references"
                                    recipe; footers and row actions
   Passive text (.tool-counter, .tool-note) is never a button.

   ONE VOCABULARY. A finding is stated the same way on all three pages:
   the checker's row verdicts and the lookup's answer sentence both name
   what the registry holds, and neither invents a second word for it.

   Verdicts are a mark plus a word plus a colour, in that order of
   importance — colour is never the only cue, and a count stays grey
   until it is non-zero.

   All type composes from variables.css tokens (--font-* roles, --fs-*
   scale) — no raw font sizes, no raw hex.
   ============================================================ */

/* ============================================================
   PAGE SCAFFOLDING
   ============================================================
   On a TOOL page the head lives inside the papyrus (.tool-strip), so
   there is no dark hero and the wrap carries the nav clearance itself:
   nav is fixed at top:16px and is 56px tall, so 96px clears it with air
   to spare and still puts the paste box comfortably inside a 900px
   laptop screen — which the old full-height hero did not.

   The /tools HUB keeps its dark hero (it is a section landing page, not
   a tool), so it takes .tool-page without the clearance and holds the
   hero to the same 860px column the cards below it use. */
.tool-page { padding: 0 0 var(--space-17); }
.tool-nohero { padding-top: var(--space-25); }
.tool-hero { max-width: 860px; }

/* ---- The page-head, as a strip on the sheet ----
   Title, then one line of lead under it. Stacked rather than flowed
   inline: a baseline row put the lead beside a short title ("DOI
   lookup") and under a long one ("Citation generator"), so the same
   block read as two different layouts across three sibling pages. The
   whole point of the strip is that it costs ~90px instead of a screen,
   so the tool is the first thing a visitor sees. */
.tool-strip { margin-bottom: var(--space-12); }
.tool-strip h1 {
    font: 500 var(--fs-4xl)/1.2 var(--serif);
    letter-spacing: var(--track-heading);
    color: var(--td1); margin: 0 0 var(--space-4);
}
/* Landing's .accent is a gold gradient painted through `background-clip:
   text` with a transparent fill, so `color` alone cannot override it on
   papyrus — the fill and the clip have to be unwound first. On the
   document surface the accent is flat brick. */
.tool-strip h1 .accent {
    background: none;
    -webkit-background-clip: initial;
    background-clip: initial;
    -webkit-text-fill-color: var(--doc-accent);
    color: var(--doc-accent);
}
/* One line each on desktop. No ch cap: these are one-line labels, not
   paragraphs, and a 56ch measure broke the longest of the three onto a
   second line while the other two stayed single. The sheet's own 860px
   column is the constraint; a phone wraps them naturally. */
.tool-strip p {
    font: var(--font-ui); color: var(--td3);
    margin: 0;
}
@media (max-width: 640px) {
    .tool-strip h1 { font-size: var(--fs-3xl); }
}

/* ============================================================
   THE WORKSHEET — the top zone of the sheet. No surface recipe of
   its own; just the rule that hands over to the prose below.
   ============================================================ */
.tool-work {
    padding-bottom: var(--space-14);
    border-bottom: 1px solid var(--doc-border-strong);
}

/* The field label is for screen readers only. Visually it was the fourth
   statement of the same fact: the DOI page said "put a DOI here" in the
   lead, in this label, in the placeholder AND in a hint under the
   buttons. The heading rank made it worse, because a 20px serif line
   directly under the h1 reads as a second title. The placeholder now
   carries the format and the label stays in the a11y tree, where it is
   the only place it was ever load-bearing. */
.tool-label {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Quiet supporting text on papyrus: the field hint under a form and the
   what-to-do-next line under a result are the same voice. The top gap
   is deliberately close to the sheet's own bottom padding — a hint set
   tight under the action row reads as part of the buttons. */
.blog-paper .tool-note {
    font: var(--font-ui); color: var(--td3);
    margin: var(--space-10) 0 0;
}
.blog-paper .tool-note a { color: var(--doc-accent); text-decoration: none; }
.blog-paper .tool-note a:hover { text-decoration: underline; text-underline-offset: 2px; }

/* Text fields wear the sources-panel search recipe (--doc-bg-light on a
   strong border) at worksheet scale. The inner shadow is what makes a
   4% tonal step read as a well rather than as an outline drawn on paper. */
.tool-textarea,
.tool-input {
    display: block; width: 100%;
    background: var(--doc-bg-light);
    border: 1px solid var(--doc-border-strong);
    border-radius: var(--radius);
    box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.07);
    color: var(--td1);
    caret-color: var(--doc-accent);
    padding: var(--space-8) var(--space-9);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* The paste box grows with what is pasted into it and stops at a cap,
   rather than carrying a drag handle. A resize grabber in the corner is
   a browser affordance, not one of ours — it sits on the papyrus as a
   foreign object, and nothing else in the product asks the reader to
   size a surface by hand. Tools.autogrow sets the inline height; the
   max-height here is the cap, and overflow takes over past it.

   The cap is ~12 lines against a ~7-line floor: enough that pasting a
   fuller list visibly opens the box, tight enough that the run button
   stays inside a 900px laptop screen, which is the whole reason the
   page-head became a strip. */
.tool-textarea {
    font: var(--font-doc-serif);
    min-height: 230px;
    max-height: 360px;
    resize: none;
    overflow-y: auto;
}
/* 16px floor also keeps iOS from zooming the field on focus. */
.tool-input { font: 400 var(--fs-lg)/1.5 var(--mono); }
.tool-textarea::placeholder,
.tool-input::placeholder { color: var(--td3); opacity: 0.8; }
/* Focus is the border going brick, and nothing else — the sources-panel
   recipe (`.src-search:focus-within`). A brick border PLUS an outer
   brick ring draws two concentric outlines around one field, which is
   what a 3px spread at 12% reads as at any pixel density. The inset
   well is kept so the field does not flatten out on focus. */
.tool-textarea:focus,
.tool-input:focus {
    outline: none;
    border-color: var(--doc-accent);
}

/* Action row: primary button, secondary buttons, live counter. Wraps to
   two rows on a phone rather than shrinking the button. */
.tool-actions {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-6) var(--space-6);
    margin-top: var(--space-9);
}
.tool-actions .tool-btn-primary { margin: 0; }
.tool-actions .tool-btn-primary[disabled] { cursor: progress; }
.tool-actions .tool-counter { margin-left: auto; }

/* THE secondary button on papyrus — the doc-context bordered button
   (sources-panel "+ Add source" recipe at worksheet scale). */
.tool-btn {
    display: inline-flex; align-items: center; gap: var(--space-3);
    font: var(--font-ui); font-weight: 500;
    color: var(--td2);
    background: transparent;
    border: 1px solid var(--doc-border-strong);
    border-radius: var(--radius-btn);
    padding: var(--space-4) var(--space-9);
    cursor: pointer;
    transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease),
                background-color var(--dur) var(--ease);
}
/* Hover goes ochre, matching .add-level-btn (the recipe this is derived
   from). A neutral hover is what made this button read as foreign on
   papyrus: every other document-context control warms to brick. */
.tool-btn:hover {
    border-color: var(--doc-ochre);
    color: var(--doc-ochre);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
.tool-btn:focus-visible { outline: 2px solid var(--doc-accent); outline-offset: 2px; }
.tool-btn[disabled] { opacity: 0.45; cursor: default; }

/* THE primary action on papyrus: the app's brick commit pill, verbatim.
   That is `--grad-brick-pill` + `--shadow-pill` with the brick halo and
   the darker top inset — the recipe behind "New document"
   (.dh-action--primary), the empty-canvas send button, import and
   preview-restore. Gold would be wrong here (chrome's signal, not the
   document's), and so is a flat --doc-accent fill: the product has no
   flat filled button anywhere, and inventing one for these pages is the
   exact mistake this rewrite exists to undo.

   border: none, like every other pill in the app — NOT a transparent
   1px one. The gradient is sized to the padding box but painted across
   the border box, so a transparent border shows the tile wrapping
   around: a dark 1px ring along the top (the gradient's end colour) and
   a light one along the bottom (its start). It reads as a stray
   outline. The 2px height difference against the bordered .tool-btn
   beside it is what .dh-action/.dh-action--primary already live with;
   .tool-actions centers them. */
.tool-btn-primary {
    --pill-halo-rgb: 139,58,42;
    --pill-top: 0.18;
    --pill-top-hover: 0.24;
    display: inline-flex; align-items: center; gap: var(--space-3);
    font: var(--font-ui); font-weight: 600;
    color: var(--btn-text);
    background: var(--grad-brick-pill);
    border: none;
    border-radius: var(--radius-btn);
    padding: var(--space-4) var(--space-11);
    cursor: pointer;
    box-shadow: var(--shadow-pill);
    transition: filter var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.tool-btn-primary:hover { filter: brightness(1.06); box-shadow: var(--shadow-pill-hover); }
.tool-btn-primary:active { filter: brightness(0.96); }
.tool-btn-primary:focus-visible { outline: 2px solid var(--doc-accent); outline-offset: 2px; }
.tool-btn-primary[disabled] { opacity: 0.6; cursor: progress; filter: none; }

/* The quiet rank: an icon and a word, the app's "Copy all references"
   recipe. Footer actions and per-row actions, never a bordered button. */
.tool-inline {
    display: inline-flex; align-items: center; gap: var(--space-3);
    background: none; border: none; padding: 0;
    font: var(--font-ui); font-weight: 500;
    color: var(--td3); cursor: pointer;
    transition: color var(--dur) var(--ease);
}
.tool-inline:hover { color: var(--doc-ochre-hover); }
.tool-inline:focus-visible { outline: 2px solid var(--doc-accent); outline-offset: 3px; border-radius: var(--radius-2xs); }
.tool-inline svg { flex-shrink: 0; opacity: 0.9; }

.tool-counter {
    font: var(--font-ui); color: var(--td3);
    font-variant-numeric: tabular-nums;
}

/* Button busy state: label swaps plus a turning spinner (shared `spin`
   keyframes, components.css). */
.tool-btn-spinner { animation: spin var(--dur-spin) linear infinite; }

/* ---- Progress: an honest indeterminate sweep + the phase + a clock ---- */
.tool-progress { margin-top: var(--space-10); }
.tool-progress-bar {
    position: relative;
    height: 3px; border-radius: var(--radius-2xs);
    background: var(--doc-border-strong);
    overflow: hidden;
}
.tool-progress-bar::after {
    content: ''; position: absolute; inset: 0; width: 38%;
    border-radius: inherit;
    background: var(--doc-accent);
    animation: tool-sweep var(--dur-pulse) var(--ease) infinite;
}
@keyframes tool-sweep {
    0%   { transform: translateX(-105%); }
    100% { transform: translateX(370%); }
}
.tool-progress [role="status"] {
    display: flex; align-items: baseline; gap: var(--space-5);
    font: var(--font-ui); color: var(--td3);
    margin-top: var(--space-5);
}
.tool-progress [role="status"] span:last-child {
    margin-left: auto;
    font-variant-numeric: tabular-nums;
}

/* ---- Inline message (validation, rate limit, network) ---- */
.tool-message {
    margin-top: var(--space-9);
    padding: var(--space-6) 0 var(--space-6) var(--space-9);
    border-left: 3px solid var(--doc-accent);
    font: var(--font-ui); color: var(--td2);
}
.tool-message--error { border-left-color: var(--diff-del-body); }
.tool-message strong { color: var(--td1); font-weight: 600; }

/* ============================================================
   RESULTS — shared
   ============================================================
   No surface of its own. Results sit on the same papyrus the worksheet
   does, separated from it by space rather than by a border, because
   they are the same document continuing.
   ============================================================ */
.tool-result { margin-top: var(--space-15); }

.blog-paper .tool-result-head {
    font: 600 var(--fs-2xl)/1.35 var(--serif);
    color: var(--td1);
    margin: 0 0 var(--space-5);
}

/* The app's tally line: bold numeral, light unit, middot separated
   (`7 sources · 7 cited · 12 citations` in the Sources panel). A count
   takes its verdict colour only when it is non-zero, so a clean run
   reads calm, and the word beside the number carries the meaning. */
.blog-paper .tool-tally {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: var(--space-1) var(--space-4);
    font: var(--font-ui); color: var(--td3);
    font-variant-numeric: tabular-nums;
    margin: 0;
}
.tool-tally b { font-weight: 400; color: inherit; }
.tool-tally [data-on] b { font-weight: 600; }
.tool-tally [data-status="verified"][data-on] b  { color: var(--diff-add-body); }
.tool-tally [data-status="mismatch"][data-on] b  { color: var(--warning); }
.tool-tally [data-status="not_found"][data-on] b { color: var(--diff-del-body); }
.tool-tally .tool-dot { color: var(--td3); opacity: 0.5; }

/* ---- Control row: plain-text filter toggles left, meta right ----
   Ported from .src-controls: brick ink for the active toggle, muted for
   the rest, nothing filled. No pills anywhere on these pages. */
.tool-controls {
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: var(--space-4) var(--space-10);
    padding: var(--space-9) 0 var(--space-6);
    font: var(--font-ui);
}
.tool-group { display: inline-flex; align-items: baseline; gap: var(--space-4); }
.tool-toggle {
    background: none; border: none; padding: 0;
    font: inherit; color: var(--td3); cursor: pointer;
    transition: color var(--dur) var(--ease);
}
.tool-toggle:hover { color: var(--doc-ochre-hover); }
.tool-toggle[aria-pressed="true"] { color: var(--doc-accent); font-weight: 500; }
.tool-toggle:focus-visible { outline: 2px solid var(--doc-ochre); outline-offset: 2px; border-radius: var(--radius-2xs); }
.tool-controls .tool-counter { margin-left: auto; }

/* ============================================================
   THE LEDGER — one row per reference (citation checker)
   ============================================================ */
.blog-paper .tool-list {
    display: flex; flex-direction: column;
    list-style: none; margin: 0; padding: 0;
    border-top: 1px solid var(--doc-border);
}
/* margin:0 is load-bearing: blog.css gives every <li> on the sheet a
   10px bottom margin, which opens a gap between the hairline and the
   next row and stops the list reading as one ledger. */
.blog-paper .tool-row { margin: 0; border-bottom: 1px solid var(--doc-border); }

/* The whole head is the toggle, laid out on the Sources panel's own
   four-column grid: mark / identity / verdict / chevron. */
.tool-row-head {
    display: grid;
    grid-template-columns: 20px 1fr auto 14px;
    gap: 0 var(--space-6);
    align-items: start;
    width: 100%; text-align: left;
    background: none; border: none;
    padding: var(--space-8) var(--space-2);
    cursor: pointer;
    transition: background-color var(--dur) var(--ease);
}
.tool-row-head:hover { background: var(--doc-hover-subtle); }
.tool-row-head:focus-visible { outline: 2px solid var(--doc-accent); outline-offset: -2px; border-radius: var(--radius-xs); }

.tool-mark { display: flex; align-items: center; justify-content: center; padding-top: 1px; }
.tool-row[data-status="verified"]  .tool-mark { color: var(--diff-add-body); }
.tool-row[data-status="mismatch"]  .tool-mark { color: var(--warning); }
.tool-row[data-status="not_found"] .tool-mark { color: var(--diff-del-body); }

.tool-row-ident { min-width: 0; }
.tool-row-title {
    display: block;
    font: 500 var(--fs-lg)/1.35 var(--serif); color: var(--td1);
    overflow-wrap: anywhere;
}
.tool-row-by {
    display: block;
    font: 400 var(--fs-sm)/1.45 var(--sans); color: var(--td3);
    margin-top: var(--space-1);
    overflow-wrap: anywhere;
}
/* The verdict: a word, in the colour its mark already carries. Never a
   badge. Wraps under the byline on a phone rather than squeezing the
   title into a two-word column. */
.tool-row-verdict {
    font: 500 var(--fs-sm)/1.6 var(--sans);
    white-space: nowrap; padding-top: var(--space-1);
}
.tool-row[data-status="verified"]  .tool-row-verdict { color: var(--diff-add-body); }
.tool-row[data-status="mismatch"]  .tool-row-verdict { color: var(--warning); }
.tool-row[data-status="not_found"] .tool-row-verdict { color: var(--diff-del-body); }

.tool-chev {
    color: var(--td3); display: flex; align-items: center;
    padding-top: var(--space-2);
    transition: transform var(--dur) var(--ease);
}
.tool-row.is-open .tool-chev { transform: rotate(90deg); }

/* Expand panel — grid-template-rows 1fr↔0fr animates to the TRUE
   content height (the app's own collapse primitive), so a long record
   and a one-line reason both land right. The inner div is the clipper. */
.tool-panel {
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows var(--dur-smooth) var(--ease);
}
.tool-row.is-open .tool-panel { grid-template-rows: 1fr; }
.tool-panel > div { overflow: hidden; min-height: 0; }
.tool-panel-in {
    padding: 0 var(--space-2) var(--space-11) calc(20px + var(--space-6));
}

/* The reference exactly as it was pasted, set as manuscript text — the
   editor's own body face. Long unbroken DOIs and URLs wrap rather than
   pushing the row wider than the phone. */
.blog-paper .tool-raw {
    font: var(--font-doc-serif); color: var(--td2b);
    margin: 0 0 var(--space-7);
    padding-left: var(--space-8);
    border-left: 1px solid var(--doc-border);
    overflow-wrap: anywhere;
}
.blog-paper .tool-reason { font: var(--font-ui); color: var(--td2); margin: 0; }

/* The dead-DOI line. "A real-looking DOI that leads nowhere" is the
   single strongest fabrication signal, so it gets its own line rather
   than a footnote inside the record. */
.blog-paper .tool-doi-line {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: var(--space-2) var(--space-5);
    margin: var(--space-7) 0 0;
    font: var(--font-ui); color: var(--td3);
}
.blog-paper .tool-doi-line code {
    font: 400 var(--fs-base)/1.5 var(--mono); color: var(--td2);
    background: var(--doc-hover); border-radius: var(--radius-xs);
    padding: var(--space-1) var(--space-3);
    overflow-wrap: anywhere;
}
.blog-paper .tool-doi-line--dead { color: var(--diff-del-body); font-weight: 500; }
.blog-paper .tool-doi-line--dead code { color: var(--diff-del-body); }
.tool-doi-line + .tool-reason { margin-top: var(--space-6); }

/* ---- The registered record, inside a row's panel ----
   The app's source-card facts grid (.src-facts): max-content key column,
   microcap uppercase keys, plain values. No card, no second surface. */
.tool-record { margin-top: var(--space-9); }
.blog-paper .tool-record-label {
    font: var(--font-microcap); text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--td3);
    margin: 0 0 var(--space-4);
}
.blog-paper .tool-record-title {
    font: 500 var(--fs-lg)/1.4 var(--serif); color: var(--td1);
    margin: 0 0 var(--space-6);
}
.tool-facts {
    display: grid; grid-template-columns: max-content 1fr;
    gap: var(--space-2) var(--space-7);
    margin: 0; font: var(--font-ui);
}
.tool-facts dt {
    font: var(--font-microcap); text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--td3);
    padding-top: var(--space-1);
}
.tool-facts dd { margin: 0; color: var(--td2); overflow-wrap: anywhere; }
/* The field the citation and the record disagree on. Amber plus a
   spelled-out note, so the disagreement is named, not just tinted. */
.tool-facts .tf-differs { color: var(--warning); }
.tf-note {
    display: block; margin-top: var(--space-1);
    font: var(--font-meta); color: var(--warning);
}

.blog-paper .tool-open {
    display: inline-block; margin-top: var(--space-7);
    font: var(--font-ui); font-weight: 500;
    color: var(--doc-accent); text-decoration: none;
    overflow-wrap: anywhere;
}
.blog-paper .tool-open:hover { text-decoration: underline; text-underline-offset: 2px; }

/* Filter that leaves the list empty (e.g. Problems on a clean list). */
.blog-paper .tool-list-empty {
    font: italic 400 var(--fs-md)/1.5 var(--serif); color: var(--td3);
    margin: 0;
    padding: var(--space-11) var(--space-2);
    border-bottom: 1px solid var(--doc-border);
}

/* ---- Footer: quiet actions, then where the answer came from ---- */
.tool-foot {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-6) var(--space-11);
    padding-top: var(--space-9);
    font: var(--font-ui); color: var(--td3);
}
.tool-foot .tool-counter { margin-left: auto; }
/* A footer link is a sibling of the quiet actions, not a block below
   them: .tool-open carries a top margin for its use inside a row panel. */
.blog-paper .tool-foot .tool-open { margin-top: 0; margin-left: auto; }

/* ============================================================
   THE ANSWER — one record (DOI lookup)
   ============================================================
   The page speaks first. A full serif sentence states what the
   identifier is registered to; the record follows as supporting fact.
   Every clause is assembled from fields the registry returned, never
   paraphrased — /api/tools/* makes no LLM call on any branch.
   ============================================================ */
.blog-paper /* Full column width, no ch cap. The sheet is already held to 860px, so a
   measure on top of that only forced the answer into a narrow ragged
   block beside empty papyrus. */
.blog-paper .tool-answer {
    font: 400 var(--fs-4xl)/1.35 var(--serif);
    letter-spacing: var(--track-heading);
    color: var(--td1);
    margin: 0 0 var(--space-7);
    text-wrap: balance;
}
.tool-answer .ok  { color: var(--diff-add-body); }
.tool-answer .bad { color: var(--diff-del-body); }
.blog-paper .tool-answer em { font-style: italic; }
.blog-paper .tool-answer-note {
    font: var(--font-ui); color: var(--td3);
    margin: 0 0 var(--space-14);
}
.blog-paper .tool-answer-note a { color: var(--doc-accent); text-decoration: none; }
.blog-paper .tool-answer-note a:hover { text-decoration: underline; text-underline-offset: 2px; }

/* The record as labelled rows: microcap key, serif value, optional
   action. Same three-column shape as the ledger row, one rank quieter. */
.tool-keyrows { border-top: 1px solid var(--doc-border); margin: 0; }
.tool-keyrow {
    display: grid; grid-template-columns: 88px 1fr auto;
    gap: 0 var(--space-9); align-items: start;
    padding: var(--space-7) var(--space-2);
    border-bottom: 1px solid var(--doc-border);
}
.tool-keyrow .tk-k {
    font: var(--font-microcap); text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--td3);
    padding-top: var(--space-3);
}
.tool-keyrow .tk-v {
    margin: 0;
    font: 400 var(--fs-lg)/1.5 var(--serif); color: var(--td1);
    overflow-wrap: anywhere;
}
/* Values that are facts rather than prose (an author list, a DOI) drop
   to the UI face — the record's own title is the only serif line. */
.tool-keyrow .tk-v--meta { font: var(--font-ui); color: var(--td2); line-height: 1.6; }
.tool-keyrow .tk-v--doi { font: 400 var(--fs-base)/1.7 var(--mono); color: var(--td2); }
.tool-keyrow .tk-v em { font-style: italic; }
/* Centred against the row rather than pinned to the first line: the only
   row carrying an action is the title, whose value routinely wraps to
   two lines, and a link hanging off the top of a two-line cell reads as
   misaligned. */
.tool-keyrow .tk-a { align-self: center; white-space: nowrap; }
.tool-keyrow .tk-a a {
    font: var(--font-ui); font-weight: 500;
    color: var(--doc-accent); text-decoration: none;
}
.tool-keyrow .tk-a a:hover { text-decoration: underline; text-underline-offset: 2px; }
@media (max-width: 600px) {
    .tool-keyrow { grid-template-columns: 1fr; gap: var(--space-2); }
    .tool-keyrow .tk-k, .tool-keyrow .tk-a { padding-top: 0; }
}

/* The abstract, behind a disclosure so 1,200 characters can never
   outweigh the title. Manuscript face: it is the paper's own prose. */
.tool-abstract { margin-top: var(--space-9); }
.tool-abstract > summary {
    font: var(--font-ui); font-weight: 500;
    color: var(--doc-accent); cursor: pointer;
    list-style: none;
}
.tool-abstract > summary::-webkit-details-marker { display: none; }
.tool-abstract > summary::after { content: ' \203A'; }
.tool-abstract[open] > summary::after { content: ' \2304'; }
.tool-abstract > summary:hover { text-decoration: underline; text-underline-offset: 2px; }
.tool-abstract > p {
    font: var(--font-doc-serif); color: var(--td2);
    margin: var(--space-7) 0 0;
    max-width: 68ch;
}

/* ============================================================
   THE REFERENCE BLOCK (citation generator)
   ============================================================
   The style picker belongs to the OUTPUT, not to the search: it sits on
   the bar directly above the reference it restyles, so what it does is
   self-evident. It appears with the first result and not before.
   ============================================================ */
.tool-out-bar {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-6);
    margin-bottom: var(--space-9);
}
.tool-out-bar .tool-inline { margin-left: auto; }

/* One segmented control, not five loose toggles: shared border, hairline
   dividers, brick fill on the pressed segment. */
.tool-seg {
    display: inline-flex; flex-wrap: wrap;
    border: 1px solid var(--doc-border-strong);
    border-radius: var(--radius-btn);
    overflow: hidden;
}
.tool-seg button {
    font: var(--font-ui); font-weight: 500;
    color: var(--td2); background: transparent;
    border: none; border-right: 1px solid var(--doc-border);
    padding: var(--space-4) var(--space-9);
    cursor: pointer;
    transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.tool-seg button:last-child { border-right: none; }
.tool-seg button:hover { color: var(--doc-ochre); background: var(--doc-hover); }
.tool-seg button[aria-pressed="true"] {
    background: var(--doc-accent); color: var(--doc-bg);
}
.tool-seg button:focus-visible { outline: 2px solid var(--doc-accent); outline-offset: -2px; }

/* The formatted reference, set in the document body face — this is what
   the entry looks like in a CiteOwl bibliography. reference_html is
   server-rendered by the exporters' own formatter, so the <em> and the
   <a class="ref-url"> come from us, not from anything typed in. */
.tool-reference {
    font: var(--font-doc-serif); color: var(--td1);
    font-size: var(--fs-xl);
    overflow-wrap: anywhere;
}
.tool-reference .ref-item { text-indent: -1.6em; padding-left: 1.6em; }
.tool-reference em { font-style: italic; }
.blog-paper .tool-reference .ref-url { color: var(--doc-accent); text-decoration: none; }
.blog-paper .tool-reference .ref-url:hover { text-decoration: underline; text-underline-offset: 2px; }

/* Provenance: one quiet line, under the thing it vouches for. */
.blog-paper .tool-provenance {
    font: var(--font-ui); color: var(--td3);
    margin: var(--space-9) 0 0;
    padding-top: var(--space-7);
    border-top: 1px solid var(--doc-border);
    max-width: 64ch;
}

/* ============================================================
   /tools HUB — the product lineup. Three rows of the blog's own card
   material (.blog-card), laid horizontally: scene left, text right, a
   gold action line naming what you do there. This is chrome sitting on
   the dark page, so gold is correct here.
   ============================================================ */
.tools-hub-grid { display: grid; gap: var(--space-10); }
.tool-page > .tools-hub-grid { margin-bottom: var(--space-14); }
.tool-card { flex-direction: row; align-items: stretch; }
/* The art is sized so the 440x180 drawing lands close to the scale it gets on
   a blog card. `meet` letterboxes it, so the box is held to the art's own
   aspect and centred.
   Unlike a blog card, the scene here is NOT a plate: no gradient ground, no
   ruled texture and no rule dividing it from the text, so the animation reads
   as drawn on the card itself. */
.tool-card .scene {
    width: 340px; flex-shrink: 0;
    aspect-ratio: 440 / 180;
    align-self: center;
    height: auto;
    background: none;
    border-bottom: none;
    border-right: none;
}
.tool-card .scene::after { content: none; }
@media (max-width: 900px) { .tool-card .scene { width: 280px; } }
.tool-card .blog-card-inner { padding: var(--space-12) var(--space-13); }
/* The card's action line: plain gold text, parallel verb-first labels,
   never a pill. The whole card is the link; the arrow nudges on hover. */
.tool-card-cta {
    display: flex; align-items: center; gap: var(--space-3);
    margin-top: auto;
    font: var(--font-ui); font-weight: 500;
    color: var(--accent);
}
.tool-card-cta svg { flex-shrink: 0; transition: transform var(--dur) var(--ease); }
.tool-card:hover .tool-card-cta svg { transform: translateX(2px); }
@media (max-width: 720px) {
    .tool-card { flex-direction: column; }
    .tool-card .scene {
        width: 100%; height: 176px;
        border-right: none;
        border-bottom: none;
    }
}
/* Below 900px the wrap is gutterless (like .blog-article), and dark
   cards on the dark page need the same side gutter blog.css restores
   for .blog-after. Hub only: the cross-link copy of this grid sits
   inside .blog-after, which already carries that gutter. */
@media (max-width: 900px) {
    .tool-page > .tools-hub-grid { padding-left: var(--space-12); padding-right: var(--space-12); }
}

/* ============================================================
   TOOL CROSS-LINKS — "the other free tools" at the foot of each tool
   page is the same .tools-hub-grid of two .tool-cards, animated scene
   and all, so a sibling tool is described and drawn identically
   wherever it is offered. No third "all the tools" card: the hub is in
   the nav and the footer, and a scene-less row beside two drawn ones
   reads as a gap.
   ============================================================ */

/* ============================================================
   MOTION — page arrival
   ============================================================
   Every blog page fades its content in on load (the staggered
   `blog-rise` block in blog.css). These pages share that chrome and
   continue the same stagger with the same keyframe and guard, so a tool
   page arrives exactly like an article does.
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
    .tool-page > .tools-hub-grid,
    .tool-page > .blog-paper,
    .tool-page > .blog-after {
        opacity: 0;
        animation: blog-rise 320ms var(--ease) both;
    }
    .tool-page > .tools-hub-grid { animation-delay:  40ms; }
    .tool-page > .blog-paper     { animation-delay:  80ms; }
    .tool-page > .blog-after     { animation-delay: 120ms; }
}

/* ============================================================
   MOTION — the run loop
   ============================================================
   Three beats and no more: the bar rises in when a run starts, fades
   out when the answer is back, and the result lands in the space it
   left. Only opacity and transform move, so nothing here can reflow the
   worksheet. One-shot classes are stripped by Tools.enter on
   animationend (tools.js); the exit is driven by Tools.leave, which
   renders what comes next only once the bar is gone.
   ============================================================ */

/* Things that appear in place inside the worksheet: the progress block
   and the inline message. */
@keyframes tool-rise {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: none; }
}
.tool-in { animation: tool-rise var(--dur-reveal) var(--ease-out) both; }

/* The progress block's exit. Opacity only: the block leaves the layout
   via `hidden` at the END of this fade, never during it, so whatever
   replaces it cannot land on top of a bar that is still going. */
@keyframes tool-leave { to { opacity: 0; } }
.tool-leaving { animation: tool-leave var(--dur) var(--ease) both; }

/* The result arrives as one block, rising into the space the bar just
   vacated. --dur-smooth because this is the answer the visitor waited
   seconds for: it should read as a landing, not a blink. */
@keyframes tool-result-rise {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}
.tool-result-in { animation: tool-result-rise var(--dur-smooth) var(--ease-out) both; }

/* Rows resolve a beat behind the block. Fade only: the block is already
   carrying the rise, and a second translate inside a moving surface
   reads as wobble. The delays live here rather than in JS so the ripple
   is capped by construction at 150ms, which keeps a 50-reference list
   to the same ~305ms arrival as a 5-reference one. Opacity touches no
   layout, so the rows are already at final height and nothing shifts. */
@keyframes tool-fade { from { opacity: 0; } to { opacity: 1; } }
.tool-row-in { animation: tool-fade var(--dur-reveal) var(--ease-out) both; }
.tool-row-in:nth-child(2)   { animation-delay:  30ms; }
.tool-row-in:nth-child(3)   { animation-delay:  60ms; }
.tool-row-in:nth-child(4)   { animation-delay:  90ms; }
.tool-row-in:nth-child(5)   { animation-delay: 120ms; }
.tool-row-in:nth-child(n+6) { animation-delay: 150ms; }

/* Restyling on the generator is NOT an arrival: runGenerate keeps the
   block standing on purpose, so nothing moves and only the reference
   itself settles back. The style bar stays at full strength throughout
   because the record did not change, which is this page's own argument
   made visible. 0.5 is where tool-restyle-in starts, so the swap frame
   carries no step in opacity at all, only new text. */
.tool-restyling .tool-reference {
    opacity: 0.5;
    transition: opacity var(--dur) var(--ease);
}
@keyframes tool-restyle-in { from { opacity: 0.5; } to { opacity: 1; } }
.tool-restyle { animation: tool-restyle-in var(--dur-reveal) var(--ease-out) both; }

/* ============================================================
   PHONE — 375px is the target. Sizes are already token-fixed, so this
   block is structural only: shorter paste box, full-width primary
   action, the verdict wrapping under the byline rather than squeezing
   the title. The sheet itself is .blog-paper, whose clamp() padding
   already handles the phone.
   ============================================================ */
@media (max-width: 600px) {
    .tool-textarea { min-height: 180px; max-height: 300px; }
    /* Full-width primary action, then the secondary actions on their own
       row. A half-width button beside a bordered one reads cramped at
       375px. */
    .tool-actions .tool-btn-primary { width: 100%; text-align: center; justify-content: center; }
    .tool-actions .tool-counter { margin-left: 0; width: 100%; }

    /* The verdict drops to its own row under the byline so the title keeps
       a readable measure. EVERY cell is placed explicitly: an explicitly
       positioned item is placed before the auto-placement pass, so leaving
       any of these to flow pushes the identity column into the 20px mark
       track and the title renders one character wide. */
    .tool-row-head { grid-template-columns: 20px 1fr 14px; gap: 0 var(--space-5); }
    .tool-mark        { grid-area: 1 / 1; }
    .tool-row-ident   { grid-area: 1 / 2; }
    .tool-chev        { grid-area: 1 / 3; padding-top: var(--space-3); }
    /* Spans to the chevron's edge and is allowed to wrap: "Record does not
       match" does not fit on one 390px line beside anything else. */
    .tool-row-verdict {
        grid-area: 2 / 2 / 3 / 4;
        padding-top: var(--space-4); white-space: normal;
    }
    .tool-panel-in { padding-left: calc(20px + var(--space-5)); }

    .blog-paper .tool-answer { font-size: var(--fs-3xl); max-width: none; }
    .tool-seg { width: 100%; }
    .tool-seg button { flex: 1; padding-left: var(--space-4); padding-right: var(--space-4); }
    .tool-out-bar .tool-inline { margin-left: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .tool-progress-bar::after { animation: none; width: 100%; opacity: 0.55; }
    .tool-btn-spinner { animation: none; }
    .tool-textarea, .tool-input, .tool-btn, .tool-card-cta svg,
    .tool-panel, .tool-chev { transition: none; }
    /* Belt to Tools.reduced()'s braces: the JS never applies these classes
       under reduce, and this makes them inert even if one slips through.
       The dimmed restyle STATE stays, only its movement goes: reduced
       motion removes the animation, not the meaning. */
    .tool-in, .tool-leaving, .tool-result-in, .tool-row-in,
    .tool-restyle { animation: none; }
    .tool-restyling .tool-reference { transition: none; }
}
