/* h.ua — public site stylesheet.
   One file, no framework, no build step, no third-party requests. The only
   JavaScript on the site is progressive enhancement (public/js/app.js), so
   every rule here must produce a complete page on its own.

   Source of truth: the Claude Design project "h.ua Public Site" — its token
   table, type scale and the "Notes for site.css" table. Keep this file in
   step with that document rather than tuning values ad hoc. */

/* ---------------------------------------------------------------- 1. Fonts
   Fira Sans, self-hosted. The Cyrillic and Latin subsets are separate files
   with `unicode-range`, so a Ukrainian page downloads ~7 KB per weight and
   never fetches the Latin Extended subset it does not draw from. */
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/fira-sans-400-cyrillic-ext.woff2") format("woff2");
    unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/fira-sans-400-cyrillic.woff2") format("woff2");
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/fira-sans-400-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/fira-sans-400-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/fonts/fira-sans-600-cyrillic-ext.woff2") format("woff2");
    unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/fonts/fira-sans-600-cyrillic.woff2") format("woff2");
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/fonts/fira-sans-600-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/fonts/fira-sans-600-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/fira-sans-700-cyrillic-ext.woff2") format("woff2");
    unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/fira-sans-700-cyrillic.woff2") format("woff2");
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/fira-sans-700-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/fira-sans-700-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------- 2. Tokens
   Light is the bare `:root` definition. Dark redefines the same properties
   twice: once under `prefers-color-scheme` guarded against an explicit light
   choice, and once under `[data-theme="dark"]` so the JS toggle wins in both
   directions. No colour is ever defined only inside a media query. */
:root {
    color-scheme: light dark;
    --bg: #f8f7f4;
    --fg: #161512;
    --muted: #6b6864;
    --border: #d9d6d0;
    --accent: #8a3324;
    --accent-hover: #6e281c;
    --focus: #161512;
    --input-bg: #ffffff;
    --surface: #f0ede5;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: #1f1e1b;
        --fg: #ecebe6;
        --muted: #a19d96;
        --border: #3a3833;
        --accent: #d9927f;
        --accent-hover: #e8ab9b;
        --focus: #ecebe6;
        --input-bg: #282725;
        --surface: #29271f;
    }
}

:root[data-theme="dark"] {
    --bg: #1f1e1b;
    --fg: #ecebe6;
    --muted: #a19d96;
    --border: #3a3833;
    --accent: #d9927f;
    --accent-hover: #e8ab9b;
    --focus: #ecebe6;
    --input-bg: #282725;
    --surface: #29271f;
}

/* ----------------------------------------------------------------- 3. Base */
*, *::before, *::after { box-sizing: border-box; }
html { font-size: 100%; }
body {
    margin: 0;
    font-family: "Fira Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    font-size: 1rem;
    line-height: 1.45;
    color: var(--fg);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    -webkit-text-size-adjust: 100%;
    text-wrap: pretty;
}

/* CJK families for the Chinese hosts. No webfont: a Chinese face is 5-15 MB,
   which the unicode-range subsetting used for Fira Sans cannot rescue. These
   sit BEHIND "Fira Sans" so Latin text in a Chinese page — names, acronyms,
   numerals — still renders in Fira Sans, and only Han falls through.
   Scoped by :lang() so the CJK families never enter the cascade on the other
   six languages. `:lang(zh)` also matches `zh-tw` per CSS language-range
   rules, so the more specific `zh-TW` rule comes second, to win. */
:lang(zh) body,
body:lang(zh) {
    font-family: "Fira Sans", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", system-ui, sans-serif;
    line-height: 1.7;
}
:lang(zh-TW) body,
body:lang(zh-TW) {
    font-family: "Fira Sans", "PingFang TC", "Microsoft JhengHei", "Noto Sans CJK TC", system-ui, sans-serif;
    line-height: 1.7;
}

img { max-width: 100%; }

/* The single column. Header, main and footer share it so the wordmark, the
   text and the footer rules all line up on the same two edges. */
header, main, footer { max-width: 40rem; margin: 0 auto; padding: 0 1.25rem; }

a { color: var(--accent); text-underline-offset: 3px; }
a:hover { color: var(--accent-hover); }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ------------------------------------------------------------ 4. Skip link */
.skip {
    position: absolute;
    top: -100%;
    left: 0.5rem;
    z-index: 10;
    padding: 0.5rem 0.75rem;
    background: var(--fg);
    color: var(--bg);
    font-size: 0.875rem;
    font-weight: 600;
    text-decoration: none;
}
.skip:focus { top: 0.5rem; }

/* --------------------------------------------------------------- 5. Header
   Sticky so the wordmark stays reachable in a long story. `is-stuck` is added
   by app.js once the page has scrolled; without JS the bar is still sticky, at
   its full height and without the rule under it.

   Stuck, it shrinks to a little over half its height and the wordmark drops a
   third of its size: a masthead earns its space at the top of the page and
   stops earning it the moment it is a bar over somebody's reading.

   The height is set explicitly rather than left to padding so that giving it
   up can be paid back exactly. A sticky element stays in flow, so a header
   that shrinks by 2rem pulls the whole page up by 2rem under a reader who did
   not ask for that — and the jump lands at the exact moment their eye is
   moving. `margin-bottom` returns precisely the 2rem the height gave up, and
   because both sides animate on the same curve the flow is unchanged in every
   frame of the transition, not merely at its ends. With `box-sizing:
   border-box` the hairline is inside the height, so it does not disturb the
   arithmetic either. */
.site-header {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    height: 4.5rem;
    background: var(--bg);
    transition: height 0.18s ease, margin-bottom 0.18s ease;
}
.site-header.is-stuck {
    height: 2.5rem;
    margin-bottom: 2rem;
    border-bottom: 1px solid var(--border);
}
.wordmark {
    font-size: 1.375rem;
    font-weight: 700;
    color: var(--fg);
    text-decoration: none;
    transition: font-size 0.18s ease;
}
.site-header.is-stuck .wordmark { font-size: 1rem; }
.wordmark:hover { color: var(--accent); }

/* ----------------------------------------------------------------- 6. Type */
h1 { margin: 2.5rem 0 1.75rem; font-size: 2.5rem; line-height: 1.1; font-weight: 700; }
@media (max-width: 900px) { h1 { font-size: 2rem; } }
@media (max-width: 480px) { h1 { font-size: 1.75rem; } }

.meta {
    margin: 0;
    font-size: 0.6875rem;
    line-height: 1.1;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}
.meta a { color: var(--muted); text-decoration: none; }
.meta a:hover { color: var(--accent); }

/* A run-in title with a rule above it: "Yesterday's edition", "Sources". */
.section-title {
    margin: 3.5rem 0 0;
    padding-top: 0.75rem;
    border-top: 1px solid var(--fg);
    font-size: 1.375rem;
    line-height: 1.15;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}
.section-title a { color: var(--fg); text-decoration: none; }
.section-title a:hover { color: var(--accent); }
.section-title .count { color: var(--muted); }

/* An empty day, an empty topic, no search results: a statement, not an error. */
.note { margin: 0; font-size: 1.1875rem; line-height: 1.45; color: var(--muted); }
.note-tight { margin: 0.875rem 0 0; font-size: 1rem; line-height: 1.45; color: var(--muted); }

/* ------------------------------------------------- 7. Edition story list
   Two columns: the emoji marker, then everything else. `compact` is the
   previous-days and archive-month variant — headline only, tighter padding. */
.stories { list-style: none; margin: 0; padding: 0; }
.stories li {
    display: grid;
    grid-template-columns: 2.75rem 1fr;
    padding: 1.375rem 0;
    border-top: 1px solid var(--border);
}
.stories .n {
    font-size: 1.375rem;
    line-height: 1.15;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.stories h2, .stories h3 { margin: 0 0 0.5rem; font-size: 1.375rem; line-height: 1.15; font-weight: 600; }
.stories li:first-child h2 { font-size: 1.625rem; }
.stories h2 a, .stories h3 a { color: var(--fg); text-decoration: none; }
.stories h2 a:hover, .stories h3 a:hover { color: var(--accent); }
.stories p { margin: 0 0 0.5rem; }
.stories .meta { margin: 0; }
.stories.compact li { padding: 0.625rem 0; }
.stories.compact .n, .stories.compact h3 { font-size: 1.125rem; margin: 0; }

/* A previous day's block on the homepage, and a day block in the archive month. */
.day-block { margin: 0; }
.day-block .stories { margin-top: 0.625rem; }

/* Wide-screen edition layout.

   Everything above is the phone layout: one story per row, one column. Past
   the breakpoint the edition pages widen the column they share with the header
   and the footer — the same 71rem the story page uses, so the two page types
   line up on the same two edges — and lay the edition out as a mosaic rather
   than a single tall stack.

   The mosaic is 6 columns wide because that divides by both 2 and 3: the two
   lead stories take three columns each, and everything after them takes two,
   so a full five-story edition falls out as 2 on the first row and 3 on the
   second with no per-item rules. The daily edition is capped at five stories
   (app.daily_edition.max_stories), so that is the shape almost every day has.

   Short editions are the exception, and each gets its own answer rather than
   being left lopsided: three stories become one row of three, and a lone story
   drops back to a single reading column, because one story stretched across a
   third of a 71rem page reads as a layout that broke. */
@media (min-width: 64rem) {
    body.edition-page header,
    body.edition-page main,
    body.edition-page footer { max-width: 71rem; }

    .stories:not(.compact) {
        display: grid;
        grid-template-columns: repeat(6, minmax(0, 1fr));
        column-gap: 2.5rem;
        align-items: start;
    }
    .stories:not(.compact) > li { grid-column: span 2; }
    .stories:not(.compact) > li:nth-child(-n+2) { grid-column: span 3; }

    /* Both stories on the lead row carry the larger headline, not just the
       first: side by side they are peers, and sizing only one of them would
       read as an accident. */
    .stories:not(.compact) > li:nth-child(2) h2 { font-size: 1.625rem; }

    /* A summary in a two-column cell runs about half as wide and so twice as
       tall as it does in the single column, which leaves the row ragged. The
       lead pair keeps its full summary; the rest are clipped to five lines,
       with the whole text a click away on the story itself. */
    .stories:not(.compact) > li:nth-child(n+3) p:not(.meta) {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 5;
        line-clamp: 5;
        overflow: hidden;
    }

    .stories:not(.compact):has(> li:nth-child(3):last-child) > li { grid-column: span 2; }
    .stories:not(.compact):has(> li:only-child) { grid-template-columns: minmax(0, 42rem); }

    /* A day of history is headlines only, so it columnises much harder than
       the edition does: three across turns a week of scrolling into a block
       the eye takes in at once. Row-wise fill keeps the ranking order. */
    .stories.compact {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        column-gap: 2.5rem;
        align-items: start;
    }
    .stories.compact li { grid-template-columns: 2rem 1fr; }
}

/* ----------------------------------------------------------- 8. Story page */
article .standfirst { margin: 0 0 0.5rem; font-size: 1.375rem; line-height: 1.35; }
article p { font-size: 1.1875rem; line-height: 1.45; margin: 0 0 1em; }
article section h2 {
    margin: 2.25rem 0 0.625rem;
    font-size: 0.75rem;
    line-height: 1.1;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
}
article .body-h2 { font-size: 2rem; line-height: 1.15; font-weight: 700; margin: 2rem 0 0.75rem; }
@media (max-width: 480px) { article .body-h2 { font-size: 1.75rem; } }

/* Sources, grouped by publication date. The favicon is decorative — the
   outlet name carries the meaning — so it takes alt="" and a fixed box that
   holds its place whether or not the file exists. */
.source-group h3 {
    margin: 1.375rem 0 0.375rem;
    font-size: 0.6875rem;
    line-height: 1.1;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}
.sources { list-style: none; margin: 0; padding: 0; }
.sources li {
    display: grid;
    grid-template-columns: 16px 1fr;
    gap: 0.625rem;
    padding: 0.625rem 0;
    border-top: 1px solid var(--border);
    font-size: 1rem;
    line-height: 1.4;
}
.sources .icon { width: 16px; height: 16px; margin-top: 0.25rem; display: block; }
.sources .outlet { font-weight: 600; }
.sources .date { color: var(--muted); font-size: 0.875rem; white-space: nowrap; }

/* Topics, people, organizations at the end of a story: a label column and a
   wrapping list. */
.tags {
    margin-top: 2.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
    font-size: 0.875rem;
    line-height: 1.45;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.tags > div { display: flex; gap: 1rem; }
.tags .label {
    width: 6rem;
    flex: none;
    font-size: 0.6875rem;
    line-height: 1.8;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}
.tags .list { display: flex; flex-wrap: wrap; gap: 0.25rem 0.875rem; }
.tags a { text-decoration: none; }
.tags a:hover { text-decoration: underline; }

/* A person named in the body text, linked to their own page. Subtle on
   purpose: a dotted underline in the body colour, not another red link. A
   paragraph where every name is an accent-coloured link stops reading like
   prose. */
.person-mention {
    color: inherit;
    text-decoration: underline dotted var(--muted);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
}
.person-mention:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* The scannable spine of the story, directly under the standfirst: a boxed
   list a reader can take in without reading the prose. It is the one block a
   reader who reads nothing else should still leave with, so it gets a tint
   and not only a rule. The tint is deliberately faint — a few percent off the
   page background, no coloured fill — because the page has exactly one accent
   colour and it belongs to links. The negative inline margin bleeds the panel past the text measure
   so the bullets stay on the body's left edge instead of stepping in; 1.125rem
   is smaller than main's 1.25rem gutter and than the story grid's 3rem
   column gap, so the bleed never reaches the viewport edge or the sidebar. */
.key-points {
    margin: 1.75rem -1.125rem 0;
    padding: 0.875rem 1.125rem 1rem;
    background: var(--surface);
    border-top: 1px solid var(--fg);
    border-bottom: 1px solid var(--border);
}
article .key-points h2 { margin-top: 0; }
.key-points ul { margin: 0; padding: 0 0 0 1.125rem; }
.key-points li { font-size: 1.0625rem; line-height: 1.4; margin-bottom: 0.375rem; }
.key-points li:last-child { margin-bottom: 0; }
.key-points li::marker { color: var(--muted); }

/* What people said. Usually our summary of a statement rather than a
   transcript, so: a rule and an indent, and deliberately no oversized
   quotation mark that would claim these are the exact words. */
.quotes { margin-top: 0.5rem; }
.quote { margin: 0 0 1.5rem; }
.quote figcaption {
    margin: 0 0 0.375rem;
    font-size: 0.8125rem;
    line-height: 1.3;
    color: var(--muted);
}
.quote figcaption i { font-style: normal; }
.quote figcaption b { color: var(--fg); font-weight: 600; }
.quote blockquote {
    margin: 0;
    padding: 0.125rem 0 0.125rem 1rem;
    border-left: 3px solid var(--border);
    font-size: 1.0625rem;
    line-height: 1.5;
}
.quotes .section-title + .quote { margin-top: 1rem; }

/* In the sidebar the statements are apparatus, not body copy, so they take the
   sidebar's smaller measure — the same step down the source list and the
   related items already take. The rule down the left stays: it is what says
   "these are words attributed to someone" without claiming they are verbatim. */
.story-aside .quote blockquote {
    font-size: 0.9375rem;
    line-height: 1.45;
    padding-left: 0.75rem;
}
.story-aside .quote { margin-bottom: 1.125rem; }

/* Wide-screen story layout. Everything above is the phone layout and stays
   the shared single column; only past the breakpoint does the story page
   widen the column it shares with the header and the footer (so all three
   still line up on the same two edges) and split the article into a reading
   column that keeps its measure and a narrower column for the apparatus. */
.story-head, .story-body, .story-aside { min-width: 0; }

@media (min-width: 64rem) {
    body.story-page header,
    body.story-page main,
    body.story-page footer { max-width: 71rem; }

    .story {
        display: grid;
        grid-template-columns: minmax(0, 40rem) minmax(14rem, 1fr);
        column-gap: 3rem;
        align-items: start;
    }
    .story-head { grid-column: 1; grid-row: 1; }
    .story-body { grid-column: 1; grid-row: 2; }
    .story-aside { grid-column: 2; grid-row: 1 / span 2; }

    /* One apparatus column until there is genuinely room for two. Splitting at
       the same breakpoint the article splits at would give each half about
       7rem, which is narrower than a source headline wants and would set the
       whole right side in two-word lines. */
    .aside-column > :first-child .section-title,
    .aside-column > .section-title:first-child { margin-top: 2.5rem; }

    /* At the top of the side column the "Sources" rule is a beginning, not a
       break in a long page, so it loses the big gap it needs when stacked. */
    .story-aside .section-title { margin-top: 2.5rem; }
    .story-aside .tags { margin-top: 2rem; }
    .story-aside .tags > div { flex-direction: column; gap: 0.125rem; }
    .story-aside .tags .label { width: auto; line-height: 1.4; }
}

/* Three columns: the article, then the apparatus split in two.
   1120px is where the two apparatus columns each clear ~15rem, which is the
   width at which a source headline stops setting in two-word lines. */
@media (min-width: 70rem) {
    body.story-page header,
    body.story-page main,
    body.story-page footer { max-width: 82rem; }

    .story {
        grid-template-columns: minmax(0, 40rem) minmax(0, 1fr);
        column-gap: 3rem;
    }

    .story-aside {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        column-gap: 2.5rem;
        align-items: start;
    }

    /* Each column starts its own flow, so the first rule in either one is a
       beginning rather than a break part-way down a long page — and whatever
       happens to lead a column starts flush with it, since which block that is
       depends on what the story has. */
    .aside-column > *:first-child { margin-top: 0; }
    .aside-column > *:first-child .section-title,
    .aside-column > .section-title:first-child { margin-top: 0; }
}

/* The line a reader meets when a saved story link no longer resolves. Set as
   a notice rather than an error: nothing has gone wrong, the edition simply
   moved on, and red would tell them otherwise. */
.note.removed {
    margin: 2rem 0 0;
    padding: 0.875rem 1rem;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    font-size: 1rem;
}

/* The parody comment. Styled to read as an aside and never as a quotation:
   no quote marks, no rule down the left, nothing borrowed from how the story's
   real quotes are set. The note under the heading is the same small uppercase
   caption the rest of the page uses for metadata, because that is what it is —
   a statement about what this text is, not part of the joke. */
/* The parody sits below the statements, so it needs its own top margin — it
   used to lead the column and take its spacing from that. Same 2.5rem as the
   related list, which is the gap between two unrelated sidebar blocks. */
.commentary { margin: 2.5rem 0; }
.commentary .section-title { margin-top: 0; }
.commentary-note {
    margin: 0.375rem 0 0;
    font-size: 0.6875rem;
    line-height: 1.1;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}
.commentary-text {
    margin: 0.625rem 0 0;
    font-size: 1rem;
    line-height: 1.4;
    color: var(--fg);
}

/* Related news in the story aside: date over headline, no summary. The date
   is the small print and the headline is the link, which is the opposite of
   the sources list above it — there the outlet is the small print. Both are
   lists of links under a story; making them look identical would invite a
   reader to think they lead to the same kind of place. */
.related { margin-top: 2.5rem; }
.related ul { list-style: none; margin: 0.625rem 0 0; padding: 0; }
.related li { padding: 0.75rem 0; border-top: 1px solid var(--border); }
.related time {
    display: block;
    margin-bottom: 0.2rem;
    font-size: 0.6875rem;
    line-height: 1.1;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}
.related a { color: var(--fg); text-decoration: none; font-size: 1rem; line-height: 1.25; }
.related a:hover { color: var(--accent); }

/* ---------------------------------------------------------- 9. Topic index */
.topics {
    list-style: none;
    margin: 0;
    padding: 1rem 0 0;
    border-top: 1px solid var(--border);
    columns: 3;
    column-gap: 2rem;
    font-size: 1.125rem;
    line-height: 1.15;
}
.topics li { padding: 0.5rem 0; break-inside: avoid; }
.topics a { color: var(--fg); text-decoration: none; }
.topics a:hover { color: var(--accent); }
@media (max-width: 700px) { .topics { columns: 1; } }

/* -------------------------------------------------------------- 10. Archive */
.years, .months { list-style: none; margin: 0; padding: 0; font-size: 1.375rem; line-height: 1.15; font-weight: 600; }
.years li, .months li {
    padding: 1.125rem 0;
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
}
.years a, .months a { color: var(--fg); text-decoration: none; }
.years a:hover, .months a:hover { color: var(--accent); }
.months .count {
    font-size: 0.6875rem;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

/* --------------------------------------------------------------- 11. Search
   A three-column grid on wide screens, one column narrow. The query spans the
   full width; every control carries a visible label. */
form.search {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
    padding-bottom: 1.75rem;
    border-bottom: 1px solid var(--border);
}
form.search .wide { grid-column: 1 / -1; }
form.search label { display: flex; flex-direction: column; gap: 0.375rem; }
form.search .field-label {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}
@media (max-width: 700px) { form.search { grid-template-columns: 1fr; } }

input, select, button {
    font: inherit;
    font-size: 1rem;
    padding: 0.625rem 0.75rem;
    border: 1px solid var(--border);
    background: var(--input-bg);
    color: var(--fg);
    border-radius: 0;
    width: 100%;
    box-sizing: border-box;
}
button {
    width: auto;
    background: var(--fg);
    color: var(--bg);
    border: 0;
    font-weight: 600;
    padding: 0.625rem 1.125rem;
    cursor: pointer;
}

.results { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.results li {
    padding: 1.375rem 0;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.results h2 { margin: 0; font-size: 1.375rem; line-height: 1.15; font-weight: 600; }
.results h2 a { color: var(--fg); text-decoration: none; }
.results h2 a:hover { color: var(--accent); }
.results p { margin: 0; }
.result-summary { margin: 1.5rem 0 0; }

/* ----------------------------------------------------------- 12. Pagination
   The unavailable direction is a muted span, never a dead link. */
.pagination {
    display: flex;
    justify-content: space-between;
    margin-top: 2rem;
    padding-top: 0.875rem;
    border-top: 1px solid var(--border);
    font-size: 0.875rem;
}
.pagination a { text-decoration: none; }
.pagination a:hover { text-decoration: underline; }
.pagination span { color: var(--muted); }

/* --------------------------------------------------------------- 13. Footer */
footer {
    margin-top: 3.5rem;
    padding-top: 1.25rem;
    padding-bottom: 2.5rem;
    border-top: 1px solid var(--border);
    font-size: 0.875rem;
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
}
footer nav { display: flex; flex-wrap: wrap; gap: 0.375rem 1.125rem; }
footer a { text-decoration: none; }
footer a:hover { text-decoration: underline; }
footer [aria-current] { font-weight: 600; color: var(--fg); }

/* The language switcher is a native <details>: all six links are in the HTML
   and it opens and closes with no JavaScript. app.js only adds outside-click
   and Escape closing. */
footer details summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    gap: 0.375rem;
    align-items: center;
    color: var(--accent);
}
footer details summary::-webkit-details-marker { display: none; }
footer details summary:hover { text-decoration: underline; }
footer details ul {
    list-style: none;
    margin: 0.625rem 0 0;
    padding: 0.625rem 0 0;
    border-top: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem 1.125rem;
}
.switcher-label {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

/* The theme toggle only exists once JS has run — a control that cannot work
   without a script must not be painted before the script arrives. */
.theme-toggle {
    display: none;
    align-self: flex-start;
    width: auto;
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    font-size: 0.875rem;
    font-weight: 400;
    cursor: pointer;
}
.theme-toggle:hover { text-decoration: underline; }
.js .theme-toggle { display: inline-flex; gap: 0.375rem; align-items: center; }

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

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

/* The reader's dismissal, applied by the inline script in the document head
   before the first paint. `!important` is not needed and not used: nothing else
   sets `display` on this section. */
.commentary-hidden .commentary {
    display: none;
}

.commentary {
    position: relative;
}

/* Hidden until app.js marks the document scripted, on the same rule as the
   theme toggle: a button that only JavaScript can implement must not be
   offered to a browser that will never run it. The About page's switch and the
   whole section around it follow the same rule — a disclaimer that survives
   without script is one thing, a switch that cannot switch is another. */
.commentary-dismiss,
.commentary-switch,
.tracking-switch {
    display: none;
}

.js .commentary-switch,
.js .tracking-switch {
    display: block;
}

/* A switch rather than a button, because a lone button labelled with an action
   («Показувати «Трампампам»») reads just as naturally as a statement of the
   current setting, and readers took it that way. The label here is fixed and
   the control carries the state, so there is nothing left to misread.

   Built on a real checkbox: `appearance: none` only takes away the native
   painting. With this rule dropped it degrades to an ordinary tickbox that
   still works, still labels itself and is still keyboard-reachable. */
.switch {
    display: inline-flex;
    align-items: center;
    gap: 0.625rem;
    cursor: pointer;
}

.switch input {
    appearance: none;
    -webkit-appearance: none;
    position: relative;
    flex: none;
    width: 2.25rem;
    height: 1.25rem;
    margin: 0;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg);
    cursor: pointer;
    transition: background-color 0.15s, border-color 0.15s;
}

/* The knob. `left` rather than a transform so the travel is the same whichever
   way the track is sized. */
.switch input::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0.1875rem;
    width: 0.875rem;
    height: 0.875rem;
    border-radius: 50%;
    background: var(--muted);
    transform: translateY(-50%);
    transition: left 0.15s, background-color 0.15s;
}

.switch input:checked {
    border-color: var(--accent);
    background: var(--accent);
}

.switch input:checked::after {
    left: calc(100% - 1.0625rem);
    background: var(--bg);
}

.switch input:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .switch input,
    .switch input::after { transition: none; }
}

.js .commentary-switch button:hover,
.js .commentary-switch button:focus-visible {
    border-color: var(--muted);
}

.js .commentary-dismiss {
    position: absolute;
    top: 0;
    right: 0;
    display: block;
    padding: 0 0.25rem;
    border: 0;
    background: none;
    color: var(--muted);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    /* A joke's dismiss button should not compete with the joke. */
    opacity: 0.5;
    transition: opacity 0.15s ease;
}

.js .commentary-dismiss:hover,
.js .commentary-dismiss:focus-visible {
    opacity: 1;
}

/* A copy-paste watermark, not decoration.

   The reason it is transparent and 1px rather than `display: none` is the whole
   feature: browsers exclude `display: none` and `visibility: hidden` text from
   the selection and so from the clipboard, which is precisely where this text
   needs to end up. Someone pasting the joke into a group chat carries none of
   this page's context with it — not the heading, not the About link — so the
   pasted line has to say for itself that a machine wrote it.

   It stays in the accessibility tree on purpose: a screen-reader user should
   hear it too. */
.commentary-watermark {
    font-size: 1px;
    line-height: 1;
    color: transparent;
    /* Belt and braces against a future `user-select: none` on an ancestor. */
    user-select: text;
    -webkit-user-select: text;
}

/* Entity reference box: the checkable half of a person/organization page.
   Everything above it is model-written prose; this is Wikidata, verified by
   label and by type before it was stored. Visually quieter than the
   standfirst on purpose — it is a reference, not the lede. */
.entity-facts {
    margin: 1.25rem 0 1.75rem;
    padding: 0.85rem 1rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--input-bg);
    font-size: 0.9rem;
}

.entity-facts-title {
    margin: 0 0 0.5rem;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

.entity-facts dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.3rem 0.85rem;
    margin: 0;
}

.entity-facts dt {
    color: var(--muted);
    white-space: nowrap;
}

.entity-facts dd {
    margin: 0;
    /* A long official URL must wrap rather than widen the grid column and
       push the page into a horizontal scroll on a phone. */
    overflow-wrap: anywhere;
}

.entity-facts-source {
    margin: 0.6rem 0 0;
    font-size: 0.75rem;
    color: var(--muted);
}

/* -------------------------------------------------- 15. Visited articles
   A story you have already opened is dimmed. public/js/app.js keeps the list
   of what this browser has read — the page itself is one cached response
   served to everybody and cannot know — and puts `is-visited` on the block.

   Dimmed rather than hidden or greyed out, because a headline you read this
   morning is the thing you use to find your way back to it this evening; and
   restored under the pointer, so a muted block is never harder to read than
   an unread one. With JavaScript blocked the class never lands and every list
   renders exactly as it does today. */
.stories li.is-visited,
.results li.is-visited,
.related li.is-visited,
.sources li.is-visited {
    opacity: 0.8;
}

.stories li.is-visited:hover,
.results li.is-visited:hover,
.related li.is-visited:hover,
.sources li.is-visited:hover {
    opacity: 1;
}

/* ------------------------------------------------------- 16. About page
   A stack of short, independent sections, which is the one shape on this site
   that a single 40rem column serves badly: on a desktop screen it left two
   thirds of the window empty and pushed the analytics switch — the only
   control on the page — below the fold.

   Multi-column rather than a grid, because these sections have wildly
   different heights and a two-column grid would align them into rows and leave
   a hole under every short one. Columns flow instead, and `break-inside` keeps
   a heading from being orphaned from its paragraph. Reading order is
   unchanged: down the first column, then down the second.

   The heading and the standfirst span both columns; the standfirst keeps a
   reading measure of its own, because a single sentence set across 71rem is
   not a standfirst, it is a banner. Below 64rem none of this applies and the
   page is the same single column it has always been. */
@media (min-width: 64rem) {
    body.about-page header,
    body.about-page main,
    body.about-page footer { max-width: 71rem; }

    body.about-page article {
        columns: 2;
        column-gap: 4rem;
    }

    body.about-page article > h1,
    body.about-page article > .standfirst { column-span: all; }

    body.about-page article > .standfirst { max-width: 45rem; }

    body.about-page article > section {
        break-inside: avoid;
        margin-bottom: 2.5rem;
    }
}
