/**
 * War 2050 — Survivor's Handbook (player wiki)
 *
 * Palette, type and spacing mirror frontend/game/css/variables.css so the
 * wiki reads as part of the game. Kept standalone (no @import of the game's
 * tokens) so the wiki can never break the game's cascade, and vice versa.
 *
 * WHY THESE NAMES ARE SHORT, AND WHY THAT IS NOT DRIFT
 * ===================================================
 * A review flagged this file as "a third vocabulary sharing not one name" with
 * the other two roots, and proposed retiring these names onto the shared ones.
 * Checked value by value, it is a naming mismatch and not a value drift, and
 * the rename would have been actively harmful:
 *
 *     --gold       = --color-accent-gold         #d4af37   same
 *     --gold-light = --color-accent-gold-light   #f4d03f   same
 *     --gold-dark  = --color-accent-gold-dark    #b8962f   same
 *     --bg         = --color-primary-900         #0a0f1a   same
 *     --cyan       = --color-accent-cyan-LIGHT   #22d3ee   same
 *     --green      = --color-accent-green-LIGHT  #10b981   same
 *     --red        = --color-accent-red-LIGHT    #ef4444   same
 *     --orange     = --color-accent-orange-LIGHT #fb923c   same
 *
 * Note the four that map to the LIGHT variants. That is deliberate and it is
 * the whole point: the handbook is long-form, somebody reads a page of it
 * rather than glancing at a label, so its floor is 7:1 rather than WCAG AA's
 * 4.5:1 — see the text-tier note below. Flattening `--cyan` onto the game's
 * base `--color-accent-cyan` (#06b6d4) would have darkened every accent in the
 * handbook and undone that.
 *
 * So: the names stay short, the values stay put, and the correspondence is
 * written down here instead. If a value on either side changes, change both.
 */

:root {
    --bg:            #0a0f1a;
    --bg-raised:     #111827;
    --bg-panel:      #131c2e;
    --line:          #1f2d47;
    --line-strong:   #2d4163;

    --gold:          #d4af37;
    --gold-light:    #f4d03f;
    --gold-dark:     #b8962f;
    --cyan:          #22d3ee;
    --cyan-deep:     #06b6d4;
    --green:         #10b981;
    --orange:        #fb923c;
    --red:           #ef4444;

/* Three tiers of text, all of them sized for reading rather than for ranking.
   Measured against the darkest surface each is used on (--bg-raised, #111827):

     --text    16.0:1   body copy, headings, table names
     --text-2   9.6:1   ledes, card copy, nav, column heads
     --text-3   7.8:1   captions, footers, muted cells

   The handbook is long-form — someone reads a whole page of it, not a label —
   so the floor here is 7:1, not the 4.5:1 WCAG AA asks for. The previous
   --text-3 (#64748b) measured 3.73:1 on a raised panel and failed AA outright
   for every figcaption, table caption, stat label and muted cell in the
   handbook; --text-2 (#94a3b8) passed AA at 6.92:1 but sat under the long-form
   bar. Both were raised until the whole tier cleared 7:1 while keeping a
   visible step between them.

   --text is deliberately NOT pure white. #f8fafc on #0a0f1a measures 18.3:1,
   and at that separation light type on a dark ground blooms — the halation
   that makes a dark-mode paragraph tiring to read for three minutes. #e6edf6
   keeps 16:1, which is far past any requirement, and reads calmer at length. */
    --text:          #e6edf6;
    --text-2:        #b3c0d1;
    --text-3:        #a0adc0;

    --font-display: 'Orbitron', 'Rajdhani', sans-serif;
    --font-head:    'Rajdhani', 'Exo 2', sans-serif;
    --font-body:    'Exo 2', system-ui, sans-serif;

    --radius:  10px;
    --radius-s: 6px;

    --wrap: 900px;
}

/* ------------------------------------------------------------------ base */

*, *::before, *::after { box-sizing: border-box; }

html {
    background: var(--bg);
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background:
        radial-gradient(1100px 500px at 50% -10%, rgba(212, 175, 55, 0.07), transparent 65%),
        var(--bg);
    color: var(--text);
    font-family: var(--font-body);
    font-size: 16px;
    line-height: 1.65;
    /* Belt and braces against a stray wide child pushing the page sideways. */
    overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }

a {
    color: var(--cyan);
    text-decoration: none;
    border-bottom: 1px solid rgba(34, 211, 238, 0.3);
    transition: color 150ms ease, border-color 150ms ease;
}
a:hover, a:focus-visible { color: var(--gold-light); border-bottom-color: var(--gold-light); }

:focus-visible { outline: 2px solid var(--gold-light); outline-offset: 3px; }

/* ------------------------------------------------------------- site head */

/* The header is sticky at z-index 20, so anything scrolled to lands UNDERNEATH
   it -- in-page anchors, and scrollIntoView from a click on a control. Measured
   at 91px on both a phone and a desktop; the extra 12 is a breathing gap.
   Verified: scrolling an h2 into view puts its top at 870 with the header
   bottom at 91, where before it would have landed underneath. This was first
   added while chasing an untappable checkbox and it did NOT fix that -- kept
   because an anchor landing under a sticky header is a real fault on its
   own. */
html {
    scroll-padding-top: 103px;
}

.site-head {
    position: sticky;
    top: 0;
    z-index: 20;
    background: rgba(10, 15, 26, 0.94);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--line);
}

/* Wider than the article column so the nine nav items fit on one row at
   desktop widths without clipping; it still scrolls below ~1000px. */
.site-head-inner {
    max-width: 1040px;
    margin: 0 auto;
    padding: 10px 16px 0;
}

.brand {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.brand-mark {
    font-family: var(--font-display);
    font-weight: 800;
    font-size: 1.05rem;
    letter-spacing: 0.12em;
    color: var(--gold);
    text-transform: uppercase;
}
.brand-mark a { color: inherit; border: 0; }

/* Back to the main site.
   Nothing in the handbook's header left it: the brand mark goes to the
   handbook's own index and every nav entry is another handbook page. Each page
   does end with "War 2050 · Play · Handbook", but that points at /game/ rather
   than the site, and it is at the foot of a long page — no use to someone who
   has just arrived at the top. Pushed to the far end of the brand row, where
   it wraps onto its own line on a narrow screen rather than squeezing the
   title. */
.brand-back {
    margin-left: auto;
    border: 0;
    font-family: var(--font-head);
    /* 0.82rem = 13.1px. Caps at 0.14em tracking lose both word-shape and word
       grouping, so this tier is set above the 12px floor rather than at it. */
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-2);
    white-space: nowrap;
}
.brand-back:hover,
.brand-back:focus-visible { color: var(--gold); }

/* 0.22em is the widest tracking in the handbook and it sits on the smallest
   string in the masthead, so this takes the largest bump of the caps tier. */
.brand-sub {
    font-family: var(--font-head);
    font-size: 0.85rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--text-3);
}

/* Horizontal scroller so the nav never widens the page on a phone. */
.site-nav {
    display: flex;
    gap: 4px;
    overflow-x: auto;
    scrollbar-width: none;
    margin: 8px -16px 0;
    padding: 0 16px;
}
.site-nav::-webkit-scrollbar { display: none; }

.site-nav a {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 10px;
    border: 0;
    border-bottom: 2px solid transparent;
    font-family: var(--font-head);
    font-weight: 600;
    font-size: 0.9rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-2);
    white-space: nowrap;
}
.site-nav a:hover { color: var(--text); }
.site-nav a[aria-current="page"] {
    color: var(--gold-light);
    border-bottom-color: var(--gold);
}

/* ---------------------------------------------------------------- layout */

main {
    max-width: var(--wrap);
    margin: 0 auto;
    padding: 28px 16px 72px;
}

.page-title {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: clamp(1.6rem, 5vw, 2.3rem);
    line-height: 1.15;
    letter-spacing: 0.02em;
    margin: 8px 0 6px;
    color: var(--text);
}

.page-lede {
    font-size: 1.05rem;
    line-height: 1.6;
    color: var(--text-2);
    margin: 0 0 28px;
    max-width: 46ch;
    text-wrap: pretty;
}

h2 {
    font-family: var(--font-head);
    font-weight: 700;
    font-size: clamp(1.25rem, 3.6vw, 1.55rem);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--gold);
    margin: 42px 0 12px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--line);
}

h3 {
    font-family: var(--font-head);
    font-weight: 600;
    font-size: 1.12rem;
    letter-spacing: 0.04em;
    color: var(--text);
    margin: 26px 0 8px;
}

/* ------------------------------------------------------------- the measure

   `ch` is the advance width of "0", and in Exo 2 that is noticeably wider than
   the average lowercase letter — so a column set to 68ch does not hold 68
   characters, it holds about 86. Measured on the rendered pages at the 900px
   desktop wrap, the old 68ch cap produced 78–90 characters per line across
   every page, well past the 45–75 band where the eye reliably finds the start
   of the next line. The ratio is a steady 1.26 characters per ch, so the caps
   below are the character target divided by that:

     p, ul, ol   50ch  ->  ~63 characters
     .page-lede  46ch  ->  ~61 characters (it is set larger, so it runs longer)
     captions    52ch  ->  ~66 characters

   These are widths a line never exceeds, not widths it always reaches — on a
   phone the viewport is far narrower than any of them and nothing changes. */
p  { margin: 0 0 14px; max-width: 50ch; text-wrap: pretty; }
ul, ol { margin: 0 0 16px; padding-left: 22px; max-width: 50ch; }
li { margin-bottom: 6px; }

strong { color: var(--text); font-weight: 600; }
em { color: var(--text-2); }

code, .num {
    font-family: 'Rajdhani', ui-monospace, monospace;
    font-weight: 600;
    font-size: 0.98em;
    color: var(--gold-light);
    letter-spacing: 0.02em;
}

hr { border: 0; border-top: 1px solid var(--line); margin: 36px 0; }

/* ----------------------------------------------------------------- cards */

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
    gap: 12px;
    margin: 22px 0 8px;
}

.card {
    display: block;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: linear-gradient(180deg, var(--bg-panel), var(--bg-raised));
    padding: 16px 16px 14px;
    color: inherit;
    transition: border-color 180ms ease, transform 180ms ease;
}
a.card { border-bottom: 1px solid var(--line); }
a.card:hover, a.card:focus-visible {
    border-color: var(--gold-dark);
    transform: translateY(-2px);
    color: inherit;
}

.card-title {
    font-family: var(--font-head);
    font-weight: 700;
    font-size: 1.06rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--gold-light);
    margin-bottom: 4px;
}

.card p { margin: 0; font-size: 0.94rem; color: var(--text-2); }

/* -------------------------------------------------------------- callouts */

/* Capped at the container, not at the paragraph inside it. p and ul are held
   to 50ch, so at the 900px wrap an uncapped .note was an 868px tinted box
   holding a 480px text column ragged hard left — the box promised a measure it
   was not delivering. */
.note {
    max-width: 56ch;
    border-left: 3px solid var(--cyan-deep);
    background: rgba(6, 182, 212, 0.07);
    border-radius: 0 var(--radius-s) var(--radius-s) 0;
    padding: 12px 16px;
    margin: 18px 0;
}
.note.tip     { border-left-color: var(--green-lt, #34d399); background: rgba(16, 185, 129, 0.07); }
.note.warn    { border-left-color: var(--orange); background: rgba(251, 146, 60, 0.08); }
.note.gold    { border-left-color: var(--gold);   background: rgba(212, 175, 55, 0.08); }

.note-label {
    display: block;
    font-family: var(--font-head);
    font-weight: 700;
    font-size: 0.82rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--cyan);
    margin-bottom: 3px;
}
/* #34d399, not #10b981. Over the tip's own 7% green wash the darker token
   computes to 6.9:1 — comfortably past WCAG AA, but under the 7:1 this file's
   header sets for itself, and the odd one out beside .warn at 7.7 and .gold at
   8.1. A standard is only worth having if the exceptions get fixed. */
.note.tip  .note-label { color: var(--green-lt, #34d399); }
.note.warn .note-label { color: var(--orange); }
.note.gold .note-label { color: var(--gold); }

.note p:last-child, .note ul:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------------- tables */

/* Tables scroll inside their own box; the page never scrolls sideways. */
.table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin: 18px 0;
    background: var(--bg-raised);
}

table {
    border-collapse: collapse;
    width: 100%;
    min-width: 460px;
    font-size: 0.95rem;
}

/* A table caption here is a sentence explaining what the numbers mean, not a
   label — it is read, so it is sized and measured like prose. Unconstrained it
   ran to 137 characters on the widest page. */
caption {
    /* Above the table, for two reasons. On a phone a bottom caption is a whole
       scroll-length PAST the numbers, so the reader meets sixteen unexplained
       rows and only afterwards learns what the columns mean. And the in-game
       modal puts it on top, so bottom here meant the same sentence appeared
       above the table in one context and below it in the other.

       Sticky because a caption is a child of <table> and therefore lives
       inside the horizontal scroller: dragging the buildings table right to
       reach Build time used to carry the sentence defining that column off the
       left edge. It is visible exactly when it is not needed, otherwise. */
    caption-side: top;
    position: sticky;
    left: 0;
    padding: 10px 14px;
    max-width: 52ch;
    font-size: 0.92rem;
    line-height: 1.55;
    color: var(--text-3);
    text-align: left;
    text-wrap: pretty;
}

th, td {
    padding: 9px 12px;
    text-align: left;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}
td.wrap, th.wrap { white-space: normal; min-width: 15ch; }

/* No `position: sticky` on the header row. There was, and it was dead code:
   .table-wrap sets overflow-x, which makes it the scroll container, and it has
   no constrained height — so it never scrolls vertically and `top: 0` never
   fired. The stickiness that actually helps a wide table on a phone is on the
   first COLUMN, below. */
thead th {
    background: #16203a;
    font-family: var(--font-head);
    font-weight: 700;
    font-size: 0.82rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-2);
}

tbody tr:last-child td { border-bottom: 0; }
tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.016); }

/* Pin the column that says WHICH ROW THIS IS.
   The tables here run 700-800px against a 380px content column on a phone, so
   they are read by dragging sideways — and without this, the moment the reader
   scrolls far enough to see a cost they have lost the name it belongs to. The
   table becomes a grid of numbers with no labels, which is the one job it had.

   The backgrounds must be OPAQUE. The even-row stripe is
   rgba(255,255,255,0.016), and a transparent sticky cell lets the scrolled
   columns slide visibly underneath it. */
.table-wrap th:first-child,
.table-wrap td:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--bg-raised);
    box-shadow: 1px 0 0 var(--line);
}
.table-wrap thead th:first-child { background: #16203a; }
.table-wrap tbody tr:nth-child(even) td:first-child { background: #131b2b; }

/* A row for something rented rather than built. Gold, because that is the War
   Bond colour everywhere else, and a left rule so it reads as a different KIND
   of row rather than merely a highlighted one — the distinction the buildings
   table is making is permanent vs temporary, not important vs ordinary.
   The nth-child stripe is overridden or the parity decides how it looks. */
tbody tr.rented,
tbody tr.rented:nth-child(even) {
    background: rgba(212, 175, 55, 0.07);
    box-shadow: inset 3px 0 0 var(--gold);
}

td.name { color: var(--text); font-weight: 600; }
td.n { font-family: 'Rajdhani', monospace; font-weight: 600; color: var(--gold-light); }
td.muted { color: var(--text-3); }
/* Hidden on anything wide enough that it is not true. This sentence tells the
   reader tables scroll sideways on a narrow screen; on a desktop they do not,
   and describing behaviour that does not occur is worse than saying nothing.
   It is also only on five of the eleven pages, which is a content problem, not
   a CSS one — but gating it here at least stops the five that have it from
   lying to half their readers. */
.scroll-hint {
    display: none;
    font-size: 0.9rem;
    color: var(--text-3);
    margin: -10px 0 18px;
}

@media (max-width: 700px) {
    .scroll-hint { display: block; }
}

/* --------------------------------------------------------------- figures */

figure {
    margin: 24px 0;
    max-width: 56ch;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--bg-raised);
}
figure img { width: 100%; }
/* Phone captures are tall; cap them so they do not dominate a wide page. */
figure.phone {
    max-width: 340px;
    margin-left: auto;
    margin-right: auto;
}
/* Same reasoning as `caption`: these describe what is in the screenshot in
   full sentences. On a full-width figure the old 13.8px ran to 126 characters
   a line, the longest measure anywhere in the handbook. */
/* The FIGURE is capped, not just its caption — see the note on .note below;
   the same mismatch applied here, with a full-width bordered panel wrapped
   around a half-width caption. */
figcaption {
    padding: 10px 14px;
    max-width: 52ch;
    font-size: 0.95rem;
    line-height: 1.55;
    color: var(--text-3);
    border-top: 1px solid var(--line);
    text-wrap: pretty;
}

/* 360px, not 240px. At 240 the two-column layout engaged from a 526px
   viewport, which put each column at 240-334px — roughly 32 to 44 characters
   of Exo 2, both under the 45-character floor. Any tablet or split-screen
   window turned the orientation section into a narrow ragged strip. 360px
   moves the threshold to a 766px viewport, where a column holds about 47. */
.fig-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr));
    gap: 14px;
    align-items: start;
}
.fig-row figure { margin: 24px 0 0; }
/* Keep the phone cap inside the row. `max-width: none` used to live on the
   line above, and at equal specificity it beat figure.phone's 340px — so a
   640x1385 capture rendered ~443px wide beside a text column half its height,
   leaving 450px of dead gutter. */
.fig-row figure.phone { max-width: 300px; }

/* ------------------------------------------------------------ misc bits */

.stat-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
    gap: 10px;
    margin: 20px 0;
}
.stat {
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
    background: var(--bg-raised);
    padding: 12px 14px;
}
.stat-value {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 1.3rem;
    color: var(--gold-light);
    line-height: 1.2;
}
.stat-label {
    font-family: var(--font-head);
    font-size: 0.82rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-3);
}

/* A formula that runs off the edge is worse than no formula: it reads as
   complete. These are `white-space: nowrap; overflow-x: auto`, and a touch
   scrollbar is invisible until dragged — so on a phone the longest of them
   showed "tent space = the largest training space you alrea" with no signal
   that a clause was missing. Below 560px they wrap instead of scrolling.
   (In the in-game modal they already wrapped, which is why this only ever
   broke on the page.) */
.formula {
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-s);
    background: #0d1526;
    padding: 12px 16px;
    margin: 16px 0;
    font-family: 'Rajdhani', monospace;
    font-weight: 600;
    font-size: 1rem;
    color: var(--gold-light);
    overflow-x: auto;
    white-space: nowrap;
}

/* 57ch, not the 50ch the body copy uses: each step is indented 58px past the
   list's own edge by the number bubble, which is a little over 7ch, so the
   text column lands at the same width as a paragraph. */
.steps { counter-reset: step; list-style: none; padding: 0; max-width: 57ch; }
.steps li {
    counter-increment: step;
    position: relative;
    padding: 2px 0 14px 44px;
    margin: 0;
    border-left: 1px solid var(--line);
    margin-left: 14px;
}
.steps li:last-child { border-left-color: transparent; padding-bottom: 0; }
.steps li::before {
    content: counter(step);
    position: absolute;
    left: -14px;
    top: 0;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--bg-raised);
    border: 1px solid var(--gold-dark);
    color: var(--gold-light);
    font-family: var(--font-head);
    font-weight: 700;
    font-size: 0.9rem;
    display: grid;
    place-items: center;
}
.steps .step-title {
    display: block;
    font-family: var(--font-head);
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--text);
    text-transform: uppercase;
    font-size: 0.98rem;
}

/* Was 0.72rem — 11.5px, the only rendered string in the handbook under the
   12px floor. It carries live values ("+30%", "−50%") inside running text, so
   it has to survive being read, not just recognised. */
.tag {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    border: 1px solid var(--line-strong);
    font-family: var(--font-head);
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-2);
    vertical-align: middle;
}
.tag.gold { border-color: var(--gold-dark); color: var(--gold-light); }
.tag.cyan { border-color: var(--cyan-deep); color: var(--cyan); }

.next-links {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 10px;
}
.next-links a {
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    padding: 8px 16px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    font-family: var(--font-head);
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--text-2);
}
.next-links a:hover { color: var(--gold-light); border-color: var(--gold-dark); }

.site-foot {
    border-top: 1px solid var(--line);
    margin-top: 48px;
    padding: 22px 16px 40px;
    text-align: center;
    color: var(--text-3);
    font-size: 0.95rem;
}
/* The footer's one line is centred, so the global measure cap would offset it
   to the left of the column rather than centring it. It is short enough not to
   need one. */
.site-foot p { max-width: none; }
.site-foot a { color: var(--text-2); }

/* The contrast audit reports this at 1.06:1. It is a false positive and no
   colour here should be changed on the strength of it.

   The tool resolves an effective background from `background-color` only. This
   button has none — it is painted entirely by the `background-image` gradient
   below — so the walk falls through to the footer's transparent background and
   ends up comparing #14161c against #0a0f1a, two near-blacks. What is actually
   on screen is #14161c on gold: 12.7:1 against --gold-light at the top of the
   gradient and 6.2:1 against --gold-dark at the bottom. It passes everywhere.

   Confirmed by eye at 412x870 before this comment was written. */
.play-cta {
    display: inline-flex;
    align-items: center;
    min-height: 48px;
    padding: 0 26px;
    margin: 6px 0 4px;
    border: 0;
    border-radius: var(--radius-s);
    background: linear-gradient(180deg, var(--gold-light), var(--gold-dark));
    color: #14161c;
    font-family: var(--font-head);
    font-weight: 700;
    font-size: 1.02rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    box-shadow: 0 0 24px rgba(212, 175, 55, 0.25);
}
.play-cta:hover, .play-cta:focus-visible { color: #14161c; filter: brightness(1.08); }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { transition: none !important; }
    a.card:hover { transform: none; }
}

/* Narrow screens: let the formulas wrap rather than scroll. See the note on
   .formula — a silent horizontal cut misrepresents them as finished. */
@media (max-width: 560px) {
    .formula {
        white-space: normal;
        line-height: 1.5;
    }
}
