/* ============================================
   calcsheet - design tokens and base type

   The system locked in docs/brainstorms/ui-direction-brainstorm.md: a
   technical publication that computes, not a SaaS dashboard. Paper surface,
   near-black ink, STIX Two Text for prose and JetBrains Mono for every value
   that can change.

   Two rules the rest of the stylesheet may not break:

   - Blueprint blue means interactive. Nothing decorative is blue.
   - Green, amber and red mean pass, caution and fail, and nothing else, and
     they never carry the meaning alone: a verdict is always also a word or a
     glyph, because roughly one engineer in twelve cannot read the colour.

   Token names are semantic (--surface, --ink) rather than literal (--white,
   --black) so a dark mode is a value swap in one block. Light only ships now.
   ============================================ */

/* ============================================
   Fonts - self-hosted, latin-subset variable woff2

   STIX Two Text (body serif, scientific-publishing pedigree) and JetBrains
   Mono (every live value, table and check). Both are the official OFL
   releases as distributed by Google Fonts, latin subset only: 128KB total
   against a ~450KB budget. `unicode-range` matches the subset the files
   actually contain, so a character outside it falls back rather than
   rendering blank. KaTeX keeps its own baked-in fonts for maths.
   ============================================ */
@font-face {
    font-family: 'STIX Two Text';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('fonts/STIXTwoText-Roman-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'STIX Two Text';
    font-style: italic;
    font-weight: 400 700;
    font-display: swap;
    src: url('fonts/STIXTwoText-Italic-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 400 800;
    font-display: swap;
    src: url('fonts/JetBrainsMono-Roman-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'JetBrains Mono';
    font-style: italic;
    font-weight: 400 800;
    font-display: swap;
    src: url('fonts/JetBrainsMono-Italic-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================
   Design tokens
   ============================================ */
:root {
    /* Surfaces - warm paper, the Tufte value */
    --surface: #fffff8;
    --surface-sunken: #f7f7ef;
    --surface-raised: #fffffd;

    /* Ink */
    --ink: #16160f;
    --ink-muted: #55554c;
    --ink-faint: #86867c;

    /* Rules - hairlines, not borders */
    --rule: #dedad0;
    --rule-strong: #b8b4a8;

    /* Blueprint blue: interactive elements only */
    --accent: #1a4f8b;
    --accent-strong: #113a69;
    --accent-wash: #e8effa;

    /* Verdicts: pass / caution / fail only, always paired with a word or glyph */
    --pass: #2c6b3f;
    --pass-wash: #e7f2e9;
    --caution: #8a5600;
    --caution-wash: #faf0dd;
    --fail: #a32118;
    --fail-wash: #fbe9e7;

    /* Value-change cue (U2.4). Neutral on purpose: a changed number is not a verdict. */
    --flash-wash: #f2eeda;

    /* Type families */
    --font-body: 'STIX Two Text', Georgia, 'Times New Roman', serif;
    --font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;

    /* Type scale, 1.2 ratio off a 17px reading size */
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1.0625rem;
    --text-lg: 1.25rem;
    --text-xl: 1.5rem;
    --text-2xl: 1.8rem;
    --text-3xl: 2.2rem;

    --leading-tight: 1.25;
    --leading-body: 1.6;

    /* The page width: one column on every page (#105). A px value, not ch,
       so it does not resolve against whichever face an element is set in.
       Wide enough for a calc title on one line beside its two buttons. */
    --page-width: 1100px;

    /* Spacing */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-6: 1.5rem;
    --space-8: 2rem;
    --space-12: 3rem;

    /* Radii - a technical document is not rounded */
    --radius-sm: 2px;
    --radius-md: 4px;

    /* Motion: value-change cues and focus only. No entrance animation exists. */
    --flash-duration: 600ms;
    --transition-fast: 120ms ease;
}

/* ============================================
   Base

   Enough to prove the tokens on a real page. Layout is U1.1's job.
   ============================================ */
*, *::before, *::after {
    box-sizing: border-box;
}

body {
    margin: 0;
    background: var(--surface);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--text-base);
    line-height: var(--leading-body);
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
    font-family: var(--font-body);
    font-weight: 600;
    line-height: var(--leading-tight);
}

/* Every number that can change is mono and tabular, so a recompute cannot
   shift the layout under the reader.

   The size step is metric, not taste. Measured on this page at a 100px em,
   x-heights are STIX Two 48, KaTeX_Main 44, JetBrains Mono 55. Setting mono
   to 48/55 puts a mono value on the same x-height as the prose around it. */
code, kbd, samp, pre, input, select, button, table {
    font-family: var(--font-mono);
    font-size: 0.875em;
    font-variant-numeric: tabular-nums;
}

/* The one KaTeX ratio, and the only one this project tunes.

   KaTeX ships 1.21em, sized for Computer Modern beside a 16px sans. Against
   STIX Two that runs the maths about 11% tall and the page reads as prose
   with equations pasted in. 48/44 lands KaTeX's x-height on STIX's, so a
   symbol in a derivation and a word in a sentence share a baseline weight.
   Per-context sizing is deliberately not attempted: one ratio, done. */
.katex {
    font-size: 1.09em;
}

a {
    color: var(--accent);
}

/* One focus indicator for everything that does not declare its own. Blueprint
   blue, because focus is interaction and interaction is the only thing blue
   means here. */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

a:hover {
    color: var(--accent-strong);
}

/* ============================================
   Layout - one centred column of one width

   Every page is the same column: the index, a calc page, the policy pages and
   the validation page (#105). Header, main and footer fill it, so the header
   rule, the Given block, the tables and the footer share one left edge and one
   right edge. Sidenotes open inline at every width.

   Below the page width plus its padding the column is the viewport less the
   gutter, by `max-width` alone. That state is the majority product, not a
   fallback: plenty of engineers read this at 1280 with an IDE sidebar open.
   ============================================ */
.page {
    max-width: var(--page-width);
    margin-inline: auto;
    padding-inline: var(--space-6);
    padding-block: var(--space-8) var(--space-12);
}

/* Nothing may push the viewport sideways. Wide maths and wide tables scroll
   inside their own block instead; for a derivation step that block is its
   maths (`.calc-step .katex-display` below), not the step. */
.calc-results,
.calc-checks,
.calc-table {
    overflow-x: auto;
}

/* A refused table says so in the same register as a refused figure: quiet,
   and unmistakably not a table with nothing in it. */
.calc-table.is-refused p {
    color: var(--ink-faint);
    font-style: italic;
}

/* ============================================
   Calc metadata header

   A drawing title block, not a hero. Sheet number and code standard sit in
   mono between two hairlines; the title and the lede read below them.
   ============================================ */
.calc-meta {
    margin-bottom: var(--space-8);
}

/* The running header (#74): the mark and wordmark with the breadcrumb on the
   left, the session controls on the right, over one hairline. The margin keeps
   the title block's top rule clear of it, so the two do not read as a double
   underline. The mono register of `.calc-meta-row` is set on the two groups and
   not on the header, so the header's own text stays in the prose face.

   Below the width where both groups fit the row wraps rather than truncating
   the title: the controls drop to their own line and `margin-left: auto` keeps
   them at the right edge. */
.site-header {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-2) var(--space-4);
    padding-block: var(--space-2);
    margin-bottom: var(--space-6);
    border-bottom: 1px solid var(--rule);
}

.site-crumbs,
.site-nav,
.site-session {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.site-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
}

/* The name reads as a name, not as a label: prose face, normal case. */
.site-brand {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    letter-spacing: 0;
    text-transform: none;
}

.site-brand img {
    width: 1em;
    height: 1em;
}

.calc-meta-row {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    padding-block: var(--space-2);
    border-top: 1px solid var(--ink);
    border-bottom: 1px solid var(--rule);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* The heading and the PDF report button share one line. The heading's block
   margins live on the row, so the button centres on the glyphs rather than on
   a margin box, and the lede keeps its distance. */
.calc-title-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    margin-block: var(--space-6) var(--space-3);
}

.calc-meta h1 {
    margin-block: 0;
    font-size: var(--text-2xl);
}

.calc-description {
    margin-block: 0;
    color: var(--ink-muted);
}

/* The review record, under the title: small and muted, since it is a fact
   about the sheet rather than an action on it. */
.calc-verified {
    margin-block: var(--space-2);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

/* ============================================
   Sections, tables and derivation steps

   The page should read as one composed document: a section heading is a rule
   with a word on it, the tables are data rather than boxes, and each
   derivation step is a block of its own with room to breathe.
   ============================================ */
/* Not `.calc > section`: the results, checks and derivation sections live
   inside the htmx swap target, so they are grandchildren. */
.calc section {
    margin-top: var(--space-12);
}

.calc h2 {
    margin: 0 0 var(--space-6);
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--rule);
    font-size: var(--text-lg);
    font-weight: 600;
}

/* The Questions section. Closed by default: the answers are prose about the
   calc, and expanded they would push the derivation off the first screen. */
.calc-faq summary {
    padding-block: var(--space-2);
    color: var(--ink);
    font-size: var(--text-base);
    cursor: pointer;
}

.calc-faq details p {
    margin-block: 0 var(--space-4);
    padding-inline-start: var(--space-4);
    color: var(--ink-muted);
}

/* The neighbours and the mail links, after the questions: a plain list, then
   one muted line, so the page ends quietly rather than on a call to action. */
.calc-related ul {
    margin-block: 0 var(--space-4);
    padding-inline-start: var(--space-4);
}

.calc-related p {
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

.data-table {
    width: 100%;
    border-collapse: collapse;
}

.data-table th {
    padding: var(--space-2) var(--space-4) var(--space-2) 0;
    border-bottom: 1px solid var(--ink);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
    text-align: left;
}

.data-table td {
    padding: var(--space-3) var(--space-4) var(--space-3) 0;
    border-bottom: 1px solid var(--rule);
    vertical-align: baseline;
}

.data-table tr:last-child td {
    border-bottom: none;
}

/* The symbol column is notation and stays narrow; the value column is the
   thing that changes, so it is mono with tabular figures and cannot reflow
   the table when a digit count changes. */
.result-symbol {
    width: 8rem;
}

/* The results table keeps the Given block's rhythm: symbol, what it means,
   the number, its unit. The description is muted for the same reason it is
   in a Given row - it labels the number, it is not the number. It is prose,
   so it takes the prose face and restores the prose size the base `table`
   rule shrinks to 0.875em; the value column stays mono and tabular (#105). */
.result-description {
    font-family: var(--font-body);
    font-size: var(--text-base);
    color: var(--ink-muted);
}

/* The number and its unit each hold a column of their own, so a long
   description cannot squeeze the value onto a second line. */
.result-value {
    width: 7rem;
    white-space: nowrap;
}

.result-unit {
    width: 5rem;
}

.magnitude {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Change-flash. The padding is always there, so the tint appearing cannot
   move anything; tabular figures mean the number itself keeps its width when
   its digits change. */
.result-number {
    padding: 2px 4px;
    margin: -2px -4px;
    border-radius: var(--radius-sm);
}

@keyframes calcsheet-change-flash {
    from {
        background-color: var(--flash-wash);
    }

    to {
        background-color: transparent;
    }
}

.result-number.is-changed {
    animation: calcsheet-change-flash var(--flash-duration) ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .result-number.is-changed {
        animation: none;
    }
}

/* Derivation steps read left-aligned, the way they would be written out by
   hand. KaTeX centres display blocks by default, which reads as a slide. */
.calc-step {
    position: relative;
    padding-block: var(--space-6) var(--space-4);
    border-bottom: 1px solid var(--rule);
}

/* The step number sits above its maths rather than beside it. Beside would
   cost horizontal room, and horizontal room is what the derivation is short
   of - see the widths measured at U1.4. 12px mono text needs 4.5:1 on the
   surface, and `--ink-faint` measures 3.66:1, so the anchor, its copied note
   and the range hints read in `--ink-muted` (7.49:1); the faint token stays
   for rules and strokes, where the text rule does not apply (#86). */
.step-anchor {
    position: absolute;
    top: var(--space-2);
    left: 0;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.06em;
    color: var(--ink-muted);
    text-decoration: none;
}

.step-anchor:hover,
.step-anchor:focus-visible {
    color: var(--accent);
}

/* Quiet confirmation, in the same mono, in place of nothing moving at all. */
.step-anchor[data-copied]::after {
    content: " link copied";
    color: var(--ink-muted);
}

:target.calc-step {
    background: var(--accent-wash);
}

.calc-step:last-child {
    border-bottom: none;
}

.calc-step .katex-display {
    margin: 0;
    text-align: left;
    /* KaTeX display blocks are width: 100%, and their own content is a couple
       of sub-pixels wider than that. Sizing them to their content instead
       stops a two-pixel scrollbar appearing on a step that fits. */
    width: max-content;
    max-width: 100%;
    /* The maths is the step's scroll box. KaTeX's ink runs two pixels past
       its own box, so the end padding keeps a step that fits from scrolling,
       or from wearing the fade below. */
    overflow-x: auto;
    padding-inline-end: 0.25em;
}

/* A step whose maths runs past the screen fades at its right edge until it is
   scrolled to the end; calcsheet.js sets and clears `data-overflow`. The mask
   is on the maths, so the clause on the step-number line stays whole. Nothing
   animates, so reduced motion needs no rule of its own. */
.calc-step[data-overflow] .katex-display {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
}

.calc-step .katex-display > .katex {
    text-align: left;
}

/* Which arm of a conditional ran, above the step it produced. Quiet, with
   the test typeset in KaTeX like the steps, and with its own bottom margin
   rather than a collapsing one so it reads as attached to the step below. */
.step-branch {
    margin-block: var(--space-4) 0;
    font-size: var(--text-xs);
    color: var(--ink-faint);
}

/* A note on a step. Mono, because a clause reference is a citation and reads
   as one set that way. */
.step-note {
    margin-block: var(--space-2) 0;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--ink-faint);
}

/* The clause a step comes from sits on the step number's line, flush right.
   That row is otherwise empty, so the citation costs neither the width the
   maths is short of nor a line of its own, and it is read before the maths. */
.step-note-clause {
    position: absolute;
    top: var(--space-2);
    right: 0;
    margin: 0;
}

/* An assumption is a sentence, not a citation, so it is set in the text face
   and italicised - a reader weighs the two differently. */
.step-note-assumption {
    font-family: inherit;
    font-style: italic;
}

/* ============================================
   The Given section

   Typeset rows, not a form. Symbol, what it means, the value, its unit and
   the range it is allowed - the way the givens are written at the top of a
   hand calculation. The only chrome is a hairline under each editable value,
   which is the affordance: this number can be changed.
   ============================================ */
/* The symbol track has a floor and no fixed width. A name is an engineering
   symbol most of the time and fits in 4rem, but a `Choice` is named for what it
   selects - `electrode`, `edge_type`, `concrete_type` - and those are set as
   upright words wider than that. Fixed, they ran into the description beside
   them with no gap at all. `auto` lets the track take its widest symbol, and
   the `1fr` description absorbs the difference, so every row on a page stays
   aligned to the same column. */
.given-row {
    display: grid;
    grid-template-columns: 0.75rem minmax(4rem, auto) minmax(0, 1fr) 7rem 4.5rem 5rem;
    align-items: baseline;
    gap: 0 var(--space-3);
    padding-block: var(--space-2);
    border-bottom: 1px solid var(--rule);
}

.given-row:last-of-type {
    border-bottom: none;
}

.given-description {
    color: var(--ink-muted);
}

.given-value {
    font-family: var(--font-mono);
    font-size: 0.875em;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: var(--space-1) var(--space-1) 2px;
    width: 100%;
    text-align: right;
}

/* The browser's number spinner is form chrome, and this section has none.
   Arrow keys still step the value with `appearance: textfield`. */
.given-value {
    appearance: textfield;
}

.given-value::-webkit-outer-spin-button,
.given-value::-webkit-inner-spin-button {
    appearance: none;
    margin: 0;
}

/* A bounded value is draggable, and says so the way Tangle taught: a dotted
   rule under the number and a horizontal resize cursor. Clicking still puts a
   caret in it - the drag only starts after the pointer has actually moved. */
.given-value[data-low] {
    border-bottom-style: dotted;
    cursor: ew-resize;
}

.given-value:hover {
    border-bottom-color: var(--accent);
}

body.is-scrubbing {
    user-select: none;
    cursor: ew-resize;
}

.given-value:focus-visible {
    outline: none;
    border-bottom: 2px solid var(--accent);
    background: var(--accent-wash);
}

/* A choice has no unit and no range to show, so it takes their columns. */
.given-choice {
    grid-column: 4 / -1;
    text-align: left;
}

/* The dirty mark. Ink, not blue and not a verdict colour: it reports that a
   value was edited, which is neither an interactive element nor a pass. */
.given-dirty {
    align-self: center;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--ink);
    visibility: hidden;
}

.given-row.is-dirty .given-dirty {
    visibility: visible;
}

/* A refused value. Red because it is a failure, and prefixed with a glyph so
   it is still a failure without the colour. */
.calc-status {
    margin: var(--space-4) 0 0;
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--fail);
}

.calc-status::before {
    content: "\2715  ";
}

/* Empty means there is nothing to say, so there is nothing to see. Lets the
   server clear the message by sending an empty element. */
.calc-status:empty {
    display: none;
}

/* The Given row a refusal was about (#108): the fail wash on the row, the
   fail rule under the value where the focus rule sits, and the bound in words
   on a line of its own under the description, in the status line's register.
   Red only here, because it is a failure; the words carry the meaning, so the
   colour is never the only signal. Empty in every other row. */
.given-refused {
    display: none;
}

.given-row.is-refused {
    background: var(--fail-wash);
}

.given-row.is-refused .given-value {
    border-bottom: 2px solid var(--fail);
}

.given-row.is-refused .given-refused {
    display: block;
    grid-column: 3 / -1;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--fail);
}

.given-row.is-refused .given-refused::before {
    content: "\2715  ";
}

/* The project fields for the PDF, asked for in a dialog when the PDF button
   is clicked (#107): a panel on the page's own paper with a hairline, rows in
   the family of `.given-row`, a text field where the number would be. */
dialog.title-block {
    width: min(28rem, calc(100% - 2 * var(--space-4)));
    padding: var(--space-6);
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--ink);
}

/* Translucent ink, written out: `::backdrop` does not inherit the page's
   custom properties in every engine. The value is `--ink` at 45 percent. */
dialog.title-block::backdrop {
    background: rgba(22, 22, 15, 0.45);
}

dialog.title-block h2 {
    margin-top: 0;
}

.title-block-note {
    margin: 0 0 var(--space-2);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

.title-block-field {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    align-items: baseline;
    gap: 0 var(--space-3);
    padding-block: var(--space-2);
    border-bottom: 1px solid var(--rule);
}

.title-block-value {
    font-family: var(--font-mono);
    font-size: 0.875em;
    color: var(--ink);
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: var(--space-1) var(--space-1) 2px;
    width: 100%;
}

.title-block-value:hover {
    border-bottom-color: var(--accent);
}

/* Skip in the copy button's outlined register, Create PDF in the PDF
   button's filled one, so creating stays the primary action. */
.title-block-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    margin: var(--space-6) 0 0;
}

/* The Given heading row: the heading on the left, Reset on the right, both on
   the section rule. The rule moves from the heading to the row, or Reset would
   sit above the line (#107). */
.given-heading {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    margin-bottom: var(--space-6);
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--rule);
}

.calc .given-heading h2 {
    margin: 0;
    padding: 0;
    border-bottom: none;
}

.given-reset {
    margin: 0;
    padding: 0;
    border: none;
    background: none;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--accent);
    cursor: pointer;
}

.given-reset:not(:disabled):hover,
.given-reset:not(:disabled):focus-visible {
    color: var(--accent-strong);
    text-decoration: underline;
}

/* Nothing to reset yet. Muted, not blue: a disabled control is not
   interactive, and blue means interactive. */
.given-reset:disabled {
    color: var(--ink-muted);
    cursor: default;
    text-decoration: none;
}

/* The first-visit hint and the resume offer: one quiet line each under the
   Given heading, shown by calcsheet.js (#88). */
.given-hint,
.given-resume {
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

/* Under the Given heading on a phone, where the results sit below every
   input row. Hidden at desktop in the 960px block below. */
.jump-results {
    display: block;
    text-align: right;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.given-bounds {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    color: var(--ink-muted);
    text-align: right;
}

/* On a phone the row folds: symbol and description on one
   line, value, unit and range on the next. */
@media (max-width: 559px) {
    .given-row {
        grid-template-columns: 0.75rem minmax(3rem, auto) minmax(0, 1fr) 4rem 5rem;
    }

    .given-description {
        grid-column: 3 / -1;
    }

    .given-value {
        grid-column: 3;
    }

    .given-choice {
        grid-column: 3 / -1;
    }
}

/* ============================================
   Hover-linked quantities

   Every occurrence of a quantity - its symbol, the number it was substituted
   for, its row in the Given block - lights up together. The tint is the
   neutral flash wash again, not blue: this is not something you can click.
   ============================================ */
[class*="sym-"] {
    border-radius: var(--radius-sm);
}

.is-linked {
    background: var(--flash-wash);
    box-shadow: 0 0 0 2px var(--flash-wash);
}

.given-row.is-linked {
    box-shadow: none;
}


@media (prefers-reduced-motion: no-preference) {
    [class*="sym-"],
    .given-row {
        transition: background-color var(--transition-fast);
    }
}

/* ============================================
   Checks and the governing limit state

   A verdict is never colour alone: the word PASS or FAIL and a glyph carry it,
   and the colour is the third signal, not the only one. Roughly one engineer
   in twelve cannot read the colour.
   ============================================ */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

th.check-ratio,
th.check-verdict {
    text-align: right;
}

.check-ratio {
    width: 4rem;
    text-align: right;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--ink-muted);
}

/* The author's own name for the check, from the assignment that produced it.
   A block label above the statement rather than its own column: a fifth fixed
   column would squeeze the substituted comparison, which is the long one. */
.check-name {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--ink-muted);
}

.check-math {
    display: block;
}

/* What the band means, above the table that uses it. The threshold is the
   author's, so the page has to state it rather than leave the reader to infer
   one from an amber row. */
.check-caution-note {
    margin: 0 0 var(--space-4);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

.check-bar {
    width: 8rem;
}

.check-bar-track {
    display: block;
    height: 4px;
    background: var(--surface-sunken);
    border: 1px solid var(--rule);
}

.check-bar-fill {
    display: block;
    height: 100%;
    background: var(--pass);
}

.check-verdict {
    width: 6rem;
    text-align: right;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    color: var(--pass);
}

.check-glyph {
    font-size: var(--text-base);
    line-height: 1;
}

.check-row.is-fail .check-bar-fill {
    background: var(--fail);
}

.check-row.is-fail .check-verdict {
    color: var(--fail);
}

.check-row.is-fail .check-statement {
    background: var(--fail-wash);
}

/* A check that passed but sits at or above the band its calc declared. The
   amber is reinforcement only: the row carries a `!` in the verdict column,
   which is DOM text and so survives grey paper and a colour-blind reader. */
.check-row.is-caution .check-bar-fill {
    background: var(--caution);
}

.check-row.is-caution .check-verdict {
    color: var(--caution);
}

.check-row.is-caution .check-statement {
    background: var(--caution-wash);
}

/* The limit state a min or max actually selected. It is the number an
   engineer looks for first, so it is named in words, not just tinted. */
.result-governs {
    margin-left: var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

tr.is-governing td {
    background: var(--surface-sunken);
}

.result-link {
    color: inherit;
    text-decoration: none;
}

.result-link:hover,
.result-link:focus-visible {
    color: var(--accent);
    text-decoration: underline;
}

/* ============================================
   Figures

   Drawn in millimetres and scaled by the viewBox, so the geometry on screen
   is the calc's own numbers. Ink for the object, semantic colour for the one
   dimension the figure is about.
   ============================================ */
/* The Given block. The figure above the inputs at every width - textbook
   order, diagram then given. Not sticky: the figure scrolls away with its
   inputs, because it belongs to them.

   The block spans the page width like every other section (#105). At desktop
   the figure used to sit beside the form on a 2:1 split, the F2.2 ruling,
   which was the floor the bolt drawing stayed legible at in 1280 (277px), not
   a preference for small. The row retires it: at 1440 the drawing gets the
   whole page width instead of a fifth of the viewport (#85). */
.calc-given.has-figure {
    display: flex;
    flex-direction: column;
}

.calc-given.has-figure .calc-figures {
    order: -1;
}

.calc-figure {
    margin: var(--space-8) 0 0;
}

/* The margin is there to separate stacked figures, not to indent the block. */
.calc-figures > .calc-figure:first-child {
    margin-top: 0;
}

/* A drawn figure writes its own class; a chart's root <svg> comes out of
   matplotlib carrying a fixed pt width and none of our classes, so it is
   matched by where it sits instead. Without this a chart keeps its natural
   432pt and overflows the column, taking its own legend and half its x axis
   off the side. */
.figure-svg,
.calc-figure > svg {
    display: block;
    width: 100%;
    max-width: 34rem;
    height: auto;
}

/* At desktop the figure row takes the page width. Two figures sit side by
   side rather than stacking two screens tall, and a portrait drawing is capped
   by the viewport so the inputs still start within about a screen. After the
   34rem cap above, which it lifts, so the order is load-bearing. */
@media (min-width: 960px) {
    .jump-results {
        display: none;
    }

    .calc-figures {
        display: flex;
        flex-wrap: wrap;
        gap: var(--space-8);
    }

    .calc-figure {
        flex: 1 1 24rem;
        margin: 0;
    }

    .figure-svg,
    .calc-figure > svg {
        max-width: none;
        max-height: 50vh;
    }
}

.figure-plate rect {
    fill: var(--surface-sunken);
    stroke: var(--ink-muted);
}

.figure-edge {
    stroke: var(--ink);
}

.figure-bolt {
    fill: var(--surface);
    stroke: var(--ink);
}

.figure-centre {
    stroke: var(--ink-faint);
    stroke-dasharray: 3 2 1 2;
}

.figure-minimum line {
    stroke: var(--ink-faint);
    stroke-dasharray: 2 2;
}

.figure-minimum text {
    fill: var(--ink-faint);
    font-family: var(--font-mono);
}

.figure-dimension text {
    font-family: var(--font-mono);
}

.figure-dimension.is-pass line,
.figure-dimension.is-pass path {
    stroke: var(--pass);
    fill: var(--pass);
}

.figure-dimension.is-pass text {
    fill: var(--pass);
}

/* A failed dimension changes in three ways at once: colour, weight and
   pattern. Any one of them read on its own still says something is wrong,
   which is the point - the colour is never carrying the meaning alone. */
.figure-dimension.is-fail line {
    stroke: var(--fail);
    stroke-width: 1.4%;
    stroke-dasharray: 3 1.5;
}

.figure-dimension.is-fail path {
    fill: var(--fail);
    stroke: var(--fail);
}

.figure-dimension.is-fail text {
    fill: var(--fail);
    font-weight: 700;
}

/* A figure that refuses says so where the drawing would have been, in the
   same register as the refusal message under the inputs: mono, small, quiet.
   The slot is reserved rather than collapsed, so a refusal on recompute does
   not move the page under the reader. */
.calc-figure.is-refused p {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

/* ============================================
   Figure style layer

   One prescriptive drawing style, so every calc's figure reads as the same
   hand. Nothing here is px-fixed against the viewport: strokes and dashes are
   percentages, which SVG resolves against the viewBox, so they scale with the
   drawing and never with the browser window. Text and geometry are the one
   thing a stylesheet cannot size that way, so a figure computes those from its
   own extent (see `Sheet` in figures.py).

   ISO 128 / ISO 129 / ASME Y14.5 in the parts that matter here: two line
   weights at 2:1, filled arrowheads at about 3:1, dimension text horizontal,
   variables italic and units upright (ISO 80000-2).
   ============================================ */

/* The two weights. Everything that is the object is thick; everything that
   talks about the object - dimension, extension, centre, hidden, hatch - is
   thin, at exactly half. A drawing with three weights reads as three hands. */
.fig-object {
    fill: none;
    stroke: var(--ink);
    stroke-width: 1%;
    stroke-linejoin: round;
}

.fig-thin,
.fig-dimension,
.fig-extension,
.fig-centre,
.fig-hidden,
.fig-leader {
    fill: none;
    stroke: var(--ink-muted);
    stroke-width: 0.5%;
}

/* The object may be filled without becoming a second weight. */
.fig-object.is-solid {
    fill: var(--surface-sunken);
}

.fig-object.is-void {
    fill: var(--surface);
}

/* Section hatch. A class rather than a `fill="url(...)"` attribute, because
   `.fig-object`'s own `fill: none` is CSS and would win against it. */
.fig-object.is-hatched {
    fill: url(#fig-hatch-steel);
}

/* Dimension furniture is quieter than the object it measures. */
.fig-extension,
.fig-centre {
    stroke: var(--ink-faint);
}

/* The three dashes, all at the thin weight and all in percent, so the pattern
   is the same fraction of the drawing whatever the drawing is of. The ratios
   are the drafting ones: hidden 4:2, centre 12:3:3:3 (long, gap, short, gap). */
.fig-hidden {
    stroke-dasharray: 2% 1%;
}

.fig-centre {
    stroke-dasharray: 6% 1.5% 1.5% 1.5%;
}

/* Arrowheads. `markerUnits="strokeWidth"` is what keeps them proportional:
   the marker is drawn in multiples of the line's own width, which is itself a
   percentage of the drawing. Nothing to recompute per figure.

   `context-stroke` is what keeps them the right colour. Marker content is
   drawn from the defs, not from the line that references it, so it does not
   inherit the line's colour - a failed dimension would otherwise turn red and
   keep grey arrowheads. The first declaration is the fallback for a browser
   that does not know the keyword. */
.fig-arrow {
    fill: var(--ink-muted);
    fill: context-stroke;
    stroke: none;
}

/* The hatch line is the one width in this layer that is not a percentage. A
   percentage inside a pattern resolves against the pattern's own viewport,
   not the drawing's, which is zero-sized here and rounds the line away. The
   tile is in user units - millimetres, like every viewBox - so the pitch is a
   real 3mm and the line a real 0.4mm on every figure that uses it. */
/* A strut-and-tie node: a filled dot where members meet. Not an object and not
   a dimension, so it takes the annotation colour and no stroke of its own. */
.fig-node {
    fill: var(--ink-muted);
    stroke: none;
}

.fig-hatch {
    stroke: var(--ink-faint);
    stroke-width: 0.4;
}

/* Labels. Symbol in the KaTeX maths italic, value and unit in the KaTeX
   upright, which is the ISO 80000-2 convention and the same pair of faces the
   derivation is typeset in. This is font usage, not KaTeX layout: subscripts
   are plain tspans and nothing here tries to rebuild the typesetter.

   The halo is what makes a label readable over linework. `paint-order` puts
   the canvas-coloured stroke behind the glyph rather than over it. */
.fig-label {
    fill: var(--ink);
    font-family: KaTeX_Main, Times New Roman, serif;
    paint-order: stroke fill;
    stroke: var(--surface);
    stroke-width: 0.6%;
    stroke-linejoin: round;
}

.fig-label .fig-sym {
    font-family: KaTeX_Math, Times New Roman, serif;
    font-style: italic;
}

.fig-label .fig-sub {
    font-size: 0.72em;
}

/* A note is a label that is not a measurement: a minimum, a reference, an
   aside. Quiet enough to read second. */
.fig-note {
    fill: var(--ink-faint);
}

/* The verdict. Colour is never alone: a failed dimension also changes weight,
   changes to a dashed line, and its label says so in words and a glyph. Any
   one of those read on its own still says something is wrong, which is what
   makes the drawing legible in greyscale and to a reader who cannot separate
   red from green. */
.fig-verdict.is-pass .fig-dimension,
.fig-verdict.is-pass .fig-extension {
    stroke: var(--pass);
}

.fig-verdict.is-pass .fig-label {
    fill: var(--pass);
}

.fig-verdict.is-fail .fig-dimension {
    stroke: var(--fail);
    stroke-width: 0.75%;
    stroke-dasharray: 1.5% 0.75%;
}

.fig-verdict.is-fail .fig-extension {
    stroke: var(--fail);
}

.fig-verdict.is-fail .fig-label {
    fill: var(--fail);
    font-weight: 700;
}

/* A hidden line inside a verdict is a section a check is taken at. It keeps
   its dash, so a failed one says so by colour and weight. */
.fig-verdict.is-pass .fig-hidden {
    stroke: var(--pass);
}

.fig-verdict.is-fail .fig-hidden {
    stroke: var(--fail);
    stroke-width: 0.75%;
}

/* The same link, drawn the only way SVG can draw it. An SVG element has no
   background and no box-shadow, so the rules above paint nothing at all on a
   figure - the link would look wired up and do nothing. Accent is the one
   colour reserved for this, and it is transient: the verdict is still carried
   by the dash and by the label's own wording while a dimension is lit. */
.figure-svg .is-linked {
    background: none;
    box-shadow: none;
}

.figure-svg .is-linked .fig-object {
    stroke: var(--accent);
}

.figure-svg .is-linked .fig-dimension,
.figure-svg .is-linked .fig-extension,
.figure-svg .is-linked .fig-hidden,
.figure-svg .is-linked .fig-centre {
    stroke: var(--accent);
    stroke-width: 0.75%;
}

.figure-svg .is-linked .fig-label {
    fill: var(--accent);
}

/* The defs block carries the arrow marker and the hatch pattern. It draws
   nothing itself and must not take part in layout. */
.figure-defs {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
}

.calc-figure figcaption {
    margin-top: var(--space-3);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

/* ============================================
   The index

   The running header carries the brand and the search box on one row; under
   it one sentence, one muted trust line, then the groups (#106).
   ============================================ */

/* The page's `h1`, set as a plain sentence rather than a display heading: the
   brand is the header's, and every `<h2>` below is a category. */
.index-lede {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 400;
    line-height: var(--leading-body);
}

.index-trust {
    margin: var(--space-1) 0 var(--space-8);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

/* The search box, boxed in a light rule with a glass inside it: a bare
   underline read as a caption, not a control (#82). The glass
   is a file beside this one, not a data URI, because the CSP's `default-src
   'self'` blocks `data:` images; as a background it cannot say `currentColor`,
   so it carries --ink-muted as a literal. No `display` here: the UA's `[hidden]` keeps the box off a page
   without scripts (#62). Focus moves the box to the interaction blue. */
.calc-search {
    width: 16rem;
    padding: var(--space-1) var(--space-2) var(--space-1) 1.75rem;
    border: 1px solid var(--rule-strong);
    border-radius: 2px;
    background: transparent url('search.svg') no-repeat 0.5rem 50% / 1rem 1rem;
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--ink);
    appearance: none;
}

.calc-search::placeholder {
    color: var(--ink-muted);
}

.calc-search:focus {
    border-color: var(--accent);
}

/* Keyboard focus thickens the blue box rather than drawing the global ring
   around it: a shadow, not a wider border, so the text does not move (#62). */
.calc-search:focus-visible {
    outline: none;
    box-shadow: 0 0 0 1px var(--accent);
}

/* Said once, where the groups were, when the filter leaves nothing. */
.search-empty {
    margin: 0 0 var(--space-8);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

/* The about paragraph closes the contents now, so it needs the same gap that
   separates one group from the next. */
.calc-about {
    margin-top: var(--space-8);
}

/* One block per category, so the second heading needs air above it. The base
   h2 rule sets no margin, and without this the next group's heading sits on
   the last row of the group before it. */
.contents + .contents {
    margin-top: var(--space-8);
}

/* The heading opens its category with a rule in full ink. 14px mono in muted
   ink is lighter than the serif titles under it, so without a mark the label
   did not read as a section start; the rule is what makes it scannable (#52). Sentence case, not tracked caps: the rule already
   does the separating, and caps here plus caps on every meta line was the one
   habit on the page that read as template output (#53). */
.contents h2 {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--ink-muted);
    border-top: 1px solid var(--ink);
    border-bottom: none;
    padding-top: var(--space-2);
    margin-bottom: var(--space-4);
}

.calc-index {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* One grid over three items: the icon in the first column, the title over
   its description in the second. Two lines, not three: the mono code line
   went because the description already names the code in prose, and its
   room went to a larger icon and title (#82). The row gap is zero because
   the rows carry their own margins; the column gap is the air beside the icon.

   The grid is on the anchor, so every part of the row is clickable. That makes
   it an `a` the global rules would colour and underline whole, hence the two
   resets (#53). */
.calc-entry {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0 var(--space-3);
    align-items: baseline;
    padding-block: var(--space-4);
    border-top: 1px solid var(--rule);
    color: inherit;
    text-decoration: none;
}

/* The mark takes the first column for the whole row; the text stacks in the
   second. Drawn in `currentColor`, so it is the row's ink at every state. */
.calc-entry-icon {
    grid-row: 1 / span 2;
    align-self: start;
    width: 2.5rem;
    height: 2.5rem;
    margin-top: 0.15em;
}

.calc-entry-title,
.calc-entry-description {
    grid-column: 2;
}

.calc-icon {
    width: 1em;
    height: 1em;
    vertical-align: -0.12em;
}

/* The rule sits above each entry, and the first row has the heading's rule
   over it instead. A grid's first row is exact at any column count and its
   last row is not, hence top rather than bottom. On the `li`, not on
   `.calc-entry`: the anchor is its item's only child, so the bare
   `:first-child` would match every row and strip all the rules (#106). */
.calc-index > li:first-child > .calc-entry {
    border-top: none;
}

/* Heading leading, so a title that wraps in a two-column row reads as one. */
.calc-entry-title {
    font-size: var(--text-xl);
    line-height: var(--leading-tight);
    color: var(--ink);
}

/* The row is the link but only the title answers to the pointer: a background
   on the whole row is louder than the page anywhere else. The focus
   ring is the global `:focus-visible`, on the anchor itself. */
.calc-entry:hover .calc-entry-title,
.calc-entry:focus-visible .calc-entry-title {
    color: var(--accent);
    text-decoration: underline;
}

.calc-entry-description {
    margin: var(--space-1) 0 0;
    color: var(--ink-muted);
}

/* From this width each group lays its entries two to a row and the page is
   half as long to scroll; the third column comes at 1200px. Grid rows share a
   height, so the rules over a row line up. */
@media (min-width: 960px) {
    .calc-index {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        column-gap: var(--space-8);
    }

    /* Stretched to its neighbour's height, the grid keeps its two lines at the
       top rather than spreading the spare height between them. */
    .calc-entry {
        height: 100%;
        box-sizing: border-box;
        align-content: start;
    }

    /* The second entry is on the first row too, under the heading's rule. */
    .calc-index > li:nth-child(2) > .calc-entry {
        border-top: none;
    }
}

/* Three to a row at the page width (#106). A column is about 330px, and about
   280px of it is left for the title beside the icon: at --text-xl five of the
   published titles wrapped, so the title steps down one size here only. */
@media (min-width: 1200px) {
    .calc-index {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .calc-index > li:nth-child(3) > .calc-entry {
        border-top: none;
    }

    .calc-entry-title {
        font-size: var(--text-lg);
    }
}

.site-footer {
    margin-top: var(--space-12);
    padding-top: var(--space-4);
    border-top: 1px solid var(--rule);
    color: var(--ink-muted);
}

/* The size goes on the paragraph, not the footer, so the footer's own box
   keeps the page's font and only its text is set small. */
.site-footer p {
    margin: 0;
    font-size: var(--text-sm);
}

/* The footer holds two paragraphs now, the disclaimer and the links. Without
   this the second sits on the last line of the first. */
.site-footer p + p {
    margin-top: var(--space-2);
}

/* The sign-in line, at the header's right (#74). A flex row wraps an item at
   its full width, so on a narrow screen the line moves to its own row whole,
   and breaks inside only if even that row is too narrow for it. The form is
   inline so the button sits on the sentence it finishes. */
.site-session {
    text-align: right;
}

/* The site's own pages, at the header's right ahead of the session line. The
   auto margin is here rather than on `.site-session`, so the two sit together
   instead of `space-between` parking the link in the middle (#84). */
.site-nav {
    margin-left: auto;
}

.site-nav a + a {
    margin-left: var(--space-3);
}

/* On the index the search box sits between the brand and the nav; it takes the
   auto margin so it and About share the right edge, rather than the two
   splitting the free space and parking the box mid-row (#106). */
.site-header .calc-search {
    margin-left: auto;
}

.page-index .site-nav {
    margin-left: 0;
}

.site-session form {
    display: inline;
}

/* A button that reads as the link beside it would. It is a button because
   signing out is a POST; it is blue because it is interactive. */
.link-button {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    color: var(--accent);
    text-decoration: underline;
    cursor: pointer;
}

.link-button:hover {
    color: var(--accent-strong);
}

/* Draft, Pro and New marks. Neutral ink on a rule, because a draft, a tier or
   a recent addition is a state of the document rather than a verdict or
   something to click: green, amber and red mean pass, caution and fail, and
   blue means interactive. Set like the sheet number, mono caps, since it is a
   label on the sheet. */
.draft-badge,
.pro-badge,
.new-badge {
    display: inline-block;
    padding: 0 var(--space-1);
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
    vertical-align: middle;
}

.draft-banner {
    margin: 0 0 var(--space-6);
    padding: var(--space-3) var(--space-4);
    border: 1px dashed var(--rule-strong);
    background: var(--surface-sunken);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

.draft-banner .draft-badge {
    margin-right: var(--space-2);
}

/* A draft's readiness checklist, closed under the banner and in the same
   register: a review aid for the owner, so neutral ink, and never the verdict
   colours even where a row reads `no`. */
.draft-checklist {
    margin: 0 0 var(--space-6);
    padding: var(--space-3) var(--space-4);
    border: 1px dashed var(--rule-strong);
    background: var(--surface-sunken);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

.draft-checklist summary {
    cursor: pointer;
}

.draft-checklist h3,
.draft-checklist h4 {
    margin: var(--space-4) 0 var(--space-2);
    font-size: var(--text-base);
}

.draft-checklist .calc-table {
    margin-block: var(--space-2) var(--space-4);
}

/* The one action on the page that leaves it, in the title block level with
   the heading. Filled blueprint blue: it is interactive, which is the only
   thing blue means here, and it is the loudest shape that rule allows. The
   global focus ring sits 2px outside the fill, so it stays visible. */
.calc-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-2);
    flex-shrink: 0;
    margin: 0;
}

.calc-export,
.title-block-create {
    display: inline-block;
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--accent);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--surface);
    text-decoration: none;
    cursor: pointer;
}

.calc-export:hover,
.calc-export:focus-visible,
.title-block-create:hover,
.title-block-create:focus-visible {
    background: var(--accent-strong);
    border-color: var(--accent-strong);
}

/* Greyed, not disabled: it still goes somewhere, to the page that says what
   Pro is. The badge inside it is the same one the index uses. */
.calc-export.is-locked {
    background: none;
    color: var(--ink-muted);
    border-color: var(--rule-strong);
    border-style: dashed;
}

.calc-export.is-locked:hover,
.calc-export.is-locked:focus-visible {
    color: var(--ink);
    border-color: var(--ink);
}

.calc-export .pro-badge {
    margin-left: var(--space-1);
}

/* Outlined beside the filled PDF button, so the report stays the primary
   action, with the step anchor's quiet copied state. The dialog's Skip button
   shares the outline and keeps the mono caps; the copy button is an icon. */
.calc-copy,
.title-block-skip {
    display: inline-block;
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: none;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent);
    cursor: pointer;
}

/* A square the height of the PDF button, which `.calc-actions` stretches it
   to; the glyph is inline so it can say `currentColor` under `default-src
   'self'` (#111). */
.calc-copy {
    display: inline-flex;
    align-items: center;
    padding: 0 var(--space-2);
}

.calc-copy svg {
    width: 1rem;
    height: 1rem;
}

.calc-copy[hidden] {
    display: none;
}

.calc-copy:hover,
.calc-copy:focus-visible,
.title-block-skip:hover,
.title-block-skip:focus-visible {
    color: var(--accent-strong);
    border-color: var(--accent-strong);
}

.calc-copy[data-copied] {
    color: var(--ink-muted);
    border-color: var(--rule-strong);
}

/* The index's recent row, in the meta row's mono caps register for its label
   and an inline list of links after it. */
.calc-recent {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2) var(--space-4);
    margin-block: var(--space-4) 0;
}

.calc-recent[hidden] {
    display: none;
}

.calc-recent-label {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.calc-recent ul {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-4);
    margin: 0;
    padding: 0;
    list-style: none;
}

@media (max-width: 559px) {
    /* Wrapped under the brand, the box takes the column. It moves to the end
       of the header, so the brand and About keep the first row as on every
       other page instead of About sitting alone under the box (#106). */
    .calc-search {
        width: 100%;
    }

    .site-header .calc-search {
        order: 1;
        margin-left: 0;
    }

    .page-index .site-nav {
        margin-left: auto;
    }

    /* At phone width the button takes its own line under the heading, even
       when the heading would fit beside it, right-aligned like Reset on the
       Given heading, so it never squeezes the title. */
    .calc-title-row {
        flex-wrap: wrap;
    }

    .calc-actions {
        flex-basis: 100%;
        text-align: right;
    }
}

/* ============================================
   Sidenotes

   Mechanics cribbed from Tufte CSS: a numbered marker, a hidden checkbox and
   the note. The marker is a toggle at every width, closed by default, and the
   note opens inline under its paragraph, never floated beside the column.
   Not floated (#66): an open floated note pushed the Given table and its
   figure down by the note's full height.
   ============================================ */
body {
    counter-reset: sidenote-counter;
}

.sidenote {
    display: none;
    font-size: var(--text-sm);
    line-height: 1.45;
    color: var(--ink-muted);
    text-indent: 0;
    vertical-align: baseline;
}

.sidenote-number {
    counter-increment: sidenote-counter;
}

.sidenote-number::after,
.sidenote::before {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 0;
    vertical-align: super;
}

.sidenote-number::after {
    content: counter(sidenote-counter);
    color: var(--accent);
    cursor: pointer;
}

.sidenote::before {
    content: counter(sidenote-counter) " ";
    color: var(--ink-faint);
}

label.sidenote-number {
    display: inline;
}

/* The numbered marker is itself the toggle, so there is no second label to
   show or hide. The checkbox is clipped rather than `display: none`, which
   would take it out of the tab order and leave the note openable only with a
   pointer. */
input.margin-toggle {
    display: inline;
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
}

label.sidenote-number:has(+ input.margin-toggle:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.margin-toggle:checked + .sidenote {
    display: block;
    float: none;
    clear: both;
    width: 100%;
    margin: var(--space-3) 0;
    padding-left: var(--space-4);
    border-left: 2px solid var(--rule);
}

@media (max-width: 480px) {
    .page {
        padding-inline: var(--space-4);
    }
}

/* ============================================
   Print styles

   A printed or saved-as-PDF copy carries the title block, the Given block
   with its figure, the results, the checks and any tables, then the footer.
   Only the derivation stays off paper, along with the FAQ, the header and
   the buttons: hiding the rest protected nothing a screenshot does not
   already take (docs/decisions/paid-exports.md). `.print-only` carries the
   notice that says the derivation is on the PDF report. Selectors are
   semantic classes owned by calcsheet templates,
   not framework internals, so no specificity overrides are needed.
   ============================================ */
.print-only {
    display: none;
}

@media print {
    /* Boxed in caution amber so the missing derivation is not read past as
       body text. Browsers drop backgrounds on paper by default; the border
       carries the notice alone if the wash is dropped anyway. */
    .print-only {
        display: block;
        margin: var(--space-6) 0;
        padding: var(--space-3) var(--space-4);
        border: 2px solid var(--caution);
        border-radius: var(--radius-md);
        background: var(--caution-wash);
        print-color-adjust: exact;
        -webkit-print-color-adjust: exact;
        break-inside: avoid;
    }

    /* The derivation: on screen and on the PDF report, not on paper. */
    .calc-derivation {
        display: none;
    }

    :root {
        --surface: white;
        --surface-sunken: white;
    }

    /* No body inset: the paper margin is the browser's `@page` to set, and a
       40px inset here only narrows the page box. */
    body {
        font-size: 11pt;
    }

    /* Hide non-essential elements. The footer is not one of them: the
       disclaimer prints with the page regardless. */
    .no-print,
    .site-header,
    .calc-form button {
        display: none;
    }

    /* Paper sets its own width: the screen's page width is not protecting a
       reading line here, it would only leave part of the sheet blank beside
       the Given block. */
    .page,
    .page > header,
    .page > main,
    .page > footer {
        max-width: none;
    }

    /* The ratio `.katex` asks for at line 183, applied where it survives. The
       vendor sheet is loaded after this one and its `.katex` font shorthand
       wins at equal specificity, so the selector has to outrank it; `main`
       does. The Given block's symbols and units still typeset with KaTeX even
       though the derivation does not print. */
    main .katex {
        font-size: 1.09em;
    }

    /* Page breaks */
    h1, h2 {
        page-break-after: avoid;
    }
}

/* The policy pages: prose at the page width, sectioned like a calc sheet. */
.policy h2 {
    margin-top: var(--space-8);
}

.policy-meta {
    color: var(--ink-muted);
    font-size: var(--text-sm);
}

/* The 402 page's case: the project fields the reader filled, label in the
   mono register of `.calc-meta-row`, value beside it (#114). */
.case-block {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--space-1) var(--space-4);
    margin: var(--space-4) 0;
    padding-block: var(--space-2);
    border-top: 1px solid var(--ink);
    border-bottom: 1px solid var(--rule);
}

.case-block dt {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
    align-self: baseline;
}

.case-block dd {
    margin: 0;
}

/* The checkout link and its price on one row. `.calc-actions` right-aligns
   for the calc page's title row; in prose it starts at the left margin. */
.policy .calc-actions {
    justify-content: flex-start;
    align-items: center;
    margin: var(--space-4) 0;
}

/* The subscribe page: its own article rather than a policy, sectioned the
   same way, with the Free and Pro columns centred under their heads. */
.page-subscribe .subscribe h2 {
    margin-top: var(--space-8);
}

.plan-table th[scope="row"] {
    font-size: inherit;
    font-weight: normal;
    letter-spacing: normal;
    text-transform: none;
    color: var(--ink);
    border-bottom: 1px solid var(--rule);
}

.plan-table th:nth-child(n+2),
.plan-table td {
    text-align: center;
}

.plan-table tr:last-child th {
    border-bottom: none;
}

/* A yes or no as a tick or a cross, from `_mark.html`. The glyph carries it;
   the colour only helps. */
.mark {
    font-size: 1.25em;
    font-weight: bold;
    line-height: 1;
}

.mark-yes {
    color: var(--pass);
}

.mark-no {
    color: var(--fail);
}

/* A validation page's date: one line, so a column never breaks it at a
   hyphen (#96). The Against cell beside it is a sentence and wraps. */
.validation-record {
    white-space: nowrap;
}

/* The section properties page (/sections): one form, then the picked section,
   then the list. Labels in the mono register of `.calc-meta-row`; controls
   underlined like the Given values, so the page reads as the same sheet. */
.sections-form {
    display: grid;
    gap: var(--space-3);
    margin: var(--space-6) 0;
}

.sections-filter {
    display: grid;
    gap: var(--space-3);
    margin: 0;
    padding: var(--space-3) 0 0;
    border: none;
    border-top: 1px solid var(--rule);
}

.sections-filter legend {
    padding: 0;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.sections-note {
    text-transform: none;
    letter-spacing: 0;
}

.sections-field {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2) var(--space-3);
}

.sections-field label {
    flex: 0 0 8rem;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.sections-field select,
.sections-criterion input {
    min-width: 0;
    max-width: 100%;
    padding: var(--space-1) 0;
    border: none;
    border-bottom: 1px solid var(--rule-strong);
    border-radius: 0;
    background: transparent;
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--ink);
}

.sections-field select:hover,
.sections-criterion input:hover {
    border-bottom-color: var(--accent);
}

.sections-field select:focus-visible,
.sections-criterion input:focus-visible {
    outline: none;
    border-bottom: 2px solid var(--accent);
}

/* A select is as wide as its longest option unless told otherwise, and the
   property titles are long enough to push a phone sideways. */
.sections-field select:first-of-type {
    flex: 1 1 12rem;
    width: 0;
}

@media (max-width: 560px) {
    .sections-field label {
        flex-basis: 100%;
    }
}

.sections-criterion input {
    width: 7rem;
    font-variant-numeric: tabular-nums;
}

.sections-form .sections-search {
    width: 20rem;
}

.section-detail {
    margin-bottom: var(--space-8);
    scroll-margin-top: var(--space-4);
}

.section-imperial {
    margin-left: var(--space-2);
    font-size: var(--text-base);
    font-weight: normal;
    color: var(--ink-muted);
}

/* The drawing beside the property table from 960px; stacked below that. */
.section-detail-body {
    display: grid;
    gap: var(--space-6);
}

@media (min-width: 960px) {
    .section-detail-body {
        grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
        align-items: start;
    }
}

.section-detail .data-table,
.sections-table {
    font-size: var(--text-sm);
}

.section-detail .data-table td,
.sections-table td,
.sections-table th[scope="row"] {
    padding-block: var(--space-1);
}

.section-code {
    font-family: var(--font-mono);
}

/* Descriptions are prose; the base table rule would set them in mono. */
.section-properties td:nth-child(2) {
    font-family: var(--font-body);
}

.section-properties td:last-child {
    white-space: nowrap;
}

.sections-page .num {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* A wide family scrolls sideways inside its own box, and the designation
   stays put so a row never loses its name. */
.sections-scroll {
    overflow-x: auto;
}

.sections-table th[scope="row"] {
    border-bottom: 1px solid var(--rule);
    letter-spacing: 0;
    text-transform: none;
    position: sticky;
    left: 0;
    background: var(--surface);
    font-family: var(--font-mono);
    font-weight: normal;
    text-align: left;
    white-space: nowrap;
}

.sections-table th[scope="col"] {
    white-space: nowrap;
    text-transform: none;
    letter-spacing: 0;
}

.sections-table tr.is-picked th,
.sections-table tr.is-picked td {
    background: var(--accent-wash);
}

.sections-empty {
    color: var(--ink-muted);
}
