/**
 * Cool Spark — main.css
 *
 * Loud contemporary newsmagazine: near-black hero, bright white editorial body,
 * electric violet as the working accent, acid lime as the shout. Heavy uppercase
 * Archivo for display, Inter for copy.
 *
 * See DESIGN.md for the rules these styles implement. The previous theme's
 * concave "notch" corner cuts and goo-filter split button are retired.
 *
 * Contents
 *   1.  Tokens
 *   2.  Reset & base
 *   3.  Layout primitives
 *   4.  Typography helpers
 *   5.  Buttons, chips, pills
 *   6.  Arrow badge
 *   7.  Header
 *   8.  Front page — hero
 *   9.  Front page — freshly published
 *   10. Front page — featured mosaic
 *   11. Front page — category tiles
 *   12. Front page — latest rail
 *   13. Front page — popular (inverted)
 *   14. Front page — highlight
 *   15. Cards (shared)
 *   16. Newsletter CTA band (lime)
 *   17. Footer
 *   18. Archive
 *   19. Single post
 *   20. Static page
 *   21. 404
 *   22. Newsletter forms
 *   23. Pagination
 *   24. Motion & responsive
 */

/* ═══ 1. TOKENS ═══════════════════════════════════════════════════════════ */
:root {
  /* Ground */
  --void: #0b0b0d;
  --void-2: #15151a;
  --void-3: #22222a;
  --paper: #ffffff;
  --paper-2: #f7f7fa;
  --mist: #f2f1fb;

  /* Ink */
  --ink: #0b0b0d;
  --ink-2: #55555f;
  --ink-3: #8a8a96;
  --on-dark: #ffffff;
  --on-dark-2: rgba(255, 255, 255, 0.68);
  --on-dark-3: rgba(255, 255, 255, 0.44);

  /* Accent — violet works, lime shouts */
  --violet: #5b2be8;
  --violet-deep: #4a1fcc;
  --violet-bright: #7a4dff;
  --violet-wash: #ede7ff;
  --violet-line: rgba(91, 43, 232, 0.24);
  /* The "shout" accent. Token name kept as --lime so its ~22 call sites stay
     grouped and semantic; only the value changed from the original acid green to
     this warmer yellow. */
  --lime: #ffd509;
  --lime-deep: #e8c000;

  /* Legacy aliases. The gold palette is gone, but keeping these pointed at the
     new accent means any rule or inline style still naming them renders in the
     current identity instead of reintroducing orange. */
  --gold: var(--violet);
  --gold-bright: var(--violet-bright);
  --gold-deep: var(--violet-deep);
  --accent: var(--violet);

  /* Semantics */
  --up: #12b76a;
  --down: #e8342b;

  /* Lines */
  --line: rgba(11, 11, 13, 0.11);
  --line-2: rgba(11, 11, 13, 0.2);
  --line-dark: rgba(255, 255, 255, 0.14);

  /* Type */
  --font-display: "Archivo", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* Meta furniture shares the body face; tabular-nums keeps digits from
     shifting width in times, counters and reading estimates. */
  --font-meta: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-code: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* Metrics */
  --wrap: 1320px;
  --gutter: 24px;
  --gap: clamp(14px, 1.6vw, 24px);
  --pad: clamp(56px, 7vw, 104px);
  --pad-tight: clamp(34px, 4vw, 56px);

  /* Shape — one media radius, one pill */
  --r: 10px;
  --r-sm: 6px;
  --r-lg: 16px;
  --r-pill: 999px;

  /* Elevation */
  --shadow-sm: 0 1px 2px rgba(11, 11, 13, 0.06), 0 2px 8px rgba(11, 11, 13, 0.04);
  --shadow-md: 0 4px 14px rgba(11, 11, 13, 0.08), 0 14px 36px rgba(11, 11, 13, 0.07);
  --shadow-lg: 0 10px 28px rgba(11, 11, 13, 0.12), 0 34px 70px rgba(11, 11, 13, 0.14);

  /* Header logo height — overridden by the customizer */
  --logo-h: 40px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ═══ 2. RESET & BASE ═════════════════════════════════════════════════════ */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img,
svg,
video {
  max-width: 100%;
  height: auto;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  cursor: pointer;
  background: none;
  border: 0;
}

input,
textarea,
select {
  font: inherit;
  color: inherit;
}

/* Display type is uppercase and negatively tracked at every size — the bigger
   it gets, the tighter it goes. Per-role tracking is set on each rule. */
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.025em;
  text-transform: uppercase;
  text-wrap: balance;
}

p {
  margin: 0;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

figure {
  margin: 0;
}

:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

/* On a dark ground the violet ring disappears into the black; lime reads at any
   size and is only ever used transiently here, so it doesn't count as a shout. */
.is-inverted :focus-visible,
.site-footer :focus-visible,
.hero :focus-visible {
  outline-color: var(--lime);
}

::selection {
  background: var(--violet);
  color: #fff;
}

.skip-link {
  position: absolute;
  top: -60px;
  left: 16px;
  z-index: 300;
  padding: 11px 20px;
  background: var(--violet);
  color: #fff;
  border-radius: var(--r-pill);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  transition: top 0.2s var(--ease);
}

.skip-link:focus {
  top: 16px;
}

.u-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ═══ 3. LAYOUT PRIMITIVES ════════════════════════════════════════════════ */
.container {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container--narrow {
  max-width: 780px;
}

.container--wide {
  max-width: 1480px;
}

.section-pad {
  padding-block: var(--pad);
}

.section-pad--tight {
  padding-block: var(--pad-tight);
}

/* Inverted band — black ground, white type. */
.is-inverted {
  background: var(--void);
  color: var(--on-dark);
}

.is-inverted h1,
.is-inverted h2,
.is-inverted h3,
.is-inverted h4 {
  color: var(--on-dark);
}

/* Mist band — the pale lilac ground the category strip sits on. */
.is-mist {
  background: var(--mist);
}

.grid-4 {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap);
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap);
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap);
}

/* Section heading row: oversized title left, controls or a link right. */
.sec-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: clamp(20px, 2.6vw, 38px);
}

.sec-head__title {
  font-size: clamp(30px, 4.6vw, 62px);
  letter-spacing: -0.03em;
  line-height: 0.94;
  margin: 0;
}

.sec-head__aside {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 18px;
}

/* The small right-hand kicker line from "FRESHLY PUBLISHED" in the reference. */
.sec-head__kicker {
  max-width: 22ch;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-2);
  text-transform: none;
  letter-spacing: 0;
}

.is-inverted .sec-head__kicker {
  color: var(--on-dark-2);
}

.sec-head__nav {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* Rail position readout, between the two arrows. tabular-nums stops the row
   jittering as the digits change. */
.rail-count {
  min-width: 54px;
  text-align: center;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

.is-inverted .rail-count {
  color: var(--on-dark-3);
}

/* Rail arrows — circular, bordered, filling in on hover. */
.rail-btn {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink);
  transition: background 0.18s var(--ease), color 0.18s var(--ease),
    border-color 0.18s var(--ease), opacity 0.18s var(--ease);
}

.rail-btn svg {
  width: 18px;
  height: 18px;
}

.rail-btn:hover:not(:disabled) {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}

.rail-btn:disabled {
  opacity: 0.3;
  cursor: default;
}

.is-inverted .rail-btn,
.is-mist .rail-btn {
  border-color: var(--line-dark);
  color: var(--on-dark);
}

.is-mist .rail-btn {
  border-color: var(--line-2);
  color: var(--ink);
}

.is-inverted .rail-btn:hover:not(:disabled) {
  background: #fff;
  border-color: #fff;
  color: var(--void);
}

/* ═══ 4. TYPOGRAPHY HELPERS ═══════════════════════════════════════════════ */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--violet);
}

.is-inverted .eyebrow,
.hero .eyebrow {
  color: var(--lime);
}

.badge-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

/* The meta row: reading time and date as small icon+label pairs. */
.metarow {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 14px;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

.metarow__item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

.metarow__item svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  opacity: 0.75;
}

.metarow--on-dark {
  color: var(--on-dark-2);
}

/* Byline — avatar plus name, optionally stacked with the author's desk. */
.byline {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-meta);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink-2);
  min-width: 0;
}

.byline__avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--paper-2);
}

.byline__name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.byline__sep {
  opacity: 0.4;
}

.byline time {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.byline--on-dark {
  color: var(--on-dark-2);
}

.byline--on-dark time {
  color: var(--on-dark-3);
}

/* Stacked: name over desk, used on article headers. */
.byline--stacked {
  gap: 12px;
  align-items: center;
}

.byline--stacked .byline__avatar {
  width: 46px;
  height: 46px;
}

.byline__stack {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.byline--stacked .byline__name {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--ink);
}

.byline--on-dark.byline--stacked .byline__name {
  color: var(--on-dark);
}

.byline__role {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--violet);
}

.byline--on-dark .byline__role {
  color: var(--lime);
}

/* ═══ 5. BUTTONS, CHIPS, PILLS ════════════════════════════════════════════ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 13px 24px;
  border: 1.5px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.2s var(--ease), color 0.2s var(--ease),
    border-color 0.2s var(--ease), transform 0.2s var(--ease);
}

.btn:hover {
  transform: translateY(-1px);
}

.btn:active {
  transform: translateY(0);
}

.btn--primary {
  background: var(--violet);
  border-color: var(--violet);
  color: #fff;
}

.btn--primary:hover {
  background: var(--violet-deep);
  border-color: var(--violet-deep);
}

/* Black pill — the reference's default action on light and lime grounds. */
.btn--ink {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}

.btn--ink:hover {
  background: var(--void-3);
  border-color: var(--void-3);
}

.btn--lime {
  background: var(--lime);
  border-color: var(--lime);
  color: var(--void);
}

.btn--lime:hover {
  background: var(--lime-deep);
  border-color: var(--lime-deep);
}

.btn--ghost {
  background: transparent;
  border-color: var(--line-2);
  color: var(--ink);
}

.btn--ghost:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}

/* Dark-ground contexts. .error-main is listed alongside .is-inverted and .hero
   because it sets its own black background without carrying the .is-inverted
   class — without it the ghost button fell through to the light-ground default
   and rendered near-black type on black with a 20%-alpha border. */
.is-inverted .btn--ghost,
.hero .btn--ghost,
.error-main .btn--ghost {
  border-color: var(--line-dark);
  color: var(--on-dark);
}

.is-inverted .btn--ghost:hover,
.hero .btn--ghost:hover,
.error-main .btn--ghost:hover {
  background: #fff;
  border-color: #fff;
  color: var(--void);
}

.btn--sm {
  padding: 10px 18px;
  font-size: 11px;
}

/* A modest step up from the base 13px/24px — the previous 16px/32px with a 36px
   badge made the 404's pair read as oversized next to the rest of the theme's
   buttons, for only a 1px gain in type size. Only the 404 uses this variant. */
.btn--lg {
  padding: 14px 26px;
  font-size: 13px;
}

/* Pill with a trailing arrow disc, as on "VIEW ALL ARTICLES ›". The disc keeps
   its own contrast on every button variant via currentColor inversion. */
.btn--arrow {
  padding-right: 6px;
}

.btn--arrow .arrow-badge {
  width: 30px;
  height: 30px;
}

/* Kept just under the button's inner height. At 36px the badge was taller than
   the reduced padding allows and would set the pill's height itself. */
.btn--arrow.btn--lg .arrow-badge {
  width: 32px;
  height: 32px;
}

/* Chips — saturated per-category badges. Colour arrives as inline custom
   properties from st_the_category_chips(), because the palette is hash-assigned
   and a stylesheet would need a rule per category. */
.chip {
  display: inline-flex;
  align-items: center;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  background: var(--chip-bg, var(--violet));
  color: var(--chip-fg, #fff);
  font-family: var(--font-meta);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  line-height: 1.4;
  transition: transform 0.16s var(--ease), filter 0.16s var(--ease);
}

.chip:hover {
  transform: translateY(-1px);
  filter: brightness(1.08);
}

/* Chips sitting on top of a photograph need to stay legible over any frame. */
.chip--on-media {
  box-shadow: 0 2px 10px rgba(11, 11, 13, 0.28);
}

.card__chips,
.article-header__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* ═══ 6. ARROW BADGE ══════════════════════════════════════════════════════ */
/* The one recurring affordance: a filled disc with a chevron. Replaces the old
   corner notch — cards are flush now, so this stands alone. */
.arrow-badge {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--ink);
  color: #fff;
  transition: background 0.2s var(--ease), color 0.2s var(--ease),
    transform 0.2s var(--ease);
}

.arrow-badge svg {
  width: 55%;
  height: 55%;
}

.arrow-badge--violet {
  background: var(--violet);
  color: #fff;
}

.arrow-badge--lime {
  background: var(--lime);
  color: var(--void);
}

.arrow-badge--light {
  background: #fff;
  color: var(--void);
}

.arrow-badge--lg {
  width: 46px;
  height: 46px;
}

.arrow-badge--sm {
  width: 26px;
  height: 26px;
}

/* Inside a pill the disc inverts against the button's own fill. */
.btn--primary .arrow-badge,
.btn--ink .arrow-badge {
  background: #fff;
  color: var(--ink);
}

.btn--lime .arrow-badge {
  background: var(--void);
  color: var(--lime);
}

.btn--ghost .arrow-badge {
  background: var(--ink);
  color: #fff;
}

.is-inverted .btn--ghost .arrow-badge,
.hero .btn--ghost .arrow-badge,
.error-main .btn--ghost .arrow-badge {
  background: #fff;
  color: var(--ink);
}

.btn--ghost:hover .arrow-badge {
  background: #fff;
  color: var(--ink);
}

.is-inverted .btn--ghost:hover .arrow-badge,
.hero .btn--ghost:hover .arrow-badge,
.error-main .btn--ghost:hover .arrow-badge {
  background: var(--ink);
  color: #fff;
}

/* Nudge the chevron on hover of whatever encloses it. */
.btn:hover .arrow-badge,
a:hover > .arrow-badge,
.card:hover .arrow-badge,
.tile:hover .arrow-badge {
  transform: translateX(2px);
}

/* ═══ 7. HEADER ═══════════════════════════════════════════════════════════ */
/* Slightly lifted off the hero's black and closed with a hairline, so the bar
   reads as its own surface. At the same --void as the hero they merged into one
   undifferentiated slab. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--void-2);
  color: var(--on-dark);
  box-shadow: inset 0 -1px 0 var(--line-dark);
  transition: box-shadow 0.25s var(--ease);
}

/* Keeps the inset hairline from the base rule — a bare box-shadow here would
   replace it, not add to it. */
.site-header--scrolled {
  box-shadow: inset 0 -1px 0 var(--line-dark), 0 10px 30px rgba(11, 11, 13, 0.45);
}

/* Masthead: the wordmark alone, centred. Three tracks are kept so the brand sits
   in the middle column and stays optically centred once the MENU pill appears in
   the right track below 900px — a two-track layout would shift the logo off
   centre at exactly the width where the pill arrives. */
.masthead {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  min-height: 68px;
}

.masthead__brand {
  grid-column: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
}

.masthead__util {
  grid-column: 3;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
}

/* The MENU pill is mobile-only: above 900px the full category row is already on
   screen, so a control that toggles it would do nothing useful. Hidden here and
   revealed in the ≤900px block. */
.menu-pill {
  display: none;
}

.site-header .custom-logo-link,
.site-header .site-logo {
  display: inline-flex;
  align-items: center;
}

.site-header .custom-logo {
  width: auto;
  height: auto;
  max-height: var(--logo-h);
  object-fit: contain;
  /* The uploaded wordmark is dark-on-transparent; the masthead is black, so it
     has to be inverted to read. filter is used rather than swapping the asset so
     the customizer's logo picker keeps working with any upload. */
  filter: invert(1) brightness(1.9);
}

.site-logo {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--on-dark);
}

/* The bordered "≡ MENU" pill. Only painted below 900px — see the display:none
   above and the reveal in the ≤900px block. */
.menu-pill {
  align-items: center;
  gap: 9px;
  padding: 9px 16px;
  border: 1.5px solid var(--line-dark);
  border-radius: var(--r-pill);
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--on-dark);
  transition: background 0.18s var(--ease), color 0.18s var(--ease),
    border-color 0.18s var(--ease);
}

.menu-pill:hover,
.menu-pill[aria-expanded="true"] {
  background: #fff;
  border-color: #fff;
  color: var(--void);
}

.menu-pill__bars {
  display: grid;
  gap: 3px;
  width: 14px;
}

.menu-pill__bars span {
  display: block;
  height: 1.5px;
  background: currentColor;
  border-radius: 2px;
}

/* Primary navigation row — the full category list, on its own hairlined row. */
.nav-primary {
  border-top: 1px solid var(--line-dark);
}

.nav-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 4px 30px;
  min-height: 50px;
}

.nav-list a {
  display: inline-block;
  padding: 14px 0;
  font-family: var(--font-meta);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-dark-2);
  position: relative;
  transition: color 0.18s var(--ease);
}

.nav-list a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 8px;
  height: 2px;
  background: var(--lime);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.22s var(--ease);
}

.nav-list a:hover {
  color: var(--on-dark);
}

.nav-list a:hover::after,
.nav-list .current-menu-item > a::after,
.nav-list .current-cat > a::after {
  transform: scaleX(1);
}

.nav-list .current-menu-item > a,
.nav-list .current-cat > a {
  color: var(--on-dark);
}

/* Hamburger is the .menu-pill above 900px' collapse point; below it the pill
   toggles this panel. */
.nav-toggle {
  display: none;
}

/* ═══ 8. FRONT PAGE — HERO ════════════════════════════════════════════════ */
/* Black band carrying "EXPLORE NOW" at display scale with an interactive card
   deck fanned in front of it. The words are fixed brand copy (Customizer:
   st_hero_words), not a post title — the deck is what carries the content.
   Deck mechanics live in main.js under "Hero card deck". */
/* The band's ground is built from three stacked layers, all painted here rather
   than as extra elements — no additional DOM, no image requests:

     1. a blueprint grid of column and row rules
     2. a soft violet glow behind the card deck
     3. the flat --void base

   Grain is added separately by ::after, since it needs its own blend mode.

   The grid is the point: 222px of flat black sat either side of the headline and
   read as an undifferentiated slab. Column rules give the band structure without
   competing with the type, which suits a newspaper masthead. */
.hero {
  position: relative;
  color: var(--on-dark);
  overflow: hidden;
  /* Asymmetric: the deck column hangs low in .hero__row and supplies the visual
     mass at the bottom, so less padding is needed there than at the top. */
  padding-block: clamp(30px, 4vw, 60px) clamp(24px, 3vw, 44px);

  /* Flat ground only. The grid and glow live on ::before so they can be masked
     independently — masking .hero itself would clip its own background and let
     the page behind show through. */
  background: var(--void);
}

/* Decorative ground: grid + glow, faded out toward the edges.
   Sits below the content (.hero__inner is z-index 2) and above the flat black. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;

  background-image:
    /* Every fourth column rule, brighter — gives the mesh a rhythm so it reads as
       deliberate structure rather than uniform graph paper. Listed first so it
       paints above the base rules. */
    repeating-linear-gradient(
      to right,
      rgba(255, 255, 255, 0.11) 0 1px,
      transparent 1px calc(var(--hero-grid, 88px) * 4)
    ),
    /* Vertical column rules. */
    repeating-linear-gradient(
      to right,
      rgba(255, 255, 255, 0.05) 0 1px,
      transparent 1px var(--hero-grid, 88px)
    ),
    /* Horizontal row rules, fainter so the verticals read as dominant. */
    repeating-linear-gradient(
      to bottom,
      rgba(255, 255, 255, 0.032) 0 1px,
      transparent 1px var(--hero-grid, 88px)
    ),
    /* Violet glow behind the deck, giving the black some depth. Kept low — at a
       higher alpha it turned the lower half of the band a hazy purple instead of
       reading as light behind the cards. */
    radial-gradient(
      52% 58% at 50% 52%,
      rgba(91, 43, 232, 0.2),
      transparent 72%
    );
  /* One entry per background-image, in the same order: accent columns, base
     columns, base rows, glow. */
  background-size:
    calc(var(--hero-grid, 88px) * 4) 100%,
    var(--hero-grid, 88px) 100%,
    100% var(--hero-grid, 88px),
    100% 100%;

  /* Dissolves the grid before the band's edges, so it reads as texture rather
     than a table with borders. */
  -webkit-mask-image: radial-gradient(
    120% 104% at 50% 48%,
    #000 38%,
    rgba(0, 0, 0, 0.5) 74%,
    transparent 100%
  );
  mask-image: radial-gradient(
    120% 104% at 50% 48%,
    #000 38%,
    rgba(0, 0, 0, 0.5) 74%,
    transparent 100%
  );
}

/* Fine grain over the whole band. An inline SVG turbulence filter — a few hundred
   bytes, no network request, and no tiling artefacts at any viewport size.
   `soft-light` lets it sit into the black rather than greying it out. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.4'/%3E%3C/svg%3E");
  mix-blend-mode: soft-light;
  opacity: 0.45;
}

/* ── Hand-drawn ink strokes, as in the reference. They self-draw on entry: the
      dash pattern is the full path length, offset to hide it, then animated to
      0. pathLength is normalised to 1 so one dash value works for both paths
      regardless of their real geometry. */
.hero__ink {
  position: absolute;
  z-index: 1;
  pointer-events: none;
}

.hero__ink path {
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 1.4;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Both sit in the upper flanks: outboard of the headline, above the dek and tag
   columns, curving inward toward the deck. Sized off the viewport so the sweep
   scales with the band rather than crowding it at narrow widths. */
.hero__ink--left {
  top: 6%;
  left: -4%;
  width: clamp(150px, 21vw, 380px);
}

.hero__ink--right {
  top: 6%;
  right: -4%;
  width: clamp(150px, 21vw, 380px);
}

/* Only animate once the deck has booted, so the strokes draw with the rest of
   the entrance rather than before the cards exist. */
.js .hero__ink path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.js .hero.is-ready .hero__ink path {
  animation: hero-draw 2.4s var(--ease) forwards;
}

/* Staggered so the pair reads as drawn by hand rather than stamped on
   symmetrically. */
.js .hero.is-ready .hero__ink--right path {
  animation-delay: 0.4s;
}

@keyframes hero-draw {
  to {
    stroke-dashoffset: 0;
  }
}

.hero__inner {
  position: relative;
  z-index: 2;
}

/* ── Stage. Title and deck share one grid cell so the type runs BEHIND the
      cards — that overlap is the core of the reference's composition. The deck
      is absolutely positioned so it can never push the stage taller. */
/* The headline's own band. The deck no longer lives here — it sits in
   .hero__row below — so this only has to hold the type. */
.hero__stage {
  position: relative;
  display: grid;
  place-items: center;
  padding-block: clamp(20px, 3vw, 44px) 0;
}

/* Bottom row: dek left, deck centred, tag list right. The side columns are equal
   fractions so the deck stays optically centred regardless of either one's
   content length. `align-items: end` bottom-aligns the text columns against the
   deck column, which is the tallest item and so sets the baseline — items are
   aligned rather than stretched, so nothing is forced to the deck's height. */
.hero__row {
  --deck-w: clamp(180px, 21vw, 292px);
  --deck-h: calc(var(--deck-w) * 4 / 3);

  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: end;
  gap: clamp(16px, 3vw, 48px);
  margin-top: clamp(8px, 1.4vw, 20px);
}

/* The deck column, nudged below the flanking text so the stack reads as sitting
   slightly lower in the composition rather than level with the copy. Negative
   top margin pulls it up under the headline; the fan's own upward spread then
   overlaps the type's lower edge. */
.hero__deck-col {
  position: relative;
  grid-column: 2;
  width: var(--deck-w);
  /* Deck height, plus room beneath for the controls row. */
  height: calc(var(--deck-h) + 56px);
  /* Only a slight lift, so the fan's topmost card just grazes the headline while
     the FRONT card still starts below the flanking copy — that is what makes the
     deck read as sitting lower in the composition. Pulling harder (past about
     -60px here) raises the front card above the dek and loses the effect. */
  margin-top: clamp(-56px, -4vw, -20px);
  /* The row is the last thing in the hero, so this trims the slack the tall deck
     column would otherwise leave beneath it. */
  margin-bottom: calc(var(--deck-h) * -0.08);
}

/* ── The words. Slightly off-white and tucked behind the deck, per the
      reference. */
.hero__title {
  position: relative;
  z-index: 1;
  grid-area: 1 / 1;
  margin: 0;
  text-align: center;
  font-size: clamp(38px, 9.2vw, 168px);
  font-weight: 800;
  line-height: 0.86;
  letter-spacing: -0.045em;
  color: #e8e8f0;
  /* Parallax offset written by JS on pointer move. */
  transform: translate3d(var(--tx, 0px), var(--ty, 0px), 0);
  transition: transform 0.5s var(--ease);
  will-change: transform;
}

/* Splitting a word into per-glyph inline-blocks lets the line break between ANY
   two glyphs, which shows up on narrow screens as "EXPLOR / E NOW". The fix is
   to forbid wrapping across the run and allow it only at the spaces, which opt
   back in below. */
.hero__title-ink {
  display: block;
  white-space: nowrap;
}

/* Per-glyph spans, so each letter can rise and unblur in sequence. */
.hero__char {
  display: inline-block;
}

/* A space collapses to nothing as an inline-block, so it needs its width back.
   It is also the one place the headline may wrap, so it opts back in. */
.hero__char--space {
  width: 0.26em;
  white-space: normal;
}

.js .hero__char {
  opacity: 0;
  transform: translateY(0.5em) rotate(6deg);
  filter: blur(14px);
}

.js .hero.is-ready .hero__char {
  /* --i is the glyph index, printed by the template — this is what staggers the
     reveal left to right. */
  animation: hero-char 0.9s var(--ease) forwards;
  animation-delay: calc(var(--i) * 55ms);
}

@keyframes hero-char {
  to {
    opacity: 1;
    transform: none;
    filter: blur(0);
  }
}

/* ── The deck. A fixed-size box the cards absolutely fill, so the fan can spread
      without any card affecting layout. */
/* Fills the top of its column, with the controls row occupying the strip below. */
.hero__deck {
  position: absolute;
  z-index: 2;
  left: 0;
  top: 0;
  width: var(--deck-w);
  height: var(--deck-h);
  /* NOT preserve-3d. Combined with will-change/backface-visibility on the
     animating card siblings, a 3D context made the browser composite the cards
     into a shared GPU layer, and adjacent cards' photos bled across each other's
     rotated edges — a hard diagonal seam appeared INSIDE a single card's box
     (verified: elementFromPoint reported the same image either side of it).
     The fan only needs 2D rotate/translate, so flattening removes the artifact
     with no visual loss. */
  transform-style: flat;
}

.hero-card {
  position: absolute;
  inset: 0;
  border-radius: var(--r-lg);
  /* Composited transform written by JS: fan offset, depth scale, drag throw and
     the pointer-tracked 3D tilt all resolve into this one property. */
  transform: var(--t, rotate(var(--rest-tilt, 0deg)));
  /* Pivot at the card's centre. A low origin (the earlier 50% 88%) swings the top
     corners through a wide arc, so each buried card's corner fanned far out of
     the stack and got sliced by the card in front of it along a hard diagonal.
     Real prints in a pile rotate about their middle. */
  transform-origin: 50% 50%;
  /* transform-style and backface-visibility are deliberately absent — see the
     .hero__deck comment. They promoted the cards into a shared compositing layer
     and made adjacent photos bleed across each other's rotated edges. */
  will-change: transform;
}

/* Cards animate under JS control; the transition covers every state change
   except an active drag, where JS removes it for 1:1 pointer tracking. */
.js .hero-card {
  transition: transform 0.62s var(--ease), opacity 0.45s var(--ease);
}

/* Entrance: the deck deals in from below, one card at a time. This animates a
   WRAPPER property (the plate's own transform) rather than .hero-card's, because
   JS owns --t on the card and the two would overwrite each other. --depth is the
   card's print order, so the front card lands first. */
.js .hero .hero-card__plate {
  opacity: 0;
  transform: translateY(90px) scale(0.9);
}

.js .hero.is-ready .hero-card__plate {
  animation: hero-deal 0.85s var(--ease) forwards;
  animation-delay: calc(160ms + var(--depth) * 90ms);
}

@keyframes hero-deal {
  to {
    opacity: 1;
    transform: none;
  }
}

.hero-card.is-dragging {
  transition: none;
  cursor: grabbing;
}

/* A card held under an active drag casts a deeper shadow, so it reads as picked
   up off the pile. */
.hero-card.is-dragging .hero-card__plate {
  box-shadow: 0 44px 90px rgba(0, 0, 0, 0.72), 0 12px 30px rgba(0, 0, 0, 0.6);
}

/* The card face. Separate from .hero-card so the plate can carry the radius,
   photo and sheen while the parent owns only transforms. */
.hero-card__plate {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  background: var(--void-2);
  /* The reference's cards are white-edged prints. */
  border: 5px solid #fff;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5), 0 4px 12px rgba(0, 0, 0, 0.35);
  transition: box-shadow 0.4s var(--ease);
}

.hero-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Depth shading — a black veil over buried cards, its strength driven by --dim
   from render() (1 = front card, fully lit). Painted as an overlay inside the
   clipping plate rather than as a filter on .hero-card, because filter creates a
   containing block that flattens preserve-3d and would kill the deck's tilt.
   z-index 1 keeps it above the photo but below the caption (z-index 2), so the
   front card's headline is never veiled. */
.hero-card__plate::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  background: #05050a;
  opacity: calc(1 - var(--dim, 1));
  transition: opacity 0.55s var(--ease);
  pointer-events: none;
}

/* A diagonal gloss that sweeps across whichever card is on top — sells the cards
   as physical prints catching light.

   The stops are deliberately WIDE and low-contrast. An earlier version used a
   narrow band (transparent 34% → 0.28 at 48% → transparent 66%), and that hard
   edge read as a diagonal seam slicing the photograph in two — especially during
   a deck transition, when the image underneath the sweep changes mid-animation.
   A gradient this soft cannot produce a visible line.

   `screen` blending also means it only ever lightens, so it can never darken one
   half of the photo and imply an edge. */
.hero-card__sheen {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    104deg,
    transparent 0%,
    rgba(255, 255, 255, 0.04) 30%,
    rgba(255, 255, 255, 0.13) 50%,
    rgba(255, 255, 255, 0.04) 70%,
    transparent 100%
  );
  mix-blend-mode: screen;
  transform: translateX(-120%);
  opacity: 0;
}

/* Runs only on the card currently on top, and not while it is being dragged. */
.js .hero-card.is-front:not(.is-dragging) .hero-card__sheen {
  animation: hero-sheen 6.4s var(--ease) 1.2s infinite;
}

@keyframes hero-sheen {
  0% {
    transform: translateX(-120%);
    opacity: 0;
  }
  8% {
    opacity: 1;
  }
  26% {
    transform: translateX(120%);
    opacity: 0;
  }
  100% {
    transform: translateX(120%);
    opacity: 0;
  }
}

/* Caption on the card, so the reader knows what EXPLORE will open. Only the
   front card shows one. */
/* Sits in the card's lower strip, clear of the centred EXPLORE pill.

   Inset by the plate's 5px white border and given the plate's inner radius, so
   the scrim is clipped to the same rounded rectangle as the photo. Without this
   the gradient paints over the border and past the card's rounded corners,
   leaving a visible wedge outside the print. */
.hero-card__cap {
  position: absolute;
  z-index: 2;
  left: 5px;
  right: 5px;
  bottom: 5px;
  border-radius: 0 0 calc(var(--r-lg) - 5px) calc(var(--r-lg) - 5px);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 34px 13px 13px;
  background: linear-gradient(
    to top,
    rgba(11, 11, 13, 0.92) 0%,
    rgba(11, 11, 13, 0.6) 45%,
    transparent 100%
  );
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
  pointer-events: none;
}

.hero-card.is-front .hero-card__cap {
  opacity: 1;
  transform: none;
}

.hero-card__kicker {
  align-self: flex-start;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  background: var(--violet);
  color: #fff;
  font-family: var(--font-meta);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.hero-card__title {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.008em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.85), 0 1px 3px rgba(0, 0, 0, 0.9);
  /* Two lines maximum, so a long headline can't run off the card. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── The EXPLORE pill, seated mid-card as in the reference. */
.hero__explore {
  position: absolute;
  z-index: 4;
  left: 50%;
  /* Vertically centred on the deck, per the reference. The deck occupies the top
     var(--deck-h) of the column, so its midpoint is half that from the top. */
  top: calc(var(--deck-h) / 2 - 21px);
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 6px 6px 18px;
  background: #fff;
  color: var(--void);
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  transition: transform 0.28s var(--ease), box-shadow 0.28s var(--ease);
}

.hero__explore:hover {
  transform: translateX(-50%) translateY(-2px) scale(1.04);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55);
}

.hero__explore-arrow {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--violet);
  color: #fff;
  transition: transform 0.28s var(--ease), background 0.28s var(--ease);
}

.hero__explore-arrow svg {
  width: 15px;
  height: 15px;
}

.hero__explore:hover .hero__explore-arrow {
  background: var(--violet-deep);
  transform: rotate(45deg);
}

/* Label crossfades when the deck advances — JS toggles .is-swapping. */
.hero__explore-label {
  transition: opacity 0.2s var(--ease);
}

.hero__explore.is-swapping .hero__explore-label {
  opacity: 0;
}

/* ── Deck controls, under the stack. */
/* The strip beneath the deck inside the same column. */
.hero__deck-ui {
  position: absolute;
  z-index: 4;
  left: 50%;
  bottom: 2px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 12px;
}

.hero__deck-btn {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1.5px solid var(--line-dark);
  border-radius: 50%;
  color: var(--on-dark-2);
  transition: background 0.2s var(--ease), color 0.2s var(--ease),
    border-color 0.2s var(--ease), transform 0.2s var(--ease);
}

.hero__deck-btn svg {
  width: 15px;
  height: 15px;
}

.hero__deck-btn:hover {
  background: #fff;
  border-color: #fff;
  color: var(--void);
  transform: scale(1.08);
}

.hero__deck-count {
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  font-variant-numeric: tabular-nums;
  color: var(--on-dark);
  min-width: 52px;
  text-align: center;
}

.hero__deck-count span {
  color: var(--on-dark-3);
  margin-inline: 2px;
}

/* ── Corner furniture: dek left column, tag list right column. Both align to the
      BOTTOM of the row, so they sit level with the deck's lower edge — the deck
      column is the tallest item, so it defines that baseline. */
.hero__dek {
  grid-column: 1;
  align-self: end;
  max-width: 30ch;
  font-size: 13px;
  line-height: 1.55;
  color: var(--on-dark-2);
}

.hero__dek strong {
  color: var(--lime);
  font-weight: 700;
  text-transform: uppercase;
}

/* Lime italic, per the reference. */
.hero__tags {
  grid-column: 3;
  align-self: end;
  justify-self: end;
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: right;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 600;
  font-style: italic;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--lime);
}

.hero__tags a {
  transition: opacity 0.18s var(--ease);
  opacity: 0.78;
}

.hero__tags a:hover {
  opacity: 1;
}

/* ═══ 9. FRONT PAGE — FRESHLY PUBLISHED ═══════════════════════════════════ */
/* Heading left, kicker + pill right, then a 4-up card row. */
.fresh__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 28px;
  margin-bottom: clamp(22px, 3vw, 42px);
}

.fresh__title {
  font-size: clamp(34px, 6vw, 86px);
  line-height: 0.9;
  letter-spacing: -0.035em;
  max-width: 12ch;
}

.fresh__aside {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 16px;
  flex-shrink: 0;
  text-align: right;
}

/* ═══ 10. FRONT PAGE — FEATURED MOSAIC ════════════════════════════════════ */
/* Violet-filled lead card with an arc-masked photo, beside three thin rows. */
.mosaic {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: var(--gap);
  align-items: start;
}

/* ── Lead: the violet card */
.mfeat {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: var(--r);
  overflow: hidden;
  background: var(--violet);
  color: #fff;
  min-height: clamp(340px, 40vw, 520px);
  transition: transform 0.3s var(--ease);
}

.mfeat:hover {
  transform: translateY(-3px);
}

/* The arc mask — the reference's one decorative shape. A radial gradient in a
   mask is used rather than clip-path because it keeps the curve resolution-
   independent and needs no per-breakpoint path rewriting. The circle is wider
   than the box so only its lower-left sweep crosses the image. */
/* The image is a plain <img> in normal flow, so the masked plate needs its own
   intrinsic height — `flex: 1 1 auto` alone collapses to zero because the flex
   base size resolves from a child that is itself sized to 100% of the parent.
   An aspect-ratio floor gives it that height, and flex-grow still lets it take
   up any slack left by the body block. */
.mfeat__media {
  position: relative;
  display: block;
  flex: 1 1 auto;
  aspect-ratio: 4 / 3;
  min-height: 220px;
  overflow: hidden;
  -webkit-mask-image: radial-gradient(
    140% 118% at 62% -22%,
    #000 99.5%,
    transparent 100%
  );
  mask-image: radial-gradient(
    140% 118% at 62% -22%,
    #000 99.5%,
    transparent 100%
  );
}

.mfeat__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s var(--ease);
}

.mfeat:hover .mfeat__img {
  transform: scale(1.03);
}

/* Chips sit above the stretched headline link so they stay independently
   clickable — the hit layer covers the whole card, so anything meant to remain
   its own link has to outrank it. */
.mfeat__chips {
  position: absolute;
  z-index: 3;
  top: 14px;
  left: 14px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* The headline's link, stretched over the whole card. This is what makes the
   card clickable without an invalid nested-anchor wrapper. */
.mfeat__hit::after,
.highlight__hit::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

.mfeat__body {
  position: relative;
  z-index: 2;
  padding: clamp(16px, 2vw, 26px);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.mfeat__title {
  font-size: clamp(20px, 2.4vw, 34px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: #fff;
}

.mfeat__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 18px;
}

/* On the violet fill the standard meta grey vanishes — force the light ramp. */
.mfeat .metarow,
.mfeat .byline {
  color: rgba(255, 255, 255, 0.78);
}

.mfeat .byline__name {
  color: #fff;
}

/* ── Side column: three thin rows */
/* Equal-height rows spanning the lead card's full height, so both mosaic
   columns bottom out together as they do in the reference. */
.mosaic__side {
  display: grid;
  grid-auto-rows: 1fr;
  gap: var(--gap);
  height: 100%;
}

.mrow {
  display: grid;
  grid-template-columns: minmax(0, 152px) minmax(0, 1fr);
  gap: 14px;
  align-items: center;
  min-height: 0;
}

/* Fills the row's stretched height rather than imposing its own ratio, which
   would fight .mosaic__side's equal-height rows. The aspect-ratio is only a
   floor for the case where the row has no height to inherit (mobile, where the
   side column is no longer matched to the lead card). */
.mrow__media {
  position: relative;
  border-radius: var(--r);
  overflow: hidden;
  background: var(--paper-2);
  height: 100%;
  min-height: 96px;
}

.mrow__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s var(--ease);
}

.mrow:hover .mrow__img {
  transform: scale(1.04);
}

.mrow__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  justify-content: center;
  min-width: 0;
}

.mrow__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.mrow__title {
  font-size: clamp(13px, 1vw, 15px);
  line-height: 1.24;
  letter-spacing: -0.005em;
}

.mrow__title a {
  transition: color 0.18s var(--ease);
}

.mrow__title a:hover {
  color: var(--violet);
}

/* The "READ NOW ›" pill on each row. */
.mrow__more {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  align-self: flex-start;
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
}

.mrow__more .arrow-badge {
  width: 22px;
  height: 22px;
}

.mrow__more:hover {
  color: var(--violet);
}

.mrow__more:hover .arrow-badge {
  background: var(--violet);
}

/* ═══ 11. FRONT PAGE — CATEGORY TILES ═════════════════════════════════════ */
/* Four photo tiles on the mist band, category name burned into bottom-left.
   The strip IS a rail — the element carries .rail__track too, so flex owns the
   layout and each tile sizes from .rail__item's flex-basis. Declaring grid here
   as well would just lose to .rail__track on source order and mislead. */
.tiles {
  align-items: stretch;
}

.tile {
  position: relative;
  display: block;
  border-radius: var(--r);
  overflow: hidden;
  aspect-ratio: 3 / 4;
  background: var(--void-2);
  transition: transform 0.3s var(--ease);
}

.tile:hover {
  transform: translateY(-4px);
}

.tile__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s var(--ease);
}

.tile:hover .tile__img {
  transform: scale(1.05);
}

/* Scrim across the lower third so the label reads without dimming the whole
   photograph. The floor is opaque enough to carry white type over a LIGHT
   image — several of these covers are pale charts and screenshots, not dark
   photos, and a lighter gradient left the label unreadable on those. */
.tile__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(11, 11, 13, 0.94) 0%,
    rgba(11, 11, 13, 0.72) 18%,
    rgba(11, 11, 13, 0.28) 42%,
    transparent 68%
  );
}

.tile__label {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 13px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.tile__name {
  font-family: var(--font-display);
  font-size: clamp(13px, 1.1vw, 16px);
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #fff;
}

.tile__count {
  font-family: var(--font-meta);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.7);
  font-variant-numeric: tabular-nums;
}

/* A coloured hairline at the tile's top edge ties it to its chip colour. */
.tile::before {
  content: "";
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: var(--chip-bg, var(--violet));
}

/* ═══ 12. FRONT PAGE — LATEST RAIL ════════════════════════════════════════ */
/* A real scroll container, not a clipped box with a translated track. This is what
   gives the rail touch swipe, kinetic momentum, trackpad gestures and keyboard
   scrolling for free — see the "Horizontal rails" block in main.js. The scrollbar
   is hidden because the arrows and counter already communicate position. */
.rail__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  /* Keeps a vertical page scroll from being hijacked by the horizontal rail. */
  touch-action: pan-x pan-y;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.rail__viewport::-webkit-scrollbar {
  display: none;
}

/* The viewport is focusable so keyboard users can reach the rail itself rather
   than only the links inside it. */
.rail__viewport:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 4px;
  border-radius: var(--r);
}

.is-mist .rail__viewport:focus-visible {
  outline-color: var(--violet);
}

.rail__track {
  display: flex;
  gap: var(--gap);
}

/* Four cards per view on desktop, accounting for the gaps between them. Each card
   is also a snap point, so a swipe always settles on a card edge rather than
   mid-card. */
.rail__item {
  flex: 0 0 calc((100% - var(--gap) * 3) / 4);
  min-width: 0;
  scroll-snap-align: start;
}

/* Snapping fights programmatic smooth-scrolling under reduced motion, and the
   momentum animation is itself motion — so opt out entirely there. */
@media (prefers-reduced-motion: reduce) {
  .rail__viewport {
    scroll-snap-type: none;
    scroll-behavior: auto;
  }
}

/* ═══ 13. FRONT PAGE — POPULAR (INVERTED) ═════════════════════════════════ */
.popular__lead {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 44px);
  align-items: center;
  margin-bottom: clamp(28px, 3.6vw, 52px);
}

.popular__media {
  position: relative;
  display: block;
  border-radius: var(--r);
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--void-2);
}

.popular__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s var(--ease);
}

.popular__media:hover .popular__img {
  transform: scale(1.03);
}

.popular__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-items: flex-start;
}

.popular__title {
  font-size: clamp(22px, 3vw, 44px);
  line-height: 1.02;
  letter-spacing: -0.028em;
}

.popular__title a {
  transition: color 0.18s var(--ease);
}

.popular__title a:hover {
  color: var(--lime);
}

.popular__excerpt {
  font-size: 15px;
  line-height: 1.6;
  color: var(--on-dark-2);
  max-width: 52ch;
}

.popular__row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap);
}

/* Compact row: small square thumb, headline, meta. */
.mini {
  display: grid;
  grid-template-columns: 86px minmax(0, 1fr);
  gap: 14px;
  align-items: center;
  padding-top: 20px;
  border-top: 1px solid var(--line-dark);
}

.mini__media {
  display: block;
  border-radius: var(--r-sm);
  overflow: hidden;
  aspect-ratio: 1 / 1;
  background: var(--void-2);
}

.mini__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s var(--ease);
}

.mini__media:hover .mini__img {
  transform: scale(1.05);
}

.mini__body {
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 0;
}

.mini__title {
  font-size: 13px;
  line-height: 1.24;
  letter-spacing: -0.005em;
}

.mini__title a {
  transition: color 0.18s var(--ease);
}

.mini__title a:hover {
  color: var(--lime);
}

/* ═══ 14. FRONT PAGE — HIGHLIGHT ══════════════════════════════════════════ */
.highlight__banner {
  position: relative;
  display: block;
  border-radius: var(--r);
  overflow: hidden;
  aspect-ratio: 21 / 9;
  margin-bottom: var(--gap);
  background: var(--void-2);
}

.highlight__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease);
}

.highlight__banner:hover .highlight__img {
  transform: scale(1.03);
}

.highlight__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(11, 11, 13, 0.88) 0%,
    rgba(11, 11, 13, 0.4) 40%,
    rgba(11, 11, 13, 0.05) 72%
  );
}

.highlight__body {
  position: absolute;
  z-index: 2;
  left: clamp(18px, 3vw, 44px);
  right: clamp(18px, 3vw, 44px);
  bottom: clamp(18px, 2.6vw, 38px);
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
  /* Full width rather than a 46ch measure: the banner is 21:9, so a narrow column
     left most of the scrim empty and the headline looked undersized against it.
     The title's own text-wrap: balance keeps the line breaks even. */
  max-width: 100%;
}

.highlight__title {
  font-size: clamp(20px, 3.2vw, 46px);
  line-height: 1.02;
  letter-spacing: -0.03em;
  color: #fff;
}

.highlight__excerpt {
  font-size: 14px;
  line-height: 1.55;
  color: var(--on-dark-2);
  /* Reads as a paragraph but is a span inside an <a>, so it needs to be told
     to occupy its own line box. */
  display: block;
  /* The body is full-width now, but running prose the length of a 21:9 banner is
     an unreadable measure — the headline gets the width, the dek does not. */
  max-width: 62ch;
}

/* ═══ 15. CARDS (SHARED) ══════════════════════════════════════════════════ */
/* The workhorse card: flush media on top, tight uppercase headline, meta row. */
.card {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Portrait, not landscape: the reference's card row is taller than wide, which
   also gives a 4-up grid more presence against the oversized section headings.
   Shared by Freshly Published, Highlight's 4-up, Related Coverage, the archive
   grid and 404 — change it here and all of them follow. */
.card__media {
  position: relative;
  display: block;
  border-radius: var(--r);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background: var(--paper-2);
  margin-bottom: 13px;
}

.card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.45s var(--ease);
}

.card:hover .card__img {
  transform: scale(1.04);
}

/* The media wrapper span inside each link. It carries no styling of its own —
   it exists so the empty-state class has somewhere to land without the
   templates having to branch on the anchor's own class list. */
.card__media-clip,
.hcard__media-clip {
  display: block;
  width: 100%;
  height: 100%;
}

/* Posts with no artwork get a deliberate violet-wash plate rather than an empty
   hole in the grid. */
.card__media--empty {
  background: var(--violet-wash);
  position: relative;
}

.card__media--empty::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
      120% 120% at 20% 0%,
      rgba(91, 43, 232, 0.16),
      transparent 60%
    ),
    repeating-linear-gradient(
      -45deg,
      transparent 0 9px,
      rgba(91, 43, 232, 0.07) 9px 18px
    );
}

.is-inverted .card__media--empty {
  background: var(--void-2);
}

/* Chips parked on the media's top-left corner. */
.card__media-chips {
  position: absolute;
  z-index: 2;
  top: 10px;
  left: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.card__body {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: 0;
}

/* ── Headline clamp (shared) ─────────────────────────────────────────────
   Every post title tops out at three lines and ellipsises, so one long
   headline can't blow out a card's height and break the row's alignment.

   The clamp lives on the TITLE element, not the <a> inside it: -webkit-box
   only ellipsises the box that owns it, and the anchor is an inline child,
   so clamping the title clamps the link text with it. Nothing is truncated
   server-side, so the full headline stays in the markup for SEO and a11y. */
.card__title,
.hcard__title,
.mfeat__title,
.mrow__title,
.popular__title,
.mini__title,
.highlight__title,
.side-item__title {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card__title {
  font-size: clamp(14px, 1.05vw, 17px);
  line-height: 1.25;
  letter-spacing: -0.005em;
}

.card__title a {
  transition: color 0.18s var(--ease);
}

.card__title a:hover {
  color: var(--violet);
}

.is-inverted .card__title a:hover {
  color: var(--lime);
}

.card__excerpt {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-2);
  /* Two lines, then ellipsis — keeps every card in a row the same height
     without hard-cropping the text server-side. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.is-inverted .card__excerpt {
  color: var(--on-dark-2);
}

.card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 14px;
  margin-top: 2px;
}

/* Chip and byline on one line: chip left, byline right. `min-width: 0` on the
   byline lets its name ellipsis instead of forcing the row wider than the card,
   and the chips block refuses to shrink so a long author name never squeezes the
   category label. */
.card__meta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 2px;
  min-width: 0;
}

.card__meta-row .card__chips {
  flex: 0 0 auto;
}

.card__meta-row .card__foot {
  margin-top: 0;
  min-width: 0;
  justify-content: flex-end;
}

/* Horizontal card used by the rail — same anatomy, vertical stack. */
.hcard {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Matches .card__media's portrait ratio — .hcard is the same card shape in the
   Latest News rail, and leaving the two different made otherwise-identical rows
   sit at different heights. */
.hcard__media {
  position: relative;
  display: block;
  border-radius: var(--r);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background: var(--paper-2);
  margin-bottom: 13px;
}

.hcard__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.45s var(--ease);
}

.hcard:hover .hcard__img {
  transform: scale(1.04);
}

.hcard__body {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: 0;
}

.hcard__title {
  font-size: clamp(14px, 1.05vw, 17px);
  line-height: 1.25;
  letter-spacing: -0.005em;
}

.hcard__title a:hover {
  color: var(--violet);
}

.hcard__excerpt {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ═══ 16. NEWSLETTER CTA BAND (LIME) ══════════════════════════════════════ */
/* The shout. One lime region per page, and this owns it. */
.cta {
  position: relative;
  background: var(--lime);
  color: var(--void);
  /* Tighter than the standard --pad: the illustration is a full-height column now
     and supplies plenty of vertical mass on its own, so the section's own padding
     only needs to keep the copy off the band's edges. */
  padding-block: clamp(40px, 4.5vw, 68px);
  overflow: hidden;
}

/* Decorative ground: halftone dots + grid rules.

   The hero's treatment cannot be reused here. On black, white lines at ~6% read
   as structure; on a saturated yellow, white is nearly invisible and dark lines
   at the same opacity look like dirt. So this uses BLACK marks kept very sparse,
   and leans on a print-halftone dot field — the classic way to give a flat spot
   colour texture, and on-brand for a newsmagazine.

   Sits below the content (.cta__grid is z-index 1) and above the flat yellow. */
.cta::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;

  /* Halftone dots only. The grid lines are a separate layer (.cta__grid::before)
     because the two need OPPOSITE masks: dots must clear away behind the copy,
     while the lines read best precisely there. One shared mask cannot do both. */
  background-image: radial-gradient(
    circle at center,
    rgba(11, 11, 13, 0.17) 1.6px,
    transparent 1.7px
  );
  background-size: var(--cta-dot, 15px) var(--cta-dot, 15px);

  /* Densest at the band's edges, clearing behind the headline and form so the
     texture never competes with the copy. */
  -webkit-mask-image: radial-gradient(
    92% 78% at 42% 50%,
    transparent 34%,
    rgba(0, 0, 0, 0.55) 66%,
    #000 100%
  );
  mask-image: radial-gradient(
    92% 78% at 42% 50%,
    transparent 34%,
    rgba(0, 0, 0, 0.55) 66%,
    #000 100%
  );
}

/* Grid rules, on their own layer so they can carry a gentler mask than the dots.
   Black on yellow needs more stroke weight than white on black to register, but
   far fewer lines — at the hero's density it would read as a table. */
/* Bounded to the band, not the grid, so the rules run its full height rather than
   stopping at the content box. .cta has overflow:hidden, so the generous vertical
   inset is clipped rather than spilling. */
.cta__grid::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -50vh;
  bottom: -50vh;
  z-index: -1;
  pointer-events: none;

  background-image:
    repeating-linear-gradient(
      to right,
      rgba(11, 11, 13, 0.09) 0 1px,
      transparent 1px calc(var(--cta-grid, 104px) * 2)
    ),
    repeating-linear-gradient(
      to bottom,
      rgba(11, 11, 13, 0.06) 0 1px,
      transparent 1px calc(var(--cta-grid, 104px) * 2)
    );
  background-size:
    calc(var(--cta-grid, 104px) * 2) 100%,
    100% calc(var(--cta-grid, 104px) * 2);

  /* Fades at the left and right edges only, so the rules stay visible through the
     middle where the dots have cleared. */
  -webkit-mask-image: linear-gradient(
    to right,
    transparent, #000 18%, #000 82%, transparent
  );
  mask-image: linear-gradient(
    to right,
    transparent, #000 18%, #000 82%, transparent
  );
}

/* Fine grain, as on the hero. `multiply` rather than soft-light: on a light
   ground soft-light barely registers, whereas multiply gives the yellow a
   printed-ink tooth. */
.cta::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  mix-blend-mode: multiply;
  opacity: 0.14;
}

/* The right column is the taller of the two now that the illustration sizes from
   its own width, so the left column is CENTRED against it rather than pinned to
   the top — otherwise the slack simply reappears as a void beneath the form.
   Whichever column is shorter, the leftover space is split above and below it
   instead of collecting in one visible block. */
/* Copy in the left track, illustration in the right. The right track is a spacer
   — the artwork is positioned against the band rather than placed in the grid, so
   it can bleed past the container's gutter without affecting layout. */
/* Copy left, illustration right — both real tracks now. `align-items: center`
   matters: the art is usually the taller item, and centring the copy against it
   stops the leftover height collecting as a void under the shorter column. */
.cta__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: center;
}

.cta__lede {
  grid-column: 1;
  text-align: left;
}

/* Benefits sit under the form in the same column. */
.cta__benefits {
  margin-top: clamp(20px, 2.6vw, 34px);
}

.cta__title {
  font-size: clamp(30px, 4.6vw, 66px);
  line-height: 0.94;
  letter-spacing: -0.032em;
  color: var(--void);
  max-width: 18ch;
}

.cta__sub {
  margin-top: 16px;
  /* Separates the dek from the signup row beneath it. */
  margin-bottom: 16px;
  font-size: 15px;
  line-height: 1.55;
  color: rgba(11, 11, 13, 0.72);
  max-width: 44ch;
}

.cta__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: clamp(18px, 2.4vw, 28px);
}

/* .cta__squiggle is retired. The reference's hand-drawn arrow pointed from the
   headline across to a benefits column; once the benefits moved under the form
   and the illustration took the right track, it had nothing left to connect and
   overlapped both the headline and the artwork.

   .cta__side is retired too — the benefits live in .cta__lede now. */

/* The bundled illustration. Sits directly on the lime with no card or frame — it
   is a transparent PNG whose black linework and pink/green/yellow palette are
   drawn for exactly this ground, so boxing it would only fight the artwork.

   `contain` rather than `cover`: it is a square composition with the subject
   running to its edges, and cropping it would cut the hands and starbursts off.
   flex:1 lets it absorb the slack the benefit list leaves, which is the whole
   point — that space was a 264px void. */
/* The illustration is SQUARE, so its drawn size is governed by whichever of the
   box's dimensions is smaller. Sizing it by width and letting `contain` letterbox
   the rest painted it at only 46% of the column with ~293px of dead space split
   either side — which is what made it read as a small floating object rather than
   part of the composition.

   So: size it by the available HEIGHT (the square's limiting dimension), cap it at
   the column width, and centre it. `aspect-ratio` makes the element itself square
   so there is no letterbox left to centre inside. */
/* Background art, not a layout item. Absolutely positioned and bleeding off the
   band's right edge, so it costs the grid no space and cannot dictate the band's
   height — earlier attempts to place it IN the right column either left a void
   beside it or grew the band to ~950px.

   Anchored bottom-right and allowed to overflow: the band clips it, which is what
   makes it read as part of the ground rather than a picture sitting on it. */
/* A real grid item in the right track, so the two columns share the row rather
   than the artwork floating over it as a background layer.

   It fills its track's width and is free to set its own height — the band grows
   to fit, which is the trade for artwork at full size. The left column is
   vertically centred against it (see .cta__grid), so no void collects beneath
   the shorter of the two. */
.cta__art {
  grid-column: 2;
  justify-self: end;
  width: 100%;
  /* A ceiling, so the square does not become absurd on very wide screens. */
  max-width: 620px;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  pointer-events: none;
}

/* Numbered benefit list. */
.cta__benefits {
  display: flex;
  flex-direction: column;
  gap: 0;
  flex-shrink: 0;
}

.cta__benefit {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 14px;
  padding-block: 18px;
  border-top: 1.5px solid rgba(11, 11, 13, 0.18);
}

.cta__benefit:last-child {
  border-bottom: 1.5px solid rgba(11, 11, 13, 0.18);
}

.cta__benefit-num {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--void);
}

.cta__benefit-title {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 800;
  letter-spacing: -0.005em;
  text-transform: uppercase;
  line-height: 1.25;
  color: var(--void);
}

.cta__benefit-text {
  margin-top: 6px;
  font-size: 13px;
  line-height: 1.55;
  color: rgba(11, 11, 13, 0.68);
}

.cta__note {
  margin-top: 14px;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(11, 11, 13, 0.55);
}

/* The CTA's own wordmark, sitting above the headline. */
/* .cta__mark and .cta__mark-text are retired — the band's wordmark was removed;
   the headline carries the section on its own. */

/* ═══ 17. FOOTER ══════════════════════════════════════════════════════════ */
.site-footer {
  background: var(--void);
  color: var(--on-dark);
  padding-block: clamp(44px, 5.5vw, 78px) 28px;
}

/* Brand row: wordmark and mission left, return-to-top right. */
.footer-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 28px;
  padding-bottom: clamp(26px, 3.4vw, 46px);
}

.footer-brand__logo {
  display: inline-block;
  margin-bottom: 16px;
}

/* The uploaded wordmark is 600x140, so height is the constraint that matters;
   width follows. Fluid rather than fixed so it scales down on narrow footers
   instead of forcing the brand column wider. */
.footer-brand__logo img {
  max-height: clamp(44px, 5vw, 64px);
  width: auto;
  object-fit: contain;
  filter: invert(1) brightness(1.9);
}

.footer-brand__name {
  display: inline-block;
  margin-bottom: 16px;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

.footer-brand__desc {
  font-size: 14px;
  line-height: 1.6;
  color: var(--on-dark-2);
  max-width: 40ch;
}

/* ── Category wall.
      The footer's centrepiece: every desk as oversized display type on a ruled
      grid, so the site's primary navigation reads as primary. Replaces a 14px
      single-column list that buried nine categories while half the footer sat
      empty. Each row carries its own --cat-bg/--cat-fg from st_term_color(), so a
      category is the same colour here as its chips are everywhere else. */
.footer-wall__heading {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--lime);
  margin-bottom: 18px;
}

/* Three columns, filled COLUMN-first so the names read down each column and any
   short final column sits on the right rather than leaving a gap mid-row.
   `grid-auto-flow: column` needs an explicit row count to know where to wrap —
   --wall-rows is set per breakpoint below.
   Capped at three rather than auto-fit: auto-fit gave four columns at 1440px,
   which left nine categories in a ragged 3-2-2-2 with one orphan. */
.footer-wall__grid {
  --wall-rows: 3;

  display: grid;
  grid-auto-flow: column;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: repeat(var(--wall-rows), auto);
  gap: 0 clamp(20px, 3vw, 52px);
  border-top: 1px solid var(--line-dark);
}

.footer-wall__item {
  border-bottom: 1px solid var(--line-dark);
}

/* Fills the whole row so the entire hairline band is the hit target, not just
   the text. */
.footer-wall__link {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: clamp(12px, 1.5vw, 18px) 4px;
  overflow: hidden;
  isolation: isolate;
}

.footer-wall__name {
  position: relative;
  z-index: 2;
  font-family: var(--font-display);
  font-size: clamp(19px, 2.1vw, 32px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.028em;
  text-transform: uppercase;
  color: var(--on-dark);
  transition: color 0.32s var(--ease), transform 0.32s var(--ease);
}

/* Post count, set small against the oversized name. */
.footer-wall__count {
  position: relative;
  z-index: 2;
  flex-shrink: 0;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  font-variant-numeric: tabular-nums;
  color: var(--on-dark-3);
  transition: color 0.32s var(--ease);
}

/* The colour sweep. Scales from the left edge on hover, sitting behind the type
   via z-index rather than opacity so the text stays fully opaque throughout. */
.footer-wall__fill {
  position: absolute;
  z-index: 1;
  inset: 0;
  background: var(--cat-bg, var(--violet));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.42s var(--ease);
}

/* Keyed off the ITEM's hover as well as the link's own. The link is inline-level
   inside the li, so hovering the row's gutter would otherwise leave the fill
   inert even though the pointer is clearly over the row. */
.footer-wall__item:hover .footer-wall__fill,
.footer-wall__link:focus-visible .footer-wall__fill {
  transform: scaleX(1);
}

/* On the filled row both the name and the count take the palette's paired
   foreground, which is what guarantees contrast on lime and amber as well as on
   violet and red. */
.footer-wall__item:hover .footer-wall__name,
.footer-wall__link:focus-visible .footer-wall__name {
  color: var(--cat-fg, #fff);
  transform: translateX(10px);
}

.footer-wall__item:hover .footer-wall__count,
.footer-wall__link:focus-visible .footer-wall__count {
  color: var(--cat-fg, #fff);
}

/* Back-to-top tile, now a plain bordered pill. */
.footer-top-link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 11px 11px 11px 20px;
  border: 1.5px solid var(--line-dark);
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: background 0.2s var(--ease), color 0.2s var(--ease),
    border-color 0.2s var(--ease);
}

.footer-top-link .arrow-badge {
  width: 28px;
  height: 28px;
  background: #fff;
  color: var(--void);
  /* Point the chevron up — this scrolls to the top. */
  transform: rotate(-90deg);
}

.footer-top-link:hover {
  background: #fff;
  border-color: #fff;
  color: var(--void);
}

.footer-top-link:hover .arrow-badge {
  background: var(--violet);
  color: #fff;
  transform: rotate(-90deg) translateX(2px);
}

/* No top border: the wall's last row already ends on a hairline, and a second
   one directly beneath it read as a double rule. */
.footer-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px 24px;
  padding-top: clamp(22px, 2.6vw, 34px);
}

.footer-copy,
.footer-legal {
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--on-dark-3);
}

.footer-legal {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 20px;
}

/* The footer-2 menu renders inline here rather than as its own column. */
.footer-legal__menu {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 20px;
}

.footer-legal a {
  transition: color 0.18s var(--ease);
}

.footer-legal a:hover {
  color: #fff;
}

/* ═══ 18. ARCHIVE ═════════════════════════════════════════════════════════ */
/* Category header: black band, oversized term name, the term's own colour as a
   rule beneath it. */
.archive-hero {
  position: relative;
  background: var(--void);
  color: var(--on-dark);
  padding-block: clamp(38px, 5vw, 74px);
  overflow: hidden;
}

/* The band was built for decorative layers (overflow:hidden, z-index:2 on the
   inner) that were never added, so it read as a flat black void. This is the
   hero's treatment at lower intensity — the archive header is furniture, not
   the front page's shout, so the grid is finer and fades harder.

   The term colour tints the wash instead of only striping the 5px rule, which
   is what made the raw hex read as an acid stripe on black: at 0.1 alpha it
   becomes an atmosphere the title sits in rather than a competing mark. */
.archive-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;

  background-image:
    radial-gradient(
      68% 120% at 88% 0%,
      color-mix(in srgb, var(--chip-bg, var(--violet)) 26%, transparent) 0%,
      transparent 62%
    ),
    repeating-linear-gradient(
      to right,
      rgba(255, 255, 255, 0.05) 0 1px,
      transparent 1px 72px
    ),
    repeating-linear-gradient(
      to bottom,
      rgba(255, 255, 255, 0.04) 0 1px,
      transparent 1px 72px
    );
  background-size: 100% 100%, 72px 100%, 100% 72px;

  /* Grid dissolves toward the right and bottom so it never boxes the copy in. */
  -webkit-mask-image: radial-gradient(
    130% 118% at 8% 22%, #000 26%, rgba(0, 0, 0, 0.45) 68%, transparent 100%
  );
  mask-image: radial-gradient(
    130% 118% at 8% 22%, #000 26%, rgba(0, 0, 0, 0.45) 68%, transparent 100%
  );
}

/* Same grain as the hero and CTA, so all three dark bands share a surface. */
.archive-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.4'/%3E%3C/svg%3E");
  mix-blend-mode: soft-light;
  opacity: 0.4;
}

.archive-hero__inner {
  position: relative;
  z-index: 2;
  max-width: 60ch;
}

.archive-hero__title {
  margin-top: 14px;
  font-size: clamp(36px, 6.4vw, 96px);
  line-height: 0.92;
  letter-spacing: -0.035em;
  color: #fff;
}

/* The term-coloured rule under the title, tinted from the chip palette.

   Fades to transparent rather than stopping dead: a flat 96px bar of raw
   category hex was the single most acid mark on the band. Wider and thinner
   so it reads as a printed rule under a masthead, not a progress bar. */
.archive-hero__rule {
  display: block;
  width: 172px;
  height: 3px;
  margin-top: 22px;
  border-radius: var(--r-pill);
  background: linear-gradient(
    to right,
    var(--chip-bg, var(--violet)) 0%,
    color-mix(in srgb, var(--chip-bg, var(--violet)) 30%, transparent) 68%,
    transparent 100%
  );
}

.archive-hero__desc {
  margin-top: 18px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--on-dark-2);
  max-width: 56ch;
}

/* The eyebrow defaults to --violet, which on a category archive meant a purple
   "DESK" sitting beside a blue rule — a third unrelated hue. Tying it to the
   term colour makes the dot, the label and the rule one family. */
.archive-hero .eyebrow {
  /* Fallback first: browsers without color-mix() drop the second declaration as
     invalid and keep the flat term colour, which is still on-family. */
  color: var(--chip-bg, var(--violet));
  color: color-mix(in srgb, var(--chip-bg, var(--violet)) 62%, #fff);
}

/* Given room so the band's lower half isn't dead space. */
.archive-hero__count {
  margin-top: clamp(20px, 2.6vw, 30px);
  max-width: 30ch;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--on-dark-3);
}

/* Was --lime. Yellow here fought the term colour on the rule directly above —
   two saturated hues on black, neither winning. The count is furniture, so it
   gets weight and brightness instead of a second accent. One accent per band. */
.archive-hero__count strong {
  color: #fff;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Lead row on the first page of an archive. */
.archive-lead {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 44px);
  align-items: center;
  padding-bottom: clamp(28px, 3.4vw, 48px);
  margin-bottom: clamp(28px, 3.4vw, 48px);
  border-bottom: 1px solid var(--line);
}

.archive-lead__media {
  position: relative;
  display: block;
  border-radius: var(--r);
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--paper-2);
}

.archive-lead__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s var(--ease);
}

.archive-lead__media:hover .archive-lead__img {
  transform: scale(1.03);
}

.archive-lead__body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: flex-start;
}

.archive-lead__title {
  font-size: clamp(22px, 3.2vw, 46px);
  line-height: 1.01;
  letter-spacing: -0.03em;
}

.archive-lead__title a:hover {
  color: var(--violet);
}

.archive-lead__excerpt {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 54ch;
}

.no-results {
  padding-block: clamp(50px, 8vw, 110px);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
}

.no-results p {
  font-family: var(--font-display);
  font-size: clamp(18px, 2.2vw, 28px);
  font-weight: 700;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  max-width: 30ch;
}

/* ═══ 19. SINGLE POST ═════════════════════════════════════════════════════ */
.read-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 200;
  height: 3px;
  background: transparent;
  pointer-events: none;
}

.read-progress__fill {
  height: 100%;
  width: 0;
  background: var(--lime);
  transition: width 0.1s linear;
}

.single-main {
  padding-block: clamp(28px, 4vw, 52px) var(--pad);
}

.single-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: clamp(28px, 4vw, 68px);
  align-items: start;
}

.article-header {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: clamp(22px, 3vw, 34px);
}

.article-header__title {
  font-size: clamp(28px, 4.4vw, 60px);
  line-height: 0.98;
  letter-spacing: -0.032em;
}

.article-header__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px 26px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.article-hero {
  margin-bottom: clamp(22px, 3vw, 36px);
}

.article-hero__img {
  width: 100%;
  border-radius: var(--r);
  object-fit: cover;
}

.article-hero__caption {
  margin-top: 10px;
  font-family: var(--font-meta);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-3);
}

/* ── Article body. The one place on the site running sentence-case, positively
      leaded type at a comfortable measure. */
.article-content {
  font-size: 18px;
  line-height: 1.75;
  color: #1c1c22;
  max-width: 68ch;
}

.article-content > * + * {
  margin-top: 1.15em;
}

.article-content h2,
.article-content h3,
.article-content h4 {
  margin-top: 1.9em;
  margin-bottom: 0.5em;
  letter-spacing: -0.02em;
}

.article-content h2 {
  font-size: clamp(21px, 2.2vw, 30px);
}

.article-content h3 {
  font-size: clamp(18px, 1.7vw, 23px);
}

.article-content h4 {
  font-size: 17px;
}

.article-content a {
  color: var(--violet);
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 2.5px;
  transition: color 0.16s var(--ease);
}

.article-content a:hover {
  color: var(--violet-deep);
}

.article-content ul,
.article-content ol {
  padding-left: 1.4em;
  list-style: revert;
}

.article-content li + li {
  margin-top: 0.5em;
}

.article-content img,
.article-content figure img {
  border-radius: var(--r);
}

.article-content figcaption {
  margin-top: 9px;
  font-family: var(--font-meta);
  font-size: 12px;
  color: var(--ink-3);
}

.article-content blockquote {
  margin: 1.8em 0;
  padding: 4px 0 4px 24px;
  border-left: 4px solid var(--violet);
  font-family: var(--font-display);
  font-size: clamp(19px, 1.9vw, 25px);
  font-weight: 700;
  line-height: 1.28;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.article-content blockquote p + p {
  margin-top: 0.6em;
}

.article-content pre {
  padding: 18px 20px;
  border-radius: var(--r);
  background: var(--void);
  color: #e8e8ef;
  font-family: var(--font-code);
  font-size: 13.5px;
  line-height: 1.6;
  overflow-x: auto;
}

.article-content code {
  font-family: var(--font-code);
  font-size: 0.9em;
}

.article-content :not(pre) > code {
  padding: 2px 6px;
  border-radius: var(--r-sm);
  background: var(--violet-wash);
  color: var(--violet-deep);
}

.article-content hr {
  margin: 2.2em 0;
  border: 0;
  border-top: 1px solid var(--line);
}

.article-content table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14.5px;
  /* Wide tables scroll inside their own box rather than pushing the page. */
  display: block;
  overflow-x: auto;
}

.article-content th,
.article-content td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  text-align: left;
}

.article-content th {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

/* ── End-of-article marker ───────────────────────────────────────────────
   The print convention: a filled square closing the piece. Without it the copy
   simply stops and the author card reads as more article rather than furniture.

   Matched to .article-content's 68ch measure rather than the full column, so it
   closes the text block it belongs to instead of floating in the layout. The
   hairline runs to the right of the mark, letting the square sit at the text's
   left edge where the eye returns on the final line. */
.article-end {
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: 68ch;
  margin-top: clamp(26px, 3vw, 38px);
}

.article-end__mark {
  flex-shrink: 0;
  width: 11px;
  height: 11px;
  border-radius: 2px;
  background: var(--cat-bg, var(--violet));
}

/* The rule fades out rather than ending flush, so the block closes softly
   instead of reading as another border. */
.article-end::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--line-2), transparent);
}

/* ── Tags, nav, related */
.article-tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: clamp(28px, 3.4vw, 42px);
  padding-top: 22px;
  border-top: 1px solid var(--line);
}

.article-tags__label {
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-right: 4px;
}

.article-tags .chip {
  --chip-bg: var(--violet-wash);
  --chip-fg: var(--violet-deep);
}

.article-nav {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap);
  margin-top: clamp(28px, 3.4vw, 44px);
}

.article-nav__item {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 18px 20px;
  border: 1.5px solid var(--line);
  border-radius: var(--r);
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}

.article-nav__item:hover {
  border-color: var(--violet);
  background: var(--violet-wash);
}

.article-nav__item--next {
  align-items: flex-end;
  text-align: right;
}

.article-nav__label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-meta);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--violet);
}

.article-nav__title {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
  line-height: 1.28;
  letter-spacing: -0.008em;
  text-transform: uppercase;
}

.related {
  margin-top: clamp(40px, 5vw, 72px);
  padding-top: clamp(30px, 3.6vw, 48px);
  border-top: 1px solid var(--line);
}

/* ── Related cards ───────────────────────────────────────────────────────
   Scoped to --related rather than applied to .card, which is shared by the
   front page, archive and 404 — this treatment is for the end of an article,
   where three cards need to feel like a considered coda rather than a grid
   dump. Each carries its own desk colour, matching the sidebar and footer.

   The base .card already scales its image on hover; everything here is
   additive, so nothing about the shared card is overridden. */
.card--related {
  position: relative;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  transition: transform 0.3s var(--ease);
}

/* The desk-coloured rule that draws across the card's top edge on hover.
   Sits on the existing hairline rather than adding height, so the row's
   baseline grid is unaffected. */
.card--related::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--cat-bg, var(--violet));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.4s var(--ease);
}

.card--related:hover::before,
.card--related:focus-within::before {
  transform: scaleX(1);
}

.card--related:hover,
.card--related:focus-within {
  transform: translateY(-3px);
}

/* The number is decorative furniture — it reads as a rank the posts do not
   actually have, so it is not exposed to assistive tech. Counter rather than
   a PHP index so the markup stays free of presentational values. */
.related .grid-3 {
  counter-reset: related;
}

/* On .card__body, NOT .card__title — the title is a -webkit-box with a 3-line
   clamp, and a pseudo-element child there would consume one of its three lines
   and be ellipsised along with the text. The body is a plain flex column, so
   the number sits above the headline as its own row. */
.card--related .card__body::before {
  counter-increment: related;
  content: counter(related, decimal-leading-zero);
  font-family: var(--font-meta);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  transition: color 0.3s var(--ease);
}

.card--related:hover .card__body::before,
.card--related:focus-within .card__body::before {
  color: var(--cat-bg, var(--violet));
}

@media (prefers-reduced-motion: reduce) {
  .card--related,
  .card--related::before {
    transition: none;
  }

  .card--related:hover,
  .card--related:focus-within {
    transform: none;
  }
}

/* ── Sidebar panels */
.single-sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  position: sticky;
  top: 132px;
}

.panel {
  /* Exposed as a var so children that bleed to the panel's edges (.cat-list)
     can cancel it exactly instead of hardcoding a second copy of the value. */
  --panel-pad: 22px;

  padding: var(--panel-pad);
  border: 1.5px solid var(--line);
  border-radius: var(--r);
}

.panel--dark {
  background: var(--void);
  border-color: var(--void);
  color: var(--on-dark);
}

.panel--violet {
  background: var(--violet);
  border-color: var(--violet);
  color: #fff;
}

.panel__heading {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--violet);
  margin-bottom: 18px;
}

.panel--dark .panel__heading {
  color: var(--lime);
}

.panel--violet .panel__heading {
  color: rgba(255, 255, 255, 0.72);
}

.panel__title {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 800;
  line-height: 1.14;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin-bottom: 9px;
}

.panel__sub {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--on-dark-2);
  margin-bottom: 18px;
}

.panel--violet .panel__sub {
  color: rgba(255, 255, 255, 0.78);
}

/* Numbered "latest" list. */
.side-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.side-item {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  gap: 12px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}

.side-item:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.side-item__num {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--violet);
  padding-top: 1px;
}

.side-item__title {
  font-size: 13px;
  line-height: 1.28;
  letter-spacing: -0.005em;
  margin-bottom: 5px;
}

.side-item__title a:hover {
  color: var(--violet);
}

.side-item__time {
  font-family: var(--font-meta);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Category list with counts. */
/* The sidebar desk list. Same mechanic as the footer wall — a colour sweep in
   the term's own hue behind fully opaque type — but at sidebar scale: the wall's
   32px display type would not fit a narrow rail, so the size stays close to the
   old list's and the sweep does the talking.

   Hairlines are --line (dark on white) rather than the wall's --line-dark, since
   this panel sits on paper rather than in the black footer. */
.cat-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line);
}

.cat-list a {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* Negative inline margin with matching padding lets the fill bleed to the
     panel's edges while the type stays optically aligned with the heading. */
  margin-inline: calc(var(--panel-pad, 22px) * -1);
  padding: 11px var(--panel-pad, 22px);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  isolation: isolate;
  font-family: var(--font-display);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* The sweep. Scales from the left behind the type via z-index rather than
   opacity, so the label never dims mid-transition. */
.cat-list__fill {
  position: absolute;
  z-index: 1;
  inset: 0;
  background: var(--cat-bg, var(--violet));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.42s var(--ease);
}

.cat-list a:hover .cat-list__fill,
.cat-list a:focus-visible .cat-list__fill {
  transform: scaleX(1);
}

.cat-list__name,
.cat-list__count {
  position: relative;
  z-index: 2;
  transition: color 0.32s var(--ease), transform 0.32s var(--ease);
}

.cat-list__count {
  flex-shrink: 0;
  font-family: var(--font-meta);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* Both take the palette's PAIRED foreground, which is what keeps the label
   readable on a light term colour as well as on a dark one. */
.cat-list a:hover .cat-list__name,
.cat-list a:focus-visible .cat-list__name {
  color: var(--cat-fg, #fff);
  transform: translateX(6px);
}

.cat-list a:hover .cat-list__count,
.cat-list a:focus-visible .cat-list__count {
  color: var(--cat-fg, #fff);
}

/* Author card on the article page. */
.author-card {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: 16px;
  align-items: center;
  margin-top: clamp(30px, 3.6vw, 44px);
  padding: 22px;
  border-radius: var(--r);
  background: var(--violet-wash);
}

.author-card__avatar {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  object-fit: cover;
}

.author-card__name {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.015em;
  text-transform: uppercase;
}

.author-card__role {
  margin-top: 3px;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--violet-deep);
}

/* ═══ 20. STATIC PAGE ═════════════════════════════════════════════════════ */
.page-main {
  padding-block: clamp(34px, 4.5vw, 66px) var(--pad);
}

.page-header {
  margin-bottom: clamp(24px, 3vw, 38px);
  padding-bottom: 22px;
  border-bottom: 1px solid var(--line);
}

.page-header__title {
  font-size: clamp(30px, 4.6vw, 62px);
  line-height: 0.96;
  letter-spacing: -0.033em;
}

.page-header__updated {
  display: inline-block;
  margin-top: 14px;
  font-family: var(--font-meta);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.page-hero {
  margin-bottom: clamp(22px, 3vw, 34px);
}

.page-hero__img {
  width: 100%;
  border-radius: var(--r);
  object-fit: cover;
}

/* Static pages reuse the article body rules verbatim. */
.page-content {
  font-size: 17px;
  line-height: 1.75;
  color: #1c1c22;
}

.page-content > * + * {
  margin-top: 1.15em;
}

.page-content h2,
.page-content h3 {
  margin-top: 1.9em;
  margin-bottom: 0.5em;
  letter-spacing: -0.02em;
}

.page-content h2 {
  font-size: clamp(20px, 2.1vw, 28px);
}

.page-content h3 {
  font-size: clamp(17px, 1.6vw, 22px);
}

.page-content a {
  color: var(--violet);
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 2.5px;
}

.page-content ul,
.page-content ol {
  padding-left: 1.4em;
  list-style: revert;
}

.page-content li + li {
  margin-top: 0.5em;
}

/* ═══ 21. 404 ═════════════════════════════════════════════════════════════ */
.error-main {
  background: var(--void);
  color: var(--on-dark);
}

.error-page {
  padding-block: clamp(56px, 8vw, 118px);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* The oversized code, in lime — this page is the one place lime appears outside
   the CTA band, and there is no CTA competing with it above the fold. */
.error-page__code {
  font-family: var(--font-display);
  font-size: clamp(96px, 22vw, 300px);
  font-weight: 800;
  line-height: 0.82;
  letter-spacing: -0.055em;
  color: var(--lime);
}

.error-page__title {
  margin-top: 18px;
  font-size: clamp(24px, 3.6vw, 50px);
  line-height: 0.98;
  letter-spacing: -0.03em;
  color: #fff;
  max-width: 24ch;
}

.error-page__desc {
  margin-top: 18px;
  font-size: 15px;
  line-height: 1.62;
  color: var(--on-dark-2);
  max-width: 54ch;
}

.error-page__actions {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 30px;
}

.error-page__search {
  width: 100%;
  max-width: 540px;
  margin-top: 30px;
}

.error-recent {
  padding-bottom: var(--pad);
}

.error-recent .sec-head__title {
  color: #fff;
}

/* Search bar — dark-band variant, used on 404. */
.searchbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 6px 6px 18px;
  border: 1.5px solid var(--line-dark);
  border-radius: var(--r-pill);
  background: var(--void-2);
  transition: border-color 0.2s var(--ease);
}

.searchbar:focus-within {
  border-color: var(--lime);
}

.searchbar__icon {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  color: var(--on-dark-3);
}

.searchbar__input {
  flex: 1;
  min-width: 0;
  padding: 11px 0;
  border: 0;
  background: transparent;
  color: #fff;
  font-size: 14px;
}

.searchbar__input::placeholder {
  color: var(--on-dark-3);
}

.searchbar__input:focus {
  outline: none;
}

.btn--search-mobile {
  display: none;
  width: 100%;
  margin-top: 10px;
}

/* ═══ 22. NEWSLETTER FORMS ════════════════════════════════════════════════ */
/* Markup and field names are fixed by the AJAX handler — see DESIGN.md §9.
   Only presentation lives here. */
.newsletter-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.newsletter-form__input {
  width: 100%;
  padding: 14px 18px;
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--paper);
  color: var(--ink);
  font-size: 14px;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

.newsletter-form__input::placeholder {
  color: var(--ink-3);
}

.newsletter-form__input:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: 0 0 0 3px var(--violet-line);
}

.newsletter-form__btn {
  flex-shrink: 0;
}

/* On the lime CTA band the row runs inline: field then button. */
/* `stretch`, not `center`: the button is shorter than the field (41px against
   52px, because .btn sizes from its own padding and a 13px line-height), and
   centring left it visibly floating in the row. Stretching makes it take the
   field's height automatically, so the two cannot drift apart if either one's
   padding changes later. */
.cta__row {
  display: flex;
  gap: 10px;
  align-items: stretch;
  max-width: 480px;
}

/* Matches the field's height via a min-height rather than by zeroing the padding:
   once the row stacks on mobile there is no sibling to stretch against, and a
   zeroed padding collapsed the button to 15px. A min-height holds in both
   layouts. */
.cta__row .newsletter-form__btn {
  min-height: 52px;
  padding-block: 0;
}

.cta__row .newsletter-form__input {
  border-color: rgba(11, 11, 13, 0.22);
  background: rgba(255, 255, 255, 0.9);
}

.cta__row .newsletter-form__input:focus {
  border-color: var(--void);
  box-shadow: 0 0 0 3px rgba(11, 11, 13, 0.14);
}

/* Inside a dark panel the field inverts. */
.panel--dark .newsletter-form__input,
.panel--violet .newsletter-form__input {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--line-dark);
  color: #fff;
}

.panel--dark .newsletter-form__input::placeholder,
.panel--violet .newsletter-form__input::placeholder {
  color: var(--on-dark-3);
}

.panel--dark .newsletter-form__input:focus {
  border-color: var(--lime);
  box-shadow: none;
}

.panel--violet .newsletter-form__input:focus {
  border-color: #fff;
  box-shadow: none;
}

.newsletter-form__msg {
  margin-top: 10px;
  font-family: var(--font-meta);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.45;
}

.newsletter-form__msg--success {
  color: var(--up);
}

.newsletter-form__msg--error {
  color: var(--down);
}

/* On dark and lime grounds the semantic greens/reds need lifting to stay
   legible against the band. */
.panel--dark .newsletter-form__msg--success,
.panel--violet .newsletter-form__msg--success {
  color: var(--lime);
}

.panel--dark .newsletter-form__msg--error,
.panel--violet .newsletter-form__msg--error {
  color: #ff8f88;
}

.cta .newsletter-form__msg--success {
  color: #0a5c37;
}

.cta .newsletter-form__msg--error {
  color: #8f1a12;
}

/* ═══ 23. PAGINATION ══════════════════════════════════════════════════════ */
.navigation.pagination {
  margin-top: clamp(34px, 4vw, 56px);
}

.pagination .nav-links {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
}

.pagination .page-numbers {
  display: inline-grid;
  place-items: center;
  min-width: 42px;
  height: 42px;
  padding-inline: 14px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  transition: background 0.18s var(--ease), color 0.18s var(--ease),
    border-color 0.18s var(--ease);
}

.pagination .page-numbers:hover {
  border-color: var(--ink);
  background: var(--ink);
  color: #fff;
}

.pagination .page-numbers.current {
  background: var(--violet);
  border-color: var(--violet);
  color: #fff;
}

.pagination .page-numbers.dots {
  border-color: transparent;
  pointer-events: none;
}

/* ═══ 24. MOTION & RESPONSIVE ═════════════════════════════════════════════ */
/* Scroll reveal. Gated on .js so a no-JS visitor never sees hidden content. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.55s var(--ease), transform 0.55s var(--ease);
}

.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

/* ── ≤1200px: rail drops to three across */
@media (max-width: 1200px) {
  .rail__item {
    flex-basis: calc((100% - var(--gap) * 2) / 3);
  }
}

/* ── ≤1080px: sidebar unstacks, mosaic and hero relax */
@media (max-width: 1080px) {
  .single-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .single-sidebar {
    position: static;
    /* Below the article the panels read better as a row than a tall column. */
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .mosaic {
    grid-template-columns: minmax(0, 1fr);
  }

  .mfeat {
    min-height: 0;
  }

  .mfeat__media {
    aspect-ratio: 16 / 10;
  }

  /* Once the side column no longer sits beside the lead card there is no height
     to stretch to, so the rows size from their own content again. */
  .mosaic__side {
    grid-auto-rows: auto;
    height: auto;
  }

  .mrow__media {
    height: auto;
    aspect-ratio: 4 / 3;
  }

  /* Two columns. The column-first flow needs its row count raised to match, or
     the surplus items would spill into a fourth, non-existent column. `auto-flow:
     row` is simpler here and gives the same visual result at two columns. */
  .footer-wall__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-flow: row;
    grid-template-rows: none;
  }
}

/* ── ≤900px: nav collapses behind the MENU pill */
@media (max-width: 900px) {
  /* Brand left, MENU pill right. The centring track is collapsed because there is
     no third element to balance against any more. */
  .masthead {
    grid-template-columns: 1fr auto;
    min-height: 62px;
  }

  .masthead__brand {
    grid-column: 1;
    justify-content: flex-start;
  }

  .masthead__util {
    grid-column: 2;
  }

  /* The pill's reason to exist: from here down it is the only way to reach the
     category list. */
  .menu-pill {
    display: inline-flex;
  }

  /* The primary row becomes a dropped panel, opened by the MENU pill. */
  .nav-primary {
    border-top: 0;
  }

  .nav-menu {
    display: none;
    padding-bottom: 16px;
    border-top: 1px solid var(--line-dark);
  }

  .nav-menu.is-open {
    display: block;
  }

  .nav-list {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    min-height: 0;
  }

  .nav-list li + li {
    border-top: 1px solid var(--line-dark);
  }

  .nav-list a {
    display: block;
    padding: 14px 2px;
    font-size: 13px;
  }

  .nav-list a::after {
    display: none;
  }

  /* Grids halve. */
  .grid-4,
  .grid-3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .rail__item {
    flex-basis: calc((100% - var(--gap)) / 2);
  }

  .popular__lead,
  .archive-lead,
  .cta__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Stacked: the art is a normal grid item now, so it simply falls below the copy
     — no reserved padding or absolute positioning needed. Capped so it does not
     dominate a phone screen. */
  .cta__art {
    grid-column: 1;
    justify-self: center;
    width: min(340px, 74vw);
    margin-top: clamp(10px, 3vw, 22px);
  }

  /* Tighter cells and finer dots: at the desktop scale only two column rules
     cross a phone screen, which reads as stray lines rather than a grid. */
  .cta {
    --cta-grid: 60px;
    --cta-dot: 12px;
  }

  /* Narrower still — nothing extra needed here; the ≤900px block already handles
     the stacked CTA. */

  .popular__row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* The third mini would leave a lone orphan in a 2-up row. */
  .popular__row .mini:nth-child(3) {
    display: none;
  }

  .fresh__head,
  .sec-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }

  .fresh__aside {
    align-items: flex-start;
    text-align: left;
  }

  .sec-head__kicker {
    max-width: 40ch;
  }

  /* Tablet: the side columns get tight next to the deck, so pull the deck's
     negative offset back and let the copy wrap harder rather than collide. */
  .hero__row {
    gap: 14px;
  }

  .hero__deck-col {
    margin-top: clamp(-40px, -4vw, -16px);
  }

  .hero__dek {
    max-width: 22ch;
  }

  .hero__tags {
    flex-direction: column;
    gap: 1px;
    text-align: right;
  }

  .article-nav {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ── ≤700px: single column throughout */
@media (max-width: 700px) {
  :root {
    --gutter: 18px;
  }

  .grid-4,
  .grid-3,
  .grid-2,
  .popular__row,
  .single-sidebar {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Brand and return-to-top stack and centre; the wall's names come down a step
     so a long category still fits one line at this width.

     align-items centres the flex children (the brand block and the back-to-top
     pill, which are both narrower than the column); text-align centres the type
     inside the brand block itself. Both are needed — either alone leaves the
     other half looking misaligned. */
  .footer-head {
    flex-direction: column;
    align-items: center;
    gap: 22px;
    text-align: center;
  }

  /* The desc caps at 40ch, which at this width is narrower than the column, so
     it needs auto margins to sit centred rather than flush left in its own box. */
  .footer-brand__desc {
    margin-inline: auto;
  }

  /* Single column, row flow. */
  .footer-wall__grid {
    grid-template-columns: minmax(0, 1fr);
    grid-auto-flow: row;
    grid-template-rows: none;
  }

  .footer-wall__name {
    font-size: clamp(17px, 5.4vw, 24px);
  }

  .rail__item {
    flex-basis: 82%;
  }

  /* Hero: the deck drops below the words rather than overlapping them, since at
     this width the overlap makes both unreadable. The deck is absolutely
     positioned on desktop and so ignores flex order — it has to be returned to
     static here, not just reordered. */
  .hero__stage {
    padding-block: 0;
  }

  /* The three-column row collapses: deck first, then the dek, then the tags. The
     deck's negative offset is dropped since there is nothing beside it to hang
     below any more. */
  .hero__row {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    margin-top: 14px;
  }

  /* Flex column so the pill and controls, both back in normal flow at this width,
     centre themselves under the deck. Height is auto here — the desktop fixed
     height plus its negative margins would leave a large empty strip below. */
  .hero__deck-col {
    order: -1;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Clears the fan's upward spread. The back card sits well above the front
       one, and with too small a margin the stack rides up over the headline. */
    margin: 64px 0 0;
    width: min(216px, 56vw);
    height: auto;
  }

  /* Both text blocks read left-aligned in a single column — the tags' desktop
     right-alignment looks like a mistake once they are no longer in a right-hand
     column. */
  .hero__dek,
  .hero__tags {
    align-self: flex-start;
    justify-self: auto;
    max-width: none;
    text-align: left;
  }

  .hero__tags {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 3px 12px;
  }

  /* The run is nowrap, so "EXPLORE NOW" has to fit one line at 320px. 8.6vw keeps
     an 11-character headline inside the gutters; longer Customizer copy shrinks
     with the viewport rather than overflowing. */
  /* Tighter cells: at 88px only four columns cross a phone screen, which reads as
     stripes rather than a grid. */
  .hero {
    --hero-grid: 56px;
  }

  .hero__title {
    font-size: clamp(26px, 8.6vw, 58px);
    /* The parallax offset is a desktop pointer effect; clear it so a stale value
       can't leave the words shifted on a touch device. */
    transform: none;
  }

  /* Narrower than the fan's own spread, so the offset cards stay inside the
     gutters instead of bleeding off the left edge. The top margin gives the fan's
     upward offset somewhere to go now that the deck is in normal flow — without
     it the back cards overlap the headline above. */
  /* `relative`, NOT `static`: the cards are `position: absolute; inset: 0`, so they
     resolve against the nearest positioned ancestor. Making the deck static sent
     them to the section instead, which is why they rendered full-width and buried
     the headline. It still sits in normal flow, just as its own containing block. */
  .hero__deck {
    position: relative;
    left: auto;
    top: auto;
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 4;
  }

  .hero__explore {
    /* relative, not static: it still has to paint above the card it overlaps.
       left/top are desktop anchors and must be cleared, or the pill keeps the 50%
       offset and lands off the right edge once back in flow. */
    position: relative;
    z-index: 5;
    left: auto;
    top: auto;
    transform: none;
    align-self: center;
    /* Sits on the card's lower edge, but high enough to clear the caption block
       inside it rather than covering the headline. */
    margin-top: -22px;
  }

  .hero__explore:hover {
    transform: translateY(-2px) scale(1.04);
  }

  .hero__deck-ui {
    position: relative;
    left: auto;
    bottom: auto;
    transform: none;
    margin-top: 14px;
  }

  /* One line only, and lifted clear of where the EXPLORE pill now overlaps the
     card's lower edge. No longer flush with the plate's bottom, so the rounded
     corners it inherited there are squared off. */
  .hero-card__cap {
    bottom: 40px;
    padding: 26px 11px 9px;
    border-radius: 0;
  }

  .hero-card__title {
    -webkit-line-clamp: 1;
    line-clamp: 1;
  }

  .hero__ink {
    /* Both strokes sit where the deck now lands. */
    display: none;
  }

  /* Hidden minis come back once the row is single-column. */
  .popular__row .mini:nth-child(3) {
    display: grid;
  }

  .mrow {
    grid-template-columns: minmax(0, 112px) minmax(0, 1fr);
  }

  .tile {
    aspect-ratio: 16 / 10;
  }

  .highlight__banner {
    aspect-ratio: 4 / 3;
  }

  .cta__row {
    flex-direction: column;
    align-items: stretch;
    max-width: none;
  }

  .cta__row .btn {
    width: 100%;
  }

  /* The inline search collapses to a stacked field plus full-width button. */
  .searchbar__btn {
    display: none;
  }

  .btn--search-mobile {
    display: inline-flex;
  }

  .article-content {
    font-size: 16.5px;
  }

  .footer-bottom {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ── ≤460px */
@media (max-width: 460px) {
  .mrow {
    grid-template-columns: minmax(0, 1fr);
  }

  .mrow__media {
    aspect-ratio: 16 / 9;
  }

  .mini {
    grid-template-columns: 68px minmax(0, 1fr);
  }

  .error-page__actions .btn {
    width: 100%;
  }

  .author-card {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ── Reduced motion: strip every transform and transition. Content must never
      depend on an animation having run. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  .js [data-reveal] {
    opacity: 1;
    transform: none;
  }

  /* Hero: the fanned stack is a static design choice and stays, but nothing
     animates — no dealing in, no letter reveal, no ink draw, no sheen. JS also
     checks the same query and never starts the auto-cycle, so the deck only
     moves when the reader presses a control. */
  .js .hero__char,
  .js .hero .hero-card__plate {
    opacity: 1;
    transform: none;
    filter: none;
    animation: none;
  }

  /* Must match the specificity of the rules that start the draw
     (.js .hero.is-ready .hero__ink path, plus the --right stagger), or those win
     and the animation is merely shortened by the global duration override rather
     than actually cancelled. */
  .js .hero__ink path,
  .js .hero.is-ready .hero__ink path,
  .js .hero.is-ready .hero__ink--right path {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    animation: none;
  }

  /* Selector must match the specificity of the rule that starts the sweep
     (.hero-card.is-front:not(.is-dragging)), or that rule wins here and the gloss
     keeps animating despite the reduced-motion request. */
  .js .hero-card.is-front:not(.is-dragging) .hero-card__sheen {
    animation: none;
    opacity: 0;
  }

  .hero__title {
    transform: none;
  }

  /* The wall's colour fill still appears on hover — it is a state change, not
     decoration — but it snaps in rather than sweeping, and the name stops
     sliding. */
  .footer-wall__link:hover .footer-wall__name,
  .footer-wall__link:focus-visible .footer-wall__name {
    transform: none;
  }

  .btn:hover,
  .card:hover .card__img,
  .tile:hover,
  .tile:hover .tile__img,
  .mfeat:hover,
  .mfeat:hover .mfeat__img {
    transform: none;
  }
}

/* ── Print */
@media print {
  .site-header,
  .site-footer,
  .cta,
  .single-sidebar,
  .related,
  .article-nav,
  .read-progress,
  .hero__ink,
  .hero__deck-ui,
  .rail-btn {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
  }

  .article-content {
    font-size: 11pt;
    max-width: none;
  }

  .article-content a {
    color: #000;
    text-decoration: underline;
  }
}
