/*
 * Instrumentize design system
 * Single stylesheet, no build step. Tokens first, then primitives, then components.
 */

/* ---------------------------------------------------------------- tokens */

:root {
    color-scheme: light dark;

    /*
     * Neutral ramp, and neutral is the point.
     *
     * It used to be blue-grey, which made every page faintly cold and — now
     * that nine hues do the colouring — tinted them all towards one of their
     * own. A ramp with the faintest warm cast reads as paper and ink rather
     * than as a screen, and leaves a family hue to be the only colour.
     *
     * `--c-text-faint` was #858c99, which is 3.4:1 on white. It labels the
     * count on a category card, the line above a figure and every key of the
     * provenance block — small text, which is held to 4.5.
     */
    --c-bg: #ffffff;
    --c-bg-subtle: #f7f6f4;
    --c-bg-sunken: #efedea;
    --c-surface: #ffffff;
    --c-border: #e0ded9;
    --c-border-strong: #bcb9b2;
    --c-text: #1a1917;
    --c-text-muted: #5c5950;
    --c-text-faint: #6f6c64;

    /*
     * The brand's own colour is not a colour.
     *
     * Nine families already carry colour, and a tenth hue for the brand would
     * compete with all of them. So the primary button and the focus ring are
     * ink, which leaves the family tint as the only colour on a page.
     *
     * Deliberately not `--c-accent`: that token is doing functional work well
     * beyond the brand — the sides of a triangle, the faces of a solid, the
     * marked span in the regex tester, the active tab and `accent-color` on
     * every checkbox. Ink there would flatten a diagram into a silhouette.
     */
    --c-ink: #14161c;
    --c-ink-hover: #000000;
    --c-ink-contrast: #ffffff;

    /* Accent — derived from the family hue, below. */
    --c-accent-contrast: #ffffff;

    /* Feedback */
    --c-ok: #157347;
    --c-ok-subtle: #e5f4ec;
    --c-warn: #8a5a00;
    --c-warn-subtle: #fdf3e0;
    --c-error: #b3261e;
    --c-error-subtle: #fbeae9;

    --c-focus: #14161c;

    /* Typography */
    --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

    --fs-xs: 0.78rem;
    --fs-sm: 0.875rem;
    --fs-base: 1rem;
    --fs-md: 1.125rem;
    --fs-lg: 1.375rem;
    --fs-xl: 1.75rem;
    --fs-2xl: 2.25rem;

    --lh-tight: 1.2;
    --lh-normal: 1.55;

    /* Spacing (4px base) */
    --sp-1: 0.25rem;
    --sp-2: 0.5rem;
    --sp-3: 0.75rem;
    --sp-4: 1rem;
    --sp-5: 1.5rem;
    --sp-6: 2rem;
    --sp-7: 3rem;
    --sp-8: 4rem;

    /* Sharper: an instrument has corners, not pillows. */
    --radius-sm: 2px;
    --radius: 3px;
    --radius-lg: 4px;

    /*
     * Flat. `--shadow` stays real because the things that use it genuinely
     * float — the suggestion list over the page, the drawer over everything.
     */
    --shadow-sm: none;
    --shadow: 0 2px 8px rgba(16, 20, 28, 0.08);

    --container: 1120px;
    --container-narrow: 760px;

    /* Height of the sticky bar; anchors have to clear it. */
    --header-h: 56px;

    /*
     * Family tint.
     *
     * Nine categories shared one blue, so nothing on a page said which part of
     * the site it belonged to and the whole thing read as a default template.
     * A category carries one number — a hue — and the two colours derived from
     * it are written once, here and in the dark block, rather than as eighteen
     * hand-picked values that would have to be kept in step.
     *
     * The default is the hue of `--c-accent`, so a page outside any category
     * looks exactly as it did.
     *
     * It is decoration and nothing else: it tints an icon, a rule and a border.
     * No text is ever set in it and no meaning ever depends on it, which is
     * what keeps the contrast question to the 3:1 asked of a non-text element.
     */
    --cat-h: 218;
}

/*
 * Japanese: the system's own Japanese faces ahead of the generic stack, so a
 * Windows page does not fall back to a Chinese-shaped glyph for a kanji, and
 * the strict line-breaking rules a Japanese reader expects — no line opening
 * on 、 or 。, nor on a small kana. Nothing is downloaded; every face here
 * ships with the system that has it.
 */
:root:lang(ja) {
    --font-sans: system-ui, -apple-system, "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic UI",
        "Yu Gothic", Meiryo, "Noto Sans JP", sans-serif;
}

:lang(ja) {
    line-break: strict;
}

[data-category="developer"] {
    --cat-h: 252;
}

[data-category="music"] {
    --cat-h: 292;
}

[data-category="calculators"] {
    --cat-h: 210;
}

[data-category="converters"] {
    --cat-h: 194;
}

[data-category="real-estate"] {
    --cat-h: 24;
}

[data-category="mathematics"] {
    --cat-h: 172;
}

[data-category="energy"] {
    --cat-h: 130;
}

[data-category="sport"] {
    --cat-h: 340;
}

[data-category="time"] {
    --cat-h: 45;
}

@media (min-width: 64rem) {
    :root {
        --header-h: 64px;
    }
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --c-bg: #0d0d0e;
        --c-bg-subtle: #151517;
        --c-bg-sunken: #080809;
        --c-surface: #212125;
        --c-border: #33333a;
        --c-border-strong: #4b4b55;
        --c-text: #ecebe8;
        --c-text-muted: #a8a6a1;
        --c-text-faint: #93918b;

        --c-accent-contrast: #0d0d0e;

        --c-ok: #5cc98d;
        --c-ok-subtle: #14261d;
        --c-warn: #e0aa53;
        --c-warn-subtle: #2a2013;
        --c-error: #f28b82;
        --c-error-subtle: #2c1917;

        --c-ink: #e9ebf0;
        --c-ink-hover: #ffffff;
        --c-ink-contrast: #0d0d0e;

        --c-focus: #e9ebf0;

        --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
        --shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
    }
}

:root[data-theme="dark"] {
    --c-bg: #0d0d0e;
    --c-bg-subtle: #151517;
    --c-bg-sunken: #080809;
    --c-surface: #212125;
    --c-border: #33333a;
    --c-border-strong: #4b4b55;
    --c-text: #ecebe8;
    --c-text-muted: #a8a6a1;
    --c-text-faint: #93918b;

    --c-accent-contrast: #0d0d0e;

    --c-ok: #5cc98d;
    --c-ok-subtle: #14261d;
    --c-warn: #e0aa53;
    --c-warn-subtle: #2a2013;
    --c-error: #f28b82;
    --c-error-subtle: #2c1917;

    --c-ink: #e9ebf0;
    --c-ink-hover: #ffffff;
    --c-ink-contrast: #0d0d0e;

    --c-focus: #e9ebf0;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}

/*
 * The tint, derived where the hue is set.
 *
 * A custom property that substitutes another resolves it on the element it is
 * declared on, so deriving these on `:root` alone would freeze them at the
 * default for anything further down that sets its own `--cat-h`. The selector
 * names both, and so does each dark variant.
 *
 * `--c-accent` is the same hue at a darker step, and it is a separate token
 * because it is held to a stricter rule: `--c-cat` only ever tints a border, a
 * pill or an underline, while the accent is set as *text* — the primary figure
 * of a worked calculation, a selected radio card, the active tab — and on the
 * subtle tint of its own hue. Every hue was checked against that pairing;
 * green is the tightest at about 5.3:1, which clears the 4.5 asked of text.
 *
 * `--c-accent-contrast` stays a literal in the blocks above: it is the colour
 * of text laid *on* the accent, and that is white on a light page and near
 * black on a dark one whatever the hue happens to be.
 */
:root,
[data-category] {
    --c-cat: hsl(var(--cat-h) 58% 38%);
    --c-cat-subtle: hsl(var(--cat-h) 70% 95%);

    --c-accent: hsl(var(--cat-h) 62% 28%);
    --c-accent-hover: hsl(var(--cat-h) 68% 20%);
    --c-accent-subtle: hsl(var(--cat-h) 70% 95%);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]),
    :root:not([data-theme="light"]) [data-category] {
        --c-cat: hsl(var(--cat-h) 70% 72%);
        --c-cat-subtle: hsl(var(--cat-h) 32% 17%);

        --c-accent: hsl(var(--cat-h) 70% 72%);
        --c-accent-hover: hsl(var(--cat-h) 78% 82%);
        --c-accent-subtle: hsl(var(--cat-h) 32% 17%);
    }
}

:root[data-theme="dark"],
:root[data-theme="dark"] [data-category] {
    --c-cat: hsl(var(--cat-h) 70% 72%);
    --c-cat-subtle: hsl(var(--cat-h) 32% 17%);

    --c-accent: hsl(var(--cat-h) 70% 72%);
    --c-accent-hover: hsl(var(--cat-h) 78% 82%);
    --c-accent-subtle: hsl(var(--cat-h) 32% 17%);
}

/* ------------------------------------------------------------- primitives */

*,
*::before,
*::after {
    box-sizing: border-box;
}

/*
 * The `hidden` attribute has to win.
 *
 * The user-agent stylesheet sets `[hidden] { display: none }`, but every rule in
 * this file that gives an element a display — `.field { display: block }`,
 * `.field__hint` and a dozen others — has the same specificity and comes later,
 * so it wins and the element stays on the page. Anything hidden by markup or by a module then
 * reappears, which is how the unused measurement fields on the area calculator
 * came out visible but disabled instead of gone.
 *
 * `!important` rather than a more specific selector, because this has to beat
 * whatever any later class happens to say.
 */
[hidden] {
    display: none !important;
}

html {
    -webkit-text-size-adjust: 100%;

    /*
     * The bar is sticky, so an anchor that scrolls to the top of the viewport
     * lands underneath it. Every in-page jump is affected: the skip link, the
     * menu link when scripting is unavailable, a shared link to a section.
     */
    scroll-padding-top: calc(var(--header-h) + var(--sp-3));
}

/*
 * The bar's own controls are always inside the strip that padding declares
 * covered, so the browser took the search field for hidden: focusing it, and
 * every keystroke after, scrolled the page up to reveal a field that never
 * moves — until the page was back at the top. A `scroll-margin` on the field
 * does not help, because typing reveals the caret inside it, not the field.
 */
html:has(.site-header :focus) {
    scroll-padding-top: 0;
}

/*
 * The drawer is a modal `<dialog>`, which makes the page behind it inert but
 * does not, in every browser, stop it scrolling.
 */
html.has-drawer {
    overflow: hidden;
}

body {
    margin: 0;
    background: var(--c-bg);
    color: var(--c-text);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    line-height: var(--lh-normal);
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

/*
 * The scale had an h1 of 2.25rem over an h2 of 1.75 — a ratio of 1.28, which is
 * close enough that a title and the section heading under it read as the same
 * thing, and every level shared `font-weight: 650`. A tool page runs h1, then
 * `How it works`, `Examples`, `Frequently asked questions`, `Sources`: without
 * a step between the two sizes there is no rhythm to scroll through.
 *
 * `letter-spacing` was -0.011em, too small to see. Tightening is worth doing or
 * not doing; it is not worth doing invisibly.
 */
h1,
h2,
h3,
h4 {
    line-height: var(--lh-tight);
    margin: 0 0 var(--sp-3);
    font-weight: 650;
    letter-spacing: -0.02em;
}

h1 {
    font-size: clamp(1.9rem, 1.45rem + 1.8vw, 2.6rem);
    line-height: 1.12;
    font-weight: 700;
    letter-spacing: -0.03em;
}

h2 {
    font-size: 1.5rem;
}

h3 {
    font-size: 1.25rem;
}

h4 {
    font-size: var(--fs-md);
}

p {
    margin: 0 0 var(--sp-4);
}

/*
 * A link is the text it is, underlined in the colour of the family it sits in.
 *
 * Colour alone never carried it anyway — the underline is what satisfies the
 * requirement that a link in prose be told apart by more than hue — so taking
 * the blue away costs nothing and stops every page from being the same blue.
 */
a {
    color: var(--c-text);
    text-decoration-color: var(--c-cat);
    text-decoration-thickness: 1.5px;
    text-underline-offset: 3px;
}

a:hover {
    color: var(--c-text);
    text-decoration-thickness: 3px;
}

code,
kbd,
samp,
pre {
    font-family: var(--font-mono);
    font-size: 0.92em;
}

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--c-focus);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: var(--sp-2);
    top: -100px;
    z-index: 100;
    background: var(--c-surface);
    color: var(--c-text);
    padding: var(--sp-2) var(--sp-4);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    transition: top 0.15s ease;
}

.skip-link:focus {
    top: var(--sp-2);
}

/* -------------------------------------------------------------- wordmark */

/*
 * The chrome is set in the same fixed pitch as the figures it frames.
 *
 * Not the prose, though, and that distinction is the whole of it: a ninety-
 * character headline in monospace runs to five lines and reads like terminal
 * output. Mono is for what is machine-like — the mark, the navigation, the
 * section headings, the labels above a figure, the keys of the receipt — and
 * the sans is for what is read rather than scanned.
 */
.site-logo,
h2,
h3,
.nav-toggle__label,
.result-panel__label,
.provenance__title,
.section-title h2 {
    font-family: var(--font-mono);
}

h2,
h3 {
    letter-spacing: -0.03em;
}

.site-logo {
    font-weight: 700;
    letter-spacing: -0.04em;
}

/*
 * The brackets are decoration, and the empty alternative text says so to
 * anything reading the page aloud — a browser that does not support the syntax
 * ignores it and reads the bracket, which is a small price for the ones that do.
 */
.site-logo::before,
.site-logo::after {
    color: var(--c-cat);
    font-weight: 400;
}

.site-logo::before {
    content: "[";
    margin-inline-end: 0.1em;
}

.site-logo::after {
    content: "]";
    margin-inline-start: 0.1em;
}

/*
 * Said twice on purpose. A declaration whose value a browser cannot parse is
 * dropped entirely, so writing only the alternative-text form left the wordmark
 * with no brackets at all in Firefox before 133 rather than with brackets that
 * are read aloud. Stated plainly first, silenced where that is understood.
 */
@supports (content: "x" / "") {
    .site-logo::before {
        content: "[" / "";
    }

    .site-logo::after {
        content: "]" / "";
    }
}

/* ---------------------------------------------------------------- layout */

.container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--sp-4);
}

.container--narrow {
    max-width: var(--container-narrow);
}

.site-main {
    flex: 1 0 auto;
    padding-block: var(--sp-6) var(--sp-8);
}

.stack > * + * {
    margin-top: var(--sp-4);
}

.stack-lg > * + * {
    margin-top: var(--sp-6);
}

.cluster {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
}

.grid {
    display: grid;
    gap: var(--sp-4);
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}

/* ---------------------------------------------------------------- header */

.site-header {
    border-bottom: 1px solid var(--c-border);
    background: var(--c-surface);
    position: sticky;
    top: 0;
    z-index: 20;
}

/*
 * `nowrap` is the whole point. The bar used to be a wrapping flex row, so its
 * height was whatever the nine category names happened to need in the language
 * being served — three or four rows on a phone, inside a sticky header, which
 * took about half the viewport away on every page. Nothing in here may wrap;
 * what does not fit is hidden until the width where it does.
 */
.site-header__inner {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 56px;
    flex-wrap: nowrap;
}

.site-logo {
    font-weight: 700;
    font-size: var(--fs-md);
    color: var(--c-text);
    text-decoration: none;
    letter-spacing: -0.02em;
    white-space: nowrap;
}

/* The brand is the one thing in the bar that can give up width: if it does
   not fit, it is the name that is cut, not a control pushed off the screen. */
.site-header .site-logo {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nav-toggle {
    flex: 0 0 auto;
    gap: var(--sp-2);
    padding-inline: var(--sp-2);
    min-width: 44px;
}

/*
 * Hidden from the eye, not from the accessibility tree: the button has to keep
 * the same name at every width. See the note in `site_header.php`.
 */
.nav-toggle__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.search-toggle {
    flex: 0 0 auto;
    margin-inline-start: auto;
}

/* The search lives in the drawer until the bar is wide enough to carry it.
   The two settings are one control each and fit at every width. */
.site-header .search {
    display: none;
}

@media (min-width: 48rem) {
    .site-header .search {
        display: block;
        flex: 1 1 auto;
        min-width: 0;
        max-width: 26rem;
        margin-inline-start: auto;
    }

    .site-header .search-toggle {
        display: none;
    }
}

@media (min-width: 64rem) {
    .site-header__inner {
        min-height: 64px;
        gap: var(--sp-4);
    }

    .nav-toggle {
        padding-inline: var(--sp-3);
        font-size: var(--fs-sm);
    }

    .nav-toggle__label {
        position: static;
        width: auto;
        height: auto;
        margin: 0;
        overflow: visible;
        clip: auto;
    }
}

/* --------------------------------------------------------------- pickers */

/*
 * The two settings in the bar — language and theme — are the same shape and
 * share this. One control each, not one link per choice: the bar used to
 * carry a link per language, which made its width a function of how many
 * there are and how long their names happen to be, and a `Theme` button
 * whose word was rewritten after load.
 *
 * A `<details>`, because it is the only disclosure a browser opens on its
 * own. The panel is taken out of flow, or opening it would push the sticky
 * bar taller and move the page under the pointer.
 */
.picker {
    position: relative;
    flex: 0 0 auto;
}

.picker__summary {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    cursor: pointer;
    /* Both spellings: the triangle is a marker in one engine and a
       pseudo-element in the other, and neither is wanted here. */
    list-style: none;
}

.picker__summary::-webkit-details-marker {
    display: none;
}

.picker__value {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.04em;
}

.picker__chevron {
    transition: transform 0.12s ease-out;
}

.picker[open] .picker__chevron {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
    .picker__chevron {
        transition: none;
    }
}

.picker__panel {
    position: absolute;
    inset-inline-end: 0;
    top: calc(100% + var(--sp-1));
    z-index: 30;
    min-width: 11rem;
    margin: 0;
    padding: var(--sp-1);
    list-style: none;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

/*
 * Ten languages are taller than a phone held sideways, and the bar is sticky,
 * so a list that runs past the bottom of the screen can never be scrolled to.
 */
.site-header .picker__panel {
    max-height: calc(100vh - var(--header-h) - var(--sp-4));
    max-height: calc(100dvh - var(--header-h) - var(--sp-4));
    overflow-y: auto;
}

/*
 * On a phone the bar is the menu, the brand, the search and this, and with
 * the code and the chevron this came to about 360px — the language switch
 * was the control pushed off the edge. Below that width it is the globe
 * alone, square like the search beside it. The code stays in the accessible
 * name through `visually-hidden`, as the label of `nav-toggle` does.
 */
@media (max-width: 26rem) {
    .site-header .picker__summary {
        width: 44px;
        min-width: 44px;
        padding: 0;
    }

    .site-header .picker__value,
    .site-header .picker__chevron {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
}

/*
 * Which icon the control wears is the mode that is set, not the theme that
 * came out of it: `system` is the answer to what the control does, and what
 * the page currently looks like is on the screen already. All three are in
 * the markup and two are hidden, so switching is an attribute rather than a
 * module writing SVG.
 */
.picker__icon {
    display: none;
}

[data-theme-mode="system"] .picker__icon[data-icon="system"],
[data-theme-mode="light"] .picker__icon[data-icon="light"],
[data-theme-mode="dark"] .picker__icon[data-icon="dark"] {
    display: block;
}

/*
 * Near the bottom of the page a panel that drops downwards drops off it, so
 * this one rises instead, and aligns with the start of its column rather
 * than with the end of a bar it is not in.
 */
.picker--up .picker__panel {
    top: auto;
    bottom: calc(100% + var(--sp-1));
    inset-inline-start: 0;
    inset-inline-end: auto;
}

/* --------------------------------------------- the list, panel and drawer */

.picker-item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    width: 100%;
    padding: var(--sp-2);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--c-text);
    font: inherit;
    font-size: var(--fs-sm);
    text-align: start;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}

.picker-item:hover {
    background: var(--c-bg-subtle);
}

/*
 * The tick keeps its width when it is not drawn, so the names line up
 * whichever choice is the current one. The mark itself comes from the
 * current state rather than from the markup, so nothing has to write it.
 */
.picker-item__tick {
    flex: 0 0 auto;
    width: 1em;
    text-align: center;
    color: var(--c-text-muted);
}

.picker-item[aria-current] {
    font-weight: 600;
}

.picker-item[aria-current] .picker-item__tick::before {
    /* A CSS escape rather than the character itself, so the mark does not
       depend on the charset this file happens to be served with. */
    content: "\2713";
}

/*
 * In the footer the control heads its column, so it starts where the links
 * beside it start instead of being inset by a button's own padding.
 */
.site-footer .picker__summary {
    padding-inline: 0;
}

.site-footer .picker__summary:hover {
    background: transparent;
    color: var(--c-accent);
}

/* ---------------------------------------------------------------- drawer */

/*
 * The navigation, at every width. A native `<dialog>`, so the top layer, the
 * backdrop, Escape, the focus trap, the inertness of the page behind it and
 * the restoring of focus on close are the browser's job rather than ours.
 *
 * It is never given a hidden resting state. A dialog that the browser has
 * opened but a stylesheet is holding at `opacity: 0` is a modal trap — the
 * page behind it is inert and there is nothing on screen to close. So the
 * panel is visible the moment it is open, and the entrance is an animation on
 * `[open]` rather than a transition towards a class that JavaScript has to
 * remember to add.
 */
.drawer {
    /*
     * Always leave a strip of backdrop. At `min(22rem, 100%)` a 390pt phone got
     * 38 pixels of it, which is not a target anybody can hit, so the only way
     * out was the close button.
     */
    width: min(22rem, calc(100% - 3.5rem));
    max-width: 100%;
    height: 100%;
    height: 100dvh;
    max-height: 100%;
    margin-block: 0;
    margin-inline: 0 auto;
    padding: 0;
    border: 0;
    border-inline-end: 1px solid var(--c-border);
    background: var(--c-surface);
    color: var(--c-text);
    overflow: hidden;
}

.drawer::backdrop {
    background: rgba(8, 8, 9, 0.62);
}

.drawer__inner {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    height: 100%;
    padding: var(--sp-4);
}

.drawer__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    min-height: 44px;
}

.drawer .search {
    flex: 0 0 auto;
    max-width: none;
}

.drawer__nav {
    flex: 1 1 auto;
    overflow-y: auto;
    /* Bleed the rows to the panel edge while the text stays on the same
       vertical as everything above it. */
    margin-inline: calc(var(--sp-2) * -1);
    padding-inline: var(--sp-2);
}

.drawer__link {
    display: grid;
    grid-template-columns: 2.25rem 1fr;
    column-gap: var(--sp-3);
    align-items: center;
    min-height: 56px;
    padding: var(--sp-2);
    border-radius: var(--radius);
    color: var(--c-text);
    text-decoration: none;
}

.drawer__link:hover {
    background: var(--c-bg-subtle);
}

.drawer__link[aria-current="page"] {
    background: var(--c-cat-subtle);
}

.drawer__icon {
    grid-row: span 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--radius);
    background: var(--c-cat-subtle);
    color: var(--c-cat);
    font-size: var(--fs-sm);
    line-height: 1;
}

.drawer__label {
    font-weight: 600;
}

.drawer__count {
    font-size: var(--fs-xs);
    color: var(--c-text-faint);
}

@media (prefers-reduced-motion: no-preference) {
    .drawer[open] {
        animation: drawer-in 0.14s ease-out;
    }
}

@keyframes drawer-in {
    from {
        opacity: 0;
        transform: translateY(0.5rem);
    }
}

/* ---------------------------------------------------------------- footer */

.site-footer {
    border-top: 1px solid var(--c-border);
    background: var(--c-bg-subtle);
    padding-block: var(--sp-6);
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
    margin-top: auto;
}

.site-footer h2 {
    font-size: var(--fs-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--c-text-faint);
    margin-bottom: var(--sp-2);
}

.site-footer ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

.site-footer li + li {
    margin-top: var(--sp-1);
}

.site-footer a {
    color: var(--c-text-muted);
    text-decoration: none;
}

.site-footer a:hover {
    color: var(--c-accent);
    text-decoration: underline;
}

.footer-bottom {
    margin-top: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--c-border);
    display: flex;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
}

/* --------------------------------------------------------------- buttons */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    font: inherit;
    font-weight: 550;
    line-height: 1.2;
    padding: var(--sp-3) var(--sp-4);
    min-height: 44px;
    border-radius: var(--radius);
    border: 1px solid var(--c-border-strong);
    background: var(--c-surface);
    color: var(--c-text);
    cursor: pointer;
    text-decoration: none;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

.btn:hover {
    background: var(--c-bg-subtle);
}

.btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.btn--primary {
    background: var(--c-ink);
    border-color: var(--c-ink);
    color: var(--c-ink-contrast);
}

.btn--primary:hover {
    background: var(--c-ink-hover);
    border-color: var(--c-ink-hover);
    color: var(--c-ink-contrast);
}

.btn--ghost {
    border-color: transparent;
    background: transparent;
    color: var(--c-text-muted);
}

.btn--ghost:hover {
    background: var(--c-bg-subtle);
    color: var(--c-text);
}

.btn--sm {
    min-height: 36px;
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--fs-sm);
}

/*
 * 36px is comfortable under a mouse and too small under a thumb — the share
 * controls, the language link, the copy button and every tool toolbar are
 * `btn--sm`. Rather than grow them for everybody, the size is asked for only
 * where the pointer cannot be precise.
 */
@media (pointer: coarse) {
    .btn--sm {
        min-height: 44px;
        padding-inline: var(--sp-4);
    }
}

/* Square, and never below the size a thumb can hit. */
.btn--icon {
    width: 44px;
    min-width: 44px;
    padding: 0;
}

/* ---------------------------------------------------------------- inputs */

.field {
    display: block;
    margin-bottom: var(--sp-4);
}

.field__label {
    display: block;
    font-weight: 550;
    font-size: var(--fs-sm);
    margin-bottom: var(--sp-2);
}

.field__hint {
    display: block;
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
    margin-top: var(--sp-1);
}

.field__error {
    display: block;
    font-size: var(--fs-xs);
    color: var(--c-error);
    margin-top: var(--sp-1);
    font-weight: 550;
}

.input,
.select,
.textarea {
    width: 100%;
    font: inherit;
    color: var(--c-text);
    background: var(--c-bg);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius);
    padding: var(--sp-3);
    min-height: 44px;
}

.textarea {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    line-height: 1.5;
    resize: vertical;
    min-height: 180px;
    white-space: pre;
    overflow-wrap: normal;
    overflow-x: auto;
}

.input:disabled,
.select:disabled,
.textarea:disabled {
    background: var(--c-bg-sunken);
    color: var(--c-text-faint);
}

.input[aria-invalid="true"],
.textarea[aria-invalid="true"] {
    border-color: var(--c-error);
}

.radio-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
}

.radio-card {
    position: relative;
    display: inline-flex;
}

.radio-card input {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
}

.radio-card span {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: var(--sp-2) var(--sp-4);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius);
    font-size: var(--fs-sm);
    font-weight: 550;
}

.radio-card input:checked + span {
    background: var(--c-accent-subtle);
    border-color: var(--c-accent);
    color: var(--c-accent);
}

.radio-card input:focus-visible + span {
    outline: 2px solid var(--c-focus);
    outline-offset: 2px;
}

/* ----------------------------------------------------------------- cards */

.card {
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-lg);
    padding: var(--sp-5);
    box-shadow: var(--shadow-sm);
}

.tool-card {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    padding: var(--sp-4);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    text-decoration: none;
    color: inherit;
    transition: border-color 0.12s ease, transform 0.12s ease, box-shadow 0.12s ease;
}

.tool-card:hover {
    border-color: var(--c-cat);
    color: inherit;
}

/*
 * The card declared `transition: transform` and never set one, so the whole
 * feedback for pointing at a full-width link was its border turning blue.
 */
@media (prefers-reduced-motion: no-preference) {
    .tool-card:hover {
        transform: translateY(-1px);
        box-shadow: var(--shadow-sm);
    }
}

.tool-card__title {
    font-weight: 600;
    color: var(--c-text);
}

.tool-card__desc {
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
    margin: 0;
}

/*
 * A column so the count can sit at the foot of the card. The grid stretches
 * every card in a row to the tallest, and the descriptions are of very
 * different lengths, so a count that followed the text left four cards with
 * their figure floating in the middle of nowhere.
 */
.category-card {
    display: flex;
    flex-direction: column;
    padding: var(--sp-5);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-lg);
    background: var(--c-surface);
    text-decoration: none;
    color: inherit;
}

.category-card:hover {
    border-color: var(--c-cat);
    color: inherit;
}

/*
 * The mark keeps its own column. Inline, a title that wraps put its second line
 * underneath the icon instead of under its own first line.
 */
.category-card h3 {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

.category-card .cat-icon {
    flex: 0 0 auto;
    margin-inline-end: 0;
    vertical-align: baseline;
}

/*
 * The same pill as the drawer rows, so a family is recognised by its colour in
 * the menu and on the card that leads to it.
 */
.cat-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    margin-inline-end: var(--sp-2);
    border-radius: var(--radius);
    background: var(--c-cat-subtle);
    color: var(--c-cat);
    font-size: var(--fs-sm);
    line-height: 1;
    vertical-align: -0.55em;
}

/* The same mark, standing on its own above a category title. */
.cat-icon--lg {
    display: flex;
    width: 3rem;
    height: 3rem;
    margin-inline-end: 0;
    margin-bottom: var(--sp-3);
    border-radius: var(--radius-lg);
    font-size: var(--fs-lg);
    vertical-align: baseline;
}

.category-card__count {
    margin-top: auto;
    padding-top: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--c-text-faint);
}

/* ---------------------------------------------------------------- alerts */

.alert {
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius);
    border: 1px solid;
    font-size: var(--fs-sm);
}

.alert--info {
    background: var(--c-accent-subtle);
    border-color: var(--c-accent);
    color: var(--c-text);
}

.alert--ok {
    background: var(--c-ok-subtle);
    border-color: var(--c-ok);
    color: var(--c-text);
}

.alert--warn {
    background: var(--c-warn-subtle);
    border-color: var(--c-warn);
    color: var(--c-text);
}

.alert--error {
    background: var(--c-error-subtle);
    border-color: var(--c-error);
    color: var(--c-text);
}

/* -------------------------------------------------------------- stretches */

/*
 * A row of a journey: a distance and a speed side by side, because that is
 * how one stretch reads, and the button that removes it at the end of the
 * row. The two fields keep their own labels so that each stays named for
 * whoever reaches it alone.
 */
.stretches {
    display: grid;
    gap: var(--sp-3);
}

.stretches__add {
    margin: var(--sp-3) 0 var(--sp-4);
}

.stretch {
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.stretch__title {
    padding: 0;
    margin-bottom: var(--sp-1);
    font-size: var(--fs-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--c-text-muted);
}

.stretch__fields {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 44px;
    gap: var(--sp-3);
    align-items: end;
}

.stretch__fields .field {
    margin-bottom: 0;
}

.stretch__remove {
    min-height: 44px;
}

/* ---------------------------------------------------------------- entries */

/*
 * Rows the visitor adds and removes — days, products, grades. The same idea
 * as the stretches above, for any number of fields: they share the row, what
 * the row comes to sits after them, and the remove button closes it. Wrapping
 * rather than a fixed grid, because a row of three fields and a figure does
 * not fit a phone and must not overflow it.
 */
.entries {
    display: grid;
    gap: var(--sp-3);
}

.entry {
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.entry__title {
    padding: 0;
    margin-bottom: var(--sp-1);
    font-size: var(--fs-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--c-text-muted);
}

.entry__fields {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    align-items: flex-end;
}

.entry__fields .field {
    flex: 1 1 7.5rem;
    min-width: 0;
    margin-bottom: 0;
}

.entry__output {
    flex: 0 0 auto;
    min-width: 6rem;
    min-height: 44px;
    display: flex;
    align-items: center;
    font-weight: 600;
}

.entry__remove {
    flex: 0 0 44px;
    min-height: 44px;
}

.entries__add {
    margin: var(--sp-3) 0 var(--sp-4);
}

/*
 * The chmod grid. A box is the whole target of its cell, since the cell has
 * no label of its own beside it: 1.5rem drawn, and the cell's padding makes
 * up the rest of a finger.
 */
.chmod-grid {
    margin-bottom: var(--sp-4);
}

.chmod-grid th,
.chmod-grid td {
    padding-inline: var(--sp-1);
}

.chmod-grid th {
    font-size: var(--fs-sm);
}

.chmod-grid td,
.chmod-grid th[scope="col"] {
    text-align: center;
}

.chmod-grid input[type="checkbox"] {
    width: 1.5rem;
    height: 1.5rem;
    margin: 0;
    vertical-align: middle;
}

.chmod-grid__specials {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-4);
    margin: var(--sp-3) 0 0;
    padding: 0;
    border: 0;
}

.chmod-grid__specials legend {
    width: 100%;
    padding: 0;
}

/*
 * The note chart. The nearest note is marked in the accent's outline rather
 * than its fill, so the figure keeps the contrast of every other cell.
 */
.note-chart caption {
    caption-side: top;
    padding: var(--sp-2) var(--sp-3);
    text-align: left;
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.note-chart th[scope="row"] {
    white-space: nowrap;
}

.note-chart td[aria-current] {
    font-weight: 700;
    outline: 2px solid var(--c-accent);
    outline-offset: -2px;
}

/* ------------------------------------------------------------ tool panel */

.tool-header {
    margin-bottom: var(--sp-5);
}

.tool-header__lead {
    font-size: var(--fs-md);
    color: var(--c-text-muted);
    max-width: 65ch;
    margin: 0;
}

/*
 * Where the visitor works. It carried exactly the weight of a `related tools`
 * card at the foot of the page — same radius, same border, same padding, same
 * shadow — so nothing on the page said which part of it was the tool.
 */
.tool-panel {
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-color: var(--c-border-strong);
    border-radius: var(--radius-lg);
    padding: var(--sp-5);
}

@media (min-width: 48rem) {
    .tool-panel {
        padding: var(--sp-6);
    }
}


.tool-panel__toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
    margin-bottom: var(--sp-4);
    padding-bottom: var(--sp-4);
    border-bottom: 1px solid var(--c-border);
}

.tool-panel__toolbar .spacer {
    margin-inline-start: auto;
}

/*
 * The answer the visitor came for.
 *
 * It used to be set at `--fs-xl`, the size of an `h2`, inside a grey box with
 * the same weight as the `related tools` cards at the foot of the page. On a
 * site of calculators the figure is the product, so it is sized like one, and
 * the panel is marked with the family tint rather than left as another
 * undifferentiated grey rectangle.
 */
.result-panel {
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border);
    border-inline-start: 3px solid var(--c-cat);
    border-radius: var(--radius);
    padding: var(--sp-5);
}

/*
 * The headings a result puts under itself — `Field by field`, `Year by year`,
 * `Chords in this scale`. They are `h2` because nothing between them and the
 * page title is a heading: the panel sits directly under the `h1`, so an `h3`
 * there skipped a level and the page ran h1, h3, h3, h2, h2. The level states
 * where they sit in the page; this rule keeps the size they were read at.
 */
.result-heading {
    font-size: 1.25rem;
}

.result-panel__label {
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--c-text-faint);
    margin-bottom: var(--sp-2);
}

.result-value {
    margin-block: 0 var(--sp-2);
    font-family: var(--font-mono);
    font-size: clamp(2rem, 1.35rem + 2.4vw, 2.75rem);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.04em;
    /* Figures that change on every keystroke must not shift the ones beside
       them; a proportional `1` is narrower than every other digit. */
    font-variant-numeric: tabular-nums;
    word-break: break-word;
}

/*
 * Labelled rows of a result, as one grid: the labels make a column and every
 * value starts on the same edge. In a flex row each value started where its
 * own label ended, so five rows had five left edges — on every tool, since
 * the generic panel and the calculator skeleton both wrote them that way. On
 * a phone the label goes above its value instead of squeezing it.
 */
.result-rows {
    display: grid;
    gap: var(--sp-3);
    margin: 0;
}

.result-panel .result-rows {
    margin-top: var(--sp-4);
}

.result-rows > div {
    display: grid;
    gap: var(--sp-1);
}

.result-rows dt {
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.result-rows dd {
    margin: 0;
    overflow-wrap: anywhere;
}

@media (min-width: 36rem) {
    .result-rows {
        grid-template-columns: max-content 1fr;
        gap: var(--sp-2) var(--sp-5);
    }

    .result-rows > div {
        display: contents;
    }
}

/*
 * The answer when it is a table rather than a figure. Still the first thing in
 * the panel, still the thing the copy button takes, but read as lines.
 */
.result-list {
    margin-block: 0 var(--sp-2);
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    line-height: 1.5;
}

/*
 * The answer restated in words, under the figure it belongs to.
 *
 * Set in the body face rather than the mono stack: it is a sentence about the
 * result, not another reading of it, and the two would otherwise compete.
 */
.result-sentence {
    margin-block: 0 var(--sp-2);
    font-size: var(--fs-md);
    line-height: 1.5;
    text-wrap: pretty;
}

/* A unit is not the answer, it qualifies it. */
.result-value .text-muted {
    font-size: 0.6em;
    font-weight: 600;
}

/*
 * The secondary figures, as two columns rather than as flex rows.
 *
 * `generic_result` wraps each label/value pair in a `.cluster`, which lays them
 * out side by side and lets every row start its value wherever its own label
 * happens to end — so a panel of four figures had four different left edges and
 * nothing to read down.
 *
 * The grid is on the list and the wrappers step out of the box tree, because a
 * grid per row is the same bug in a better disguise: `max-content` sizes a
 * column against the grid it belongs to, so each row would size its own and the
 * values come out on a ragged edge again as soon as one label is longer than
 * the others. Stacked below 30rem, where two columns leave the value in a
 * gutter.
 */
/*
 * Stacked on a phone, two columns above it.
 *
 * Not every secondary value is a figure: the carbon page answers `11.1 times
 * less (world average 458.29 gCO2e/kWh)` against a label three lines long, and
 * splitting 358 pixels between the two leaves a column of about twenty
 * characters for each. Capping the label column with `fit-content` does not
 * save it — the problem is that there is no room for two columns at all.
 *
 * Above 30rem the label column takes what it needs and never more than a share
 * of the panel, so a long one wraps inside its own column instead of pushing
 * the figure off the edge.
 */
.result-panel dl {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-1);
}

.result-panel dl > .cluster {
    display: contents;
}

/*
 * Stacked, the pairs need telling apart from each other, and the value is
 * stepped in under its own label on purpose — the browser's own 40px indent on
 * a `dd` did it by accident, and ate a ninth of a phone's width doing it.
 */
.result-panel dd + dt {
    margin-top: var(--sp-2);
}

.result-panel dd {
    margin: 0;
    margin-inline-start: var(--sp-4);
}

@media (min-width: 30rem) {
    .result-panel dl {
        grid-template-columns: fit-content(45%) 1fr;
        gap: var(--sp-2) var(--sp-4);
        align-items: baseline;
    }

    .result-panel dd + dt {
        margin-top: 0;
    }

    .result-panel dd {
        margin-inline-start: 0;
    }
}

/*
 * A value that is a string rather than a figure — a set of note names, a pace,
 * the formula of a scale. It is set smaller because it is long, and it stops
 * being tracked tight because tight tracking is for numerals.
 *
 * It used to be called `--mono` and to name the typeface. Every result value is
 * set in the mono stack now, so the name described nothing and the declaration
 * repeated the rule above it.
 */
.result-value--long {
    font-size: clamp(1.35rem, 1.1rem + 1.1vw, 1.75rem);
    letter-spacing: 0;
}

@media (prefers-reduced-motion: no-preference) {
    .result-panel {
        animation: result-in 0.18s ease-out;
    }
}

@keyframes result-in {
    from {
        opacity: 0;
        transform: translateY(0.25rem);
    }
}

.privacy-note {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border);
    border-radius: 999px;
    padding: var(--sp-1) var(--sp-3);
}

.io-grid {
    display: grid;
    gap: var(--sp-4);
    grid-template-columns: 1fr;
}

@media (min-width: 900px) {
    .io-grid--split {
        grid-template-columns: 1fr 1fr;
    }
}

.field__symbol {
    margin-left: var(--sp-1);
    padding: 0 var(--sp-1);
    border-radius: var(--radius-sm);
    background: var(--c-bg-subtle);
    color: var(--c-text-muted);
    font-weight: 400;
}

/* ---------------------------------------------------------------- worked */

/*
 * A figure and the working that produced it, as one row. The value sits on the
 * heading line rather than repeated as a last step, because the panel that used
 * to follow this said every number a second time.
 */
.worked {
    margin-bottom: var(--sp-4);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    background: var(--c-surface);
}

.worked__heading {
    margin: 0;
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--c-border);
    font-size: var(--fs-sm);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--c-text-muted);
}

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

.worked__row {
    padding: var(--sp-3) var(--sp-4);
}

.worked__row + .worked__row {
    border-top: 1px solid var(--c-border);
}

.worked__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-2);
    margin: 0;
}

.worked__label {
    font-weight: 550;
}

.worked__value {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* The answer the page was asked for, set apart from the rest. */
.worked__row--primary {
    background: var(--c-accent-subtle);
}

.worked__row--primary .worked__value {
    font-size: var(--fs-xl, 1.5rem);
    color: var(--c-accent);
}

/* An intermediate is part of the working, not an answer. */
.worked__row--step .worked__label,
.worked__row--step .worked__value {
    font-weight: 400;
    color: var(--c-text-muted);
}

/*
 * Each line scrolls on its own rather than the page scrolling sideways: a
 * formula is wider than a phone at any font size worth reading.
 */
.worked__step {
    margin: var(--sp-1) 0 0;
    overflow-x: auto;
    white-space: nowrap;
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

/* ------------------------------------------------------------- diagrams */

/*
 * Figures colour themselves from the tokens rather than from attributes in the
 * markup: the Content Security Policy blocks inline `style`, and going through
 * the stylesheet is also what makes a diagram follow the theme.
 */
.figure-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    margin-bottom: var(--sp-4);
}

.figure-triangle {
    margin: 0;
    flex: 1 1 14rem;
    max-width: 22rem;
    text-align: center;
}

.triangle-svg {
    width: 100%;
    height: auto;
}

.figure-shape {
    margin: 0 0 var(--sp-4);
    max-width: 20rem;
    text-align: center;
}

.shape-svg {
    width: 100%;
    height: auto;
}

/*
 * A solid is painted furthest face first, so the only thing distinguishing the
 * faces is their shade. One flat colour would read as an outline rather than a
 * solid.
 *
 * The shades come from the accent colour thinned down, not from the already
 * pale tint thinned down. Thinning the tint is what turned a cone into an empty
 * outline: its whole body is one face, and a near-white fill at three quarters
 * opacity is a fill nobody can see.
 */
.solid-svg__face {
    fill: var(--c-accent);
    fill-opacity: 0.22;
    stroke: var(--c-accent);
    stroke-width: 0.8;
    stroke-linejoin: round;
}

.solid-svg__face--side {
    fill-opacity: 0.34;
}

.solid-svg__face--front {
    fill-opacity: 0.46;
}

.solid-svg__face--bottom {
    fill-opacity: 0.14;
}

/*
 * A great circle drawn on the solid: the equator and a meridian on a sphere,
 * the ridge round the top of a torus. A bare outline is a circle; the same
 * circle with these on it is a sphere.
 */
.solid-svg__guide {
    fill: none;
    stroke: var(--c-accent);
    stroke-width: 0.6;
    stroke-opacity: 0.55;
}

/*
 * An edge that is really there and is hidden by the solid in front of it. The
 * dashes are the technical drawing convention, and they are what makes a
 * cylinder read as a tube rather than a rectangle with a lid.
 */
.solid-svg__hidden {
    fill: none;
    stroke: var(--c-accent);
    stroke-width: 0.6;
    stroke-opacity: 0.45;
    stroke-dasharray: 2 1.6;
}

.shape-svg__face,
.triangle-svg__face {
    fill: var(--c-accent-subtle);
    stroke: var(--c-accent);
    stroke-width: 1.2;
    stroke-linejoin: round;
}

.triangle-svg__side,
.triangle-svg__vertex {
    font-size: 7px;
    font-family: inherit;
}

.triangle-svg__side {
    fill: var(--c-text-muted);
}

.triangle-svg__vertex {
    fill: var(--c-accent);
    font-weight: 600;
}

/* -------------------------------------------------------------- qr code */

/*
 * The symbol carries its own white quiet zone, so the figure must not tint it:
 * a dark-theme background bleeding into the margin is one of the ways a printed
 * code stops scanning.
 */
.qr-figure {
    margin: 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    min-height: 12rem;
}

.qr-figure svg {
    max-width: 100%;
    height: auto;
}

/* ------------------------------------------------------------------ tabs */

.tabs {
    display: flex;
    gap: var(--sp-1);
    border-bottom: 1px solid var(--c-border);
    margin-bottom: var(--sp-4);
    overflow-x: auto;
}

.tabs button {
    font: inherit;
    font-weight: 550;
    font-size: var(--fs-sm);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--c-text-muted);
    padding: var(--sp-3) var(--sp-3);
    min-height: 44px;
    cursor: pointer;
    white-space: nowrap;
}

.tabs button[aria-selected="true"],
.tabs a[aria-current="page"] {
    color: var(--c-accent);
    border-bottom-color: var(--c-accent);
}

.tabs a {
    border-radius: 0;
    border-bottom: 2px solid transparent;
}

.select--auto {
    width: auto;
}

/* ---------------------------------------------------------------- tables */

.table-wrap {
    overflow-x: auto;
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
}

table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-sm);
}

th,
td {
    text-align: start;
    padding: var(--sp-2) var(--sp-3);
    border-bottom: 1px solid var(--c-border);
}

th {
    background: var(--c-bg-subtle);
    font-weight: 600;
    white-space: nowrap;
}

tbody tr:last-child td {
    border-bottom: none;
}

td.num,
th.num {
    text-align: end;
    font-variant-numeric: tabular-nums;
    font-family: var(--font-mono);
}

/* ---------------------------------------------------------------- search */

.search {
    position: relative;
    flex: 1 1 240px;
    max-width: 420px;
}

.search--hero {
    max-width: 100%;
    margin-inline: auto;
}

/*
 * On the search page the box sits under the title, on the same edge as it and
 * the cards, and stays the width of a line of text rather than of the grid.
 */
.search-page .search--hero {
    max-width: var(--container-narrow);
    margin-inline: 0;
}

.search__input {
    width: 100%;
    font: inherit;
    padding: var(--sp-3) var(--sp-4);
    min-height: 44px;
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius);
    background: var(--c-bg);
    color: var(--c-text);
}

.search--hero .search__input {
    min-height: 56px;
    font-size: var(--fs-md);
}

.search__hint {
    display: none;
}

/*
 * Only where a keyboard is likely and the bar is wide enough to spare the room.
 * A badge announcing a key nobody can press is worse than no badge.
 */
@media (min-width: 64rem) and (hover: hover) {
    .search__hint {
        position: absolute;
        inset-inline-end: var(--sp-2);
        top: 50%;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 1.5rem;
        height: 1.5rem;
        padding: 0 var(--sp-1);
        border: 1px solid var(--c-border);
        border-radius: var(--radius-sm);
        background: var(--c-bg-subtle);
        color: var(--c-text-faint);
        font-family: var(--font-mono);
        font-size: var(--fs-xs);
        line-height: 1;
        transform: translateY(-50%);
        pointer-events: none;
    }

    .site-header .search__input {
        padding-inline-end: var(--sp-7);
    }
}

/* Out of the way as soon as the field is being used. */
.search__input:focus ~ .search__hint,
.search__input:not(:placeholder-shown) ~ .search__hint {
    display: none;
}

.search__results {
    position: absolute;
    inset-inline: 0;
    top: calc(100% + 4px);
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    list-style: none;
    margin: 0;
    padding: var(--sp-1);
    max-height: 60vh;
    overflow-y: auto;
    z-index: 30;
}

.search__results:empty,
.search__results[hidden] {
    display: none;
}

.search__results a {
    display: block;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    text-decoration: none;
    color: var(--c-text);
}

.search__results li[aria-selected="true"] a,
.search__results a:hover {
    background: var(--c-accent-subtle);
}

.search__cat {
    display: block;
    font-size: var(--fs-xs);
    color: var(--c-text-faint);
}

.search__empty {
    padding: var(--sp-3);
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

/* ----------------------------------------------------------- zone picker */

/*
 * The time zone converter's two zones, side by side with the swap between
 * them once there is room, stacked under each other until then.
 */
.zone-pair {
    display: grid;
    gap: 0 var(--sp-3);
}

@media (min-width: 48rem) {
    .zone-pair {
        grid-template-columns: 1fr auto 1fr;
        align-items: start;
    }

    /* Level with the two fields rather than with their labels. */
    .zone-pair__swap {
        margin-top: calc(var(--fs-sm) * 1.5 + var(--sp-2));
        min-height: 44px;
    }
}

.zone-pair__swap {
    justify-self: start;
    margin-bottom: var(--sp-4);
}

.zone-picker {
    position: relative;
}

.zone-picker__list {
    position: absolute;
    inset-inline: 0;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    list-style: none;
    margin: 4px 0 0;
    padding: var(--sp-1);
    max-height: 50vh;
    overflow-y: auto;
    z-index: 30;
}

.zone-picker__list li {
    display: flex;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.zone-picker__list li[aria-selected="true"],
.zone-picker__list li[data-value]:hover {
    background: var(--c-accent-subtle);
}

.zone-picker__offset {
    flex: none;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
}

.zone-picker__list .zone-picker__empty {
    cursor: default;
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

.zone-picker__mine {
    margin-top: var(--sp-1);
}

/* ----------------------------------------------------------- provenance */

/*
 * The receipt under the answer.
 *
 * It carries the same tinted edge as the result panel it follows, so the two
 * read as one object continued rather than as an answer and an unrelated box.
 * Quieter than the figure in every way — smaller, lighter, keys in the same
 * fixed pitch as the figures — because it is there to be checked, not read.
 */
.provenance {
    border: 1px solid var(--c-border);
    border-inline-start: 3px solid var(--c-cat);
    border-radius: var(--radius);
    padding: var(--sp-4);
    background: var(--c-bg);
}

.provenance__title {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--c-text-faint);
}

/*
 * One grid for the whole list, not one per row: `auto` sizes a column against
 * the grid it belongs to, so a grid per row gives every row its own key column
 * and the values come out on a ragged edge.
 *
 * Stacked on a narrow screen, because the longest key — `In force since` —
 * takes a third of a phone's width and leaves the source, which is the longest
 * value and the one worth reading, in a gutter.
 */
.provenance__list {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-1);
    margin: 0;
    font-size: var(--fs-sm);
}

/* Stacked, the pairs need telling apart from each other. */
.provenance__value + .provenance__key {
    margin-top: var(--sp-2);
}

@media (min-width: 30rem) {
    .provenance__list {
        grid-template-columns: minmax(5rem, max-content) 1fr;
        gap: var(--sp-2) var(--sp-4);
        align-items: baseline;
    }

    .provenance__value + .provenance__key {
        margin-top: 0;
    }
}

.provenance__key {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--c-text-faint);
}

.provenance__value {
    margin: 0;
    color: var(--c-text-muted);
    overflow-wrap: anywhere;
}

/* One source reads as a line; several read as a list, without changing shape. */
.provenance__sources {
    margin: 0;
    padding: 0;
    list-style: none;
}

.provenance__sources li + li {
    margin-top: var(--sp-1);
}

/* ------------------------------------------------------------ breadcrumb */

.breadcrumb {
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
    margin-bottom: var(--sp-4);
}

.breadcrumb ol {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin: 0;
    padding: 0;
}

.breadcrumb li + li::before {
    content: "/";
    margin-inline-end: var(--sp-2);
    color: var(--c-text-faint);
}

.breadcrumb a {
    color: var(--c-text-muted);
    text-decoration: none;
}

.breadcrumb a:hover {
    color: var(--c-accent);
}

/* ------------------------------------------------------------------ hero */

.hero {
    text-align: center;
    padding-block: var(--sp-7) var(--sp-6);
}

.hero h1 {
    /*
     * The lower bound is what a phone gets, and the headline is ninety
     * characters: at 2.1rem it ran to five lines and pushed the search field,
     * which is the way into a catalogue this size, below the fold on a 390pt
     * screen. It grows to 3.25rem where there is room for it.
     */
    font-size: clamp(1.85rem, 1.3rem + 2.2vw, 3rem);
    max-width: 20ch;
    margin-inline: auto;
}

.hero__sub {
    font-size: var(--fs-md);
    color: var(--c-text-muted);
    max-width: 60ch;
    margin: 0 auto var(--sp-5);
}

/* ------------------------------------------------------------- prose/faq */

.prose {
    max-width: 68ch;
}

.prose h2 {
    margin-top: var(--sp-6);
    font-size: var(--fs-lg);
}

.prose h3 {
    margin-top: var(--sp-5);
    font-size: var(--fs-md);
}

.prose ul,
.prose ol {
    padding-inline-start: var(--sp-5);
    margin-bottom: var(--sp-4);
}

.prose li + li {
    margin-top: var(--sp-2);
}

.legal-notice dt {
    font-weight: 600;
}

.legal-notice dd {
    margin: 0 0 var(--sp-3);
}

.faq details {
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-surface);
}

.faq details + details {
    margin-top: var(--sp-2);
}

.faq summary {
    cursor: pointer;
    font-weight: 600;
    min-height: 32px;
    display: flex;
    align-items: center;
}

.faq details p:last-child {
    margin-bottom: 0;
}

/* --------------------------------------------------------------- ad slot */

.ad-slot {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px dashed var(--c-border);
    border-radius: var(--radius);
    color: var(--c-text-faint);
    font-size: var(--fs-xs);
    background: var(--c-bg-subtle);
}

/*
 * 728 pixels do not fit a phone, and above the form a smaller banner would
 * still push the tool below the fold, so the leaderboard is not shown there.
 * AdSense documents hiding a unit this way and serves nothing into it.
 */
.ad-slot--leaderboard {
    display: none;
    min-height: 90px;
}

.ad-slot--rectangle {
    min-height: 250px;
}

/*
 * Google chooses the height of a multiplex grid. It only ever sits at the
 * foot of a page, so this is a floor rather than a promise.
 */
.ad-slot--multiplex {
    min-height: 280px;
}

/*
 * AdSense's own element. It expects `display: block` and is normally given it
 * through a style attribute, which the Content Security Policy forbids on our
 * markup — so it comes from here instead. Each fixed format gets an exact
 * width and height matching the reserved box around it, so a filled slot
 * never pushes the content down.
 */
.adsbygoogle {
    display: block;
}

/*
 * A 320-pixel phone leaves 288 pixels between the gutters, so the 300×250
 * would push the page sideways there. 250×250 is the fixed size that fits;
 * the unit in the console stays the same, since a fixed-size unit is filled
 * at whatever size its `<ins>` is given.
 */
.adsbygoogle--rectangle {
    width: 250px;
    height: 250px;
}

.adsbygoogle--multiplex {
    width: 100%;
}

@media (min-width: 22rem) {
    .adsbygoogle--rectangle {
        width: 300px;
    }
}

@media (min-width: 48rem) {
    .ad-slot--leaderboard {
        display: flex;
    }

    .adsbygoogle--leaderboard {
        width: 728px;
        height: 90px;
    }

    .ad-slot--rectangle {
        min-height: 280px;
    }

    .adsbygoogle--rectangle {
        width: 336px;
        height: 280px;
    }
}

/* --------------------------------------------------------------- utility */

.text-muted {
    color: var(--c-text-muted);
}

.text-sm {
    font-size: var(--fs-sm);
}

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

/*
 * A value that was built as a list of lines is read as one. The race splits
 * are such a list in a row the shared calculator renders, so they are named
 * here rather than given a class: without it the watch times ran together on
 * one line, on the server's render and the module's alike.
 */
.lines,
[data-result="splits"] {
    white-space: pre-line;
}

/*
 * A short marker inside a table cell: the year two lines cross, the lots
 * carrying a rounded cent. It has to read as an annotation on the figure next
 * to it rather than as a second figure, so it never takes the accent colour.
 */
.badge {
    display: inline-block;
    padding: 0 var(--sp-1);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm, 3px);
    background: var(--c-bg-subtle);
    color: var(--c-text-muted);
    font-size: var(--fs-xs);
    font-weight: 600;
    white-space: nowrap;
    vertical-align: middle;
}

.section-title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
}

.error-page {
    text-align: center;
    padding-block: var(--sp-8);
}

.error-page__code {
    font-size: 4rem;
    font-weight: 700;
    color: var(--c-text-faint);
    line-height: 1;
    margin-bottom: var(--sp-3);
}

/* ------------------------------------------------------------ stat tiles */

.stat-grid {
    display: grid;
    gap: var(--sp-3);
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    margin-block: var(--sp-4);
}

.stat {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
}

.stat__value {
    font-size: var(--fs-lg);
    font-weight: 650;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.stat--primary .stat__value {
    font-size: var(--fs-xl);
    color: var(--c-accent);
}

.stat__label {
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
}

/* --------------------------------------------------------- regex tester */

.regex-input {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

.regex-input__slash {
    font-family: var(--font-mono);
    color: var(--c-text-faint);
}

.regex-preview {
    margin: 0;
    max-height: 40vh;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    white-space: pre-wrap;
    word-break: break-word;
}

mark {
    padding: 0 2px;
    border-radius: 2px;
    background: var(--c-accent-subtle);
    color: var(--c-text);
    box-shadow: inset 0 -1px 0 var(--c-accent);
}

/*
 * Cloned next to every `[data-copy-source]` by `share.mjs`. It sits under the
 * figure rather than inside it: several tools rewrite that element's text on
 * every keystroke, and a button living in there would be thrown away with it.
 */
.copy-value {
    vertical-align: middle;
}

.worked__value + .copy-value {
    margin-inline-start: var(--sp-2);
}

/* ------------------------------------------------------------- tap tempo */

.tap-area {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    align-items: center;
    margin-block: var(--sp-4);
}

.tap-button {
    flex: 1 1 220px;
    min-height: 96px;
    font-size: var(--fs-lg);
}

.tap-button:active {
    background: var(--c-accent-hover);
    color: var(--c-accent-contrast);
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ----------------------------------------------------------- operator area */

/*
 * The back office reuses the site's tokens, spacing and table styles so there
 * is one stylesheet and one visual language. What it adds is only what has no
 * equivalent on a public page: a utility header, a stat row and a change list.
 */

.admin-header {
    background: var(--c-surface);
    border-bottom: 1px solid var(--c-border);
}

.admin-header__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
    max-width: 72rem;
    margin: 0 auto;
    padding: 0.75rem 1rem;
}

.admin-header__brand {
    font-weight: 600;
}

.admin-header__user {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-left: auto;
}

.admin-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
}

.admin-main {
    max-width: 72rem;
    margin: 0 auto;
    padding: 1.5rem 1rem 4rem;
}

.admin-narrow {
    max-width: 34rem;
}

.admin-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
    margin: 1.5rem 0;
}

.admin-stats dt {
    color: var(--c-text-muted);
    font-size: 0.875rem;
}

.admin-stats dd {
    margin: 0;
    font-size: 1.75rem;
    font-weight: 600;
}

.admin-target-list {
    list-style: none;
    padding: 0;
}

.admin-target-list li {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.25rem 0;
    border-bottom: 1px solid var(--c-border);
}

.admin-target-list li a {
    text-transform: uppercase;
    font-size: 0.75rem;
}

.admin-change-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.admin-pager {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-top: 1.5rem;
}

/* ------------------------------------------------------------------- diff */

.diff-panes {
    display: grid;
    gap: 1rem;
    grid-template-columns: 1fr;
}

@media (min-width: 48rem) {
    .diff-panes {
        grid-template-columns: 1fr 1fr;
    }
}

.diff {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
}

.diff th,
.diff td {
    padding: 0.1rem 0.5rem;
    border: 0;
    vertical-align: top;
}

.diff__num {
    width: 3.5rem;
    text-align: right;
    color: var(--c-text-faint);
    user-select: none;
    white-space: nowrap;
}

.diff__text {
    white-space: pre-wrap;
    word-break: break-word;
}

/*
 * The sign carries the meaning for anyone who cannot see the background
 * colour, printed output included. Colour alone would fail that.
 */
.diff__sign {
    display: inline-block;
    width: 1ch;
    margin-right: 0.75rem;
    color: var(--c-text-faint);
}

.diff__row--added {
    background: var(--c-ok-subtle);
}

.diff__row--added .diff__sign {
    color: var(--c-ok);
}

.diff__row--removed {
    background: var(--c-error-subtle);
}

.diff__row--removed .diff__sign {
    color: var(--c-error);
}

/* ------------------------------------------------------------- code block */

/*
 * Read-only output that is meant to be looked at rather than edited. Wide
 * content scrolls inside the block so the page itself never scrolls sideways.
 */
.code-block {
    margin: 0 0 var(--sp-4);
    padding: var(--sp-3);
    background: var(--c-bg-sunken);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    overflow-x: auto;
    font-size: var(--fs-sm);
    line-height: 1.5;
}

.code-block code {
    white-space: pre;
}

/* -------------------------------------------------------------- timers */

/*
 * Shared by the countdown, the stopwatch and the Pomodoro. The readout is
 * deliberately the largest thing on the page: these are read from across a
 * room, or out of the corner of an eye, far more often than they are studied.
 */
.timer {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-5);
    margin-bottom: var(--sp-5);
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-lg);
}

.timer__readout {
    margin: 0;
    font-size: clamp(2.75rem, 12vw, 4.5rem);
    font-weight: 700;
    line-height: 1;
    /* Tabular figures, or the whole number shifts sideways as digits change. */
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
}

.timer__phase {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--c-text-muted);
}

/*
 * Work and rest are told apart by colour and by the word above the clock, not
 * by colour alone: the phase name is always there to be read.
 */
.timer__phase[data-phase="work"] {
    color: var(--c-accent);
}

.timer__round {
    margin: 0;
}

.timer__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-2);
}

.timer__play {
    min-width: 8rem;
}

.timer__laps {
    margin-bottom: var(--sp-5);
}

/* ----------------------------------------------------------- metronome */

/*
 * The console: the tempo and the button, which is what gets touched with
 * sticks in hand, and nothing else. Everything below it is configuration and
 * looks like it.
 */

.metronome {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-5);
    margin-bottom: var(--sp-5);
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-lg);
}

.metronome__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    width: 100%;
    min-height: 36px;
}

.metronome__count {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--c-text-muted);
}

.metronome__full {
    margin-left: auto;
}

.metronome__tempo {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    width: 100%;
}

/*
 * The tempo is typed, not only dragged. Thirty to three hundred across a phone
 * is about a beat and a half per pixel, and a drummer needs 112 rather than
 * "around 110".
 */

.metronome__bpm {
    font: inherit;
    font-size: var(--fs-2xl);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    text-align: center;
    width: 4.5ch;
    padding: var(--sp-2);
    color: var(--c-text);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius);
}

.metronome__bpm:hover,
.metronome__bpm:focus {
    border-color: var(--c-border-strong);
    background: var(--c-bg);
}

/* The spinners are a second, tiny pair of buttons next to two large ones. */
.metronome__bpm::-webkit-outer-spin-button,
.metronome__bpm::-webkit-inner-spin-button {
    appearance: none;
    margin: 0;
}

.metronome__bpm {
    -moz-appearance: textfield;
    appearance: textfield;
}

.metronome__unit {
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

.metronome__step {
    min-width: 44px;
    font-size: var(--fs-lg);
    font-variant-numeric: tabular-nums;
}

.metronome__slider {
    max-width: 32rem;
}

.metronome__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-3);
}

.metronome__play {
    min-width: 8rem;
}

.metronome__lights {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-2);
    min-height: 1rem;
}

.metronome__light {
    width: 0.9rem;
    height: 0.9rem;
    border-radius: 50%;
    background: var(--c-bg-sunken);
    border: 1px solid var(--c-border-strong);
}

.metronome__light.is-on {
    background: var(--c-accent);
    border-color: var(--c-accent);
}

/*
 * The lights are a pulse, not an animation. Anyone who has asked for less
 * motion still needs to see which beat is which, so the state stays and only
 * the transition goes.
 */
@media (prefers-reduced-motion: no-preference) {
    .metronome__light {
        transition: background-color 0.08s ease-out, border-color 0.08s ease-out;
    }
}

/*
 * Full screen. A screen on a stand two metres away shows nothing at the sizes
 * above, so everything that carries information grows and everything that does
 * not gets out of the way. `:fullscreen` covers the real API; `.is-full` is the
 * same layout for the browsers that refuse it, which is why both are listed
 * against every rule.
 */

.metronome:fullscreen,
.metronome.is-full {
    justify-content: center;
    margin: 0;
    border: 0;
    border-radius: 0;
    background: var(--c-bg);
}

.metronome.is-full {
    position: fixed;
    inset: 0;
    z-index: 100;
    overflow: auto;
}

.metronome:fullscreen .metronome__count,
.metronome.is-full .metronome__count {
    font-size: var(--fs-xl);
    color: var(--c-text);
}

.metronome:fullscreen .metronome__bpm,
.metronome.is-full .metronome__bpm {
    font-size: clamp(4rem, 22vw, 12rem);
    width: 4ch;
}

.metronome:fullscreen .metronome__light,
.metronome.is-full .metronome__light {
    width: clamp(2rem, 8vw, 4rem);
    height: clamp(2rem, 8vw, 4rem);
}

.metronome:fullscreen .metronome__slider,
.metronome.is-full .metronome__slider {
    display: none;
}

/*
 * Presets. One low line: they are reached before you play and never while you
 * are playing, so they earn a row and not a panel.
 */

.presets {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    margin-bottom: var(--sp-5);
}

.presets__label {
    font-size: var(--fs-sm);
    font-weight: 550;
    color: var(--c-text-muted);
}

.presets__slot {
    display: flex;
    align-items: center;
    gap: 2px;
}

.presets__load {
    min-width: 7rem;
    font-variant-numeric: tabular-nums;
}

.presets__clear {
    min-width: 36px;
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
}

/* The disclosures under the console: sound, practice, and the drawn grid. */

.metronome__panel {
    margin-bottom: var(--sp-5);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    padding: var(--sp-3) var(--sp-4);
    background: var(--c-surface);
}

.metronome__panel summary {
    cursor: pointer;
    font-weight: 600;
    min-height: 32px;
    display: flex;
    align-items: center;
}

.metronome__panel[open] summary {
    margin-bottom: var(--sp-4);
}

/* ------------------------------------------------------- rhythm grid */

/*
 * One column per step, one row per voice. It scrolls sideways rather than
 * wrapping: a clave read across two lines is not a clave, and twenty-eight
 * steps of seven against four will not fit on a phone at any cell size worth
 * looking at.
 */

.rhythm-grid {
    margin: 0 0 var(--sp-5);
    padding: var(--sp-4);
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-lg);
}

.rhythm-grid__caption {
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
    margin-bottom: var(--sp-3);
}

.rhythm-grid__rows {
    display: flex;
    align-items: stretch;
    gap: 2px;
    overflow-x: auto;
    padding-bottom: var(--sp-2);
}

.rhythm-grid__labels {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-right: var(--sp-3);
    flex: 0 0 auto;
    position: sticky;
    left: 0;
    background: var(--c-bg-subtle);
    z-index: 1;
}

.rhythm-grid__label {
    font-size: var(--fs-xs);
    color: var(--c-text-muted);
    line-height: 1;
    height: 0.9rem;
    display: flex;
    align-items: center;
    white-space: nowrap;
}

.rhythm-grid__col {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 0 0 auto;
    padding: 2px;
    border-radius: 2px;
}

.rhythm-grid__col.is-beat {
    background: var(--c-bg-sunken);
}

.rhythm-grid__col.is-bar {
    margin-left: var(--sp-2);
}

.rhythm-grid__col.is-now {
    outline: 2px solid var(--c-accent);
}

.rhythm-grid__cell {
    width: 0.9rem;
    height: 0.9rem;
    border-radius: 2px;
    border: 1px solid var(--c-border);
}

.rhythm-grid__cell.is-ghost {
    background: var(--c-border-strong);
}

.rhythm-grid__cell.is-normal {
    background: var(--c-accent);
    border-color: var(--c-accent);
}

.rhythm-grid__cell.is-accent {
    background: var(--c-accent);
    border-color: var(--c-accent);
    box-shadow: inset 0 0 0 2px var(--c-bg-subtle);
}

/* --------------------------------------------------- circle of fifths */

.circle-of-fifths {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-5);
    align-items: flex-start;
    margin-bottom: var(--sp-5);
}

.circle-of-fifths__svg {
    width: 320px;
    max-width: 100%;
    height: auto;
    flex: 0 0 auto;
}

.circle-of-fifths .table-wrap {
    flex: 1 1 18rem;
}

.circle-of-fifths__marker {
    fill: var(--c-bg-subtle);
    stroke: var(--c-border-strong);
    stroke-width: 1;
}

.circle-of-fifths__major {
    fill: var(--c-text);
    font-size: 13px;
    font-weight: 650;
}

.circle-of-fifths__minor {
    fill: var(--c-text-muted);
    font-size: 11px;
}

.circle-of-fifths__slot.is-selected .circle-of-fifths__marker {
    fill: var(--c-accent-subtle);
    stroke: var(--c-accent);
    stroke-width: 2;
}

.circle-of-fifths__slot.is-selected .circle-of-fifths__major {
    fill: var(--c-accent);
}

/*
 * The selected row is marked with weight as well as colour, so the highlight
 * survives being printed or read without colour.
 */
.circle-of-fifths tr.is-selected {
    background: var(--c-accent-subtle);
    font-weight: 650;
}

/* ------------------------------------------------------------- fretboard */

/*
 * The neck is wider than the page on anything but a short bass, so it scrolls
 * inside its own block. The page itself never scrolls sideways.
 */
.fretboard__scroll {
    margin-bottom: var(--sp-4);
    overflow-x: auto;
}

/*
 * A left-handed neck is drawn with its nut at the right-hand end, so the block
 * is flush right and, when it is wider than the page, already scrolled there.
 * `direction` is what does both: it moves the start edge, and a scroll box
 * opens at its start edge.
 */
.fretboard__scroll--left {
    direction: rtl;
}

.fretboard__svg {
    display: block;
    max-width: none;

    /*
     * Set back explicitly: the mirrored container is `rtl`, and inheriting that
     * would let the bidi algorithm reorder the note names inside — a `C♯` can
     * come out `♯C`. The picture is mirrored by its coordinates, never by the
     * text direction.
     */
    direction: ltr;
}

.fretboard__wire {
    stroke: var(--c-border-strong);
    stroke-width: 1;
}

.fretboard__wire--nut {
    stroke-width: 4;
}

.fretboard__string {
    stroke: var(--c-border);
    stroke-width: 1;
}

.fretboard__inlay {
    fill: var(--c-border-strong);
    opacity: 0.4;
}

.fretboard__number {
    fill: var(--c-text-muted);
    font-size: 10px;
    text-anchor: middle;
}

.fretboard__open {
    fill: var(--c-text-muted);
    font-size: 11px;
    font-weight: 650;
}

/*
 * On a mirrored neck the open note sits in the right-hand margin, so it has to
 * grow leftwards from its anchor instead of rightwards, or a two-character name
 * runs off the edge of the diagram.
 */
.fretboard__open--left {
    text-anchor: end;
}

.fretboard__dot-shape {
    fill: var(--c-bg-subtle);
    stroke: var(--c-border-strong);
    stroke-width: 1;
}

.fretboard__dot-text {
    fill: var(--c-text);
    font-size: 9px;
    text-anchor: middle;
}

/*
 * The root is marked by weight as well as colour, so it survives being printed
 * or read without colour.
 */
.fretboard__dot--root .fretboard__dot-shape {
    fill: var(--c-accent-subtle);
    stroke: var(--c-accent);
    stroke-width: 2;
}

.fretboard__dot--root .fretboard__dot-text {
    fill: var(--c-accent);
    font-weight: 700;
}

/* -------------------------------------------------------------- keyboard */

/*
 * One octave, shown beside the note list rather than instead of it. The
 * diagram is a second way of reading the same thing, never the only way.
 */
.keyboard {
    margin-bottom: var(--sp-4);
    overflow-x: auto;
}

.keyboard__svg {
    display: block;
}

.keyboard__key--white .keyboard__shape {
    fill: var(--c-bg);
    stroke: var(--c-border-strong);
    stroke-width: 1;
}

.keyboard__key--black .keyboard__shape {
    fill: var(--c-text);
    stroke: var(--c-text);
    stroke-width: 1;
}

.keyboard__key--white .keyboard__label {
    fill: var(--c-text);
    font-size: 10px;
    text-anchor: middle;
}

.keyboard__key--black .keyboard__label {
    fill: var(--c-bg);
    font-size: 9px;
    text-anchor: middle;
}

.keyboard__key.is-on.keyboard__key--white .keyboard__shape {
    fill: var(--c-accent-subtle);
    stroke: var(--c-accent);
}

.keyboard__key.is-on.keyboard__key--black .keyboard__shape {
    fill: var(--c-accent);
    stroke: var(--c-accent);
}

/*
 * The root is marked by weight as well as colour, so it survives being printed
 * or read without colour.
 */
.keyboard__key.is-root .keyboard__shape {
    stroke-width: 3;
}

.keyboard__key.is-root .keyboard__label {
    font-weight: 700;
}

/* -------------------------------------------------------------- range */

/*
 * Used by the password length control. A number field would do, but a slider
 * makes the trade-off visible: the entropy figure moves as it is dragged.
 */
.range {
    width: 100%;
    max-width: 28rem;
    margin: var(--sp-2) 0;
    accent-color: var(--c-accent);
}

/* ------------------------------------------------------------ discount */

/*
 * The answer as the object it describes.
 *
 * Every other calculator puts its figure under a label, which is right when
 * the figure is a payment or a ratio. A discount already has a shape everyone
 * reads without being taught — the old price struck through, the new one
 * large, and the reduction on a sticker — and the sticker is the point of the
 * page: it carries the *effective* discount, which is the figure two stacked
 * reductions get wrong.
 *
 * Two columns rather than a badge positioned over a corner. The sticker is
 * narrow and the prices are not, so `auto` keeps it out of their way at every
 * width instead of overlapping them at one.
 */
.price-tag {
    display: grid;
    grid-template-columns: 1fr auto;
    /* Named rather than left to auto-placement, which would put the struck
       price and the one that replaced it side by side on the first row. */
    grid-template-areas:
        "was   off"
        "now   off"
        "saved off";
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-5);
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border);
    border-inline-start: 3px solid var(--c-cat);
    border-radius: var(--radius);
}

.price-tag > p {
    margin: 0;
}

/*
 * `share.mjs` clones the copy button next to the value it copies, which here
 * makes it a child of this grid — and a grid child with no area of its own
 * lands in an implicit row and stretches across the whole first column. It is
 * a button, so it takes the width of its own word, under the price it copies.
 */
.price-tag > .copy-value {
    grid-column: 1;
    justify-self: start;
}

.price-tag__was {
    grid-area: was;
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    color: var(--c-text-faint);
    font-variant-numeric: tabular-nums;
}

.price-tag__now {
    grid-area: now;
    /* `.result-value` sizes it; this only stops the two margins it carries
       from spacing a grid that is already spaced. */
    margin-block: 0;
}

.price-tag__saved {
    grid-area: saved;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: baseline;
    font-size: var(--fs-sm);
}

/*
 * The sticker. Ink rather than the family tint: this one has text on it, and
 * the tint is a decoration held to the 3:1 of a non-text element.
 */
.price-tag__off {
    grid-area: off;
    justify-self: end;
}

.price-tag__off-figure {
    display: inline-block;
    padding: var(--sp-2) var(--sp-3);
    background: var(--c-ink);
    color: var(--c-ink-contrast);
    border-radius: var(--radius-lg);
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: 700;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    /* Stuck on by hand, not printed with the label. */
    rotate: -3deg;
}

/*
 * Why twenty per cent twice is not forty, as three lengths.
 *
 * A `progress` rather than a filled div: its length is a property, so there is
 * no inline `style` for the content security policy to refuse, no class per
 * percentage point, and — the part that matters — the server can draw the
 * opening example without any script running at all.
 */
.price-steps {
    list-style: none;
    padding: 0;
    display: grid;
    gap: var(--sp-2);
}

/*
 * The browser's own margin on an `ol`, dropped at zero specificity so that the
 * `.stack` around it still decides the space above it. Written flat it won the
 * cascade against `.stack > * + *` — same specificity, later in the sheet — and
 * the first bar sat against the bottom edge of the price tag.
 */
:where(.price-steps) {
    margin: 0;
}

.price-steps__step {
    display: grid;
    grid-template-columns: minmax(7rem, 12rem) 1fr auto;
    align-items: center;
    gap: var(--sp-3);
}

.price-steps__label {
    font-size: var(--fs-sm);
    color: var(--c-text-muted);
}

.price-steps__figure {
    font-variant-numeric: tabular-nums;
}

.price-steps__bar {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    height: 0.65rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--c-bg-sunken);
    color: var(--c-cat);
}

.price-steps__bar::-webkit-progress-bar {
    background: var(--c-bg-sunken);
    border-radius: var(--radius-sm);
}

.price-steps__bar::-webkit-progress-value {
    background: var(--c-cat);
    border-radius: var(--radius-sm);
}

.price-steps__bar::-moz-progress-bar {
    background: var(--c-cat);
    border-radius: var(--radius-sm);
}

/* The one that is paid, told apart from the two that are not. */
.price-steps__bar[data-bar="final_price"] {
    color: var(--c-ink);
}

.price-steps__bar[data-bar="final_price"]::-webkit-progress-value {
    background: var(--c-ink);
}

.price-steps__bar[data-bar="final_price"]::-moz-progress-bar {
    background: var(--c-ink);
}

@media (max-width: 34rem) {
    .price-steps__step {
        grid-template-columns: 1fr auto;
    }

    .price-steps__bar {
        grid-column: 1 / -1;
        grid-row: 2;
    }
}

/*
 * A percentage, with something to drag it by.
 *
 * The presets are the reductions a window actually shows and the slider is the
 * rest of the range; both are hidden in the markup and revealed by the module,
 * because neither does anything without it. The field beside them is the one
 * that was always there and is still the only value anything is read from.
 */
.dial__presets {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-bottom: var(--sp-2);
}

.dial__preset {
    min-height: 44px;
    padding: var(--sp-2) var(--sp-4);
    background: var(--c-surface);
    color: var(--c-text);
    border: 1px solid var(--c-border-strong);
    border-radius: 999px;
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

.dial__preset:hover {
    border-color: var(--c-ink);
}

.dial__preset[aria-pressed="true"] {
    background: var(--c-ink);
    border-color: var(--c-ink);
    color: var(--c-ink-contrast);
}

.dial__row {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
}

.dial__range {
    flex: 1 1 auto;
    min-width: 0;
    accent-color: var(--c-ink);
}

/* Narrow on purpose: it is the exact value, not the way in. */
.dial__field {
    flex: 0 0 6rem;
}

.dial-more {
    border-top: 1px solid var(--c-border);
    padding-top: var(--sp-3);
}

/*
 * The summary already says which discount this is, and the field's own label
 * would say it a second time two lines below. It stays in the markup — it is
 * what names the field to a screen reader and what the slider points at — and
 * only stops being drawn.
 */
.dial-more .dial > .field__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.dial-more__summary {
    cursor: pointer;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--c-text-muted);
}

.dial-more[open] > .dial-more__summary {
    margin-bottom: var(--sp-3);
}

/* ------------------------------------------------------------- the bill */

/*
 * A bill, a tip and a total are a receipt, so they are laid out as one.
 *
 * The bar underneath is one element reading two ways: what is filled is the
 * bill, what is left is the tip. Two stacked bars would have been two lengths
 * to keep in step and a rounding error between them; this way the remainder is
 * the tip by construction. The squares in front of the two labels are its
 * legend, which is why there is no sentence explaining it.
 */
.bill {
    padding: var(--sp-3) var(--sp-5) var(--sp-4);
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
}

.bill__line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
    margin: 0;
    padding-block: var(--sp-2);
}

.bill__label {
    color: var(--c-text-muted);
    font-size: var(--fs-sm);
}

.bill__label--bill::before,
.bill__label--tip::before {
    content: "";
    display: inline-block;
    width: 0.6rem;
    height: 0.6rem;
    margin-inline-end: var(--sp-2);
    border-radius: 2px;
}

.bill__label--bill::before {
    background: var(--c-ink);
}

.bill__label--tip::before {
    background: var(--c-cat);
}

/* The rate is a figure about the line, not a second amount on it. */
.bill__rate {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--c-text-faint);
    font-variant-numeric: tabular-nums;
}

.bill__amount {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.bill__line--total {
    margin-top: var(--sp-1);
    border-top: 1px solid var(--c-border-strong);
    font-weight: 700;
}

.bill__line--total .bill__label {
    color: var(--c-text);
    font-size: var(--fs-base);
}

.bill__line--total .bill__amount {
    font-size: var(--fs-md);
}

.bill__bar {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    height: 0.5rem;
    margin-top: var(--sp-3);
    border: 0;
    border-radius: var(--radius-sm);
    /* The part no value covers, which is the tip. */
    background: var(--c-cat);
    color: var(--c-ink);
}

.bill__bar::-webkit-progress-bar {
    background: var(--c-cat);
    border-radius: var(--radius-sm);
}

.bill__bar::-webkit-progress-value {
    background: var(--c-ink);
    border-radius: var(--radius-sm);
}

.bill__bar::-moz-progress-bar {
    background: var(--c-ink);
    border-radius: var(--radius-sm);
}

/*
 * A count, and a thumb-sized way to change it by one.
 *
 * The buttons are 44px because that is what a thumb needs and because the
 * spinner they stand in for is about a third of it. They are decoration over
 * the field between them, which keeps the typing, the arrow keys and the
 * bounds.
 */
.stepper__row {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

.stepper__step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    min-width: 44px;
    height: 44px;
    background: var(--c-surface);
    color: var(--c-text);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius);
    font: inherit;
    font-size: var(--fs-md);
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}

.stepper__step:hover:not(:disabled) {
    border-color: var(--c-ink);
}

.stepper__step:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.stepper__field {
    flex: 0 0 5rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------- markdown preview */

/*
 * The preview pane. It holds HTML the renderer built from a document, so it is
 * scoped to a class rather than styled loosely: nothing in it should be able to
 * reach the page around it.
 */
.markdown-preview {
    min-height: 12rem;
    max-height: 40rem;
    padding: var(--sp-3);
    overflow: auto;
    background: var(--c-bg-sunken);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
}

.markdown-preview > :first-child {
    margin-top: 0;
}

.markdown-preview > :last-child {
    margin-bottom: 0;
}

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

.markdown-preview th,
.markdown-preview td {
    padding: var(--sp-1) var(--sp-2);
    border: 1px solid var(--c-border);
    text-align: left;
}

/* Column alignment, as classes: an inline style attribute is blocked by the
   content security policy. */
.markdown-preview .md-left {
    text-align: left;
}

.markdown-preview .md-center {
    text-align: center;
}

.markdown-preview .md-right {
    text-align: right;
}

.markdown-preview input[type="checkbox"] {
    margin-right: var(--sp-1);
}

/* ------------------------------------------------------------------ print */

/* What a tool page is on paper.
 *
 * Somebody printing an amortisation schedule or a charge split wants the
 * figures and the table, not the navigation, the advertisement, the search
 * box or the twelve links to other tools. None of that survives a print, and
 * printing was the one way to get a long table out of the page before the CSV
 * button existed — it is still the way to get one onto a desk.
 *
 * Colours are dropped to plain black on white: a result panel's tinted
 * background costs a visible amount of toner and reads worse than nothing on
 * a monochrome printer. */
@media print {
    :root {
        --c-bg: #fff;
        --c-surface: #fff;
        --c-text: #000;
        --c-text-muted: #333;
        --c-border: #999;
        --c-accent: #000;
    }

    body {
        background: #fff;
        color: #000;
    }

    .site-header,
    .site-footer,
    .drawer,
    .ad-slot,
    .tabs,
    .breadcrumb,
    .copy-value,
    [data-share],
    [data-export-button],
    .tool-panel form,
    .related-tools {
        display: none !important;
    }

    /* The panel keeps its figures and loses its frame. */
    .tool-panel,
    .result-panel {
        border: 0;
        padding: 0;
        background: none;
        box-shadow: none;
    }

    /* A table that scrolls sideways on a phone has nowhere to scroll on
       paper, so it is allowed its full width and its rows are kept whole. */
    .table-wrap {
        overflow: visible;
    }

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

    tr,
    .result-panel {
        break-inside: avoid;
    }

    thead {
        display: table-header-group;
    }

    /* The address of the page, once, at the end: a printed sheet that has
       been carried to a meeting should say where it came from. */
    .tool-header::after {
        content: attr(data-print-source);
        display: block;
        margin-top: var(--sp-1);
        font-size: 0.75rem;
        color: #333;
    }
}
