/*
    Public reader design system. Hand-written (no Tailwind build, no MudBlazor) to reproduce the
    Lovable reference design: dark-default, editorial, mobile-first, Inter + JetBrains Mono with
    mono uppercase micro-labels and an accent pulse-dot. Tokens come straight from the design's
    compiled CSS; theme is switched by a class on <html> (.theme-dark default / .theme-light).
*/

/* ---- Breakpoint layers (documentation only) ----
   The stylesheet has FOUR responsive layers, each encoding a real content boundary rather than a device
   name. Every @media rule in this file belongs to one of them (plus prefers-reduced-motion and
   prefers-color-scheme, which are preference queries, not layers):

     639 / 640   compact chrome    scrollable .tabs, .home-type-filter, .search-form <-> .search-toggle,
                                   footer row/column, the whole mobile card + filter treatment
     767 / 768   navigation        drawer <-> .cat-nav rail, .view-grid two columns, .hero, .hot-grid,
                                   .cat-box-grid, .nav-links
     899 / 900   wide layout       .hero-overlay-grid 4 columns, .event-grid two columns
     1024        sidebar           .feed-grid 1fr 320px, wider .search-input

   WARNING: the variables below are DOCUMENTATION. A CSS custom property cannot be used in a media
   query condition (@custom-media is not natively supported), so every @media rule spells the value out
   as a literal. Do NOT try to write @media (min-width: var(--bp-sm)) — it silently never matches.
   When adding a rule, pick one of these four layers; a new orphan breakpoint needs a stated reason. */
:root {
    --bp-sm: 640px;    /* compact chrome -> full chrome */
    --bp-md: 768px;    /* drawer -> .cat-nav rail; one column -> multiple columns */
    --bp-lg: 900px;    /* wide layout: hero 4 columns, event-grid two columns */
    --bp-xl: 1024px;   /* feed sidebar, wider search input */
}

/* ---- Design tokens ----
   :root carries the dark defaults (so a class-less <html> still paints dark, matching the
   default). .theme-light overrides them and is declared AFTER, so at equal specificity it wins
   on html.theme-light. Do NOT add a trailing :root override below — that would beat .theme-light
   and leave the page background/text stuck on dark while only some tokens switched. */
:root,
.theme-dark {
    --background: #08090d;
    --foreground: #f8fafc;
    --card: #0c0e13;
    --primary: #5f93e8;
    --secondary: #151c28;
    --muted: #111822;
    --muted-foreground: #8b94a7;
    --accent: #ef4343;
    --border: #1d2839;
    --surface: #0e131b;
    --surface-hover: #141b26;
    --ring: #5f93e8;

    /* Search-result highlighting (search redesign F2 / design §7). A muted amber that reads as a marker
       without competing with --primary. Measured contrast against its own foreground: 9.16:1 — the design
       requires AA 4.5:1 and this clears it with room, which matters because a <mark> run sits inside a
       normal-weight sentence. */
    --highlight: #4a3a12;
    --highlight-foreground: #fde9b0;

    /* Text laid on a solid --primary surface. A token rather than a literal so the search page's CSS
       can stay hex-free, and so a future primary that needs dark text has one place to change. */
    --primary-foreground: #ffffff;

    /* Per-category accent tokens (dark theme). The single source of category color; each is used as
       a label/text color on the near-black surfaces and as a tint, so the dark values are kept light
       enough (high luminance) to clear WCAG AA as text. Tuned, not arbitrary — see CategoryAccent.cs
       for the key list and the unknown-key fallback. */
    --cat-politics:  #5b9bf6;   /* blue */
    --cat-economy:   #45c07a;   /* green */
    --cat-world:     #2dd4bf;   /* turquoise */
    --cat-sports:    #f0b429;   /* amber */
    --cat-tech:      #a78bfa;   /* violet */
    --cat-culture:   #f472b6;   /* rose */
    --cat-lifestyle: #fb8265;   /* coral */
    --cat-weather:   #5ec5f2;   /* sky */
    --cat-tabloid:   #e879f9;   /* magenta */
    --cat-health:    #34d399;   /* emerald */
    --cat-education: #818cf8;   /* indigo */
    --cat-crime:     #ef6a52;   /* brick */
    --cat-accidents: #fb923c;   /* orange — accidents & disasters (hazard family with crime) */
    --cat-uncategorized: #8b94a7; /* neutral gray — the "Egyéb" bucket */
}

.theme-light {
    --background: #fbfaf9;
    --foreground: #101219;
    --card: #ffffff;
    --primary: #3567c2;
    --secondary: #f3f4f6;
    --muted: #f3f4f6;
    --muted-foreground: #5b6472;
    --accent: #df2020;
    --border: #dcdfe4;
    --surface: #ffffff;
    --surface-hover: #f3f4f6;
    --ring: #3567c2;

    /* Search-result highlighting (light). Warm, low-saturation yellow, the classic marker.
       Measured contrast against its own foreground: 11.03:1. */
    --highlight: #fdeba8;
    --highlight-foreground: #3b2f0b;

    /* Text laid on a solid --primary surface (see the dark block). */
    --primary-foreground: #ffffff;

    /* Per-category accent tokens (light theme). Deeper than the dark variants so they clear WCAG AA
       as text on the warm paper background. Same hues, lower lightness. */
    --cat-politics:  #1d63d6;
    --cat-economy:   #1f8a4c;
    --cat-world:     #0f8f86;
    --cat-sports:    #a66a00;
    --cat-tech:      #6d3fd1;
    --cat-culture:   #c63d86;
    --cat-lifestyle: #d24a32;
    --cat-weather:   #0e7fb8;
    --cat-tabloid:   #b13ec6;
    --cat-health:    #0c8a5e;
    --cat-education: #4f46e5;
    --cat-crime:     #c0392b;
    --cat-accidents: #c2560c;   /* deeper orange for the light paper background */
    --cat-uncategorized: #6b7280; /* neutral gray — the "Egyéb" bucket */
}

/* ---- Category accent mapping ----
   Any element marked with data-cat gets a local --cat-accent it can read for its label color and
   accent bar. The base rule provides a neutral fallback (used for unknown keys until their inline
   hashed hue from CategoryAccent.InlineStyle overrides it). Each known key maps its curated token
   onto --cat-accent. This keeps the per-category color in ONE place (the tokens above) while
   components just say data-cat="@key". */
[data-cat] { --cat-accent: var(--primary); }
[data-cat="politics"]  { --cat-accent: var(--cat-politics); }
[data-cat="economy"]   { --cat-accent: var(--cat-economy); }
[data-cat="world"]     { --cat-accent: var(--cat-world); }
[data-cat="sports"]    { --cat-accent: var(--cat-sports); }
[data-cat="tech"]      { --cat-accent: var(--cat-tech); }
[data-cat="culture"]   { --cat-accent: var(--cat-culture); }
[data-cat="lifestyle"] { --cat-accent: var(--cat-lifestyle); }
[data-cat="weather"]   { --cat-accent: var(--cat-weather); }
[data-cat="tabloid"]   { --cat-accent: var(--cat-tabloid); }
[data-cat="health"]    { --cat-accent: var(--cat-health); }
[data-cat="education"] { --cat-accent: var(--cat-education); }
[data-cat="crime"]     { --cat-accent: var(--cat-crime); }
[data-cat="accidents"] { --cat-accent: var(--cat-accidents); }
[data-cat="uncategorized"] { --cat-accent: var(--cat-uncategorized); }

/* ---- Base ---- */
* { box-sizing: border-box; }

/* Reader-adjustable text size: --font-scale (set by window.hirheraldFont, persisted in localStorage)
   multiplies the root font-size. 100% resolves to the browser's own default (typically 16px), so the UI
   also respects the reader's browser font preference — and because the UI is rem-based, scaling the root
   cleanly scales the whole page (text + spacing). */
:root { --font-scale: 1; }
html {
    font-size: calc(100% * var(--font-scale));
    -webkit-text-size-adjust: 100%;   /* don't let mobile browsers auto-inflate on top of our scale */
}
html, body {
    margin: 0;
    padding: 0;
    background-color: var(--background);
    color: var(--foreground);
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}
body { font-size: 1rem; }   /* inherit the scaled root (was a hard 16px) */

a { color: inherit; text-decoration: none; }
a:hover { color: var(--primary); }

.mono {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}

.container {
    /* Fixed px so the reader's text-size control (A−/A+) scales the text WITHOUT widening the page — the
       content reflows within a constant-width column (the common news-site behaviour). Was 80rem. */
    max-width: 1280px;
    margin: 0 auto;
    padding: 0 1rem;
}

.muted { color: var(--muted-foreground); }

/* ---- Border convention (2026-08-10) ----
   A frame is chrome, and on this page it is almost never the thing that carries meaning. Labels, chips and
   quiet buttons therefore have NO border: what identifies them is the micro-label typography (mono,
   uppercase, tracked, muted), a colour, or a soft fill. A border is justified only when it does work
   nothing else does:

     • it carries STATE — e.g. .sort-btn / .sort-dir-opt / .type-filter-btn are borderless at rest and
       outlined only when active, so the frame IS the "selected" signal;
     • it is the edge of a real surface — cards, boxes, the drawer, alerts, table cells;
     • it belongs to a form control, where the box is the affordance (inputs, date fields, .chip-opt).

   Everything else lost its frame in this pass: .source-agg-out-mark, .timeline-badge--out,
   .timeline-region, .media-type-chip, .rep-tag, .filter-badge, .coverage-more-btn, .load-more-btn. Add a
   border to a new label only if it fails all three tests above. */

/* Micro-label: small, uppercase, tracked, mono. Used for section headers and meta. */
.label {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 0.625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--muted-foreground);
}

.label-accent { color: var(--accent); }
.label-primary { color: var(--primary); }

/* Accent pulse dot for "live"/"hot" markers. */
.pulse-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 9999px;
    background: var(--accent);
    display: inline-block;
    animation: pulse-dot 1.8s ease-in-out infinite;
}
@keyframes pulse-dot {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}

/* Card entrance animation (staggered via inline animation-delay). */
.entrance { animation: entrance 0.4s ease both; }
@keyframes entrance {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .pulse-dot, .entrance { animation: none; }
}

/* ---- Header / nav ---- */
.nav {
    position: sticky;
    top: 0;
    z-index: 50;
    border-bottom: 1px solid var(--border);
    background: color-mix(in srgb, var(--background) 80%, transparent);
    backdrop-filter: blur(12px);
}
.nav-inner {
    display: flex;
    height: 3.5rem;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}
/* Flex hygiene (mobile redesign F2.1). The old pair was `.nav-left { min-width: 0 }` with no flex basis
   on either side: at 375px the left block collapsed to a measured width of ZERO while its children
   (the toggle at 16-43px and the brand at 67-202px) kept their intrinsic size and spilled out, painting
   on top of the right-hand buttons — the visible "logo under the filter icon" bug.

   min-width: auto stops the collapse; the right block is the one allowed to shrink, because its
   contents are icons that can wrap or hide. Note this alone would NOT have been enough: the header's
   natural content is ~486px in a 375px band, so the real fix is fewer controls below 640px (see the
   compact-chrome block further down), not tighter flex rules. */
.nav-left  { display: flex; align-items: center; gap: 1.5rem; flex: 0 0 auto; min-width: auto; }
.brand {
    font-family: 'JetBrains Mono', monospace;
    font-weight: 700;
    letter-spacing: -0.03em;
    color: var(--primary);
    font-size: 1rem;
    flex-shrink: 0;
}
/* Yin-yang wordmark: one continuous brand-coloured line flows around the two halves. "hir" is a soft-filled
   tag whose TOP + RIGHT edges carry the line; "herald" carries the same line along its BOTTOM. Both spans use
   a full transparent 2px border (so their box heights match and the coloured edges meet exactly at the shared
   corner), and only the intended sides are coloured. The fill is a lightened primary (mixed toward the
   surface) so it stays softer than the line in both themes. */
.brand-hir,
.brand-herald {
    display: inline-block;
    vertical-align: baseline;
    color: var(--primary);
    padding: 0.06em 0.16em;
    border: 2px solid transparent;   /* equalises box height; individual sides coloured below */
}
.brand-hir {
    background: color-mix(in srgb, var(--primary) 60%, var(--surface));   /* filled tag, still softer than the line */
    color: #fff;   /* white letters on the filled tag ("herald" stays in the brand colour) */
    border-top-color: var(--primary);
    border-right-color: var(--primary);
    /* Right-angle turns (no rounding): the line meets at sharp corners. */
    border-radius: 0;
    padding-right: 0.22em;
}
.brand-herald {
    border-bottom-color: var(--primary);
    border-radius: 0;
}
/* Beta tag next to the wordmark (config Site:Beta). A small muted superscript badge in the brand colour and
   the same mono font as the wordmark — design-linked but deliberately low-emphasis (soft fill + border, small
   size), so it reads as a status marker without competing with the logo. Sharp corners match the wordmark. */
.brand-beta {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.5rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
    border-radius: 0;
    padding: 0.1em 0.32em;
    margin-left: 0.3rem;
    vertical-align: super;
    line-height: 1;
}
/* Deployment badge next to the wordmark — non-production only (see PublicLayout / BuildInfo). Same mono,
   low-emphasis shape as the beta tag, but in the neutral muted colour rather than the brand one: it is
   diagnostic furniture, not part of the identity, and it must never read as a product label. Its whole job
   is making a dev tab unmistakable next to a prod one, so it sits on the baseline (not superscript) and
   keeps the full identity in its tooltip. */
/* Deployment identity (non-production only), at the foot of the page under its own rule — see the
   .footer-build block in the footer section. It was a bordered badge beside the wordmark until
   2026-08-10; both the frame and the header slot are gone. The frame existed to separate a badge from
   the brand it sat next to, and there is nothing to separate it from down here — a rule above the row
   already does that job, and a second box inside it would just be noise around three words of text. */
.build-info {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.6rem;
    color: var(--muted-foreground);
    line-height: 1;
    white-space: nowrap;
    cursor: help;
}
.nav-links { display: none; gap: 1.25rem; font-size: 0.875rem; font-weight: 500; }
.nav-links a { color: var(--muted-foreground); }
.nav-links a:hover, .nav-links a.active { color: var(--foreground); }
.nav-right { display: flex; align-items: center; gap: 0.75rem; flex: 1 1 auto; min-width: 0; justify-content: flex-end; }

/* Compact header (mobile redesign F2.1). Below 640px the header carries exactly three interactive
   elements: the brand (left), search and the drawer toggle (right). Everything else is hidden with CSS
   rather than removed from the markup — desktop needs it, and static SSR renders one DOM for both. The
   hidden controls are not lost: the drawer's "Appearance" section carries a second instance of each
   (see .drawer-section), and detailed search lives on /search anyway. */
@media (max-width: 639px) {
    .nav-right .search-adv,
    .nav-right .font-toggle,
    .nav-right .lang-toggle,
    .nav-right .settings-btn,
    .nav-right .theme-btn { display: none; }
}

/* The search field is the ONLY elastic control in the header — every other item on the right is a
   fixed-size icon button or a segmented pill. Without a shrink allowance the whole right-hand group kept
   its intrinsic width, overflowed its (shrunken) flex box and, since overflow is visible, painted over
   the wordmark. That was the visible collision between roughly 640px and 700px: from 640px the search
   FORM replaces the compact ⌕ toggle, and brand (~140) + form (224) + five controls (~226) + gaps (~76)
   + container padding (32) needs ~698px of viewport. Letting the form give way keeps the row honest at
   every width; above ~700px it never shrinks, so the wide layout is unchanged. */
.search-form { position: relative; display: none; align-items: center; flex: 0 1 auto; min-width: 0; }
/* The fixed controls opt OUT of shrinking, so the space always comes from the search field. */
.nav-right > .icon-btn,
.nav-right > .font-toggle,
.nav-right > .lang-toggle { flex: 0 0 auto; }
.search-input {
    height: 2.25rem;
    width: 14rem;
    /* Follows the form down when the row is tight; 14rem stays the preferred (and, above ~700px, the
       actual) width. */
    max-width: 100%;
    min-width: 0;
    border-radius: 0.375rem;
    border: 1px solid var(--border);
    background: color-mix(in srgb, var(--secondary) 40%, transparent);
    padding: 0 0.75rem;
    color: var(--foreground);
    font-size: 0.75rem;
}
.search-input::placeholder { color: var(--muted-foreground); }
.search-input:focus { outline: none; border-color: var(--primary); }

.lang-toggle {
    display: flex;
    height: 2rem;
    align-items: center;
    border-radius: 0.375rem;
    border: 1px solid var(--border);
    background: color-mix(in srgb, var(--secondary) 30%, transparent);
    padding: 0 0.25rem;
    font-size: 0.625rem;
    font-weight: 700;
    font-family: 'JetBrains Mono', monospace;
}
.lang-toggle a { padding: 0.25rem 0.5rem; color: var(--muted-foreground); }
.lang-toggle a.active { color: var(--primary); }
.lang-sep { width: 1px; height: 0.75rem; background: var(--border); }

/* Text-size control (A− / A+): a segmented pill group mirroring .lang-toggle. The buttons scale the page
   font via window.hirheraldFont; they disable at the min/max bound. */
.font-toggle {
    display: flex;
    height: 2rem;
    align-items: center;
    border-radius: 0.375rem;
    border: 1px solid var(--border);
    background: color-mix(in srgb, var(--secondary) 30%, transparent);
    padding: 0 0.25rem;
    font-family: 'JetBrains Mono', monospace;
    font-weight: 700;
}
.font-btn {
    padding: 0.25rem 0.5rem;
    background: none;
    border: 0;
    color: var(--muted-foreground);
    cursor: pointer;
    line-height: 1;
}
.font-btn:hover:not(:disabled) { color: var(--foreground); }
.font-btn:disabled { opacity: 0.4; cursor: default; }
/* The classic "small A / big A" size cue; the fixed px keeps the buttons stable regardless of the scale. */
.font-btn.font-dec { font-size: 11px; }
.font-btn.font-inc { font-size: 15px; }

.icon-btn {
    width: 2rem;
    height: 2rem;
    border-radius: 0.375rem;
    border: 1px solid var(--border);
    background: color-mix(in srgb, var(--secondary) 30%, transparent);
    color: var(--foreground);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 0.875rem;
}
.icon-btn:hover { background: var(--surface-hover); }

/* ---- Layout grid ----
   CONVENTION (2026-08-10, mobile redesign F1): every fluid grid track is written `minmax(0, 1fr)`,
   never a bare `1fr`. A `1fr` track's automatic minimum is `min-content`, so a track can refuse to
   shrink below its widest unbreakable word — and Hungarian compounds (kulcsfontosságú,
   sajtótájékoztatóinak) are routinely wider than a 375px phone column. Measured on the live site:
   the two-column card grid computed to 447px inside a 343px container, giving the page a
   documentElement.scrollWidth of 453px against a clientWidth of 375px. `minmax(0, 1fr)` drops that
   floor so the text wraps instead of the layout overflowing. */
main { padding: 2rem 0; }
/* On a phone the 2rem gap under the sticky header is dead space above the fold: the first control row
   (the Témák/Cikkek switcher and the filter menus) sits right there, and 32px of nothing pushes the
   first headline further down a screen that has ~660px to work with. The BOTTOM padding keeps its 2rem —
   that one separates the content from the footer, where the breathing room is still wanted. */
@media (max-width: 639px) {
    main { padding-top: 0.75rem; }
}
.feed-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }

/* View shell: the left category nav + the page content. Single column on small screens (the nav
   collapses into a horizontal scroller until F3's drawer); a fixed nav rail + fluid content ≥768px. */
.view-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
@media (min-width: 768px) {
    .view-grid { grid-template-columns: 176px minmax(0, 1fr); gap: 2rem; }   /* px nav rail: fixed under text zoom */
}

/* ---- Category nav (left rail) ----
   Always visible on the main views (default "All" active). A vertical list of the taxonomy, each
   item prefixed by its category-accent dot, so the reader can both navigate and learn the color code.
   Sticky on desktop so it stays in view while the feed scrolls. */
.cat-nav { min-width: 0; }
@media (min-width: 768px) {
    .cat-nav { position: sticky; top: 4.5rem; align-self: start; }
}
.cat-nav-list { display: flex; flex-direction: column; gap: 0.125rem; }
/* On small screens the in-page rail is hidden; the same nav is reached from the header ☰ drawer
   (see .drawer below), so it isn't lost — just relocated. */
@media (max-width: 767px) {
    .view-grid > .cat-nav { display: none; }
}
/* Rail section heading ("NÉZET" / "KATEGÓRIÁK"): names the group of rows under it. Block-level so it takes
   its own line in the rail's flex column, with the item padding's left inset so it lines up with the labels
   below rather than with the accent dots. */
.cat-nav-head {
    display: block;
    padding: 0 0.6rem;
    margin-bottom: 0.35rem;
    color: color-mix(in srgb, var(--muted-foreground) 80%, transparent);
}
/* The first heading sits at the very top of the rail; the second follows the switcher, which already ends
   in a hairline separator, so it only needs a little air above it. */
.cat-nav-head + .seg-control--rail { margin-bottom: 0.65rem; }
.seg-control--rail + .cat-nav-head { margin-top: 0.15rem; }
.cat-nav-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.6rem;
    border-radius: 0.375rem;
    color: var(--muted-foreground);
    font-size: 0.875rem;
    font-weight: 500;
    /* Allow long category names (e.g. "Katasztrófák és balesetek") to wrap within the fixed-width rail
       instead of overflowing past it — otherwise the active highlight (which is the item box, capped at the
       rail width) only covered the part of the text inside the box. The dot stays top-aligned so it sits by
       the first line of a wrapped label. */
    white-space: normal;
    line-height: 1.3;
}
.cat-nav-item .cat-nav-dot { align-self: flex-start; margin-top: 0.28rem; }
.cat-nav-item:hover { background: var(--surface); color: var(--foreground); }
.cat-nav-item.active { background: color-mix(in srgb, var(--cat-accent, var(--primary)) 14%, transparent); color: var(--foreground); font-weight: 700; }
/* The per-category color dot (the legend). Uses the item's own --cat-accent (set via data-cat). */
.cat-nav-dot { width: 0.5rem; height: 0.5rem; border-radius: 9999px; background: var(--cat-accent, var(--muted-foreground)); flex-shrink: 0; }
/* Name + count share the row: the name takes the slack (and wraps inside it), the figure is pushed to the
   right edge so the numbers form a scannable column of their own down the rail. */
.cat-nav-name { flex: 1 1 auto; min-width: 0; }
/* How many events the category has in the current window. Mono and quiet — it is a reading aid beside the
   label, not a second label: tabular figures keep the column aligned across rows, and it never shrinks or
   wraps, so a long category name wraps around it rather than pushing it off the rail. */
.cat-nav-count {
    flex: 0 0 auto;
    align-self: flex-start;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.75rem;
    line-height: 1.3;                       /* matches .cat-nav-item, so it sits on the first line */
    color: color-mix(in srgb, currentColor 65%, transparent);
}

/* ---- Mobile category drawer (F3) ----
   The header ☰ button opens an off-canvas drawer holding the category nav, since the left rail is
   hidden on small screens. Pure CSS + a tiny site.js toggle (no Blazor interactivity): the .open class
   slides the panel in and fades the backdrop. Hidden (and non-interactive) entirely on ≥768px, where
   the rail is visible instead. */
.nav-toggle { display: none; }          /* shown only on mobile (see breakpoint below) */
.drawer { display: none; }              /* off on desktop; the in-page rail is used there */
@media (max-width: 767px) {
    .nav-toggle { display: inline-flex; }
    .drawer {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 60;
        visibility: hidden;             /* keep out of the a11y tree until opened */
        pointer-events: none;
    }
    .drawer.open { visibility: visible; pointer-events: auto; }
    .drawer-backdrop {
        position: absolute;
        inset: 0;
        background: color-mix(in srgb, #000 55%, transparent);
        opacity: 0;
        transition: opacity .2s ease;
    }
    .drawer.open .drawer-backdrop { opacity: 1; }
    .drawer-panel {
        position: absolute;
        top: 0;
        left: 0;
        height: 100%;
        width: 16rem;
        max-width: 80vw;
        background: var(--card);
        border-right: 1px solid var(--border);
        padding: 1rem;
        overflow-y: auto;
        transform: translateX(-100%);
        transition: transform .2s ease;
    }
    .drawer.open .drawer-panel { transform: none; }
    .drawer-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; }
    /* The drawer's nav is always a vertical list, regardless of the rail's mobile rules. */
    .drawer .cat-nav { position: static; }
}

/* ---- Drawer "Appearance" section (mobile redesign F2.2) ----
   Holds the second instance of the controls the compact header drops: text size, language, theme, plus
   a link to the reader settings page. Separated from the category list by a hairline so the drawer
   reads as "where to go" then "how it looks".

   Visible ONLY below 640px: from 640px up the header carries these controls itself, and two visible
   instances would mean two tab stops and two screen-reader announcements for the same preference. The
   drawer as a whole already disappears at 768px (the in-page rail replaces it). */
.drawer-section { display: none; }
@media (max-width: 639px) {
    .drawer-section {
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
        margin-top: 1rem;
        padding-top: 0.75rem;
        border-top: 1px solid var(--border);
    }
    .drawer-section-title { margin-bottom: 0.25rem; }
    /* Label left, control right — the settings-row convention, so each toggle is self-describing
       without a tooltip (which a touch screen cannot show anyway). */
    .drawer-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; min-height: 44px; }
    .drawer-row-label { font-size: 0.875rem; color: var(--muted-foreground); }
    .drawer-link { display: flex; align-items: center; min-height: 44px; font-size: 0.875rem; color: var(--muted-foreground); }
    .drawer-link:hover { color: var(--foreground); }
}
@media (prefers-reduced-motion: reduce) {
    .drawer-backdrop, .drawer-panel { transition: none; }
}

/* ---- Section header ---- */
.section-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1.25rem; }

/* ---- Hot clusters (featured cards) ---- */
.hot-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; margin-bottom: 2.5rem; }
.hot-card {
    position: relative;
    overflow: hidden;
    border-radius: 0.5rem;
    border: 1px solid var(--border);
    background: var(--surface);
    /* No padding: the full-bleed image + the tinted text panel fill the card edge-to-edge. */
    padding: 0;
    transition: border-color .15s;
    /* Flex column so the tinted panel grows to fill the card. In a grid row the cards are already
       stretched to equal height; this makes each card's CONTENT fill that height (no empty space). */
    display: flex;
    flex-direction: column;
}
.hot-card:hover { border-color: color-mix(in srgb, var(--primary) 40%, transparent); }
/* The image keeps its aspect ratio; the tinted panel grows to fill the rest, with its coverage foot
   pinned to the bottom so a stretched card reads as full rather than top-aligned with a gap below. */
.hot-card > .story-media { flex: 0 0 auto; }
.hot-card > .tinted-panel { flex: 1 1 auto; display: flex; flex-direction: column; }
.hot-card > .tinted-panel .hot-card-foot { margin-top: auto; }
.hot-card h3 {
    margin: 0.75rem 0 1rem;
    font-size: 1.125rem;
    font-weight: 700;
    line-height: 1.25;
    color: var(--foreground);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.hot-card-foot { display: flex; align-items: center; justify-content: space-between; border-top: 1px solid var(--border); padding-top: 1rem; }
/* Hero card: head row (badge left, "open page" ↗ icon right), a title that expands inline, and the
   inline coverage panel. */
.hot-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; }
.hot-card-title { display: block; color: inherit; text-decoration: none; cursor: pointer; }
.hot-card-title:hover h3 { color: var(--primary); }
.hot-card .story-expand { padding: 0.5rem 1.25rem 1rem; }
/* Last-update timestamp in the card footer (right of the coverage count). */
.hot-card-time { white-space: nowrap; }

/* category badge — tinted with the element's own category accent (set via data-cat). Falls back to
   the brand primary when no category accent is present. */
.badge {
    border-radius: 0.25rem;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 20%, transparent);
    color: var(--cat-accent, var(--primary));
    padding: 0.125rem 0.375rem;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.625rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* A category micro-label (the .label.cat-link in feed rows) is always coloured by its own category
   accent — on every page, including the active-filter chip (no brand-colour override). */
.label.cat-link[data-cat] { color: var(--cat-accent); }

/* Number-first coverage block: a large source count + a "sources" caption + a discrete segmented
   meter. Replaces the bare proportional bar (which read ambiguously as cohesion, not coverage). */
.coverage-count { display: flex; align-items: baseline; gap: 0.35rem; }
.coverage-count .num { font-family: 'JetBrains Mono', monospace; font-size: 1.5rem; font-weight: 700; line-height: 1; color: var(--foreground); }
.coverage-count .cap { font-family: 'JetBrains Mono', monospace; font-size: 0.625rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted-foreground); }
/* Discrete segmented coverage meter: N filled segments out of a fixed reference, so "more sources"
   reads as a fuller meter without implying a precise percentage. */
.coverage-segs { display: flex; gap: 2px; margin-top: 0.4rem; }
.coverage-segs i { width: 0.5rem; height: 0.25rem; border-radius: 1px; background: var(--secondary); }
.coverage-segs i.on { background: var(--cat-accent, var(--primary)); }

/* source-count coverage bar (legacy; still used by the sidebar "most covered" rail) */
.coverage-bar { height: 0.375rem; width: 6rem; overflow: hidden; border-radius: 9999px; background: var(--secondary); }
.coverage-bar > span { display: block; height: 100%; background: var(--primary); }

/* ---- Source monogram chip ----
   A small rounded chip showing a source's 1–2 letter initial on a deterministic hashed background
   (see Monogram.cs). The hue comes from an inline --mono-h custom property; S/L are fixed here so
   every chip reads at the same weight and white text stays legible. Image-free source identity. */
.monogram {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    height: 1.25rem;
    flex-shrink: 0;
    border-radius: 0.25rem;
    background: hsl(var(--mono-h, 215) 45% 42%);
    color: #fff;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.5625rem;
    font-weight: 700;
    letter-spacing: 0;
    line-height: 1;
}
/* Favicon variant of the .monogram chip: shows the source portal's real icon on a neutral tile instead of
   the hashed initials. On <img> load error, the inline onerror in SourceIcon.razor removes .monogram-fav →
   the chip falls back to the .monogram hashed background + the .monogram-txt initials below. */
.monogram-fav {
    background: #ffffff;
    border: 1px solid var(--border);
    overflow: hidden;
    padding: 1px;
}
/* The favicon image fills the chip (contain keeps non-square icons intact). */
.monogram-fav .monogram-img { width: 100%; height: 100%; object-fit: contain; border-radius: inherit; display: block; }
/* Initials are hidden while the favicon shows; revealed automatically once .monogram-fav is stripped on error. */
.monogram-fav .monogram-txt { display: none; }
/* A horizontal stack of monograms used as a compact coverage glyph on cards. */
.monograms { display: flex; align-items: center; gap: 0.25rem; }

/* ---- Category pills ---- */
.pills { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; margin-bottom: 1.5rem; }
/* Smaller window pills so the full set of time options fits on one row (the date-range sits to the right). */
.pills .pill { font-size: 0.72rem; padding: 0.24rem 0.5rem; }
/* Date-range (from–to) filter, pushed to the right of the window pills. */
.date-range { display: flex; gap: 0.35rem; align-items: center; margin-left: auto; }
.date-range input[type="date"] { font: inherit; font-size: 0.72rem; padding: 0.18rem 0.4rem; min-width: 8.5rem; border: 1px solid var(--border); border-radius: 0.4rem; background: var(--surface); color: var(--foreground); color-scheme: dark; }
/* The app switches theme via an <html> class (.theme-dark default / .theme-light), NOT the OS color-scheme.
   Align the NATIVE date control's scheme to the app theme so the calendar-picker icon renders in a contrasting
   colour (otherwise, in the dark default with a light OS, it was drawn dark-on-dark and looked missing). */
.theme-light .date-range input[type="date"] { color-scheme: light; }
/* Keep the calendar-picker button visible + obviously clickable. */
.date-range input[type="date"]::-webkit-calendar-picker-indicator { opacity: 0.8; cursor: pointer; }
.date-range input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: 1; }
.date-range-sep { color: var(--muted-foreground); }
.date-range-apply { cursor: pointer; border: 1px solid var(--border); display: inline-flex; align-items: center; justify-content: center; padding: 0.24rem 0.45rem; }
.pill {
    border-radius: 9999px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--muted-foreground);
    padding: 0.25rem 0.75rem;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.pill:hover { color: var(--foreground); }
.pill.active { border-color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, transparent); color: var(--primary); }
/* Category-tinted pills (Sources page entry points) pick up their accent on hover. */
.pill[data-cat]:hover { color: var(--cat-accent); border-color: color-mix(in srgb, var(--cat-accent) 60%, transparent); }

/* ---- Coverage figures (mobile redesign F3.3; plate removed 2026-08-13) ----
   The source count is the product's differentiator and used to render as plain grey micro-text — weaker
   than the timestamp sitting next to it. It is therefore emphasized: the accent colour plus bold weight,
   on the SOURCE figure only (.cov-count-sources); the article figure stays quiet, because promoting both
   would promote neither.

   NO PLATE. It first shipped as an accent-tinted pill, and on 2026-08-13 the owner ruled the same way they
   had for the photo variant below: the fill read as a FRAME around the figure, everywhere it appeared
   (front page, category pages, cluster pages). Colour and weight already do the promoting, so the fill was
   decoration — and this codebase's convention is that a fill has to inform, not decorate. Nothing is drawn
   around the figure now.

   The figure's TEXT is untouched — the windowed/total fraction ("4/5 forrás") renders exactly as before.
   That is decision D1/A: the fraction carries real information (there is coverage outside the selected
   window), so this changes its weight, not its wording. */
.cov-count-sources {
    color: var(--primary);
    font-weight: 700;
}
.cov-count-sources b { color: inherit; }
/* On a photo the accent colour is invisible, so the figure carries its emphasis with weight and pure white
   instead. The bold number and the scrim beneath it are what make it stand out. */
.card-overlay-body .cov-count-sources {
    color: #fff;
}

/* ---- Meta typography on phones (mobile redesign F3.4) ----
   The uppercase monospace micro-label is a signature of the design, but at 375px "16 AKTÍV ESEMÉNY" and
   "1 ÓRA 22 PERC" are both hard to read and wasteful of width. Below 640px the COUNT and TIME metas drop
   to lowercase sans; source NAMES keep the monospace (there it works, and it is how a source reads as a
   source), and the category badge on an image keeps its uppercase (short, and a badge by nature).

   All sizes are rem, never px: the reader's text-size control scales the root font size, and a px value
   here would quietly opt these elements out of it. */
@media (max-width: 639px) {
    .story-meta-time,
    .story-meta-range,
    .hot-card-time,
    .cat-head .label,
    .card-overlay-body .meta {
        font-family: inherit;
        text-transform: none;
        letter-spacing: 0;
    }

    /* Size hierarchy: the contrast between headline and meta does the work, not sheer size. The body
       stays at 16px — this is about the ratio between the levels. */
    .hero-overlay-grid .hero-lead--overlay .headline { font-size: 1.35rem; }
    .hero-overlay-grid .hot-card--overlay .headline { font-size: 1.05rem; }
    .hero-overlay-grid .card-overlay-body .lead { font-size: 0.82rem; }
    .hero-overlay-grid .card-overlay-body .meta { font-size: 0.7rem; }
    /* Same size as the featured compact tiles' headline, so the two lists on a category page match. */
    .topic-feed .story-title-link h4 { font-size: 0.875rem; }
}

/* ---- Touch target sizing (mobile redesign F2.6) ----
   Measured before the change: 55 interactive elements were under 44px tall or under 32px wide, the worst
   being the A− button at 30x19 and a language link at 28x23. WCAG 2.5.8 (AA) asks for 24x24, which those
   only just clear; WCAG 2.5.5 (AAA) and both platform HIGs ask for 44x44.

   The targets are grown with a centred pseudo-element rather than by padding the elements: padding would
   change the painted size, which would reflow the header and the drawer rows. A pseudo-element hit-tests
   where it paints, so the tappable area grows while the visual box stays put. max(100%, 44px) keeps an
   already-large control unchanged instead of shrinking it to 44px.

   The elements must not be inside an `overflow: hidden` box, which would clip the pseudo-element — that
   is why this applies to chrome controls (header, drawer, footer), not to card internals. */
@media (max-width: 639px) {
    .icon-btn,
    .font-btn,
    .lang-toggle a,
    .cat-nav-item,
    .footer-links a { position: relative; }

    .icon-btn::after,
    .font-btn::after,
    .lang-toggle a::after,
    .footer-links a::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: max(100%, 44px);
        height: max(100%, 44px);
    }

    /* Neighbouring targets must not overlap, or the grown area of one control would swallow taps meant
       for the next. The two segmented groups are the tight cases: their buttons sat 0.5rem apart inside
       a bordered pill, so both the inner gap and the group's own padding grow to clear 44px of target
       plus breathing room. The header itself is no longer crowded — below 640px it carries two buttons. */
    .font-toggle,
    .lang-toggle { gap: 0.5rem; padding: 0 0.5rem; }
    .cat-nav-item { min-height: 44px; }
}

/* ---- Keyboard focus ring (mobile redesign T1) ----
   The stylesheet had NO focus styling before 2026-08-10: `:focus` appeared twice, both times as
   `outline: none`. A site-wide `:focus-visible` rule would have been the complete fix, but this task's
   contract is "zero desktop regression", and a ring on every link changes every page. So the ring is
   scoped to the controls this redesign touches or adds — the header/drawer chrome and the mobile
   filter menus — where new interaction is being introduced and unreachable focus would be a new bug.

   :focus-visible (not :focus) so a mouse click does not paint a ring; outline (not box-shadow) so it
   follows the element's border-radius and survives forced-colors mode. Extending this to the rest of
   the site is a separate, deliberate change. */
.icon-btn:focus-visible,
.font-btn:focus-visible,
.lang-toggle a:focus-visible,
.cat-nav-item:focus-visible,
.drawer-panel a:focus-visible,
.drawer-panel button:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
}

/* ---- Headline word breaking (mobile redesign F1.2) ----
   Hungarian compounds are routinely longer than a 375px text column (kulcsfontosságú,
   sajtótájékoztatóinak, külügyesekkel). `overflow-wrap: anywhere` only breaks when there is no better
   opportunity, so it never changes a line that already fits — it is applied at every width because
   where it DOES act, the alternative is an overflowing layout.

   `hyphens: auto` is different: it changes line breaking wherever it applies, so it is scoped to
   <640px only, keeping desktop line breaks byte-identical. It also depends on a correct <html lang>
   (see App.razor) — with a hard-coded lang the browser would hyphenate with the wrong dictionary.

   Scoped to card / row / timeline headlines. Deliberately NOT applied to the legal pages' prose,
   whose desktop typography must stay as it is. */
.headline,
.hot-card-title h3,
.story-title-link h4,
.story-compact-title,
.timeline-title { overflow-wrap: anywhere; }
@media (max-width: 639px) {
    .headline,
    .hot-card-title h3,
    .story-title-link h4,
    .story-compact-title,
    .timeline-title { hyphens: auto; }
}

/* ---- Sort tabs ---- */
.tabs { display: flex; gap: 1.5rem; border-bottom: 1px solid var(--border); margin-bottom: 0.5rem; }
/* On narrow screens the four tabs can overflow; let them scroll horizontally as a strip. */
@media (max-width: 639px) {
    .tabs { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: thin; }
    .tab { white-space: nowrap; flex: 0 0 auto; }
}
.tab {
    font-size: 0.875rem;
    font-weight: 700;
    padding-bottom: 0.5rem;
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    color: var(--muted-foreground);
}
.tab:hover { color: var(--foreground); }
.tab.active { border-bottom-color: var(--primary); color: var(--foreground); }

/* ---- Ranked story list ---- */
/* A thin left accent bar carries the story's (dominant) category color — the same accent token as the
   chip, so the row is color-coded at a glance. Transparent until a data-cat is present. */
.story-row { border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); border-left: 3px solid transparent; padding: 0 0.5rem; transition: background .15s; }
.story-row[data-cat] { border-left-color: color-mix(in srgb, var(--cat-accent) 55%, transparent); }
.story-row:hover { background: var(--surface); }
.story-row.open { background: var(--surface); }
.story-row-link { display: block; color: inherit; }
.story-row-link:hover { color: inherit; }
.story-row-inner { display: flex; align-items: flex-start; gap: 1rem; padding: 1.5rem 0; }
/* Expand affordance: a chevron that rotates down when the row is open. */
.story-chevron { align-self: center; flex-shrink: 0; font-size: 1.25rem; color: var(--muted-foreground); transition: transform .15s; }
.story-row.open .story-chevron { transform: rotate(90deg); color: var(--primary); }
/* Inline coverage panel, indented to line up under the headline (past the rank column). */
.story-expand { padding: 0 0.5rem 1.5rem 3rem; }
.story-expand-msg { color: var(--muted-foreground); padding: 0.5rem 0; }
/* Smooth the inline coverage appearance: fade + slight slide-in when a panel becomes visible. Keyed on
   the panel itself (not the row) so it works for every expandable container — feed rows, compact box
   rows and hero cards alike. */
.story-expand:not([hidden]) { animation: story-expand-in 0.38s cubic-bezier(0.22, 1, 0.36, 1); }
/* The story image is only rendered as a card image on category pages (see .topic-feed below); on the
   flat feed / search the rows stay text-only, so hide it by default. The element is an <a> (the picture
   links to the same target as the title), so the display rules must stay on the class, not on a tag. */
.story-row-media { display: none; }

/* ---- Category (topic) pages and the "Mind" front page: each cluster is a HORIZONTAL row — image LEFT,
   text RIGHT. The load-more fragment reuses StoryRow, so appended rows inherit this automatically. ---- */
/* NOT A CARD (owner, 2026-08-13): the rows used to be bordered, rounded, filled boxes. A list of boxes
   draws a frame around every item and then needs gaps between the frames, which spends horizontal and
   vertical space on the container instead of the content. They are now separated by a single hairline —
   the same device the flat feed has always used — so the eye follows one column of stories rather than a
   stack of panels. This is also why the left category accent bar goes: on a frameless row a coloured edge
   is the last remaining piece of box, and the category is now named (in its own colour) on the meta line. */
.topic-feed { display: grid; gap: 0; }
.topic-feed .story-row {
    /* Image column width as a variable, so the expanded coverage can indent to align with the title. */
    --topic-img-w: clamp(104px, 22vw, 192px);   /* px so the card image width is fixed under text zoom */
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 0.9rem;                 /* space between the image and the text */
    border: none;
    border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
    border-radius: 0;
    background: none;
    padding: 1rem 0;             /* the separator spans the full column; the padding is the row's air */
    transition: background .15s;
}
/* The last row's rule would draw a line under the feed with nothing below it — that is the load-more
   button's or the page end's boundary, not a separator between two stories. */
.topic-feed .story-row:last-child { border-bottom: none; }
.topic-feed .story-row:hover { background: var(--surface); }
.topic-feed .story-rank { display: none; }   /* no rank number in card mode */
/* Left image: a rounded thumbnail, stretched to the row height; the card padding is its margin. */
.topic-feed .story-row-media {
    display: block;
    flex: 0 0 auto;
    width: var(--topic-img-w);
    align-self: stretch;
    overflow: hidden;
    border-radius: 0.4rem;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 12%, var(--surface));
}
.topic-feed .story-row-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Right side: the normal category-list row content (category chip, title, lead, meta) on the surface —
   no tint, no white text; the card padding provides its spacing. */
.topic-feed .story-row-inner {
    flex: 1 1 0;
    min-width: 0;
    padding: 0;
}
/* Expanded coverage: the articles indent to align with the cluster TITLE (past the image column), with a
   thin horizontal rule separating the cluster header row from the coverage below. */
.topic-feed .story-row .story-expand {
    flex: 1 1 100%;
    padding: 0.65rem 0 0.25rem calc(var(--topic-img-w) + 0.9rem);
    border-top: 1px solid var(--border);
    margin-top: 0.65rem;         /* the row is frameless now: the rule needs its own air above it */
}
@keyframes story-expand-in {
    from { opacity: 0; transform: translateY(-12px); }
    to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .story-expand:not([hidden]) { animation: none; }
}
.story-coverage-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.story-rank { font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; color: color-mix(in srgb, var(--muted-foreground) 60%, transparent); padding-top: 0.25rem; width: 1.5rem; flex-shrink: 0; }
.story-body { flex: 1; min-width: 0; }
/* Row 1 meta: source monogram + name (single-article) or the source/article counts (multi-article),
   with the last-updated time pushed to the right. gap 0.5rem keeps the monogram close to its name. */
.story-meta { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.375rem; flex-wrap: wrap; }
.story-meta .dot { width: 0.25rem; height: 0.25rem; border-radius: 9999px; background: var(--border); }
/* Single-article source name on the meta line: mono, bold, category-accented; wraps if very long. */
.story-source-name { min-width: 0; font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; font-weight: 700; color: var(--cat-accent, var(--primary)); overflow-wrap: anywhere; }
/* Last-updated time pushed to the right end of the meta line; never shrinks or wraps. */
.story-meta-time { margin-left: auto; flex: 0 0 auto; white-space: nowrap; }
/* Footer meta line (StoryRow): sits BELOW the lead, so it takes top spacing instead of bottom. It is the
   row's ONE meta strip — category label, coverage figures, related-media figures — with the last-updated
   time pushed to the right edge (margin-left:auto on .story-meta-time). A single-article cluster has no
   coverage figures here; its source is named at the top of the panel instead. */
.story-meta-foot { margin-top: 0.5rem; margin-bottom: 0; }
/* The separating dots are DRAWN, not written into the markup: any part of this line can be absent (no
   category on a category page, no counts on a single-article story, no media on most stories), and a
   markup dot would then have to know what came before it. Excludes the time, which is pushed to the
   right edge and reads as its own column. */
.story-meta-foot > *:not(:first-child):not(.story-meta-time)::before {
    content: "·";
    margin-right: 0.5rem;       /* the flex gap sits before the dot, this puts the same space after it */
    color: var(--muted-foreground);
    opacity: 0.55;
}
/* One typographic voice across the whole line (owner, 2026-08-13): every item on it is a .label, so the
   source figure drops the accent colour it carries on the CARD surfaces. The emphasis it used to add is
   redundant here — the figure is already first among the counts — while the difference made the line read
   as two competing kinds of text. The "/total" half stays dimmed (.cov-total), which is the one contrast
   this line does want. */
.story-meta-foot .cov-count-sources { color: var(--muted-foreground); }
.story-row h4 { margin: 0; font-size: 1rem; font-weight: 700; line-height: 1.35; color: var(--foreground); }
/* Title row: the (expandable) title link + the small "open cluster page" ↗ icon. */
.story-title-row { display: flex; align-items: baseline; gap: 0.4rem; }
.story-title-link { color: inherit; text-decoration: none; min-width: 0; }
.story-title-link:hover h4 { color: var(--primary); }
.story-lead {
    margin: 0.5rem 0 0;
    font-size: 0.875rem;
    color: var(--muted-foreground);
    line-height: 1.55;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The generated cluster lead on the event-detail page — shown in full (not clamped), a touch
   larger than the row excerpt since it is the page's summary paragraph. Spans the full content width. */
.detail-lead {
    margin: 0.75rem 0 0;
    font-size: 1rem;
    color: var(--foreground);
    line-height: 1.6;
}
/* ---- "Latest development" line ----
   The newest member headline, rendered directly UNDER THE CLUSTER TITLE in the feed rows (home page and
   category pages). Not shown on the event page: there the coverage list is right below, so the newest
   article is visible anyway.

   Typography follows the TITLE — same family (inherited), same colour, same weight — and is set apart
   only by SIZE, one step below the title of the row it belongs to. That keeps it readable as a second
   headline rather than as meta text, without a frame or a badge (the reader's emphasis-over-borders
   convention). Only the timestamp stays muted: it is metadata, not part of the headline. */
.story-latest {
    margin: 0.25rem 0 0;
    line-height: 1.35;
    color: var(--foreground);
    font-weight: 700;
    /* Never grows the row: one line, ellipsis. */
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* One step below each row type's own title size (1rem in .story-row, 0.9rem in .story-compact). */
.story-row .story-latest { font-size: 0.8125rem; }
.story-compact .story-latest { font-size: 0.75rem; }
.story-latest-label { font-weight: 500; }
.story-latest-time { color: var(--muted-foreground); font-weight: 400; }
.story-latest-time::before { content: " · "; }

.story-sources { margin-top: 1rem; display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }

/* ---- Sidebar ---- */
.aside { display: flex; flex-direction: column; gap: 2rem; }
.aside h3 { margin: 0 0 1rem; }
.ranked-item { display: flex; flex-direction: column; gap: 0.25rem; margin-bottom: 1rem; }
.ranked-item .bar { height: 0.25rem; width: 100%; overflow: hidden; border-radius: 9999px; background: var(--secondary); }
.ranked-item .bar > span { display: block; height: 100%; background: var(--primary); }
.ranked-item .t { font-size: 0.75rem; font-weight: 700; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ranked-item:hover .t { color: var(--primary); }
.panel { border-radius: 0.5rem; border: 1px solid var(--border); background: var(--surface); padding: 1rem; }

/* ---- Story detail ---- */
.detail-head { margin-bottom: 1.5rem; }
/* Category accent bar on the event header (color set via data-cat / --cat-accent). */
.detail-head[data-cat] { border-left: 3px solid color-mix(in srgb, var(--cat-accent) 65%, transparent); padding-left: 1rem; }
.detail-head h1 { font-size: 1.875rem; font-weight: 800; line-height: 1.15; margin: 0.5rem 0; letter-spacing: -0.01em; }
.detail-meta { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
/* One coverage row = one source article, stacked in four lines. ROW 1: source monogram + portal name on
   the left, the publish time pushed to the right. ROW 2: the headline. ROW 3: the lead (capped at 200
   chars). ROW 4: the article's category chip, left-aligned. */
.source-item { display: block; padding: 1rem 0; border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
/* Top line: monogram + name (left) with the publish time pushed to the right. align-items:center keeps
   the monogram centered on the name. No flex-wrap, so the time stays on this line while a long name
   wraps within the .source-feed element itself. */
.source-topline { display: flex; align-items: center; gap: 0.5rem; }
/* Portal name: emphasized (bold), colored with the row's category accent, and sized to its content
   (no fixed/reserved column width) so the time sits right after it. */
/* flex: 0 1 auto + min-width: 0 let a very long feed name shrink and WRAP (up to two lines) instead of
   forcing one line and pushing the time off the row; overflow-wrap breaks an over-long token. */
.source-feed { flex: 0 1 auto; min-width: 0; font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; font-weight: 700; color: var(--cat-accent, var(--primary)); overflow-wrap: anywhere; }
/* When the source name is a link to the publication's site, keep the look but hint on hover. */
a.source-feed { text-decoration: none; }
a.source-feed:hover { text-decoration: underline; }
/* Publish time pushed to the right end of the top line; never shrinks or wraps. */
.source-time { margin-left: auto; flex: 0 0 auto; white-space: nowrap; }
/* Headline: its own block line below the source line. The <a> is inline by default, so force block. */
.source-headline { display: block; margin-top: 0.35rem; color: var(--foreground); font-size: 0.9rem; font-weight: 600; text-decoration: none; }
.source-headline:hover { color: var(--primary); }
.story-compact .source-headline { font-size: 0.8rem; }
/* Lead: capped at 200 chars in markup (with an ellipsis); the full text is in the headline tooltip. */
.source-lead { margin: 0.3rem 0 0; font-size: 0.8rem; line-height: 1.45; color: var(--muted-foreground); }
/* Category line: the article's category chip as the last row, left-aligned. */
.source-catline { margin-top: 0.4rem; }

/* Coverage paging: rows past the first page are collapsed until "load more" reveals them. */
.source-item.collapsed { display: none; }

/* Event-page coverage as horizontal cards: the article's image on the LEFT, the text body on the RIGHT
   (mirroring the category-page cluster cards). Scoped to .source-list-cards (ShowImages) so the inline
   feed/category expansions keep their plain text rows. */
.source-list-cards { display: grid; gap: 0.75rem; }
.source-list-cards .source-item {
    display: flex;
    gap: 0.9rem;
    align-items: stretch;
    padding: 0.8rem;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    background: var(--surface);
}
.source-list-cards .source-item.collapsed { display: none; }   /* keep the paging behaviour */
.source-list-cards .source-body { flex: 1 1 0; min-width: 0; }
/* Left image: a rounded thumbnail stretched to the card height. order:-1 places it before the text body
   in the flex row (the markup renders it after the body). */
.source-list-cards .source-media {
    order: -1;
    flex: 0 0 auto;
    width: clamp(96px, 20vw, 176px);   /* px so the coverage image width is fixed under text zoom */
    align-self: stretch;
    overflow: hidden;
    border-radius: 0.4rem;
    display: block;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 12%, var(--surface));
}
.source-list-cards .source-media img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Related media (F3) woven into the coverage list: a video/podcast row rendered like an article coverage
   row, marked with a small kind glyph + a "videó/podcast" chip. It is tinted with its OWN topic category
   accent — data-cat sets --cat-accent exactly like an article row; an uncategorized/untagged item resolves
   --cat-accent to the neutral token, and where none is set the media accent (--primary) is the fallback.
   In card mode (event page) it inherits the horizontal card layout from .source-item. */
.source-item--media .source-feed { color: var(--cat-accent, var(--primary)); }
/* Small play/mic glyph in the top line — the "kis ikon" that flags the row as related media. Sized like the
   article monogram so the two row kinds align. Tinted by the row's category accent. */
.media-monogram {
    flex: 0 0 auto;
    width: 1.6rem; height: 1.6rem;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 0.3rem;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 14%, var(--surface));
    color: var(--cat-accent, var(--primary));
    font-size: 0.8rem; line-height: 1;
}
.media-monogram::before { content: "▶"; }
.media-monogram[data-kind="podcast"]::before { content: "🎙"; }
/* Explicit kind chip after the source name, so the row reads as media even where the glyph is ambiguous.
   Tinted by the row's category accent (falls back to --primary). */
.media-kind-chip {
    flex: 0 0 auto;
    padding: 0.05rem 0.3rem; border-radius: 0.25rem;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 12%, transparent);
    color: var(--cat-accent, var(--primary));
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.56rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
}
/* Meta line under the media title: the discreet non-editorial type chip + podcast duration. */
.media-metaline { display: flex; align-items: center; gap: 0.4rem; }
.media-metaline .media-duration { opacity: 0.75; }

/* Feed-level "has related media" badge (MediaBadge): small play/mic glyphs appended to a story card/row's
   source·article meta, hinting that the event page carries an associated video/podcast. Subtle by design. */
.media-badge { display: inline-flex; align-items: center; gap: 0.15rem; margin-left: 0.4rem; vertical-align: baseline; }
.media-badge-icon {
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 0.8em; line-height: 1; color: var(--primary); opacity: 0.85;
}

/* Subtle dim while the event coverage swaps during the in-place per-event source filter (site.js). */
.coverage-loading { opacity: 0.55; transition: opacity 0.1s ease; }

/* Window pills whose range contains no article of the cluster — de-emphasized so the reader sees at a glance
   which ranges actually have coverage. The active pill never gets this class (see EmptyPillClass). */
.pill--empty { opacity: 0.4; }

/* Category-scoped coverage banner: "Only <category> articles (N) · [Show all (M)]". Shown when the
   reader arrives via a category filter and the cluster has mixed-category members. */
.coverage-filter { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin: 0.25rem 0 0.75rem; }
.coverage-filter .coverage-showall { white-space: nowrap; }
.coverage-more { display: flex; justify-content: center; margin: 0.5rem 0 0.25rem; }
.coverage-more-btn { font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted-foreground); background: none; border: none; border-radius: 0.25rem; padding: 0.35rem 1rem; cursor: pointer; }
.coverage-more-btn:hover { color: var(--primary); }
.source-main { flex: 1; min-width: 0; }
/* Source headline one step below the cluster title: 0.9rem in the flat feed / event page (cluster
   title 1rem), and 0.8rem inside a compact box (cluster title 0.9rem — see the .story-compact override). */
.source-main h4 { margin: 0 0 0.25rem; font-size: 0.9rem; font-weight: 600; }
.story-compact .source-main h4 { font-size: 0.8rem; }
.rep-tag { font-family: 'JetBrains Mono', monospace; font-size: 0.5625rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--primary); padding: 0; margin-left: 0.5rem; }
/* Coverage sort bar: small toggle buttons to re-sort the source list by publish time or match
   strength. The active button is outlined in the primary color and shows a direction arrow. */
.source-sort { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
/* Last-update time on the left of the sort row. */
.source-sort-time { color: var(--muted-foreground); }
/* Coverage sort control: BOTH direction options ("Legújabb elől" / "Legrégebbi elől") are shown at
   once as a small segmented control. The active option is emphasized (primary color, outlined, bold);
   the inactive one is a muted plain button that highlights on hover. */
.sort-dir-group { display: inline-flex; align-items: center; gap: 0.25rem; }
/* On the event page the group IS the .source-sort bar (same element): make it a full-width flex row so
   the two options sit together at the RIGHT edge. (In the compact meta line the group is inline-flex and
   already sits on the right, next to the last-update time.) */
.source-sort.sort-dir-group { display: flex; justify-content: flex-end; }
.sort-dir-opt { font-size: 0.72rem; line-height: 1; padding: 0.45rem 0.7rem; color: var(--muted-foreground); background: none; border: 1px solid transparent; border-radius: 0.25rem; cursor: pointer; white-space: nowrap; }
.sort-dir-opt:hover { color: var(--foreground); }
.sort-dir-opt.active { color: var(--primary); border-color: var(--primary); font-weight: 600; }

/* Coverage KIND filter (event page): the Articles / Videos / Podcasts toggles, left of the sort direction
   in the .source-sort bar. Same segmented look as the sort options; an ACTIVE (shown) kind is emphasized
   (outlined, bold), an inactive (hidden) kind reads as a muted plain button. All start active. */
.type-filter { display: inline-flex; align-items: center; gap: 0.25rem; flex-wrap: wrap; }
/* Keep the sort direction pinned to the RIGHT of the .source-sort bar whether or not the type filter is
   present (with a single child, space-between alone would left-align it). */
.source-sort > .sort-dir-group { margin-left: auto; }
.type-filter-btn { font-size: 0.72rem; line-height: 1; padding: 0.45rem 0.7rem; color: var(--muted-foreground); background: none; border: 1px solid transparent; border-radius: 0.25rem; cursor: pointer; white-space: nowrap; }
.type-filter-btn:hover { color: var(--foreground); }
.type-filter-btn.active { color: var(--primary); border-color: var(--primary); font-weight: 600; }
/* The article-view HOME content-type filter (Cikkek/Videók/Podcastok) mirrors the sort-tabs (.tabs/.tab)
   format — an underline-tab row — so the article home's top control reads identically to the grouped home's
   sort tabs. Active here means "this kind is shown" (all start active → all underlined); toggling a kind off
   mutes it (no underline). Overrides the outlined-pill .type-filter-btn styling above via higher specificity. */
.home-type-filter { display: flex; gap: 1.5rem; border-bottom: 1px solid var(--border); margin-bottom: 0.5rem; }
/* line-height: inherit (not the base .type-filter-btn's line-height:1) so these tab-style buttons have the
   SAME control height + underline position as the .tab sort tabs — otherwise the topics/articles toggle
   shows a vertical offset between "Legfontosabb…" (anchors) and "Cikkek/Videók…" (buttons). */
.home-type-filter .type-filter-btn { padding: 0 0 0.5rem; margin-bottom: -1px; font-size: 0.875rem; font-weight: 700; line-height: inherit; color: var(--muted-foreground); background: none; border: none; border-bottom: 2px solid transparent; border-radius: 0; }
.home-type-filter .type-filter-btn:hover { color: var(--foreground); }
.home-type-filter .type-filter-btn.active { border-bottom-color: var(--primary); color: var(--foreground); }
/* De-emphasized type button: the filter always shows all three kinds, so one with no items in the current
   set is dimmed (still clickable) to signal there is nothing of that kind right now. */
.type-filter-btn.type-filter-muted { opacity: 0.4; }
.type-filter-btn.type-filter-muted:hover { opacity: 0.7; }
@media (max-width: 639px) {
    .home-type-filter { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: thin; }
    .home-type-filter .type-filter-btn { white-space: nowrap; flex: 0 0 auto; }
}
/* A coverage row hidden by the kind filter. Distinct from .collapsed (paging): a row can be paging-visible
   yet kind-hidden, and either state hides it. */
.source-item.type-hidden { display: none; }
/* Article-view front page: a box row hidden by the content-type filter, and a whole box hidden when the
   filter leaves it with no visible row. Mirrors the coverage .type-hidden above for the box layout. */
.cat-box-list .story-row.type-hidden { display: none; }
.cat-box.type-empty { display: none; }

/* Category box "load more" (front page): a discreet, small control at the box bottom that appends the next
   page of the box's rows in place. NO own border (the last row's separator already sits above it) and small,
   muted text + a down chevron so it stays understated. Hidden with the box body when the box is collapsed. */
.cat-box-more-btn { display: block; width: 100%; margin-top: 0.75rem; padding: 0.2rem; font-size: 0.7rem; color: var(--muted-foreground); background: none; border: none; border-radius: 0; cursor: pointer; text-align: center; }
.cat-box-more-btn::after { content: " ▾"; }
.cat-box-more-btn:hover { color: var(--foreground); }
.cat-box.collapsed .cat-box-more-btn { display: none; }

/* "Load more" reveal animation: newly appended box rows fade + slide in, each with a small per-row delay
   (set inline in site.js) so the batch cascades in gently instead of popping. */
@keyframes boxRowEnter {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}
.cat-box-list .box-row-enter { animation: boxRowEnter 0.32s ease both; }
/* Respect reduced-motion: skip the slide/fade and the staggered delay entirely. */
@media (prefers-reduced-motion: reduce) {
    .cat-box-list .box-row-enter { animation: none; }
}

/* Segmented view switchers (Témák/Cikkek on the browse pages, Kártyák/Idővonal on the event coverage):
   FRAMELESS — no border, no fill, no surrounding box. The active segment is marked by emphasis alone (accent
   colour + bold), the same way the coverage kind switch and the sidebar sort keys mark theirs, so the reader
   sees one consistent "selected" language across the site instead of three competing outlined controls. */
.seg-control { display: inline-flex; align-items: center; gap: 6px; }
.seg-control a { display: inline-flex; align-items: center; gap: 6px; padding: 5px 8px; font-size: 0.8rem; line-height: 1; color: var(--muted-foreground); background: none; border: none; border-radius: 0.375rem; text-decoration: none; white-space: nowrap; }
.seg-control a:hover { color: var(--foreground); }
.seg-control a[aria-current="page"] { color: var(--primary); font-weight: 700; }
.seg-control .seg-icon { flex: none; }

/* Rail variant: the switcher sits at the TOP OF THE CATEGORY RAIL and is styled to match the category items
   (same row shape, size, weight and hover) so the column reads as one navigation unit — view first, then the
   categories, separated by a hairline. The active row uses the neutral primary tint rather than a category
   accent, because it is not a category. */
.seg-control--rail { display: flex; flex-direction: column; gap: 0.125rem; align-items: stretch; margin-bottom: 0.5rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--border); }
/* white-space: normal overrides the control's shared nowrap — the rail is a fixed 176px column and a view
   name can be a sentence ("Események kategóriák szerint" ≈ 196px at this size), which would otherwise spill
   past the rail instead of wrapping. Same treatment (and same reason) as .cat-nav-item, whose longest
   category names wrap too; the icon top-aligns so it sits by the label's FIRST line, like the accent dot. */
.seg-control--rail a { justify-content: flex-start; align-items: flex-start; padding: 0.4rem 0.6rem; font-size: 0.875rem; font-weight: 500; border-radius: 0.375rem; line-height: 1.3; white-space: normal; }
.seg-control--rail .seg-icon { margin-top: 0.15em; }
.seg-control--rail a:hover { background: var(--surface); color: var(--foreground); }
.seg-control--rail a[aria-current="page"] { background: color-mix(in srgb, var(--primary) 14%, transparent); color: var(--foreground); font-weight: 700; }
.seg-control--rail .seg-icon { width: 0.95em; height: 0.95em; opacity: 0.85; }

/* The switcher has ONE home: the category rail. On desktop that rail is the left column; below 768px the
   rail is hidden and the ☰ drawer renders it, so the switcher travels into the drawer with it — above the
   categories, which is where the owner asked for it (2026-08-13). No copies, nothing to hide. */

/* Top control bar for the browse pages (Home + Topic): the time-window selector, plus the view switcher as a
   mobile-only fallback (hidden on ≥768px, where the rail carries it — see .seg-control--rail). The row wraps
   on narrow screens. */
.browse-bar { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem 1.5rem; flex-wrap: wrap; margin-bottom: 1.25rem; }

/* ---- Mobile filter menus (mobile redesign F2.5) ----
   Below 640px the ten window pills and the four sort tabs — measured at ~160px of vertical space above
   the first headline on a 375px screen — collapse into two <details> buttons showing the current value.
   The chips are hidden rather than removed: static SSR renders one DOM for every width, and the desktop
   chip rows must not change.

   The open list is absolutely positioned so opening it OVERLAYS the feed instead of pushing it down —
   a disclosure that reflows the page would register as layout shift (CLS) on every tap. */
.mobile-filter-row { display: none; }
@media (max-width: 639px) {
    /* Only the WINDOW pills collapse into a menu. The sort .tabs stay a horizontally scrollable strip
       (see the .tabs rule in the sort-tabs section), matching the article view's content-type filter:
       four short labels are quicker to read laid out than hidden behind a button, and both browse views
       then present their in-page filter the same way. */
    .browse-bar .pills { display: none; }
    /* The filter menus sit on the SAME line as the Témák/Cikkek switcher, pushed right. nowrap plus the
       min-width: 0 chain below is what keeps them there: the topics view puts three controls in this row
       (switcher + window + sort), and with wrapping allowed the pair dropped to a second line as soon as
       their combined intrinsic width passed 343px — which it does at 375px. Now they shrink instead,
       and an over-long value ellipsises (see .mobile-filter-value). */
    .mobile-filter-row { display: flex; align-items: center; gap: 0.5rem; flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; justify-content: flex-end; }
    .browse-bar { flex-wrap: nowrap; }
    /* Tighter below the control row too — with the chips gone it is a single 44px line, and 1.25rem
       under it re-opened the gap that was just closed above it. */
    .browse-bar { margin-bottom: 0.75rem; }
    .mobile-filter { position: relative; min-width: 0; }
    .mobile-filter-summary {
        display: flex;
        align-items: center;
        gap: 0.35rem;
        min-height: 44px;           /* WCAG 2.5.5 target size — this is a primary control */
        padding: 0 0.6rem;
        border: 1px solid var(--border);
        border-radius: 0.375rem;
        background: color-mix(in srgb, var(--secondary) 30%, transparent);
        color: var(--foreground);
        font-size: 0.8rem;
        cursor: pointer;
        list-style: none;           /* hide the native disclosure triangle (Firefox) */
        white-space: nowrap;
    }
    /* WebKit/Blink use a separate pseudo-element for the marker; both must go, or the caret doubles. */
    .mobile-filter-summary::-webkit-details-marker { display: none; }
    .mobile-filter-summary:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
    /* Last resort when even two shrunken menus plus the switcher exceed the row: truncate the value
       rather than wrap the row. The full text is one tap away in the open list, and the accessible name
       on the summary carries it in full regardless. */
    .mobile-filter-value { font-weight: 700; overflow: hidden; text-overflow: ellipsis; }
    /* The axis name beside the value (opt-in, see MobileFilterMenu.ShowLabel). Micro-label treatment, and
       it gives way first: on a narrow screen the VALUE is the part that must stay readable. */
    .mobile-filter-label { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--muted-foreground); }

    /* The event page's window selector: same swap as the browse bar, but the row is the page's own
       (.event-window-row) rather than .browse-bar, and it holds nothing else — so the menu simply sits at
       the right end of its own line. */
    .event-window-row .pills { display: none; }
    .event-window-row { display: flex; justify-content: flex-end; }
    .mobile-filter-caret { flex: 0 0 auto; color: var(--muted-foreground); transition: transform .15s; }
    .mobile-filter[open] .mobile-filter-caret { transform: rotate(180deg); }
    .mobile-filter-list {
        position: absolute;
        top: calc(100% + 0.25rem);
        right: 0;
        z-index: 40;                /* under the header (50) — the panel belongs to the page, not the chrome */
        min-width: 10rem;
        max-height: 60vh;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        padding: 0.25rem;
        border: 1px solid var(--border);
        border-radius: 0.5rem;
        background: var(--card);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
    }
    .mobile-filter-item {
        display: flex;
        align-items: center;
        min-height: 44px;
        padding: 0 0.6rem;
        border-radius: 0.375rem;
        color: var(--muted-foreground);
        font-size: 0.85rem;
    }
    .mobile-filter-item:hover { background: var(--surface); color: var(--foreground); }
    .mobile-filter-item.active { background: color-mix(in srgb, var(--primary) 14%, transparent); color: var(--primary); font-weight: 700; }
    .mobile-filter-item:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }
}
@media (prefers-reduced-motion: reduce) {
    .mobile-filter-caret { transition: none; }
}
/* The window pills sit LEFT-aligned right after the switcher (flowing left-to-right, wrapping left). */
.browse-bar .pills { margin: 0; flex: 1 1 auto; justify-content: flex-start; }


/* Search — the advanced-filter panel (progressive disclosure via a native <details>). */
.search-page-form { max-width: 52rem; }
.btn-apply { padding: 0.45rem 0.9rem; font-size: 0.85rem; color: #fff; background: var(--primary); border: 1px solid var(--primary); border-radius: 0.375rem; cursor: pointer; white-space: nowrap; }
.btn-apply:hover { filter: brightness(1.08); }
.search-filters { border: 1px solid var(--border); border-radius: 0.5rem; background: var(--card); }
.search-filters > summary { display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 0.9rem; cursor: pointer; list-style: none; font-size: 0.85rem; color: var(--muted-foreground); }
.search-filters > summary::-webkit-details-marker { display: none; }
.search-filters > summary::marker { content: ""; }
.search-filters[open] > summary { border-bottom: 1px solid var(--border); color: var(--foreground); }
.filter-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 1.2rem; height: 1.2rem; padding: 0 0.35rem; font-size: 0.7rem; font-weight: 600; color: var(--primary); background: var(--secondary); border-radius: 0.6rem; }
.search-filters-body { padding: 0.9rem; display: flex; flex-direction: column; gap: 0.9rem; }
.search-fieldset { border: none; margin: 0; padding: 0; }
.search-fieldset > legend { display: block; padding: 0; margin-bottom: 0.4rem; color: var(--muted-foreground); font-size: 0.78rem; }
.search-fieldset .opt { display: inline-flex; align-items: center; gap: 0.3rem; margin: 0 0.85rem 0.35rem 0; font-size: 0.82rem; color: var(--foreground); cursor: pointer; }
.search-fieldset .opt input { accent-color: var(--primary); }
.chip-opt { padding: 0.12rem 0.45rem; border: 1px solid var(--border); border-radius: 0.375rem; }
.chip-opt:has(input:checked) { border-color: var(--cat-accent, var(--primary)); background: color-mix(in srgb, var(--cat-accent, var(--primary)) 12%, transparent); }
.date-range input[type="date"] { font-size: 0.8rem; padding: 0.2rem 0.35rem; border: 1px solid var(--border); border-radius: 0.3rem; background: var(--background); color: var(--foreground); }
.search-filters-actions { display: flex; align-items: center; gap: 1rem; margin-top: 0.15rem; }
.sort-btn { font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted-foreground); background: none; border: 1px solid transparent; border-radius: 0.25rem; padding: 0.15rem 0.5rem; cursor: pointer; }
.sort-btn:hover { color: var(--foreground); }
.sort-btn.active { color: var(--primary); border-color: var(--primary); }
.sort-btn .arrow { font-family: inherit; }

/* Per-source sub-line: the article's own category + its publish time on one row. */
.source-sub { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.source-sub .dot { width: 0.25rem; height: 0.25rem; border-radius: 9999px; background: var(--border); }
/* Source headline links out to the original article; styled like the heading, hint on hover. */
.source-main h4 a { color: inherit; text-decoration: none; }
.source-main h4 a:hover { color: var(--primary); text-decoration: underline; }

/* Source (portal) name shown before a single-article cluster's title — in the category accent, with a
   separator, so it reads as "Telex · Headline". Not part of the stored title, display-only. */
.story-source-prefix { color: var(--cat-accent, var(--primary)); font-weight: 700; }
.story-source-prefix::after { content: " · "; color: var(--muted-foreground); font-weight: 400; }

/* Clickable category chips/labels: a click runs the category filter (/?category={key}). The pointer
   cursor + hover underline signal they're actionable, wherever the chip appears. */
.cat-link { cursor: pointer; }
.cat-link:hover { color: var(--primary); text-decoration: underline; }
a.cat-link { text-decoration: none; }

/* Small per-category article-count badge shown next to a category chip in a story header
   ("Politics 5" = 5 of the event's articles are in that category). Pill-shaped, muted, and
   never underlined on hover so it reads as a count rather than part of the clickable label. */
.cat-count {
    margin-left: 0.3em;
    padding: 0 0.35em;
    border-radius: 9999px;
    background: var(--secondary);
    color: var(--muted-foreground);
    font-size: 0.9em;
    line-height: 1.4;
    vertical-align: baseline;
}
.cat-link:hover .cat-count { text-decoration: none; }

/* ---- Category header ---- */
.cat-head { margin-bottom: 1rem; }
/* Category header: a plain (sans) title, kept restrained, with one subtle touch — a short category-coloured
   accent bar beneath it (a small magazine-style flourish rather than a full rule or a mono/uppercase
   masthead). The title colour is the category accent (set inline via data-cat on the h1). */
.cat-head h1 {
    font-size: 2.15rem;
    font-weight: 800;
    letter-spacing: -0.015em;
    line-height: 1.1;
    margin: 0.25rem 0 0.6rem;
    position: relative;
    padding-bottom: 0.5rem;
}
.cat-head h1::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 2.5rem;
    height: 3px;
    border-radius: 2px;
    /* Softened accent — the category colour mixed toward transparent so the bar is gentler, not a hard block. */
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 60%, transparent);
}

/* ---- Generic page heading ---- */
h1.page-title { font-size: 1.75rem; font-weight: 800; margin: 0 0 1rem; }

/* ---- Pager ---- */
.pager { display: flex; gap: 0.75rem; justify-content: center; margin-top: 2rem; }
.pager a, .pager span {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.75rem;
    padding: 0.5rem 1rem;
    border: 1px solid var(--border);
    border-radius: 0.375rem;
    color: var(--muted-foreground);
}
.pager a:hover { color: var(--foreground); border-color: var(--primary); }
.pager .disabled { opacity: 0.4; }

/* "Load more": centered button that appends the next page of feed rows (see site.js). */
.load-more { display: flex; justify-content: center; margin: 1.75rem 0; }
.load-more-btn { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--foreground); background: none; border: none; border-radius: 0.375rem; padding: 0.6rem 1.5rem; cursor: pointer; }
.load-more-btn:hover { color: var(--primary); }

/* ---- Alerts / empty states ---- */
.alert { border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem 1.25rem; background: var(--surface); color: var(--muted-foreground); }
.alert-error { border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); color: var(--foreground); }
/* Hint under an empty feed pointing back to the source picker (shown only when sources are hidden). */
.empty-hint { margin-top: 0.75rem; font-size: 0.85rem; }
.empty-hint a { color: var(--primary); text-decoration: underline; }

/* ---- Source picker (/sources) ---- */
/* Full page width so the auto-fill grid below can lay the source/category rows out in as many columns as
   fit (3–4 on a desktop), instead of the old ~2 the 42rem cap allowed. */
.source-picker { margin-top: 1.5rem; }
/* Toolbar: text filter on the left, show-all / hide-all pills on the right. */
.source-picker-toolbar { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.source-filter { flex: 1 1 12rem; min-width: 0; padding: 0.5rem 0.75rem; border: 1px solid var(--border); border-radius: 0.5rem; background: var(--surface); color: var(--foreground); font-size: 0.9rem; }
.source-filter:focus { outline: none; border-color: var(--primary); }
.source-picker-actions { display: flex; gap: 0.5rem; }
/* The checkbox list — one row per feed, monogram + name. (Named -picker-list to avoid colliding with the
   coverage list's existing .source-list container, which must NOT become a grid.) */
.source-picker-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.25rem 1.25rem; }
/* Sub-heading separating the video/podcast media sources from the article feeds inside the one picker. */
.source-picker-subhead { margin: 1.25rem 0 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--border); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
/* Per-kind sub-heading inside the media picker (Videók / Podcastok): a lighter, bordered sub-level so the
   video and podcast source groups read as clearly separate blocks under the "Kapcsolódó média" section. */
.source-picker-subhead--kind { margin: 1rem 0 0.4rem; padding-top: 0.5rem; font-size: 0.72rem; color: var(--muted-foreground); }
.source-picker-list--kind { margin-bottom: 0.5rem; }
/* A row is the toggle label PLUS an optional outbound link to the publisher's site, side by side — hence
   the flex container here rather than on the label alone. min-width:0 lets the label's ellipsis work. */
.source-row { margin: 0; display: flex; align-items: center; min-width: 0; }
.source-check { display: flex; align-items: center; gap: 0.6rem; padding: 0.45rem 0.5rem; border-radius: 0.5rem; cursor: pointer; flex: 1 1 auto; min-width: 0; }
.source-check:hover { background: var(--surface); }
.source-check input { width: 1.05rem; height: 1.05rem; accent-color: var(--primary); flex: 0 0 auto; }
.source-check .monogram { flex: 0 0 auto; }
.source-name { font-size: 0.9rem; color: var(--foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Per-source article contribution count, pushed to the right edge of the row. */
.source-count { margin-left: auto; flex: 0 0 auto; font-variant-numeric: tabular-nums; }
/* Outbound link to the publisher's home page at the end of a source row. Deliberately quiet (muted arrow,
   only lighting up on hover): the row's job is the toggle, the link is the transparency affordance. */
.source-home { flex: 0 0 auto; padding: 0.25rem 0.4rem; color: var(--muted-foreground); text-decoration: none; font-size: 0.85rem; line-height: 1; border-radius: 0.35rem; }
.source-home:hover { color: var(--primary); background: var(--surface); }
/* Category picker: a small accent dot (the category colour) before the name, in place of the source monogram. */
.cat-dot { width: 0.85rem; height: 0.85rem; border-radius: 50%; background: var(--cat-accent); flex: 0 0 auto; }
/* Settings sub-section heading (category picker under the source picker). */
.page-subtitle { font-size: 1.15rem; font-weight: 800; letter-spacing: -0.01em; margin: 0 0 0.35rem; }

/* ---- Legal / static content pages (Privacy, Contact, How it works, Sources) ---- */
/* Full page width (no narrow reading cap) + comfortable line-height; muted body text. */
.legal-updated { color: var(--muted-foreground); font-size: 0.8rem; margin: -0.5rem 0 1.5rem; }
.legal-h2 { font-size: 1.15rem; font-weight: 700; margin: 2rem 0 0.5rem; }
.legal-p { color: var(--muted-foreground); line-height: 1.7; margin: 0 0 0.75rem; }
.legal-p a { color: var(--primary); }
.legal-p code, .legal-table code { font-family: 'JetBrains Mono', monospace; font-size: 0.85em; }
/* Cookie table: compact, bordered, scrolls horizontally on very narrow screens via its wrapper. */
.legal-table { width: 100%; border-collapse: collapse; margin: 0.5rem 0 1rem; font-size: 0.85rem; }
.legal-table th, .legal-table td { border: 1px solid var(--border); padding: 0.4rem 0.6rem; text-align: left; vertical-align: top; }
.legal-table th { font-weight: 700; }
.legal-table td { color: var(--muted-foreground); }

/* Markdown-rendered legal document (.legal-doc wraps the Markdig HTML output). Maps the generated elements
   to the same typography as the hand-written legal pages; full page width. */
.legal-doc { line-height: 1.7; }
.legal-doc h1 { font-size: 1.75rem; font-weight: 800; margin: 0 0 0.75rem; }
/* The italic "last updated" line rendered as the first paragraph. */
.legal-doc > p:first-of-type em { color: var(--muted-foreground); font-size: 0.85rem; font-style: normal; }
.legal-doc h2 { font-size: 1.15rem; font-weight: 700; margin: 2rem 0 0.5rem; }
.legal-doc p { color: var(--muted-foreground); margin: 0 0 0.75rem; }
.legal-doc a { color: var(--primary); }
.legal-doc code { font-family: 'JetBrains Mono', monospace; font-size: 0.85em; }
.legal-doc strong { color: var(--foreground); }
.legal-doc table { width: 100%; border-collapse: collapse; margin: 0.5rem 0 1rem; font-size: 0.85rem; }
.legal-doc th, .legal-doc td { border: 1px solid var(--border); padding: 0.4rem 0.6rem; text-align: left; vertical-align: top; }
.legal-doc th { font-weight: 700; }
.legal-doc td { color: var(--muted-foreground); }
/* Lists: not used by the built-in documents, but an operator-authored page (/oldal/{slug}) may well use
   them, and an unstyled list would be the one markdown construct that looks out of place. */
.legal-doc ul, .legal-doc ol { color: var(--muted-foreground); margin: 0 0 0.75rem; padding-left: 1.25rem; }
.legal-doc li { margin: 0 0 0.25rem; }
/* A horizontal rule authored in the body (the front-matter fence never reaches the HTML). */
.legal-doc hr { border: 0; border-top: 1px solid var(--border); margin: 1.5rem 0; }
/* Public Sources list: source rows (favicon + name link) laid out in a responsive multi-column grid — the
   same auto-fill columns as the settings source picker (.source-picker-list), so wide screens fill several
   columns and narrow screens collapse to one. */
.src-list { list-style: none; padding: 0; margin: 0 0 1rem; display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.25rem 1.25rem; }
.src-list-item { display: flex; align-items: center; gap: 0.6rem; min-width: 0; padding: 0.35rem 0.25rem; }
.src-list-item a, .src-list-item span { color: var(--foreground); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.src-list-item a:hover { color: var(--primary); text-decoration: underline; }
/* A hidden (unchecked) source dims its name so the visible set stands out. */
.source-check input:not(:checked) ~ .source-name { color: var(--muted-foreground); }
/* "GDELT-only" source marker (admin-gated): a small uppercase chip after the source name. */
.source-gdelt-badge {
    flex: 0 0 auto;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    line-height: 1;
    padding: 0.15rem 0.35rem;
    border-radius: 0.25rem;
    color: var(--muted-foreground);
    border: 1px solid var(--border);
    background: var(--surface);
}
/* Footer: hidden-count status + the apply button. */
.source-picker-footer { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; justify-content: space-between; margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.source-hidden-status { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: var(--muted-foreground); }
.source-hidden-status strong { color: var(--foreground); }
.source-apply { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--primary-foreground, #fff); background: var(--primary); border: 1px solid var(--primary); border-radius: 0.375rem; padding: 0.6rem 1.5rem; cursor: pointer; }
.source-apply:hover { filter: brightness(1.05); }

/* ---- Footer ---- */
.footer { border-top: 1px solid var(--border); margin-top: 4rem; }
.footer-inner { display: flex; flex-direction: column; gap: 0.75rem; padding: 2rem 0; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem; color: var(--muted-foreground); }
.footer-links { display: flex; gap: 1rem; flex-wrap: wrap; }

/* Phone footer (mobile redesign F2.4). The column direction is already the base state (the row layout
   starts at 640px, see the compact-chrome block below), so this only centres the stack and opens the
   link rows up: at 375px the six links wrap onto three lines, and a 1rem single-axis gap left them
   close enough to mis-tap. line-height 2 plus the vertical padding gives each link a comfortable row. */
@media (max-width: 639px) {
    .footer-inner { align-items: center; text-align: center; }
    .footer-links { justify-content: center; gap: 0.5rem 1rem; line-height: 2; }
    .footer-links a { padding: 0.25rem 0; }
}
.footer .brand-inline { color: var(--primary); font-weight: 700; }
/* Second footer row (copyright + rights): sits tight under the first row, slightly dimmer. */
.footer-legal { padding-top: 0; opacity: 0.85; }

/* The deployment-identity row: the last thing on the page, separated from the footer above it by a rule
   of its own. Centred on phones together with the rest of the footer (see the <640px block above). */
.footer-build {
    border-top: 1px solid var(--border);
    padding: 0.6rem 0 1rem;
    text-align: right;
}
@media (max-width: 639px) {
    .footer-build { text-align: center; }
}

/* ---- Mobile search overlay entry ---- */
.search-toggle { display: inline-flex; }

/* ---- Responsive ---- */
@media (min-width: 640px) {
    .search-form { display: flex; }
    .search-toggle { display: none; }
    .footer-inner { flex-direction: row; align-items: center; justify-content: space-between; }
}
@media (min-width: 768px) {
    .nav-links { display: flex; }
    .hot-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
    .feed-grid { grid-template-columns: minmax(0, 1fr) 320px; }   /* px sidebar: fixed under text zoom */
    .search-input { width: 18rem; }
}

/* Featured stories strip — operator-featured headlines under the header, on every page.
   Horizontally scrollable on narrow screens; a subtle surface band that sits between the
   sticky nav and the page body. Hidden entirely when there are no featured stories. */
.featured-bar {
    border-bottom: 1px solid var(--border);
    background: var(--surface);
}
.featured-bar-inner {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    overflow-x: auto;
    scrollbar-width: thin;
}
.featured-bar-list {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    white-space: nowrap;
}
.featured-bar-item {
    flex: 0 0 auto;
    font-size: 0.8125rem;
    font-weight: 400;
    color: var(--foreground);
    text-decoration: none;
    transition: color 0.15s ease;
}
.featured-bar-item:hover { color: var(--primary); }

/* ===== Boxed front page (F2) ===== */

/* ---- Hero / lead zone ----
   One large lead story + a few secondary cards above the fold. Single column on mobile; the big lead
   beside a column of secondary cards on wide screens. */
.hero { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem; margin-bottom: 2.5rem; }
@media (min-width: 768px) {
    /* Big lead (3fr) beside the two stacked secondary cards (2fr). align-items: start so expanding one
       column's inline coverage doesn't stretch the neighbouring column with empty space. */
    .hero { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); align-items: start; }
}
.hero-lead {
    /* Flex column so the tinted panel can grow to fill the box up to the JS-set min-height (which matches
       the two-card column) — no empty space under the content in the big lead. */
    display: flex;
    flex-direction: column;
    /* align-self: start (not stretch) so the big lead does NOT follow the grid row height when a small
       card is expanded (which would grow the big box with empty space). Instead, site.js sets a
       min-height equal to the two-card column's DEFAULT height, so in the resting state the big box is
       at least as tall as the two small cards, while expanding a small card leaves the big box untouched
       and expanding the big box itself just grows it past the min-height. */
    align-self: start;
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    background: var(--surface);
    /* No padding: the full-bleed image + tinted panel fill the box (see .tinted-panel). */
    padding: 0;
    transition: border-color .15s;
}
.hero-lead:hover { border-color: color-mix(in srgb, var(--primary) 40%, transparent); }
/* The image keeps its aspect ratio; the tinted panel grows to fill the remaining height so the big lead
   has no empty space below its content. */
.hero-lead > .story-media { flex: 0 0 auto; }
.hero-lead > .tinted-panel { flex: 1 1 auto; display: flex; flex-direction: column; }
/* Head/title/lead sit at the top; the coverage foot is pushed to the bottom so the grown panel reads as
   deliberately full rather than top-aligned with a colored gap below. */
.hero-lead > .tinted-panel .hero-foot { margin-top: auto; }
/* Head row: badge left, "open page" ↗ icon right. */
.hero-lead-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; }
/* Title is a link that expands the coverage inline (no navigation); cue on hover. */
.hero-title-link { display: block; color: inherit; text-decoration: none; }
.hero-title-link:hover .hero-title { color: var(--primary); }
.hero-lead .story-expand { padding: 0.75rem 1.5rem 1rem; }
/* The signature element: a confident, large lead headline. clamp() scales it with the viewport. */
.hero-title {
    margin: 0.75rem 0 0.5rem;
    font-size: clamp(1.5rem, 1rem + 2.2vw, 2.5rem);
    font-weight: 800;
    line-height: 1.1;
    letter-spacing: -0.015em;
    color: var(--foreground);
}
.hero-lead-text {
    margin: 0 0 1rem;
    font-size: 1rem;
    line-height: 1.55;
    color: var(--muted-foreground);
    /* Full lead in the big featured box — no line clamp (it drove the "cut at ~200 chars" look). */
}
.hero-foot { display: flex; align-items: center; justify-content: space-between; border-top: 1px solid var(--border); padding-top: 1rem; }
.hero-time { color: var(--muted-foreground); }
/* Secondary leads: a stack of the existing StoryCards. */
/* Secondary featured cards: stacked in the 2fr column beside the big lead. */
.hero-rest { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; }

/* ---- Category boxes ----
   Two INDEPENDENT flex columns on desktop, filled EVEN/ODD by the markup (cat0→left, cat1→right, cat2→left,
   …), so the boxes read LEFT-TO-RIGHT while each column flows on its OWN — collapsing a box slides up only
   the boxes below it in the SAME column, so there is never a cross-column height gap (the CSS-grid version
   left one, because a grid row is as tall as its tallest cell). On mobile the column wrappers dissolve
   (display:contents) and every box is ordered by --box-order (its taxonomy index), giving one sequential
   column in reading order. On desktop order:0 restores DOM order within each column (so drag-drop sticks). */
.cat-box-grid { display: flex; flex-direction: column; gap: 1.25rem; }
.cat-box-col { display: contents; }
.cat-box { order: var(--box-order, 0); }
@media (min-width: 768px) {
    .cat-box-grid { flex-direction: row; align-items: flex-start; }
    .cat-box-col { display: flex; flex-direction: column; flex: 1 1 0; min-width: 0; gap: 1.25rem; }
    .cat-box { order: 0; }
}
.cat-box {
    border: 1px solid var(--border);
    border-top: 2px solid var(--cat-accent, var(--primary));
    border-radius: 0.5rem;
    background: var(--card);
    padding: 0.5rem 1rem 0.75rem;
    min-width: 0;
}
/* Box header: drag handle + the category name (its accent) + a "more →" link pushed to the right. */
.cat-box-head { display: flex; align-items: center; justify-content: flex-start; gap: 0.5rem; padding: 0.5rem 0; border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); margin-bottom: 0.25rem; }
/* Drag handle for personal box reordering (grabs the whole box; see site.js). */
.cat-box-drag { cursor: grab; color: var(--muted-foreground); font-size: 0.85rem; line-height: 1; user-select: none; flex: 0 0 auto; }
.cat-box-drag:active { cursor: grabbing; }
.cat-box-drag:hover { color: var(--foreground); }
/* The box being dragged is dimmed for feedback. */
.cat-box.dragging { opacity: 0.5; }
/* The title is a bare toggle button (collapses the box). Styled to read as the heading, not a button. */
/* Title link (navigates to the topic page) — no underline by default, accent-coloured title, underline on
   hover. Takes the natural width between the drag handle and the right-aligned toggle. */
.cat-box-title-link { text-decoration: none; color: inherit; min-width: 0; }
.cat-box-title-link:hover .cat-box-title { text-decoration: underline; }
/* Collapse/expand icon button — pushed to the far right (margin-left:auto), just the chevron. */
.cat-box-toggle { display: flex; align-items: center; justify-content: center; margin-left: auto; background: none; border: none; padding: 0.15rem 0.2rem; cursor: pointer; font: inherit; color: inherit; flex: 0 0 auto; }
.cat-box-chevron { font-size: 1.5rem; line-height: 1; color: var(--muted-foreground); transition: transform .15s ease; }
.cat-box-toggle:hover .cat-box-chevron { color: var(--cat-accent, var(--foreground)); }
.cat-box-title { margin: 0; font-size: 0.95rem; font-weight: 800; letter-spacing: -0.01em; color: var(--cat-accent, var(--foreground)); }

/* "Reset order" control: shown (via site.js) only when a personal box order is saved. */
.box-order-reset-wrap { display: flex; justify-content: flex-end; margin-bottom: 0.5rem; }
.box-order-reset { background: none; border: 1px solid var(--border); border-radius: 0.25rem; color: var(--muted-foreground); cursor: pointer; padding: 0.2rem 0.6rem; }
.box-order-reset:hover { color: var(--foreground); border-color: var(--muted-foreground); }
/* Grid-rows animator: 1fr (open) → 0fr (collapsed) smoothly animates the list height. The inner list
   must clip its overflow and allow a 0 min-height for the collapse to reach zero. */
.cat-box-listwrap { display: grid; grid-template-rows: 1fr; transition: grid-template-rows 0.22s ease; }
.cat-box.collapsed .cat-box-listwrap { grid-template-rows: 0fr; }
.cat-box-list { display: flex; flex-direction: column; overflow: hidden; min-height: 0; }
/* Category totals line under the box header (clusters · sources · articles). */
.cat-box-totals { display: block; margin: 0.1rem 0 0.35rem; color: var(--muted-foreground); }
/* Collapsed state: point the chevron right (the height itself is animated by the wrap above). */
.cat-box.collapsed .cat-box-chevron { transform: rotate(-90deg); }
@media (prefers-reduced-motion: reduce) { .cat-box-listwrap { transition: none; } }
@media (prefers-reduced-motion: reduce) { .cat-box-chevron { transition: none; } }

/* ---- Compact story row (inside boxes) ---- */
.story-compact { padding: 0 0 0 0.5rem; }
/* No category accent line beside the cluster-LIST rows in a box (the box header already carries the
   category). Override the base .story-row[data-cat] accent to transparent; the accent line instead runs
   down the left of the EXPANDED coverage (see .story-compact .story-expand below). */
.story-compact[data-cat] { border-left-color: transparent; }
/* The header row is a horizontal flex: an optional related-image thumbnail on the LEFT (rendered only when
   the row has an image) + the text body on the right — the same anatomy as the category-page cards, sized
   down for the dense boxes. The body takes the remaining width; without a thumbnail it fills the row. */
.story-compact-inner { padding: 0.75rem 0; display: flex; gap: 0.9rem; align-items: stretch; }
.story-compact-inner > .story-body { flex: 1 1 0; min-width: 0; }
/* Left thumbnail: SAME size + behaviour as the cluster coverage list (.source-list-cards .source-media) —
   a fixed-width (px, stable under text zoom), rounded image stretched to the row height. */
.story-compact-media {
    /* `flex: 0 1 auto` + max-width, NOT `flex: 0 0 auto`: the width below is VIEWPORT-relative, but this
       row lives in a box that is half of a content column that has already given 176px to the category
       rail — so its container shrinks far faster than the viewport does. Measured (1280px container,
       2rem grid gap, 1.25rem box gap):

           viewport 1200px → box column 470px, image 176px → 280px left for the text   (fine)
           viewport 1000px → box column 370px, image 176px → 180px left
           viewport  800px → box column 270px, image 160px →  96px left  → the title wraps to 5+ lines

       and because .story-compact-inner stretches its items, that tall row stretched the IMAGE to match —
       the box visibly fell apart on its way down from 1200px. Capping the image at a share of the ROW
       keeps the proportion stable at every width. 38% is just above the widest case that already worked
       (176/470 = 37.4%), so nothing changes at 1200px and up.

       The vw term is kept rather than replaced: it is what keeps the image from ballooning on a very wide
       screen, where the percentage cap alone would let it grow with the column.

       The width cap alone is NOT enough, which is why align-self changed too. This box used to be
       `align-self: stretch`, i.e. its height was whatever the text column happened to need — so a
       narrow column that wrapped the title onto four lines produced a 150px-tall thumbnail. Capping the
       width helps the text get more room, but at 800px the row is still ~155px tall and a stretched
       image still follows it. An explicit aspect-ratio makes the height a function of the WIDTH alone,
       so the thumbnail is the same shape at every viewport and can never be inflated by a long title.
       16/10 is the ratio the featured cards already use. */
    flex: 0 1 auto;
    width: clamp(96px, 20vw, 176px);
    max-width: 38%;
    align-self: flex-start;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    border-radius: 0.4rem;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 12%, var(--surface));
}
.story-compact-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.story-compact .story-rank { display: none; }
.story-compact .story-meta { margin-bottom: 0.25rem; gap: 0.5rem; }
/* Title row: the clickable/expandable title + the small "open cluster page" icon. */
.story-compact-title-row { display: flex; align-items: baseline; gap: 0.35rem; }
.story-compact-title { color: var(--foreground); font-size: 0.9rem; font-weight: 700; line-height: 1.3; }
.story-compact-title:hover { color: var(--primary); cursor: pointer; }
/* Cluster lead under the title — one step smaller than the 0.9rem title, muted, clamped to 2 lines. */
.story-compact-lead {
    margin: 0.2rem 0 0;
    font-size: 0.75rem;
    line-height: 1.4;
    color: var(--muted-foreground);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.story-compact-open { flex: 0 0 auto; color: var(--muted-foreground); font-size: 0.8rem; line-height: 1; text-decoration: none; }
.story-compact-open:hover { color: var(--primary); }
/* Cluster-row expand/collapse chevron (the title navigates; this small icon toggles the inline coverage).
   Rotates 180° when the row is open. Since 2026-08-13 only SEARCH rows render it — the browse feeds and the
   front-page boxes dropped the affordance — so these rules now serve one surface. */
.story-compact-toggle { flex: 0 0 auto; align-self: center; background: none; border: none; padding: 0; margin: 0; cursor: pointer; color: var(--muted-foreground); line-height: 1; display: inline-flex; align-items: center; }
/* Collapsed (default) the chevron points RIGHT; expanded (.open) it points DOWN — like the category box. */
.story-compact-chevron { font-size: 1.5rem; line-height: 1; transition: transform .15s ease; transform: rotate(-90deg); }
/* Homepage BOX cluster rows use a slightly smaller chevron than the category-page (StoryRow) ones. */
.story-compact .story-compact-chevron { font-size: 1.3rem; }
.story-compact-toggle:hover .story-compact-chevron { color: var(--cat-accent, var(--foreground)); }
.story-row.open .story-compact-chevron { transform: rotate(0deg); }
@media (prefers-reduced-motion: reduce) { .story-compact-chevron { transition: none; } }

/* ---- Category-box rows as full-bleed CARDS on narrow layouts (trial, 2026-08-10) ----
   Requested variant: in the single-column regime a box row stops being a thumbnail-plus-text row and
   becomes the same kind of card the featured tiles are — the story's picture is the whole background and
   the text sits ON it, bottom-aligned, over a scrim.

   BREAKPOINT: max-width 767px, not 639px. 768px is where .cat-box-grid turns into two side-by-side
   columns (see the .cat-box-grid rule above); below it every box is one full-width column, which is the
   layout the card shape is for — the card's width is simply the box's inner width, so it tracks the box
   at every size instead of the viewport. At 768px and up nothing changes: the desktop row shape stands.

   ANATOMY (deliberately the featured tiles' one, minus the category chip — every row in a box already
   belongs to the box's category, so the chip would repeat it on every card):
     layer 0  .story-compact-media  — absolute, full-bleed, object-fit:cover
     layer 1  .story-compact-inner::after — the scrim gradient, tinted from --panel-bg (the image's
              AA-clamped dominant colour, emitted inline by CompactStoryRow) or the category accent
     layer 2  .story-body — title + meta, in normal flow at the bottom of the card

   The scrim is a PSEUDO-ELEMENT rather than a .card-scrim div because this row's markup has no such
   element, and the whole point of doing this in CSS is that the DOM is shared with the desktop shape.

   HEIGHT: `aspect-ratio` gives the card its picture-like proportion, and `min-height: fit-content` is
   what keeps that from clipping — the box has overflow:hidden, so a title that needs more room than the
   ratio allows would otherwise be cut in half. With the content minimum the ratio is a floor, not a cap.

   NOTE vs. F3.1: below 640px the FEATURED tiles render as panels (image above, text below), because at
   375px a title written across a ~130px-tall photo covers the picture. These cards are the opposite
   choice on the same screen. That is intentional for the trial — if the result reads well, the honest
   follow-up is to bring the featured tiles back to overlay at phone width too, so the page speaks one
   language again; if it does not, this block is the thing to delete.

   Placed AFTER the base .story-compact-* rules on purpose — a media query adds no specificity, so these
   would silently lose to the plain rules above if they came first. */
@media (max-width: 767px) {
    /* TWO cards per row while two FIT, one when they do not. The list was a flex column of rows; as a
       card grid it becomes a real track grid.

       The track expression does two jobs at once, which is why it is not a plain `repeat(2, …)`:

         minmax(max(140px, (100% - 0.75rem) / 2), 1fr)

       • the `140px` term is the floor — auto-fit can only place a second track when two of them plus the
         gap fit, so the grid drops to ONE column by itself on a narrow box. Measured at the reference
         widths (container padding 1rem, box padding 1rem, gap 0.75rem):
             375px viewport → box inner 309px → 2 x 148.5px   (two fit)
             320px viewport → box inner 254px → 1 x 254px     (two do not: 2x140+12 = 292)
       • the `(100% - 0.75rem) / 2` term is the CAP. Without it auto-fit would keep adding tracks on a
         wide box — in the 640-767px band the box is still full-width (~620px inner), which is room for
         four 140px tracks. Making the minimum half the container means two tracks always exactly fill
         it, so the count can never go above two.

       overflow/min-height stay as they were: the collapse animation needs them. */
    .cat-box .cat-box-list {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(max(140px, (100% - 0.75rem) / 2), 1fr));
        gap: 0.75rem;
        align-items: start;
    }
    /* No `.story-row.open` rule here on purpose: the inline expansion has no trigger on a card (see the
       hidden .story-compact-toggle below), so a row can never reach the open state at this width. */
    .cat-box .story-compact {
        padding: 0;             /* the card spans the box's full inner width (base: 0.5rem left inset) */
        border-bottom: none;
        border-left: none;
        background: none;       /* .story-row:hover/.open paint --surface; the card brings its own */
    }

    /* The card itself. --tint is the same expression the featured tiles use: the photo's clamped colour
       when there is one, else the category accent mixed toward near-black (coloured, never neutral). */
    .cat-box .story-compact-inner {
        --tint: var(--panel-bg, color-mix(in srgb, var(--cat-accent, var(--primary)) 60%, #0b0b0c));
        position: relative;
        isolation: isolate;     /* keeps the layer order local, like .hot-card--overlay */
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        gap: 0;
        aspect-ratio: 16 / 10;
        min-height: -webkit-fit-content;    /* older WebKit only understands the prefixed keyword */
        min-height: fit-content;
        overflow: hidden;
        border-radius: 0.5rem;
        padding: 0;
        background: var(--tint);    /* visible when the story has no image, or the image fails to load */
    }

    /* Layer 0 — the picture fills the card. Undoes every part of the desktop thumbnail sizing. */
    .cat-box .story-compact-media {
        position: absolute;
        inset: 0;
        z-index: 0;
        width: auto;
        max-width: none;
        aspect-ratio: auto;
        align-self: auto;
        border-radius: 0;
        background: none;
    }

    /* Layer 1 — the scrim. Opaque under the text, clear at the top, so the photo still reads as a photo
       while the white text keeps its contrast. Decorative: it must not swallow clicks on the picture. */
    .cat-box .story-compact-inner::after {
        content: "";      /* the pseudo-element only exists at this width; its ramp is the shared rule */
        position: absolute;
        inset: 0;
        z-index: 1;
        pointer-events: none;
    }

    /* Layer 2 — the text block. Stays in normal flow (that is what lets the card grow for a long title)
       and is pinned to the bottom by the container's justify-content. */
    .cat-box .story-compact-inner > .story-body {
        position: relative;
        z-index: 2;
        flex: 0 0 auto;
        min-width: 0;
        padding: 0.85rem 1rem 0.95rem;
        color: #fff;
    }

    /* Title: the featured headline treatment (heavier, tighter, clamped, shadowed for light photos).
       The row switches from baseline to top alignment because a clamped multi-line title has no useful
       baseline to align the chevron to. */
    .cat-box .story-compact-title-row { align-items: flex-start; }
    .cat-box .story-compact-title {
        flex: 1 1 auto;
        min-width: 0;
        color: #fff;
        font-size: 1rem;
        font-weight: 800;
        line-height: 1.16;
        letter-spacing: -0.015em;
        text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45);
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        overflow: hidden;
    }
    .cat-box .story-compact-title:hover { color: rgba(255, 255, 255, 0.82); }
    .cat-box .story-compact-title .story-source-prefix { color: rgba(255, 255, 255, 0.9); }

    /* Dropped on the card: the lead and the latest-development line. The featured tiles show title +
       meta only, and both of these would push the text band over most of the picture. Both are still
       reachable — the lead on the title's tooltip, the latest headline on the event page. */
    .cat-box .story-compact-lead,
    .cat-box .story-compact .story-latest { display: none; }

    /* Meta: same figures, white on the scrim. The source pill needs the plate treatment the featured
       cards use — its accent tint is invisible over a photo. */
    .cat-box .story-meta-compact {
        margin-top: 0.5rem;
        font-family: inherit;
        font-size: 0.72rem;
        text-transform: none;
        letter-spacing: 0;
        color: rgba(255, 255, 255, 0.88);
    }
    /* Same as the featured cards: white and bold (see .card-overlay-body .cov-count-sources). */
    .cat-box .story-meta-compact .cov-count-sources { color: #fff; }
    .cat-box .story-meta-compact .cov-total,
    .cat-box .story-meta-range { color: rgba(255, 255, 255, 0.82); }

    /* No inline expansion on a card. The chevron opened the coverage list inside the row, which on a card
       means an article list unrolling under a photo tile — and in the two-up grid it also reflowed the
       whole box. The card's job here is to lead to the event page, which the title already does; the
       coverage is one tap away there, in a layout built for it.
       Superseded on 2026-08-13: the box rows no longer render a chevron at ANY width (the owner removed the
       inline expansion from the front page and the category pages outright), so this rule is what that
       decision generalized — kept only because a phone must not show one if the markup ever comes back. */
    .cat-box .story-compact-toggle { display: none; }

    /* The collapsed-sibling links live on the scrim. */
    .cat-box .story-attached-link { color: rgba(255, 255, 255, 0.85); }
    .cat-box .story-attached-link:hover { color: #fff; }
    /* The video/podcast glyphs are drawn in the accent colour, which disappears on a dark scrim. */
    .cat-box .story-meta-compact .media-badge-icon { color: #fff; opacity: 0.9; }

    /* ---- The ARTICLE view's rows on the same cards ----
       CompactArticleRow and CompactMediaRow already use this row's markup (.story-compact-inner /
       .story-compact-media / .story-body), so the card shape above applies to them unchanged — what they
       need is the finish, because their own chrome is coloured for a light surface and disappears on a
       scrim. Only colours are set here; nothing about the layout differs from the cluster cards.

       One thing the article cards CANNOT have: --panel-bg. ArticleDto and RelatedMediaDto carry no
       dominant image colour (only ImageUrl / ThumbnailUrl), so their scrim tints from the category accent
       — every card in a box shares the box's hue instead of picking up its own photo. Changing that is a
       publishing-pipeline change (a new field through Contracts and the public API), not a CSS one. */
    .cat-box .story-meta-source .story-source-name { color: rgba(255, 255, 255, 0.92); }
    /* The favicon/monogram square sits on the photo; its light plate would read as a sticker. */
    .cat-box .story-meta-source .monogram { background: rgba(255, 255, 255, 0.22); color: #fff; }
    /* Media rows: the kind glyph, the kind chip and the duration are all accent-on-light by default. */
    .cat-box .media-monogram { background: rgba(255, 255, 255, 0.18); color: #fff; }
    .cat-box .media-kind-chip { background: none; padding: 0; color: rgba(255, 255, 255, 0.9); }
    .cat-box .media-metaline .media-duration,
    .cat-box .media-type-chip { color: rgba(255, 255, 255, 0.85); }
}

/* The SMALL two-up cell: narrow enough that the cards need their own proportions, wide enough that two
   still belong side by side. A 16/10 tile at this width would be ~94px tall, which the text band alone
   fills, so the tile goes square and gives the picture back its share, and the type drops one step.

   The lower bound is what keeps this off phones: below 480px the grid is one card per row (see the block
   after the category feed), where the cell is full width again and the wider block above already fits. */
@media (min-width: 480px) and (max-width: 639px) {
    .cat-box .story-compact-inner { aspect-ratio: 1 / 1; }
    .cat-box .story-compact-inner > .story-body { padding: 0.6rem 0.7rem 0.7rem; }
    .cat-box .story-compact-title { font-size: 0.85rem; -webkit-line-clamp: 3; }
    .cat-box .story-meta-compact { margin-top: 0.35rem; font-size: 0.64rem; gap: 0.3rem; }
    /* Collapsed siblings would outgrow a 150px tile; they stay on the wider layouts. */
    .cat-box .story-attached { display: none; }
}

/* ---- Category feed as full-bleed CARDS on narrow layouts (2026-08-10) ----
   The same treatment the front page's category boxes get, applied to the category (topic) pages' cluster
   list, so the two surfaces read alike: the picture is the whole background, the text sits on it, and the
   list is a two-up grid that drops to one column when two no longer fit.

   The row's markup differs from the box row's (.story-row-media / .story-row-inner instead of
   .story-compact-media / .story-body), which is why this is a second block rather than a shared selector
   list — a combined selector would have to carry both element names for every declaration and would read
   worse than two blocks that each say what they do.

   The track expression is identical to the boxes'; see the comment there for the measured widths and for
   why the minimum is `max(140px, (100% - gap) / 2)` — floor and two-column cap in one term. The feed is
   wider than a box (it is the page's content column, not half of one), so the cap is what does the work
   here: without it auto-fit would put four or five tracks in a 700px feed.

   Placed AFTER the base .topic-feed rules (which are far above, in the category-card section) — a media
   query adds no specificity, so these would silently lose if they came first. */
@media (max-width: 767px) {
    .topic-feed {
        grid-template-columns: repeat(auto-fit, minmax(max(140px, (100% - 0.75rem) / 2), 1fr));
        gap: 0.75rem;
        align-items: start;
    }

    /* The card. --tint is the photo's AA-clamped colour (RowStyle already emits --panel-bg) or the
       category accent mixed toward near-black. The base row's border/padding go: the picture is the card. */
    .topic-feed .story-row {
        --tint: var(--panel-bg, color-mix(in srgb, var(--cat-accent, var(--primary)) 60%, #0b0b0c));
        position: relative;
        isolation: isolate;
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        flex-wrap: nowrap;
        gap: 0;
        aspect-ratio: 16 / 10;
        min-height: -webkit-fit-content;    /* older WebKit only understands the prefixed keyword */
        min-height: fit-content;            /* a long title lengthens the card instead of being clipped */
        overflow: hidden;
        border: none;
        border-radius: 0.5rem;
        padding: 0;
        background: var(--tint);
    }

    /* Layer 0 — the picture fills the card (was a fixed-width column stretched to the row height).
       aspect-ratio is reset for the same reason as on the featured tiles: an absolutely positioned box
       with a definite width honours its ratio over the block inset and then sits inside the inset box
       instead of filling it, leaving a hard-edged band of card background below the picture. */
    .topic-feed .story-row-media {
        position: absolute;
        inset: 0;
        z-index: 0;
        width: auto;
        aspect-ratio: auto;
        align-self: auto;
        border-radius: 0;
        background: none;
    }

    /* Layer 1 — the scrim, as a pseudo-element: this markup has no .card-scrim element, and keeping the
       DOM identical to the desktop shape is the whole point of doing this in CSS. */
    .topic-feed .story-row::after {
        content: "";      /* the pseudo-element only exists at this width; its ramp is the shared rule */
        position: absolute;
        inset: 0;
        z-index: 1;
        pointer-events: none;
    }

    /* Layer 2 — the text, in normal flow (so the card can grow) and pinned to the bottom by the
       container's justify-content. */
    /* NOT positioned on purpose: z-index applies to a FLEX ITEM without it, and leaving this static is what
       lets the category pill below resolve its absolute position against the whole card (.story-row) rather
       than against this bottom text block. */
    .topic-feed .story-row-inner {
        z-index: 2;
        flex: 0 0 auto;
        min-width: 0;
        padding: 0.85rem 1rem 0.95rem;
        color: #fff;
    }

    /* Title: the featured headline treatment. */
    .topic-feed .story-title-row { align-items: flex-start; }
    .topic-feed .story-row h4 {
        color: #fff;
        font-size: 1rem;
        font-weight: 800;
        line-height: 1.16;
        letter-spacing: -0.015em;
        text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45);
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        overflow: hidden;
    }
    .topic-feed .story-title-link { flex: 1 1 auto; }
    .topic-feed .story-title-link:hover h4 { color: rgba(255, 255, 255, 0.82); }

    /* Dropped on the card, exactly as on the box cards: the lead and the latest-development line. The
       meta line stays — it is where the category label now lives, and on a mixed feed that is the only
       thing saying which section the card belongs to. */
    .topic-feed .story-lead,
    .topic-feed .story-latest { display: none; }

    /* Meta: same figures, white on the scrim; the source pill takes the plate treatment used on photos. */
    .topic-feed .story-meta-foot { margin-top: 0.5rem; gap: 0.4rem; }
    .topic-feed .story-meta-foot .label,
    .topic-feed .story-meta-source .story-source-name { color: rgba(255, 255, 255, 0.88); }
    .topic-feed .story-meta-foot .label {
        font-family: inherit;
        font-size: 0.72rem;
        text-transform: none;
        letter-spacing: 0;
    }
    .topic-feed .story-meta-foot .cov-count-sources { color: #fff; }
    .topic-feed .story-meta-foot .media-badge-icon { color: #fff; opacity: 0.9; }
    /* On a phone the category LEAVES the meta line and becomes the card's corner pill — the same place and
       shape the featured tiles use (.card-chiprow .rovat: top-left, over the image). Two reasons: the bottom
       meta strip is the busiest part of a photo card, and a section marker reads faster where the eye lands
       first; and the accent tokens are tuned for the page surface, not for a dark scrim — the light theme's
       are deep by design (AA on paper) and sank into the photo as plain text. As a filled pill the accent is
       the BACKGROUND, so every theme reads at full strength.
       It also has to restate the mono/uppercase micro-type, because the rule above deliberately strips those
       from the .labels on this line. */
    .topic-feed .story-meta-foot .cat-link {
        position: absolute;
        top: 0.7rem;
        left: 0.7rem;
        z-index: 3;
        display: inline-flex;
        align-items: center;
        padding: 0.2rem 0.45rem;
        border-radius: 0.3rem;
        background: var(--cat-accent, var(--primary));
        color: #fff;
        font-family: 'JetBrains Mono', ui-monospace, monospace;
        font-size: 0.6rem;
        font-weight: 700;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
    }
    .topic-feed .story-meta-foot .cat-link:hover { color: #fff; text-decoration: none; filter: brightness(1.14); }
    /* The pill is out of flow but still the meta line's FIRST child, so the item after it would draw a
       leading separator dot with nothing before it. */
    .topic-feed .story-meta-foot > .cat-link + *::before { content: none; }
    /* The drawn separator inherits the muted page colour; on the scrim it needs the white voice too. */
    .topic-feed .story-meta-foot > *::before { color: rgba(255, 255, 255, 0.75); }

    /* No inline expansion on a card — the decision that, on 2026-08-13, was extended to every width: these
       feeds no longer render a chevron at all. Kept as a guard for the same reason as the box rule above. */
    .topic-feed .story-compact-toggle { display: none; }
}

/* The small two-up cell, as on the box cards: square tile, type one step down. Bounded below at 480px for
   the same reason — under that the grid is one card per row. This block must stay AFTER the 767px block
   above: a media query adds no specificity, so a later rule at equal specificity wins regardless of which
   breakpoint is "narrower". */
@media (min-width: 480px) and (max-width: 639px) {
    .topic-feed .story-row { aspect-ratio: 1 / 1; }
    .topic-feed .story-row-inner { padding: 0.6rem 0.7rem 0.7rem; }
    .topic-feed .story-row h4 { font-size: 0.85rem; }
    .topic-feed .story-meta-foot { margin-top: 0.35rem; gap: 0.3rem; }
    .topic-feed .story-meta-foot .label { font-size: 0.64rem; }
}

/* ---- Phones: one card per row (2026-08-10) ----
   Two cards side by side stop being scannable once the cell drops to phone width — at 375px a box cell is
   ~148px and the feed's is ~165px, which is a thumbnail with a headline crammed onto it. Below 480px both
   grids therefore go to a single column, and the card is full width again.

   480px rather than a container-derived floor: the auto-fit expression above would switch at a different
   viewport for each surface (a box is half a content column, the feed is the whole one — ~418px vs ~384px
   with the same floor), and "how wide is the phone" is the question actually being asked. 480px puts every
   phone in portrait — 320 (SE 1st gen), 375 (SE 2nd/3rd), 390, 412, 430 (Pro Max) — on one card per row,
   and leaves two-up to landscape phones and small tablets.

   Nothing else needs restating: with one card per row the cell is as wide as it is in the 767px block, so
   that block's 16/10 ratio and full-size type are correct again — which is exactly why the square/small
   type above is bounded to the 480-639px band instead of being undone here. */
@media (max-width: 479px) {
    .cat-box .cat-box-list,
    .topic-feed { grid-template-columns: minmax(0, 1fr); }
}

/* Keep the category's own colour on hover: the shared .cat-link:hover recolours to the brand accent, which
   would drop exactly the information a category label carries. The underline still marks it as actionable.
   The extra class in the selector wins the tie against `.cat-link:hover` independently of source order. */
.label.cat-link[data-cat]:hover { color: var(--cat-accent); text-decoration: underline; }

/* ---- Category page, ARTICLE view: the coverage list as full-bleed CARDS (2026-08-10) ----
   The third surface to get the treatment, and it needs its own block because its markup is a third one
   again: the article view of a category renders the shared SourceList (.source-list-cards > .source-item >
   .source-body + a.source-media), not a story row. That same component draws the EVENT page's coverage and
   the inline row expansions, and both must keep their list shape — hence the .topic-article-coverage scope
   (a class on the container in Topic.razor, which survives the in-place source filter because site.js
   swaps the container's CONTENTS, not the container).

   DateAboveImage is false here, so every item is the simple two-child arrangement — .source-body plus the
   image anchor — which is exactly what a card needs: the image becomes the absolute backdrop and the body
   stays in flow at the bottom.

   No --panel-bg on this surface either: the coverage rows carry an image URL but no dominant colour, so
   the scrim tints from the category accent (same limitation as the article boxes). */
@media (max-width: 767px) {
    .topic-article-coverage .source-list-cards {
        grid-template-columns: repeat(auto-fit, minmax(max(140px, (100% - 0.75rem) / 2), 1fr));
        align-items: start;
    }
    .topic-article-coverage .source-list-cards .source-item {
        --tint: color-mix(in srgb, var(--cat-accent, var(--primary)) 60%, #0b0b0c);
        position: relative;
        isolation: isolate;
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        gap: 0;
        aspect-ratio: 16 / 10;
        min-height: -webkit-fit-content;
        min-height: fit-content;
        overflow: hidden;
        border: none;
        border-radius: 0.5rem;
        padding: 0.85rem 1rem 0.95rem;
        background: var(--tint);
        color: #fff;
    }
    /* Layer 0 — the picture. `order: -1` was what put it left of the text in the row; irrelevant now, and
       aspect-ratio is reset for the reason documented on the featured tiles (an absolutely positioned box
       honours its ratio over the block inset and then sits inside it instead of filling it). */
    .topic-article-coverage .source-list-cards .source-media {
        position: absolute;
        inset: 0;
        z-index: 0;
        order: 0;
        width: auto;
        align-self: auto;
        aspect-ratio: auto;
        border-radius: 0;
        background: none;
    }
    /* Layer 1 — the scrim (a pseudo-element again: this markup has no scrim node). */
    .topic-article-coverage .source-list-cards .source-item::after {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 1;
        pointer-events: none;
        background: linear-gradient(to top,
            color-mix(in srgb, var(--tint) 96%, transparent) 0%,
            color-mix(in srgb, var(--tint) 92%, transparent) 16%,
            color-mix(in srgb, var(--tint) 78%, transparent) 30%,
            color-mix(in srgb, var(--tint) 60%, transparent) 42%,
            color-mix(in srgb, var(--tint) 42%, transparent) 54%,
            color-mix(in srgb, var(--tint) 26%, transparent) 66%,
            color-mix(in srgb, var(--tint) 13%, transparent) 78%,
            color-mix(in srgb, var(--tint) 5%, transparent) 88%,
            color-mix(in srgb, var(--tint) 1%, transparent) 96%,
            transparent 100%);
    }
    /* Layer 2 — the text. */
    .topic-article-coverage .source-list-cards .source-body {
        position: relative;
        z-index: 2;
        flex: 0 0 auto;
        min-width: 0;
    }
    .topic-article-coverage .source-headline {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        overflow: hidden;
        color: #fff;
        font-weight: 800;
        line-height: 1.16;
        letter-spacing: -0.015em;
        text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45);
    }
    /* The source line sits above the headline inside the text band. */
    .topic-article-coverage .source-topline { margin-bottom: 0.2rem; }
    .topic-article-coverage .source-feed,
    .topic-article-coverage .source-time,
    .topic-article-coverage .media-kind-chip,
    .topic-article-coverage .media-duration,
    .topic-article-coverage .media-type-chip { color: rgba(255, 255, 255, 0.88); }
    .topic-article-coverage .media-kind-chip { background: none; padding: 0; }
    .topic-article-coverage .source-topline .monogram,
    .topic-article-coverage .media-monogram { background: rgba(255, 255, 255, 0.2); color: #fff; }
    /* Dropped, as on every other card: the lead would push the text band over the picture. */
    .topic-article-coverage .source-lead { display: none; }
}

/* Phone: one card per row here too — same threshold and reasoning as the box and feed grids. */
@media (max-width: 479px) {
    .topic-article-coverage .source-list-cards { grid-template-columns: minmax(0, 1fr); }
}

/* Inline coverage (front-page boxes AND category pages): frameless GRID cards — no border/background/box
   padding. Layout: text body on the LEFT (spans both rows), the article DATE top-right (above the image),
   the image bottom-right. The event page keeps its framed flex cards (its coverage is rendered directly on
   the page, not inside a .story-expand). */
.story-expand .source-list-cards .source-item { border: none; background: none; padding: 0; display: block; }
/* Split rows (image present): grid — source line + date on row 1, body-rest + image on row 2, so the image
   top aligns with the article TITLE and the date sits at the source-line level. Rows without an image use the
   default block layout above → full-width text. */
.story-expand .source-list-cards .source-item--split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "topline date" "rest media";
    column-gap: 0.9rem;
    row-gap: 0.3rem;
    align-items: start;
}
.story-expand .source-item--split > .source-topline { grid-area: topline; }
.story-expand .source-item--split > .source-time-aside { grid-area: date; justify-self: end; text-align: right; margin: 0; align-self: center; }
.story-expand .source-item--split > .source-body { grid-area: rest; }
.story-expand .source-item--split > .source-media {
    grid-area: media;
    align-self: start;
    aspect-ratio: 4 / 3;   /* definite height in the stacked right column (was stretch-to-row) */
}
/* The headline is flush with the cell top so its top lines up with the image top. */
.story-expand .source-item--split .source-headline { margin-top: 0; }
/* Front-page BOX cluster expansion: FIXED image width (the category page keeps the responsive clamp width). */
.story-compact .story-expand .source-item--split > .source-media { width: 140px; }
/* A faint separator line between the article rows in the expanded coverage. */
.story-expand .source-list-cards .source-item + .source-item {
    border-top: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
    padding-top: 0.75rem;
}
/* Front-page BOX inline coverage: a "load more" styled like the box's own bottom "load more" button
   (.cat-box-more-btn) — full width, centered, muted, chevron suffix. */
.story-compact .story-expand .coverage-more { display: block; margin: 0.5rem 0 0; }
.story-compact .story-expand .coverage-more-btn {
    display: block; width: 100%; margin: 0; padding: 0.2rem;
    font-family: inherit; font-size: 0.7rem; text-transform: none; letter-spacing: normal;
    color: var(--muted-foreground); background: none; border: none; border-radius: 0; cursor: pointer; text-align: center;
}
.story-compact .story-expand .coverage-more-btn::after { content: " ▾"; }
.story-compact .story-expand .coverage-more-btn:hover { color: var(--foreground); }
/* Meta: "N forrás, M cikk" on the left; the coverage span pushed to the right edge. */
.story-meta-compact { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.4rem; }
/* Right group of the compact meta line: last-update time + the (expand-only) coverage sort toggle. */
.story-meta-right { margin-left: auto; display: inline-flex; align-items: center; gap: 0.4rem; }
.story-meta-range { margin-left: auto; }
/* (Removed 2026-08-08: the inline row expansion never rendered a .coverage-sort-toggle of its own — the
   class belongs to the event page's coverage toolbar alone, where a display:none default only survived
   because a later rule overrode it.) */
/* The inline coverage panel inside a box: the category accent line runs down its LEFT edge, with the
   article rows indented beside it (moved here from the cluster-list rows above). */
.story-compact .story-expand { padding: 0 0.25rem 1rem 0.85rem; }
.story-compact .story-expand:not([hidden]) {
    border-left: 3px solid color-mix(in srgb, var(--cat-accent) 55%, transparent);
    margin-left: 0.15rem;
}

/* ---- Story images (F1) — hotlinked publisher images; the image is an accent, never required.
   Every media box has a fixed aspect-ratio so the layout never shifts whether the image loads or
   not, and a category-tinted background so an image-less / broken box reads as intentional, not
   missing. Fallback = the category color the parent already carries via data-cat (--cat-accent). ---- */
.story-media {
    position: relative;
    overflow: hidden;
    border-radius: 0.375rem;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 12%, var(--surface));
}
.story-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* A failed image (onerror adds .broken): hide the broken glyph, keep the category-tinted panel. */
.story-media.broken img { display: none; }

/* ---- Clickable card/hero picture (StoryMediaLink) ----
   A stretched, empty anchor covering the media box, so clicking the PICTURE opens the same target as the
   headline. z-index 1 inside the media box: above the image (and the overlay monogram block), but BELOW
   the source monogram badges (z-index 4), which keep their own hover tooltips. In overlay mode the whole
   media box is z-index 0 within the card, so the text body (z-index 2) and the chip row (z-index 3) still
   take the click on their own area; the scrim and the melt gradient are pointer-events:none and never
   intercept it. Cursor comes from the anchor itself — no extra affordance needed. */
.story-media-link {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: block;
}

/* ---- Source monogram badges laid on a featured/hero/event image ----
   A small row of source-initial chips at the top-right of an image (inside .story-media, which is
   position:relative). The chips overlap ~30% so a cluster's covering sources read as a compact stack; a
   light ring separates them on the photo, and each chip's tooltip is the source name. A "+N" chip collapses
   the remainder. In overlay mode the scrim is transparent at the top, so the chips stay legible over it. */
/* Slightly faded so the source badges stay a subtle secondary detail on the image, not a loud element. */
.source-badge-row { position: absolute; top: 0.6rem; right: 0.6rem; z-index: 4; display: flex; flex-direction: row; opacity: 0.75; }
/* Size/shape override for the shared .monogram chip: a round avatar with a separating ring. The hashed
   background colour still comes from .monogram (+ the inline --mono-h), which this does not override. */
.source-badge-row .source-badge {
    width: 1.5rem;
    height: 1.5rem;
    flex-shrink: 0;
    border-radius: 50%;
    font-size: 0.6rem;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9), 0 1px 4px rgba(0, 0, 0, 0.35);
}
/* Each chip after the first slides left ~30% of its 1.5rem width, so the chips overlap into a stack. */
.source-badge-row .source-badge + .source-badge { margin-left: -0.45rem; }
/* Overflow "+N" chip: neutral dark (no hashed colour), same size/shape as a monogram. */
.source-badge-row .source-badge-more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(20, 20, 25, 0.9);
    color: #fff;
    font-family: 'JetBrains Mono', monospace;
    font-weight: 700;
    letter-spacing: 0;
}

/* Category pill on the EVENT hero image (top-left), mirroring the featured cards' .card-chiprow .rovat. */
.event-hero-cat {
    position: absolute;
    top: 0.7rem;
    left: 0.7rem;
    z-index: 4;
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.45rem;
    border-radius: 0.3rem;
    background: var(--cat-accent, var(--primary));
    color: #fff;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-decoration: none;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
}

/* ---- Particle-style tinted panels (F1 colour). Full-bleed image on top + a text panel below whose
   background is the image's AA-clamped dominant colour (inline style from ColorPanel), white text on it.
   No image colour → the CSS category-accent fallback below (still coloured, never neutral). ---- */

/* Full-bleed media at the top of a card/hero: edge-to-edge (no radius/margin; the card clips corners). */
/* Hero image sizes the box: it fills the width at its natural aspect (no crop, no blurred side fill) —
   the box height follows the image. The secondary cards keep a fixed 16:10 crop. */
.hero-media { border-radius: 0; }
.hero-media img { height: auto; }        /* natural aspect (overrides the base height:100%/cover) */
.hero-media::after { height: 4rem; }     /* a softer, taller fade toward the text below */
.hot-card-media { aspect-ratio: 16 / 10; border-radius: 0; }

/* The tinted text panel. Default background = the category accent mixed toward near-black (a deep,
   readable tint) — used when there is no inline image colour. White text throughout. */
.tinted-panel {
    padding: 1.1rem 1.25rem 1.2rem;
    color: #fff;
    /* The panel colour comes from --panel-bg (the image's AA-clamped dominant colour, set inline on the
       container), falling back to the category-accent tint when there's no image colour. The image's
       bottom gradient (below) fades into the SAME variable, so the seam melts. */
    background: var(--panel-bg, color-mix(in srgb, var(--cat-accent, var(--primary)) 58%, #0b0b0c));
}
/* Melt: the bottom edge of the lead image fades into the panel colour, so there is no hard seam between
   the photo and the tinted text panel. The gradient reads the same --panel-bg variable as the panel. */
.hero-media::after, .hot-card-media::after, .detail-media::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 3rem;
    background: linear-gradient(to bottom, transparent, var(--panel-bg, color-mix(in srgb, var(--cat-accent, var(--primary)) 58%, #0b0b0c)));
    pointer-events: none;
    z-index: 2;   /* above the (z-index:1) contained hero image so the melt is visible */
}
/* Text + chrome recoloured for the dark tinted background. */
.tinted-panel h2, .tinted-panel h3, .tinted-panel .hero-title { color: #fff; }
.tinted-panel .hero-lead-text,
.tinted-panel .detail-lead,
.tinted-panel .label,
.tinted-panel .cap,
.tinted-panel .num { color: rgba(255, 255, 255, 0.86); }
.tinted-panel .num { color: #fff; }
.tinted-panel .story-compact-open { color: rgba(255, 255, 255, 0.85); }
.tinted-panel .story-compact-open:hover { color: #fff; }
.tinted-panel .story-source-prefix { color: rgba(255, 255, 255, 0.9); }
/* Category badge on a tinted panel: a near-white pill with the CATEGORY-colour text, so the category's
   colour code shows here too (readable on the image-tinted panel). --cat-accent comes from the badge's
   own data-cat. */
.tinted-panel .badge { background: rgba(255, 255, 255, 0.92); color: var(--cat-accent, var(--primary)); }
.tinted-panel .cat-count { background: rgba(0, 0, 0, 0.22); color: #fff; }
/* Foot separators become translucent-white on the tint. */
.tinted-panel .hot-card-foot, .tinted-panel .hero-foot { border-top-color: rgba(255, 255, 255, 0.22); }
/* Title hover cue stays within the white family (the brand primary would clash with the tint). */
.tinted-panel .hot-card-title:hover h3,
.tinted-panel .hero-title-link:hover .hero-title { color: rgba(255, 255, 255, 0.82); }
/* The panel headings keep their own sizing; just drop the top margin (image sits directly above). */
.tinted-panel .hot-card-head, .tinted-panel .hero-lead-head { margin-top: 0; }
.hot-card .tinted-panel h3 { margin-top: 0.5rem; }
.hero-lead .tinted-panel .hero-title { margin-top: 0.5rem; }

/* Event detail: a 16:9 lead image on top, then the tinted header panel (they read as one unit — the image
   rounds the top, the panel rounds the bottom). Coverage list below stays on the normal background. */
.detail-media {
    aspect-ratio: 16 / 9;
    border-radius: 0.5rem 0.5rem 0 0;
    overflow: hidden;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 12%, var(--surface));
}
.detail-media + .detail-head.tinted-panel { border-radius: 0 0 0.5rem 0.5rem; margin-top: 0 !important; }
.detail-head.tinted-panel {
    border-radius: 0.5rem;
    border-left: none;
    padding: 1.25rem 1.5rem 1.35rem;
    margin-bottom: 1.5rem;
}
.detail-head.tinted-panel h1 { color: #fff; }
.detail-media-credit { font-size: 0.8rem; color: rgba(255, 255, 255, 0.75); margin: 0.6rem 0 0; }
.detail-media-credit a { color: #fff; text-decoration: underline; }

/* ===== Overlay card variant (opt-in: Display:CardImageStyle=overlay) =====
   A "newspaper front page" render mode for the boxed surfaces (home hero zone + category/topic feeds):
   a full-bleed image with the dominant-colour scrim gradient and white text laid ON the photo, instead
   of the panel-below-image layout above. The event page, search and the front-page category boxes are
   NOT affected (they never get the --overlay modifier). WCAG-AA is guaranteed by construction: --tint is
   the SAME AA-clamped dominant colour (--panel-bg, relative luminance <= 0.16) the panel mode uses, and
   the scrim is >= 94% opaque under the text band, plus a text-shadow — so white text keeps contrast
   >= 4.5:1. No-image → the scrim tints from the category accent and a monogram block fills the photo. */

/* Card container: its own stacking context (isolation:isolate) so the layered image/scrim/text/chip are
   never re-ordered by a hovered transform (the §7 "image only on hover" trap), with an intrinsic
   aspect-ratio so there is no layout shift whether the image loads or not. --tint = the image's
   AA-clamped colour (--panel-bg) or, with no image colour, the category accent mixed toward near-black
   (still coloured, never neutral). */
.hot-card--overlay, .hero-lead--overlay {
    display: block;
    position: relative;
    isolation: isolate;
    overflow: hidden;
    border-radius: 0.5rem;
    padding: 0;
    --tint: var(--panel-bg, color-mix(in srgb, var(--cat-accent, var(--primary)) 60%, #0b0b0c));
    /* Belt and braces behind the picture: without it the card's own surface (white in light theme) is
       what shows through wherever the image does not reach — which is how the aspect-ratio bug above
       painted a white band under every tile. With the tint there, the same failure would at least read
       as a deliberately dark card rather than a broken one. */
    background: var(--tint);
}
.hot-card--overlay { aspect-ratio: 16 / 10; }
/* The big lead has no standalone aspect-ratio: in the featured matrix it spans a 2×2 grid area and takes
   its size from the surrounding tiles (see .hero-overlay-grid .hero-lead--overlay below). */

/* Layer 0 — the image (or monogram block) fills the whole card. Overrides the panel-mode media rules
   (which size the box and add a bottom "melt" gradient); here the media is an absolute full-bleed layer. */
.hot-card--overlay .story-media, .hero-lead--overlay .story-media {
    position: absolute;
    inset: 0;
    z-index: 0;
    /* MUST be reset (measured 2026-08-10). The panel-mode media box carries `.hot-card-media
       { aspect-ratio: 16 / 10 }`, and an absolutely positioned box does NOT ignore its aspect ratio just
       because both insets are 0: with a definite inline size the ratio wins over the block inset, and the
       box is then aligned inside the inset rectangle instead of filling it. Since the overlay tile is
       10/9 (the "taller tiles" experiment), the picture ended up 204px tall in a 293px card and the
       bottom 89px showed the card's own background — a hard horizontal line across every small featured
       card, at every viewport width. Measured on the running reader: card 328x295, media 326x204.
       The big lead never showed it because `.hero-media` declares no ratio, which is why the first
       featured item looked correct and the rest did not. */
    aspect-ratio: auto;
    border-radius: 0;
    background: none;
}
.hot-card--overlay .story-media img, .hero-lead--overlay .story-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;      /* fill + crop (overrides the panel-mode hero-media natural-aspect rule) */
}
/* The panel-mode bottom "melt" gradient must not render in overlay mode (the scrim replaces it). */
.hot-card--overlay .story-media::after, .hero-lead--overlay .story-media::after { content: none; }
/* No-image monogram block: a stable hashed-hue square with the source initials, in place of the photo. */
.card-mono {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    font-weight: 800;
    font-size: 2.5rem;
    color: #fff;
    background: hsl(var(--mono-h, 220) 42% 34%);
}

/* Layer 1 — the dominant-colour scrim: opaque at the bottom (behind the text), fading to clear at the
   top, so the white title/meta always sit on a dark band while the top of the photo stays visible. */
.card-scrim {
    position: absolute;
    inset: 0;
    z-index: 1;
    /* Purely decorative gradient — must not intercept the pointer, or it would swallow the hover/click
       on the elements laid on the image (source badges + the event category pill inside .story-media). */
    pointer-events: none;
}

/* The scrim RAMP, shared by every surface that lays text on a photo: the featured tiles and big lead
   (.card-scrim), the front-page box cards and the category-feed cards (whose markup has no scrim element,
   so they draw it with a pseudo-element). One rule so the three can never drift apart.

   Why the long stop list. The ramp used to be four stops — 94% at the bottom, 72% at a third of the
   height, 20% at 62%, clear at 84% — which spends most of its length either nearly opaque or nearly
   clear and drops 52 percentage points across a quarter of the card. Easing it out over more stops keeps
   the photo emerging gradually instead of over one short span.

   NOT the fix for the hard line reported on 2026-08-10: that was the media box keeping its 16/10 ratio
   inside a 10/9 card (see `.hot-card--overlay .story-media`), leaving real, uncovered card background
   below the picture. This ramp was changed first on a wrong hypothesis; it is kept because it is a small
   improvement on its own terms, not because it fixed anything.

   The contrast guarantee is unchanged and in fact stronger: the text band sits in the bottom ~30%, where
   this ramp is 78% opaque or more (the old one fell to 72% by 34%), over the same AA-clamped tint. Keep
   any future edit at or above these values below the 30% mark. Placed OUTSIDE the card media queries on
   purpose — the pseudo-elements only generate where `content` is set, so this rule is inert elsewhere. */
.card-scrim,
.cat-box .story-compact-inner::after,
.topic-feed .story-row::after {
    background: linear-gradient(to top,
        color-mix(in srgb, var(--tint) 96%, transparent) 0%,
        color-mix(in srgb, var(--tint) 92%, transparent) 16%,
        color-mix(in srgb, var(--tint) 78%, transparent) 30%,
        color-mix(in srgb, var(--tint) 60%, transparent) 42%,
        color-mix(in srgb, var(--tint) 42%, transparent) 54%,
        color-mix(in srgb, var(--tint) 26%, transparent) 66%,
        color-mix(in srgb, var(--tint) 13%, transparent) 78%,
        color-mix(in srgb, var(--tint) 5%, transparent) 88%,
        color-mix(in srgb, var(--tint) 1%, transparent) 96%,
        transparent 100%);
}

/* Layer 3 — category badge + source count, top-left over the image. */
.card-chiprow {
    position: absolute;
    top: 0.7rem;
    left: 0.7rem;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
/* Solid category pill (its own data-cat accent) — the category colour code shows over the photo. */
.card-chiprow .rovat {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.45rem;
    border-radius: 0.3rem;
    background: var(--cat-accent, var(--primary));
    color: #fff;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
}
/* The pill is clickable (.cat-link → the category page). The shared .cat-link hover recolours the TEXT and
   underlines it, which is right for a bare label but wrong on a solid accent pill — white-on-accent is the
   whole point of this chip. So it keeps its colours and brightens the plate instead. */
.card-chiprow .rovat.cat-link:hover { color: #fff; text-decoration: none; filter: brightness(1.14); }
/* Source-count pill: near-white with a dark number. */
.card-chiprow .srcbadge {
    display: grid;
    place-items: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.3rem;
    border-radius: 0.7rem;
    background: rgba(255, 255, 255, 0.94);
    color: #111;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.66rem;
    font-weight: 800;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
}

/* Layer 2 — the text block, pinned to the bottom, white on the scrim. The whole block is the link. */
.card-overlay-body {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.9rem 1rem 1rem;
    color: #fff;
    text-decoration: none;
}
.card-overlay-body .headline {
    margin: 0;
    font-weight: 800;
    line-height: 1.16;
    letter-spacing: -0.015em;
    color: #fff;
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.hot-card--overlay .headline { font-size: 1.02rem; -webkit-line-clamp: 3; }
.hero-lead--overlay .headline { font-size: clamp(1.4rem, 1rem + 2vw, 1.9rem); -webkit-line-clamp: 4; }
/* The lead paragraph shows only on the hero (the small cards stay title + meta). */
.card-overlay-body .lead {
    margin: 0;
    font-size: 0.92rem;
    line-height: 1.4;
    color: rgba(255, 255, 255, 0.93);
    text-shadow: 0 1px 8px rgba(0, 0, 0, 0.45);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}
.hot-card--overlay .card-overlay-body .lead { display: none; }
.card-overlay-body .meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    font-size: 0.72rem;
    color: rgba(255, 255, 255, 0.88);
}
.card-overlay-body .meta b { font-weight: 800; color: #fff; }
.card-overlay-body .story-source-prefix { color: rgba(255, 255, 255, 0.9); }
.card-overlay-body:hover .headline { color: rgba(255, 255, 255, 0.82); }

/* ===== Overlay "featured matrix" (the hero / lead zone in overlay mode) =====
   A 4-column grid where the big lead spans 2×2 (top-left) and up to 8 secondary overlay tiles auto-flow
   into the remaining cells — the top-right 2×2 block and the full bottom row:
       x x y y
       x x y y
       y y y y
   The small tiles' 16:10 aspect-ratio sizes the auto rows; the big lead spans two of them, so its height
   is two tiles + one gap (no separate height math). Collapses to 2 columns below 900px. Only the
   featured zone uses this — the category feed below stays the horizontal .topic-feed rows. */
/* The base is ONE column: two ~215px min-content cards plus a 16px gap need 447px, which is 72px more
   than a 375px phone viewport — this was the single largest contributor to the measured 453px
   scrollWidth. Two columns start at 640px, four at 900px (unchanged). See the minmax(0, 1fr)
   convention note in the "Layout grid" section. */
.hero-overlay-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-auto-flow: row dense;
    gap: 1rem;
}
@media (min-width: 640px) {
    .hero-overlay-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
    .hero-overlay-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* The big lead spans a 2×2 block; its size comes from the grid area (the surrounding tiles size the
   tracks), so it drops any standalone aspect-ratio. align-self:stretch overrides the panel-mode
   .hero-lead's align-self:start — otherwise, with its content absolutely positioned, the lead would
   collapse to zero height instead of filling the 2×2 area. */
.hero-overlay-grid .hero-lead--overlay {
    grid-column: span 2;
    grid-row: span 2;
    aspect-ratio: auto;
    align-self: stretch;
}
/* Single-cluster category: the big lead is the ONLY tile, so there are no secondary tiles to size the
   grid rows — the 2×2 span would otherwise collapse to a thin strip (a ~20-30px image sliver). Restore an
   intrinsic featured-image aspect ratio when the lead stands alone, so it keeps a proper height. */
.hero-overlay-grid .hero-lead--overlay:only-child { aspect-ratio: 16 / 10; }
/* Experiment: taller featured tiles. The small tiles size the grid rows, so a taller tile ratio raises
   the whole matrix — the big 2×2 lead spans two of these rows, so it grows in step. 10:9 gives
   height = 0.9×width, i.e. +44% over the original 16:10 (0.625×width) — two +20% steps compounded. */
.hero-overlay-grid .hot-card--overlay {
    aspect-ratio: 10 / 9;
}
/* Mobile / narrow (the 2-column regime, < 900px): the big lead spans BOTH columns, so no secondary tile
   ever sits beside it to size its two rows — the 2×2 span therefore collapses to a ~10-20px image sliver
   (the same failure as the single-child case, but with siblings present). Fix: on this layout the big lead
   is a single-row, full-width banner with an intrinsic aspect ratio, and the secondary tiles flow below it.
   The ≥900px 4-column layout is untouched (there the top-right tiles size the big lead's rows). */
@media (max-width: 899px) {
    .hero-overlay-grid .hero-lead--overlay {
        grid-row: span 1;
        aspect-ratio: 16 / 10;
    }
}
/* ===== Phone rendering of the featured matrix (2026-08-10 — supersedes F3.1 + F3.2) =====

   OWNER DECISION. The redesign's F3.1 turned the overlay tiles into PANELS below 640px (image on top,
   text underneath) on the grounds that a title written across a ~130px-tall photo covers the picture and
   the scrim's contrast is unreliable over a light one; F3.2 then demoted the 4th and later tiles to
   76px-thumbnail rows, because 12-16 full-width cards would have been ~6000px of scrolling. The owner
   asked for the card form back on phones, together with two-up category boxes. Both objections are
   answered by the SAME change rather than by ignoring them:

     • two columns instead of one — the tiles get ~165px each, so the matrix is HALF as tall as the
       single-column card layout F3.2 was avoiding, and shorter than the panel layout it replaced
       (a panel is image + text stacked; a card is image WITH text);
     • the scrim, the text-shadow and the AA-clamped tint are unchanged from the desktop tiles, which is
       where the contrast guarantee actually comes from — it was never the photo's size that provided it.

   What is genuinely given up: on a 165px tile the headline is 0.85rem and clamped to 3 lines, so a long
   title is cut sooner than it was in the panel form. The full title stays on the link's tooltip.

   LAYOUT: the matrix stays ONE column — the big lead is one per row and so is every small tile. The
   density that F3.2 was buying with 76px rows is not bought back here; the page is longer, and that is
   the accepted cost of "every item on the front page is a card". The two-up density lives in the
   category boxes instead (see the .cat-box card grid).

   The single column needs the `grid-column: auto` reset below: the big lead's `grid-column: span 2` in
   the base rule is UNCONDITIONAL, and grid auto-placement sizes the implicit grid to hold the largest
   column span among auto-placed items — so a span-2 item silently re-creates a second, auto-sized column
   even though grid-template-columns declares one. That would bring back the horizontal overflow this
   breakpoint exists to remove. The selector matches the specificity of the rule it overrides.

   Everything else is simply the desktop overlay anatomy left alone: the layers, the scrim, the tint and
   the text colours are the base rules, and only the type sizes are stepped down (in the 639px block near
   the top of this file — headline 1.05rem on a tile, 1.35rem on the big lead). */
@media (max-width: 639px) {
    .hero-overlay-grid .hero-lead--overlay { grid-column: auto; }

    /* …and a ratio that keeps it the BIGGEST item, which single-column exposed as a bug: every card is
       the same width here, so the ratio alone decides the height, and the lead's 16/10 (0.625 x width)
       was SHORTER than the tiles' 10/9 (0.9 x width) — measured at 375px, lead 205px against tiles 295px.
       The lead is meant to be the largest thing on the page and was rendering as the smallest.

       1/1 puts it just above the tiles rather than far above: on a 375px phone that is a 343px square,
       already a third of the viewport, and the lead is further distinguished by its larger type and its
       lead paragraph. The wider layouts are untouched — from 640px up the lead spans both columns, so it
       is wider than a tile and its 16/10 is taller in absolute terms; only the equal-width single column
       had the inversion. The `:only-child` rule (0,2,1) deliberately still wins: with no tiles beside it
       there is nothing to be bigger than. */
    .hero-overlay-grid .hero-lead--overlay { aspect-ratio: 1 / 1; }

    /* The small tiles show their LEAD here, like the big one. The base rule
       (`.hot-card--overlay .card-overlay-body .lead { display: none }`) exists because a tile in the
       desktop matrix is a quarter of the row — title + meta is all that fits. In the single column a tile
       is the full width and 10/9 tall (~309px at 375px), of which the text band uses about half, so the
       reason for hiding it does not apply. Two lines, the same clamp the big lead uses.
       Scoped to .hero-overlay-grid so the desktop matrix keeps its title-and-meta tiles. */
    .hero-overlay-grid .hot-card--overlay .card-overlay-body .lead {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
    }

    /* The category feed's own phone shape lives with the other card treatments (see "Category feed as
       full-bleed cards" further up) — it is a card grid now, not a compact row. */
}

@media (prefers-reduced-motion: reduce) {
    .hot-card--overlay, .hero-lead--overlay { transition: none; }
}

/* ===== Breadcrumb + share bar (top of every page except the front page) =====
   Left: a clickable path (Home / category / event) with a faint divider beneath. Right: an (inert)
   Share button. Static SSR — the path segments are plain links. */
.breadcrumb {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0 0 0.6rem;
    margin: 0.25rem 0 1.1rem;
    border-bottom: 1px solid var(--border);      /* the faint horizontal divider under the path */
}
.breadcrumb-path { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; min-width: 0; font-size: 0.85rem; }
.breadcrumb-link { color: var(--muted-foreground); text-decoration: none; }
.breadcrumb-link:hover { color: var(--foreground); text-decoration: underline; }
.breadcrumb-current { color: var(--foreground); font-weight: 600; }
.breadcrumb-sep { color: var(--muted-foreground); opacity: 0.6; }
.breadcrumb-share {
    display: inline-flex; align-items: center; gap: 0.35rem; flex-shrink: 0;
    padding: 0.35rem 0.7rem; border-radius: 0.4rem;
    border: 1px solid var(--border); background: var(--surface);
    color: var(--foreground); font-size: 0.8rem; font-weight: 600; cursor: pointer;
}
.breadcrumb-share:hover { background: color-mix(in srgb, var(--foreground) 6%, var(--surface)); }
.breadcrumb-share-icon { font-weight: 700; }
/* Icon-only on narrow screens. Moved from an orphan `max-width: 560px` to the compact-chrome layer
   (F1.4): 560px belonged to no breakpoint layer, so the label disappeared 79px earlier than every other
   compact-chrome change. */
@media (max-width: 639px) { .breadcrumb-share-text { display: none; } }

/* ===== Event (cluster) detail: hero image + two-column body =====
   The hero honours the same overlay/panel config switch as the featured cards and is sized to ~2/3 of
   the viewport. Below it, the coverage list takes 2/3 of the width and a sidebar (Sources + Related) 1/3. */

/* Hero image container — ~2/3 viewport tall, with sane min/max bounds. In panel mode the image sits on
   top and the tinted header panel follows; in overlay mode it is the full-bleed backdrop (rules below). */
.event-hero-media {
    /* No fixed height: the box height FOLLOWS the image's natural aspect ratio (see the img rule), so
       there is neither a crop nor a letterbox — the frame simply matches the photo. */
    border-radius: 0.5rem 0.5rem 0 0;
    overflow: hidden;
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 12%, var(--surface));
}
/* The image fills the FULL column width; its height is capped so the event's title + lead + the first
   coverage rows stay ABOVE THE FOLD on an average laptop (~1440×800). object-fit:cover crops the excess from
   the BOTTOM (object-position: top), so the top is never cut, and the box background (category accent / the
   dominant colour) shows at the crop edges. Applies to both the panel and overlay hero modes. */
.event-hero-media img { display: block; width: 100%; height: auto; max-height: clamp(340px, 58vh, 560px); object-fit: cover; object-position: top center; }
.event-hero-media.broken img { display: none; }
/* Mobile: a proportionally smaller cap. Moved from `max-width: 640px` to 639px (F1.4) — the old bound
   OVERLAPPED the `min-width: 640px` layer at exactly 640px. It hit different selectors, so it caused no
   visible conflict, but a breakpoint that is simultaneously "mobile" and "not mobile" is a trap. */
@media (max-width: 639px) { .event-hero-media img { max-height: clamp(240px, 44vh, 400px); } }
/* The tinted header panel directly under a hero image joins it (square the shared corners, no gap). */
.event-hero-media + .event-hero-panel.tinted-panel { border-radius: 0 0 0.5rem 0.5rem; margin-top: 0 !important; }

/* Overlay hero: full-bleed image + dominant-colour scrim + white header text laid ON the photo. --tint is
   the AA-clamped image colour (--panel-bg) or the category accent mixed toward near-black (reused by the
   shared .card-scrim). Same construction as the featured overlay cards, only taller. */
.event-hero--overlay {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    border-radius: 0.5rem;
    margin-bottom: 1.5rem;
    /* No fixed height: the image (in normal flow, see below) drives the hero height, so the overlay box
       matches the photo's aspect ratio exactly — full image, no crop, no letterbox — and the scrim +
       header text are laid ON it. */
    --tint: var(--panel-bg, color-mix(in srgb, var(--cat-accent, var(--primary)) 60%, #0b0b0c));
}
/* The media box is in NORMAL FLOW (not absolute) so its height — the image's height — becomes the hero's
   height; the scrim + overlay body remain absolutely positioned over it. */
.event-hero--overlay .event-hero-media {
    position: static; z-index: 0; border-radius: 0;
}
/* Header text pinned to the bottom, white on the scrim. Reuses the panel content classes (detail-meta,
   detail-lead, detail-media-credit) but recolours them white for the on-photo placement. */
.event-hero-overlay-body {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
    padding: 1.25rem 1.5rem 1.4rem;
    color: #fff;
    max-height: 100%;
    overflow: hidden;
}
.event-hero-overlay-body h1 {
    color: #fff;
    font-size: clamp(1.5rem, 1.05rem + 2vw, 2.35rem);
    font-weight: 800; line-height: 1.14; letter-spacing: -0.01em; margin: 0.5rem 0;
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45);
}
.event-hero-overlay-body .detail-lead { color: rgba(255, 255, 255, 0.92); text-shadow: 0 1px 8px rgba(0, 0, 0, 0.4); }

/* ---- Event lead placement (2026-08-10) ----
   On the overlay hero the title and the lead share one bottom-pinned box over the photo. That reads well
   on a wide screen; on a phone the lead covers most of the picture and crowds the title in the same dark
   band. So the lead has two copies — one inside the hero, one right below it — and the viewport picks.
   Exactly one is ever displayed (display: none, not visibility/opacity), so a screen reader hears it once
   and the hidden copy takes no layout space. */
.detail-lead--below { display: none; }
@media (max-width: 639px) {
    .event-hero-overlay-body .detail-lead--onimage { display: none; }
    /* Smaller than the desktop 1rem: below the hero the lead is a supporting paragraph, not the page's
       opening statement — the title above it already carries that — and at 1rem it read as a second
       headline on a narrow column. */
    .detail-lead--below { display: block; margin: 0.9rem 0 0; font-size: 0.875rem; line-height: 1.55; }
}

.event-hero-overlay-body .label { color: rgba(255, 255, 255, 0.86); }
.event-hero-overlay-body .badge { background: rgba(255, 255, 255, 0.92); color: var(--cat-accent, var(--primary)); }
.event-hero-overlay-body .detail-media-credit { color: rgba(255, 255, 255, 0.8); }
.event-hero-overlay-body .detail-media-credit a { color: #fff; }
/* Bottom meta row on the hero image: source/article counts on the LEFT, the date range on the RIGHT. */
.event-hero-meta-row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; margin-top: 0.75rem; }
/* The "/{event total}" half of a header count figure ("3/8 forrás"): the same size and colour as the number
   it follows, only dimmed, so the pair reads as one figure while the in-window number stays the emphasized
   one. Faded rather than recoloured because it must work on BOTH hero variants — dark text on the tinted
   panel and white text over the photo. Hidden (by site.js or server-side) when the window shows everything. */
.cov-total { opacity: 0.65; }
.cov-total[hidden] { display: none; }
/* A count figure carrying a tooltip: hint the pointer without the underline a full <abbr> would add. */
.cov-count-group[title] { cursor: help; }
.event-hero-daterange { margin-left: auto; white-space: nowrap; }

/* ---- Overlay hero on phones (2026-08-10) ----
   The body is pinned to the image's bottom edge and clipped to its height (max-height: 100% + overflow:
   hidden on .event-hero-overlay-body), so everything competing for that space has to earn it. Three
   adjustments, all phone-only: tighter padding, a stacked meta block, and a headline that steps down for
   a long title.

   PLACEMENT MATTERS: this block must stay AFTER .event-hero-meta-row and .event-hero-daterange above.
   A media query adds NO specificity, so `.event-hero-daterange` inside one still scores 0,1,0 — exactly
   the same as the plain rule — and the later declaration wins. When this block sat earlier in the file
   the phone rules lost silently: the meta items stacked (flex-direction had nothing competing with it)
   but the date stayed pushed to the right edge by the `margin-left: auto` above, and align-items stayed
   `baseline`. Half-applied, which is the worst kind of broken — it looked deliberate.
   (Contrast the .hero-overlay-grid block further up, where the competing rules are descendant-qualified
   and the override therefore has to WIN on specificity, not on order.) */
@media (max-width: 639px) {
    /* 1.5rem of side padding is a quarter of a 375px screen once both sides are counted. */
    .event-hero-overlay-body { padding: 0.9rem 1rem 1rem; }

    /* Counts first, date range under them, both flush left. Side by side with `justify-content:
       space-between` they pushed to opposite edges of a 343px row, which read as two unrelated
       fragments; and once the counts grew (a "3/8 forrás · 12/40 cikk" figure) the row wrapped anyway —
       into the same stacked layout, only with the date still shoved right by its `margin-left: auto`.
       Both the auto margin and the baseline alignment have to be undone explicitly. */
    .event-hero-meta-row {
        flex-direction: column;
        align-items: flex-start;
        gap: 0.2rem;
        margin-top: 0.5rem;
    }
    .event-hero-daterange { margin-left: 0; }

    /* Headline size tiers, chosen server-side from the title's length (see Event.razor HeroTitleTier).
       The default is the clamp above (~24px at 375px); a long title steps down so the meta block below it
       stays inside the clipped box instead of being cut off. */
    .event-hero--title-l .event-hero-overlay-body h1 { font-size: 1.25rem; }
    .event-hero--title-xl .event-hero-overlay-body h1 { font-size: 1.05rem; line-height: 1.2; }

    /* Backstop for a title beyond even the second tier: clamp it rather than let it eat the meta block.
       Applied ONLY to the extreme tier — an ellipsised headline is a real loss, so it is the last resort,
       not the general case. The full title is still in the <title>, the breadcrumb and the og: tags. */
    .event-hero--title-xl .event-hero-overlay-body h1 {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 5;
        line-clamp: 5;
        overflow: hidden;
    }
}

/* Sidebar source rows hidden by the type filter (article/video/podcast), + the media sub-heading when empty. */
.source-agg-row.type-hidden, .source-agg-subhead.type-hidden { display: none; }
/* Small ease-in when the coverage list is swapped in after a source-filter change (mirrors the cluster
   expansion effect). */
.coverage-swap-in { animation: story-expand-in 0.38s cubic-bezier(0.22, 1, 0.36, 1); }
@media (prefers-reduced-motion: reduce) { .coverage-swap-in { animation: none; } }

/* Two-column body: coverage (2/3) on the left, sidebar (1/3) on the right; single column when narrow. */
.event-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem; align-items: start; }
@media (min-width: 900px) {
    .event-grid { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 2rem; }
}
.event-main { min-width: 0; }
/* The sidebar (sources aggregate + related threads) flows with the page and scrolls TOGETHER with the
   coverage list, rather than sticking to the header — so the whole event body scrolls as one column pair. */
.event-aside { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }

/* Sidebar boxes (Sources, Related threads). */
.aside-box { border: 1px solid var(--border); border-radius: 0.5rem; background: var(--surface); padding: 0.85rem 1rem 1rem; }
.aside-box-title { margin: 0 0 0.6rem; font-size: 0.95rem; font-weight: 800; letter-spacing: -0.01em; }
/* The source-filter anchor: offset the scroll target below the sticky header so the heading isn't hidden. */
#event-sources { scroll-margin-top: 5rem; }

/* Bulk selection controls above the source rows: "select all" / "clear selection". Small outlined chips;
   the one that would be a no-op (everything already selected / nothing selected) is muted and inert. */
.source-agg-bulk { display: flex; gap: 0.35rem; margin: 0 0 0.5rem; flex-wrap: wrap; }
.source-agg-bulk-btn { font-size: 0.7rem; line-height: 1; padding: 0.3rem 0.4rem; color: var(--muted-foreground); text-decoration: none; border: none; border-radius: 0.25rem; white-space: nowrap; cursor: pointer; }
.source-agg-bulk-btn:hover { color: var(--primary); }
.source-agg-bulk-btn.disabled { opacity: 0.4; pointer-events: none; }

/* Source-list sort keys (name / article count / last update). The active key is marked by emphasis alone —
   accent colour + bold + its direction arrow — deliberately WITHOUT an outline, so the small sidebar column
   stays quiet (the boxed segmented look of the coverage sort bar would be too loud here). */
.source-agg-sort { display: flex; gap: 0.15rem; margin: 0 0 0.4rem; flex-wrap: wrap; }
.source-sort-key { font-size: 0.7rem; line-height: 1; padding: 0.3rem 0.4rem; color: var(--muted-foreground); background: none; border: none; border-radius: 0.25rem; cursor: pointer; white-space: nowrap; }
.source-sort-key:hover { color: var(--foreground); }
.source-sort-key.active { color: var(--primary); font-weight: 700; }
.source-sort-key.active[data-dir="asc"]::after { content: " ▲"; font-size: 0.55rem; }
.source-sort-key.active[data-dir="desc"]::after { content: " ▼"; font-size: 0.55rem; }

/* Sources box: one row per feed — monogram + name (left), article count + last article time (right). */
.source-agg-list { display: flex; flex-direction: column; gap: 0.1rem; }
/* Sub-heading separating the video/podcast media sources from the article feeds in the category sidebar. */
.source-agg-subhead { margin: 0.6rem 0 0.15rem; padding-top: 0.5rem; border-top: 1px solid var(--border); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; font-size: 0.7rem; }
.source-agg-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0; }
/* Clickable source rows (per-event source filter): hover/selected affordance. As an <a> they inherit no
   link colour here; the selected row gets a tinted background + accent bar so the active filter is obvious. */
.source-agg-toggle { cursor: pointer; text-decoration: none; color: inherit; padding-left: 0.4rem; padding-right: 0.4rem; margin: 0 -0.4rem; border-radius: 0.4rem; border-left: 3px solid transparent; }
.source-agg-toggle:hover { background: var(--surface); }
.source-agg-toggle.selected { background: var(--surface); border-left-color: var(--primary); }
.source-agg-toggle.selected .source-agg-name { color: var(--primary); }
.source-agg-row .monogram { width: 1.6rem; height: 1.6rem; font-size: 0.7rem; border-radius: 0.3rem; }
/* A long source name WRAPS onto a second line (overflow-wrap breaks an over-long token). The element is
   CONTENT-sized (flex: 0 1 auto — not a block-filling -webkit-box), so the article count stays right next
   to the name instead of being pushed to the far right. */
.source-agg-name { flex: 0 1 auto; min-width: 0; font-size: 0.85rem; font-weight: 600; color: var(--foreground); overflow-wrap: anywhere; }
.source-agg-count { flex-shrink: 0; font-size: 0.7rem; font-weight: 600; }
/* Right-aligned (margin-left:auto), smaller than the count/name. */
.source-agg-time { margin-left: auto; flex-shrink: 0; font-size: 0.66rem; white-space: nowrap; text-align: right; }
/* The timeline anchor's source when it has no in-window article: same subdued treatment as the anchor row on
   the timeline, and NOT a filter toggle (no hover/pointer affordance) — filtering to it would empty the list.
   It carries no article count either: nothing of it is counted in the window. */
.source-agg-row--out { opacity: 0.62; padding-left: 0.4rem; padding-right: 0.4rem; margin: 0 -0.4rem; }
.source-agg-out-mark { flex-shrink: 0; font-size: 0.62rem; padding: 0; color: var(--muted-foreground); white-space: nowrap; }

/* Phones: the "outside the window" badge moves to its own line, under the source name.
   On one line the row is icon + name + this badge + the timestamp, and the badge is a nowrap ~90px block
   that neither shrinks nor wraps — so on a narrow row it takes its width out of the NAME's share, and the
   name (overflow-wrap: anywhere) breaks mid-word. The row then looks misaligned against the ordinary
   source rows above it, which is the reported symptom.

   GRID, not flex-wrap. Wrapping a flex line needs `flex-basis: 100%` on the badge to force the break, and
   that fights the shape we want: at 100% basis the badge either fills the whole line as a stretched pill,
   or — with the indent margin added on top of a 100% basis — overflows the row by the indent. A three-
   column grid states the intent directly: name and time on row 1, the badge on row 2 spanning from the
   name's column to the end, sized to its own content. The favicon needs no rule — it is the only item
   left with an auto position, so it lands in the first free cell.

   Desktop is untouched: there the sidebar column is wide enough for one line. */
@media (max-width: 767px) {
    .source-agg-row--out {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        align-items: center;
        column-gap: 0.5rem;
        row-gap: 0.15rem;
    }
    .source-agg-row--out .source-agg-name { grid-column: 2; grid-row: 1; }
    .source-agg-row--out .source-agg-time { grid-column: 3; grid-row: 1; }
    .source-agg-row--out .source-agg-out-mark {
        grid-column: 2 / -1;
        grid-row: 2;
        justify-self: start;    /* the pill keeps its own width instead of stretching across the span */
    }
}

/* Related threads box: small cards, nearest-first, each with its category accent (like the featured cards). */
.related-list { display: flex; flex-direction: column; gap: 0.6rem; }
.related-card {
    display: flex; gap: 0.6rem; text-decoration: none; color: inherit;
    border-radius: 0.4rem; padding: 0.4rem 0.5rem;
    border-left: 3px solid color-mix(in srgb, var(--cat-accent, var(--primary)) 65%, transparent);
    background: color-mix(in srgb, var(--cat-accent, var(--primary)) 5%, var(--surface));
}
.related-card:hover { background: color-mix(in srgb, var(--cat-accent, var(--primary)) 10%, var(--surface)); }
.related-card-media { flex-shrink: 0; width: 3.5rem; height: 3.5rem; border-radius: 0.3rem; overflow: hidden; }
.related-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.related-card-body { min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.related-card-title {
    margin: 0; font-size: 0.85rem; font-weight: 700; line-height: 1.2;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.related-card-meta { font-size: 0.72rem; }
.related-card .rovat {
    align-self: flex-start; display: inline-flex; padding: 0.1rem 0.35rem; border-radius: 0.25rem;
    background: var(--cat-accent, var(--primary)); color: #fff;
    font-family: 'JetBrains Mono', monospace; font-size: 0.56rem; font-weight: 700;
    letter-spacing: 0.06em; text-transform: uppercase;
}

/* Related media cards (F3): reuse the related-card layout; a kind badge + a placeholder when no thumbnail. */
.media-card .media-thumb { position: relative; background: color-mix(in srgb, var(--text) 8%, var(--surface)); }
/* Placeholder (no/failed thumbnail): a centred kind glyph. Neutral — never signals a value judgement. */
.media-thumb--placeholder { display: flex; align-items: center; justify-content: center; }
.media-thumb--placeholder img { display: none; }
.media-thumb--placeholder::before { content: "▶"; font-size: 1.2rem; opacity: 0.55; }
.media-thumb--placeholder[data-kind="podcast"]::before { content: "🎙"; }
.media-kind-badge {
    position: absolute; bottom: 2px; right: 2px; background: rgba(0,0,0,0.72); color: #fff;
    font-size: 0.56rem; font-weight: 700; padding: 0 0.25rem; border-radius: 0.2rem; text-transform: uppercase;
    letter-spacing: 0.04em;
}
.media-source-name { font-weight: 600; }
.media-type-chip {
    margin-left: 0.3rem; padding: 0; font-size: 0.64rem; opacity: 0.75;
}

/* Dev-only admin-edit pencil next to the event title (localhost only; see Event.razor _showAdminEdit).
   Muted and small so it never competes with the headline; brightens to the accent on hover. Fixed-px
   icon so it stays small regardless of the (large) h1 font-size it lives inside. */
.admin-edit-link {
    display: inline-flex; align-items: center; vertical-align: middle;
    margin-inline-start: 0.5rem; color: inherit;
    opacity: 0.7; text-decoration: none; transition: opacity 0.12s ease;
}
.admin-edit-link:hover { opacity: 1; }
.admin-edit-link svg { display: block; width: 20px; height: 20px; }

/* Card variant of the admin-edit pencil: a small circular button that stays legible over a photo or a
   tinted panel (unlike the inline .admin-edit-link, which inherits the title color). Used in the hero /
   featured card headers (.card-chiprow / .hot-card-head). */
.admin-edit-badge {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.6rem; height: 1.6rem; flex-shrink: 0; border-radius: 50%;
    background: rgba(0, 0, 0, 0.45); color: #fff; text-decoration: none;
    opacity: 0.85; transition: opacity 0.12s ease, background 0.12s ease;
}
.admin-edit-badge:hover { opacity: 1; background: rgba(0, 0, 0, 0.65); }
.admin-edit-badge svg { display: block; width: 15px; height: 15px; }
/* In the panel-card header (flex, space-between) push the pencil to the far right, opposite the category. */
.hot-card-head .admin-edit-badge { margin-inline-start: auto; }

/* ==========================================================================================
   Cluster coverage: view switch toolbar + the timeline view.
   The toolbar (card/timeline switch + timeline sort toggle) sits at the top of the coverage
   region; the timeline is an alternative to the SourceList card list. Static SSR + theme-aware
   (all colors come from the shared tokens, so light/dark both work with no extra rules).
   ========================================================================================== */

/* Section-header row holding the view switch (left) and, in timeline view, the sort control (right). */
.coverage-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.75rem; }
/* With the card view disabled the view switch is not rendered, so the sort control is the toolbar's only
   child: space-between would park it on the left, away from the list it orders. Push it to the right, where
   the switch used to leave it. */
.coverage-toolbar--sort-only { justify-content: flex-end; }
/* Timeline sort control: an icon indicating the current direction + the current order in words (click flips
   both). It used to be a muted, icon-only link and read as decoration in an otherwise empty row; since
   2026-08-08 it carries a readable label, a larger accent-coloured glyph and full-strength text, so the one
   coverage choice that remains is visible without hovering. Deliberately FRAMELESS (owner request): no
   border and no filled background — the weight comes from the accent icon and the label, not from a box. */
.coverage-sort-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    /* Padding is kept for the touch target even without a visible frame. */
    padding: 0.3rem 0.15rem;
    border: 0;
    background: none;
    color: var(--foreground);
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.1;
    white-space: nowrap;
    text-decoration: none;
}
.coverage-sort-toggle:hover { color: var(--primary); }
/* The glyph is the accent: primary by default, and it follows the text colour on hover (inherit). */
.coverage-sort-toggle .seg-icon { display: block; flex: none; color: var(--primary); }
.coverage-sort-toggle:hover .seg-icon { color: inherit; }

/* The timeline itself: a semantic <ol> with a vertical spine drawn behind the per-item dots, a left time
   gutter to the left of the spine, and the content to the right. The spine and dots are decorative (the dots
   are aria-hidden); the reading order is the list. The left indent (--tl-content) leaves room for the gutter
   time + the spine; the spine (--tl-spine) sits just left of the content and the dot is centered on it. */
.story-timeline { list-style: none; margin: 0; padding: 0; position: relative; --tl-gutter: 5.6rem; --tl-spine: 6.1rem; --tl-content: 6.85rem; }
/* The spine — a thin vertical line running the height of the list, centered under the dots. Inset a little
   at top/bottom so it starts/ends at the first/last dot rather than overshooting. */
.story-timeline::before { content: ""; position: absolute; left: var(--tl-spine); top: 0.55rem; bottom: 0.55rem; width: 2px; background: color-mix(in srgb, var(--border) 85%, transparent); }
/* One article row: indented to clear the gutter + spine + dot; bottom padding separates consecutive entries. */
.timeline-item { position: relative; padding: 0 0 1.4rem var(--tl-content); }
.timeline-item:last-child { padding-bottom: 0.2rem; }
/* The exact time in the left gutter, right-aligned so it butts up against the spine, with its top in line
   with the dot. Today shows only the clock; an earlier day adds a date line above it (two lines). The date
   may wrap for a long earlier-year value; the short common case stays on one line. */
.timeline-gutter-time { position: absolute; left: 0; top: 0.3rem; width: var(--tl-gutter); text-align: right; line-height: 1.3; }
/* Date + clock lines share the same size/weight — the numeric date matches the HH:mm clock. */
.timeline-gutter-date,
.timeline-gutter-clock { display: block; white-space: nowrap; }

/* First/latest marker chips in the left gutter (above the date/time): the ORIGINAL solid style — bold white
   text on a solid amber ("reported first") / teal ("latest") fill. Inline-block + right-aligned by the
   gutter's text-align; wraps within the gutter width for the longer label. */
.timeline-badge { display: inline-block; max-width: 100%; margin-bottom: 0.2rem; padding: 0.18rem 0.32rem; border-radius: 0.25rem; font-weight: 700; color: #fff; line-height: 1.2; white-space: normal; }
.timeline-badge--first { background: #f59e0b; }   /* amber */
.timeline-badge--latest { background: #0d9488; }  /* teal */

/* ---- Timeline collapse on phones (mobile redesign F1.3) ----
   Desktop puts the publish time in a left gutter beside a vertical spine. Measured at a 375px viewport
   that gutter + spine consumed 110px of the 343px content column, leaving 85-145px for the headline —
   one title wrapped onto seven lines. On phones the spine shrinks to a hairline and the time moves into
   the normal flow, on its own line above the headline.

   --tl-content is the variable that actually indents the row (see .timeline-item / .timeline-gap
   padding-left); resetting only --tl-gutter and --tl-spine would leave the 110px indent in place. With
   --tl-content: 1.1rem (17.6px) the headline column is 343 - 17.6 = 325px, and the dot still fits:
   it is 0.62rem wide at left: calc(--tl-spine - 0.3rem), i.e. its right edge lands at ~13px. */
@media (max-width: 639px) {
    .story-timeline { --tl-gutter: 0; --tl-spine: 0.5rem; --tl-content: 1.1rem; }
    .timeline-gutter-time {
        position: static;
        width: auto;
        text-align: left;
        display: block;
        margin-bottom: 0.15rem;
    }
    /* The date and the clock are block-level in the gutter layout (their own lines). In the flow they
       must read as one line, hence inline + an explicit separating space (adjacent inline elements
       generated by Razor have no whitespace between them). */
    .timeline-gutter-date,
    .timeline-gutter-clock { display: inline; }
    .timeline-gutter-date + .timeline-gutter-clock::before { content: " "; }
    /* "Reported first" / "latest" keeps its own line: badge + date + clock on one 375px line would wrap
       mid-badge. */
    .timeline-badge { display: block; width: fit-content; max-width: 100%; }
}
/* The spine dot, centered on the spine line. A surface-colored border punches a gap in the spine behind it,
   and a 1px ring keeps it visible on both themes. Every dot is the same uniform small, neutral size. */
.timeline-dot { position: absolute; left: calc(var(--tl-spine) - 0.3rem); top: 0.4rem; width: 0.62rem; height: 0.62rem; border-radius: 50%; background: var(--muted-foreground); border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--border); box-sizing: border-box; }

/* Out-of-window anchor row: an article outside the active time window, shown as context so the header's
   full-span date range has a visible counterpart. Subdued as a whole (opacity on the <li>, so a row without a
   thumbnail fades uniformly — per-element colours would leave the favicon and the image at full strength) and
   marked by a HOLLOW spine dot: the row sits on the timeline but is not part of the counted set. */
.timeline-item--out { opacity: 0.62; }
.timeline-dot--out { background: var(--background); border-color: var(--surface); box-shadow: 0 0 0 1.5px var(--muted-foreground); }
/* The "outside the time window" marker: deliberately NEUTRAL (muted text, no fill) — the solid amber/teal
   badges mean "reported first"/"latest", and this is a state, not a distinction. Both can appear on the same
   row. It used to be outlined; the frame was dropped with the border convention above. */
.timeline-badge--out { color: var(--muted-foreground); background: none; font-weight: 600; }

/* The gap marker between the anchor and the in-window rows: a dashed spine segment plus a label naming what
   the break hides ("+7 cikk · 2 nap 14 óra").
   CSS technique (see also StoryTimelineGap.razor): the container's spine is ONE continuous absolutely
   positioned line (.story-timeline::before) whose top/bottom insets must stay untouched — cutting it per item
   would mean recomputing those insets from the item geometry. So this element instead (1) lays a strip of the
   PAGE background over the spine across its own height, hiding the continuous line exactly there, and (2)
   paints a dashed line on top with a repeating gradient. The mask uses --background because the timeline
   renders directly on the page background (the coverage column has no surface panel of its own); the
   .timeline-dot uses --surface for its own punch-through, which is close enough at dot scale but would show
   as a faint stripe over this taller segment. */
.timeline-gap { position: relative; padding: 0 0 1.4rem var(--tl-content); min-height: 2.2rem; }
.timeline-gap-spine { position: absolute; left: calc(var(--tl-spine) - 2px); top: -0.4rem; bottom: 0.3rem; width: 6px; background: var(--background); }
.timeline-gap-spine::after {
    content: ""; position: absolute; left: 2px; top: 0; bottom: 0; width: 2px;
    /* Dashed line drawn as a gradient (a CSS border-style would need a real box and would not align with the
       2px spine width): 4px of line, 4px of gap, repeated down the segment. */
    background: repeating-linear-gradient(to bottom, var(--muted-foreground) 0 4px, transparent 4px 8px);
    opacity: 0.5;
}
/* The label sits in the content column, aligned with the article rows' text so it reads as part of the list. */
.timeline-gap-body { padding-top: 0.1rem; }
.timeline-gap-label { color: var(--muted-foreground); }
.timeline-gap-count { font-weight: 700; }

/* Kind switch (Articles/Videos/Podcasts) hiding a timeline row — the gap marker goes with the articles. */
.timeline-item.type-hidden, .timeline-gap.type-hidden { display: none; }

/* Associated media row on the timeline: same skeleton as an article row, distinguished by a kind-tinted
   spine dot and the play/mic glyph + kind chip in the meta line (the shared .media-* styles do the rest).
   Media is context here, so the dot is hollow-ish (kind colour on a surface ring) rather than the solid
   neutral of a report. */
.timeline-dot--media { background: var(--primary); }
.timeline-dot--media[data-kind="podcast"] { background: color-mix(in srgb, var(--primary) 55%, var(--muted-foreground)); }
/* The duration reads as a quiet suffix on the meta line, like the card view's. */
.timeline-item--mediarow .media-duration { color: var(--muted-foreground); }

/* An item with a lead image: the text column on the left + a right media column holding the thumbnail
   (the exact time lives in the left gutter, not here). */
.timeline-item--media .timeline-body { display: flex; gap: 0.9rem; align-items: flex-start; }
.timeline-media-col { flex: 0 0 auto; width: clamp(84px, 18vw, 148px); }
/* The thumbnail: fixed aspect box so rows stay tidy regardless of the source image's shape; hotlinked and
   object-fit cover. A faint accent wash shows while/if the image is absent. */
.timeline-media { display: block; width: 100%; aspect-ratio: 16 / 10; overflow: hidden; border-radius: 0.4rem; background: color-mix(in srgb, var(--primary) 12%, var(--surface)); }
.timeline-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Text column takes the remaining width (min-width:0 so long headlines/leads wrap instead of overflowing). */
.timeline-text-col { flex: 1 1 0; min-width: 0; }

/* Meta line: favicon + source + optional category/delta/region chips. Reuses the shared .label look. */
.timeline-meta { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.timeline-source { font-weight: 700; color: var(--foreground); }
/* The article's own category, in its category colour (from .label.cat-link[data-cat]) and with no frame —
   the same treatment the feed rows' label has. It never wraps mid-word away from the source name it
   qualifies, and it is a real link, so it takes the link reset the rest of this line does not need. */
.timeline-cat { white-space: nowrap; text-decoration: none; }
/* Time-difference chip (ascending order only): a small primary-tinted pill. */
.timeline-delta { padding: 0 0.35rem; border-radius: 0.25rem; font-weight: 600; color: var(--primary); background: color-mix(in srgb, var(--primary) 14%, transparent); white-space: nowrap; }
/* Cross-border region marker next to the source name: muted micro-label, no frame (border convention). */
.timeline-region { padding: 0; color: var(--muted-foreground); white-space: nowrap; }

/* Headline: same scale as the card coverage headline (.source-headline, 0.9rem), no thumbnail. Block, like
   .source-headline, so the (usually disabled) admin-edit pencil follows it exactly as on the card rows. */
.timeline-title { display: block; margin-top: 0.3rem; color: var(--foreground); font-size: 0.9rem; font-weight: 600; line-height: 1.35; text-decoration: none; }
.timeline-title:hover { color: var(--primary); }
/* Lead: 1–2 lines, secondary color, clamped. Rendered only when the source article has a lead. */
.timeline-lead { margin: 0.3rem 0 0; font-size: 0.8rem; line-height: 1.45; color: var(--muted-foreground); display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* F2 coverage-intensity bar: a small column chart above the timeline (article count per time bucket) plus a
   header line summarizing sources · articles · duration. Pure flex columns — no charting library. */
.coverage-intensity { margin-bottom: 1rem; }
/* Header ROW: the summary text on the left and the coverage controls (the sort toggle) on the right — one
   line instead of the separate control row this block used to sit under. margin-left:auto on the toolbar
   (rather than justify-content: space-between) keeps the control right-aligned even when there is no
   summary — the single-article case, where the row carries the control alone. min-height keeps the row from
   collapsing around the small label when the control is absent. */
.coverage-summary-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.35rem; }
.coverage-summary-row > .coverage-toolbar { margin-left: auto; margin-bottom: 0; flex: 0 0 auto; }
.coverage-intensity-head { margin-bottom: 0; }
/* The bar track: columns grow from a shared baseline; each column flexes to share the width, so even a long
   span (many day buckets) fits without horizontal overflow — the columns just get thinner. */
.coverage-intensity-bars { display: flex; align-items: flex-end; gap: 2px; height: 2.5rem; padding-top: 0.15rem; }
.coverage-intensity-bar { flex: 1 1 0; min-width: 0; min-height: 2px; border-radius: 1px 1px 0 0; background: color-mix(in srgb, var(--primary) 55%, transparent); }
/* An empty bucket collapses to a faint baseline sliver so gaps in coverage stay visible. */
.coverage-intensity-bar.empty { background: color-mix(in srgb, var(--border) 70%, transparent); }

/* ---------------------------------------------------------------------------------------------
   Saga dedup: the sibling stories collapsed into a card (see SagaDedup / StoryAttachments.razor).
   Deliberately subordinate — smaller, muted, indented — so the card still reads as ONE story with
   footnotes rather than as a list of equals. Plain links, so they stay crawlable.
   --------------------------------------------------------------------------------------------- */
.story-attached { display: flex; flex-direction: column; gap: 0.15rem; margin-top: 0.4rem; }
.story-attached-link {
    display: block; font-size: 0.78rem; line-height: 1.35; color: var(--muted-foreground);
    text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.story-attached-link:hover { color: var(--foreground); text-decoration: underline; }
.story-attached-label { opacity: 0.75; }
.story-attached-meta { opacity: 0.75; }
/* On the overlay tiles the links sit UNDER the image block (an anchor cannot nest another anchor),
   so they need the card's own padding — the overlay body's padding does not reach them. */
.hot-card--overlay .story-attached { padding: 0 0.75rem 0.6rem; }
.hero-lead--overlay .story-attached { padding: 0 1rem 0.8rem; }

/* ===== Search page (search redesign F2 — design SPEC-kereses.md) =============================
   EVERYTHING here is scoped to `.srch`. The design's borderless direction is an accepted direction
   for THIS page, not a site-wide turn (design §7): the shared `.opt`, `.chip-opt` and `.sort-btn`
   used by the browse surfaces are deliberately left alone, and this page uses its own `.srch-opt`
   instead of extending them.

   Sizes are in rem, never px: the reader's A+ text zoom multiplies the root font size, and a control
   pinned to a pixel height spills its label at the larger steps (design §2 / checklist 13). */

.srch { max-width: 52rem; }

/* ---- Search bar ---- */
.srch-bar { display: flex; gap: 0.5rem; align-items: stretch; }
.srch-field {
    position: relative;
    display: flex; align-items: center; gap: 0.4rem;
    flex: 1 1 auto; min-width: 0;
    height: 3rem; padding: 0 0.75rem;
    /* Filled, borderless — the fill IS the affordance here (design §7). */
    background: var(--muted); border-radius: 0.5rem;
}
.srch-field-icon { flex: 0 0 auto; color: var(--muted-foreground); }
.srch-input {
    flex: 1 1 auto; min-width: 0;
    background: none; border: none; outline: none;
    color: var(--foreground); font-size: 0.95rem; font-family: inherit;
}
.srch-input::placeholder { color: var(--muted-foreground); }
/* The native clear affordance of type=search would duplicate our own link-based one. */
.srch-input::-webkit-search-cancel-button { display: none; }
.srch-clear-query {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.75rem; height: 1.75rem; border-radius: 9999px;
    color: var(--muted-foreground); text-decoration: none; font-size: 1.1rem; line-height: 1;
}
.srch-clear-query:hover { background: var(--surface-hover); color: var(--foreground); }
/* The ONE solid primary surface on the page (design checklist 12). */
.srch-submit {
    flex: 0 0 auto;
    height: 3rem; padding: 0 1.4rem;
    background: var(--primary); border: none; border-radius: 0.5rem;
    color: var(--primary-foreground); font-family: inherit; font-size: 0.9rem; font-weight: 600; cursor: pointer;
    white-space: nowrap;
}
.srch-submit:hover { filter: brightness(1.08); }

.srch-explain {
    display: flex; align-items: flex-start; gap: 0.4rem;
    margin: 0.5rem 0 1.1rem;
    color: var(--muted-foreground); font-size: 0.8rem; line-height: 1.5;
}
.srch-explain svg { flex: 0 0 auto; margin-top: 0.2rem; }
.srch-explain strong { color: var(--foreground); font-weight: 600; }

/* ---- Filter bar ---- */
.srch-filters { border-top: 1px solid var(--border); }
.srch-filters-head {
    display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem 0.75rem;
    padding: 0.7rem 0; cursor: pointer;
    /* The default disclosure triangle is replaced by our own chevron. */
    list-style: none;
}
.srch-filters-head::-webkit-details-marker { display: none; }
.srch-filters-head::marker { content: ""; }
.srch-filters-toggle {
    display: inline-flex; align-items: center; gap: 0.35rem;
    color: var(--foreground); font-size: 0.8rem; font-weight: 600;
}
.srch-chevron { transition: transform 0.15s ease; color: var(--muted-foreground); }
.srch-filters[open] .srch-chevron { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .srch-chevron { transition: none; } }

.srch-chips { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; flex: 1 1 auto; }
/* Chips are labels, not surfaces: no fill, no frame (design §7 + the border convention above). */
.srch-chip {
    display: inline-flex; align-items: center; gap: 0.3rem;
    font-family: "JetBrains Mono", ui-monospace, monospace;
    font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--muted-foreground);
}
.srch-chip-key { font-weight: 700; }
.srch-chip-val { color: var(--foreground); }
.srch-chip-x { color: var(--muted-foreground); text-decoration: none; font-size: 0.85rem; line-height: 1; }
.srch-chip-x:hover { color: var(--foreground); }
.srch-clear-filters { margin-left: auto; text-decoration: none; }
.srch-clear-filters:hover { color: var(--foreground); }
.srch-clear-filters.is-disabled { opacity: 0.45; }

.srch-panel {
    display: flex; flex-direction: column; gap: 1.1rem;
    padding: 0.3rem 0 1rem;
}
.srch-group { border: none; margin: 0; padding: 0; }
.srch-group > legend { display: inline; padding: 0; margin-right: 0.5rem; }
.srch-group-note { color: var(--muted-foreground); font-size: 0.72rem; }
.srch-group-note a { color: var(--primary); }
.srch-opts { display: flex; flex-wrap: wrap; gap: 0.15rem 0.35rem; margin-top: 0.45rem; }

/* Borderless option control: transparent, no frame, no fill. The tick, the text colour and the
   weight carry the selected state (design §7). The 2.75rem minimum keeps every touch target at or
   above 44px at the default root size, at every width (design §6). */
.srch-opt {
    display: inline-flex; align-items: center; gap: 0.35rem;
    min-height: 2.75rem; padding: 0 0.4rem;
    border: none; background: none; border-radius: 0.25rem;
    color: var(--muted-foreground); font-size: 0.82rem; cursor: pointer;
}
.srch-opt:hover { background: var(--surface-hover); }
.srch-opt input { accent-color: var(--primary); }
.srch-opt:has(input:checked) { color: var(--foreground); font-weight: 600; }
.srch-opt.is-disabled { opacity: 0.45; cursor: default; }
.srch-opt.is-disabled:hover { background: none; }
/* Period and sort: the active one also gets the 2px primary underline (design §7). */
.srch-opt-underline:has(input:checked) { box-shadow: inset 0 -2px 0 0 var(--primary); }
/* Category options are NOT tinted per category (operator, 2026-08-13). The panel shows the whole
   taxonomy at once — fourteen accent colours in one block is the loudest thing on the page, and nowhere
   else on the site does the full palette appear together. They use the same neutral treatment as every
   other option here: primary tick, --foreground text when checked. */

.srch-daterow { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.5rem; }
/* Date inputs keep their box: for a form control the box IS the affordance (border convention). */
.srch-daterow input[type="date"] {
    height: 2.25rem; padding: 0 0.5rem;
    background: var(--muted); border: 1px solid var(--border); border-radius: 0.375rem;
    color: var(--foreground); font-family: inherit; font-size: 0.8rem;
}

/* The always-visible submit. Quiet on purpose — the page may carry only one solid primary surface. */
.srch-apply-row { display: flex; padding: 0 0 1.1rem; border-bottom: 1px solid var(--border); }
.srch-apply {
    min-height: 2.75rem; padding: 0 1rem;
    background: none; border: 1px solid var(--border); border-radius: 0.375rem;
    color: var(--foreground); font-family: inherit; font-size: 0.82rem; cursor: pointer;
}
.srch-apply:hover { background: var(--surface-hover); border-color: var(--primary); }

/* ---- Result header ---- */
.srch-results { margin-top: 1.2rem; }
/* Three stacked rows: the count, then the sort, then the type filter — each starting at the left edge
   (operator, 2026-08-13). The design §4.1 puts all three on ONE line with the type filter right-aligned,
   which this column does not have room for anyway: measured 1054px of content against 832px available, so
   the header wrapped regardless. Stacking it deliberately is what makes the wrap read as a layout instead
   of an accident. */
.srch-resulthead {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0.35rem;
    padding-bottom: 0.6rem; border-bottom: 1px solid var(--border);
}
.srch-count { flex: 0 0 auto; }
.srch-count-term { color: var(--foreground); }
/* The two control groups keep real <fieldset>/<legend> semantics (design §11), which constrains how they
   can be laid out: a fieldset's FIRST legend is not a normal child — the browser renders it in the
   fieldset's own top slot and never as a flex item, so making the fieldset a flex container pushed the
   label onto its own line and broke the header into three rows.

   The fix is to leave the fieldset a block box and float the legend (a float DOES apply inside a block
   container, and a floated legend loses its special placement in every current engine), with the controls
   in their own flex row beside it. */
.srch-sort, .srch-typefilter {
    display: block;
    border: none; margin: 0; padding: 0;
    min-width: 0;
}
.srch-sort > legend, .srch-typefilter > legend {
    float: left;
    padding: 0; margin-right: 0.4rem;
    /* Matches the control row's height so the label sits on the options' centre line, not on their top. */
    line-height: 2.75rem;
}
.srch-optrow { display: flex; align-items: center; flex-wrap: wrap; gap: 0.2rem; }
.srch-typecount { color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.srch-sort .srch-opt, .srch-typefilter .srch-opt { font-size: 0.78rem; }

/* ---- Result list ---- */
.srch-list { list-style: none; margin: 0; padding: 0; }
.srch-hit { padding: 1rem 0; border-bottom: 1px solid var(--border); }
.srch-hit-meta {
    display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem;
    font-family: "JetBrains Mono", ui-monospace, monospace;
    font-size: 0.62rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--muted-foreground);
}
/* Separator dot, same shape as the story rows'. */
.srch-hit-meta .dot { width: 0.25rem; height: 0.25rem; border-radius: 9999px; background: var(--border); flex: 0 0 auto; }
/* The category and the source are links (operator, 2026-08-14). They stay meta-line typography — inheriting
   the muted colour, no underline at rest — so the row still reads as one quiet line; the affordance shows
   on hover and on keyboard focus. */
.srch-hit-meta a { color: inherit; text-decoration: none; }
.srch-hit-meta a:hover { color: var(--foreground); text-decoration: underline; }
/* The category in the meta line is neutral too (operator, 2026-08-13). This DOES deviate from design
   §4.2 / checklist 3, which asks for the per-category accent "as everywhere else on the site" — restoring
   it is one declaration: color: var(--cat-accent, var(--muted-foreground)). The browse surfaces are
   untouched, so a coloured category chip still means the same thing there. */
.srch-hit-cat { color: var(--muted-foreground); }
.srch-hit-title { margin: 0.4rem 0 0; font-size: 1rem; line-height: 1.35; font-weight: 700; }
.srch-hit-title a { color: var(--foreground); text-decoration: none; }
.srch-hit-title a:hover { color: var(--primary); }
.srch-hit-lead {
    margin: 0.35rem 0 0;
    color: var(--muted-foreground); font-size: 0.85rem; line-height: 1.5;
    /* Three lines, as the design specifies. */
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
/* The match marker. A <mark> element (not just a background) so it survives forced-colors and is
   announced by assistive tech (design §11). */
.srch-hit mark {
    background: var(--highlight); color: var(--highlight-foreground);
    border-radius: 0.15rem; padding: 0 0.1em;
}

/* ---- Related events ---- */
.srch-related { margin-top: 0.5rem; }
.srch-related > summary {
    display: inline-flex; align-items: center; gap: 0.3rem;
    min-height: 2.75rem;
    color: var(--primary); font-size: 0.75rem; cursor: pointer; list-style: none;
}
.srch-related > summary::-webkit-details-marker { display: none; }
.srch-related > summary::marker { content: ""; }
.srch-related-arrow { transition: transform 0.15s ease; }
.srch-related[open] .srch-related-arrow { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .srch-related-arrow { transition: none; } }
.srch-related-list {
    list-style: none; margin: 0.1rem 0 0.4rem; padding: 0 0 0 0.8rem;
    border-left: 2px solid var(--border);
}
.srch-related-list li { padding: 0.35rem 0; }
.srch-related-list a { display: block; text-decoration: none; }
.srch-related-meta { display: block; }
.srch-related-title { display: block; margin-top: 0.15rem; color: var(--foreground); font-size: 0.85rem; font-weight: 600; }
.srch-related-list a:hover .srch-related-title { color: var(--primary); }

/* ---- States ---- */
.srch-state { padding: 1.5rem 0; }
.srch-none { margin: 0 0 0.5rem; color: var(--foreground); font-size: 0.95rem; }
.srch-none-tip { margin: 0 0 0.8rem; color: var(--muted-foreground); font-size: 0.85rem; line-height: 1.5; max-width: 46rem; }
.srch-retry { margin-left: 0.5rem; }
.srch-more {
    display: inline-block; margin-top: 1rem;
    color: var(--primary); font-size: 0.85rem; text-decoration: none;
}
.srch-more:hover { text-decoration: underline; }

/* ---- Focus ---- */
/* On borderless controls the ring is the ONLY focus signal, so it is not optional here (design §7). */
.srch a:focus-visible,
.srch button:focus-visible,
.srch summary:focus-visible,
.srch input:focus-visible:not(.srch-input),
.srch .srch-opt:has(input:focus-visible) {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
    border-radius: 0.25rem;
}
/* The query field is a COMPOSITE control: a filled box holding the magnifier, the text input and the
   clear "×". Ringing the <input> alone drew the outline inside that box, around the text area only, which
   read as a stray frame rather than as focus. The ring therefore goes on the wrapper — the thing the
   reader perceives as "the search box" — and follows its 0.5rem corners. The inner input keeps
   outline:none (it already has it) so the two cannot both paint. */
.srch-field:has(.srch-input:focus-visible) {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
}

/* ---- Type badges ----
   Two new data-kind values on the EXISTING .media-kind-chip (design §4.2) — no new badge component and
   no new colour. video/podcast keep the category tint they carry everywhere else, so only the two new
   values are declared here. */
.media-kind-chip[data-kind="event"] {
    background: color-mix(in srgb, var(--primary) 16%, transparent);
    color: var(--primary);
}
.media-kind-chip[data-kind="article"] {
    background: var(--muted);
    color: var(--muted-foreground);
}

/* ---- Responsive (design §6) ----
   Every change lands in the 768 layer; the 640 layer is deliberately untouched. */
@media (max-width: 768px) {
    /* The query row stacks: full-width field, then a full-width button under it. */
    .srch-bar { flex-wrap: wrap; }
    .srch-field { flex: 1 1 100%; }
    .srch-submit { flex: 1 1 100%; }
    /* Chips move to their own full-width row under the toggle; "clear filters" stays on the toggle row. */
    .srch-chips { order: 3; flex: 1 1 100%; }
    .srch-clear-filters { margin-left: auto; order: 2; }
    /* Date range: four flex items (label, from, dash, to) that wrap wherever they happen to fit read as
       misaligned on a narrow screen — the label ends up next to one picker and the dash orphaned. Give the
       label its own line and let the two pickers share the next one, each taking half the width. The
       native control has an intrinsic minimum, so min-width:0 is what actually lets them shrink to fit. */
    .srch-daterow { row-gap: 0.35rem; }
    .srch-daterow > .srch-group-note { flex: 1 0 100%; }
    .srch-daterow input[type="date"] { flex: 1 1 0; min-width: 0; }
}

/* ---- Loading skeleton (search redesign F3) ----
   Only ever built by site.js, so it costs nothing on the no-script path. Under prefers-reduced-motion
   the bars stay flat (design §11): a reader who asked for less motion gets a static placeholder, not a
   slower pulse. */
.srch-skeleton { list-style: none; }
.srch-skeleton-row { padding: 1rem 0; border-bottom: 1px solid var(--border); }
.srch-skeleton-bar {
    display: block; border-radius: 0.25rem; background: var(--muted);
    animation: srch-pulse 1.4s ease-in-out infinite;
}
.srch-skeleton-meta { width: 40%; height: 0.6rem; margin-bottom: 0.55rem; }
.srch-skeleton-title { width: 85%; height: 1rem; margin-bottom: 0.45rem; }
.srch-skeleton-lead { width: 65%; height: 0.8rem; }
@keyframes srch-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) {
    .srch-skeleton-bar { animation: none; }
}
