/* ==========================================================================
   PAIKALLISLEHTI — THE COLOUR VOCABULARY
   --------------------------------------------------------------------------
   Every colour on the site is a ROLE, and a role is named for the job it does
   rather than for the hue it happens to be. `--prose-link` says what it is for;
   `--color-link-light: #C1121F` said what it was, which is how it came to be the
   brand red, the visited red and the link red at once without anybody deciding
   that.

   Four rules hold the whole thing together:

     A HEADLINE IS INK.  A headline that happens to link is still a headline, so
     it takes `--ink-strong` at rest. The story page's <h1> rendered as a blue
     underlined link, and a reader cannot tell that from a hyperlink dropped in a
     headline slot.

     A READ HEADLINE IS QUIETER, NOT LOUDER.  `--ink-read` is dimmed ink, not a
     hue jump. On a card grid "have I read this" is the most useful thing the
     page can say, and saying it in the brand colour would make the stories the
     reader is finished with the loudest objects on the page.

     A PROSE LINK IS BLUE AND ITS VISITED STATE IS VIOLET.  Two different hues,
     because the previous pair — #C1121F and #7F1D1D — differed only in lightness
     and were invisible as a pair in a paragraph. Blue and violet also happen to
     be what every reader already knows a link looks like.

     THE BRAND MEANS THE BRAND.  The red is the nameplate, the topic rail and one
     accent rail. It is also what everything reaches for on hover, which is a
     transient state and so cannot be confused with a permanent one. It is never
     "visited".

   ONE DEFINITION SITE PER THEME. The previous sheet held a `--x-light` and a
   `--x-dark` for every token and then a third line aliasing one of them, so a
   token was three lines in three places and a typo in any of them was silent:
   `--bs-link-hover-color: var(--color-link-hover-dark)` named a token that never
   existed, CSS threw the whole declaration away rather than complain, and
   Bootstrap's default blue came back through the hole. Light values live on
   :root, dark values live in the dark block, and nowhere else.

   THE RATIOS ARE MEASURED, NOT ESTIMATED. Every number below is WCAG 2.1
   contrast against the worst surface the token can land on — in light mode the
   page, which is darker than a card and so the harder ground for dark ink; in
   dark mode `--surface-quiet`, which is the lightest thing light ink sits on.
   tests/colourSystem.test.js recomputes all of them, because the sheet this one
   replaced carried a comment reading "14:1 contrast ratio" beside a token that
   had been re-picked twice since.
   ========================================================================== */

:root {
    /* Archivo carries the mark and the small labelling voice — kickers, bylines, tabs: its
       width axis runs 75-125, so one face narrows for a label and widens for the nameplate.
       Newsreader sets everything meant to be read rather than scanned, which since sketch E
       includes the story headlines (see THE STORY). Montserrat and Raleway were geometric
       general-purpose faces and read as "app" rather than "paper". */
    /* The masthead panel. Light in both themes, dark type on it in both, so the
       letters never land on the red block — which is where an outlined or
       transparent panel measured 2.00:1 and failed every contrast threshold.
       In light mode the panel is close to the page colour, so it is given a
       hard offset shadow: a second impression rather than a soft UI shadow. */
    --color-masthead-panel: #FFFFFF;
    --color-masthead-panel-ink: #14161A;
    --color-masthead-panel-shadow: .035em .035em 0 0 #14161A;

    --font-headline: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
    --font-body: 'Newsreader', Georgia, 'Times New Roman', serif;

    /* --- SURFACES (light) — every ratio below is measured against one of these --- */
    --page: #F1F5F9;             /* the paper. Darker than a card, so it is the hard ground */
    --surface: #FFFFFF;          /* a card */
    --surface-soft: #F8FAFC;     /* a promoted block: the digest, a called-out box */
    --surface-quiet: #E2E8F0;    /* table heads, edit blocks, the footer */
    --edge: #CBD5E1;

    /* --- INK (light) --- */
    --ink: #0F172A;              /* 16.30:1 on --page — running text */
    --ink-strong: #0B1220;       /* 17.09:1 on --page — headlines, at rest, always */
    --ink-quiet: #475569;        /* 6.92:1 on --page — datelines, captions, counts */
    --ink-read: #5B6478;         /* 5.42:1 on --page — a headline whose story has been read */

    /* --- THE BRAND (light) ---
       Two tokens for one red, because a colour that works as a block behind 5rem
       type does not work as 16px type on the page. --brand-block is the nameplate;
       --brand-ink is the brand used as type. */
    --brand-block: #C1121F;      /* --on-brand on it: 6.22:1 */
    --brand-ink: #C1121F;        /* 5.68:1 on --page */
    --brand-ink-hover: #9B0E19;  /* 7.76:1 on --page */
    --on-brand: #FFFFFF;         /* 6.22:1 on --brand-block */

    /* --- PROSE LINKS (light) --- */
    --prose-link: #1A4FA0;         /* 7.18:1 on --page; ΔE 45 from --ink */
    --prose-link-visited: #6D28D9; /* 6.49:1 on --page; ΔE 59 from --prose-link */
    --prose-link-hover: #9B0E19;   /* 7.76:1 on --page — hover is the brand, everywhere */

    /* --- CHIPS: the topic rail --- */
    --chip-bg: #E2E8F0;
    --chip-ink: #334155;         /* 8.40:1 on --chip-bg */
    --chip-edge: #CBD5E1;
    --brand-chip-bg: #C1121F;    /* the selected topic. One rail, one loud state */
    --brand-chip-ink: #FFFFFF;   /* 6.22:1 on --brand-chip-bg */

    /* --- THE BAR: dark in both themes, so it carries its own ink --- */
    --bar-bg: #0F172A;
    --bar-ink: #F8FAFC;          /* 17.06:1 on --bar-bg */
    --bar-ink-strong: #FFFFFF;   /* 18.87:1 on --bar-bg */
    --bar-edge: rgba(255, 255, 255, 0.08);
    /* The open mobile menu sits under the bar and needs to read as a separate
       block, so its top rule is firmer than the bar's own hairline. */
    --bar-edge-strong: rgba(255, 255, 255, 0.16);

    /* --- THE FOOTER --- */
    --foot-bg: #E2E8F0;
    --foot-ink: #475569;         /* 6.15:1 on --foot-bg */
    --foot-ink-strong: #0F172A;  /* 14.48:1 on --foot-bg */

    /* --- A FULL-WIDTH HEADER BLOCK (the nearby page) ---
       Was a #0d6efd→#00baf1 gradient with white type, and white on #00baf1 is
       2.26:1: the right-hand half of that banner was unreadable. */
    --header-from: #0F172A;
    --header-to: #1A4FA0;
    --on-header: #FFFFFF;        /* 7.87:1 at the pale end of the gradient */
    --on-header-quiet: #C3D0E6;  /* 5.05:1 at the pale end of the gradient */

    /* --- STATUS (light) ---
       Opaque grounds rather than a tint over whatever is behind. A translucent
       badge's real contrast depends on its backdrop, so it passed on a card and
       failed on the page; frozen, the ratio is a property of the badge. */
    --ok-bg: #DBF5EC;   --ok-ink: #065F46;   --ok-edge: #9FE3CD;   /* 6.69:1 */
    --info-bg: #DEE8FC; --info-ink: #1D4ED8; --info-edge: #A8C1F7; /* 5.44:1 */
    --warn-bg: #F9EBDA; --warn-ink: #92400E; --warn-edge: #F0C99B; /* 6.05:1 */
    --bad-bg: #FADEDE;  --bad-ink: #B91C1C;  --bad-edge: #F1A8A8;  /* 5.10:1 */
    --note-bg: #DBF2FC; --note-ink: #0369A1; --note-edge: #9FDBF6; /* 5.12:1 */

    /* --- DEPTH & TRANSIENT WASHES --- */
    --shadow-card: 0 1px 3px 0 rgba(15, 23, 42, 0.07), 0 1px 2px -1px rgba(15, 23, 42, 0.07);
    --shadow-lift: 0 4px 12px rgba(15, 23, 42, 0.10);
    --focus-ring: 0 0 0 3px rgba(193, 18, 31, 0.18);
    --dot-ring: 0 0 0 3px rgba(193, 18, 31, 0.25);
    --row-hover: rgba(15, 23, 42, 0.05);

    /* --- FIELDS --- */
    --field-bg: #FFFFFF;
    --field-ink: #0F172A;         /* 17.85:1 on --field-bg */
    --field-placeholder: #64748B; /* 4.76:1 on --field-bg — a placeholder is text too */

    /* --- BOOTSTRAP'S OWN VARIABLES ---
       Bound in BOTH themes. Binding them only in the dark block left light mode
       on Bootstrap's #0d6efd, which is where the front page's blue [Lähde] links
       and its blue "Kaikki" chip came from. */
    --bs-body-color: var(--ink);
    --bs-body-bg: var(--page);
    --bs-card-bg: var(--surface);
    --bs-card-border-color: var(--edge);
    --bs-card-cap-bg: var(--surface);
    --bs-secondary-bg: var(--surface-quiet);
    --bs-tertiary-bg: var(--surface-soft);
    --bs-emphasis-color: var(--ink-strong);
    --bs-secondary-color: var(--ink-quiet);
    --bs-border-color: var(--edge);
    --bs-border-color-translucent: rgba(15, 23, 42, 0.14);
    --bs-link-color: var(--prose-link);
    --bs-link-hover-color: var(--prose-link-hover);
    --bs-heading-color: var(--ink-strong);

    /* --- THE OLD NAMES, kept as aliases ---
       The nameplate and the bar's mark are finished work and are not touched by
       this pass, so the three tokens their rules name still resolve — to the same
       values they always did. Nothing else in the sheet uses them. */
    --color-primary: var(--brand-block);
    --color-muted: var(--ink-quiet);
    --color-navbar-text: var(--bar-ink);
}

/* ==========================================================================
   THE DARK THEME
   --------------------------------------------------------------------------
   The same roles, re-pointed. Not a filter over the light values: ink and ground
   swap places, and the worst surface swaps with them — in light mode a dark ink
   has least contrast on the page, in dark mode a light ink has least contrast on
   --surface-quiet, which is the lightest thing it lands on. Every ratio below is
   measured against that.
   ========================================================================== */
[data-theme="dark"],
[data-bs-theme="dark"] {
    /* A light panel on a dark ground already has an edge, so no shadow here.
       Adding one would be decoration, and would quietly make the two themes
       two different marks. */
    --color-masthead-panel: #EDEBE5;
    --color-masthead-panel-ink: #0F1114;
    --color-masthead-panel-shadow: none;

    --page: #0B0F19;
    --surface: #151D2E;
    --surface-soft: #1A2333;
    --surface-quiet: #1E293B;
    --edge: #2A374D;

    --ink: #F1F5F9;              /* 13.35:1 on --surface-quiet */
    --ink-strong: #FFFFFF;       /* 14.63:1 on --surface-quiet */
    --ink-quiet: #94A3B8;        /* 5.71:1 on --surface-quiet */
    --ink-read: #8B98AC;         /* 5.00:1 on --surface-quiet */

    /* The nameplate's red stays exactly what it was. As TYPE it does not reach
       AA on a dark card (4.47:1), which is why --brand-ink is a step lighter:
       the block and the type are two different jobs and now two different tokens. */
    --brand-block: #EF4444;      /* --on-brand on it: 3.76:1 — the nameplate is 5rem, so large-text AA */
    --brand-ink: #F87171;        /* 5.29:1 on --surface-quiet */
    --brand-ink-hover: #FCA5A5;  /* 7.71:1 on --surface-quiet */
    --on-brand: #FFFFFF;         /* 3.76:1 on --brand-block, large text only */

    --prose-link: #8AB4F8;         /* 6.94:1 on --surface-quiet; ΔE 43 from --ink */
    --prose-link-visited: #D19BF5; /* 6.77:1 on --surface-quiet; ΔE 33 from --prose-link */
    --prose-link-hover: #FCA5A5;   /* 7.71:1 on --surface-quiet */

    --chip-bg: #243044;
    --chip-ink: #CBD5E1;         /* 8.94:1 on --chip-bg */
    --chip-edge: #3B4A63;
    /* Inverted here: a #C1121F block on a dark card is 2.71:1 against its own
       surround and barely visible, and white on #EF4444 is 3.76:1 and fails as
       chip-sized type. A light fill with dark type solves both at once. */
    --brand-chip-bg: #F87171;
    --brand-chip-ink: #0B0F19;   /* 6.92:1 on --brand-chip-bg */

    --bar-bg: #080C14;
    --bar-ink: #F1F5F9;          /* 17.86:1 on --bar-bg */
    --bar-ink-strong: #FFFFFF;   /* 19.15:1 on --bar-bg */
    --bar-edge: rgba(255, 255, 255, 0.08);

    --foot-bg: #080C14;
    --foot-ink: #94A3B8;         /* 7.63:1 on --foot-bg */
    --foot-ink-strong: #F1F5F9;  /* 17.86:1 on --foot-bg */

    --header-from: #0B0F19;
    --header-to: #16305F;
    --on-header: #FFFFFF;        /* 12.95:1 at the pale end of the gradient */
    --on-header-quiet: #C3D0E6;  /* 8.31:1 at the pale end of the gradient */

    --ok-bg: #143C3F;   --ok-ink: #34D399;   --ok-edge: #135B4F;   /* 6.25:1 */
    --info-bg: #182B54; --info-ink: #60A5FA; --info-edge: #1B397A; /* 5.46:1 */
    --warn-bg: #3C2F26; --warn-ink: #FBBF24; --warn-edge: #63411E; /* 7.73:1 */
    --bad-bg: #3D1F2C;  --bad-ink: #F87171;  --bad-edge: #65212B;  /* 5.31:1 */
    --note-bg: #143853; --note-ink: #38BDF8; --note-edge: #125379; /* 5.70:1 */

    --shadow-card: 0 1px 3px 0 rgba(0, 0, 0, 0.45), 0 1px 2px -1px rgba(0, 0, 0, 0.45);
    --shadow-lift: 0 4px 14px rgba(0, 0, 0, 0.55);
    --focus-ring: 0 0 0 3px rgba(248, 113, 113, 0.24);
    --dot-ring: 0 0 0 3px rgba(248, 113, 113, 0.3);
    --row-hover: rgba(255, 255, 255, 0.055);

    --field-bg: #0D1424;
    --field-ink: #F8FAFC;         /* 17.57:1 on --field-bg */
    --field-placeholder: #94A3B8; /* 7.17:1 on --field-bg */

    --bs-body-color: var(--ink);
    --bs-body-bg: var(--page);
    --bs-card-bg: var(--surface);
    --bs-card-border-color: var(--edge);
    --bs-card-cap-bg: var(--surface);
    --bs-secondary-bg: var(--surface-quiet);
    --bs-tertiary-bg: var(--surface-soft);
    --bs-emphasis-color: var(--ink-strong);
    --bs-secondary-color: var(--ink-quiet);
    --bs-border-color: var(--edge);
    --bs-border-color-translucent: rgba(255, 255, 255, 0.12);
    --bs-link-color: var(--prose-link);
    --bs-link-hover-color: var(--prose-link-hover);
    --bs-heading-color: var(--ink-strong);

    --color-primary: var(--brand-block);
    --color-muted: var(--ink-quiet);
    --color-navbar-text: var(--bar-ink);
}

/* ==========================================================================
   LINKS
   --------------------------------------------------------------------------
   The element, not a class. The old sheet had exactly one rule consuming its
   link tokens, so almost every anchor on the site fell through to a Bootstrap
   default and the front page ended up carrying four unrelated link treatments at
   once. Styling `a` itself is what makes "[Lähde]", the source line, the
   search-scope link and a link inside an article agree without any of them being
   told to.

   Underlined at rest, because colour alone is not a signal every reader receives
   (WCAG 1.4.1), and because these are links in running text — the one place an
   underline is doing work rather than adding noise.

   :visited can only set colour, background-color, border-color, outline-color
   and column-rule-color, and only where the unvisited rule already sets that
   property. Everything else it is asked to do is silently dropped, so the rest
   state below sets `color` precisely so the visited state has something to
   override.
   ========================================================================== */

a {
    color: var(--prose-link);
    text-decoration-line: underline;
    text-decoration-thickness: 0.06em;
    text-underline-offset: 0.16em;
}

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

a:hover,
a:focus-visible {
    color: var(--prose-link-hover);
    text-decoration-thickness: 0.12em;
}

/* ==========================================================================
   HEADLINES
   --------------------------------------------------------------------------
   A headline that happens to link is still a headline. It is ink at rest in both
   states, and the difference between them is how much ink: --ink-strong for a
   story not yet read, --ink-read for one that has been. ΔE between the two is 34
   in light and 35 in dark, which is unmistakable across a card grid, while the
   read state stays above 4.5:1 so it is dimmer without being faint.

   The brand appears on hover and only on hover. A permanent brand-coloured
   headline would make "read" the loudest thing on the page, which is backwards.
   ========================================================================== */

.headline-link {
    color: var(--ink-strong);
    text-decoration-line: none;
}

.headline-link:visited {
    color: var(--ink-read);
}

/* (0,2,0) beats `a:hover` at (0,1,1), which would otherwise repaint a hovered
   headline with the prose-link hover colour. */
.headline-link:hover,
.headline-link:focus-visible {
    color: var(--brand-ink);
    text-decoration-line: underline;
    text-decoration-thickness: 0.06em;
    text-underline-offset: 0.16em;
}

/* The story page's own <h1>. It links to the source, which is why it was picking
   up link styling at all and rendering blue and underlined at the top of every
   article. It is a headline; it is set as one. */
.story-headline-link {
    color: var(--ink-strong);
    text-decoration-line: none;
}

.story-headline-link:visited {
    color: var(--ink-read);
}

.story-headline-link:hover,
.story-headline-link:focus-visible {
    color: var(--brand-ink);
}


/* ==========================================================================
   BASE & TYPOGRAPHY
   ========================================================================== */
body {
    font-family: var(--font-body);
    background-color: var(--page);
    color: var(--ink);
    line-height: 1.6;
    transition: background-color 0.2s ease, color 0.2s ease;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-headline);
    font-weight: 700;
    color: var(--ink-strong);
    letter-spacing: -0.25px;
}

h1 { font-weight: 900; }

/* ==========================================================================
   BREAKING LONG FINNISH WORDS
   --------------------------------------------------------------------------
   Finnish breaks a narrow column with single long words, not long sentences.
   "ympäristöterveydenhuollon" is twenty-five characters and a 176px card
   column cannot hold it: without a break it simply hangs out of the card.

   Two mechanisms, and they cooperate rather than compete. The browser's own
   hyphenation works from the Finnish patterns it ships with and breaks by
   syllable; the soft hyphens we insert server-side mark where the compound is
   actually JOINED, which is the break a reader expects. `hyphens: auto`
   honours both, preferring nothing in particular — it simply has more legal
   break points to choose from, and the compound seams are the good ones.

   Only on headings and card text. Running body copy is wide enough not to
   need it, and hyphenating a whole article reads as a printed page rather
   than a screen.

   overflow-wrap is the floor beneath both: a word with no legal break at all
   — a URL, a registration code — breaks anywhere rather than overflowing.
   ========================================================================== */

h1, h2, h3, h4, h5, h6,
.card-text,
.story-ingress {
    hyphens: auto;
    overflow-wrap: break-word;
}
/* ==========================================================================
   THE BOOTSTRAP COLOUR UTILITIES
   --------------------------------------------------------------------------
   The views reach for .text-muted, .text-primary, .text-bg-secondary and the
   rest because that is what Bootstrap offers, and every one of them is a colour
   picked by Bootstrap rather than a role picked here. .text-primary was #0d6efd
   — a blue that appears nowhere else on this site — and .text-dark was #212529
   in light mode and something else again under the dark override, so one class
   meant two colours.

   Redirecting them here rather than rewriting every view is deliberate: an admin
   template that says .text-bg-warning is saying "this is a warning", which is a
   role. It just needs the role to resolve to our colour.
   ========================================================================== */

.text-muted,
.text-secondary {
    color: var(--ink-quiet) !important;
}

.text-body,
.text-dark {
    color: var(--ink) !important;
}

.text-emphasis,
.text-body-emphasis {
    color: var(--ink-strong) !important;
}

.text-primary {
    color: var(--brand-ink) !important;
}

.text-success { color: var(--ok-ink) !important; }
.text-danger  { color: var(--bad-ink) !important; }
.text-warning { color: var(--warn-ink) !important; }
.text-info    { color: var(--note-ink) !important; }

.bg-light {
    background-color: var(--surface-quiet) !important;
    color: var(--ink) !important;
}

/* Bootstrap's border utilities are a seventh palette again, and .border-light is
   a near-white hairline: invisible on a light footer and a bright scar on a dark
   one. A rule of the same specificity, later in the cascade, is all it takes. */
.border-light,
.border-secondary { border-color: var(--edge) !important; }
.border-primary   { border-color: var(--brand-ink) !important; }
.border-success   { border-color: var(--ok-edge) !important; }
.border-danger    { border-color: var(--bad-edge) !important; }
.border-warning   { border-color: var(--warn-edge) !important; }
.border-info      { border-color: var(--note-edge) !important; }

/* A filled progress bar is a block of colour with no type on it, so it takes the
   status ink rather than the paler status ground. */
.progress-bar.bg-success { background-color: var(--ok-ink) !important; }
.progress-bar.bg-danger  { background-color: var(--bad-ink) !important; }
.progress-bar.bg-warning { background-color: var(--warn-ink) !important; }
.progress-bar.bg-info    { background-color: var(--note-ink) !important; }

.bg-white {
    background-color: var(--surface) !important;
    color: var(--ink) !important;
}

/* ==========================================================================
   NAVBAR
   --------------------------------------------------------------------------
   The bar is dark in both themes, so it carries its own ink pair rather than the
   page's. Its buttons need saying too: Bootstrap's .btn-outline-secondary is a
   mid grey drawn for a white page, and on #0F172A it is a smudge.
   ========================================================================== */
.navbar {
    background-color: var(--bar-bg) !important;
    border-bottom: 1px solid var(--bar-edge);
}

.navbar .navbar-brand {
    font-family: var(--font-headline);
    font-weight: 900;
    font-size: 1.35rem;
    color: var(--bar-ink-strong) !important;
}

.navbar .nav-link,
.navbar .dropdown-toggle,
.navbar .navbar-text {
    color: var(--bar-ink) !important;
    font-weight: 500;
}

.navbar .nav-link:hover,
.navbar .nav-link:focus-visible,
.navbar .dropdown-toggle:hover,
.navbar .nav-link.active {
    color: var(--bar-ink-strong) !important;
}

.navbar .btn-outline-light,
.navbar .btn-outline-secondary,
.navbar .btn-outline-info {
    color: var(--bar-ink);
    border-color: var(--bar-ink);
    background-color: transparent;
}

.navbar .btn-outline-light:hover,
.navbar .btn-outline-secondary:hover,
.navbar .btn-outline-info:hover {
    color: var(--bar-bg);
    background-color: var(--bar-ink-strong);
    border-color: var(--bar-ink-strong);
}

.navbar .form-control {
    background-color: var(--bar-bg);
    border-color: var(--bar-ink);
    color: var(--bar-ink-strong);
}

.navbar .form-control::placeholder {
    color: var(--bar-ink);
    opacity: 0.75;
}

/* ==========================================================================
   CARDS & SURFACES
   ========================================================================== */
.card,
.admin-card {
    background-color: var(--surface) !important;
    border: 1px solid var(--edge) !important;
    color: var(--ink);
    border-radius: 0.5rem;
    box-shadow: var(--shadow-card);
    transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.2s ease, border-color 0.2s ease;
}

.card-header,
.card-footer {
    background-color: var(--surface) !important;
    border-color: var(--edge) !important;
    color: var(--ink-quiet);
}

.card-header {
    color: var(--ink-strong);
    font-weight: 600;
}

.offcanvas {
    background-color: var(--surface);
    color: var(--ink);
}

/* ==========================================================================
   MASTER TABS & SUB-TABS
   ========================================================================== */
.nav-tabs {
    border-bottom: 1px solid var(--edge);
    background-color: var(--surface);
}

.nav-tabs .nav-link {
    color: var(--ink-quiet) !important;
    font-weight: 600;
    border: none;
    border-bottom: 3px solid transparent;
    padding: 0.85rem 1.25rem;
    text-decoration: none;
    transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.nav-tabs .nav-link:hover {
    color: var(--ink-strong) !important;
    background-color: var(--row-hover);
    border-color: transparent;
}

.nav-tabs .nav-link.active {
    color: var(--brand-ink) !important;
    background-color: transparent !important;
    border-bottom: 3px solid var(--brand-ink) !important;
}

.nav-pills .nav-link {
    color: var(--ink-quiet);
    font-weight: 600;
    border-radius: 0.375rem;
    text-decoration: none;
}

.nav-pills .nav-link.active {
    background-color: var(--brand-chip-bg);
    color: var(--brand-chip-ink) !important;
}

/* ==========================================================================
   TABLES & DATA LISTS
   ========================================================================== */
.table {
    color: var(--ink);
    border-color: var(--edge);
}

.table th,
.table thead th,
.table-light th {
    background-color: var(--surface-quiet) !important;
    color: var(--ink-strong) !important;
    font-weight: 700;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    border-bottom: 2px solid var(--edge) !important;
}

.table td {
    color: var(--ink);
    border-color: var(--edge);
    vertical-align: middle;
}

.table-hover > tbody > tr:hover > * {
    background-color: var(--row-hover) !important;
    color: var(--ink);
}

/* A table of stories is a list of headlines, so its links follow the headline
   rule and not the prose rule: ink at rest, dimmed once read. An admin scanning
   a hundred rows for the one they have already dealt with wants exactly that. */
.table a {
    color: var(--ink-strong);
    font-weight: 500;
    text-decoration: none;
}

.table a:visited {
    color: var(--ink-read);
}

.table a:hover,
.table a:focus-visible {
    color: var(--brand-ink);
    text-decoration: underline;
}

.list-group-item {
    background-color: var(--surface);
    border-color: var(--edge);
    color: var(--ink);
}

/* ==========================================================================
   BADGES & STATUS CHIPS
   --------------------------------------------------------------------------
   Five status families, each an opaque ground with its own ink and edge. They
   used to be a translucent tint over whatever happened to be behind, which means
   the same badge measured 4.77:1 on a card and 4.39:1 on the page — passing in
   one place and failing in the other with nothing in the stylesheet to say so.
   ========================================================================== */
.badge {
    font-weight: 600;
    letter-spacing: 0.25px;
    border-radius: 0.25rem;
}

.badge.bg-success, .badge.bg-success.bg-opacity-10, .badge.text-bg-success,
.alert-success {
    background-color: var(--ok-bg) !important;
    color: var(--ok-ink) !important;
    border: 1px solid var(--ok-edge) !important;
}

.badge.bg-primary, .badge.bg-primary.bg-opacity-10, .badge.text-bg-primary,
.alert-primary {
    background-color: var(--info-bg) !important;
    color: var(--info-ink) !important;
    border: 1px solid var(--info-edge) !important;
}

.badge.bg-danger, .badge.bg-danger.bg-opacity-10, .badge.text-bg-danger,
.alert-danger {
    background-color: var(--bad-bg) !important;
    color: var(--bad-ink) !important;
    border: 1px solid var(--bad-edge) !important;
}

.badge.bg-warning, .badge.bg-warning.bg-opacity-10, .badge.text-bg-warning,
.alert-warning {
    background-color: var(--warn-bg) !important;
    color: var(--warn-ink) !important;
    border: 1px solid var(--warn-edge) !important;
}

.badge.bg-info, .badge.bg-info.bg-opacity-10, .badge.text-bg-info,
.alert-info,
.distance-badge {
    background-color: var(--note-bg) !important;
    color: var(--note-ink) !important;
    border: 1px solid var(--note-edge) !important;
}

/* Neutral. Also where .text-bg-light lands, which Bootstrap renders as near-white
   type on near-white ground once the dark theme is on. */
.badge.bg-secondary,
.badge.text-bg-secondary,
.badge.text-bg-light {
    background-color: var(--chip-bg) !important;
    color: var(--chip-ink) !important;
    border: 1px solid var(--chip-edge) !important;
}

/* The one badge that is meant to be a block of ink: a section kicker. */
.badge.bg-dark,
.badge.text-bg-dark {
    background-color: var(--ink-strong) !important;
    color: var(--surface) !important;
    border: 1px solid var(--ink-strong) !important;
}

/* A link inside a coloured alert keeps the alert's ink and earns its underline,
   because a blue link on a yellow warning ground is a second colour system. */
.alert-link {
    color: inherit;
    font-weight: 700;
    text-decoration: underline;
}

.alert-link:visited {
    color: inherit;
}

/* ==========================================================================
   THE TOPIC RAIL
   --------------------------------------------------------------------------
   One rail, one treatment. It used to run a blue "Kaikki" chip, grey topic chips
   and a red "Kaikki 250 aihetta →" side by side: three colours for one row of
   controls, and the blue was Bootstrap's, not ours.

   Selected is the loud state and it is the brand — a topic rail is one of the
   two places the brand is allowed to be. In dark mode the fill and the ink swap
   places: a #C1121F block on a dark card is 2.71:1 against its own surround and
   all but invisible, while white on #EF4444 is 3.76:1 and fails as chip-sized
   type. A light fill with dark type answers both.
   ========================================================================== */
.topic-chip {
    background-color: var(--chip-bg);
    color: var(--chip-ink);
    border: 1px solid var(--chip-edge);
    text-decoration-line: none;
    font-weight: 600;
}

.topic-chip:visited {
    color: var(--chip-ink);
}

.topic-chip:hover,
.topic-chip:focus-visible {
    background-color: var(--surface-quiet);
    border-color: var(--brand-ink);
    color: var(--brand-ink);
}

.topic-chip.is-active,
.topic-chip.is-active:visited {
    background-color: var(--brand-chip-bg);
    color: var(--brand-chip-ink);
    border-color: var(--brand-chip-bg);
}

.topic-chip.is-active:hover,
.topic-chip.is-active:focus-visible {
    background-color: var(--brand-ink-hover);
    border-color: var(--brand-ink-hover);
    color: var(--on-brand);
}

/* The way out of the sidebar's shortlist and into the full index. An outline
   rather than a fill, so it reads as "more of these" and not as another topic. */
.topic-chip.topic-chip-more,
.topic-chip.topic-chip-more:visited {
    background-color: transparent;
    color: var(--brand-ink);
    border-color: var(--brand-ink);
}

/* ==========================================================================
   FORMS & INPUTS
   ========================================================================== */
.form-control,
.form-select,
.input-group-text {
    background-color: var(--field-bg);
    border: 1px solid var(--edge);
    color: var(--field-ink);
    border-radius: 0.375rem;
}

.form-control::placeholder {
    color: var(--field-placeholder);
    opacity: 1;
}

.form-control:focus,
.form-select:focus {
    background-color: var(--field-bg);
    border-color: var(--brand-ink);
    color: var(--field-ink);
    box-shadow: var(--focus-ring);
}

.form-label {
    color: var(--ink);
    font-weight: 600;
}

/* One focus treatment for everything that takes focus. Without it the browser's
   default ring lands on whatever colour the UA picked, which on a dark card is
   often invisible. */
:focus-visible {
    outline: 2px solid var(--brand-ink);
    outline-offset: 2px;
}

/* ==========================================================================
   BUTTONS
   ========================================================================== */
.btn-primary {
    background-color: var(--brand-block);
    border-color: var(--brand-block);
    color: var(--on-brand);
}

.btn-primary:hover,
.btn-primary:focus-visible {
    background-color: var(--brand-ink-hover);
    border-color: var(--brand-ink-hover);
    color: var(--on-brand);
}

.btn-outline-primary {
    color: var(--brand-ink);
    border-color: var(--brand-ink);
}

.btn-outline-primary:hover,
.btn-outline-primary.active {
    background-color: var(--brand-block);
    border-color: var(--brand-block);
    color: var(--on-brand);
}

.btn-outline-secondary {
    color: var(--ink);
    border-color: var(--edge);
}

.btn-outline-secondary:hover,
.btn-outline-secondary:focus-visible {
    background-color: var(--surface-quiet);
    border-color: var(--ink-quiet);
    color: var(--ink-strong);
}

/* The admin's destructive and confirming buttons. They were Bootstrap's own
   palette, which is a sixth set of greens and reds beside the five status
   families the badges already use — so they join those instead. */
.btn-secondary {
    background-color: var(--surface-quiet);
    border-color: var(--edge);
    color: var(--ink);
}

.btn-success,
.btn-outline-success:hover {
    background-color: var(--ok-ink);
    border-color: var(--ok-ink);
    color: var(--surface);
}

.btn-danger,
.btn-outline-danger:hover {
    background-color: var(--bad-ink);
    border-color: var(--bad-ink);
    color: var(--surface);
}

.btn-warning,
.btn-outline-warning:hover {
    background-color: var(--warn-ink);
    border-color: var(--warn-ink);
    color: var(--surface);
}

.btn-outline-success { color: var(--ok-ink); border-color: var(--ok-ink); }
.btn-outline-danger  { color: var(--bad-ink); border-color: var(--bad-ink); }
.btn-outline-warning { color: var(--warn-ink); border-color: var(--warn-ink); }
.btn-outline-info    { color: var(--note-ink); border-color: var(--note-ink); }

/* Bootstrap draws the dismiss cross as a black SVG, which on a dark card is a
   black cross on a near-black ground. */
[data-theme="dark"] .btn-close,
[data-bs-theme="dark"] .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
}

/* A button that looks like a link has to BE a link, or the two vocabularies
   drift: the feedback controls under a story are .btn-link and read as prose. */
.btn-link {
    color: var(--prose-link);
    text-decoration: underline;
}

.btn-link:hover,
.btn-link:focus-visible {
    color: var(--prose-link-hover);
}

/* ==========================================================================
   PAGINATION
   ========================================================================== */
.page-link {
    background-color: var(--surface);
    border-color: var(--edge);
    color: var(--prose-link);
    text-decoration: none;
}

.page-link:hover,
.page-link:focus-visible {
    background-color: var(--surface-quiet);
    border-color: var(--edge);
    color: var(--prose-link-hover);
}

/* The page you are on is not a link, so it is not link-coloured. It is the brand,
   which is the third and last place the brand is allowed to be. */
.page-item.active .page-link {
    background-color: var(--brand-chip-bg);
    border-color: var(--brand-chip-bg);
    color: var(--brand-chip-ink);
}

.page-item.disabled .page-link {
    background-color: var(--surface);
    border-color: var(--edge);
    color: var(--ink-quiet);
}

/* ==========================================================================
   ACCORDIONS & PROGRESS
   ========================================================================== */
.accordion-item {
    background-color: var(--surface);
    border-color: var(--edge);
    color: var(--ink);
}

.accordion-button {
    background-color: var(--surface);
    color: var(--ink-strong);
    font-weight: 600;
}

.accordion-button:not(.collapsed) {
    background-color: var(--surface-quiet);
    color: var(--brand-ink);
    box-shadow: none;
}

.progress {
    background-color: var(--surface-quiet);
    border-radius: 999px;
    overflow: hidden;
}

.progress-bar {
    background-color: var(--brand-block);
    color: var(--on-brand);
}

/* ==========================================================================
   SVG CHARTS & CONTAINERS
   ========================================================================== */
.svg-chart-box {
    background-color: var(--surface-quiet);
    border: 1px solid var(--edge);
    border-radius: 0.5rem;
    padding: 1rem;
}

.svg-chart-box text {
    fill: var(--ink);
}

.svg-chart-box text.text-meta {
    fill: var(--ink-quiet);
}

/* ==========================================================================
   STORY CARDS
   ========================================================================== */
.story-card {
    border: 1px solid var(--edge);
    background-color: var(--surface);
    margin-bottom: 2rem;
    border-radius: 0.5rem;
    /* !important because .card above sets the whole `border` shorthand !important, which
       expands to an important border-top-width and would eat this rule outright. */
    border-top: 2px solid var(--ink-strong) !important;
}

.story-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-lift);
}

h5.card-title,
h6.card-title {
    color: var(--ink-strong);
}

h5.card-title a {
    color: inherit !important;
    text-decoration: none;
}

h5.card-title a:hover {
    color: var(--color-primary) !important;
}

/* ==========================================================================
   THE STORY
   --------------------------------------------------------------------------
   The nameplate says which paper this is. This says what a story looks like in
   it, and it has to say the same thing on a card in a list and on the article
   page — a reader who clicks a headline should land on the same paper, not on
   a second site that happens to share a logo.

   The face is inverted from the usual arrangement. Archivo carries the mark,
   the kicker and the byline; Newsreader carries the HEADLINE as well as the
   running text. A sans headline over a serif body reads as a web page with an
   article in it. A serif headline over serif copy reads as one column of type
   that simply starts large, which is what a newspaper is.

   WHY EACH NUMBER:

     -.022em     Newsreader at headline size sets loose — it was drawn for
     tracking    running text, where the extra sidebearing is what makes it
                 readable. Pulled in, it holds together as a single object.

     1.1         headline leading. At the body's 1.6 a two-line headline reads
                 as two headlines.

     52ch /      the ingress is deliberately NARROWER than the body. Italic at
     63ch        a full measure is tiring, and the shorter line is what makes
                 the ingress read as a different kind of sentence rather than
                 as a first paragraph that happens to be slanted.

     1.17 /      ingress over body. Close enough that the two are the same
     1.06rem     voice, far enough that the order is not in question.

   The kicker's rule is drawn by ::after with flex:1, so its length is whatever
   the topic name leaves over. A long topic shortens it, a short one runs it
   nearly the width of the column. Every story is spaced slightly differently
   and there is nothing to tune per story.

   Finnish is why hyphens:auto and overflow-wrap are on the headlines rather
   than only on the body: "kaupunkisuunnittelu" and friends are single words
   wider than a two-column card, and a serif headline is exactly where an
   unbreakable compound would push out of its own box.
   ========================================================================== */

.story-kicker {
    display: flex;
    align-items: center;
    gap: .7em;
    margin: 0 0 .55rem;
    font-family: var(--font-headline);
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--color-primary);
    text-decoration: none;
}

.story-kicker:hover {
    color: var(--brand-ink-hover);
    text-decoration: none;
}

.story-kicker::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--color-primary);
    /* The rule carries the eye to the headline; at full strength it competes with
       the label it belongs to and the pair reads as a border, not as one line. */
    opacity: .45;
}

/* The card headline is a .stretched-link: its ::after covers the whole card at z-index 1,
   so without this the kicker is unclickable and every card sends the reader to the story
   even when they aimed at the topic. */
.story-card .story-kicker {
    position: relative;
    z-index: 2;
}

.story-headline {
    font-family: var(--font-body);
    font-weight: 700;
    font-size: clamp(2rem, 1.3rem + 2.6vw, 3.1rem);
    line-height: 1.1;
    letter-spacing: -.022em;
    text-wrap: balance;
    color: var(--ink-strong);
    hyphens: auto;
    overflow-wrap: break-word;
}

.story-headline a,
.story-headline .story-headline-link {
    color: inherit;
    text-decoration: none;
}

.story-headline a:hover {
    color: var(--color-primary);
}

/* Card headlines are the same setting one step down. Not a separate size ramp:
   a card that used its own proportions would make the list a second design. */
.story-card .card-title,
.story-card h5.card-title,
.story-card h6.card-title {
    font-family: var(--font-body);
    font-weight: 700;
    font-size: clamp(1.3rem, 1.05rem + .8vw, 1.7rem);
    line-height: 1.15;
    letter-spacing: -.022em;
    text-wrap: balance;
    margin-bottom: .6rem;
    color: var(--ink-strong);
    hyphens: auto;
    overflow-wrap: break-word;
}

/* The lead story is louder by size alone — same face, same tracking, same everything. */
.main-story-card .card-title {
    font-size: clamp(1.7rem, 1.2rem + 1.8vw, 2.4rem);
    /* Wraps at a measure rather than running the whole content column. At 2.4rem across
       ~1100px a headline is one very long line, while the ingress beneath it wraps at 52ch
       - so the two never share a right edge and the rest of the card is empty by accident.
       26ch of display type and 52ch of ingress land at about the same width, which is what
       makes them read as one column. */
    max-width: 26ch;
}

.story-ingress,
.story-card .card-text {
    font-family: var(--font-body);
    font-style: italic;
    /* 500, not 400, and the font request carries a real 1,6..72,400..600 so this is a
       drawn weight rather than a synthesised one. A 400 italic under a 700 roman reads as
       a caption on the headline instead of as the second voice - the eye takes the weight
       drop and the slant as one signal, so the slant has to be paid for. */
    font-weight: 500;
    /* Scales with the body below, and stays ahead of it. Growing the body alone would put
       desktop body copy ABOVE the ingress and invert the one hierarchy this layout has. */
    font-size: clamp(1.22rem, 1.1rem + 0.36vw, 1.42rem);
    /* Looser than the roman would want. An italic's slanted stems narrow the channel
       between lines, so the same leading that is comfortable upright reads as cramped
       here. */
    line-height: 1.52;
    max-width: 52ch;
    color: var(--ink);
}

/* In a two-up card the ingress sits directly under a headline barely larger than
   it; at the article's size the two would read as one block of italic. */
.story-card .card-text {
    /* Fluid, because the card headline is. The lead card's headline runs to 2.4rem, and
       against a fixed 1.08rem italic that is 2.2:1 - the story page, which reads
       comfortably, is nearer 1.5:1. A slanted serif loses more to a size gap than an
       upright one does, so it was the widest ratio on the site sitting under the largest
       type. */
    font-size: clamp(1.12rem, 1.0rem + 0.42vw, 1.4rem);
    margin-bottom: 0;
}

.story-body {
    /* Fluid, because 1.06rem is a fine phone size and a small one on a desktop: at a 63ch
       measure across a wide window the line is long and the type has to grow with it or
       the eye loses its place returning to the left margin. Phones keep the lower bound. */
    font-size: clamp(1.1rem, 1.0rem + 0.32vw, 1.26rem);
    line-height: 1.62;
    max-width: 63ch;
    hyphens: auto;
    overflow-wrap: break-word;
}

.story-body p {
    margin-bottom: 1.1em;
}

.story-byline {
    border-top: 1px solid var(--edge);
    padding-top: .55rem;
    font-family: var(--font-headline);
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--color-muted);
    /* Dates and clock times sit in these lines one under another down a list of
       cards. Proportional figures make that column visibly ragged. */
    font-variant-numeric: tabular-nums;
}

.story-byline a {
    color: inherit;
    text-decoration: none;
}

.story-byline a:hover {
    color: var(--color-primary);
    text-decoration: underline;
}

/* The hover lift is decoration. A reader who has asked the system for less motion
   should still get the card, just not the movement — including the transition,
   which would otherwise animate the colour and shadow changes on its own. */
@media (prefers-reduced-motion: reduce) {
    .story-card,
    .story-card:hover {
        transition: none;
        transform: none;
    }}

.edit-control-block {
    background-color: var(--surface-quiet);
    border: 1px solid var(--edge);
    border-radius: 0.375rem;
    padding: 1rem;
    margin-bottom: 1rem;
    color: var(--ink);
}

/* Pulse animation for active AI processing */
@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

.animate-pulse {
    animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

/* ==========================================================================
   THE FOOTER
   --------------------------------------------------------------------------
   It was .bg-light with .text-muted on it, which meant one theme's footer took
   its ground from a Bootstrap utility and its type from ours, and neither knew
   about the other.
   ========================================================================== */
.footer {
    background-color: var(--foot-bg) !important;
    border-top: 1px solid var(--edge) !important;
    color: var(--foot-ink);
}

.footer .text-muted,
.footer .text-secondary,
.footer small {
    color: var(--foot-ink) !important;
}

.footer strong {
    color: var(--foot-ink-strong);
}

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

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

.footer a:hover,
.footer a:focus-visible {
    color: var(--prose-link-hover);
}

/* ── Page-specific rules, moved out of the views ──────────────────────────
   index.ejs, story.ejs, nearby.ejs and nearby-results.ejs each opened their
   own <!DOCTYPE>, <html> and <head> and then included partials/header.ejs,
   which opens all three again. Every one of those pages shipped two complete
   documents and two <title>s, which is why the tab read
   "Paikallislehti - Joensuu - Paikallislehti". The heads are gone; the CSS
   that lived in them is here.
   ──────────────────────────────────────────────────────────────────────── */

/* from views/index.ejs — the day's three most important stories.
   The rail was #0d6efd, which was the fourth blue on that page. A promoted block
   is exactly the sort of thing the brand accent is for. */
.daily-digest-banner {
    background-color: var(--surface-soft);
    border: 1px solid var(--edge) !important;
    border-left: 4px solid var(--brand-ink) !important;
}

.daily-digest-banner .card {
    background-color: var(--surface) !important;
}

/* from views/story.ejs */
.audio-reader-box {
    background-color: var(--surface-soft);
    border: 1px solid var(--edge);
    transition: all 0.2s ease;
}

.audio-reading-active {
    border-color: var(--brand-ink) !important;
    box-shadow: var(--focus-ring);
}

.developing-story-box {
    border-left: 4px solid var(--brand-ink);
    background-color: var(--surface-soft);
}

.timeline-item {
    position: relative;
    padding-left: 20px;
}

.timeline-item::before {
    content: '';
    position: absolute;
    left: 0;
    top: 6px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: var(--ink-quiet);
}

.timeline-item.active::before {
    background-color: var(--brand-ink);
    box-shadow: var(--dot-ring);
}

/* from views/nearby-results.ejs */
#nearby-map {
    height: 380px;
    width: 100%;
    border-radius: 12px;
    box-shadow: var(--shadow-lift);
    margin-bottom: 2rem;
    z-index: 1;
}

.distance-badge {
    font-weight: 600;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    font-size: 0.85em;
}

/* The banner ran #0d6efd → #00baf1 with white type on it, and white on #00baf1
   is 2.26:1 — the right-hand half of that header could not be read at all. It
   now runs from the page's darkest ink to the prose blue, so the type clears AA
   at the pale end as well as the dark one. */
.location-header {
    background: linear-gradient(135deg, var(--header-from) 0%, var(--header-to) 100%);
    color: var(--on-header);
    padding: 2rem;
    border-radius: 12px;
    margin-bottom: 2rem;
}

.location-header .text-white,
.location-header h1 {
    color: var(--on-header) !important;
}

.location-header .text-white-50,
.location-header .text-muted,
.location-header small {
    color: var(--on-header-quiet) !important;
}

.location-header .btn-outline-light {
    color: var(--on-header);
    border-color: var(--on-header-quiet);
}

.location-header .btn-outline-light:hover,
.location-header .btn-light {
    background-color: var(--on-header);
    border-color: var(--on-header);
    color: var(--header-from);
}

.location-header .btn-light.text-primary {
    color: var(--header-to) !important;
}

.radius-pill-btn {
    border-radius: 20px !important;
    padding: 0.25rem 0.75rem !important;
    font-size: 0.85rem !important;
}

/* from views/choose-city.ejs — a whole card is the link, so the headline rule
   applies to it: ink at rest, dimmed once the reader has been to that town. */
.city-highlight {
    text-decoration: none;
    color: var(--ink);
}

/* Split from the rule above rather than folded into its selector list: the
   browser honours only colour-ish properties on a :visited rule and drops the
   rest, so a shared rule quietly means two different things to its two halves. */
.city-highlight:visited {
    color: var(--ink);
}

.city-highlight:hover,
.city-highlight:focus-visible {
    border-color: var(--brand-ink) !important;
    color: var(--ink);
}

/* ==========================================================================
   THE MAP POPUP
   --------------------------------------------------------------------------
   Leaflet builds its popups from a template string inside nearby-results.ejs,
   which paints the headline with an inline `color:#0d6efd` — the last Bootstrap
   blue on the site, and the one place a stylesheet cannot normally reach, since
   an inline style outranks any selector.

   `!important` is the exception to that: an important author declaration beats a
   normal inline one. So the popup joins the system from here rather than by
   editing the map's script block, which is out of scope for this pass.
   ========================================================================== */

.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
    background-color: var(--surface) !important;
    color: var(--ink) !important;
}

.leaflet-popup-content a {
    color: var(--ink-strong) !important;
    text-decoration-line: none !important;
}

.leaflet-popup-content a:visited {
    color: var(--ink-read) !important;
}

.leaflet-popup-content a:hover {
    color: var(--brand-ink) !important;
}

.leaflet-popup-content span {
    color: var(--ink-quiet) !important;
}


/* ==========================================================================
   THE MASTHEAD
   --------------------------------------------------------------------------
   joensuun.tienoilla.fi already reads as a Finnish sentence, so the mark only
   has to say it.

   Two blocks. The town is the variable and carries the colour; "tienoilla" is
   the constant and is a light panel set in italic. Two voices rather than two
   shouts: an offset between two identically-set words reads as a mistake, and
   an offset between two different kinds of thing reads as deliberate. Same
   reason the Guardian's masthead pairs a serif italic with a bold sans.

   GEOMETRY, and why each number is what it is:

     .34 / .17   horizontal padding, upper / lower, both measured from --fs
                 rather than from each block's own font-size. The panel's type
                 is larger, so an identical em value there would produce a
                 LARGER padding — the opposite of what is wanted.

     letters     the panel is offset by (.34 - .17), which puts the two words'
     align       first letters on one vertical line. The stagger is not chosen;
                 it is the remainder once the type lines up. It also cannot be
                 centred at any name length, because the left is pinned.

     -.16em      overlap. At 0 the mark reads as two objects; deeper than this
                 the panel starts eating the capitals.

     trim        both blocks are trimmed to cap-height and baseline, so padding
                 is measured from ink rather than from the em box. The em box
                 reserves descender space, and all-caps has no descenders, so
                 without this the block sits visibly low in its own box.

     -.035em /   the panel's own trim. "tienoilla" in italic has ascenders and
     -.205em     no descenders at all, so its ink is top-weighted: a box split
                 evenly LOOKS bottom-heavy. Optical, not geometric.
   ========================================================================== */

.masthead { margin: 0 0 2rem; }
/* In the sidebar the plate has a column to fit, not a page. */
.sidebar-column .plate { --plate-fs: clamp(1.4rem, calc(min(15rem, 22vw) / var(--len, 9) * 1.44), 2.6rem); }

.plate {
    display: inline-grid;
    justify-items: start;
    isolation: isolate;
    --plate-fs: clamp(2rem, calc(min(34rem, 76vw) / var(--len, 9) * 1.44), 5rem);
    --plate-pu: calc(var(--plate-fs) * .34);
    --plate-pl: calc(var(--plate-fs) * .17);
    --plate-vp: calc(var(--plate-fs) * .17);
}

.plate-town,
.plate-tienoilla {
    display: block;
    width: max-content;
    max-width: 100%;
    position: relative;
    line-height: 1;
}

.plate-town {
    background: var(--color-primary);
    padding: var(--plate-vp) var(--plate-pu);
    font-family: var(--font-headline);
    font-weight: 800;
    font-variation-settings: "wdth" 86;
    text-transform: uppercase;
    letter-spacing: -.012em;
    font-size: var(--plate-fs);
}
/* a folded compound: second half directly beneath the first, same block */
.plate-town + .plate-town { margin-top: .06em; }

.plate-out,
.plate-in { display: block; white-space: nowrap; }
.plate-out::before, .plate-out::after,
.plate-in::before,  .plate-in::after { content: ''; display: block; height: 0; }
.plate-out::before, .plate-in::before { margin-bottom: -.055em; }
.plate-out::after,  .plate-in::after  { margin-top: -.213em; }

/* the layer that is only ever seen ABOVE the block: the escaping diacritics */
.plate-out {
    position: absolute;
    top: var(--plate-vp);
    left: var(--plate-pu);
    color: var(--color-primary);
}
/* the reversed layer, cut off exactly where the block ends */
.plate-in {
    position: relative;
    color: #FFFFFF;
    clip-path: inset(0 -60% -60% -60%);
}

.plate-tienoilla {
    background: var(--color-masthead-panel);
    color: var(--color-masthead-panel-ink);
    padding: var(--plate-vp) var(--plate-pl);
    margin-top: -.16em;
    margin-left: calc(var(--plate-pu) - var(--plate-pl));
    box-shadow: var(--color-masthead-panel-shadow);
    font-family: var(--font-body);
    font-style: italic;
    font-weight: 400;
    letter-spacing: -.005em;
    font-size: calc(var(--plate-fs) * 1.16);
}
.plate-tienoilla::before,
.plate-tienoilla::after { content: ''; display: block; height: 0; }
.plate-tienoilla::before { margin-bottom: -.035em; }
.plate-tienoilla::after  { margin-top: -.205em; }

/* Where the browser can read the font's own metrics, say it declaratively and
   drop the hand-measured numbers above. They are correct for Archivo and
   Newsreader and for nothing else. */
@supports (text-box: trim-both cap alphabetic) {
    .plate-out, .plate-in, .plate-tienoilla { text-box: trim-both cap alphabetic; }
    .plate-out::before, .plate-out::after,
    .plate-in::before,  .plate-in::after,
    .plate-tienoilla::before, .plate-tienoilla::after { margin: 0; }
}

.masthead-dateline {
    margin: 1rem 0 0;
    font-family: var(--font-headline);
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   THE NAVBAR MARK
   --------------------------------------------------------------------------
   The same mark one line high: the town in its block, "tienoilla" plain in the
   same face beside it. A different lockup here would mean the bar carried a
   different mark from the page it sits on.

   Both parts are inline-block so they share a baseline. As plain inlines with
   padding they do not - padding on an inline does not move its baseline - which
   is what made the two words sit crooked.
   ========================================================================== */

.navbar-mark {
    display: inline-flex;
    align-items: baseline;
    gap: .3em;
    text-decoration: none;
    line-height: 1;
}
.navbar-mark:hover { text-decoration: none; }

.navbar-mark-town,
.navbar-mark-tienoilla {
    display: inline-block;
    vertical-align: baseline;
    font-family: var(--font-headline);
    font-weight: 800;
    font-variation-settings: "wdth" 86;
    text-transform: uppercase;
    letter-spacing: -.012em;
    font-size: 1rem;
    line-height: 1;
}
.navbar-mark-town {
    background: var(--color-primary);
    color: #FFFFFF;
    padding: .17em .28em .21em;
}
/* The masthead's second voice, at bar size. Not the same face as the town:
   the nameplate pairs a bold sans with a serif italic, and a bar that used one
   voice for both would be carrying a different mark from the page it sits on.

   No escaping diacritics here. At this size the dots would be a couple of
   pixels above a block that is itself only a few tall, which is fussy rather
   than characterful — the flourish belongs to the nameplate. */
.navbar-mark-tienoilla {
    font-family: var(--font-body);
    font-style: italic;
    font-weight: 400;
    font-variation-settings: normal;
    text-transform: none;
    letter-spacing: -.005em;
    font-size: 1.16rem;
    color: var(--color-navbar-text);
    padding: .17em 0 .21em;
    margin-left: .1em;
}

.navbar-change {
    font-family: var(--font-headline);
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .11em;
    text-transform: uppercase;
    margin-left: 1rem;
}

/* The count on a topic chip. Without it every topic promises the same, and half of
   them promise a single story. */
.tag-count {
    margin-left: .4em;
    opacity: .65;
    font-variant-numeric: tabular-nums;
    font-weight: 400;
}
/* The way out of the sidebar's shortlist and into the full index. Its colour now
   comes from .topic-chip-more with the rest of the rail; what is left here is the
   shape. */


/* ==========================================================================
   CHROME THAT SURVIVES A SCROLL
   --------------------------------------------------------------------------
   Two faults, one cause. On desktop the mark appeared twice - once as the
   nameplate above the topics, once in the bar - and scrolling past the top
   left the reader with no chrome at all: no way back, no topics, no search.

   So the bar sticks, and its mark is the nameplate's understudy: hidden while
   the nameplate is on screen, revealed the moment it leaves. One mark visible
   at a time, and something always in reach.
   ========================================================================== */

.navbar {
    position: sticky;
    top: 0;
    z-index: 1030;
}

@media (min-width: 768px) {
    /* The bar's mark waits offstage while the nameplate is doing the naming.
       Only on desktop: the sidebar - and so the nameplate - is not rendered on
       narrow screens, where the bar is the only mark there is. */
    /* Hidden AND out of the flow. Opacity alone left the mark occupying its full
       width, so the bar opened with a mark-shaped hole and "Vaihda" stranded to the
       right of nothing. max-width rather than display:none so the handover can still
       be animated. */
    .navbar-mark {
        opacity: 0;
        max-width: 0;
        overflow: hidden;
        transform: translateY(-.35em);
        pointer-events: none;
        transition: opacity .18s ease, transform .18s ease, max-width .18s ease;
    }
    body.masthead-hidden .navbar-mark {
        opacity: 1;
        max-width: 30rem;
        transform: none;
        pointer-events: auto;
    }

    /* Topics stay with the reader instead of scrolling away with the top of
       the page. align-self is load-bearing: a stretched flex item is full
       height, and a sticky box cannot move inside its own full height. */
    .sidebar-column {
        position: sticky;
        top: 4.5rem;
        align-self: flex-start;
        max-height: calc(100vh - 5.5rem);
        overflow-y: auto;
        scrollbar-width: thin;
    }
    /* Once the bar has taken over the naming, the nameplate's own space in the
       sidebar is dead weight; the topics move up into it. */
    body.masthead-hidden .sidebar-column .masthead {
        display: none;
    }
}

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

/* ==========================================================================
   THE BAR AT EVERY WIDTH
   --------------------------------------------------------------------------
   On a phone the bar used to put everything behind the hamburger, the mark
   included. A reader arriving at helsingin.tienoilla.fi saw a hamburger and
   the word "Aiheet": no sign of whose paper this is, and no way to search
   without opening a menu first.

   So the mark and the search field sit outside the collapse. The row wraps
   rather than truncating - "Uudenkaupungin" is fourteen characters and cannot
   share a 390px line with a usable field, and shortening the town name is not
   an option when the town name IS the masthead. When they do not fit, the
   search takes the second line whole.
   ========================================================================== */

.navbar-bar {
    flex-wrap: wrap;
    gap: .5rem .75rem;
    row-gap: .5rem;
}

/* The toggler now opens a menu of secondary things, so it stops being the
   first thing the eye lands on. */
.navbar-bar-toggle { flex: 0 0 auto; order: 1; }
.navbar-bar .navbar-mark,
.navbar-bar-change { flex: 0 0 auto; order: 2; margin-right: auto; }

/* Ordered last, and explicitly. `order` defaults to 0, so a collapse with no
   order of its own sorted AHEAD of a toggler at 1, a mark at 2 and a search at
   3: opening the menu put Aiheet, Vaihda, Lähellä and the location above the
   nameplate. The reader met the controls first and the masthead fourth. */
.navbar-collapse {
    order: 4;
    flex-basis: 100%;
    border-top: 1px solid var(--bar-edge-strong);
    margin-top: .6rem;
    padding-top: .65rem;
}

/* The open menu is a list, so it is laid out as one: full-width targets, one
   rhythm, and the controls gathered into a single group instead of four bare
   children wrapping wherever they fell. */
.navbar-collapse .navbar-nav { gap: 0; margin-bottom: .35rem; }
.navbar-collapse .nav-link {
    padding: .55rem 0;
    border-bottom: 1px solid var(--bar-edge);
}
.navbar-collapse .navbar-nav > li:last-child .nav-link { border-bottom: 0; }

.navbar-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    padding-top: .5rem;
}
.navbar-tools-location,
.navbar-tools-error {
    font-size: .8em;
    /* Takes the rest of the line so the two buttons stay together on the left
       rather than being pushed apart by a long "Sijainti: ..." string. */
    flex: 1 1 8rem;
    min-width: 0;
}

.navbar-search {
    display: flex;
    align-items: center;
    /* Grows into whatever is left, shrinks to nothing, and asks for 12rem before
       it will wrap. min-width:0 is load-bearing: a flex item's default minimum is
       its content, so without it the input's intrinsic width forces the bar wider
       than the screen and the whole page scrolls sideways. */
    flex: 1 1 12rem;
    min-width: 0;
    order: 3;
}

.navbar-search-input {
    flex: 1 1 auto;
    min-width: 0;
}

/* Collapsed to nothing at rest and opened when the field is in use. Enter
   submits either way, so this is an affordance rather than the only way
   through - which is what lets it be absent most of the time.

   max-width rather than display:none because display cannot be transitioned,
   and a button that appears instantly under a moving cursor reads as a glitch. */
.navbar-search-submit {
    max-width: 0;
    padding-left: 0;
    padding-right: 0;
    margin-left: 0;
    border-width: 0;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    transition: max-width .18s ease, opacity .18s ease, margin-left .18s ease,
                padding .18s ease;
}
.navbar-search:focus-within .navbar-search-submit,
.navbar-search-input:not(:placeholder-shown) ~ .navbar-search-submit {
    max-width: 6rem;
    padding-left: .75rem;
    padding-right: .75rem;
    margin-left: .5rem;
    border-width: 1px;
    opacity: 1;
}

/* On a phone the mark is the only thing naming the paper, and at bar size beside
   a full-width field it read as a caption on the search box. Below the desktop
   breakpoint it is set larger and given the room. */
@media (max-width: 991.98px) {
    .navbar-mark-town { font-size: 1.15rem; }
    .navbar-mark-tienoilla { font-size: 1.33rem; }
    .navbar-bar { gap: .5rem .6rem; }
    /* Shorter than a form control's default: the field is full width already, and
       at default height it out-weighed the nameplate above it. */
    .navbar-search-input { padding-top: .35rem; padding-bottom: .35rem; }
}

@media (min-width: 992px) {
    /* Wide enough for the menu to be inline, so the search belongs on the right,
       after the links - not hard left where taking it out of the collapse would
       otherwise leave it. */
    .navbar-bar { flex-wrap: nowrap; }
    .navbar-search { order: 4; flex: 0 1 22rem; margin-left: auto; }
    .navbar-collapse {
        order: 3;
        flex-basis: auto;
        border-top: 0;
        margin-top: 0;
        padding-top: 0;
    }
    .navbar-collapse .nav-link { padding: .5rem .75rem; border-bottom: 0; }
    .navbar-tools { padding-top: 0; flex-wrap: nowrap; }
    .navbar-tools-location, .navbar-tools-error { flex: 0 0 auto; }
    .navbar-bar .navbar-mark { margin-right: 0; }
}

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

/* ==========================================================================
   ASKING THE READER
   --------------------------------------------------------------------------
   The selection pill only appears once a reader has already decided to
   highlight something. This is the line that tells them the option exists,
   so it has to read as a sentence rather than as a toolbar - and the two
   words ARE the buttons, because a sentence that describes an action and
   then offers it again underneath is read once and obeyed never.

   Styled as a link inside running text, not as a control: a button here
   would out-shout the article it is asking about.
   ========================================================================== */

.story-feedback-invite {
    font-family: var(--font-body);
    font-size: .96rem;
    line-height: 1.55;
    color: var(--ink-quiet);
    max-width: 63ch;
    margin: 1.6rem 0 0;
    padding-top: 1rem;
    border-top: 1px solid var(--edge);
}

.story-feedback-word {
    /* A button that has to sit inside a sentence: every trace of button
       removed, then given back the one affordance a reader needs. */
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--prose-link);
    text-decoration: underline;
    text-underline-offset: .16em;
    cursor: pointer;
}
.story-feedback-word:hover,
.story-feedback-word:focus-visible {
    color: var(--prose-link-hover);
}

/* ==========================================================================
   A CARD ENDS WHERE ITS TEXT ENDS
   --------------------------------------------------------------------------
   Bootstrap's h-100 makes every card in a row the same height and lets
   .card-body grow to fill. In a row with more than one card that is what
   aligns the footers. In a ONE-column tier there is nothing to be equal to,
   so the growth is pure void between the ingress and the tags - the empty
   lower half of every lead card on desktop, and of every card on a phone.
   ========================================================================== */

.main-stories-section .card-body { flex-grow: 0; }

@media (max-width: 767.98px) {
    /* Every tier is a single column here, so none of them has anything to
       match heights with. */
    .story-card .card-body { flex-grow: 0; }
}

/* The lead tier is one card per row, so the card had the full content width and put its
   text in the left half of it. A card that stops at a reading width reads as a column with
   a margin; a full-width box with text in half of it reads as broken. */
.main-stories-section .story-card {
    max-width: 48rem;
}

/* The mark that says "this keyword does not belong". Part of the chip's own shape rather
   than a second element beside it: as a separate button with the word "väärä?" it doubled
   the number of things in the keyword row and repeated the same question once per tag. */
.topic-chip-flag {
    appearance: none;
    background: none;
    border: 0;
    padding: 0 .1em 0 .35em;
    margin-left: -.25rem;
    font: inherit;
    font-size: .95rem;
    line-height: 1;
    color: var(--ink-quiet);
    cursor: pointer;
}
.topic-chip-flag:hover,
.topic-chip-flag:focus-visible {
    color: var(--brand-ink);
}

/* Asks once, for the whole list, and takes itself away once asked. */
.topic-flag-toggle {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    margin: .35rem 0 0;
    font-family: var(--font-body);
    font-size: .92rem;
    color: var(--prose-link);
    text-decoration: underline;
    text-underline-offset: .16em;
    cursor: pointer;
    display: block;
}
.topic-flag-toggle:hover,
.topic-flag-toggle:focus-visible {
    color: var(--prose-link-hover);
}
