/* The Letters Archive -- shared brand styles.
   Ported from the static prototype (archive/site/). League Spartan +
   Lora via Google Fonts; brand palette below. */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --red:       #D35147;
  --dark:      #2F2F37;
  --nearblack: #23232a;
  --gold:      #f8c571;
  --brown:     #63584C;
  --cream:     #f5f0eb;
  --muted:     #9a948b;
  --type-light: #b3aea4;
  /* article body on the dark field: warmer + a touch brighter than the old
     #b3aea4 so long passages read comfortably without losing the moody tone */
  --type-dark-muted: #c8c2b6;
  --col-width: 680px;
}

/* registered so the scroll-cue bob can interpolate the custom length
   smoothly (un-registered custom props animate discretely / jump). */
@property --cuey {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

/* No CSS smooth-scroll: the JS inertial smooth-scroll (static/app.js) owns
   scroll smoothing on desktop, and native momentum owns it on touch. A CSS
   scroll-behavior:smooth here would fight the JS engine (double easing /
   stutter), so it is intentionally absent. Under prefers-reduced-motion the
   JS engine is off and scrolling is plain/native (see the reduced-motion
   block below, which pins scroll-behavior:auto). */

body {
  background-color: var(--nearblack);
  color: var(--cream);
  font-family: 'League Spartan', 'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* ---- MASTHEAD ---- */
.masthead {
  border-bottom: 1px solid rgba(248, 197, 113, 0.18);
  padding: 28px 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.masthead__wordmark {
  font-family: 'League Spartan', sans-serif;
  font-weight: 300;
  font-size: 11px;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--gold);
  text-decoration: none;
}
.masthead__nav { display: flex; gap: 32px; list-style: none; }
.masthead__nav a {
  font-family: 'League Spartan', sans-serif;
  font-weight: 400;
  font-size: 10px;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s;
}
.masthead__nav a:hover { color: var(--cream); }

/* ---- HERO HEADER (index) ---- */
.page-header {
  padding: 96px 48px 80px;
  max-width: 900px;
  margin: 0 auto;
  text-align: center;
}
.page-header__eyebrow {
  font-size: 10px;
  letter-spacing: 5px;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 24px;
}
.page-header__title {
  font-size: clamp(72px, 10vw, 128px);
  font-weight: 900;
  line-height: 0.9;
  letter-spacing: -3px;
  color: var(--cream);
  text-transform: uppercase;
  margin-bottom: 28px;
}
.page-header__desc {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 17px;
  line-height: 1.7;
  color: var(--type-light);
  max-width: 480px;
  margin: 0 auto;
}

/* ---- THIN GOLD RULE ---- */
.rule {
  width: 48px;
  height: 1px;
  background: var(--gold);
  margin: 0 auto 48px;
  opacity: 0.6;
}

/* ---- ISSUE GRID ---- */
.grid-section { padding: 0 40px 120px; max-width: 1200px; margin: 0 auto; }
.grid-section__label {
  font-size: 10px;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 32px;
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(255,255,255,0.07);
}
.issues-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 40px; }
.grid-empty {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 18px;
  color: var(--type-light);
}

/* ---- ISSUE CARD ---- */
.issue-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  position: relative;
}
.issue-card--live { cursor: pointer; }
.issue-card--soon { cursor: default; }
.issue-card__cover {
  position: relative;
  overflow: hidden;
  aspect-ratio: 3 / 4;
  background-color: #1a1a20;
}
.issue-card__cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  filter: brightness(0.82);
}
.issue-card--live:hover .issue-card__cover img {
  transform: scale(1.04);
  filter: brightness(0.92);
}
.issue-card__season {
  position: absolute;
  top: 16px;
  right: 16px;
  font-size: 9px;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--gold);
  background: rgba(35, 35, 42, 0.82);
  backdrop-filter: blur(4px);
  padding: 5px 10px;
  font-weight: 500;
}
.issue-card__number {
  position: absolute;
  bottom: 0;
  left: 0;
  font-size: 108px;
  font-weight: 900;
  line-height: 0.85;
  letter-spacing: -4px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.10);
  padding: 0 0 4px 10px;
  user-select: none;
  pointer-events: none;
  transition: color 0.4s;
}
.issue-card--live:hover .issue-card__number { color: rgba(255,255,255,0.16); }
.issue-card__read-cta {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%) translateY(6px);
  font-size: 10px;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: #fff;
  background: var(--red);
  padding: 9px 20px;
  opacity: 0;
  transition: opacity 0.25s, transform 0.25s;
  white-space: nowrap;
}
.issue-card--live:hover .issue-card__read-cta {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.issue-card__coming-soon {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.issue-card__coming-soon span {
  font-size: 9px;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid rgba(154, 148, 139, 0.35);
  padding: 8px 14px;
}
.issue-card__meta { padding: 18px 0 0; }
.issue-card__issue-label {
  font-size: 9px;
  letter-spacing: 3.5px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}
.issue-card__title {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 21px;
  line-height: 1.25;
  color: var(--cream);
  margin-bottom: 8px;
}
.issue-card__tagline {
  font-size: 12px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--muted);
}

/* ---- PLACEHOLDER COVER (no photo) ---- */
.cover-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: repeating-linear-gradient(
    -45deg, #1a1a20 0px, #1a1a20 18px, #1c1c23 18px, #1c1c23 36px);
}
.cover-placeholder__num {
  font-size: 96px;
  font-weight: 900;
  letter-spacing: -4px;
  color: rgba(255,255,255,0.05);
  line-height: 1;
}

/* ---- SITE FOOTER (shared) ---- */
.site-footer {
  border-top: 1px solid rgba(248, 197, 113, 0.12);
  padding: 48px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
}
.site-footer__wordmark {
  font-size: 10px;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--gold);
}
.site-footer__galleries { font-size: 11px; letter-spacing: 1.5px; color: var(--muted); }
.site-footer__galleries a { color: var(--gold); text-decoration: none; }
.site-footer__galleries a:hover { text-decoration: underline; }
.site-footer__back a {
  font-size: 10px;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s;
}
.site-footer__back a:hover { color: var(--cream); }
.site-footer__fine { font-size: 11px; color: #4a4640; letter-spacing: 0.5px; }

/* ============================================================ */
/* ISSUE PAGE                                                    */
/* ============================================================ */

/* ---- READING PROGRESS BAR ---- */
.progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: rgba(255, 255, 255, 0.07);
  z-index: 130;
}
.progress__bar {
  display: block;
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--red), var(--gold));
}

/* ---- STICKY BACK NAV (frosts solid past hero) ---- */
.back-nav {
  position: fixed;
  top: 3px; left: 0; right: 0;
  z-index: 120;
  padding: 16px max(20px, calc((100vw - 1180px) / 2));
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background 0.45s ease, color 0.45s ease,
              backdrop-filter 0.45s ease, border-color 0.45s ease;
}
/* The nav rides over BOTH the dark intro and the cream "turn"/dream
   sections, so a transparent bar makes the text vanish on light. Give it a
   persistent dark frosted backdrop (reads on dark AND cream) from the start;
   .is-solid just deepens it slightly once past the hero. */
.back-nav {
  background: rgba(28, 28, 34, 0.55);
  -webkit-backdrop-filter: blur(16px) saturate(1.35);
  backdrop-filter: blur(16px) saturate(1.35);
  border-bottom-color: rgba(248, 197, 113, 0.14);
}
.back-nav.is-solid {
  background: rgba(28, 28, 34, 0.82);
  -webkit-backdrop-filter: blur(20px) saturate(1.5);
  backdrop-filter: blur(20px) saturate(1.5);
  border-bottom-color: rgba(248, 197, 113, 0.22);
}
.back-nav__link {
  font-size: 11px;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold);
  text-decoration: none;
  transition: opacity 0.2s;
  display: flex;
  align-items: center;
  gap: 8px;
}
.back-nav__link svg { transition: transform 0.3s ease; }
.back-nav__link:hover { opacity: 0.78; }
.back-nav__link:hover svg { transform: translateX(-3px); }
.back-nav__center {
  font-size: 10px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.7);
}
/* desktop "Browse Shop" sits opposite "All Letters" in the sticky header */
.back-nav__shop {
  font-size: 11px;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold);
  text-decoration: none;
  transition: opacity 0.2s ease;
}
.back-nav__shop:hover { opacity: 0.78; }

/* ============================================================ */
/* STICKY BOTTOM ACTION BAR (mobile) -- All Letters | Browse Shop */
/* ============================================================ */
.actionbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 130;
  display: none; /* desktop uses the header links; shown < 760px */
  gap: 10px;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
  background: rgba(35, 35, 42, 0.72);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
  border-top: 1px solid rgba(248, 197, 113, 0.16);
}
.actionbar__btn {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 46px;
  border-radius: 999px;
  font-family: 'League Spartan', sans-serif;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 2px;
  text-transform: uppercase;
  text-decoration: none;
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.actionbar__btn:active { transform: scale(0.97); }
.actionbar__btn--primary {
  color: #fff;
  background: var(--red);
  box-shadow: 0 8px 22px rgba(211, 81, 71, 0.32);
}
.actionbar__btn--ghost {
  color: var(--cream);
  background: rgba(245, 240, 235, 0.08);
  border: 1px solid rgba(245, 240, 235, 0.28);
}
/* show the bottom bar on phones; hide the desktop shop link there */
@media (max-width: 760px) {
  .actionbar { display: flex; }
  .back-nav__shop { display: none; }
  /* keep the last content / footer clear of the fixed bar */
  .site-footer { padding-bottom: calc(40px + 66px + env(safe-area-inset-bottom, 0px)); }
}

/* ---- HERO ---- */
.hero {
  position: relative;
  height: 100svh;
  min-height: 600px;
  overflow: hidden;
  background: var(--nearblack);
}
.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  will-change: transform;
}
.hero__img {
  width: 100%;
  height: 112%;
  object-fit: cover;
  object-position: center 42%;
  display: block;
  transform: scale(1.06);
  filter: contrast(1.03) brightness(0.94);
  will-change: transform;
}
.hero__placeholder {
  width: 100%;
  height: 100%;
  background: repeating-linear-gradient(
    -45deg, #1a1a20 0px, #1a1a20 18px, #1c1c23 18px, #1c1c23 36px);
}
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to bottom,
    rgba(35,35,42,0.55) 0%,
    rgba(35,35,42,0.0) 24%,
    rgba(35,35,42,0.0) 46%,
    rgba(35,35,42,0.66) 76%,
    var(--nearblack) 100%);
}
.hero__content {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 2;
  padding: 0 24px clamp(48px, 9vh, 96px);
  text-align: center;
}
.hero__issue-label {
  display: inline-block;
  font-family: 'League Spartan', sans-serif;
  font-weight: 700;
  font-size: clamp(10px, 1.2vw, 12px);
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: clamp(16px, 2.4vh, 24px);
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(35, 35, 42, 0.34);
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  backdrop-filter: blur(10px) saturate(1.3);
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.hero__title {
  margin: 0;
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(56px, 11vw, 124px);
  line-height: 0.95;
  letter-spacing: -0.01em;
  color: var(--cream);
  text-shadow: 0 2px 40px rgba(0, 0, 0, 0.45);
}
.hero__byline {
  margin: clamp(22px, 3vh, 32px) 0 0;
  font-family: 'League Spartan', sans-serif;
  font-weight: 600;
  font-size: clamp(11px, 1.4vw, 13px);
  letter-spacing: 3px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.86);
}
.hero__date {
  margin: 10px 0 0;
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: clamp(14px, 1.6vw, 17px);
  color: rgba(255, 255, 255, 0.62);
}
.hero__scrollcue {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: clamp(20px, 4vh, 36px);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  font-family: 'League Spartan', sans-serif;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
}
.hero__scrollcue-line {
  width: 1px; height: 40px;
  background: linear-gradient(180deg, rgba(255,255,255,0.6), transparent);
  animation: cue 2.4s ease-in-out infinite;
}
@keyframes cue {
  0%, 100% { transform: scaleY(0.4); opacity: 0.4; transform-origin: top; }
  50%      { transform: scaleY(1);   opacity: 1;   transform-origin: top; }
}

/* hero load-in: blur + fade, staggered rise */
.hero__issue-label, .hero__title, .hero__byline, .hero__date {
  opacity: 0;
  filter: blur(14px);
  transform: translateY(20px);
  animation: heroin 1.15s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}
.hero__issue-label { animation-delay: 0.25s; }
.hero__title       { animation-delay: 0.45s; }
.hero__byline      { animation-delay: 0.85s; }
.hero__date        { animation-delay: 1.0s; }
@keyframes heroin {
  to { opacity: 1; filter: blur(0); transform: none; }
}

/* ============================================================ */
/* ARTICLE BODY                                                  */
/* ============================================================ */
.article {
  position: relative;
  z-index: 1;
  background: var(--nearblack);
  color: var(--type-dark-muted);
  padding-top: clamp(64px, 12vh, 120px);
}

/* The reading column. Movement sections set their own field colour;
   a plain (generic) letter just inherits the dark field + column. */
.article > * {
  max-width: var(--col-width);
  margin-left: auto;
  margin-right: auto;
  padding-left: 24px;
  padding-right: 24px;
}
/* movements & turn span full width; their inner content re-columns */
.article > .movement,
.article > .turn { max-width: none; padding-left: 0; padding-right: 0; }

.movement { padding: 0 24px; }
.movement > * {
  max-width: var(--col-width);
  margin-left: auto;
  margin-right: auto;
}
.movement--dread { color: rgba(245, 240, 235, 0.84); }
.movement--dream {
  background: var(--cream);
  color: var(--dark);
  padding-top: clamp(8px, 4vh, 48px);
  padding-bottom: clamp(48px, 9vh, 96px);
}

.article h2 {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(29px, 4.2vw, 42px);
  line-height: 1.14;
  letter-spacing: -0.01em;
  color: var(--cream);
  /* more air above a heading than below it, so it groups with what follows */
  margin: clamp(40px, 7vh, 76px) auto 28px;
}
/* the very first heading sits right under the hero -- no big top gap */
.article > h2:first-child { margin-top: 8px; }
.article h3 {
  font-family: 'League Spartan', sans-serif;
  font-weight: 700;
  font-size: clamp(13px, 1.4vw, 15px);
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--gold);
  margin: clamp(36px, 6vh, 64px) auto 18px;
}
.movement--dream h2, .movement--dream h3 { color: var(--dark); }
.article p {
  font-family: 'Lora', Georgia, serif;
  font-size: clamp(17px, 1.35vw, 19px);
  line-height: 1.78;
  margin: 0 auto 1.5em;
  color: inherit;
}
.article a { color: var(--gold); text-underline-offset: 3px; }
.movement--dream a { color: var(--red); }

/* ---- KICKER (eyebrow / dateline: "A QUICK CORRECTION", locations) ---- */
.article p.kicker {
  font-family: 'League Spartan', sans-serif;
  font-weight: 600;
  font-size: clamp(11px, 1.2vw, 12px);
  letter-spacing: 3.5px;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 auto 1.3em;
  line-height: 1.5;
}
/* a kicker immediately before a heading hugs it (acts as its eyebrow) */
.article p.kicker + h2 { margin-top: 0.1em; }
.movement--dream p.kicker { color: var(--brown); }

/* ---- SECTION DIVIDER (was "* * *") ---- */
.article hr.divider {
  width: 0;
  height: 0;
  border: 0;
  text-align: center;
  margin: clamp(34px, 6vh, 60px) auto;
  overflow: visible;
}
.article hr.divider::after {
  content: "\2731 \2731 \2731";  /* three small stars */
  display: block;
  font-size: 13px;
  letter-spacing: 8px;
  color: var(--gold);
  opacity: 0.7;
  /* nudge left to optically centre the letter-spaced glyphs */
  margin-left: 8px;
}

/* ---- LEDE (opening paragraph) with drop cap ---- */
.lede {
  font-family: 'Lora', Georgia, serif !important;
  font-size: clamp(20px, 2.1vw, 23px) !important;
  line-height: 1.62 !important;
  color: var(--cream);
}
.movement--dream .lede { color: var(--dark); }
/* drop cap ONLY on a real lede paragraph (never a stray short first <p>) */
.lede::first-letter {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 4.6em;
  font-weight: 500;
  line-height: 0.72;
  float: left;
  padding: 8px 14px 0 0;
  color: var(--red);
}

/* ---- PULL QUOTES ---- */
.article blockquote,
.pull {
  max-width: 40rem !important;
  margin: clamp(52px, 9vh, 100px) auto !important;
  padding: 0;
  border: 0;
  text-align: center;
}
.article blockquote p,
.pull p {
  margin: 0 auto;
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(28px, 5vw, 48px);
  line-height: 1.16;
}
.pull--dark p { color: var(--cream); }
.pull--joy { max-width: 30rem !important; }
.pull--joy p {
  color: var(--red);
  font-size: clamp(38px, 8vw, 72px);
}

/* ---- IMAGE-BACKED PULL QUOTE (attic / "stopped feeling like home") ---- */
/* full-bleed dark moment: attic photo bg + scrim + cream serif quote */
.quotebg {
  position: relative;
  max-width: none !important;
  width: 100%;
  margin: clamp(56px, 9vh, 110px) auto !important;
  min-height: clamp(420px, 78vh, 680px);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  text-align: center;
  will-change: opacity;
}
/* the whole block fades up gently on reveal (the media settles from a touch
   darker + scaled via .quotebg__media below; the text rises in separately).
   We use .quotebg[data-reveal] so these beat the generic [data-reveal] /
   figure[data-reveal] rules (which would otherwise add a scale-in transform
   and their own timing). A small block-level rise + fade only -- no scale on
   the block, so it doesn't fight the media parallax or the quote's own rise. */
.quotebg[data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 1.1s cubic-bezier(0.2, 0.7, 0.2, 1),
              transform 1.1s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.quotebg[data-reveal].in-view { opacity: 1; transform: none; }
.quotebg__media {
  position: absolute;
  inset: -8% 0;
  background-size: cover;
  background-position: center;
  /* parallax/scale keyed to --qp (set by app.js); 0 -> static */
  transform: translate3d(0, calc(var(--qp, 0) * -6%), 0)
             scale(calc(1.06 + var(--qp, 0) * 0.06));
  will-change: transform, filter, opacity;
  /* settle-in: the photo starts a shade darker (and the figure scale-fades up
     via the block opacity), then brightens to its resting tone as it lands.
     Filter is independent of the parallax transform so the two don't fight. */
  filter: brightness(0.6) saturate(0.92);
  transition: filter 1.25s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.quotebg.in-view .quotebg__media { filter: brightness(1) saturate(1); }
.quotebg__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(20, 20, 26, 0.55) 0%,
    rgba(20, 20, 26, 0.7) 45%,
    rgba(20, 20, 26, 0.82) 100%);
}
.quotebg__quote {
  position: relative;
  z-index: 1;
  max-width: 40rem !important;
  margin: 0 !important;
  padding: 0 28px;
  border: 0;
  /* the line fades + rises in on reveal */
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 1s cubic-bezier(0.2, 0.7, 0.2, 1),
              transform 1s cubic-bezier(0.2, 0.7, 0.2, 1);
  will-change: opacity, transform;
}
.quotebg.in-view .quotebg__quote { opacity: 1; transform: none; }
.quotebg__quote p {
  margin: 0 auto !important;
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(28px, 5vw, 48px) !important;
  line-height: 1.16 !important;
  color: var(--cream) !important;
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.45);
}

/* ---- ASIDE (time is a circle) ---- */
.article .aside {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: clamp(22px, 3.2vw, 28px);
  line-height: 1.45;
  color: #b9b1a4;
  text-align: center;
  margin: clamp(40px, 6vh, 64px) auto;
  max-width: 32rem;
}

/* ---- EFFORTS LEAD ("The efforts ... And instead of dread,") ----
   Closes the dread movement, set centred so the short line doesn't read as
   an orphaned left-aligned fragment before the section break. The sentence
   intentionally completes into the turn below ("I was greeted by /
   dreams."). Normal body copy weight, just a calm centred lead-in. */
.article .efforts-lead {
  text-align: center;
  max-width: 34rem !important;
  margin: clamp(28px, 5vh, 48px) auto clamp(8px, 2vh, 16px) !important;
  color: var(--cream);
}

/* dream rule (gold divider) */
.article .dream-rule {
  width: 70px;
  height: 2px;
  background: var(--gold);
  border: 0;
  margin: 0 auto clamp(40px, 7vh, 64px);
}

/* ---- TWO-COLUMN PASSAGE (stacks on mobile) ---- */
.twocol {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 36px;
  max-width: 50rem !important;
  margin: clamp(36px, 5vh, 56px) auto;
}
.twocol p { margin: 0 !important; }

.closing-line {
  font-family: 'Lora', Georgia, serif !important;
  font-style: italic;
  font-size: clamp(22px, 2.8vw, 28px) !important;
  line-height: 1.5 !important;
  color: var(--brown) !important;
  text-align: center;
  max-width: 36rem !important;
  margin: clamp(40px, 6vh, 68px) auto !important;
}
.signoff--name {
  font-family: 'Lora', Georgia, serif !important;
  font-style: italic;
  font-size: clamp(26px, 4vw, 38px) !important;
  color: var(--red) !important;
  text-align: center;
  margin: 0.2em auto 0 !important;
}

/* ---- FIGURES ---- */
.article figure,
.figure { margin: clamp(40px, 6vh, 72px) auto; }
.article figure img,
.figure img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 3px;
}
.article figcaption,
.figure figcaption {
  font-family: 'League Spartan', sans-serif;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: rgba(245, 240, 235, 0.5);
  text-align: center;
  margin-top: 14px;
}
.movement--dream .figure figcaption,
.movement--dream figcaption { color: var(--brown); }

/* bordered "polaroid" childhood photo */
.figure--bordered { max-width: 26rem !important; }
/* ON animation: the polaroid fades + rises + settles to its resting tilt as
   it scrolls into view. It starts a touch lower, slightly smaller, and rotated
   a bit further (a hair more tilt + a whisper of lift) so it reads like a
   photo being set down on the page, then eases to the resting -1.4deg. The
   .figure--bordered[data-reveal] rule below neutralises the generic reveal's
   translate so this frame transform is the single source of the motion. */
.figure--bordered .figure__frame {
  background: #fff;
  padding: 14px;
  border-radius: 3px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.35);
  opacity: 0;
  transform: rotate(-3.2deg) translateY(26px) scale(0.965);
  transition: opacity 0.95s cubic-bezier(0.2, 0.7, 0.2, 1),
              transform 1.05s cubic-bezier(0.2, 0.7, 0.2, 1),
              box-shadow 1.05s cubic-bezier(0.2, 0.7, 0.2, 1);
  will-change: opacity, transform;
}
.figure--bordered.in-view .figure__frame {
  opacity: 1;
  transform: rotate(-1.4deg) translateY(0) scale(1);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.5);
}
.figure--bordered .figure__frame img { border-radius: 1px; }
.figure--bordered figcaption {
  margin-top: 22px;
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 15px;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(245, 240, 235, 0.6);
}

/* full-bleed figures (dogs, gallery) */
.figure--bleed {
  max-width: min(1100px, 100%) !important;
  margin: clamp(48px, 8vh, 92px) auto !important;
}
.figure--bleed img {
  width: 100%;
  max-height: 78vh;
  object-fit: cover;
  border-radius: 4px;
}

/* ============================================================ */
/* THE TURN -- dark -> light pivot (dreams.)                     */
/* ============================================================ */
.turn {
  --p: 0;
  --pw: 0;
  position: relative;
  /* tall enough to give the sticky pivot a comfortable scroll-travel
     window (~120vh of travel) for the colour + scale ramp. Shortened on
     mobile (see media query) so the beats arrive with less finger-drag;
     app.js re-times the turn sub-windows for the shorter mobile pin. */
  min-height: 220vh;
  background: linear-gradient(
    180deg,
    var(--nearblack) 0%,
    color-mix(in srgb, var(--nearblack), var(--cream) calc(var(--pw) * 100%)) 50%,
    var(--cream) 100%);
}
.turn__inner {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 24px;
}
.turn__eyebrow {
  margin: 0 0 clamp(8px, 1.4vh, 16px) !important;
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: clamp(18px, 2.4vw, 24px) !important;
  /* ramp toward brown a touch faster than --p so it reads warm */
  color: color-mix(in srgb, #b9b1a4, var(--brown) calc(var(--pw) * 100%));
  /* the eyebrow arrives just before the word: fades in early in the turn */
  opacity: var(--pe, 0);
  transform: translateY(calc((1 - var(--pe, 0)) * 14px));
  will-change: opacity, transform;
}
.turn__word {
  margin: 0 !important;
  font-family: 'Lora', Georgia, serif !important;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(64px, 16vw, 200px) !important;
  line-height: 0.92 !important;
  letter-spacing: -0.01em;
  /* always red -- the word fades in (via --pr opacity) already red, so it
     introduces as red rather than flashing cream/white first. */
  color: var(--red);
  /* HIDDEN at first; the word fades + rises + scales in as the field turns
     to light. --pr is a reveal curve (0 until ~mid-turn, 1 by the end),
     set by app.js from the same turn progress that drives the colour. */
  opacity: var(--pr, 0);
  transform:
    translateY(calc((1 - var(--pr, 0)) * 0.42em))
    scale(calc(0.88 + var(--pr, 0) * 0.12));
  will-change: opacity, transform;
}

/* NOTE: the turn previously carried a centred lead ("The efforts ... instead
   of dread,") and a hand-drawn marker SVG that circled the eyebrow. Both were
   removed -- the efforts line now closes the dread movement above, and the
   turn is just "I was greeted by" + "dreams." fading in on the pivot. The
   related .turn__lead / .turn__dread / .turn__marker rules were dropped with
   the markup (app.js buildMarker() guards on the elements and no-ops). */

/* ---- CTA ---- */
/* The CTA sits between the article body and the newsletter band, both of
   which are on the dark field. It used to paint a hard cream slab, which
   collided badly with the dark body above / dark newsletter below. Now it
   is transparent so it inherits the dark article field on every letter
   (dark-ending back-catalog letters AND the cream-ending dread-to-dream,
   whose cream dream movement has already closed above this block). The red
   pill carries the call to action; a hairline gold rule and small eyebrow
   give it a clean seam from the body. */
.cta-block {
  background: transparent;
  text-align: center;
  padding: clamp(40px, 8vh, 84px) 24px clamp(40px, 8vh, 72px);
  max-width: none !important;
}
.cta-block::before {
  content: "";
  display: block;
  width: 48px;
  height: 1px;
  background: var(--gold);
  opacity: 0.55;
  margin: 0 auto clamp(28px, 5vh, 44px);
}
.cta-btn {
  display: inline-block;
  font-family: 'League Spartan', sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: #fff;
  background: var(--red);
  text-decoration: none;
  padding: 17px 40px;
  border-radius: 999px;
  box-shadow: 0 16px 34px rgba(211, 81, 71, 0.28);
  transition: background 0.2s, transform 0.25s, box-shadow 0.25s;
}
.cta-btn:hover {
  background: #c0493f;
  transform: translateY(-2px);
  box-shadow: 0 22px 44px rgba(211, 81, 71, 0.36);
}
/* tasteful P.S. under the button -- serif italic, muted. The CTA block is
   transparent and sits on each letter's field where the CTA lands; in
   practice that is the near-black .article field (dread-to-dream closes its
   cream "dream" movement above the CTA), so a warm light taupe reads
   quietly there. Scoped via .cta-block so it beats the generic `.article p`
   colour. Not shouty. */
.cta-block .cta-ps {
  margin: clamp(18px, 3vh, 26px) auto 0 !important;
  max-width: 30rem;
  font-family: 'Lora', Georgia, serif !important;
  font-style: italic;
  font-size: clamp(15px, 1.7vw, 17px) !important;
  line-height: 1.5 !important;
  color: #b8ab9c;
  opacity: 0.92;
}

/* ---- PREV / NEXT LETTER NAV ---- */
.letternav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  background: var(--nearblack);
}
.letternav__item {
  padding: clamp(40px, 8vh, 80px) max(24px, calc((100vw - 1180px) / 2 + 24px));
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-decoration: none;
  transition: background 0.3s ease;
}
.letternav__item:hover { background: rgba(245, 240, 235, 0.04); }
.letternav__item--muted { opacity: 0.55; }
.letternav__item--next {
  text-align: right;
  align-items: flex-end;
  border-left: 1px solid rgba(245, 240, 235, 0.1);
}
.letternav__dir {
  font-family: 'League Spartan', sans-serif;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--gold);
}
.letternav__num {
  font-family: 'League Spartan', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: rgba(245, 240, 235, 0.5);
}
.letternav__name {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: clamp(20px, 3vw, 28px);
  color: var(--cream);
  line-height: 1.2;
}

/* ============================================================ */
/* REVEAL (IntersectionObserver)                                 */
/* ============================================================ */
/* Calm editorial fade-in-up: a gentle ~16px rise over ~0.7s, block-level
   only (paragraphs / figures / quotes), gently staggered. Tuned to read
   as a quiet rhythm that helps the eye through long passages -- not a
   twitchy per-line animation. */
[data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  /* gentle smooth-out arrival (calm settle, no linear stop) */
  transition: opacity 0.8s cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 0.8s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: opacity, transform;
}
[data-reveal].in-view { opacity: 1; transform: none; }

/* figures get a whisper of scale-in on top of the rise */
figure[data-reveal], .figure[data-reveal] {
  transform: translateY(18px) scale(0.99);
}
figure[data-reveal].in-view, .figure[data-reveal].in-view {
  transform: none;
}
/* the bordered polaroid: the INNER frame carries the whole reveal motion
   (fade + rise + settle to the resting tilt), so the outer wrapper stays put
   and visible -- no double-translate, and the caption (outside the frame)
   reveals via the generic block fade. */
.figure--bordered[data-reveal] { opacity: 1; transform: none; }
.figure--bordered[data-reveal].in-view { transform: none; }

/* ---- NEWSLETTER SIGNUP ---- */
.newsletter-section {
  background: #1e1e25;
  padding: 72px 24px;
  text-align: center;
  border-top: 1px solid rgba(248, 197, 113, 0.12);
}
.newsletter-section__eyebrow {
  font-size: 9px;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 14px;
}
.newsletter-section__heading {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: clamp(24px, 4vw, 34px);
  color: var(--cream);
  margin-bottom: 10px;
}
.newsletter-section__sub {
  font-size: 14px;
  color: var(--muted);
  margin-bottom: 32px;
  line-height: 1.55;
}
.newsletter-form { display: flex; max-width: 420px; margin: 0 auto; gap: 0; }
.newsletter-form input[type="email"] {
  flex: 1;
  font-family: 'League Spartan', sans-serif;
  font-size: 13px;
  padding: 14px 18px;
  background: #2a2a32;
  border: 1px solid rgba(255,255,255,0.1);
  border-right: none;
  color: var(--cream);
  outline: none;
  appearance: none;
  -webkit-appearance: none;
}
.newsletter-form input[type="email"]::placeholder { color: var(--muted); }
.newsletter-form input[type="email"]:focus { border-color: var(--gold); }
.newsletter-form button {
  font-family: 'League Spartan', sans-serif;
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 2px;
  text-transform: uppercase;
  background: var(--gold);
  color: var(--nearblack);
  border: none;
  padding: 14px 20px;
  cursor: pointer;
  transition: background 0.2s;
}
.newsletter-form button:hover { background: #f0b94a; }
.newsletter-thanks {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 15px;
  color: var(--gold);
  margin-top: 18px;
}

/* ---- 404 ---- */
.error-page {
  min-height: 70vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 80px 24px;
  gap: 18px;
}
.error-page__code {
  font-size: clamp(80px, 14vw, 160px);
  font-weight: 900;
  letter-spacing: -4px;
  color: rgba(255,255,255,0.08);
  line-height: 1;
}
.error-page__msg {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 22px;
  color: var(--type-light);
}
.error-page__link {
  font-size: 10px;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--gold);
  text-decoration: none;
}
.error-page__link:hover { text-decoration: underline; }

/* ---- HERO TITLE ACCENT (the word "Dread" in brand red) ---- */
.hero__title-accent { color: var(--red); }

/* ============================================================ */
/* DUOSLIDE -- horizontal filmstrip tied to vertical scroll      */
/* (still by hand | the press, gone quiet)                       */
/* ============================================================ */
/* A clipping frame holds a horizontal track that is wider than the frame:
   two panels sit in a row, and the track translateX's left as the reader
   scrolls DOWN through the block. The pan amount is keyed to a smoothed
   scroll-progress channel --duo (0..1), set by app.js (same rect-mapping
   technique as the route traveler), so the strip GLIDES rather than tracking
   raw scroll. The frame clips overflow so nothing ever spills into the page
   width. Math: the panels are sized as fractions of the frame via CSS vars,
   so the maximum leftward shift (track width - frame width) is expressible in
   the same units and the second panel lands flush at --duo == 1.

     panel basis      = var(--duo-panel)  (% of frame)
     gap              = var(--duo-gap)    (% of frame)
     track width      = 2*panel + gap
     max shift        = track - 100%      = (2*panel + gap) - 100%
     translateX       = -1 * --duo * max shift                                */
.duoslide {
  --duo: 0;
  /* panel width as a % of the FRAME. The track is exactly the frame width
     (100%), so a flex-basis % on the panels also resolves against the frame --
     keeping the panel measurement in one reference. The gap is a FIXED length
     (percentage flex gaps resolve inconsistently across engines), so the
     translateX max-shift below is (2*panel - 100%) + gap, reached at
     --duo == 1, landing the second panel flush. */
  --duo-panel: 70%;     /* each panel is 70% of the frame width (desktop) */
  --duo-gap: 18px;      /* fixed gap between the two panels */
  position: relative;
  max-width: 44rem !important;
  margin: clamp(40px, 6vh, 72px) auto !important;
  overflow: hidden;             /* CLIP -- the strip never spills the page */
  border-radius: 3px;
}
.duoslide__track {
  display: flex;
  align-items: flex-start;
  gap: var(--duo-gap);
  /* track is exactly the frame width; the two panels overflow it and the
     track slides left as --duo grows, revealing the second panel. Max shift =
     (2*panel - 100% of frame) + gap, reached at --duo == 1. */
  width: 100%;
  transform: translate3d(
    calc(var(--duo, 0) * -1 *
      ((2 * var(--duo-panel) - 100%) + var(--duo-gap))),
    0, 0);
  will-change: transform;
}
.duoslide__panel {
  flex: 0 0 var(--duo-panel);
  margin: 0 !important;
  min-width: 0;
}
/* the source photos are square (900x900). Pinning aspect-ratio reserves each
   panel's height BEFORE the lazy image loads, so the block never collapses to
   the caption (which would let the clip hide everything) and the page never
   jumps when the image arrives. */
.duoslide__panel img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 3px;
}
/* a hairline edge cue so the clipped strip reads as intentional */
.duoslide::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(245, 240, 235, 0.05);
  border-radius: 3px;
}
/* the FRAME fades up on reveal (generic block fade); the horizontal pan is a
   separate, scroll-tied motion driven by --duo. We override the generic
   figure/[data-reveal] scale so only the calm fade-up plays on entrance and
   the track transform stays purely horizontal. */
.duoslide[data-reveal] { transform: translateY(20px); }
.duoslide[data-reveal].in-view { transform: none; }

/* ============================================================ */
/* P.S. -- closing note with kiln photo                          */
/* ============================================================ */
.ps {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 26px;
  align-items: center;
  max-width: 44rem !important;
  margin: clamp(40px, 6vh, 64px) auto 0 !important;
  padding-top: clamp(28px, 4vh, 40px) !important;
  border-top: 1px solid #e3dccf;
}
.ps__photo { margin: 0; }
.ps__photo img {
  width: 100%;
  height: auto;
  display: block;
  border: 5px solid #fff;
  border-radius: 2px;
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.18);
}
.ps__note {
  margin: 0 !important;
  font-size: clamp(15px, 1.3vw, 17px) !important;
  line-height: 1.62 !important;
  color: var(--dark) !important;
}
.ps__label {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 1.25em;
  color: var(--red);
  margin-right: 4px;
}

/* ============================================================ */
/* COIN PROGRESSION -- 5 -> 10 -> 25 (struck), staggered scale-in */
/* ============================================================ */
/* board story + coins: on desktop the coins tuck into the column beneath
   the board copy (right-aligned, filling the space); they stack centered
   on mobile. The container carries the reveal hook. */
.board-block { max-width: var(--col-width); margin: 0 auto; }
.board-block__text { margin-bottom: 0 !important; }
.coins {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 14px;
  max-width: none !important;
  margin: clamp(18px, 2.4vh, 28px) 0 0 !important;
}
.coin {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  border: 2px solid var(--gold);
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 22px;
  color: var(--gold);
  /* start state (revealed via parent .in-view) */
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 0.55s cubic-bezier(0.2, 0.7, 0.2, 1),
              transform 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
  will-change: opacity, transform;
}
.coin--quit { border-color: var(--red); color: var(--red); }
.coin--quit s { text-decoration-thickness: 2px; }
.coin-arrow {
  font-family: 'League Spartan', sans-serif;
  font-size: 20px;
  color: var(--brown);
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.board-block.in-view .coin { opacity: 1; transform: scale(1); }
.board-block.in-view .coin-arrow { opacity: 1; transform: none; }

/* ============================================================ */
/* FORT MAP -- inline SVG overhead map; trail draws, nodes fade   */
/* ============================================================ */
.fortmap {
  max-width: 30rem !important;
  margin: clamp(36px, 5vh, 56px) auto !important;
  padding: 20px 22px 16px;
  border: 1px dashed #4a463f;
  border-radius: 8px;
}
.fortmap__svg { width: 100%; height: auto; display: block; }
.fortmap__label {
  font-family: 'Lora', Georgia, serif;
  font-size: 15px;
  fill: #cfc8bb;
}
.fortmap__label--farm {
  font-family: 'League Spartan', sans-serif;
  font-size: 10px;
  letter-spacing: 1.5px;
  fill: var(--gold);
}
/* trail: drawn in on reveal by collapsing one long dash (offset 460 -> 0);
   app.js restores the short dotted dash pattern when the draw finishes. */
.fortmap__trail {
  stroke-dasharray: 460;
  stroke-dashoffset: 460;
  transition: stroke-dashoffset 1.2s ease 0.1s;
}
.fortmap.in-view .fortmap__trail { stroke-dashoffset: 0; }
.fortmap__node {
  opacity: 0;
  transform: scale(0.5);
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  will-change: opacity, transform;
}
.fortmap.in-view .fortmap__node { opacity: 1; transform: none; }
.fortmap__pine {
  opacity: 0;
  transition: opacity 0.6s ease 0.7s;
}
.fortmap.in-view .fortmap__pine { opacity: 1; }
.fortmap__cap {
  font-family: 'Lora', Georgia, serif;
  font-style: italic;
  font-size: 13px;
  color: #6f6a63;
  text-align: center;
  margin-top: 12px;
}

/* ============================================================ */
/* ROUTE STRIP -- line draws across; waypoint dots pop in         */
/* ============================================================ */
.route {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 6px;
  max-width: 40rem !important;
  margin: clamp(28px, 4vh, 40px) auto 0 !important;
  padding: 16px 6px 0;
}
.route__line {
  position: absolute;
  top: 21px;
  left: 18px;
  right: 18px;
  height: 2px;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1.1s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.route.in-view .route__line { transform: scaleX(1); }
/* the route the traveler has covered so far -- a brighter line that grows
   left->right with scroll progress (--rp 0..1), riding on top of the base line */
.route__progress {
  position: absolute;
  top: 21px;
  left: 18px;
  right: 18px;
  height: 2px;
  background: var(--gold);
  transform: scaleX(var(--rp, 0));
  transform-origin: left center;
  z-index: 1;
  pointer-events: none;
}
/* the traveling marker -- a little dot that drives along the route as you
   scroll the route section. Position interpolated between the end stops. */
.route__traveler {
  position: absolute;
  top: 21px;
  left: calc(18px + (100% - 36px) * var(--rp, 0));
  width: 13px;
  height: 13px;
  margin: -6.5px 0 0 -6.5px;
  border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 0 3px var(--cream), 0 0 10px rgba(211, 81, 71, 0.6);
  opacity: 0;
  z-index: 3;
  transition: opacity 0.3s ease;
}
.route.in-view .route__traveler { opacity: 1; }
.route__stop {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  flex: 1 1 0;
  min-width: 0;
}
.route__dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 0 3px var(--cream);
  opacity: 0;
  transform: scale(0);
  transition: opacity 0.4s ease, transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.route.in-view .route__dot { opacity: 1; transform: none; }
/* a waypoint the traveler has reached: brightens gold->red + a gentle pop */
.route__stop.is-passed .route__dot {
  background: var(--red);
  transform: scale(1.25);
  transition: background 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.route__stop.is-passed .route__label { color: var(--red); }
.route__dot--end {
  width: 14px;
  height: 14px;
  background: var(--red);
}
.route__label {
  font-family: 'League Spartan', sans-serif;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--brown);
  text-align: center;
  line-height: 1.3;
}
.route__label--end { color: var(--red); font-weight: 700; }
.route-caption {
  font-family: 'Lora', Georgia, serif !important;
  font-style: italic;
  font-size: clamp(14px, 1.5vw, 16px) !important;
  color: #8a7d6a !important;
  text-align: center;
  max-width: 34rem !important;
  margin: 14px auto clamp(8px, 2vh, 20px) !important;
}

/* ============================================================ */
/* 50-YEAR-OLD SHADOWS -- emotional letter (No.18).              */
/* Stays on the somber near-black field the whole way (the       */
/* lights are going out); turns to hope only at the very end.    */
/* Signature "lights" motif flicks bulbs off one by one.         */
/* ============================================================ */
.movement--shadows {
  color: rgba(245, 240, 235, 0.82);
  padding-top: clamp(40px, 8vh, 88px);
  padding-bottom: clamp(48px, 9vh, 96px);
}
.shadows__eyebrow {
  font-family: 'Lora', Georgia, serif !important;
  font-size: clamp(11px, 2.4vw, 13px) !important;
  letter-spacing: 5px;
  text-transform: uppercase;
  color: var(--gold);
  text-align: center;
  margin: 0 auto 0.9em !important;
}
.shadows__key {
  font-family: 'Lora', Georgia, serif !important;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(34px, 6vw, 60px) !important;
  line-height: 1.08 !important;
  letter-spacing: -0.01em;
  color: var(--cream) !important;
  text-align: center;
  margin: 0 auto clamp(28px, 5vh, 44px) !important;
  max-width: 18ch;
  text-wrap: balance;
}
.shadows__key-sub {
  display: block;
  margin-top: 0.22em;
  font-size: 0.62em;
  color: rgba(245, 240, 235, 0.62);
}

/* ---- the lights: a strung row of warm bulbs ---- */
.lights {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(18px, 5vw, 40px);
  max-width: 32rem !important;
  margin: clamp(8px, 3vh, 28px) auto clamp(40px, 7vh, 72px) !important;
  padding: 14px 8px 0;
}
.lights__wire {
  position: absolute;
  top: 16px; left: 8%; right: 8%;
  height: 1px;
  background: rgba(248, 197, 113, 0.28);
}
.bulb {
  position: relative;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--gold);
  /* lit by default (so reduced-motion + no-JS shows a warm row); the flick
     animation runs on reveal */
  box-shadow: 0 0 12px 2px rgba(248, 197, 113, 0.65);
}
.lights.in-view .bulb {
  animation: bulbflick 1.5s cubic-bezier(0.4, 0, 0.2, 1) var(--d, 0s) forwards;
}
/* lit -> a final dim ember (the light, turned out) */
@keyframes bulbflick {
  0%   { background: var(--gold);
         box-shadow: 0 0 16px 4px rgba(248, 197, 113, 0.8); }
  55%  { background: var(--gold);
         box-shadow: 0 0 16px 4px rgba(248, 197, 113, 0.8); }
  100% { background: #3a352e;
         box-shadow: 0 0 0 0 rgba(248, 197, 113, 0); }
}

/* ---- the moody studio photos: unify the near-monochrome set ---- */
.shadowshot img {
  filter: grayscale(0.35) brightness(0.92) contrast(1.05);
}
.movement--shadows .figure figcaption,
.movement--shadows figcaption {
  color: rgba(245, 240, 235, 0.5);
}

/* ---- the grief, set large ---- */
.pull--shadows { max-width: 34rem !important; }
.pull--shadows p { color: var(--cream); }

/* ---- the turn to hope: "We ain't done yet." ---- */
.shadows__rally {
  font-family: 'Lora', Georgia, serif !important;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(34px, 7vw, 64px) !important;
  line-height: 1.04 !important;
  color: var(--red) !important;
  text-align: center;
  margin: clamp(8px, 2vh, 16px) auto clamp(28px, 5vh, 44px) !important;
  text-shadow: 0 0 38px rgba(211, 81, 71, 0.28);
}
.signoff--shadows {
  font-family: 'Lora', Georgia, serif !important;
  font-style: italic;
  font-size: clamp(17px, 2vw, 21px) !important;
  color: rgba(245, 240, 235, 0.7) !important;
  text-align: center;
  margin: 0 auto !important;
}

/* ---- quiet news footer ---- */
.shadows__news {
  max-width: 38rem !important;
  margin: clamp(48px, 9vh, 96px) auto 0 !important;
  text-align: center;
}
.shadows__news-rule {
  width: 48px;
  height: 1px;
  border: 0;
  background: var(--gold);
  opacity: 0.5;
  margin: 0 auto clamp(22px, 4vh, 34px);
}
.shadows__news p {
  font-size: clamp(14px, 1.2vw, 16px) !important;
  line-height: 1.7 !important;
  color: rgba(245, 240, 235, 0.55) !important;
}
.shadows__news .kicker { margin-bottom: 0.7em !important; }

/* ---- RESPONSIVE ---- */
@media (max-width: 900px) {
  .issues-grid { grid-template-columns: repeat(2, 1fr); gap: 28px; }
  .masthead { padding: 22px 24px; }
  .page-header { padding: 64px 24px 56px; }
  .grid-section { padding: 0 24px 80px; }
}
@media (max-width: 580px) {
  .issues-grid { grid-template-columns: 1fr; gap: 40px; }
  .masthead__nav { display: none; }
  .page-header__title { letter-spacing: -2px; }
  .issue-card__number { font-size: 80px; }
  .site-footer { padding: 40px 24px; }
}
@media (max-width: 680px) {
  .back-nav { padding: 14px 20px; }
  .newsletter-form { flex-direction: column; }
  .newsletter-form input[type="email"] {
    border-right: 1px solid rgba(255,255,255,0.1);
    border-bottom: none;
  }
  .newsletter-form button { padding: 14px; }
}

/* ---- ARTICLE RESPONSIVE ---- */
@media (max-width: 620px) {
  .hero__title { font-size: clamp(48px, 16vw, 80px); }
  .hero__content { padding-bottom: 56px; }
  .hero__scrollcue { display: none; }

  /* mobile body gutter: the .article > .movement rule zeroes side padding,
     so the dread/dream copy was running edge-to-edge on phones. Give the
     movement sections a small side gutter so text never touches the edge.
     (Higher specificity than `.article > .movement` so it wins.) */
  .article > .movement { padding-left: 18px; padding-right: 18px; }

  .twocol { grid-template-columns: 1fr; gap: 0; }
  .twocol p:first-child { margin-bottom: 1.5em !important; }

  .figure--bordered { max-width: 86% !important; }
  .pull--joy p { font-size: clamp(38px, 13vw, 60px); }

  /* duoslide on mobile: same scroll-tied horizontal pan, but the panels take
     a larger share of the (narrow) frame so each shot reads big and the pan
     still reveals the second within the small viewport. Frame still clips, so
     no horizontal page overflow. */
  .duoslide { --duo-panel: 86%; --duo-gap: 12%; }
  /* ps stacks on mobile */
  .ps { grid-template-columns: 1fr; gap: 18px; text-align: center; }
  .ps__photo { max-width: 220px; margin: 0 auto; }

  /* coins shrink + wrap, and center under the board copy on mobile */
  .coins { gap: 10px; justify-content: center; margin-top: 22px !important; }
  .coin { width: 50px; height: 50px; font-size: 19px; }
  .coin-arrow { font-size: 17px; }

  /* route strip: smaller labels, tighter dots */
  .route { padding-top: 14px; gap: 2px; }
  .route__label { font-size: 8px; letter-spacing: 0.5px; }
  .route__line { top: 19px; }

  /* SHORTER PINS ON MOBILE -- the tall sticky tracks read as too much
     scrolling on a phone and amplify any jerk. Shorten the turn pin so the
     dark->light pivot + "dreams." reveal arrive with less finger-drag.
     app.js re-times the turn sub-windows (eased) for this shorter track so
     nothing gets cut off: the field still warms, the eyebrow + word still
     fully arrive, the marker still fully draws. (turn: 220vh -> 170vh) */
  .turn { min-height: 170vh; }

  .letternav { grid-template-columns: 1fr; }
  .letternav__item--next {
    text-align: left;
    align-items: flex-start;
    border-left: 0;
    border-top: 1px solid rgba(245, 240, 235, 0.1);
  }
}

/* ============================================================ */
/* ROAD INTRO -- opening sequence for the Dread to Dream letter   */
/* only. Ported from archive/design/road-intro. Pinned foggy-road */
/* photo + handwritten note writes on, then title lockup          */
/* assembles around the pinned "dread" as the photo lifts away.   */
/* Scroll progress vars (--p, --ld, --exit, etc.) set by app.js.  */
/* Namespaced .road/.note__/.ttl__/.readon-cue -- no collision   */
/* with the existing .hero/.hero__scrollcue.                      */
/* ============================================================ */
.road { position: relative; height: 520vh; }              /* scroll length / pacing */
/* SHORTER ROAD PIN ON MOBILE -- 520vh of finger-drag is a lot on a phone
   and amplifies jerk. Cut the track so the handwriting + title assembly
   arrive sooner. app.js re-times the road WRITING + HANDOFF windows (eased)
   for the shorter track so the note still fully writes, "dread" lands, and
   the "to / Dream" lockup still fully assembles. (520vh -> 360vh) */
@media (max-width: 620px) {
  .road { height: 360vh; }
}
.road__sticky {
  position: sticky; top: 0; height: 100vh;
  overflow: hidden; background: var(--nearblack);
}

/* PHOTO LAYER -- behind the note; lifts + fades away on --exit (eased) */
.road__stage {
  position: absolute; inset: 0; z-index: 1;
  transform: translateY(calc(var(--exit, 0) * -14vh));
  opacity: calc(1 - var(--exit, 0));
  transition: transform .12s linear, opacity .12s linear;  /* smooths rAF steps */
  will-change: transform, opacity;
}
.road__img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 100%;
  transform: scale(calc(1.04 + var(--p, 0) * 0.06));
  transform-origin: 50% 100%; will-change: transform;
}
.road__wash {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(238, 236, 226, 0.34) 0%, rgba(238, 236, 226, 0.06) 40%,
    rgba(20, 22, 26, 0.06) 72%, rgba(20, 22, 26, 0.34) 100%);
}

/* NOTE/TITLE LAYER -- one centred flex column so nothing collides or
   overflows. dread lives in the column (stable, never moves). Context
   handwriting hangs off the dread wrapper (absolute, no layout impact). */
.road__note {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center; gap: 0.34em; padding: 0 6vw;
}

.ttl__eyebrow {
  font-family: 'Lora', Georgia, serif;
  font-size: clamp(11px, 2.6vw, 13px);
  letter-spacing: 5px; text-transform: uppercase; color: var(--gold);
  opacity: var(--we, 0);
  transform: translateY(calc((1 - var(--we, 0)) * -12px));
  will-change: opacity, transform;
  /* sits on its own as the kicker -- big gap below so it doesn't read as
     "...from Brad / From"; the "From" belongs with "dread" below. */
  margin-bottom: 1.4em;
}

/* "From" -- sits between the eyebrow and the "dread" word in the assembled
   title lockup. Styled to match .ttl__to (Lora uppercase, letter-spaced,
   muted). Hidden during the handwriting-writing phase and faded in with the
   title assembly via --wfrom (set in app.js), so it never overlaps the
   context handwriting that hangs above "dread" while it is being written. */
.ttl__from {
  font-family: 'Lora', Georgia, serif;
  font-size: clamp(12px, 2.8vw, 14px);
  letter-spacing: 6px; text-transform: uppercase; color: var(--muted);
  opacity: var(--wfrom, 0);
  transform: translateY(calc((1 - var(--wfrom, 0)) * -8px));
  will-change: opacity, transform;
  /* "From" belongs to "dread" below it -- hug them together (offset the flex
     gap above + pull toward the big word) so it doesn't trail "...from Brad". */
  margin-top: -0.4em; margin-bottom: -0.18em;
}

/* dread wrapper: in normal flow, sized to the word. Context lines float
   off it so dread stays put while they sit above/below during writing. */
.dreadwrap { position: relative; }

/* "dread" -- TRUE single-stroke handwriting write-on. The container holds an
   inline SVG whose centerline path (the word set in the public-domain Hershey
   Script Simplex single-stroke cursive, smoothed) writes itself via
   stroke-dashoffset, driven by the road WRITING progress --ld (set in app.js).
   Height is matched to the OLD filled Caveat word's line-box so the centered
   column's vertical rhythm is unchanged (no layout shift vs the clip-wipe word);
   the SVG width follows the artwork aspect ratio, centered, and is capped so it
   never overflows the note column. */
.note__dread {
  font-family: 'Caveat', cursive; color: var(--red);
  font-size: clamp(72px, 18vw, 160px); line-height: .92; font-weight: 700;
  padding: 0 .12em;                       /* room for italic overhang */
  /* SOFT-EDGE WIPE: a horizontal mask reveals the word left->right as --ld
     goes 0..1, with a feathered ~9% transition zone at the leading edge so
     letters fade in at the boundary instead of snapping (no hard rectangle).
     The mask stops are positioned just behind the reveal front (--ld*100%),
     opaque to the left, a soft gradient across the front, transparent ahead.
     Default / reduced-motion (--ld unset -> falls back to 1): fully shown. */
  --ldp: calc(var(--ld, 1) * 100%);
  -webkit-mask-image: linear-gradient(to right,
    #000 0%,
    #000 max(0%, calc(var(--ldp) - 9%)),
    rgba(0,0,0,0) var(--ldp),
    rgba(0,0,0,0) 100%);
  mask-image: linear-gradient(to right,
    #000 0%,
    #000 max(0%, calc(var(--ldp) - 9%)),
    rgba(0,0,0,0) var(--ldp),
    rgba(0,0,0,0) 100%);
}

/* context handwriting -- hangs above/below dread; fades out fast on handoff */
.note__ctx {
  position: absolute; left: 50%; transform: translateX(-50%); width: 86vw;
  font-family: 'Caveat', cursive; color: #3a4046;
  font-size: clamp(24px, 5vw, 44px); line-height: 1.16;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
  opacity: calc(1 - var(--ctxout, 0)); pointer-events: none;
}
.note__ctx--top { bottom: 100%; margin-bottom: .35em; }
.note__ctx--bot { top: 100%; margin-top: .2em; }
.note__line {
  display: block;
  clip-path: inset(0 calc(100% - var(--l, 0) * 100%) 0 0);
  opacity: calc(0.15 + var(--l, 0) * 0.85); white-space: nowrap;
}
.note__line + .note__line { margin-top: 0.04em; }

/* to + Dream */
.ttl__to {
  font-family: 'Lora', Georgia, serif;
  font-size: clamp(12px, 2.8vw, 14px);
  letter-spacing: 6px; text-transform: uppercase; color: var(--muted);
  opacity: var(--wto, 0); margin-top: .15em;
}
.ttl__d2 {
  font-family: 'Lora', Georgia, serif; font-style: italic; font-weight: 500;
  font-size: clamp(56px, 14vw, 132px); line-height: .92; color: var(--cream);
  opacity: var(--w2, 0);
  transform: translateY(calc((1 - var(--w2, 0)) * 40px));
  /* gold glow blooms in as Dream arrives (its own progress, no clip = clean) */
  text-shadow:
    0 0 calc(var(--w2, 0) * 40px) rgba(248, 197, 113, calc(var(--w2, 0) * 0.55)),
    0 0 calc(var(--w2, 0) * 16px) rgba(248, 197, 113, calc(var(--w2, 0) * 0.45));
  will-change: opacity, transform;
}

/* "Read On" scroll cue (Postscript Flourish) -- the approved cue. Lives inside
   .road__stage (bottom-centre over the foggy road) so it never collides with
   the article .hero__scrollcue, and fades out as the reader scrolls via
   opacity:calc(1 - var(--pall)) -- the same channel app.js drives so the cue
   disappears as the road lifts away. A handwritten "read on" in Caveat, a gold
   self-drawing swash underline, a hand-drawn calligraphic DOWN arrow (filled
   variable-width ink, revealed top->bottom via clip-path inset) ending in a
   small red ink period that blooms in. Soft cream halo for legibility on the
   foggy sky. Gentle whole-cue bob (via --cuey so the centring translateX stays
   intact -- --cuey is registered with @property at the top of this file).
   Ported verbatim from archive/design/scrollcue-c (markup + CSS + SVG +
   keyframes + easing/timing). */
.road .readon-cue {
  position: absolute; left: 50%; bottom: 6.5vh;
  --cuey: 0px;
  transform: translate(-50%, var(--cuey));
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  opacity: calc(1 - var(--pall, 0));
  transition: opacity .2s linear; pointer-events: none; z-index: 3;
  animation: readonbob 3.4s ease-in-out infinite;
  will-change: transform, opacity;
}
/* soft cream halo lifts the dark ink off the foggy sky / dark road.
   Large + very soft so the disc edge is imperceptible -- reads as light,
   not a spotlight blob. */
.readon-cue::before {
  content: ""; position: absolute; left: 50%; top: 48%;
  width: 400px; height: 360px; transform: translate(-50%, -50%);
  border-radius: 50%;
  /* a soft, wide glow -- lifts the ink off the fog without reading as a
     hard white disc (lower peak, earlier + gentler falloff, blurred edge). */
  background: radial-gradient(closest-side,
    rgba(247, 243, 237, .38) 0%, rgba(247, 243, 237, .24) 26%,
    rgba(247, 243, 237, .09) 50%, rgba(247, 243, 237, 0) 76%);
  filter: blur(7px);
  z-index: -1; pointer-events: none;
}

/* the handwritten word -- intimate margin-note feel */
.readon-cue__word {
  font-family: 'Caveat', cursive;
  font-size: 31px; line-height: 1; color: #26262d;
  letter-spacing: .3px;
  text-shadow: 0 1px 0 rgba(247, 243, 237, .7), 0 0 10px rgba(247, 243, 237, .6);
}

/* gold flourish under the word -- a loose underline-swash with a tail,
   sits a touch below so it reads as a flourish, not a strikethrough. */
.readon-cue__swash { display: block; width: 118px; height: 16px; margin-top: 3px; overflow: visible; }
.readon-cue__swash path {
  fill: none; stroke: var(--gold); stroke-width: 2.3; stroke-linecap: round;
  filter: drop-shadow(0 1px 1px rgba(120, 90, 30, .22));
  stroke-dasharray: 170; stroke-dashoffset: 170;
  animation: readon-draw-swash 2.8s ease-out .2s forwards;
}

/* the hand-drawn arrow -- a real pen gesture rendered as FILLED ink shapes
   (variable width: thick on the downstroke, tapering to fine points) so it
   reads as a calligraphic nib stroke, not a UI icon. The whole mark is
   "drawn on" by an animated CSS clip-path inset that opens top -> bottom
   (see .readon-cue__inkgroup below), so the ink appears to flow down the page. */
.readon-cue__arrow {
  display: block; width: 62px; height: 96px; margin-top: 5px; overflow: visible;
  filter: drop-shadow(0 1px 1.5px rgba(247, 243, 237, .5));
}
.readon-cue__ink { fill: #26262d; }
/* the reveal: a CSS clip-path inset on the ink group that opens top->bottom,
   so the filled ink appears to flow down the page. CSS clip-path animates
   on the compositor (cheap, 60fps) -- unlike the SVG height attribute. */
.readon-cue__inkgroup {
  clip-path: inset(0 0 100% 0);
  animation: readon-sweep 2.9s cubic-bezier(.5, 0, .15, 1) .4s forwards;
}
@keyframes readon-sweep { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }

/* ink "period" -- a small red full-stop tight under the tip, like the dot
   ending a sentence. Blooms in (ink spreading) AFTER the downstroke arrives at
   the tip (sweep ends ~3.3s), so it lands like the full-stop at the end of a
   sentence. */
.readon-cue__dot {
  fill: var(--red); opacity: 0; transform-box: fill-box; transform-origin: center;
  filter: drop-shadow(0 0 3px rgba(211, 81, 71, .4));
  animation: readon-ink-dot 1.1s ease-out 3.0s forwards;
}

@keyframes readon-draw-swash { to { stroke-dashoffset: 0; } }
@keyframes readon-ink-dot { 0% { opacity: 0; transform: scale(.2); } 55% { opacity: .95; } 100% { opacity: 1; transform: scale(1); } }

/* gentle whole-cue float (transform via --cuey keeps horizontal centring stable) */
@keyframes readonbob { 0%, 100% { --cuey: 0px; } 50% { --cuey: 8px; } }

/* ROAD INTRO -- MOBILE SIDE SPACING (placed after the base road rules above
   so it wins on source order). On phones the title lockup sat too tight to
   the screen edges; widen the note's horizontal padding and trim the big
   handwritten "dread" + serif "Dream" font floors a touch so both keep a
   comfortable margin and never touch / clip the viewport edge at 360 or 390. */
@media (max-width: 620px) {
  .road__note { padding: 0 12vw; }
  /* dread -- smaller on phones so the big word clears the widened side padding */
  .note__dread { font-size: clamp(50px, 15.5vw, 160px); }
  .ttl__d2 { font-size: clamp(42px, 12vw, 132px); }
  /* "read on" cue -- tuned down a touch for phones (ported from prototype) */
  .road .readon-cue { bottom: 5.5vh; }
  .readon-cue__word { font-size: 28px; }
  .readon-cue__swash { width: 104px; }
  .readon-cue__arrow { width: 54px; height: 82px; }
}

/* ============================================================ */
/* REDUCED MOTION                                                */
/* ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* road intro: everything visible/static, photo present, no motion */
  .road__note .note__line { clip-path: none !important; opacity: 1 !important; }
  /* "dread": fully shown + static (mask cleared, no write-on) */
  .note__dread {
    opacity: 1 !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
  }
  .road__img { transform: none !important; }
  .ttl__eyebrow, .ttl__from, .ttl__to, .ttl__d2 { opacity: 1 !important; transform: none !important; }
  .road__stage { opacity: 1 !important; transform: none !important; }
  .note__ctx { opacity: 1 !important; }
  /* "read on" cue: fully drawn + static and visible, still clearly a downward
     arrow (swash drawn, ink revealed, red period shown, no bob). Force
     opacity:1 here because under reduced motion app.js paints the road intro's
     end-state (which pins --pall:1, the scroll-fade channel) -- without this
     the cue would compute to opacity 0. The road photo stays present in that
     static end-state, so the cue still reads correctly over it. */
  .road .readon-cue { animation: none !important; transform: translateX(-50%) !important; opacity: 1 !important; }
  .readon-cue__swash path { animation: none !important; stroke-dashoffset: 0 !important; }
  .readon-cue__inkgroup { animation: none !important; clip-path: none !important; }
  .readon-cue__dot { animation: none !important; opacity: 1 !important; transform: none !important; }
  [data-reveal],
  [data-reveal].in-view,
  figure[data-reveal],
  .figure[data-reveal],
  .figure--bordered[data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .figure--bordered .figure__frame {
    opacity: 1 !important;
    transform: rotate(-1.4deg) !important;
    transition: none !important;
  }
  /* static turn: cream field, eyebrow + red word fully visible, no scroll
     dependency */
  .turn {
    --p: 1; --pw: 1; --pr: 1; --pe: 1;
    background: var(--cream) !important;
  }
  .turn__word {
    transform: none !important;
    opacity: 1 !important;
    color: var(--red) !important;
  }
  .turn__eyebrow {
    transform: none !important;
    opacity: 1 !important;
    color: var(--brown) !important;
  }
  /* static image-backed quote: legible, no parallax, no fade/darken settle */
  .quotebg,
  .quotebg[data-reveal],
  .quotebg[data-reveal].in-view {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .quotebg__media {
    transform: none !important;
    filter: none !important;
    transition: none !important;
  }
  .quotebg__quote { opacity: 1 !important; transform: none !important; }
  .hero__issue-label, .hero__title, .hero__byline, .hero__date {
    opacity: 1 !important;
    filter: none !important;
    transform: none !important;
    animation: none !important;
  }
  .hero__media { transform: none !important; }
  .hero__scrollcue-line { animation: none; }

  /* new elements: fully drawn / visible, no motion */
  .coin, .coin-arrow,
  .fortmap__node, .fortmap__pine,
  .route__line, .route__dot {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .route__line { transform: scaleX(1) !important; }
  .fortmap__trail {
    stroke-dasharray: 5 6 !important;
    stroke-dashoffset: 0 !important;
    transition: none !important;
  }
  /* shadows letter: bulbs stay lit + static (no flick-off animation) */
  .bulb { animation: none !important; }

  /* DUOSLIDE: no horizontal scrub. Lay both panels out static + fully
     visible -- side by side on desktop, stacked on mobile. The clipping
     frame is released so nothing is hidden. */
  .duoslide {
    overflow: visible !important;
    transform: none !important;
    opacity: 1 !important;
  }
  .duoslide__track {
    width: 100% !important;
    transform: none !important;
    display: grid !important;
    grid-template-columns: 1fr 1fr;
    gap: 22px;
    align-items: end;
  }
  .duoslide::after { display: none !important; }
}
/* DUOSLIDE static fallback when JS does not run (no .in-view ever set and
   --duo stays 0): also covers the brief pre-JS paint. We DON'T release the
   clip here in the motion build (the engine drives --duo), so this block is
   scoped to reduced-motion above. For touch devices the engine still runs and
   sets --duo off native scroll, so the pan works there too. */
@media (max-width: 620px) and (prefers-reduced-motion: reduce) {
  .duoslide__track { grid-template-columns: 1fr; gap: 28px; }
}
