/* ==========================================================================
   TOKENS — single source of truth for both themes.
   Light is default; dark applies via prefers-color-scheme unless the user
   explicitly chose light; data-theme="dark" forces dark regardless of OS.
   ========================================================================== */

:root {
  --paper: #f7f2e7;
  --surface: #fbf7ee;
  --ink: #221e17;
  --ink-soft: #443e30;
  --muted: #6d6555;
  --brass: #7d6247;
  --hairline: #d9d2bf;
  --hairline-soft: #e7e0cf;
  --cat-arts: #8f5a5a;
  --cat-culture: #7a5a92;
  --cat-stem: #47725a;
  --cat-sports: #8a5c33;
  --cat-industry: #7d6440;
  --cat-opinion: #635e55;
  --cat-local: #4f6156;
  --cat-nations: #54688a;
  /* The reading measure. Prose is unreadable much past this, so the article
     column is sized to it rather than to a share of the viewport — see
     .article-layout. Single source so the column and the text it holds cannot
     drift apart and open a gutter. */
  --measure: 70ch;
  --serif: "Noto Serif", "Noto Serif Fallback", Georgia, "Times New Roman", serif;
  /* Headlines. Libre Franklin is the open Franklin Gothic descendant — the face
     American papers have set headlines in for a century — so a story title reads
     as a title against the Noto Serif prose beneath it, with no ambiguity about
     which is which. Neutral on purpose: the Permanent Marker masthead carries the
     personality, and a characterful headline face would compete with it. */
  --display: "Libre Franklin", "Libre Franklin Fallback", system-ui, -apple-system, "Segoe UI", sans-serif;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --img-filter: none;
  /* One duration and one easing for every state change on the site. They were
     hard-coded at four call sites in three sheets (0.15s, 0.2s, 0.3s) before
     this, which is why a chip and a sitemap link eased at different speeds. */
  --motion-fast: 0.15s;
  --ease: ease;
  /* Spacing scale, 4px base.

     Colour was fully tokenised here from the start; spacing never was, and the
     sheets had drifted to 26 distinct px values -- including 5, 15, 22, 25, 26,
     34 and 38, which sit on no grid at all and came from nudging a number until
     a screenshot looked right. Inconsistent spacing is the single thing that
     most reliably makes a considered layout read as careless.

     The off-grid values are snapped to the nearest step. The rest of the sheets
     still use plain px where they already agreed with this scale: converting
     ~250 healthy declarations would be a large unreviewable diff for no visual
     change. New work uses the tokens. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #181510;
    --surface: #1f1b14;
    --ink: #e8e2d3;
    --ink-soft: #c4bca9;
    --muted: #a89e8a;
    --brass: #c9a878;
    --hairline: #383226;
    --hairline-soft: #2c2719;
    --cat-arts: #c99a9a;
    --cat-culture: #b49ac9;
    --cat-stem: #93bda6;
    --cat-sports: #c99e73;
    --cat-industry: #bfa677;
    --cat-opinion: #aaa398;
    --cat-local: #9cab9f;
    --cat-nations: #9db0cf;
    --img-filter: brightness(0.85) saturate(0.9);
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --paper: #181510;
  --surface: #1f1b14;
  --ink: #e8e2d3;
  --ink-soft: #c4bca9;
  --muted: #a89e8a;
  --brass: #c9a878;
  --hairline: #383226;
  --hairline-soft: #2c2719;
  --cat-arts: #c99a9a;
  --cat-culture: #b49ac9;
  --cat-stem: #93bda6;
  --cat-sports: #c99e73;
  --cat-industry: #bfa677;
  --cat-opinion: #aaa398;
  --cat-local: #9cab9f;
  --cat-nations: #9db0cf;
  --img-filter: brightness(0.85) saturate(0.9);
  color-scheme: dark;
}

/* Map category slugs to the active --cat. Covers the slug class wherever it
   appears (pills, category headers) and the card that contains a slugged pill. */
.arts, article:has(.category-pill.arts) { --cat: var(--cat-arts); }
.culture, article:has(.category-pill.culture) { --cat: var(--cat-culture); }
.stem, article:has(.category-pill.stem) { --cat: var(--cat-stem); }
.sports, article:has(.category-pill.sports) { --cat: var(--cat-sports); }
.industry, article:has(.category-pill.industry) { --cat: var(--cat-industry); }
.opinion, article:has(.category-pill.opinion) { --cat: var(--cat-opinion); }
.local, article:has(.category-pill.local) { --cat: var(--cat-local); }
.nations, article:has(.category-pill.nations) { --cat: var(--cat-nations); }

/* ==========================================================================
   FONT — the only webfont on the site (masthead). Self-hosted (Task 3).
   Metric-tuned local fallback keeps the masthead's layout box stable
   before the woff2 arrives, so the swap causes no CLS.
   ========================================================================== */

/* Noto Serif carries every serif surface: article prose, headlines, the quote
   band. Self-hosted for the same reason Permanent Marker is — the site links to
   no font CDN, and test/integration/theme_test.rb fails if one appears.
   Variable across 400-700, so these two files cover regular, bold and italic
   rather than the four static files Droid Serif (its predecessor) would need. */
@font-face {
  font-family: "Noto Serif";
  src: url("/assets/noto-serif-latin-a64ce73c.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Noto Serif";
  src: url("/assets/noto-serif-latin-italic-13c24dcc.woff2") format("woff2");
  font-weight: 400 700;
  font-style: italic;
  font-display: swap;
}

/* Metric-tuned stand-in shown during the swap, so swapping in the real font does
   not reflow the page (layout shift is a ranking signal, not just a nuisance).
   Noto Serif is WIDER than Georgia — measured in-browser at 40px, 1364px of text
   against Georgia's 1249px — so Georgia has to be scaled up to match, not down.
   size-adjust 109.2% is that ratio; the ascent/descent overrides come from the
   same measurement, where Noto's line box runs about 20% taller. */
@font-face {
  font-family: "Noto Serif Fallback";
  src: local("Georgia"), local("Times New Roman"), local("Times");
  size-adjust: 109.2%;
  ascent-override: 98%;
  descent-override: 27%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Libre Franklin";
  src: url("/assets/libre-franklin-latin-0c54693a.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

/* Metric-tuned stand-in, same reasoning as the serif one above.
   Libre Franklin is about 5.8% wider than Arial: measured in-browser at 40px/700,
   1125px against 1063px. That 1063 is the real Arial advance width — Liberation
   Sans and Nimbus Sans, both metric-compatible with Arial by design, measure
   identically. (The bare `font-family: Arial` keyword reports 1124px here only
   because fontconfig substitutes a wider face for it; local() finds the genuine
   metrics, which is what the fallback actually gets.) */
@font-face {
  font-family: "Libre Franklin Fallback";
  src: local("Arial"), local("Helvetica"), local("Liberation Sans");
  size-adjust: 105.8%;
  ascent-override: 92%;
  descent-override: 24%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Permanent Marker";
  src: url("/assets/permanent-marker-latin-243826b7.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Masthead Fallback";
  src: local("Arial Black"), local("Arial Bold"), local("Arial");
  size-adjust: 106%;
  ascent-override: 95%;
  descent-override: 25%;
}

/* ==========================================================================
   BASE
   ========================================================================== */

body {
  margin: 0;
  padding: 0;
  font-family: var(--serif);
  font-size: clamp(1.0625rem, 1rem + 0.3vw, 1.125rem); /* 17–18px */
  line-height: 1.6;
  color: var(--ink);
  background-color: var(--paper);
}

::-webkit-scrollbar { background: var(--paper); }
::-webkit-scrollbar-thumb { background: var(--hairline); }

p { margin-top: 0; margin-bottom: 1.1em; }

h1, h2, h3, h4 { line-height: 1.2; text-wrap: balance; }

img { max-width: 100%; }

/* Photographic content dims slightly at night so photos don't glare. */
.img-dim, .article-hero-img, .hero-figure img, .sidebar-art-img {
  filter: var(--img-filter);
}

:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}

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

/* ==========================================================================
   SHARED PRIMITIVES (used on every page type)
   ========================================================================== */

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

h2 a, .card-link {
  color: inherit;
  text-decoration: none;
}

h2 a:hover, .card-link:hover { text-decoration: underline; }

.section-label {
  font-family: var(--sans);
  font-size: 0.8125rem; /* 13px */
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  border-bottom: 2px solid var(--ink);
  padding-bottom: 6px;
  margin: 0 0 16px;
}

/* Category kicker — small caps colored label (was a pill). */
.category-pill {
  display: inline-block;
  font-family: var(--sans);
  font-size: 0.75rem; /* 12px */
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cat, var(--brass));
  text-decoration: none;
  margin-bottom: 4px;
}

.source-label {
  display: inline-block;
  font-family: var(--sans);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.read-more-btn {
  display: inline-block;
  font-family: var(--sans);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--ink);
  border-radius: 2px;
  padding: 10px 18px;
  min-height: 44px;
  box-sizing: border-box;
  text-decoration: none;
}

.read-more-btn:hover { background: var(--ink); color: var(--paper); }

.text-link {
  font-family: var(--sans);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--brass);
  text-decoration: none;
}

.text-link:hover { text-decoration: underline; }

.breadcrumbs {
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--muted);
  margin: 10px 0 18px;
}

.breadcrumb-link { color: var(--muted); text-decoration: none; }
.breadcrumb-link:hover { color: var(--brass); text-decoration: underline; }
.breadcrumb-sep { margin: 0 6px; color: var(--muted); }
.breadcrumb-current { color: var(--ink-soft); }

.no-articles-message {
  color: var(--muted);
  font-style: italic;
  text-align: center;
  margin: 40px 0;
}

/* Visually hidden but available to screen readers — used by search field labels. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Sitemap links inherit ink (replaces the old !important block). */
.sitemap-link, .sitemap-col ul li a {
  color: var(--ink);
  text-decoration: none;
}

/* ==========================================================================
   PRESSED STATES

   Audited 2026-09-01: of the 107 interactive elements on the homepage, 106 had
   a :hover rule and *none* had an :active one. Hover does not exist on a phone,
   so tapping a headline produced no acknowledgement whatsoever until the next
   page painted -- on a slow connection, several hundred ms of a reader
   wondering whether the tap registered. These rules are that missing quarter
   second of feedback.

   Two shapes, because two kinds of thing are being pressed. Text links dim:
   opacity cannot reflow, so a headline that wraps to three lines cannot shift
   the page under a thumb that is still on it. Buttons and chips take a small
   scale, which is the affordance of a physical control being pushed.

   base.css already collapses transition-duration under prefers-reduced-motion,
   so both degrade to an instant, non-animated state change rather than
   disappearing -- the feedback survives, only the tween goes.
   ========================================================================== */

.card-link:active,
.brief-link:active,
.text-link:active,
.product-link:active,
.lead-link:active,
.hero-link:active,
.band-more:active,
.trust-link:active,
.footer-link:active,
.archive-link:active,
.breadcrumb-link:active,
.sitemap-link:active,
.main-logo-link:active,
.subhead a:active {
  opacity: 0.6;
}

.read-more-btn:active,
.subcat-chip:active,
.pagination-btn:active,
.theme-toggle:active,
.head-search-submit:active,
#backToTop:active {
  transform: scale(0.97);
}

@media (prefers-reduced-motion: no-preference) {
  .card-link, .brief-link, .text-link, .product-link, .lead-link, .hero-link,
  .band-more, .trust-link, .footer-link, .archive-link, .breadcrumb-link,
  .sitemap-link, .main-logo-link, .subhead a {
    transition: opacity var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease);
  }

  .read-more-btn, .subcat-chip, .pagination-btn, .theme-toggle,
  .head-search-submit, #backToTop {
    transition: transform var(--motion-fast) var(--ease),
                color var(--motion-fast) var(--ease),
                border-color var(--motion-fast) var(--ease),
                background var(--motion-fast) var(--ease);
  }
}

/* The wordmark is the home affordance on every page and was the one link on the
   site with no hover state at all. */
.main-logo-link:hover { color: var(--brass); }
