@charset "UTF-8";
/* tokens.css is loaded as its own versioned <link> in render.mjs, not
   @import-ed here: an @import carries no ?v=, and .htaccess serves .css as
   immutable for a year, so an imported tokens.css could never be refreshed by
   an ASSET_VERSION bump. See render.mjs's cssLinks comment. */

/* ---------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
img, picture, svg, video { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); }
ul, ol { margin: 0; padding: 0; list-style: none; }
input, textarea { font: inherit; color: inherit; }

/* root font-size is overwritten in px by the font-size tool (rem-based scaling) */
html {
  font-size: 100%;
  scroll-behavior: smooth;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  -webkit-font-smoothing: antialiased;
  transition: background var(--transition), color var(--transition);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* -------------------------------------------------------------- typography */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  color: var(--text-soft);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-snug);
  margin: 0 0 .6em;
}
/* Every level takes a step from the type scale in tokens.css — no raw sizes
   here, so a heading's size is decided in exactly one place for the whole
   site and two headings of the same rank cannot drift apart.

   The ladder descends, and is anchored at the bottom rather than the top:
   h6 sits on --fs-base, body text itself, because a heading rendering
   smaller than the paragraph beneath it reads as less important than the
   text it introduces. Nothing goes under that floor.

   h4 and h5 deliberately share --fs-lg. This content mixes heading levels
   for what is one role — a subheading inside an article — because each
   piece kept whatever level its original publication used. Giving the
   middle levels one size means that inherited inconsistency cannot show as
   a visible size difference. See "Type scale" in CLAUDE.md. */
h1 { font-size: var(--fs-3xl); font-weight: var(--fw-display); }
h2 { font-size: var(--fs-2xl); }
h3 { font-size: var(--fs-xl); }
h4 { font-size: var(--fs-lg); }
h5 { font-size: var(--fs-lg); }
h6 { font-size: var(--fs-base); }
p { margin: 0 0 1.2em; }
p:last-child { margin-bottom: 0; }

/* --accent-strong, not --accent: this is 12px uppercase text on a light
   ground, where the brand gold measures 2.32:1 (AA needs 4.5:1); the deeper
   gold is 4.96:1. Every dark-ground eyebrow uses its own class and its own
   lighter gold (.page-banner-eyebrow, .sticky-subheader-eyebrow,
   .gallery-eyebrow, .hx-words-eyebrow, .hx-voices-eyebrow,
   .hx-announce-body .eyebrow), so none of them are affected by this. */
.eyebrow {
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-widest);
  text-transform: uppercase;
  color: var(--accent-strong);
}

/* Fallback so an article-body quote/highlight block looks identical even if
   a content file uses a bare <blockquote> with no class — the canonical
   look (gold quotation mark, tinted panel) lives on .archival-quote/
   .pull-quote in components.css; this makes a missed class attribute
   harmless instead of producing a visually different quote. Scoped to
   .article-body (not a bare `blockquote` selector) so it doesn't reach the
   homepage's differently-styled .hero-quote / .endorsement blockquote.
   See "Quote/highlight blocks" in CLAUDE.md. */
.article-body blockquote:not(.archival-quote):not(.pull-quote) {
  position: relative;
  margin: var(--sp-lg) 0;
  padding: var(--sp-lg) var(--sp-lg) var(--sp-md) var(--sp-2xl);
  background: var(--bg-sunken);
  border-top: 1px solid var(--border-color);
  border-bottom: 1px solid var(--border-color);
  border-left: 3px solid var(--gold-500);
}
.article-body blockquote:not(.archival-quote):not(.pull-quote)::before {
  content: '\201C';
  position: absolute;
  top: .4rem;
  left: .85rem;
  font-family: var(--font-display);
  font-size: 3.2rem;
  line-height: var(--lh-none);
  color: var(--gold-500);
  opacity: .6;
}
.article-body blockquote:not(.archival-quote):not(.pull-quote) p {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--fw-regular);
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
  color: var(--text-soft);
  margin: 0 0 var(--sp-sm);
}
.article-body blockquote:not(.archival-quote):not(.pull-quote) p:last-of-type { margin-bottom: 0; }
.article-body blockquote:not(.archival-quote):not(.pull-quote) cite {
  display: block;
  margin-top: var(--sp-sm);
  font-family: var(--font-sans);
  font-style: normal;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--accent-strong);
}
.article-body blockquote:not(.archival-quote):not(.pull-quote) cite::before { content: '— '; color: var(--accent); }

/* ----------------------------------------------------------------- layout */
.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--pad);
}
.container--wide { max-width: var(--container-wide); }
.container--list { max-width: var(--container-list); }
.container--narrow { max-width: var(--container-narrow); }

section { padding-block: var(--sp-fluid-xl); }
.section-head { max-width: 640px; margin-bottom: var(--sp-fluid-md); }
.section-head--center { margin-inline: auto; text-align: center; }

.grid-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sp-fluid-xs);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  font-family: var(--font-sans);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  padding: .75em 1.5em;
  border-radius: var(--radius);
  border: 1px solid var(--accent);
  color: var(--accent-strong);
  transition: var(--transition);
}
.btn:hover { background: var(--accent); color: var(--white); }
/* Navy label rather than white: white on the brand gold measures 2.33:1,
   well under AA, while navy on that same gold is 7.99:1 — so the button
   keeps its bright brand-gold fill instead of being darkened. On hover the
   fill goes to the deeper gold, where white is the readable pair (4.96:1)
   and navy would not be (3.74:1) — hence the label colour flips too. */
.btn--solid { background: var(--accent); border-color: var(--accent); color: var(--navy-900); }
.btn--solid:hover { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--white); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 1rem;
  z-index: var(--z-top);
  background: var(--accent-strong);
  color: var(--white);
  padding: .6em 1.2em;
  border-radius: var(--radius);
}
.skip-link:focus { left: 1rem; }
/* The skip link's own ground IS --accent-strong, so the default ring would
   vanish into it; white is 4.96:1 against that fill. */
.skip-link:focus-visible { outline-color: var(--white); }

/* ------------------------------------------------------------------ focus
   One rule for the whole site. Every interactive element gets the same ring;
   :focus-visible rather than :focus so it appears for keyboard and
   assistive-tech users without flashing on every mouse click.

   Dark containers below only reassign --focus-ring; they do not restate the
   rule. Browsers follow an element's own border-radius for the outline, so
   round controls get a round ring for free.

   scroll-margin-top exists for WCAG 2.2's "focus not obscured": the header
   is sticky, and without this, tabbing to an element just above the fold
   scrolls it to the very top where the header covers it. */
:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}
:is(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  scroll-margin-top: calc(var(--header-h) + var(--sp-md));
}

/* The one case where the ring is deliberately withheld: an element that a
   script just handed focus to as part of opening a surface the user opened
   themselves — the search field, the lightbox's close button, the print
   dialog's first button.

   The ring answers "where did Tab take me?". Nobody pressed Tab here, so it
   answers nothing and reads as an error outline around the search field.
   It cannot be avoided by not moving focus (that strands focus on <body>
   behind the modal), and it is not fixable per component: by spec, an
   element that takes keyboard input matches :focus-visible whenever it is
   focused, however the focus arrived.

   main.js's focusQuietly() adds this class, and removes it on the first
   blur — so the suppression lasts exactly as long as that one script-given
   focus. Tab away and back, and the ring is there as normal. Nothing else
   on the site loses its ring. See CLAUDE.md, "Interaction states". */
.is-focus-quiet:focus-visible {
  outline: none;
}

/* Grounds dark enough that the default ring would be hard to see. Measured:
   --accent-strong is 3.74:1 on navy against --gold-400's 10.70:1. */
.site-header,
.site-footer,
.sticky-subheader,
.lightbox,
.mobile-nav-panel,
.search-overlay,
.selection-share-bubble,
.selection-thread-panel,
.print-photos-box,
.hx-hero,
.hx-announce-panel,
.hx-words,
.hx-voices {
  --focus-ring: var(--gold-400);
}

/* -------------------------------------------------------------- selection
   Selecting text is a real feature here, not incidental: the
   selection-to-tweet bubble is built on it (assets/js/main.js), so this is
   one of the interactions a visitor performs deliberately most often. Left
   to the browser it looked however the operating system decided — different
   on every device, and never the site's own colours.

   One pair for every ground. --accent-soft carries its own background, so
   what matters is text against it, not against the page: navy on gold-300
   is 13.18:1, which stays legible over white sections, navy sections and
   photographs alike. */
::selection {
  background: var(--accent-soft);
  color: var(--navy-900);
}

/* ------------------------------------------------------------- placeholder
   The search field's prompt. It previously inherited a browser grey that had
   never been contrast-checked; --text-faint is 5.21:1 on the field's white
   ground and is the same tone every other hint on the site uses. */
::placeholder {
  color: var(--text-faint);
  opacity: 1; /* Firefox dims placeholders by default; the colour above already
                 encodes the intended weight, so let it stand as written */
}

/* ------------------------------------------------------------------ active
   Feedback for the moment of pressing. It matters most on touch, where
   :hover does not exist at all and a tap otherwise confirms nothing.

   Scoped to buttons and cards, deliberately never to text links — the same
   division the hover rules follow: colour is the response for text, movement
   belongs to things that look pressable. A link that shifts under the finger
   reads as a fault, not as feedback. */
:is(button, .btn, .pager-btn, .listing-row, .contact-channel):active {
  transform: translateY(1px);
}
/* Cards that lift on hover settle back to their resting position instead of
   pushing below it — the press cancels the lift rather than adding to it. */
.listing-row:active { transform: none; }

/* ----------------------------------------------------------------- motion */
[data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}
[data-reveal].is-revealed { opacity: 1; transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* ------------------------------------------------------------------ print */
.print-header { display: none; }
@media print {
  /* ---- the type scale, restated for paper -------------------------------
     Redefining the tokens re-sizes every rule on the site at once, because
     nothing sets a raw font-size any more (see tokens.css). Paper needs its
     own values for two reasons:

       1. The screen scale is built on vw. A percentage of the viewport is
          meaningless on paper — it resolves against the page box, which
          differs by paper size, margins and browser, so the same article
          would print at different sizes from different machines.
       2. Screen sizes are simply too big for print. 17px body text is
          right at arm's length on a monitor and clumsy at reading distance
          on A4.

     pt, the unit paper is actually measured in: 1pt = 1/72 inch, so these
     are physical sizes that do not depend on any screen. 11pt body text sits
     in the normal range for a printed page. The steps keep the same ~1.14x
     progression as the screen ladder, so the printed page has the same
     proportions, just smaller.

     Before this block existed print was genuinely broken: the page title
     carried a one-off 1.5rem override (24px) while subheadings kept their
     screen value (24.32px), so a subheading printed LARGER than the title
     above it — the same inversion that was fixed on screen, still alive on
     paper. Measured, not assumed.

     The :root selector is not optional — custom properties are declarations
     and need a rule to live in. Written bare inside the media block they are
     silently ignored, and every size falls back to the screen values. */
  :root {
    --fs-3xs:  7.5pt;
    --fs-2xs:  8pt;
    --fs-xs:   8.5pt;
    --fs-sm:   10pt;
    --fs-base: 11pt;
    --fs-lg:   13pt;
    --fs-xl:   16pt;
    --fs-2xl:  19pt;
    --fs-3xl:  22pt;
    --fs-4xl:  27.5pt;
    --fs-5xl:  55pt;
  }

  /* Scoped to .site-header/footer, not the bare `header`/`footer` tags —
     the in-content title block (<header class="article-hero">) also uses
     the <header> element semantically and must stay visible when printing. */
  .site-header, footer, .no-print, .sticky-subheader { display: none !important; }
  /* Chrome + decorative template graphics — never printed, regardless of
     the photo choice below. .page-banner is a shared category/template
     background image (never "the article's own photo" anywhere else in
     this codebase either — see computeOgImage() in render.mjs), so it's
     grouped here, not with the real content photos. */
  .breadcrumbs, .page-banner, .share-bar, .article-pager,
  .mobile-nav-panel, .search-overlay, .lightbox, .eyebrow,
  .print-photos-dialog { display: none !important; }
  /* Real content photos — visible by default (owner's decision, 2026-07-30:
     printing directly, e.g. Ctrl+P or the browser's own print menu, prints
     photos too, no prompt involved — this is the print stylesheet's default
     state, not an opt-in). Hidden only when the visitor explicitly chooses
     "Text only" in the Share block's Print dialog (see the
     print-photos-dialog prompt in assets/js/main.js), which toggles
     body.print-text-only before calling window.print(). */
  .article-lead-image, .article-body img, .article-body figure, .inline-gallery { display: block !important; }
  .article-body img { max-width: 100%; margin-block: .5em; }
  .article-lead-image { border: none; padding: 0; margin-bottom: 1em; }
  body.print-text-only .article-lead-image,
  body.print-text-only .article-body img,
  body.print-text-only .article-body figure,
  body.print-text-only .inline-gallery { display: none !important; }
  /* No font-size override for .article-hero h1 any more: it takes --fs-3xl
     like every other page title, which the block above has already restated
     in pt. The old override was what put the title below its own
     subheadings on paper. */
  .print-header {
    display: block;
    font-family: var(--font-sans);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-wide);
    margin-bottom: var(--sp-md);
  }
  body { color: #000; background: #fff; }
}

/* ------------------------------------------------------------- 404 page
   Styles for 404.html. This project has no component-scoped CSS mechanism —
   every rule is global and hand-maintained — so these live here rather than
   alongside the template that emits them (render.mjs's render404Page). */
.not-found {
  min-height: 50vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--sp-sm);
  padding-block: var(--sp-fluid-xl);
}

/* ---------------------------------------------- cross-document navigation
   This project uses real multi-page navigation: every article is its own
   static HTML file, with no client-side router and no DOM persisted across
   navigations. The native CSS View Transitions API still gives a smooth
   cross-fade between those full page loads, with zero JS and none of the
   complexity a SPA router would bring (stuck-hover states, stale active-nav
   classes, persisted-DOM edge cases) — see CLAUDE.md "Navigation
   architecture". Unsupported browsers just navigate normally; nothing else
   depends on this rule. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}
