/*
 * Chrome shared by every page: the palette, the fonts, the aurora the page sits on, the
 * header, the footer, and the blocks that appear on more than one page — the tool grid,
 * the generator frame, prose, the error pages. Anything a single generator alone needs
 * stays inline in that page.
 */

/*
 * The display and UI faces, self-hosted so the site needs no third party at all.
 *
 * Both were changed for one reason: neither of the faces that were here covers
 * Vietnamese. Titan One has no o-horn or u-horn and no tone marks at all; Fredoka is
 * missing those plus a-breve and d-stroke. A name like "Cơm gà" was therefore being set
 * letter by letter out of two different fonts — three glyphs from the webfont, two from
 * whatever the device fell back to — which looks exactly as broken as it sounds.
 *
 * Baloo 2 and Quicksand carry the full Vietnamese block, and both are subset here to
 * Latin, Vietnamese, punctuation, arrows and the card suits: 683KB of Baloo 2 becomes
 * 56KB, and the pair together are lighter than the two faces they replace.
 *
 * Baloo 2 is declared at one weight on purpose. It is a variable font, and pinning the
 * face to 700 means every rule that asks for the display family gets the heavy cut
 * without each of them having to say so — which is how the old single-weight face
 * behaved, so nothing else in this stylesheet had to change.
 */
@font-face {
    font-family: 'Baloo 2';
    src: url('/fonts/Baloo2.woff2') format('woff2');
    font-weight: 700;
    font-display: swap;
}

@font-face {
    font-family: 'Quicksand';
    src: url('/fonts/Quicksand.woff2') format('woff2');
    font-weight: 300 700;
    font-display: swap;
}

/*
 * Daylight: white paper on a soft blue wash. Chance is the subject here, so the chrome
 * stays out of the way and the colour is spent where it means something: the result of a
 * spin, the slices of the wheel, the face of a die.
 */
:root {
    /* The page under everything. --ink is the darkest thing on it, not the background. */
    --paper: #FFFFFF;
    --wash: #F5F8FF;
    --wash-2: #EDF2FF;
    --wash-3: #E4ECFF;
    --ink: #131C42;

    --violet: #7C3AED;
    --indigo: #3B5BFF;
    --cyan: #16B6CF;
    --mint: #10B981;
    --amber: #F59E0B;
    --rose: #F43F5E;

    --text: #131C42;
    --muted: #55618B;
    --faint: #8790B4;
    /* Links and quiet hover states need a hue that holds up against white. */
    --link: #2B4CE0;

    --surface: #FFFFFF;
    --surface-hi: #F2F6FF;
    --line: #E3E9F7;
    --line-hi: #CBD6EE;
    --shadow: 0 10px 30px rgba(26, 42, 105, 0.10);
    /* The frosted surfaces — the stage, the masthead, the menu that drops out of it.
       Translucent on purpose, so the page behind carries through them. */
    --glass: rgba(255, 255, 255, 0.55);
    --glass-solid: rgba(255, 255, 255, 0.86);
    --glass-line: rgba(255, 255, 255, 0.8);
    /* The graph-paper grid: ink on white, and its opposite on a dark ground. */
    --grid-line: rgba(19, 28, 66, 0.035);
    /* The tinted band the app strip and the closing call to action stand on. Named
       rather than written twice, so a change of theme reaches both of them. */
    --band: linear-gradient(150deg, #F3EEFF, #E9F6FF 55%, #EEF3FF);
    /* No veil in daylight. */
    --veil: linear-gradient(transparent, transparent);
    /* Pale blooms over a white-to-blue ramp: the site's own backdrop, and what a page
       without a picture of its own falls back to. */
    --wash-layers:
            radial-gradient(58rem 40rem at 12% -6%, rgba(124, 58, 237, 0.14), transparent 62%),
            radial-gradient(52rem 38rem at 92% 4%, rgba(22, 182, 207, 0.13), transparent 60%),
            radial-gradient(60rem 44rem at 78% 96%, rgba(59, 91, 255, 0.12), transparent 62%),
            linear-gradient(180deg, var(--wash-2) 0%, var(--wash) 42%, var(--paper) 100%);

    --display: 'Baloo 2', 'Quicksand', system-ui, sans-serif;
    /* Baloo 2 sits in the body stack only to lend it the arrow: Quicksand has no U+2192,
       and a history line reading "2d6 → 8" should not borrow that one glyph from whatever
       the operating system happens to offer. */
    --body: 'Quicksand', 'Baloo 2', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
    --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    --radius: 20px;

    /* Each generator page overrides this one line; everything accented follows it. */
    --accent: var(--indigo);
}

/*
 * ---------- the dark palette ----------
 *
 * Only the colours are restated. Everything on this site draws from the variables above,
 * so redefining them is most of the work; what is left is the handful of places that name
 * white outright, and those are dealt with where they are.
 *
 * Three states, not two. A reader who has chosen nothing gets what their system asks for;
 * a reader who has chosen gets what they chose, in either direction — which is why the
 * media query is written as "unless light was asked for" rather than on its own. Without
 * that, choosing light on a machine set to dark would do nothing.
 *
 * The dark ground is not black. #10141F is a very dark blue that keeps the site's hue, and
 * pure black under a white-text page is the combination that smears on OLED panels.
 */
:root[data-theme="dark"],
:root:not([data-theme="light"]) {
    color-scheme: light;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        --paper: #10141F;
        --wash: #151A29;
        --wash-2: #1A2033;
        --wash-3: #212942;
        --ink: #E8ECFA;

        --text: #E8ECFA;
        --muted: #A2ADD0;
        --faint: #737EA4;
        --link: #8AA2FF;

        --surface: #171D2E;
        --surface-hi: #1E2539;
        --line: #2A3350;
        --line-hi: #3A456A;
        --shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
        --glass: rgba(23, 29, 46, 0.62);
        --glass-solid: rgba(16, 20, 31, 0.88);
        --glass-line: rgba(255, 255, 255, 0.10);
        --grid-line: rgba(255, 255, 255, 0.045);
        /*
         * A flat dark wash laid over the backdrop.
         *
         * The eleven pictures are pale pastels — lovely at midday, blinding at midnight — and it
         * is the same file in both themes, so the page is what has to dim them. Stacked as a
         * layer rather than applied as a filter: a filter on this element would also dim the wash
         * on the pages that have no picture, and that wash is already dark.
         */
        --veil: linear-gradient(rgba(16, 20, 31, 0.74), rgba(16, 20, 31, 0.74));
        --band: linear-gradient(150deg, #1D2440, #172231 55%, #1B2238);
    }
}

:root[data-theme="dark"] {
    color-scheme: dark;

    --paper: #10141F;
    --wash: #151A29;
    --wash-2: #1A2033;
    --wash-3: #212942;
    --ink: #E8ECFA;

    --text: #E8ECFA;
    --muted: #A2ADD0;
    --faint: #737EA4;
    --link: #8AA2FF;

    --surface: #171D2E;
    --surface-hi: #1E2539;
    --line: #2A3350;
    --line-hi: #3A456A;
    --shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
    --glass: rgba(23, 29, 46, 0.62);
    --glass-solid: rgba(16, 20, 31, 0.88);
    --glass-line: rgba(255, 255, 255, 0.10);
    --grid-line: rgba(255, 255, 255, 0.045);
    /*
     * A flat dark wash laid over the backdrop.
     *
     * The eleven pictures are pale pastels — lovely at midday, blinding at midnight — and it
     * is the same file in both themes, so the page is what has to dim them. Stacked as a
     * layer rather than applied as a filter: a filter on this element would also dim the wash
     * on the pages that have no picture, and that wash is already dark.
     */
    --veil: linear-gradient(rgba(16, 20, 31, 0.74), rgba(16, 20, 31, 0.74));
    --band: linear-gradient(150deg, #1D2440, #172231 55%, #1B2238);
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
    /* The header is sticky, so anchored sections must clear it rather than hide under it. */
    scroll-padding-top: 6rem;
}

body {
    font-family: var(--body);
    background: var(--paper);
    color: var(--text);
    line-height: 1.7;
    -webkit-font-smoothing: antialiased;
}

/*
 * The wash: pale blooms in the site's hues over a white-to-blue ramp. It is fixed and
 * behind everything, so scrolling moves the content across a steady light rather than
 * dragging a gradient along with it. Kept faint on purpose — the page is white, and the
 * colour on it should be a result, not the wallpaper.
 */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -2;
    /*
     * A page may hang its own picture here by setting --page-backdrop; the wash is what
     * shows when it does not, and the veil above it is what makes a pale picture bearable
     * after dark. One layer, one rule: the eleven pages with a picture used to restate all
     * of this each, which would have meant restating the veil eleven times as well.
     */
    background-image: var(--veil), var(--page-backdrop, var(--wash-layers));
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

/* A faint grid gives the flat background a scale, the way graph paper gives a plot one. */
body::after {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: 0.7;
    background-image:
            linear-gradient(var(--grid-line) 1px, transparent 1px),
            linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
    background-size: 64px 64px;
    mask-image: radial-gradient(80rem 60rem at 50% 0%, #000 20%, transparent 80%);
}

.wrap {
    max-width: 1180px;
    margin: 0 auto;
    padding: 0 1.5rem;
}

h1, h2, h3 {
    font-family: var(--display);
    font-weight: 400;
    line-height: 1.18;
    letter-spacing: 0.2px;
}

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

section {
    padding: 4.5rem 0;
}

img, svg {
    max-width: 100%;
}

/* Everything the keyboard can reach shows the same ring, on any surface. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 6px;
}

/*
 * The hidden attribute has to beat any display a class sets, or it does nothing at all.
 * A rule like `.lang-menu { display: grid }` outranks the browser's own `[hidden]`, so the
 * menu that script had just marked hidden stayed on the screen. This is the one line that
 * stops that happening to every panel, tray and menu on the site.
 */
[hidden] {
    display: none !important;
}

/* Content that is for a screen reader and nothing else. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---------- shared blocks ---------- */

.section-head {
    max-width: 46rem;
    margin-bottom: 2.8rem;
}

.section-head h2 {
    font-size: clamp(1.8rem, 3.6vw, 2.6rem);
    margin-bottom: 0.8rem;
}

.section-head p {
    color: var(--muted);
    font-size: 1.05rem;
}

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.6rem;
    box-shadow: var(--shadow);
    backdrop-filter: blur(8px);
}

.card h3 {
    font-size: 1.12rem;
    margin-bottom: 0.55rem;
}

.card p {
    color: var(--muted);
    font-size: 0.97rem;
}

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    font-family: var(--display);
    font-size: 0.95rem;
    text-decoration: none;
    padding: 0.75rem 1.5rem;
    border-radius: 999px;
    border: 1px solid transparent;
    cursor: pointer;
    /* Some buttons carry an address or a long result, wider than a small phone. Without
       this the page gains a horizontal scrollbar because of one of them. */
    max-width: 100%;
    text-align: center;
    transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.btn:hover {
    transform: translateY(-2px);
}

.btn-primary {
    color: #FFFFFF;
    background: linear-gradient(135deg, var(--violet), var(--indigo) 55%, var(--cyan));
    box-shadow: 0 8px 20px rgba(59, 91, 255, 0.30);
}

.btn-primary:hover {
    box-shadow: 0 12px 26px rgba(59, 91, 255, 0.42);
}

.btn-ghost {
    color: var(--text);
    background: var(--paper);
    border-color: var(--line-hi);
    box-shadow: 0 2px 6px rgba(26, 42, 105, 0.06);
}

.btn-ghost:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.btn[disabled],
.btn[aria-disabled="true"] {
    opacity: 0.45;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

.btn .icon {
    width: 18px;
    height: 18px;
}

/* ---------- the tool grid ---------- */
/*
 * One card per generator, used on the landing page and again at the foot of every
 * generator page. Each card carries its own --accent, which is the only colour that
 * changes between them: the layout is identical so the grid reads as one set of tools
 * rather than a dozen unrelated pages.
 */
.tools {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr));
    gap: 1.1rem;
}

.tool-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 1.4rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    backdrop-filter: blur(8px);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}

/* A wash of the tool's own hue, so the card is recognisable before the name is read. */
.tool-card::before {
    content: '';
    position: absolute;
    inset: -45% 45% 55% -25%;
    background: radial-gradient(closest-side, var(--accent), transparent);
    opacity: 0.13;
    pointer-events: none;
    transition: opacity 0.25s ease;
}

.tool-card:hover {
    transform: translateY(-4px) scale(1.015);
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
    box-shadow: 0 18px 38px rgba(26, 42, 105, 0.16);
}

.tool-card:hover::before {
    opacity: 0.22;
}

/*
 * The picture, not a symbol of one. A card in a grid of twelve is chosen at a glance and
 * from a distance, and a 24px glyph of a wheel and a 24px glyph of a coin are the same
 * small grey circle at that distance. Each picture sits on a wash of its own tool's hue,
 * so the colour identifies the card even before the drawing resolves.
 */
.tool-art {
    position: relative;
    display: block;
    margin-bottom: 0.3rem;
    padding: 0.5rem 0.7rem;
    border-radius: 16px;
    background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 14%, var(--surface)), var(--surface) 85%);
    border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--line));
    transition: transform 0.25s ease;
}

.tool-art svg {
    display: block;
    width: 100%;
    height: auto;
}

/* Pointing at a card brings its picture forward — the same beat as picking a result. */
.tool-card:hover .tool-art {
    transform: scale(1.04);
}

.tool-card h3 {
    position: relative;
    font-size: 1.1rem;
}

.tool-card p {
    position: relative;
    color: var(--muted);
    font-size: 0.94rem;
    flex: 1;
}

.tool-card .go {
    position: relative;
    font-size: 0.88rem;
    color: var(--accent);
    font-family: var(--display);
}

/* The card for the tool you are already on, shown flat so it is not offered as a link. */
.tool-card[aria-current="page"] {
    border-color: var(--accent);
    cursor: default;
}

.tool-card[aria-current="page"]:hover {
    transform: none;
}

/*
 * The same set again at the foot of a generator page, as text chips rather than cards.
 * Twelve illustrated cards under a tool would compete with the tool; twelve names in a
 * row is a way out of a dead end, which is all this needs to be.
 */
.tool-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.tool-chips a {
    font-size: 0.92rem;
    text-decoration: none;
    color: var(--muted);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.4rem 1rem;
    transition: color 0.2s, border-color 0.2s, background 0.2s;
}

.tool-chips a:hover {
    color: var(--text);
    border-color: var(--line-hi);
    background: var(--surface-hi);
}

/* The chip for the page you are already on stays, flat, so the row never changes shape. */
.tool-chips a[aria-current="page"] {
    color: var(--text);
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 22%, transparent);
    cursor: default;
}

/* ---------- generator pages ---------- */

.tool-hero {
    padding: 2.6rem 0 1.6rem;
}

.crumbs {
    font-size: 0.85rem;
    color: var(--faint);
    margin-bottom: 0.9rem;
}

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

.crumbs a:hover {
    color: var(--link);
}

.crumbs span {
    margin: 0 0.45rem;
}

.tool-hero h1 {
    font-size: clamp(2rem, 5vw, 3rem);
    margin-bottom: 0.7rem;
}

.tool-hero h1 em {
    font-style: normal;
    background: linear-gradient(120deg, var(--accent), var(--cyan));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.tool-hero .lede {
    color: var(--muted);
    font-size: 1.06rem;
    max-width: 42rem;
}

/*
 * Stage on the left, controls on the right. The stage is where the result lands, so on a
 * phone it comes first in the source and stays first in the flow: the answer should never
 * be below the fold of the button that produced it.
 */
.tool-layout {
    display: grid;
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
    gap: 1.4rem;
    align-items: start;
    padding-bottom: 1.5rem;
}

.stage,
.panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 24px;
    box-shadow: var(--shadow);
    backdrop-filter: blur(10px);
}

/*
 * The stage is glass, not a white card and not nothing.
 *
 * Ten of these pages have a picture behind them now, and the first attempt gave each one a
 * transparent stage — which left a hairline border drawn around thin air, the worst of both
 * answers: no surface to hold the content and still a box around it. An opaque card is the
 * other wrong answer, punching a white hole through the middle of the very picture it is
 * standing on.
 *
 * Frosting is what settles it. The picture carries on through, blurred enough that a result
 * in large type stays readable over it, and the panel keeps its edges. On the pages with no
 * picture the wash behind is pale, so the same treatment reads as a soft white card and
 * nothing is lost by having one rule instead of two.
 */
.stage {
    position: relative;
    background: var(--glass);
    border-color: var(--glass-line);
    box-shadow: 0 18px 44px rgba(26, 42, 105, 0.12);
    backdrop-filter: blur(16px) saturate(1.15);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.2rem;
    min-height: 24rem;
    padding: 2rem 1.5rem;
    overflow: hidden;
    text-align: center;
}

/* The stage carries the tool's hue as a soft floor light under whatever lands on it. */
.stage::before {
    content: '';
    position: absolute;
    inset: auto -20% -45% -20%;
    height: 70%;
    background: radial-gradient(closest-side, var(--accent), transparent);
    opacity: 0.12;
    pointer-events: none;
}

.stage > * {
    position: relative;
}

/*
 * The result itself. It is deliberately the largest thing on the page — someone holding a
 * phone up so a room can see who won should not have to point at it.
 */
.result {
    /* Full width on purpose: see fitOneLine in random.js — the answer is measured against
       the room it has, and a shrink-to-fit box has no answer to give. */
    width: 100%;
    font-family: var(--display);
    font-size: clamp(2.4rem, 11vw, 5rem);
    line-height: 1.25;
    letter-spacing: 0.01em;
    overflow-wrap: anywhere;
    background: linear-gradient(135deg, var(--ink) 5%, var(--accent) 115%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    filter: drop-shadow(0 10px 22px color-mix(in srgb, var(--accent) 26%, transparent));
}

/*
 * A picture character has to be let out of the gradient above.
 *
 * background-clip: text makes the text a mask, and a colour emoji used as a mask is a
 * silhouette — which is why a fish came out as a black fish-shaped hole until it was
 * selected and the browser painted the real glyph over the top.
 */
.result .glyph,
.result.has-glyph,
.pop-text.has-glyph {
    -webkit-text-fill-color: initial;
    color: var(--ink);
    background: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
}

/* A long name needs to stay readable more than it needs to stay enormous. */
.result.is-long {
    font-size: clamp(1.6rem, 6vw, 3rem);
}

.result.is-idle {
    background: none;
    color: var(--faint);
    font-size: clamp(1.1rem, 3.4vw, 1.5rem);
    filter: none;
}

.result-note {
    color: var(--muted);
    font-size: 0.95rem;
}

/* A run of results — several numbers, several dice, a hand of cards. */
.result-set {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    width: 100%;
}

.pill {
    font-family: var(--display);
    font-size: 1.25rem;
    padding: 0.45rem 1.05rem;
    border-radius: 999px;
    background: var(--surface-hi);
    border: 1px solid var(--line-hi);
    color: var(--text);
}

.pill.is-hero {
    font-size: 1.6rem;
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* An ordered result set, where the position is part of the answer. */
.ranked {
    list-style: none;
    width: 100%;
    max-width: 30rem;
    display: grid;
    gap: 0.5rem;
    text-align: left;
}

.ranked li {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.6rem 1rem;
    background: var(--surface-hi);
    border: 1px solid var(--line);
    border-radius: 14px;
    overflow-wrap: anywhere;
}

.ranked li b {
    flex: none;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    font-family: var(--display);
    font-weight: 400;
    font-size: 0.9rem;
    background: color-mix(in srgb, var(--accent) 30%, transparent);
    border: 1px solid var(--line-hi);
}

.stage-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.7rem;
}

/* ---------- the control panel ---------- */

.panel {
    padding: 1.5rem;
}

.panel h2 {
    font-size: 1.05rem;
    margin-bottom: 1.2rem;
}

.panel h2 + .field {
    margin-top: 0;
}

.field {
    margin-bottom: 1.1rem;
}

.field > label,
.field > .field-label {
    display: block;
    font-size: 0.8rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--faint);
    margin-bottom: 0.45rem;
}

.field .hint {
    display: block;
    font-size: 0.85rem;
    color: var(--faint);
    margin-top: 0.4rem;
}

input[type="number"],
input[type="text"],
input[type="date"],
input[type="time"],
select,
textarea {
    width: 100%;
    font-family: var(--body);
    font-size: 1rem;
    color: var(--text);
    background: var(--paper);
    border: 1px solid var(--line-hi);
    border-radius: 12px;
    padding: 0.65rem 0.85rem;
    box-shadow: inset 0 1px 2px rgba(26, 42, 105, 0.05);
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

input[type="number"]:focus,
input[type="text"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
select:focus,
textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}

/*
 * Dates and times.
 *
 * They were left out of the rule above and so came out as the browser draws them by
 * default — a thin grey box beside fields that are rounded and padded. They take the same
 * shape as everything else now.
 *
 * `appearance` is deliberately left alone: setting it to none strips the picker button off
 * the control in WebKit, and a date field you cannot open a calendar from is worse looking
 * than one that does not match.
 */
input[type="date"],
input[type="time"] {
    /* Matched to the height a number field comes out at from its own padding — measured,
       not guessed: 2.85rem left the date fields three pixels taller than the row below. */
    min-height: 2.7rem;
}

/* The picker button: sized and tinted to sit with the text rather than against it. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator {
    width: 1.05rem;
    height: 1.05rem;
    padding: 0.15rem;
    border-radius: 6px;
    cursor: pointer;
    opacity: 0.55;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}

input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="time"]:hover::-webkit-calendar-picker-indicator {
    opacity: 1;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
}

/* The digits WebKit lays out inside the field, so they sit on the same line as the rest. */
input[type="date"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit {
    padding: 0;
}

input[type="date"]::-webkit-datetime-edit-fields-wrapper,
input[type="time"]::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
}

textarea {
    min-height: 11rem;
    line-height: 1.6;
    resize: vertical;
}

select {
    appearance: none;
    /* The caret is drawn rather than shipped, so it follows the text colour in both a
       light and a dark rendering of the form controls. */
    background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                      linear-gradient(135deg, var(--muted) 50%, transparent 50%);
    background-position: calc(100% - 18px) 55%, calc(100% - 12px) 55%;
    background-size: 6px 6px, 6px 6px;
    background-repeat: no-repeat;
    padding-right: 2.2rem;
}

.field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.8rem;
}

/* A switch row: the label carries the text, the input sits at the end of the line. */
.check {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.5rem 0;
    color: var(--muted);
    font-size: 0.95rem;
    cursor: pointer;
}

.check input {
    flex: none;
    width: 18px;
    height: 18px;
    accent-color: var(--accent);
    cursor: pointer;
}

/* A row of mutually exclusive choices, drawn as pills rather than radios. */
.choices {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
}

.choices input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.choices label {
    font-size: 0.92rem;
    padding: 0.4rem 0.95rem;
    border-radius: 999px;
    background: var(--paper);
    border: 1px solid var(--line-hi);
    color: var(--muted);
    cursor: pointer;
    transition: border-color 0.2s, color 0.2s, background 0.2s;
}

.choices label:hover {
    color: var(--text);
}

.choices input:checked + label {
    color: var(--text);
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 26%, transparent);
}

.choices input:focus-visible + label {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.panel-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-top: 1.3rem;
}

.panel-actions .btn {
    flex: 1 1 auto;
}

/* A quiet inline button for "shuffle the list", "clear", "load an example". */
.link-btn {
    font-family: var(--body);
    font-size: 0.88rem;
    color: var(--muted);
    background: none;
    border: 0;
    padding: 0.2rem 0;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.link-btn:hover {
    color: var(--link);
}

.mini-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.9rem;
    margin-top: 0.5rem;
}

/* Something the person needs to fix before the tool can do anything. */
.warn {
    display: none;
    font-size: 0.9rem;
    color: #9A5B00;
    background: #FFF6E6;
    border: 1px solid #FFD79A;
    border-radius: 12px;
    padding: 0.6rem 0.85rem;
    margin-top: 0.9rem;
}

.warn.is-shown {
    display: block;
}

/* ---------- history ---------- */

.history {
    margin-top: 1.4rem;
    border-top: 1px solid var(--line);
    padding-top: 1.1rem;
}

.history-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.8rem;
    margin-bottom: 0.6rem;
}

.history-head h3 {
    font-family: var(--body);
    font-weight: 600;
    font-size: 0.8rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--faint);
}

.history ol {
    list-style: none;
    display: grid;
    gap: 0.35rem;
    max-height: 13rem;
    overflow-y: auto;
}

.history li {
    font-size: 0.92rem;
    color: var(--muted);
    padding: 0.35rem 0.65rem;
    background: var(--wash-2);
    border-radius: 9px;
    overflow-wrap: anywhere;
}

.history li:first-child {
    color: var(--text);
}

.history .empty {
    font-size: 0.9rem;
    color: var(--faint);
}

/* ---------- notes under a generator ---------- */

.tool-notes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 1.1rem;
    padding: 1rem 0 0;
}

.note {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 18px;
    padding: 1.3rem;
}

.note h3 {
    font-size: 1rem;
    margin-bottom: 0.5rem;
}

.note p {
    color: var(--muted);
    font-size: 0.94rem;
}

.note p + p {
    margin-top: 0.7rem;
}

/* ---------- header ---------- */
header {
    position: sticky;
    top: 0;
    z-index: 100;
    background: var(--glass-solid);
    backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--line);
}

/*
 * The bar sits on an element that is also a .wrap, so only the vertical padding is set
 * here. The `padding` shorthand would reset the horizontal padding .wrap provides and
 * leave the logo and the menu button flush against the edge of the screen.
 */
.header-content {
    display: flex;
    align-items: center;
    /*
     * The logo holds the left; the navigation, the language and the day/night switch gather
     * on the right as one group. space-between used to push them apart from each other, so
     * the three of them straggled across the bar with gaps of their own making.
     */
    justify-content: flex-end;
    gap: 1rem;
    padding-top: 0.8rem;
    padding-bottom: 0.8rem;
}

.logo {
    display: flex;
    align-items: center;
    /*
     * The gap and the word space are set together on purpose: the mark, "Random" and
     * "Toolkit" are three objects in a row, and the eye reads them as one name only if the
     * space inside the name is not larger than the space beside it.
     */
    /*
     * No gap: this row holds three children, and a gap would separate the two words of the
     * name by the same amount as it separates the name from the mark. The mark carries the
     * outer space itself, and .logo span carries the inner one.
     */
    gap: 0;
    font-family: var(--display);
    font-size: 1.32rem;
    color: var(--text);
    text-decoration: none;
    white-space: nowrap;
}

.logo img {
    width: 36px;
    height: 36px;
    margin-right: 0.5rem;
}

/*
 * The wordmark.
 *
 * Flat indigo on the second word, and nothing else. The gradient that was here read as
 * three colours across seven letters, sitting next to a logo mark that already carries six
 * — two busy things beside each other rather than a name and a mark.
 *
 * Indigo rather than var(--accent): the accent is set per page, so the wordmark would have
 * changed colour on the way from the wheel to the dice. A name does not do that.
 */
.logo b {
    font-weight: 700;
    letter-spacing: -0.035em;
    color: var(--text);
}

.logo span {
    font-weight: 700;
    letter-spacing: -0.035em;
    /* The gap inside the name, set here rather than left to the font's own space glyph. */
    margin-left: 0.2em;
    color: #3B5BFF;
}

/* Indigo on ink is too close in the dark; the same hue, lifted. */
:root[data-theme="dark"] .logo span { color: #8AA2FF; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .logo span { color: #8AA2FF; }
}

/*
 * Scoped to the header on purpose. These rules collapse the bar into a sheet on a phone,
 * and an unscoped `nav` would take any other navigation on the page down with it.
 */
header nav {
    display: flex;
    align-items: center;
    gap: 1.5rem;
}

/* What pushes everything else to the right. */
.header-content .logo {
    margin-inline-end: auto;
}

header nav a {
    color: var(--muted);
    text-decoration: none;
    font-size: 0.97rem;
    transition: color 0.2s;
}

header nav a:hover,
header nav a:focus-visible,
header nav a[aria-current="page"] {
    color: var(--text);
}

/*
 * The categories menu.
 *
 * Positioned against .nav-drop on a desktop and against nothing at all on a phone, where
 * the whole nav is already a sheet and a floating panel inside a sheet would be a second
 * layer over the first.
 */
.nav-drop {
    position: relative;
}

.nav-drop-button {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    color: var(--muted);
    font: inherit;
    font-size: 0.97rem;
    transition: color 0.2s;
}

.nav-drop-button:hover,
.nav-drop-button:focus-visible,
.nav-drop-button[aria-expanded="true"] {
    color: var(--text);
}

.nav-drop-button .chev {
    width: 15px;
    height: 15px;
    transition: transform 0.2s ease;
}

.nav-drop-button[aria-expanded="true"] .chev {
    transform: rotate(180deg);
}

.nav-drop-menu {
    position: absolute;
    top: calc(100% + 0.85rem);
    inset-inline-start: 50%;
    transform: translateX(-50%);
    z-index: 60;
    display: grid;
    gap: 0.1rem;
    min-width: 15.5rem;
    padding: 0.45rem;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 15px;
    box-shadow: var(--shadow);
}

header nav .nav-drop-menu a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.2rem;
    padding: 0.55rem 0.7rem;
    border-radius: 10px;
    white-space: nowrap;
}

header nav .nav-drop-menu a:hover,
header nav .nav-drop-menu a:focus-visible {
    color: var(--text);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.nav-drop-menu i {
    font-style: normal;
    font-size: 0.8rem;
    color: var(--muted);
}

.nav-drop-all {
    margin-top: 0.2rem;
    padding-top: 0.6rem !important;
    border-top: 1px solid var(--line);
    border-radius: 0 0 10px 10px !important;
    font-weight: 600;
}

.nav-cta {
    font-family: var(--display);
    font-size: 0.88rem;
    color: #FFFFFF !important;
    background: linear-gradient(135deg, var(--violet), var(--indigo) 60%, var(--cyan));
    padding: 0.5rem 1.15rem;
    border-radius: 999px;
    white-space: nowrap;
    box-shadow: 0 6px 16px rgba(59, 91, 255, 0.32);
}

/* The toggle only exists on narrow screens; on desktop the links are always shown. */
.nav-toggle {
    display: none;
    width: 42px;
    height: 42px;
    align-items: center;
    justify-content: center;
    background: var(--paper);
    border: 1px solid var(--line-hi);
    border-radius: 12px;
    color: var(--text);
    cursor: pointer;
}

.nav-toggle svg {
    width: 20px;
    height: 20px;
}

.nav-toggle .close,
.nav-toggle[aria-expanded="true"] .open {
    display: none;
}

.nav-toggle[aria-expanded="true"] .close {
    display: block;
}

/* ---------- the language switcher ---------- */
/*
 * Twenty-seven languages is too many for a row of links and too few to need a search box,
 * so it is a menu. Each language is listed under its own name, because that is what
 * somebody scanning for their language is looking for.
 */
.lang {
    position: relative;
}

.lang-button {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-family: var(--body);
    font-size: 0.92rem;
    color: var(--muted);
    background: var(--paper);
    border: 1px solid var(--line-hi);
    border-radius: 999px;
    padding: 0.4rem 0.85rem;
    cursor: pointer;
    transition: color 0.2s, border-color 0.2s;
}

.lang-button:hover {
    color: var(--accent);
    border-color: var(--accent);
}

.lang-button svg {
    width: 17px;
    height: 17px;
    flex: none;
}

.lang-menu {
    position: absolute;
    right: 0;
    top: calc(100% + 0.5rem);
    z-index: 130;
    display: grid;
    gap: 2px;
    width: 14rem;
    max-height: min(62vh, 26rem);
    overflow-y: auto;
    padding: 0.4rem;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 16px;
    box-shadow: 0 18px 40px rgba(26, 42, 105, 0.16);
}

/*
 * The flag beside each language. A fixed box, so the names line up in a column whatever
 * shape the flag is, and the globe on the button steps aside once a flag is there.
 */
.lang-flag {
    flex: none;
    width: 21px;
    height: 14px;
    display: block;
}

.lang-flag-slot {
    display: inline-flex;
    align-items: center;
}

.lang-button .lang-flag-slot:not(:empty) {
    margin-right: 0.1rem;
}

.lang-button:has(.lang-flag-slot:not(:empty)) > svg {
    display: none;
}

.lang-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-family: var(--body);
    font-size: 0.93rem;
    text-align: left;
    padding: 0.45rem 0.7rem;
    border: 0;
    border-radius: 10px;
    background: none;
    color: var(--text);
    cursor: pointer;
}

.lang-item:hover {
    background: var(--wash-2);
}

.lang-item.is-current {
    background: color-mix(in srgb, var(--accent) 14%, var(--surface));
    color: var(--accent);
    font-weight: 600;
}

/* Right-to-left reading order for Arabic. */
[dir="rtl"] .lang-menu {
    right: auto;
    left: 0;
}

[dir="rtl"] .lang-item {
    text-align: right;
}

@media (max-width: 900px) {
    /* Inside the phone sheet the menu is part of the flow rather than floating over it. */
    .lang {
        margin-top: 0.8rem;
    }

    .lang-menu {
        position: static;
        width: 100%;
        max-height: 40vh;
        margin-top: 0.5rem;
    }
}

/* ---------- footer ---------- */
footer {
    margin-top: 2rem;
    background: var(--wash-2);
    backdrop-filter: blur(12px);
    border-top: 1px solid var(--line);
    padding: 3rem 0 2.4rem;
    color: var(--muted);
    font-size: 0.92rem;
}

.footer-grid {
    display: grid;
    grid-template-columns: 1.6fr 1fr 1fr 1.2fr;
    gap: 2rem;
    padding-bottom: 2.2rem;
    border-bottom: 1px solid var(--line);
}

.footer-brand .logo {
    margin-bottom: 0.9rem;
}

.footer-brand p {
    max-width: 24rem;
    color: var(--muted);
}

.footer-col h3 {
    font-family: var(--body);
    font-weight: 600;
    font-size: 0.8rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--faint);
    margin-bottom: 0.9rem;
}

.footer-col ul {
    list-style: none;
    display: grid;
    gap: 0.5rem;
}

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

.footer-col a:hover {
    color: var(--link);
}

/*
 * The address, with its envelope.
 *
 * It was centred against the whole address, which is right while that is one line and wrong
 * the moment the column is narrow enough to wrap it: the envelope then floats between the
 * two lines, level with neither. It belongs on the first line, so the alignment is to the
 * top and the icon is nudged down by the fraction that puts it on that line's own middle.
 *
 * break-all is gone with it. An address has nowhere to break, so it may break anywhere —
 * but only when it has to, which is what break-all never respected: it broke mid-address
 * even where there was room for the whole of it.
 */
.footer-mail {
    display: inline-flex;
    align-items: flex-start;
    gap: 0.5rem;
    color: var(--text);
    text-decoration: none;
    overflow-wrap: anywhere;
}

.footer-mail .icon {
    flex: none;
    margin-top: 0.22em;
}

.footer-mail:hover {
    color: var(--link);
}

/*
 * The row of sites we also run. A row rather than a fifth column: the grid above is
 * 1.6/1/1/1.2 and squeezing a fifth track in would take the support address back down to
 * the width that made it wrap in the first place.
 */
.footer-friends {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.5rem 1.4rem;
    padding-top: 1.4rem;
    font-size: 0.88rem;
}

.footer-friends > span {
    color: var(--faint);
}

.footer-friends ul {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1.4rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.footer-friends a {
    color: var(--muted);
    text-decoration: none;
    transition: color 0.2s;
}

.footer-friends a:hover,
.footer-friends a:focus-visible {
    color: var(--text);
    text-decoration: underline;
}

.footer-bottom {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
    padding-top: 1.6rem;
    color: var(--faint);
    font-size: 0.88rem;
}

/* ---------- sub-page chrome ---------- */
/*
 * The pages that are prose rather than a generator open the same way: a title, a line
 * under it, and then the text. Keeping that in one place is what makes About, Contact,
 * Privacy and Terms read as four pages of one site.
 */
.page-head {
    padding: 3.6rem 0 1rem;
    text-align: center;
}

.page-head h1 {
    font-size: clamp(2.2rem, 5.5vw, 3.2rem);
    margin: 0.9rem 0 0.7rem;
}

.page-head p {
    max-width: 40rem;
    margin: 0 auto;
    color: var(--muted);
    font-size: 1.08rem;
}

.prose {
    max-width: 48rem;
    margin: 0 auto 4rem;
    padding: clamp(1.5rem, 4vw, 2.8rem);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 24px;
    box-shadow: var(--shadow);
    backdrop-filter: blur(8px);
}

.prose h2 {
    font-size: 1.4rem;
    margin: 2.4rem 0 0.8rem;
}

.prose h2:first-child {
    margin-top: 0;
}

.prose h3 {
    font-family: var(--body);
    font-weight: 600;
    font-size: 1.04rem;
    margin: 1.6rem 0 0.5rem;
}

.prose p {
    color: var(--muted);
    margin-bottom: 1rem;
}

.prose ul, .prose ol {
    color: var(--muted);
    margin: 0 0 1.2rem 0;
    padding-left: 1.3rem;
}

.prose li {
    margin-bottom: 0.45rem;
}

.prose strong {
    color: var(--text);
    font-weight: 600;
}

.prose a {
    color: var(--link);
}

.prose code {
    font-family: var(--mono);
    font-size: 0.88em;
    color: #0E7490;
    background: #EAFBFF;
    border: 1px solid #B8E9F3;
    border-radius: 5px;
    padding: 0.05rem 0.35rem;
}

.updated {
    color: var(--faint);
    font-size: 0.9rem;
    margin-bottom: 2rem;
}

.summary {
    background: #ECFDF5;
    border: 1px solid #B7EBD5;
    border-radius: 16px;
    padding: 1.4rem 1.6rem;
    margin-bottom: 2.4rem;
}

.summary h2 {
    font-size: 1.05rem;
    color: #047857;
    margin: 0 0 0.7rem;
}

.summary ul {
    margin: 0;
    padding-left: 1.1rem;
}

.notice {
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 3px solid var(--amber);
    border-radius: 10px;
    padding: 1.1rem 1.3rem;
    margin-bottom: 1.4rem;
}

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

.callout {
    background: #EFF3FF;
    border: 1px solid #C7D3FB;
    border-radius: 16px;
    padding: 1.4rem 1.6rem;
    margin-top: 2.4rem;
}

.callout h2 {
    margin-top: 0;
    font-size: 1.15rem;
}

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

.table-shell {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 14px;
    overflow-x: auto;
    margin-bottom: 1.6rem;
}

.key-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.93rem;
}

.key-table th, .key-table td {
    text-align: left;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--line);
    vertical-align: top;
}

.key-table th {
    font-family: var(--display);
    font-weight: 400;
    color: var(--faint);
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.key-table td:first-child {
    color: var(--text);
    white-space: nowrap;
}

.key-table td:last-child {
    color: var(--muted);
}

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

/* ---------- error pages ---------- */
/*
 * Boot serves /static/error/<status>.html itself, so these are flat files like the rest of
 * the site rather than anything the app has to route. They share the chrome deliberately:
 * a visitor who hits a dead link should still be one click from the generators.
 */
.errorpage {
    max-width: 44rem;
    margin: 0 auto;
    padding: 4.5rem 0 5rem;
    text-align: center;
}

.errorcode {
    font-family: var(--display);
    font-size: clamp(5rem, 18vw, 9rem);
    line-height: 1.25;
    letter-spacing: 0.02em;
    background: linear-gradient(130deg, var(--violet), var(--indigo) 45%, var(--cyan));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* The gradient is the whole mark here, so it gets a glow rather than a flat fill. */
    filter: drop-shadow(0 10px 26px rgba(59, 91, 255, 0.28));
}

.errorpage h1 {
    font-size: clamp(1.6rem, 4.5vw, 2.4rem);
    margin: 0.6rem 0 0.9rem;
}

.errorpage p {
    color: var(--muted);
    font-size: 1.05rem;
    max-width: 34rem;
    margin: 0 auto 1rem;
}

.errorpage .actions {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 0.8rem;
    margin-top: 1.8rem;
}

.error-art {
    display: block;
    width: min(230px, 62vw);
    height: auto;
    margin: 0 auto 1.6rem;
}

/* The one face that did not land, so the gap reads as deliberate rather than as a failure. */
.error-art .gap {
    fill: none;
    stroke: var(--line-hi);
    stroke-width: 2;
    stroke-dasharray: 7 6;
}

.errorpage .more {
    margin-top: 2.4rem;
    font-size: 0.92rem;
    color: var(--faint);
}

.errorpage .more a {
    color: var(--muted);
}

.errorpage .more a:hover {
    color: var(--link);
}

/* A one-line footer for the error pages: the legal links and the copyright, nothing else. */
.footer-compact {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.8rem 1.4rem;
    flex-wrap: wrap;
    color: var(--faint);
    font-size: 0.88rem;
}

.footer-compact nav {
    display: flex;
    gap: 1.1rem;
    flex-wrap: wrap;
}

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

.footer-compact a:hover {
    color: var(--link);
}

/* ---------- closing call to action ---------- */
.cta {
    text-align: center;
    background: var(--band);
    border: 1px solid var(--line-hi);
    border-radius: 28px;
    padding: 3.4rem 1.5rem;
    margin-bottom: 4rem;
    box-shadow: var(--shadow);
    backdrop-filter: blur(10px);
}

.cta h2 {
    font-size: clamp(1.8rem, 4vw, 2.6rem);
    margin-bottom: 0.7rem;
}

.cta p {
    color: var(--muted);
    max-width: 36rem;
    margin: 0 auto 1.6rem;
}

.cta-actions {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 0.8rem;
}

/* ---------- store badges ---------- */
.badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
}

.badge {
    display: inline-block;
    line-height: 0;
    border-radius: 8px;
    transition: transform 0.2s ease;
}

.badge:hover {
    transform: translateY(-2px);
}

.badge img {
    display: block;
    height: 42px;
    width: auto;
}

/* ---------- toast ---------- */
/*
 * One line, bottom centre, for things that happened but produced nothing to look at —
 * a copy, a saved list, a cleared history.
 */
#toast {
    position: fixed;
    left: 50%;
    bottom: 1.6rem;
    transform: translate(-50%, 1.2rem);
    z-index: 200;
    max-width: min(28rem, calc(100vw - 2rem));
    padding: 0.6rem 1.2rem;
    border-radius: 999px;
    background: rgba(19, 28, 66, 0.95);
    border: 1px solid rgba(255, 255, 255, 0.16);
    box-shadow: 0 12px 30px rgba(19, 28, 66, 0.28);
    color: #FFFFFF;
    font-size: 0.92rem;
    text-align: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
}

#toast.is-shown {
    opacity: 1;
    transform: translate(-50%, 0);
}

/* ---------- the winner ---------- */
/*
 * Every generator ends the same way: one answer, arrived at. That moment gets a screen of
 * its own — the answer blown up to the size of the room, a burst of paper, a chime — and
 * then it gets out of the way. It is built here rather than in the markup because it is
 * the same curtain for all twelve tools, and a page that never draws a winner should not
 * carry the HTML for one.
 */
.pop {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: grid;
    place-items: center;
    padding: 1.5rem;
    /* Frosted white rather than the usual black scrim: a light page should not go dark to
       show you something good. */
    background: rgba(244, 247, 255, 0.78);
    backdrop-filter: blur(10px);
    opacity: 0;
    transition: opacity 0.25s ease;
}

.pop[hidden] {
    display: none;
}

.pop.is-shown {
    opacity: 1;
}

.pop-card {
    position: relative;
    width: min(46rem, 100%);
    max-height: 100%;
    overflow: auto;
    text-align: center;
    padding: clamp(1.8rem, 5vw, 3.2rem) clamp(1.2rem, 4vw, 3rem);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 28px;
    box-shadow: 0 30px 70px rgba(26, 42, 105, 0.22);
    transform: scale(0.72);
    opacity: 0;
    transition: transform 0.42s cubic-bezier(0.18, 1.5, 0.42, 1), opacity 0.22s ease;
}

/* The tool's own hue, as a band across the top and a glow under the card. */
.pop-card::before {
    content: '';
    position: absolute;
    inset: 0 0 auto;
    height: 6px;
    border-radius: 28px 28px 0 0;
    background: linear-gradient(90deg, var(--violet), var(--accent) 50%, var(--cyan));
}

.pop.is-shown .pop-card {
    transform: scale(1);
    opacity: 1;
}

.pop-eyebrow {
    font-size: 0.78rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--faint);
}

/*
 * The answer, at the size the request asks for: big enough to be read across a room from
 * a phone held up. It shrinks in steps rather than all at once, because a name and a
 * two-digit number want very different sizes.
 */
.pop-text {
    font-family: var(--display);
    font-size: clamp(2.6rem, 12vw, 6.5rem);
    line-height: 1.25;
    margin: 0.6rem 0 0.2rem;
    overflow-wrap: anywhere;
    background: linear-gradient(135deg, var(--ink) 5%, var(--accent) 115%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    filter: drop-shadow(0 14px 28px color-mix(in srgb, var(--accent) 28%, transparent));
}

.pop-text.is-long {
    font-size: clamp(1.7rem, 7vw, 3.6rem);
}

.pop-text.is-very-long {
    font-size: clamp(1.2rem, 4.4vw, 2.1rem);
}

.pop-note {
    color: var(--muted);
    font-size: 1rem;
    margin-top: 0.4rem;
}

.pop-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.7rem;
    margin-top: 1.6rem;
}

.pop-hint {
    margin-top: 1rem;
    font-size: 0.85rem;
    color: var(--faint);
}

/*
 * The paper. Each piece is one absolutely positioned block given its own hue, delay and
 * horizontal drift by the script; the fall itself is this one animation, so a burst of
 * eighty pieces still costs one compositor layer each and nothing on the main thread.
 */
.pop-confetti {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
}

.pop-confetti i {
    position: absolute;
    top: -8vh;
    width: 10px;
    height: 16px;
    border-radius: 2px;
    opacity: 0;
    animation: confetti-fall 2.6s linear forwards;
}

@keyframes confetti-fall {
    0% {
        opacity: 1;
        transform: translate3d(0, -10vh, 0) rotate(0deg);
    }
    100% {
        opacity: 0;
        transform: translate3d(var(--drift, 0px), 108vh, 0) rotate(720deg);
    }
}

/* The inline result takes the same beat, for anyone who closes the curtain and spins again. */
@keyframes result-pop {
    0% {
        transform: scale(0.6);
        opacity: 0.2;
    }
    62% {
        transform: scale(1.12);
        opacity: 1;
    }
    100% {
        transform: scale(1);
    }
}

.is-popping {
    animation: result-pop 0.5s cubic-bezier(0.18, 1.4, 0.44, 1) both;
}

/*
 * The mute. Sound that cannot be turned off is worse than no sound, and the control has to
 * be reachable without first winning something — so it sits in the corner of every
 * generator page rather than inside the curtain.
 */
.sound-toggle {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 210;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: var(--paper);
    border: 1px solid var(--line-hi);
    color: var(--muted);
    box-shadow: 0 6px 16px rgba(26, 42, 105, 0.12);
    cursor: pointer;
    transition: color 0.2s, border-color 0.2s, transform 0.2s;
}

.sound-toggle:hover {
    color: var(--accent);
    border-color: var(--accent);
    transform: translateY(-2px);
}

.sound-toggle svg {
    width: 19px;
    height: 19px;
}

.sound-toggle .off,
.sound-toggle[aria-pressed="false"] .on {
    display: none;
}

.sound-toggle[aria-pressed="false"] .off {
    display: block;
}

@media (max-width: 560px) {
    .sound-toggle {
        width: 38px;
        height: 38px;
    }
}

/* ---------- full screen ---------- */
/*
 * The stage, and only the stage, across the whole screen: a wheel being spun in front of
 * a room is the one thing everybody is meant to be looking at, and the entry box and the
 * notes underneath are for the person driving, not the audience.
 *
 * Everything here hangs off a class the script sets rather than off :fullscreen, because
 * one unsupported pseudo-class in a selector list throws the whole rule away — and the
 * browsers most likely to be plugged into a projector are the old ones.
 */
.stage > .stage-full {
    position: absolute;
    top: 0.9rem;
    right: 0.9rem;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border-radius: 11px;
    background: var(--paper);
    border: 1px solid var(--line-hi);
    color: var(--muted);
    box-shadow: 0 4px 12px rgba(26, 42, 105, 0.1);
    cursor: pointer;
    transition: color 0.2s, border-color 0.2s, transform 0.2s;
}

.stage > .stage-full:hover {
    color: var(--accent);
    border-color: var(--accent);
    transform: translateY(-2px);
}

.stage-full svg {
    width: 18px;
    height: 18px;
}

.stage-full .out,
.stage-full[aria-pressed="true"] .in {
    display: none;
}

.stage-full[aria-pressed="true"] .out {
    display: block;
}

.stage.is-fullscreen {
    /*
     * One percent of the shorter side of the screen, and the unit every picture on every
     * full-screen stage is measured in.
     *
     * The rules used to be written in vh alone, which is right on a desktop: the height is
     * what runs out first and the width never does. A phone held upright is the other way
     * round — 896 tall against 414 wide — so 20vh came out at 179 pixels on a screen with
     * 414 of width to give, and the picture ate the room the answer and the buttons needed.
     *
     * At 4:3 and wider the height still wins and nothing about a desktop changes. Held
     * upright the width takes over, and every picture on the stage shrinks by the same
     * factor — which is what keeps each stage in its own proportions instead of in twenty
     * separately guessed ones.
     */
    --u: min(1vh, 1.33vw);
    width: 100%;
    height: 100%;
    max-width: none;
    border: 0;
    border-radius: 0;
    justify-content: center;
    /*
     * Something has to give when the answer is bigger than the screen — twenty dice, a
     * hundred names drawn at once, a wall of chips. Until now it simply hung off the
     * bottom: the buttons and the last rows of the result were not merely awkward to
     * reach, they were unreachable, because a full-screen element does not scroll the
     * page behind it.
     *
     * Centred while it fits and scrolled when it does not. "safe" is what stops the
     * centring itself from doing the damage: a centred flex item taller than its box
     * overflows equally at both ends, and the top end is the one a scrollbar cannot get
     * back. Browsers without it keep plain centring and the scrollbar.
     */
    overflow: auto;
    justify-content: safe center;
    overscroll-behavior: contain;
    gap: clamp(0.8rem, calc(3 * var(--u)), 2.2rem);
    padding: clamp(1rem, calc(4 * var(--u)), 3rem);
    /* A full-screen element is painted on black unless it is told otherwise. */
    background: linear-gradient(180deg, var(--wash-2), var(--paper) 55%);
}

/* The way out stays put while the answer scrolls under it. */
.stage.is-fullscreen > .stage-full {
    position: fixed;
}

/*
 * The glow under the stage reaches past its edges — a fifth of the width either side and
 * nearly half a height below — which was free while the stage clipped nothing. A stage
 * that scrolls counts it as something to scroll to, and every full-screen page came up
 * with two scrollbars and nothing at the end of them. Inside the edges here.
 */
.stage.is-fullscreen::before {
    inset: auto 0 0 0;
}

/* Room-sized type: the answer is being read from the back, not from the keyboard. */
.stage.is-fullscreen .result {
    font-size: clamp(3rem, calc(15 * var(--u)), 10rem);
}

.stage.is-fullscreen .result.is-long {
    font-size: clamp(2rem, calc(9 * var(--u)), 6rem);
}

.stage.is-fullscreen .result.is-idle {
    font-size: clamp(1.2rem, calc(4 * var(--u)), 2.2rem);
}

.stage.is-fullscreen .result-note {
    font-size: clamp(1rem, calc(2.4 * var(--u)), 1.5rem);
}

.stage.is-fullscreen .pill {
    font-size: clamp(1.2rem, calc(4 * var(--u)), 2.4rem);
    padding: 0.5rem 1.4rem;
}

.stage.is-fullscreen .ranked {
    max-width: min(48rem, 92vw);
}

/*
 * A long answer scrolls inside itself, not by taking the screen with it.
 *
 * Twenty draws is a list two thousand pixels tall: with the stage scrolling as a whole,
 * the buttons and the headline result went below the fold and had to be scrolled back to.
 * Capping the list keeps the answer and the controls where they were and gives the scroll
 * to the part that is actually long.
 */
.stage.is-fullscreen .ranked,
.stage.is-fullscreen .lines,
.stage.is-fullscreen .dice-out,
.stage.is-fullscreen .coins-out,
.stage.is-fullscreen .secret-list {
    max-height: min(52vh, 34rem);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.stage.is-fullscreen .ranked li {
    font-size: clamp(1rem, calc(2.8 * var(--u)), 1.6rem);
}

.stage.is-fullscreen .btn {
    font-size: clamp(0.95rem, calc(2.2 * var(--u)), 1.3rem);
    padding: 0.8rem 2rem;
}

.stage.is-fullscreen > .stage-full {
    top: 1.4rem;
    right: 1.4rem;
    width: 44px;
    height: 44px;
}

@media (max-width: 560px) {
    .stage > .stage-full {
        top: 0.7rem;
        right: 0.7rem;
        width: 34px;
        height: 34px;
    }
}

/* ---------- scroll reveal ---------- */
/*
 * Blocks fade up as they arrive. The class is only added by script, so with JavaScript
 * off — or with reduced motion asked for — everything is simply visible from the start.
 */
.reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal.is-visible {
    opacity: 1;
    transform: none;
}

/* ---------- responsive ---------- */
@media (max-width: 980px) {
    .tool-layout {
        grid-template-columns: 1fr;
    }

    .stage {
        min-height: 19rem;
    }
}

@media (max-width: 900px) {
    .footer-grid {
        grid-template-columns: 1fr 1fr;
        gap: 1.8rem;
    }

    .footer-brand {
        grid-column: 1 / -1;
    }

    /*
     * Two columns take four blocks in two rows, which leaves the third list — the one
     * carrying the support address — alone on the last row with an empty cell beside it.
     * The address then broke across two lines inside 169 pixels while 366 sat unused next
     * to it. It takes the whole row instead.
     */
    .footer-col:last-child {
        grid-column: 1 / -1;
    }

    .nav-toggle {
        display: inline-flex;
    }

    /*
     * The bar keeps three things: the wordmark, the menu button and the day/night switch.
     * None of them may be squashed — they are tap targets, and flex-shrink had been taking
     * the difference out of the menu button, leaving it two pixels wide.
     */
    .logo,
    .nav-toggle,
    .theme-toggle {
        flex: none;
    }

    /*
     * In the sheet the icon link gives way to a box you can type in: it was a row that sent
     * you to a page to do the one thing you had already opened the menu to do.
     */
    header nav .header-search {
        display: none;
    }

    header nav .nav-search {
        display: flex;
        align-items: center;
        gap: 0.6rem;
        margin: 1rem 0 0;
        padding: 0.62rem 0.9rem;
        border: 1px solid var(--line);
        border-radius: 999px;
        background: var(--surface);
        transition: border-color 0.15s ease;
    }

    header nav .nav-search:focus-within {
        border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
    }

    header nav .nav-search svg {
        width: 1.05rem;
        height: 1.05rem;
        flex: none;
        color: var(--muted);
    }

    /* The field itself is invisible: the pill around it is the control. */
    header nav .nav-search input {
        flex: 1;
        min-width: 0;
        border: 0;
        padding: 0;
        background: none;
        font: inherit;
        font-size: 1rem;
        color: var(--text);
    }

    header nav .nav-search input:focus {
        outline: none;
    }

    /* Safari draws its own clear button and its own inner shadow on a search field. */
    header nav .nav-search input::-webkit-search-decoration,
    header nav .nav-search input::-webkit-search-cancel-button {
        -webkit-appearance: none;
    }

    /* And the language picker is simply the last row of the sheet. */
    .lang {
        margin-top: 1rem;
    }

    .lang-button {
        width: 100%;
        justify-content: center;
        padding-block: 0.7rem;
    }

    /*
     * The links leave the bar and become a sheet under it. The call to action stays in
     * the bar, because on a phone it is the one thing worth a permanent thumb target.
     */
    header nav {
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        padding: 0.5rem 1.5rem 1.2rem;
        /*
         * Opaque, not frosted. Nested inside a header that already has a backdrop-filter,
         * this element's own blur never applies — leaving --glass-solid's 14% as plain
         * transparency, with the page showing through the menu.
         */
        background: var(--paper);
        box-shadow: var(--shadow);
        border-bottom: 1px solid var(--line);
        display: none;
    }

    header nav.is-open {
        display: flex;
    }

    header nav a {
        padding: 0.8rem 0;
        border-bottom: 1px solid var(--line);
        font-size: 1.02rem;
    }

    header nav a.nav-cta {
        margin-top: 1rem;
        text-align: center;
        border-bottom: none;
        padding: 0.75rem 1.15rem;
    }

    /* In the sheet the panel is not a panel: it opens in place and pushes the rest of
       the menu down, which is the one behaviour that cannot end up off the screen. */
    .nav-drop {
        position: static;
    }

    .nav-drop-button {
        width: 100%;
        justify-content: space-between;
        padding: 0.8rem 0;
        border-bottom: 1px solid var(--line);
        font-size: 1.02rem;
    }

    .nav-drop-menu {
        position: static;
        transform: none;
        min-width: 0;
        padding: 0;
        border: 0;
        border-radius: 0;
        background: none;
        box-shadow: none;
    }

    header nav .nav-drop-menu a {
        padding: 0.7rem 0 0.7rem 0.9rem;
        border-bottom: 1px solid var(--line);
        border-radius: 0;
    }

    .nav-drop-all {
        margin-top: 0;
        padding-top: 0.7rem !important;
        border-top: 0;
    }
}

@media (max-width: 560px) {
    section {
        padding: 3.2rem 0;
    }

    /*
     * Two up, and only what identifies the tool. The description and the "Spin it →" link
     * are for a card that owns a whole row; at half the width they crowd out the picture,
     * which is the part that says what this is at a glance.
     */
    .tools {
        grid-template-columns: 1fr 1fr;
        gap: 0.7rem;
    }

    .tool-card {
        padding: 0.85rem 0.8rem 1rem;
        gap: 0.4rem;
    }

    .tool-card p,
    .tool-card .go {
        display: none;
    }

    .tool-card h3 {
        font-size: 0.95rem;
        line-height: 1.3;
    }

    .tool-art {
        margin-bottom: 0;
        padding: 0.35rem 0.45rem;
    }

    .field-row {
        grid-template-columns: 1fr;
    }

    .badge img {
        height: 38px;
    }

    /*
     * The bar carries a mark, a wordmark, a menu button, a search link, the language
     * button and the day/night switch. At desktop sizes they sit comfortably; at 375px
     * they add up to more than the window is wide, and flex-shrink resolves that by
     * squashing whatever gives — which was the menu button, down to two pixels.
     *
     * Everything below either fixes a size or gives one back.
     */
    .header-content {
        padding-top: 0.6rem;
        padding-bottom: 0.6rem;
        padding-inline: 1rem;
        gap: 0.55rem;
    }


    .logo {
        font-size: 1.1rem;
    }

    /* Smaller mark, and the space beside it comes down with the type. */
    .logo img {
        width: 30px;
        height: 30px;
        margin-right: 0.42rem;
    }

    .nav-toggle {
        width: 38px;
        height: 38px;
        border-radius: 11px;
    }

        .btn {
        padding: 0.7rem 1.15rem;
        font-size: 0.88rem;
    }

    .stage {
        padding: 1.5rem 1rem;
        border-radius: 20px;
    }

    .panel {
        padding: 1.2rem;
        border-radius: 20px;
    }

    /* An email address has no space to break at, so it is allowed to break anywhere. */
    .cta-actions .btn,
    .errorpage .actions .btn {
        overflow-wrap: anywhere;
    }
}

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    .reveal {
        opacity: 1;
        transform: none;
        transition: none;
    }

    .btn:hover,
    .tool-card:hover,
    .badge:hover,
    .tool-card:hover .tool-art {
        transform: none;
    }

    .pop-card {
        transform: none;
        transition: opacity 0.2s ease;
    }

    .pop.is-shown .pop-card {
        transform: none;
    }

    .pop-confetti {
        display: none;
    }

    .is-popping {
        animation: none;
    }
}

/*
 * The flag's slot, held open while it is empty.
 *
 * The flag is an inline SVG the script writes in once it knows the language, and a box that
 * grows from nothing to twenty-one pixels drags the whole masthead across with it. Reserved
 * here, it lands without moving anything.
 */
.lang-flag-slot {
    display: inline-flex;
    align-items: center;
    width: 21px;
    height: 14px;
    flex: none;
}

/* ---------- right to left ---------- */

/*
 * Arabic. Everything that flows reverses on its own once dir="rtl" is set; what does not
 * is anything nailed to a physical side. These are those rules, and only those.
 *
 * Written as physical overrides rather than by converting the whole stylesheet to logical
 * properties, because that conversion touches every layout on the site and this touches
 * eight things that are visibly on the wrong side.
 */
[dir="rtl"] .search-field svg,
[dir="rtl"] .home-search svg {
    left: auto;
    right: 1.15rem;
}

[dir="rtl"] .search-field input[type="search"],
[dir="rtl"] .home-search input[type="search"] {
    padding: 0.9rem 3.1rem 0.9rem 1.2rem;
}

/* The two controls pinned to the bottom corner move to the other corner together. */
[dir="rtl"] .to-top,
[dir="rtl"] .sound-toggle {
    right: auto;
    left: 1rem;
}

[dir="rtl"] .notice {
    border-left: 0;
    border-right: 3px solid var(--amber);
}

[dir="rtl"] .prose ul,
[dir="rtl"] .prose ol,
[dir="rtl"] .summary ul {
    padding-left: 0;
    padding-right: 1.3rem;
}

[dir="rtl"] .ranked,
[dir="rtl"] .key-table th,
[dir="rtl"] .key-table td {
    text-align: right;
}

[dir="rtl"] select {
    padding-right: 0.85rem;
    padding-left: 2.2rem;
    background-position: left 0.8rem center;
}

/* ---------- finding a generator ---------- */

/*
 * [hidden] means hidden.
 *
 * The attribute's own rule is display:none at the very bottom of the cascade, and any
 * later display on the element itself overrides it — which both the cards and the chips
 * set. Saying it again here, with the attribute in the selector, settles it.
 */
.tool-card[hidden],
.tool-chips a[hidden] { display: none !important; }

.header-search {
    display: grid;
    place-items: center;
    width: 2.4rem;
    height: 2.4rem;
    flex: none;
    border-radius: 50%;
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--muted);
    transition: color 0.15s ease, border-color 0.15s ease;
}

.header-search:hover {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}

.header-search svg { width: 1.1rem; height: 1.1rem; }

/* The word beside the icon belongs to the mobile sheet; in the bar the icon is enough. */
.header-search span { display: none; }

/* The sheet's search box. In the bar the icon link above stands in for it. */
.nav-search { display: none; }

/*
 * The way out of the landing grid.
 *
 * The grid shows every generator there is today, so this is not a "load more" — it is the
 * door to the two pages that list them by name and by errand, which is what somebody who
 * did not recognise anything in the grid actually needs.
 */
.see-all {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 0.7rem;
    margin: 2.2rem 0 0;
}

/*
 * The box on the landing page and the one on the search page: the same control, so the same
 * rules, and the .search-field wrapper on the search page only differs in width.
 *
 * input[type="search"] is not one of the fields the form rules above cover, so this is the
 * only place it is dressed at all. Given that, it may as well be dressed properly: a pill
 * rather than a rounded rectangle, so it reads as a place to type a word rather than a
 * field in a form, and lifted off the page so it survives being sat on a coloured backdrop.
 */
.home-search,
.search-field {
    position: relative;
    width: min(100%, 32rem);
    margin: 0 auto 2rem;
}

.home-search svg,
.search-field svg {
    position: absolute;
    left: 1.15rem;
    top: 50%;
    transform: translateY(-50%);
    width: 1.15rem;
    height: 1.15rem;
    color: var(--faint);
    pointer-events: none;
    transition: color 0.18s ease;
}

.home-search input[type="search"],
.search-field input[type="search"] {
    width: 100%;
    font-family: var(--body);
    font-size: 1rem;
    color: var(--text);
    background: var(--paper);
    border: 1px solid var(--line-hi);
    border-radius: 999px;
    padding: 0.9rem 1.2rem 0.9rem 3.1rem;
    box-shadow: 0 1px 2px rgba(26, 42, 105, 0.05), 0 8px 22px -14px rgba(26, 42, 105, 0.45);
    -webkit-appearance: none;
    appearance: none;
    transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}

.home-search input[type="search"]::placeholder,
.search-field input[type="search"]::placeholder {
    color: var(--faint);
}

.home-search input[type="search"]:hover,
.search-field input[type="search"]:hover {
    border-color: color-mix(in srgb, var(--accent) 30%, var(--line-hi));
}

/*
 * The focus ring is drawn on the input rather than as an outline, because an outline on a
 * pill sits square at the ends in more than one browser. The generic :focus-visible rule
 * above would do exactly that, so it is turned off here and replaced.
 */
.home-search input[type="search"]:focus,
.home-search input[type="search"]:focus-visible,
.search-field input[type="search"]:focus,
.search-field input[type="search"]:focus-visible {
    outline: none;
    border-color: var(--accent);
    background: var(--surface);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent),
                0 10px 26px -16px rgba(26, 42, 105, 0.5);
}

.home-search input[type="search"]:focus + svg,
.home-search:focus-within svg,
.search-field:focus-within svg {
    color: var(--accent);
}

/*
 * The clear button. WebKit draws a small grey cross of its own and Firefox draws nothing;
 * this replaces the first with a mark in the page's own colours and leaves the second
 * alone, since the field is one word long and selecting it is no hardship.
 */
.home-search input[type="search"]::-webkit-search-cancel-button,
.search-field input[type="search"]::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
    width: 1.15rem;
    height: 1.15rem;
    margin-right: 0.15rem;
    cursor: pointer;
    border-radius: 50%;
    background: var(--muted);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12M18 6 6 18'/%3E%3C/svg%3E") center / 0.8rem no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12M18 6 6 18'/%3E%3C/svg%3E") center / 0.8rem no-repeat;
}

.home-search input[type="search"]::-webkit-search-cancel-button:hover,
.search-field input[type="search"]::-webkit-search-cancel-button:hover {
    background: var(--accent);
}

.search-empty {
    text-align: center;
    color: var(--muted);
    padding: 1.5rem 0 2.5rem;
}

/* ---------- back to the top ---------- */

/*
 * Sits above the sound control, not on top of it: that one is already pinned to the same
 * corner at 1rem, so this one starts a button's height plus a gap higher up.
 *
 * Hidden until there is something to scroll back from — a button offering to take you to
 * the top of a page you are already at the top of is furniture, not help.
 */
.to-top {
    position: fixed;
    right: 1rem;
    bottom: 3.9rem;
    z-index: 210;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    border: 1px solid var(--line);
    background: var(--paper);
    color: var(--muted);
    box-shadow: var(--shadow);
    cursor: pointer;
    opacity: 0;
    visibility: hidden;
    transform: translateY(6px);
    transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
}

.to-top.is-shown {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.to-top:hover {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}

.to-top svg { width: 1.15rem; height: 1.15rem; }

/* ---------- the day/night switch ---------- */

/*
 * Which icon shows is decided by the same three-state rule the palette uses, so the button
 * offers the side you are not on: a moon while the page is light, a sun while it is dark.
 * Doing it in CSS rather than in the script means it is already correct on the first paint,
 * before any script has run.
 */
.theme-toggle {
    display: grid;
    place-items: center;
    width: 2.4rem;
    height: 2.4rem;
    flex: none;
    border-radius: 50%;
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease;
}

.theme-toggle:hover {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}

.theme-toggle svg { width: 1.15rem; height: 1.15rem; }

/* Light is in force: offer the moon. */
.theme-toggle .to-sun { display: none; }
.theme-toggle .to-moon { display: block; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-toggle .to-sun { display: block; }
    :root:not([data-theme="light"]) .theme-toggle .to-moon { display: none; }
}

:root[data-theme="dark"] .theme-toggle .to-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .to-moon { display: none; }

:root[data-theme="light"] .theme-toggle .to-sun { display: none; }
:root[data-theme="light"] .theme-toggle .to-moon { display: block; }

/* ---------- where an advert goes ---------- */

/*
 * The band an advert will occupy, held open and visible while it is still empty.
 *
 * Two units, one per shape of screen. Below 768 pixels it is the 300 by 250 medium
 * rectangle, which is the standard unit narrow enough to fit a phone; from a tablet up it
 * is the 728 by 90 leaderboard, which is the shape that belongs across the top of a wide
 * page and costs a desktop reader far less of the first screenful. The room reserved at
 * each width is the room that unit will need. Holding it open
 * now is the point: filling a collapsed slot as the ad arrives shoves the whole page down,
 * and that shove is exactly what Core Web Vitals measures.
 *
 * :empty is doing the switching. Today the div has no children, so what shows is the tiled
 * placeholder below; the moment the AdSense snippet is put inside it, every one of those
 * rules stops matching and the slot is a plain, silent container around the real advert.
 */
.ad-slot {
    display: grid;
    place-items: center;
    width: 100%;
    max-width: 300px;
    margin: 1.1rem auto 0;
    min-height: 250px;
    overflow: hidden;
}

/*
 * The tiling, on the empty slot only. No words in it on purpose: anything written here
 * would be a string in the stylesheet, which is the one place on this site that cannot be
 * translated — an English label would sit there untranslated on all fifteen languages.
 */
/*
 * The empty slot says what it is.
 *
 * The label is drawn from a data attribute rather than written inside the div, because
 * :empty is what switches all of this off the moment a real advert is put in — and a child
 * element would make the slot permanently non-empty. An attribute is not a child, so the
 * server can still translate the word.
 */
.ad-slot:empty::before {
    content: attr(data-label);
    font-family: var(--body);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.18em;
    color: color-mix(in srgb, var(--accent) 62%, var(--muted));
}

.ad-slot:empty {
    border: 1px dashed color-mix(in srgb, var(--accent) 32%, var(--line));
    border-radius: 14px;
    background-color: color-mix(in srgb, var(--accent) 7%, var(--wash-2));
    background-image:
            repeating-linear-gradient(90deg, transparent 0 74px,
                    color-mix(in srgb, var(--line) 70%, transparent) 74px 75px),
            repeating-linear-gradient(0deg, transparent 0 29px,
                    color-mix(in srgb, var(--line) 70%, transparent) 29px 30px),
            repeating-linear-gradient(135deg, transparent 0 16px,
                    color-mix(in srgb, var(--line) 30%, transparent) 16px 17px);
}

/*
 * The leaderboard takes over at the width it fits. 768 is the same breakpoint the rest of
 * the layout turns at, so the advert changes shape when the page around it does.
 */
@media (min-width: 768px) {
    .ad-slot {
        max-width: 728px;
        min-height: 90px;
    }
}

/*
 * Add ?ads=preview to any address to see where the slot is and how much room it takes.
 * A development aid; without the query string there is nothing on the page at all.
 */
.ads-preview .ad-slot {
    display: grid;
    place-items: center;
    margin: 1.1rem auto 0;
    min-height: 100px;
    border: 2px dashed color-mix(in srgb, var(--accent) 45%, var(--line));
    border-radius: 14px;
    background: color-mix(in srgb, var(--accent) 5%, transparent);
    color: var(--faint);
    font-family: var(--body);
    font-size: 0.82rem;
    letter-spacing: 0.04em;
}

.ads-preview .ad-slot::after {
    content: 'ad slot · ' attr(data-ad);
}

@media (min-width: 768px) {
    .ads-preview .ad-slot { min-height: 90px; }
}

/* The two options on /priority-picker. A pill that is a real button: the reader is being
   asked a question, so it has to look pressable and answer to the keyboard. */
.pill-choice {
    cursor: pointer;
    border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    font: inherit;
    text-align: left;
    transition: background .15s ease, transform .15s ease;
}
.pill-choice:hover,
.pill-choice:focus-visible {
    background: color-mix(in srgb, var(--accent) 22%, transparent);
    transform: translateY(-1px);
}
@media (prefers-reduced-motion: reduce) {
    .pill-choice { transition: none; }
    .pill-choice:hover, .pill-choice:focus-visible { transform: none; }
}

/* A result set whose entries are sentences rather than tokens. The older pages pass
   is-lines and let the pills wrap, which reads fine for a word and badly for a trivia
   question, so the pages that print whole lines ask for a column instead. */
.result-set.is-stack {
    flex-direction: column;
    align-items: stretch;
    gap: 0.45rem;
    max-width: 38rem;
    margin: 0 auto;
}

.result-set.is-stack .pill {
    font-size: 1.05rem;
    text-align: left;
    border-radius: 0.9rem;
}

.result-set.is-stack .pill.is-head {
    background: transparent;
    border: none;
    padding-bottom: 0;
    font-size: 0.85rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
}

/* The blank line between a round of questions and its answer key. */
.result-set.is-stack .pill.is-spacer {
    background: transparent;
    border: none;
    padding: 0.25rem;
}
