/* ============================================================
   Editorial layer — apple.com anatomy (Appendix A3/A4)
   + learner components (cards, quiz, progress, rails).
   The CONTENT layer: photography, jewel gradients, big Didot.
   ============================================================ */

* { box-sizing: border-box; margin: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.5;
  color: var(--ink);
  background: var(--ground);
  min-height: 100dvh;
  /* clear the fixed nav */
  padding-bottom: calc(var(--tabbar-h) + 28px);
}
@media (min-width: 768px) {
  body { padding-bottom: 0; padding-top: calc(var(--nav-h) + 28px); }
}
img, video { max-width: 100%; display: block; }
button { font: inherit; cursor: pointer; }

.wrap { max-width: var(--content-max); margin: 0 auto; padding: 0 var(--section-x); }

h1, h2, .display {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.08;
}
h1 { font-size: var(--text-title); }
.hero-title { font-size: var(--text-hero); color: #fff; text-wrap: balance; }

/* [design DISPLAY-TYPE CONSISTENCY, MEDIUM] Shared grotesque page-header classes.
   PROBLEM: utility pages (Search / You / Review) render bare <h1>/<h2>, which
   resolve to the rule above (capitalized Didot) — while content + reader pages
   use lowercase grotesque (.r-display / .r-head). Two competing display voices
   read inconsistent across the app. UNIFY page headers on the grotesque display;
   Didot stays reserved for numerals, eyebrows and pull-quotes (the reader keeps
   its own .r-display/.r-head; .stat-numeral, .eyebrow, .r-quote stay Didot).
   The utility SHELLS apply these to their <h1>/<h2> (FROZEN new names; additive
   — nothing renamed, bare <h1>/<h2> still work if a shell hasn't adopted them). */
.page-display, .page-head {
  font-family: var(--font-grotesque);
  text-transform: lowercase;
  color: var(--ink);
  text-wrap: balance;
  overflow-wrap: normal;
  word-break: normal;
  -webkit-hyphens: none;
  hyphens: none;
}
.page-display {
  font-weight: var(--reader-display-weight, 800);
  font-size: var(--text-hero);
  line-height: .92;
  letter-spacing: var(--reader-display-tracking, -.035em);
}
.page-head {
  font-weight: var(--reader-head-weight, 700);
  font-size: var(--text-title);
  line-height: 1.0;
  letter-spacing: var(--reader-head-tracking, -.03em);
}

.eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent-text);
}
/* [polish] consistent eyebrow → Didot-title rhythm: the reset zeroes <p>
   margins, so an eyebrow sat flush on its heading everywhere. A small,
   uniform beat below it (only when a display heading follows) gives every
   section head the same cadence. Page-set inline margins still win. */
.eyebrow + :is(h1, h2, .display, .hero-title, .stat-numeral, .stat-unit) { margin-top: 6px; }
.secondary { color: var(--ink-secondary); }
.footnote { font-size: 12px; line-height: 1.55; color: var(--ink-secondary); }

/* ---------- Sections ---------- */
.section { padding: var(--section-y) var(--section-x); }
.section--alt { background: var(--ground-alt); }

/* ---------- Hero (background layer + overlay + Didot) ---------- */
.hero {
  position: relative;
  min-height: 46dvh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--sp-7) var(--section-x) var(--sp-6);
  background-size: cover;
  background-position: center;
  overflow: hidden;
}
.hero::before { content: ""; position: absolute; inset: 0; background: var(--hero-overlay); }
.hero > * { position: relative; }
.hero .eyebrow { color: var(--gold-light); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;              /* already clears the 44px WCAG 2.5.5 target */
  padding: 12px 26px;
  border: none;
  border-radius: var(--r-pill);
  /* [P1-8] gold-as-metal fill (--cta-fill, NOT --accent) so the runtime jewel
     override can't turn primary CTAs into jewel-filled surfaces. A soft
     top-to-bottom burnish reads as brushed metal rather than a flat swatch. */
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--cta-fill, var(--accent)) 92%, #fff) 0%,
      var(--cta-fill, var(--accent)) 52%,
      var(--cta-fill-2, var(--cta-fill, var(--accent))) 100%);
  color: var(--cta-ink, var(--ink-on-accent));
  font-weight: 700;
  font-size: 1rem;
  text-decoration: none;
  min-width: 0;
  overflow-wrap: normal;
  word-break: normal;
  -webkit-hyphens: none;
  hyphens: none;
  text-wrap: pretty;
  transition:
    transform var(--dur-micro) var(--ease-spring),
    filter var(--dur-micro),
    box-shadow var(--dur-micro) var(--ease-gentle);
}
.btn:active { transform: translateY(0); }
@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    filter: brightness(1.06);
    transform: translateY(-1px);
    /* a whisper of gold lift on hover — metal catching light, not a glow */
    box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--cta-fill-2, var(--gold-deep)) 55%, transparent);
  }
}
/* [P1-8] quiet/secondary keeps transparent + gold-metal ink, unchanged intent */
.btn--quiet {
  background: transparent;
  color: var(--accent-text);
  border: 1px solid var(--glass-border);
}

/* ---------- Progress ---------- */
.progress {
  height: 8px;
  border-radius: var(--r-pill);
  /* [P1-6] designed rest state: a faint jewel-tinted inset track (leans toward
     --track-jewel where a bg-* ancestor supplies it, else reads as the prior
     ink@12% wash — color-mix with the same color is identity). A subtle inner
     shadow reads as a real recessed channel, so a 0% bar no longer looks like
     an orphaned divider. Page overrides (.p-row/.tile) win by specificity. */
  background: color-mix(in srgb, var(--track-jewel, var(--ink)) 14%, transparent);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--navy) 14%, transparent);
  overflow: hidden;
}
.progress > i {
  display: block; height: 100%;
  width: var(--value, 0%);
  border-radius: inherit;
  background: linear-gradient(90deg, var(--gold-deep), var(--accent));
  transition: width .5s var(--ease-out);
}
.progress-pill {
  position: fixed;
  top: max(12px, env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%) translateZ(0);
  z-index: 150;
  padding: 8px 18px;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .04em;
}

/* ---------- Packet cards (App-Store-Today style) ---------- */
.packet-card {
  display: block;
  position: relative;
  border-radius: var(--r-card);
  overflow: hidden;
  aspect-ratio: 16 / 10;
  text-decoration: none;
  color: #fff;
  box-shadow: 0 10px 30px rgba(12, 35, 64, .18);
  background-size: cover;
  background-position: center;
}
.packet-card::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(4,10,20,.06) 30%, rgba(4,10,20,.62) 100%);
}
.packet-card .meta {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--sp-3) var(--sp-4);
}
.packet-card .meta h3 { font-family: var(--font-display); font-size: var(--text-card-title); letter-spacing: -0.01em; }
.packet-card .meta .sub { font-size: .8rem; opacity: .85; margin-top: 2px; display: flex; gap: 10px; align-items: center; }
.packet-card .done-tick { color: var(--gold-light); font-weight: 700; }
@media (hover: hover) and (pointer: fine) {
  .packet-card { transition: transform var(--dur-micro) var(--ease-spring), box-shadow var(--dur-micro); }
  .packet-card:hover { transform: translateY(-2px); box-shadow: 0 20px 44px rgba(12,35,64,.28); }
}

/* ---------- Rails (horizontal carousels) ---------- */
.rail {
  display: flex;
  gap: var(--sp-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding: var(--sp-2) var(--section-x) var(--sp-4);
  scrollbar-width: none;
}
.rail::-webkit-scrollbar { display: none; }
.rail > * { flex: 0 0 clamp(260px, 72vw, 380px); scroll-snap-align: start; }
@media (min-width: 768px) { .rail > * { flex-basis: clamp(280px, 30vw, 400px); } }

.grid { display: grid; gap: var(--sp-4); }
@media (min-width: 768px) { .grid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } }

/* ---------- Learning cards (the calm surface) ---------- */
.learn-stage {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: calc(var(--sp-7)) var(--section-x) calc(var(--tabbar-h) + var(--sp-6));
  position: relative;
}
.learn-card {
  width: min(560px, 100%);
  padding: var(--sp-5) var(--sp-5) var(--sp-6);
  text-align: left;
}
.learn-card .card-img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--r-inner);
  margin-bottom: var(--sp-4);
}
/* [P2-3] Drop the hard white picture-frame mat: give the card image a
   hairline-gold rim so it reads bezel-less, unified with the packet-card /
   track-tile treatment. Applied on the .card-media WRAPPER when present (the
   player wraps the <img> there and clips it, so a rim on the bare replaced
   <img> would hide behind the image content). The rim rides on ::BEFORE — the
   player's .card-media is also a .glaze-pass, whose sheen owns ::after, so a
   rim there would be swept off with the glaze; ::before is free and static.
   .cover-fallback is pseudo-free (see tokens.css) so this composes with it too. */
.learn-card .card-media { position: relative; }
.learn-card .card-media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;                     /* above the image, below the (::after) sheen */
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--gold) 34%, transparent),
    inset 0 1px 0 color-mix(in srgb, #fff 26%, transparent);
}
/* fallback rim for a bare (un-wrapped) .card-img */
.learn-card > .card-img {
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--gold) 34%, transparent),
    0 2px 8px -4px rgba(4, 10, 20, .35);
}
.learn-card h2 { font-size: var(--text-card-title); margin-bottom: var(--sp-2); }
.learn-card .body { font-size: 1.09rem; line-height: 1.55; }
/* [P0-1] Flowed by default (was position:absolute right/bottom, which overlapped
   the last body lines on footnote-less cards). Now a right-aligned inline-flex
   pill that sits AFTER the body in normal flow — the collision can't occur even
   without packet.html's .card-actions wrapper. The player still wraps it in a
   hairline-gold action row (packet.html .card-actions/.learn-card .deeper-btn),
   which composes cleanly on top of this safe base. */
.deeper-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--sp-3);
  margin-left: auto;               /* right-align within a flex/grid parent */
  min-width: 44px; min-height: 44px;
  border-radius: var(--r-pill);
  border: 1px solid var(--glass-border);
  background: transparent;
  color: var(--accent-text);
  font-weight: 700;
  font-size: .85rem;
  padding: 8px 16px;
}

/* Depth sheet content */
.sheet h3 { font-family: var(--font-display); font-size: 1.3rem; margin-bottom: var(--sp-2); }
.sheet .sci-toggle {
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-top: 1px solid var(--glass-border);
  margin-top: var(--sp-4);
  padding: var(--sp-3) 0 0;
  color: var(--accent-text);
  font-weight: 700;
}
.sheet .sci-body { display: none; padding-top: var(--sp-2); }
.sheet .sci-body.open { display: block; }

/* Player chrome */
.stage-controls {
  position: fixed;
  left: 0; right: 0;
  bottom: max(14px, env(safe-area-inset-bottom));
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 var(--sp-4);
  z-index: 150;
  pointer-events: none;
}
.stage-controls > * { pointer-events: auto; }

/* ---------- Video ---------- */
.video-shell { width: min(560px, 100%); margin: 0 auto; position: relative; }
.video-frame {
  position: relative;
  border-radius: var(--r-card);
  overflow: hidden;
  background: #000;
  box-shadow: 0 18px 50px rgba(4, 10, 20, .4);
}
.video-frame.ratio-16x9 { aspect-ratio: 16 / 9; }
.video-frame.ratio-9x16 { aspect-ratio: 9 / 16; max-width: min(400px, 88vw); margin: 0 auto; }
.video-frame iframe, .video-frame video {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
}
/* desktop ambient echo behind vertical video */
@media (min-width: 1024px) {
  .video-shell.vertical::before {
    content: "";
    position: absolute; inset: -12% -34%;
    background: var(--ambient, linear-gradient(160deg, var(--navy), var(--gold-deep)));
    filter: blur(48px) saturate(140%);
    opacity: .55;
    border-radius: 48px;
    z-index: -1;
  }
}

/* ---------- Quiz ---------- */
.quiz-choice {
  display: block;
  width: 100%;
  text-align: left;
  /* [design REVIEW QUIZ SKELETON, HIGH] guarantee the 44px WCAG 2.5.5 tap
     target even where box-model/context collapses the row (review.html rendered
     these at 24px). min-height keeps the 52px designed feel; min-height wins. */
  min-height: 52px;
  padding: 14px 18px;
  margin-bottom: var(--sp-2);
  border-radius: var(--r-inner);
  /* [design REVIEW QUIZ SKELETON, HIGH] The base border was var(--glass-border)
     — white@.28 in light theme, i.e. an INVISIBLE hairline on review.html's
     white card (which does NOT load reader.css to enhance it), so options read
     as borderless loading skeletons. Give the BASE a real ink-derived hairline
     + a faint ground fill so a .quiz-choice looks like a tappable button
     EVERYWHERE. reader.css's .r-quiz .quiz-choice override still wins inside the
     reader; Studio surfaces gain a visible affordance too. */
  border: 1.5px solid color-mix(in srgb, var(--ink) 22%, transparent);
  background: color-mix(in srgb, var(--ground) 88%, var(--ink) 4%);
  box-shadow: 0 1px 2px rgba(12, 35, 64, .05);
  color: var(--ink);
  font-size: 1rem;
  transition: border-color var(--dur-micro), background var(--dur-micro),
              box-shadow var(--dur-micro);
}
/* [design REVIEW QUIZ SKELETON, HIGH] hover/press so it reads as a button on
   every surface (reader.css re-tints its own hover inside .r-quiz). */
@media (hover: hover) and (pointer: fine) {
  .quiz-choice:hover {
    border-color: color-mix(in srgb, var(--ink) 40%, transparent);
    background: color-mix(in srgb, var(--ground) 80%, var(--ink) 8%);
  }
}
.quiz-choice.correct { border-color: #2E7F73; background: color-mix(in srgb, #2E7F73 16%, transparent); }
.quiz-choice.wrong { border-color: var(--jewel-ruby); background: color-mix(in srgb, #A6192E 12%, transparent); }
.quiz-feedback { margin: var(--sp-3) 0; font-weight: 600; min-height: 1.4em; }

/* [design YOU-PAGE progress rows overflow @375px] Shared reflow helper for a
   "title + trailing single-line status" header line (the You-page track rows:
   .p-top = dot + <strong>title</strong> + .p-msg status). At wide widths the
   title and status share one baseline line (status pushed right); at ≤400px the
   title wrapped to 3 lines beside a clipped nowrap status. The full-width jewel
   bar already sits beneath in .p-main, so only this line needs to reflow.
   The You-page shell adds .rowflow to its .p-top and .rowflow-status to its
   .p-msg (FROZEN new names, additive — nothing renamed). Below the breakpoint
   the status drops under the title as its own single line; above it, unchanged. */
.rowflow {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-width: 0;
}
.rowflow > strong {
  min-width: 0;
  overflow-wrap: normal;
  word-break: normal;
  -webkit-hyphens: none;
  hyphens: none;
}
.rowflow-status { margin-left: auto; white-space: nowrap; }
@media (max-width: 400px) {
  .rowflow { flex-wrap: wrap; }
  /* force the status onto its own full line beneath the title, left-aligned,
     un-clipped (nowrap dropped so it can use the full width) */
  .rowflow-status {
    flex-basis: 100%;
    margin-left: 0;
    white-space: normal;
  }
}

/* ---------- Chips (interest picker, search zero-state) ---------- */
.chip {
  display: inline-flex; align-items: center;
  min-height: 44px;
  padding: 10px 20px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--glass-border);
  /* [P1-5] faint hairline-pill fill + inner top light so Popular/Recent read as
     real chips (matching the .track-chip idiom), not bare text links. */
  background: color-mix(in srgb, var(--ground) 60%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--glass-rim-top) 34%, transparent);
  color: var(--ink);
  font-weight: 600;
  margin: 0 8px 10px 0;
  transition: transform var(--dur-micro) var(--ease-spring),
              border-color var(--dur-micro) var(--ease-gentle),
              background var(--dur-micro) var(--ease-gentle),
              box-shadow var(--dur-micro) var(--ease-gentle);
}
@media (hover: hover) and (pointer: fine) {
  .chip:hover {
    border-color: color-mix(in srgb, var(--gold) 45%, var(--glass-border));
    transform: translateY(-1px);
  }
}
.chip:active { transform: translateY(0); }
.chip[aria-pressed="true"] {
  /* [P1-5] selected state uses the gold-metal CTA fill (not the raw jewel
     --accent override) so a pressed chip stays on-language with the buttons. */
  background: var(--cta-fill, var(--accent));
  color: var(--cta-ink, var(--ink-on-accent));
  border-color: var(--cta-fill, var(--accent));
  box-shadow: none;
}

/* ---------- Scroll reveal ---------- */
@keyframes rise { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: none; } }
.reveal { opacity: 0; }
.reveal.in-view { animation: rise var(--dur-enter) var(--ease-out) forwards; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; }
  .reveal.in-view { animation: none; }
}

/* ---------- Forms ---------- */
.field {
  width: 100%;
  min-height: 48px;
  padding: 12px 16px;
  border-radius: var(--r-inner);
  border: 1.5px solid var(--glass-border);
  background: color-mix(in srgb, var(--ground) 70%, transparent);
  color: var(--ink);
  font: inherit;
  /* [P2-4] a touch of glazed depth so a field reads as a real surface, not a
     hairline box — soft inner top light + a resting drop. Additive; page-scoped
     search elevation (.s-search .field) composes on top. */
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--glass-rim-top) 40%, transparent),
    0 1px 2px rgba(12, 35, 64, .05);
  transition: border-color var(--dur-micro) var(--ease-gentle),
              box-shadow var(--dur-micro) var(--ease-gentle);
}
/* [P2-4 / P2-6] focus lifts the field with a gold hairline + the shared,
   contrast-safe focus ring (ink light / gold dark, via --focus-ring). */
.field:focus {
  outline: 2px solid var(--focus-ring, var(--accent));
  outline-offset: 1px;
  border-color: color-mix(in srgb, var(--gold) 55%, var(--glass-border));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--glass-rim-top) 55%, transparent),
    0 4px 14px -6px color-mix(in srgb, var(--gold-deep) 40%, transparent);
}
label { display: block; font-size: .85rem; font-weight: 700; margin: var(--sp-3) 0 6px; }

/* honeypot — visually gone, present for bots */
.hp-field { position: absolute; left: -9999px; opacity: 0; height: 0; overflow: hidden; }

.sr-only { position: absolute; width: 1px; height: 1px; clip-path: inset(50%); overflow: hidden; }

/* Skip-to-content link (WCAG 2.4.1) — off-screen until keyboard focus, then it
   slides into the top-left. Theme-aware by construction: ink-on-ground in light,
   flips automatically in dark (both tokens invert). Modeled on .ssr-skip in
   worker/src/seo.ts so the app shells and the SSR twins read identically. */
.skip-link {
  position: fixed;
  left: 1rem;
  top: -4rem;
  z-index: 1000;
  padding: .7rem 1.1rem;
  border-radius: 10px;
  background: var(--ink);
  color: var(--ground);
  text-decoration: none;
  font-family: var(--font-grotesque);
  font-weight: 700;
  font-size: .9rem;
  letter-spacing: .01em;
  box-shadow: 0 8px 24px -6px rgba(0, 0, 0, .35);
  transition: top .18s var(--ease-gentle, ease);
}
.skip-link:focus {
  top: 1rem;
  outline: 2px solid var(--gold-bright);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

/* ============================================================
   DETAIL LAYER — the Apple micro-detail pass (Phase 2).
   Cross-page transitions, staggered reveals, specular hover
   physics, sticky local-nav, shimmer, selection & focus.
   ============================================================ */

/* ============================================================
   VIEW TRANSITIONS (v3 pass — owned by the transitions agent).
   Progressive enhancement, Chromium/Safari 18+; everything here
   scales by --motion-scale and dies under prefers-reduced-motion.

   THE NAME MAP (one source of truth — keep these from fighting):
   · root            — quiet page cross-fade (@view-transition).
   · hub-active-pill — nav highlight morph (glass.css).
   · hub-art         — THE cover-art morph. Exactly ONE element per
     page may carry it at any moment:
       – index/tracks/track pages assign it to the CLICKED cover
         (a .packet-card / .track-tile / .continue-cover) in a click
         handler just before navigation, clearing any prior holder;
       – track.html's hero (.t-hero) holds it statically as the
         inbound morph DESTINATION, and yields it (inline `none`)
         the moment a packet card is clicked;
       – packet.html's player stage may adopt it as its own inbound
         destination (player pass — see packet.js owner).
     Named elements are lifted out of the root snapshot by the
     browser, so the cross-fade and the art morph never double-play.
   ============================================================ */
@view-transition { navigation: auto; }

/* Root cross-fade: quick and quiet so it reads as a glaze, not an
   event — the art/pill morphs are the event. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: calc(.32s * var(--motion-scale, 1));
  animation-timing-function: var(--ease-gentle);
}

/* Text remains in the root cross-fade at its final raster size. Only artwork
   may own a geometry-morphing transition group; scaling a link or heading
   snapshot makes its glyphs visibly compact while the new page settles. */
:is(h1, h2, h3, a, button, .btn, .chip) { view-transition-name: none; }

/* hub-art: the group box glides card→hero on a gentle-out curve;
   both snapshots crop (never stretch) while the box interpolates
   between the card's 16:10 / 21:9 crop and the hero's field. */
::view-transition-group(hub-art) {
  animation-duration: calc(.52s * var(--motion-scale, 1));
  animation-timing-function: var(--ease-out);
}
::view-transition-image-pair(hub-art) {
  isolation: isolate;               /* clean cross-fade, no double-expose */
  border-radius: var(--r-card);
  overflow: clip;
}
::view-transition-old(hub-art),
::view-transition-new(hub-art) {
  height: 100%;
  width: 100%;
  object-fit: cover;
}

@media (prefers-reduced-motion: reduce) {
  /* no cross-fade, no morphs — pages just swap */
  ::view-transition-old(root),
  ::view-transition-new(root),
  ::view-transition-group(hub-art),
  ::view-transition-image-pair(hub-art),
  ::view-transition-old(hub-art),
  ::view-transition-new(hub-art) { animation: none !important; }
}
html { scroll-behavior: smooth; }

::selection { background: color-mix(in srgb, var(--gold) 35%, transparent); }
/* [P2-6] focus indicator clears 3:1: light mode rings in ink-navy (gold-on-bone
   was ~2.9:1); dark keeps gold (passes on navy). A 1px inverse offset via
   box-shadow separates the ring from any same-hue surface underneath. */
:focus-visible {
  outline: 2px solid var(--focus-ring, var(--accent));
  outline-offset: 2px;
  border-radius: 4px;
  box-shadow: 0 0 0 1px var(--ground);
}
@media (prefers-reduced-transparency: reduce) { :focus-visible { box-shadow: none; } }

/* Staggered entrance — delay set per element by revealOnScroll() in api.js */
.reveal { animation-delay: var(--stagger, 0s); }

/* Specular sheen sweep across packet cards on hover (pointer devices) */
.packet-card::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 40%, rgba(255,255,255,.18) 50%, transparent 60%);
  transform: translateX(-120%);
  pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
  .packet-card:hover::after { transition: transform calc(.9s * var(--motion-scale, 1)) var(--ease-out); transform: translateX(120%); }
}

/* Progress bars get a soft moving sheen while under 100% */
.progress > i { position: relative; overflow: hidden; }
.progress > i::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.35), transparent);
  transform: translateX(-100%);
  animation: shimmer calc(2.6s * var(--motion-scale, 1)) var(--ease-gentle) infinite;
}
@keyframes shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .progress > i::after { animation: none; } }

/* Sticky local-nav (apple.com product-nav): hidden until you scroll past the hero */
.local-nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 120;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  height: 52px;
  padding: 0 var(--section-x);
  background: color-mix(in srgb, var(--ground) 78%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  transform: translateY(-100%);
  transition: transform calc(.45s * var(--motion-scale, 1)) var(--ease-out);
}
.local-nav.pinned { transform: translateY(0); }
.local-nav .ln-title { font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; color: var(--ink); }
/* [P1-9] raise the sticky sub-bar pill to the 44px WCAG 2.5.5 target (was 34px);
   the local-nav bar is 52px tall so a 44px pill still clears its chrome. */
.local-nav .btn { min-height: 44px; padding: 8px 18px; font-size: .85rem; }
@media (max-width: 767px) { .local-nav { top: 0; } }
/* solid fallback, matching .glass's pattern in glass.css */
@media (prefers-reduced-transparency: reduce) {
  .local-nav {
    background: var(--glass-solid-fallback);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* Hero eyebrow underline flourish */
.hero .eyebrow::after {
  content: "";
  display: block;
  width: 42px; height: 2px;
  margin-top: 8px;
  background: linear-gradient(90deg, var(--gold-light), transparent);
}

/* Footer */
.site-footer {
  margin-top: var(--sp-7);
  padding: var(--sp-6) var(--section-x) calc(var(--sp-7));
  border-top: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
}
.site-footer .footnote { max-width: 62ch; }

/* Bottom-of-page escape routes. These sit directly on the page ground (no
   glass/backdrop-filter); the one gold hairline is metalwork, while all link
   text uses the theme's AA ink tokens. */
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin: 0 0 var(--sp-5);
}
.footer-nav a {
  display: inline-flex;
  align-items: center;
  gap: .65rem;
  min-height: 44px;
  color: var(--ink);
  font-family: var(--font-grotesque);
  font-size: .88rem;
  font-weight: 700;
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
}
.footer-nav__mark {
  color: var(--accent-text);
  font-family: var(--font-display);
  font-size: 1.05rem;
}
@media (hover: hover) and (pointer: fine) {
  .footer-nav a:hover { border-bottom-color: var(--accent-text); }
}

/* Packet-reader end navigation. The async teaser is after the stable actions,
   so inserting it changes only the document tail and never moves the controls
   a reader is already using. */
.reader .r-bottom-nav {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(1.25rem, 4vw, 2.5rem);
  margin-top: clamp(1.5rem, 4vw, 2.5rem);
  padding-top: 1px;
  text-align: left;
  background: var(--hairline-gold) top / 100% 1px no-repeat;
}
.reader .r-bottom-link {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: .8rem;
  min-height: 68px;
  padding: .9rem 0;
  color: var(--reader-ink, #23211E);
  text-decoration: none;
  border-bottom: 1px solid var(--reader-line, rgba(35, 33, 30, .14));
}
.reader .r-bottom-mark {
  color: var(--reader-clay, #8F5330);
  font-family: var(--font-display);
  font-size: 1.25rem;
  line-height: 1;
}
.reader .r-bottom-kicker {
  display: block;
  color: var(--reader-ink-soft, #67635C);
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.reader .r-bottom-title {
  display: block;
  margin-top: .18rem;
  color: var(--reader-ink, #23211E);
  font-family: var(--font-grotesque);
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: -.01em;
}
.reader .r-next-teaser:empty { display: none; }
@media (hover: hover) and (pointer: fine) {
  .reader .r-bottom-link:hover .r-bottom-title { color: var(--reader-clay, #8F5330); }
}
@media (max-width: 560px) {
  .reader .r-bottom-nav { grid-template-columns: 1fr; }
}

/* ============================================================
   GLAZED MAXIMALISM LAYER (2026-07, strictly additive).
   iTunes artwork-forward energy in Rhode-calm glazed chrome.
   Content layer carries the maximalism; chrome stays quiet;
   gold is a metal; one jewel per screen; rhythm not mixture.
   New-class names below are FROZEN CONTRACT (design/DIRECTION.md).
   ============================================================ */

/* ---------- .ambient-backdrop — the iTunes move, Rhode-clamped ----------
   Blurred oversaturated cover art behind a hero/stage. Consumer sets
   `--cover-url: url(...)` on this element (or a parent) and gives the HOST
   position:relative (the :where() helper below covers plain hosts).
   Uses filter, NOT backdrop-filter — exempt from the ≤3 glass budget. */
:where(*:has(> .ambient-backdrop)) {
  position: relative;
  isolation: isolate;
  overflow: clip;
}
.ambient-backdrop {
  position: absolute;
  inset: -12%;
  z-index: -1;
  background-image: var(--cover-url);
  background-size: cover;
  background-position: center;
  filter: blur(64px) saturate(180%);
  transform: translate3d(0, 0, 0) scale(1.08);
  pointer-events: none;
  animation: ambient-drift calc(60s * var(--motion-scale, 1)) ease-in-out infinite alternate;
}
/* Ground-clamp: ambience is a whisper, text stays ≥4.5:1
   (--ambient-clamp: 72% light / 55% dark, from tokens.css) */
.ambient-backdrop::after {
  content: "";
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--ground) var(--ambient-clamp, 72%), transparent);
}
@keyframes ambient-drift {
  from { transform: translate3d(-1.6%, -1%, 0) scale(1.06); }
  to   { transform: translate3d(1.6%, 1.2%, 0) scale(1.14); }
}

/* ---------- .grain — film-grain overlay ---------- */
.grain {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-image: var(--noise-url);
  background-size: 140px 140px;
  opacity: var(--grain-opacity, .04);
  pointer-events: none;
}

/* ---------- .cover-fallback — designed covers for art-less cards ----------
   Deep jewel gradient (near-black jewel → saturated) + faint gold radial +
   baked-in grain + a bottom legibility scrim. White Didot-ready.
   Uses NO pseudo-elements, so it composes with .packet-card's scrim/sheen
   and with .glaze-pass. */
.cover-fallback {
  position: relative;
  overflow: hidden;
  color: #fff;
  background-color: var(--cover-deep, #070b14);
  background-image:
    var(--noise-url-soft),
    linear-gradient(to bottom, rgba(3, 6, 12, 0) 46%, rgba(3, 6, 12, .42) 100%),
    radial-gradient(120% 90% at 84% 10%, rgba(203, 161, 78, .30), transparent 55%),
    linear-gradient(155deg, var(--cover-deep, #070b14) 0%, var(--cover-jewel, var(--jewel-sapphire)) 135%);
  background-size: 140px 140px, auto, auto, auto;
}
.cover--emerald  { --cover-jewel: var(--jewel-emerald);  --cover-deep: #03130b; }
.cover--aqua     { --cover-jewel: var(--jewel-aqua);     --cover-deep: #021413; }
.cover--sapphire { --cover-jewel: var(--jewel-sapphire); --cover-deep: #030d18; }
.cover--amethyst { --cover-jewel: var(--jewel-amethyst); --cover-deep: #100621; }
.cover--ruby     { --cover-jewel: var(--jewel-ruby);     --cover-deep: #170409; }
.cover--coral    { --cover-jewel: var(--jewel-coral);    --cover-deep: #190604; }
.cover--jasper   { --cover-jewel: var(--jewel-jasper);   --cover-deep: #191102; }
.cover--gold     { --cover-jewel: var(--gold-bright);    --cover-deep: #151004; }

/* ---------- .glaze-pass — one-shot specular sheen sweep ----------
   Fires on hover (fine pointers) or when JS adds .is-entering. */
.glaze-pass { position: relative; overflow: hidden; }
.glaze-pass::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, .30) 50%, transparent 60%);
  transform: translateX(-130%);
  pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
  /* transition only on the hover state = sweeps once, resets silently */
  .glaze-pass:hover::after {
    transform: translateX(130%);
    transition: transform calc(.9s * var(--motion-scale, 1)) var(--ease-out);
  }
}
.glaze-pass.is-entering::after {
  animation: glaze-sweep calc(1.1s * var(--motion-scale, 1)) var(--ease-out) 1 both;
}
@keyframes glaze-sweep {
  from { transform: translateX(-130%); }
  to   { transform: translateX(130%); }
}

/* ---------- .rule-gold — gold hairline under Didot titles ---------- */
.rule-gold {
  display: block;
  border: 0;
  width: 100%;
  height: 1px;
  margin: var(--sp-2) 0 var(--sp-3);
  background: var(--hairline-gold);
}
/* [P2-1] title + rule read as ONE unit: when a gold rule sits directly under a
   Didot heading, tighten the top gap so the cadence matches the page-title
   treatment everywhere it's applied (Home "Up next", Library category heads). */
:is(h1, h2, .display, .shelf-header) + .rule-gold { margin-top: 6px; }

/* ---------- .stat-numeral — the big-number unit ----------
   A confident stats-page numeral (mac-stats "100M+ / Downloads" energy,
   rendered in our Didot/gold idiom): huge lining tabular figures so digits
   sit on a shared metric column, tight optical set, generous air around it.
   Used bare (progress hero, quiz score) and — additively — inside .stat-unit
   with a .stat-label eyebrow. The completion ring re-scopes this (packet.html
   `.completion-ring .stat-numeral`); nothing here fights that override. */
.stat-numeral {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: calc(clamp(4rem, 12vw + 1rem, 9rem) * var(--type-scale, 1));
  line-height: .9;
  letter-spacing: -.035em;
  color: var(--accent-text);
  /* tabular so every digit occupies one column — the credibility tell */
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
  /* [polish] consistency: never let a fallback serif synthesize a faux-700 for
     the hero figure — a mushy bold would break the "credibility tell". The
     Didot/Playfair stack ships a real 700; block synthesis so weaker fallbacks
     render at their true weight instead. */
  font-synthesis-weight: none;
}
/* the trailing <small> is a numeric unit fragment (/5, %) — a quiet, still-
   tabular sub-mark that pairs with the figure without competing. (A descriptive
   LABEL is a different job — that's .stat-label, or the page-scoped eyebrow
   treatment on progress.html's "of N".) */
.stat-numeral small {
  font-size: .34em;
  font-weight: 700;
  letter-spacing: -.01em;
  margin-left: .05em;
  font-variant-numeric: tabular-nums lining-nums;
}

/* ---------- .stat-unit — optional big-number + label pairing (additive) ----
   Wrap a .stat-numeral with a .stat-label to compose a labeled statistic the
   mac-stats way: the number leads, a small UPPERCASE gold eyebrow names it,
   tightly paired with real space around the pair. Default is stacked (numeral
   over label); .stat-unit--inline sets them side-by-side, baseline-optical. */
.stat-unit {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-1);
  margin: var(--sp-3) 0 var(--sp-4);
}
.stat-unit .stat-numeral { margin: 0; }
.stat-label {
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent-text);
  line-height: 1.2;
  max-width: 22ch;
}
.stat-unit--inline {
  flex-direction: row;
  align-items: baseline;
  gap: var(--sp-2);
}
/* inline label drops back to a hanging caption so the numeral still dominates */
.stat-unit--inline .stat-label { align-self: center; max-width: 12ch; }

/* ---------- .stat-cite — a number backed by its citation (additive) --------
   THE CREDIBILITY MOMENT: a .stat-numeral (or .stat-unit) sitting directly
   above an .rt-foot reads as a citation-backed statistic — the footnote's gold
   hairline becomes the number's evidence line. Purely presentational; the
   richtext renderer contract is untouched. Works whether the pairing is placed
   by hand (this wrapper) OR emerges when a .stat-numeral happens to precede an
   .rt-foot in flow (the adjacency rule below). */
.stat-cite {
  display: block;
  margin: var(--sp-3) 0 var(--sp-4);
  max-width: min(var(--rt-measure, 66ch), 100%);
}
.stat-cite .stat-numeral,
.stat-cite .stat-unit { margin: 0; }
/* pull the citation up under the figure: the number and its source are ONE
   unit, so the gap tightens from the footnote's default sp-4 to sp-2, and the
   hairline sits close enough to read as "this figure is sourced." */
.stat-numeral + .rt-foot,
.stat-unit + .rt-foot,
.stat-cite .rt-foot {
  margin-top: var(--sp-2);
}

/* ---------- .shelf-header — iTunes shelf section header ----------
   Sized/padded to pair with a full-bleed .rail below it; if you place it
   inside .wrap (already padded), the padding doubles — keep it beside rails. */
.shelf-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 0 var(--section-x);
  margin-bottom: var(--sp-1);
}
.shelf-header :is(h2, h3) {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.15;
  font-size: calc(clamp(1.35rem, 2vw + .6rem, 1.75rem) * var(--type-scale, 1));
}
.shelf-header a {
  flex: none;
  font-size: .9rem;
  font-weight: 600;
  color: var(--accent-text);
  text-decoration: none;
  white-space: nowrap;
  transition: opacity var(--dur-micro) var(--ease-gentle);
}
@media (hover: hover) and (pointer: fine) {
  .shelf-header a:hover { text-decoration: underline; text-underline-offset: 3px; }
}

/* ---------- .track-chip — jewel dot + label pill ----------
   Reads var(--track-jewel) from a bg-* ancestor OR an inline
   style="--track-jewel:#…" on the chip itself; defaults to gold.
   Tint is recomputed locally so inline jewels get a matching wash. */
.track-chip {
  --track-tint-08: color-mix(in oklch, var(--track-jewel, var(--gold)) 8%, var(--ground));
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 30px;
  padding: 5px 14px 5px 10px;
  border-radius: var(--r-pill);
  border: 1px solid color-mix(in srgb, var(--track-jewel, var(--gold)) 34%, transparent);
  background: var(--track-tint-08);
  color: var(--ink);
  font-size: var(--text-caption);
  font-weight: 600;
  letter-spacing: .02em;
  white-space: nowrap;
}
.track-chip::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--track-jewel, var(--gold));
  box-shadow: inset 0 0 0 1px rgba(4, 10, 20, .12);
}

/* ---------- Section rhythm: .band / .band--art ----------
   Calm editorial section → full-bleed artwork moment → calm section. */
.band {
  padding: var(--section-y) var(--section-x);
  background: var(--ground-alt);
}
.band--art {
  position: relative;
  isolation: isolate;
  overflow: clip;
  padding: calc(var(--section-y) * 1.15) var(--section-x);
  background-color: var(--track-deep, var(--navy));
  background-image: var(--cover-url);
  background-size: cover;
  background-position: center;
  color: #fff;
}
.band--art::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--hero-overlay);
  pointer-events: none;
}
.band--art::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--noise-url);
  background-size: 140px 140px;
  opacity: var(--grain-opacity, .04);
  pointer-events: none;
}
.band--art > * { position: relative; z-index: 1; }
.band--art .eyebrow { color: var(--gold-light); }

/* Putty/alt surfaces re-point secondary + accent text at their darker
   companions (tokens.css) so contrast holds ≥4.5:1 on --ground-alt.
   Dark-mode companions equal the base values, so this is a light-only shift. */
.section--alt, .band {
  --ink-secondary: var(--ink-secondary-alt);
  --accent-text: var(--accent-text-alt);
}

/* ---------- Elevation: .packet-card as cover art ---------- */
.packet-card {
  box-shadow: var(--shadow-card, 0 10px 30px rgba(12, 35, 64, .18));
}
/* [P1-2] Stronger, eased scrim curve with a lifted mid-stop + deeper floor —
   white cover titles stay legible over LIGHT-bottomed imagery (crystal /
   pink / aqua covers where the old .74 floor read ~2.5:1). */
.packet-card::before {
  background: linear-gradient(to bottom,
    rgba(4, 10, 20, .04) 0%,
    rgba(4, 10, 20, .06) 34%,
    rgba(4, 10, 20, .22) 54%,
    rgba(4, 10, 20, .46) 72%,
    rgba(4, 10, 20, .68) 86%,
    rgba(4, 10, 20, .82) 100%);
}
/* [P1-2] belt-and-suspenders: a soft ink text-shadow on cover titles/subs so a
   stray bright pixel under the glyphs can't drop contrast below AA. */
.packet-card .meta h3,
.packet-card .meta .sub,
.packet-card .card-title {
  text-shadow: 0 1px 2px rgba(4, 10, 20, .5), 0 1px 12px rgba(4, 10, 20, .28);
}
@media (hover: hover) and (pointer: fine) {
  .packet-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-card-hover, 0 20px 44px rgba(12, 35, 64, .28));
  }
}

/* ---------- Elevation: jewel-tinted progress ----------
   Default is EXACTLY the current gold gradient (mixing a color with itself
   is identity). When an ancestor sets --track-jewel (bg-* classes do), the
   fill leans toward the track's jewel — gold stays the metal underneath. */
.progress > i {
  background: linear-gradient(90deg,
    color-mix(in oklch, var(--track-jewel, var(--gold-deep)) 40%, var(--gold-deep)),
    color-mix(in oklch, var(--track-jewel, var(--accent)) 55%, var(--accent)));
}

/* ---------- Elevation: quiz selection micro-motion ---------- */
.quiz-choice {
  position: relative;
  transition: border-color var(--dur-micro), background var(--dur-micro),
              box-shadow var(--dur-micro) var(--ease-spring);
}
.quiz-choice:active { transform: none; }
.quiz-choice.correct {
  padding-right: 46px;
  animation: choice-affirm calc(.5s * var(--motion-scale, 1)) var(--ease-spring) 1;
}
/* emerald tick reveal — UI glyph, not content copy */
.quiz-choice.correct::after {
  content: "\2713";
  position: absolute;
  right: 16px;
  top: 50%;
  translate: 0 -50%;
  font-weight: 700;
  color: color-mix(in oklch, var(--jewel-emerald) 70%, var(--ink));
  animation: tick-reveal calc(.35s * var(--motion-scale, 1)) var(--ease-spring);
}
.quiz-choice.wrong {
  animation: choice-shake calc(.42s * var(--motion-scale, 1)) var(--ease-out) 1;
}
@keyframes choice-affirm {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, #2E7F73 35%, transparent); }
  45%  { box-shadow: 0 0 0 6px color-mix(in srgb, #2E7F73 18%, transparent); }
  100% { box-shadow: 0 0 0 12px transparent; }
}
@keyframes tick-reveal { from { opacity: 0; scale: .3; } }
@keyframes choice-shake {
  0%, 100% { transform: translateX(0); }
  22%      { transform: translateX(-5px); }
  48%      { transform: translateX(4px); }
  74%      { transform: translateX(-2px); }
}

/* ---------- Elevation: sheet grab-handle (phone only) ---------- */
.sheet::before {
  content: "";
  position: absolute;
  top: 10px;
  left: 50%;
  translate: -50% 0;
  width: 44px;
  height: 5px;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--glass-ink) 26%, transparent);
}
@media (min-width: 768px) { .sheet::before { display: none; } }

/* ---------- Elevation: designed footer sign-off ---------- */
.site-footer {
  position: relative;
  border-top-color: transparent;
}
.site-footer::before {
  content: "";
  position: absolute;
  top: -1px;
  left: var(--section-x);
  right: var(--section-x);
  height: 1px;
  background: var(--hairline-gold);
}
.footer-wordmark {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: calc(1.5rem * var(--type-scale, 1));
  letter-spacing: .01em;
  line-height: 1.15;
  color: var(--ink);
  margin-bottom: var(--sp-2);
}

/* ---------- footer language switcher (chrome only; lessons stay English) ---------- */
.footer-lang {
  display: flex; align-items: center; flex-wrap: wrap; gap: .1rem .35rem;
  margin-top: var(--sp-4);
}
.footer-lang .flang-label { font-size: .85rem; opacity: .55; margin-right: .35rem; }
.footer-lang .flang {
  font-family: var(--font-mono, monospace);
  font-size: .7rem; font-weight: 600; letter-spacing: .1em;
  color: var(--ink-secondary); background: none; border: 0; padding: .25rem .3rem;
  cursor: pointer; border-radius: 4px; line-height: 1;
}
.footer-lang .flang:hover { color: var(--ink); }
.footer-lang .flang.is-on { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
.footer-lang .flang-sep { color: var(--ink-secondary); opacity: .4; font-size: .7rem; }
@media (hover: hover) and (pointer: fine) {
  .footer-lang .flang { transition: color .15s; }
}
.footer-lang-note { margin-top: var(--sp-1) !important; font-style: italic; opacity: .7; }

/* ---------- Accessibility fallbacks for everything above ---------- */
@media (prefers-reduced-motion: reduce) {
  .ambient-backdrop { animation: none; }
  .glaze-pass::after,
  .glaze-pass:hover::after,
  .glaze-pass.is-entering::after { transition: none !important; animation: none !important; }
  .quiz-choice, .packet-card, .track-chip, .shelf-header a,
  .btn, .chip, .field { transition: none; }
  .quiz-choice.correct,
  .quiz-choice.wrong,
  .quiz-choice.correct::after { animation: none; }
  .quiz-choice:active { transform: none; }
  .packet-card:hover { transform: none; }
  /* [polish] neutralize the new hover/active lifts so nothing snaps */
  .btn:hover, .btn:active,
  .chip:hover, .chip:active { transform: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .ambient-backdrop {
    background-image: none;
    background-color: var(--ground-alt);
    filter: none;
    animation: none;
  }
  .ambient-backdrop::after { background: transparent; }
  .grain, .band--art::after { display: none; }
  .glaze-pass::after { display: none; }
}

/* ============================================================
   READING LAYER (2026-07-04, strictly additive).
   Type rhythm for body copy + the frozen micro-markup convention
   (design/DIRECTION.md → "The Reading Layer"). The renderer emits:
     **…**  → <strong>            *…*  → <em>
     \n\n   → <p> paragraphs      "→ " line → .rt-step row
     trailing "† " line → .rt-foot citation block
   Everything below must read beautifully BOTH ways: plain un-marked
   text (a single <p>, no markers yet) and fully marked-up text.
   All .rt-* names are new and additive; nothing existing is renamed.
   No new motion is introduced (nothing for prefers-reduced-motion
   to disable) and no new transparency effects (hairlines only).
   ============================================================ */

/* ---------- .richtext — the reading container ----------
   Measure in the 62–68ch band, Avenir-tuned leading, paragraph
   spacing clearly wider than the line spacing (.95em vs the ~.55em
   leading gap), never justified. */
.richtext {
  max-width: min(var(--rt-measure, 66ch), 100%);
  line-height: 1.55;             /* Avenir Next's tall x-height wants air */
  text-align: start;             /* never justify body copy */
  text-wrap: pretty;             /* progressive: kinder rag, fewer widows */
  overflow-wrap: break-word;     /* long INCI names on 320px screens */
}
.richtext p { margin: 0; }
.richtext p + p { margin-top: .95em; }

/* ---------- emphasis: strong = emphasis-weight ink, em = true italic --
   strong is the phrase a skimmer must catch: weight over color — NEVER
   gold (gold stays a metal, law 4). 600 is Avenir Next's crisp Demi
   face (--rt-strong-weight in tokens.css documents the 650 story);
   font-synthesis-weight blocks mushy faux-bold on fallback stacks. */
.richtext strong, .sheet strong, .quiz-feedback strong, .done-card strong {
  font-weight: var(--rt-strong-weight, 600);
  color: var(--ink);
  font-synthesis-weight: none;
}
.richtext em, .sheet em, .quiz-feedback em, .learn-card .body em {
  font-style: italic;            /* Avenir Next carries true italics */
}
.richtext a, .rt-foot a, .quiz-feedback a {
  color: inherit;
  text-decoration-color: var(--reader-clay, var(--gold));
  text-decoration-thickness: 1px;
  text-underline-offset: .2em;
}
.richtext a:hover, .rt-foot a:hover, .quiz-feedback a:hover {
  color: var(--reader-clay, var(--gold));
}
.richtext a:focus-visible, .rt-foot a:focus-visible, .quiz-feedback a:focus-visible {
  outline: 2px solid var(--reader-clay, var(--gold));
  outline-offset: 3px;
}

/* ---------- .rt-lead — opening paragraph, one step up ---------- */
.rt-lead {
  font-size: calc(var(--text-body) * 1.12);
  line-height: 1.5;
  letter-spacing: .005em;        /* slightly looser at the larger size */
}

/* ---------- depth sheet adopts the reading metrics ----------
   packet.js's sheet strings are verbatim content — the polish lands
   purely via CSS on the existing .sheet structure. */
.sheet { line-height: 1.55; }
.sheet p { max-width: min(var(--rt-measure, 66ch), 100%); }
.sheet p + p { margin-top: .95em; }
/* "The science" expanded body is the longest read in the product —
   a touch more leading than the pro tier */
.sheet .sci-inner { line-height: 1.6; }
/* Tier chips breathe: air above the chip cluster and a beat before
   the gold rule, so the body text below never feels crowded (chip
   fill/border/ink live in packet.html — margins only here) */
.sheet .depth-chip { margin-top: var(--sp-1); margin-bottom: 2px; }

/* ---------- .rt-step — protocol sequence rows ----------
   "→ " lines become numbered rows: a small tabular gold Didot
   numeral in a fixed gutter, step text alongside, and a faint gold
   spine connecting consecutive steps. Works as
   <ol class="rt-steps"><li class="rt-step"> OR as bare consecutive
   .rt-step elements (CSS counters number sibling runs either way). */
.rt-steps {
  list-style: none;
  margin: var(--sp-3) 0;
  padding: 0;
  counter-reset: rt-step;
}
/* bare runs need a reset scope too — an increment that lives on the
   ::before would implicitly scope INSIDE each host and every numeral
   would read "1", so the increment sits on the element and the
   containers reset. (Two bare sequences in one container number
   continuously; wrap each in .rt-steps to restart.) */
.richtext, .sheet { counter-reset: rt-step; }
/* [both-design rt-step GRID BUG, HIGH] Was `display:grid` with a
   2-column (gutter | 1fr) template. Grid makes EACH direct child its own
   grid item, so a step whose text contains a mid-line **bold** phrase emitted
   the loose run `text + <strong> + text` as THREE items: the <strong> landed in
   the 2.4em numeral column and wrapped vertically ("Cli/mat/e/Con/trol"),
   colliding with the body. Steps without bold happened to render fine.
   FIX: drop grid entirely. The row is now a BLOCK whose text (all inline nodes:
   plain text, <strong>, <em>) flows as ONE normal line; the counter ::before is
   ABSOLUTELY positioned into a left gutter carved by padding-left. The numeral
   is the only thing in the gutter, and mid-step bold now flows inline like any
   other body <strong>. --rt-step-gutter name + value are unchanged. */
.rt-step {
  position: relative;
  display: block;
  padding-left: var(--rt-step-gutter, 2.4em);
  padding-bottom: var(--sp-2);
  counter-increment: rt-step;
}
.rt-step:last-child { padding-bottom: 0; }
.rt-step::before {
  content: counter(rt-step);
  /* seat the numeral in the carved gutter, centered on its column, baseline
     aligned with the first text line (the row's own line-height) */
  position: absolute;
  left: 0;
  top: 0;
  width: var(--rt-step-gutter, 2.4em);
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: .9em;
  line-height: inherit;          /* align the figure to the first text line */
  font-variant-numeric: tabular-nums lining-nums;
  /* the small-gold-that-clears-WCAG recipe (matches .card-index):
     deepens toward ink in light, lightens toward ink in dark */
  color: color-mix(in srgb, var(--accent-text) 72%, var(--ink));
}
/* the connecting spine: numeral to numeral through the row gap.
   Flat low alpha — the fading --hairline-gold would read dashed here.
   :has() keeps it BETWEEN steps only (no stub before a footnote);
   browsers without :has() simply skip the spine — rows still work. */
.rt-step:has(+ .rt-step)::after {
  content: "";
  position: absolute;
  left: calc(var(--rt-step-gutter, 2.4em) / 2);
  top: 1.5em;
  bottom: .1em;
  width: 1px;
  background: rgba(156, 138, 90, .35);
  pointer-events: none;
}
/* rhythm around a sequence when bare steps sit between paragraphs
   (inside .richtext/.sheet, p + p’s .95em ≈ the same beat — either
   winning keeps the spacing consistent) */
p + .rt-step, .rt-step + p { margin-top: var(--sp-3); }
/* compact on phones: narrower gutter, tighter rows */
@media (max-width: 479px) {
  .rt-step { --rt-step-gutter: 1.9em; padding-bottom: var(--sp-1); }
  .rt-step:last-child { padding-bottom: 0; }
}

/* ---------- .rt-foot — citation / footnote block ----------
   A proper academic footnote: caption size, secondary ink, gold
   hairline top rule, hanging dagger. */
.rt-foot {
  margin-top: var(--sp-4);
  font-size: var(--text-caption);
  line-height: 1.6;
  font-weight: 400;
  color: var(--ink-secondary);
  padding-left: 1.15em;
  text-indent: -1.15em;          /* the † hangs; runover lines align */
  max-width: min(var(--rt-measure, 66ch), 100%);
}
/* boosted selectors so the sp-4 gap outranks .richtext/.sheet p + p */
.richtext p + .rt-foot, .sheet p + .rt-foot { margin-top: var(--sp-4); }
.rt-foot::before {
  content: "";
  display: block;
  height: 1px;
  /* pull the rule back across the hanging indent to the true edge */
  width: calc(100% + 1.15em);
  margin: 0 0 var(--sp-1) -1.15em;
  background: var(--hairline-gold);
}
/* consecutive citations: one shared rule, quieter gaps */
.rt-foot + .rt-foot { margin-top: var(--sp-2); }
.rt-foot + .rt-foot::before { display: none; }

/* ---------- learning-card body — display-adjacent text ----------
   ≤140 chars at 1.09rem: open the leading a touch, tighten tracking,
   and let **key phrases** carry the eye with weight + a whisper of
   gold underline (a rule/ornament — law 4 — NEVER gold text). */
.learn-card .body {
  line-height: 1.6;
  letter-spacing: -.01em;
  max-width: min(var(--rt-measure, 66ch), 100%);
}
.learn-card .body strong {
  font-weight: var(--rt-strong-weight, 600);
  color: var(--ink);
  font-synthesis-weight: none;
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  /* --hairline-gold is a gradient (can't feed decoration-color):
     use its peak stop at the same alpha */
  text-decoration-color: rgba(156, 138, 90, .55);
  text-decoration-skip-ink: auto;
}

/* ---------- quiz explanations + completion recap ----------
   Reading metrics at caption-plus size. (.quiz-feedback is shared
   with the Studio — these are pure value tunes, no renames.)
   Weight eases 600 → 500: at the original all-600 setting, a
   **key phrase** inside an explanation rendered invisibly (strong
   is also 600); Medium body + Demi strong restores the emphasis
   the markup convention promises, and status lines stay firm. */
.quiz-feedback {
  font-size: var(--text-caption-plus, .9375rem);
  line-height: 1.55;
  font-weight: 500;
  max-width: min(var(--rt-measure, 66ch), 100%);
}
.done-card .secondary {
  font-size: var(--text-caption-plus, .9375rem);
  line-height: 1.55;
  max-width: min(48ch, 100%);
  margin-inline: auto;           /* the completion card is centered */
}

/* [P2-2] completion "moment" bloom — a low-alpha gold radial rising behind the
   ring so the panel warms from a flat metallic gray into a quiet celebratory
   beat (still zero gamification: no confetti, no color pops — just gold as
   metal catching light). z-index:-1 seats it ABOVE the .glass fill (level 1)
   but BELOW all in-flow content (level 3) — so it can never veil the ring or
   completion copy, whether those children are static OR positioned. .glass's
   translateZ already makes .done-card a stacking context, so the negative-z
   pseudo stays inside this card (isolation reinforces it). It never touches
   packet.html's absolute .completion-glaze overlay or the .glass::after sheen. */
.done-card { isolation: isolate; }
.done-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background:
    radial-gradient(120% 78% at 50% 30%,
      color-mix(in srgb, var(--gold) 22%, transparent) 0%,
      color-mix(in srgb, var(--gold) 7%, transparent) 34%,
      transparent 64%);
}
@media (prefers-reduced-transparency: reduce) {
  /* keep the warmth but drop the translucent wash to a solid faint tint */
  .done-card::before {
    background: color-mix(in srgb, var(--gold) 6%, transparent);
  }
}

/* [Fix 7 completion-moment hooks — additive, packet shell opts in] The review
   found the completion "moment" under-built vs DIRECTION (gold ring + Didot
   numeral + track recap + shimmer). The bloom above + .stat-numeral already
   exist; these are the remaining SHARED, additive hooks the packet shell can
   apply. Nothing existing is renamed; .completion-ring's own layout stays in
   packet.html — this only supplies a canonical gold META ring frame and a
   quiet track-recap row so the moment doesn't have to reinvent them. */

/* .gold-ring — a metal ring frame (gold-as-metal, law 4: a rim, never a fill).
   Wrap the completion stat numeral; the ring is a conic gold hairline with a
   soft inner burnish. Works in both themes (gold reads on bone and navy). */
.gold-ring {
  position: relative;
  display: grid;
  place-items: center;
  width: min(220px, 62vw);
  aspect-ratio: 1;
  margin: var(--sp-3) auto var(--sp-4);
  border-radius: 50%;
  isolation: isolate;
}
.gold-ring::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  padding: 2px;                    /* ring thickness */
  background: conic-gradient(from 220deg,
    color-mix(in srgb, var(--gold) 30%, transparent),
    color-mix(in srgb, var(--gold-bright, var(--gold)) 85%, transparent),
    color-mix(in srgb, var(--gold-light, var(--gold)) 95%, transparent),
    color-mix(in srgb, var(--gold-bright, var(--gold)) 85%, transparent),
    color-mix(in srgb, var(--gold) 30%, transparent));
  /* mask so only the rim shows (the fill window stays open for the numeral) */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  pointer-events: none;
}
/* a whisper of inner burnish so the disc catches light, not a flat hole */
.gold-ring::after {
  content: "";
  position: absolute;
  inset: 6px;
  border-radius: 50%;
  z-index: -1;
  background: radial-gradient(90% 90% at 50% 32%,
    color-mix(in srgb, var(--gold) 12%, transparent),
    transparent 70%);
  pointer-events: none;
}
/* the numeral inside the ring sits tight and centered (re-scopes the base
   .stat-numeral the way packet.html's `.completion-ring .stat-numeral` does) */
.gold-ring .stat-numeral { margin: 0; line-height: 1; text-align: center; }

/* .completion-recap — the quiet "3 of 7 in <track> · that's the read" row.
   A centered mono caption with a gold hairline crown, reusing the reader's
   fine-print idiom. Track recap text is authored by the shell (no content
   here — presentation only). */
.completion-recap {
  margin: var(--sp-4) auto 0;
  max-width: min(46ch, 100%);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  line-height: 1.6;
  letter-spacing: .02em;
  color: var(--ink-secondary);
}
.completion-recap::before {
  content: "";
  display: block;
  width: 42px;
  height: 1px;
  margin: 0 auto var(--sp-2);
  background: var(--hairline-gold);
}

/* the completion bloom's gold radial can gently pulse ONCE on entry — the
   "shimmer" the review asked for, still zero-gamification (one slow breath of
   light, no confetti). The shell adds .is-complete when the moment mounts. */
@keyframes completion-bloom {
  from { opacity: 0; transform: scale(.9); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: scale(1); }
}
.done-card.is-complete::before {
  animation: completion-bloom calc(1.1s * var(--motion-scale, 1)) var(--ease-out) 1 both;
}
@media (prefers-reduced-motion: reduce) {
  .done-card.is-complete::before { animation: none; }
}

/* ---------- READING LAYER · global audit ----------
   Surgical value tweaks only — no renames, no structural changes.
   · .eyebrow letter-spacing: AUDITED, no change — a single class
     carries .14em everywhere (pages only recolor it or restyle its
     ::after flourish); already consistent.
   · .hero-title: -.02em at --text-hero (up to 6.5rem ≈ -2.1px)
     pinches Didot's hairline serifs; eased to -.015em at hero scale
     only. h1/h2/.display top out at title scale, where -.02em holds.
   · .packet-card titles: the 2-line clamps (home shelves, track
     tiles) inherited the body's 1.5 leading — too airy over artwork
     and off-pattern with every other display size (1.08–1.16).
   · .secondary: aligned to the reading layer's Avenir leading. */
.hero-title { letter-spacing: -.015em; }
.packet-card .meta h3 { line-height: 1.18; }
.secondary { line-height: 1.55; }

/* ---------- no hyphenation under thumbs ----------
   (hyphens already default to manual — pinned here per the contract
   so no future `hyphens:auto` experiment can leak into reading text) */
@media (pointer: coarse) {
  .richtext, .sheet, .learn-card .body, .quiz-feedback, .rt-foot {
    -webkit-hyphens: none;
    hyphens: none;
  }
}

/* ============================================================
   CARD LAYOUTS (Studio v3 "visual freedom", 2026-07 — additive).
   OWNER: packet renderer agent (packet.js). Coordinate here by
   header — the transitions agent owns view-transition/motion
   sections; the Studio agent does not touch this file.
   cards.layout drives the packet player's card composition:
     (null)/'top'  base .learn-card rules above — UNCHANGED
     'wrap'        .learn-card--wrap   (media floats, text wraps)
     'float'       .learn-card--float  (media is the card; ONE
                   glass panel replaces the card's own glass, so
                   the ≤3 backdrop-filter budget is untouched)
     'specimen'    .learn-card--specimen (crystal vitrine, below)
   Per-theme accent plumbing: packet.js validates free-hex accents
   and sets --track-accent-light/--track-accent-dark on :root;
   --layout-accent resolves the right one per theme (gold fallback)
   so rims/labels can tint to the owner's accent. bg-* classes stay
   the ambience/DB contract — nothing here renames or replaces them.
   ============================================================ */

.learn-card { --layout-accent: var(--track-accent-light, var(--gold)); }
[data-theme="dark"] .learn-card {
  --layout-accent: var(--track-accent-dark, var(--gold-bright)); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .learn-card {
    --layout-accent: var(--track-accent-dark, var(--gold-bright)); }
}

/* ---------- 'wrap' — editorial float, text wraps the art ---------- */
.learn-card--wrap .card-media--wrap {
  float: right;
  width: 44%;
  aspect-ratio: 4 / 5;                /* portrait plate suits a side float */
  margin: 4px 0 var(--sp-3) var(--sp-4);
  border-radius: var(--r-inner);
  overflow: hidden;
  shape-outside: margin-box;          /* wrap follows the rounded corners */
}
.learn-card--wrap .card-media--wrap .card-img {
  width: 100%; height: 100%;
  aspect-ratio: auto;                 /* the wrapper owns the ratio */
  object-fit: cover; display: block; margin: 0;
}
.learn-card--wrap .card-media--wrap.cover-fallback { aspect-ratio: 4 / 5; }
.learn-card--wrap .card-headline { margin-top: var(--sp-2); }
/* the action row must sit BELOW the float, hairline at full width */
.learn-card--wrap .card-actions { clear: both; }
/* phones: the float would starve the measure — stack like 'top' */
@media (max-width: 599px) {
  .learn-card--wrap .card-media--wrap {
    float: none; width: 100%;
    aspect-ratio: 16 / 10;
    margin: 0 0 var(--sp-4);
    shape-outside: none;
  }
  .learn-card--wrap .card-media--wrap.cover-fallback { aspect-ratio: 16 / 10; }
}

/* ---------- 'float' — the image IS the card ---------- */
.learn-card--float {
  position: relative;
  padding: 0;                          /* the panel carries the padding */
  min-height: clamp(430px, 68dvh, 640px);
  display: flex; flex-direction: column; justify-content: flex-end;
  border-radius: var(--r-card);
  overflow: clip;
  isolation: isolate;
  /* card-grade depth + a gold hairline so the bare image still reads
     as a designed object (box-shadow rim = zero glass-budget cost) */
  box-shadow:
    var(--glass-shadow),
    0 24px 48px -12px rgba(0, 0, 0, .5),
    inset 0 0 0 1px color-mix(in srgb, var(--gold) 32%, transparent);
}
.card-float-media { position: absolute; inset: 0; border-radius: inherit; overflow: hidden; }
.card-float-media > img {
  width: 100%; height: 100%; object-fit: cover; display: block; }
/* legibility scrim rising under the panel (Rhode clamp, image-side) */
.card-float-media::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to top,
    rgba(4, 10, 20, .46), rgba(4, 10, 20, .10) 48%, rgba(4, 10, 20, 0) 72%);
}
.learn-card--float > .grain { z-index: 1; }
.card-float-panel {
  position: relative; z-index: 2;
  margin: var(--sp-2);
  padding: var(--sp-4);
  border-radius: calc(var(--r-card, 24px) - var(--sp-2));  /* concentric */
}
.card-float-panel .card-actions { margin-top: var(--sp-3); }

/* ============================================================
   SPECIMEN — the crystal-glass vitrine (hero of the v3 build).
   Reads "laboratory specimen", not "photo frame": the art fills a
   plate behind frosted glass. The frost is a PRE-BLURRED copy of
   the same art (filter — exempt from the ≤3 backdrop-filter
   budget); only its outer ring shows around the inset crisp plate,
   so it reads as a frosted mat. A 1px inner crystal rim + faceted
   corner glints edge the glass; grain keeps it filmic; a gold-
   hairline slide label (the card TITLE — no new content field)
   sits beneath like a lab-slide caption. Layer order inside
   .specimen-vitrine: frost(0) → wash(1) → plate(2) → rim(3) →
   grain(4). Optional refraction: packet.html applies an SVG
   displacement to .specimen-frost under @supports (the url(#…)
   rule must live in the DOCUMENT so the fragment resolves there).
   Works in both themes — every color derives from theme tokens.
   ============================================================ */
.learn-card--specimen { padding-top: var(--sp-4); }
.specimen-vitrine {
  position: relative;
  margin: 0 0 var(--sp-3);
  aspect-ratio: 16 / 11;
  border-radius: var(--r-inner);
  overflow: hidden;
  isolation: isolate;
  /* the vitrine sits ON the card like an object on a bench */
  box-shadow:
    0 18px 38px -16px rgba(4, 10, 20, .55),
    0 2px 10px -2px rgba(4, 10, 20, .30);
  transform: translateZ(0);
}
.specimen-frost {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
  filter: blur(14px) saturate(155%) brightness(1.04);
  transform: scale(1.15);              /* hide the blur's edge bleed */
}
/* frosted wash — the "glass" of the mat: a ground-tinted veil with a
   soft diagonal sheen (light frosts white, dark frosts navy) */
.specimen-vitrine::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(135deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, 0) 42%),
    color-mix(in srgb, var(--ground) 32%, transparent);
}
.specimen-plate {
  position: absolute; inset: clamp(12px, 4.5%, 20px); z-index: 2;
  border-radius: calc(var(--r-inner) - 8px);
  overflow: hidden;
  /* plate floats above the frost; accent hairline traces its edge */
  box-shadow:
    0 8px 22px -10px rgba(4, 10, 20, .60),
    0 0 0 1px color-mix(in srgb, var(--layout-accent) 42%, transparent),
    inset 0 1px 0 rgba(255, 255, 255, .22);
}
.specimen-plate > img {
  width: 100%; height: 100%; object-fit: cover; display: block; }
/* 1px inner crystal rim on the vitrine's own edge */
.specimen-rim {
  position: absolute; inset: 0; z-index: 3;
  border-radius: inherit; pointer-events: none;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .36),
    inset 0 1px 0 rgba(255, 255, 255, .52),
    inset 0 -1px 0 rgba(255, 255, 255, .14);
}
/* faceted corner glints — cut-glass highlights, accent-warmed */
.specimen-rim::before, .specimen-rim::after {
  content: ""; position: absolute;
  width: 36%; height: 36%; pointer-events: none;
}
.specimen-rim::before {
  top: 0; left: 0;
  background: linear-gradient(135deg,
    rgba(255, 255, 255, .42), rgba(255, 255, 255, .05) 36%, transparent 52%);
}
.specimen-rim::after {
  bottom: 0; right: 0; opacity: .75;
  background: linear-gradient(315deg,
    color-mix(in srgb, var(--layout-accent) 30%, rgba(255, 255, 255, .20)),
    transparent 55%);
}
.specimen-vitrine > .grain { z-index: 4; }

/* slide label — gold hairlines top + bottom, lab-slide cadence */
.specimen-label {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-2) 2px;
  background:
    var(--hairline-gold) top / 100% 1px no-repeat,
    var(--hairline-gold) bottom / 100% 1px no-repeat;
}
.specimen-meta {
  margin-bottom: 4px;
  /* one quiet line: "No. 03 · Packet title" — truncates, never wraps */
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.specimen-no {
  font-variant-numeric: tabular-nums lining-nums;
  color: color-mix(in srgb, var(--layout-accent) 55%, var(--glass-ink));
}
.specimen-dot { margin: 0 8px; opacity: .6; }
.specimen-label .card-headline { margin: 2px 0 0; }

/* accessibility: the vitrine must survive with its glasswork off */
@media (prefers-reduced-transparency: reduce) {
  .specimen-frost { display: none; }
  .specimen-vitrine::before { background: var(--ground-alt); }
  .specimen-rim::before, .specimen-rim::after { display: none; }
  .specimen-rim { box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--glass-ink) 25%, transparent); }
  .card-float-media::before {
    background: linear-gradient(to top,
      rgba(4, 10, 20, .62), rgba(4, 10, 20, .18) 55%, rgba(4, 10, 20, .06)); }
}

/* ============================================================
   RHODE CHARACTERISTICS LAYER (2026-07-05, strictly additive).
   The four surfaces below extend the Reading Layer with the calm,
   editorial "product characteristics" idiom rhodeskin.com uses:
   a spec definition-table, a quiet pro-tip callout, a frameless
   product-on-ground card, and a calm review card. All are CONTENT-
   layer surfaces (things you LOOK AT / READ) — NONE glaze (Law 3):
   no backdrop-filter is introduced, so the ≤3-surface glass budget
   is untouched. Gold appears only as metal — hairlines, small-caps
   eyebrows, a top rule (Law 4). Every color derives from a theme
   token, so light + dark both resolve; new motion/transparency
   effects carry their reduced-* fallbacks at the end of the block.
   All class names here are new and additive — nothing is renamed.

   Local hairline: this codebase has no --line token; row/section
   dividers everywhere use ink@~10% (see .local-nav, .site-footer,
   .field). We expose that as a consumable default via
   var(--line, <ink@11%>) so a future --line knob composes with zero
   churn, while today it resolves to the established hairline in BOTH
   themes (ink = navy light / near-white dark → a correct hairline
   on each ground).
   ============================================================ */

/* ---------- .rt-spec — the Rhode characteristics table ----------
   A definition list rendered as a two-column spec sheet: a small-caps
   Avenir label on the left, a value on the right, hairline dividers
   between rows, and a single gold top rule crowning the block (metal,
   not fill). Values are tabular so numbers/percentages sit on a shared
   metric column — the "spec sheet" credibility tell. Below 380px the
   pair stacks (label as an eyebrow ABOVE its value) so nothing crushes
   on the narrowest phones. Authored as <dl class="rt-spec"> with each
   pair wrapped in <div class="rt-spec-row"><dt>label</dt><dd>value</dd>. */
.rt-spec {
  --line: color-mix(in srgb, var(--ink) 11%, transparent);
  margin: var(--sp-3) 0 var(--sp-4);
  max-width: min(var(--rt-measure, 66ch), 100%);
  /* the crowning gold top rule — hairline metal, full width */
  border-top: 1px solid transparent;               /* reserve the box */
  background: var(--hairline-gold) top / 100% 1px no-repeat;
  padding-top: 1px;                                /* seat rows under the rule */
}
.rt-spec-row {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  align-items: baseline;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line);
}
.rt-spec-row:last-child { border-bottom: 0; }
/* label — small-caps Avenir 700, letterspaced, secondary ink (quiet) */
.rt-spec dt {
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1.35;
  color: var(--ink-secondary);
}
/* value — reads as body ink, right-aligned, tabular figures */
.rt-spec dd {
  margin: 0;
  text-align: right;
  color: var(--ink);
  line-height: 1.4;
  overflow-wrap: break-word;                       /* long INCI values wrap */
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}
/* the value may carry the frozen micro-markup (**strong** / *em*) */
.rt-spec dd strong {
  font-weight: var(--rt-strong-weight, 600);
  color: var(--ink);
  font-synthesis-weight: none;
}
.rt-spec dd em { font-style: italic; }
/* < 380px: stack — label becomes an eyebrow above its left-aligned value */
@media (max-width: 379px) {
  .rt-spec-row {
    grid-template-columns: 1fr;
    gap: 2px;
    padding: var(--sp-2) 0;
  }
  .rt-spec dd { text-align: left; }
}

/* ---------- .rt-callout — the quiet "pro tip" box ----------
   A soft --ground-alt panel with a single 1px gold hairline LEFT rule
   (Law 4 — metal as an edge, never a fill), a small-caps gold eyebrow
   label, and calm body copy. No backdrop-filter (content surface, Law
   3). Reuses the reading measure so a tip never runs wider than the
   prose it sits inside. Authored as
   <aside class="rt-callout"><p class="rt-callout-label">Pro tip</p>
   <div class="rt-callout-body">…</div></aside>. */
.rt-callout {
  position: relative;
  margin: var(--sp-4) 0;
  max-width: min(var(--rt-measure, 66ch), 100%);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-inner);
  background: var(--ground-alt);
  /* the gold-hairline left rule: a 2px metal edge, full height. A
     border-image from --hairline-gold would fade at the ends; a solid
     gold at the same peak alpha reads as a clean lab-margin rule. */
  border-left: 2px solid color-mix(in srgb, var(--gold) 55%, transparent);
  /* on --ground-alt, keep secondary/accent text contrast-safe (light
     only; the -alt companions equal the base values in dark) */
  --ink-secondary: var(--ink-secondary-alt);
  --accent-text: var(--accent-text-alt);
}
.rt-callout-label {
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent-text);                       /* gold-metal eyebrow */
  margin-bottom: var(--sp-1);
}
.rt-callout-body {
  color: var(--ink);
  line-height: 1.55;
}
.rt-callout-body p { margin: 0; }
.rt-callout-body p + p { margin-top: .8em; }
.rt-callout-body strong {
  font-weight: var(--rt-strong-weight, 600);
  color: var(--ink);
  font-synthesis-weight: none;
}
.rt-callout-body em { font-style: italic; }

/* ---------- FRAMELESS GROUND-FUSION — product on "one ground" ----------
   The Rhode move: a product image sits directly ON --ground with NO mat
   and NO rim, only a soft drop shadow — so the packshot appears to float
   on the same surface as the page, not inside a frame. This is the calm
   counterpart to the gold-rim .specimen (a lab vitrine); use ground-fusion
   for hero packshots that should read as "the product, on our ground."

   The shells opt in additively: .packet-card.is-frameless on a card, and
   .card-media.is-fused on a media plate (learner card OR packet player).
   Both must NEUTRALIZE the inherited framing this codebase applies —
   the .packet-card scrim/sheen + shadow, and the .card-media gold-rim
   (which lives BOTH as an inline box-shadow in packet.html AND as
   .learn-card .card-media::before here) — then restore a single soft drop. */

/* ground-fusion on a card: drop the dark scrim + heavy card shadow, let
   the image meet --ground with only a soft, wide drop. Titles that ride
   inside such a card should sit on the image's own dark region or below
   it — the scrim is intentionally gone (that's the frameless look). */
.packet-card.is-frameless {
  background-color: var(--ground);
  box-shadow:
    0 2px 6px -2px color-mix(in srgb, var(--navy) 10%, transparent),
    0 18px 40px -16px color-mix(in srgb, var(--navy) 22%, transparent);
}
.packet-card.is-frameless::before,               /* the legibility scrim */
.packet-card.is-frameless::after { display: none; } /* the specular sheen */

/* ground-fusion on a media plate: strip the gold rim (inline box-shadow
   in packet.html) and the ::before rim (editorial.css) — one soft drop
   remains. The plate blends into --ground; the image is the only object. */
.card-media.is-fused {
  box-shadow:
    0 2px 6px -2px color-mix(in srgb, var(--navy) 10%, transparent),
    0 16px 36px -16px color-mix(in srgb, var(--navy) 20%, transparent);
}
.learn-card .card-media.is-fused::before { box-shadow: none; content: none; }

/* ---------- .specimen refinement (v3) — crystal reads as LAB SPECIMEN ----
   Small pass so macro/ingredient shots read unmistakably as a laboratory
   specimen, not a photo in a frame: (a) a faint accent vignette inside the
   plate so a bright macro sits IN a vitrine rather than filling a tile;
   (b) crisper cut-glass corner glints; (c) a hair-tighter mat inset on
   phones so the frost ring is always visible (the "specimen is mounted"
   read). All additive on the v3 .specimen-* structure — no renames. */
.specimen-plate::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  /* inward vignette + a whisper of the accent at the corners — the
     macro reads as mounted under glass, edges gently receding */
  background:
    radial-gradient(120% 120% at 50% 42%, transparent 58%,
      color-mix(in srgb, var(--navy) 26%, transparent) 100%),
    linear-gradient(135deg,
      color-mix(in srgb, var(--layout-accent) 14%, transparent), transparent 34%);
  mix-blend-mode: multiply;
}
[data-theme="dark"] .specimen-plate::after { mix-blend-mode: screen; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .specimen-plate::after { mix-blend-mode: screen; }
}
/* sharper facet on the top-left glint so the glass edge catches light
   like cut crystal (the vitrine's "science" tell) */
.specimen-rim::before {
  background: linear-gradient(135deg,
    rgba(255, 255, 255, .52), rgba(255, 255, 255, .06) 30%, transparent 46%);
}
@media (max-width: 479px) {
  /* keep the frost ring visible on phones so it never reads as a bare tile */
  .specimen-plate { inset: clamp(10px, 6%, 16px); }
}

/* ---------- REVIEW CARD (Home) — calm retrieval-practice surface ----------
   The spaced-review / "check yourself" card on Home. Calm, gold as accent,
   NEVER alarmist: no red, no urgency chrome — a quiet putty card with a
   gold-hairline top rule and small-caps eyebrow. States are carried on the
   host as .review-surface plus a state class; correctness reads through the
   same calm-emerald / quiet-ruby idiom the quiz uses (an emerald tick, a
   soft ruby wash) so a "due" or "missed" card is informative, not scolding. */
.review-card {
  position: relative;
  padding: var(--sp-4);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card, 0 1px 2px rgba(12,35,64,.05));
  /* putty card crowned by a gold hairline rule — metal, matches
     .rt-spec's cadence (Law 4). The rule rides the background layer
     stack so it never adds a box the padding must clear. */
  background:
    var(--hairline-gold) top / 100% 1px no-repeat,
    var(--ground-alt);
  /* -alt companions keep secondary/accent text ≥4.5:1 on putty (light) */
  --ink-secondary: var(--ink-secondary-alt);
  --accent-text: var(--accent-text-alt);
}
.review-card .eyebrow { color: var(--accent-text); }
/* the prompt/question line reads as a calm card heading */
.review-card h3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.15;
  font-size: var(--text-card-title);
  margin: var(--sp-1) 0 var(--sp-2);
  color: var(--ink);
}
.review-card .secondary { color: var(--ink-secondary); }

/* review-surface: the answerable body (choices / reveal). A gold gutter
   rule ties choices to the card's metal without shouting. */
.review-surface {
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid color-mix(in srgb, var(--gold) 22%, transparent);
}
/* state washes — calm, informational, gold-forward. "due" is the resting
   invitation (a warm gold whisper); "revealed" settles to neutral; the
   correctness tints reuse the quiz palette at low alpha (never alarmist). */
.review-surface.is-due {
  background: color-mix(in srgb, var(--gold) 6%, transparent);
  border-radius: var(--r-inner);
  padding: var(--sp-2) var(--sp-3);
  border-top-color: transparent;
}
.review-surface.is-revealed {
  background: transparent;
}
.review-surface.is-correct {
  background: color-mix(in srgb, var(--jewel-emerald) 9%, transparent);
  border-radius: var(--r-inner);
  padding: var(--sp-2) var(--sp-3);
  border-top-color: color-mix(in srgb, var(--jewel-emerald) 30%, transparent);
}
.review-surface.is-missed {
  /* quiet ruby — informational, not an error state; a soft wash + a
     calm hairline, no bold border, no icon shout */
  background: color-mix(in srgb, var(--jewel-ruby) 7%, transparent);
  border-radius: var(--r-inner);
  padding: var(--sp-2) var(--sp-3);
  border-top-color: color-mix(in srgb, var(--jewel-ruby) 26%, transparent);
}

/* ---------- Accessibility fallbacks for this block ----------
   .rt-spec / .rt-callout / ground-fusion / review-card introduce no
   motion (no transitions, no keyframes) — nothing for reduced-motion to
   disable. They introduce no backdrop-filter and no translucent BLUR;
   the only transparency is flat low-alpha color-mix washes, which stay
   legible when composited. Under reduced-transparency we still swap those
   washes for solid --ground-alt so nothing depends on alpha compositing,
   and we drop the specimen-plate glass vignette to a flat state. */
@media (prefers-reduced-transparency: reduce) {
  .rt-callout { background: var(--ground-alt); }
  .review-surface.is-due,
  .review-surface.is-correct,
  .review-surface.is-missed { background: var(--ground-alt); }
  .specimen-plate::after { display: none; }
}

/* ============================================================
   RHODE READER — IDIOM RESTYLES  (2026-07-05, additive)
   ------------------------------------------------------------
   The brief asks that .rt-spec + .rt-callout be RESTYLED toward the
   reader idiom while KEEPING their names (Studio + other pages depend
   on the markup). We do this WITHOUT regressing their standalone
   (non-reader) appearance: the base rules above are unchanged; these
   overrides are SCOPED to the .reader subtree only. Inside .reader the
   local --ground/--ink/--line are already the putty tokens (see
   reader.css .reader), so most of the credibility just inherits — we
   only re-face the labels to the reader's grotesque/clay and swap the
   spec's gold crown for the reference's solid ink top rule.

   Because these live in editorial.css (a CONTENT file) they add ZERO
   backdrop-filter — the glass budget is untouched. And because the
   selectors are .reader-scoped, the Studio's use of the SAME classes
   OUTSIDE a reader renders exactly as before (no regression).
   ============================================================ */

/* --- .rt-spec in the reader: the reference's ink-crowned ledger --- */
.reader .rt-spec {
  /* reference crown is a solid 1.5px INK top rule, not the gold hairline;
     override the base's gold background-crown with a plain ink border */
  background: none;
  border-top: 1.5px solid var(--reader-ink, #23211E);
  padding-top: 0;
  max-width: min(var(--reader-measure, 620px), 100%);
}
.reader .rt-spec-row {
  /* reference cadence: label column a touch narrower, value a touch wider */
  grid-template-columns: minmax(6rem, 8rem) 1fr;
  border-bottom-color: var(--reader-line, rgba(35, 33, 30, .14));
}
/* label re-faced to the reader's grotesque micro-caps */
.reader .rt-spec dt {
  font-family: var(--font-grotesque);
  font-weight: 700;
  font-size: .68rem;
  letter-spacing: .16em;
  color: var(--reader-ink-soft, #6E6A63);
}
.reader .rt-spec dd { color: var(--reader-ink, #23211E); }

/* --- .rt-callout in the reader: the reference's pro-tip box --- */
.reader .rt-callout {
  background: var(--reader-ground-2, #F1EFEA);
  border-left: 2px solid color-mix(in srgb, var(--gold) 55%, transparent);
  border-radius: 0 4px 4px 0;      /* reference: squared left, softened right */
  max-width: min(var(--reader-measure, 620px), 100%);
}
/* the callout label re-faced to grotesque clay (the reference "Distributor trick") */
.reader .rt-callout-label {
  font-family: var(--font-grotesque);
  font-weight: 700;
  font-size: .68rem;
  letter-spacing: .16em;
  color: var(--reader-clay, #A9673F);
}
.reader .rt-callout-body { color: var(--reader-ink, #23211E); }


/* ============================================================
   HOME / LIBRARY  — EDITORIAL LAYER  (2026-07-05, additive)
   ------------------------------------------------------------
   For build-agent C. A photo-forward, air-rich home/library idiom in
   the Rhode language — BIG grotesque, generous whitespace, imagery that
   carries the information. This is DELIBERATELY NOT the glass grid:
   .ed-* surfaces are frameless content surfaces (no backdrop-filter),
   the airy counterpart to the .packet-card glass shelf. They render on
   the PAGE ground (--ground/--ink), so they read Rhode in BOTH themes
   automatically via the existing theme tokens — no reader re-grounding
   needed here (Home/Library are not inside .reader).

   CONTRACT (author these; C wires data):
     .ed-hero        the giant grotesque page masthead
       .ed-hero-eyebrow / .ed-hero-title / .ed-hero-lead
     .ed-shelf       a track/packet division — big, airy, info-carrying
       .ed-shelf-head (a.ed-shelf-more) + .ed-shelf-grid of .ed-tile
     .ed-tile        one photo-forward packet/track tile (an <a>)
       .ed-tile-media (img) / .ed-tile-eyebrow / .ed-tile-title / .ed-tile-meta
   ============================================================ */

/* --- the masthead --- */
.ed-hero {
  padding: clamp(3rem, 9vw, 7rem) var(--section-x) clamp(1.5rem, 4vw, 3rem);
  max-width: var(--content-max);
  margin-inline: auto;
}
.ed-hero-eyebrow {
  font-family: var(--font-grotesque);
  font-weight: 700;
  font-size: .72rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--accent-text);       /* gold-metal eyebrow (theme-aware) */
  margin: 0 0 .6em;
}
.ed-hero-title {
  font-family: var(--font-grotesque);
  font-weight: 800;
  font-size: clamp(3rem, 12vw, 7rem);
  line-height: .88;
  letter-spacing: -.035em;
  text-transform: lowercase;
  color: var(--ink);
  margin: 0;
  text-wrap: balance;
}
.ed-hero-lead {
  font-size: clamp(1.15rem, 2.6vw, 1.5rem);
  line-height: 1.45;
  color: var(--ink);
  max-width: 46ch;
  margin: 1rem 0 0;
}

/* --- the shelf (a division) --- */
.ed-shelf {
  padding: clamp(2rem, 5vw, 4rem) var(--section-x);
  max-width: var(--content-max);
  margin-inline: auto;
}
.ed-shelf-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: clamp(1.2rem, 3vw, 2rem);
}
.ed-shelf-title {
  font-family: var(--font-grotesque);
  font-weight: 700;
  font-size: clamp(1.6rem, 4.5vw, 2.6rem);
  line-height: 1;
  letter-spacing: -.03em;
  text-transform: lowercase;
  color: var(--ink);
  margin: 0;
}
.ed-shelf-more {
  flex: none;
  font-family: var(--font-grotesque);
  font-weight: 600;
  font-size: .82rem;
  letter-spacing: .02em;
  color: var(--accent-text);
  text-decoration: none;
  white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) {
  .ed-shelf-more:hover { text-decoration: underline; text-underline-offset: 3px; }
}
/* airy auto-fit grid — big tiles, generous gaps (NOT the dense glass grid) */
.ed-shelf-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: clamp(1.4rem, 3.5vw, 2.6rem);
}

/* --- the tile: photo-forward, frameless --- */
.ed-tile {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: var(--ink);
  transition: transform var(--dur-micro, .3s) var(--ease-spring, ease);
}
.ed-tile-media {
  width: 100%;
  aspect-ratio: 4 / 5;             /* tall, editorial */
  object-fit: cover;
  border-radius: var(--r-inner, 18px);
  display: block;
  /* frameless: the image carries itself with a soft float, no mat/rim */
  box-shadow: var(--shadow-card, 0 12px 32px -8px rgba(12, 35, 64, .10));
}
/* Reviewed Home track art is a wide key-art system, not packet photography.
   Its sidecars specify a 21:9 tile and keep every meaningful motif inside the
   cover-safe canvas. Do not send these SVGs through the generic 4:5 crop. */
.ed-tile-media--track-art {
  aspect-ratio: 21 / 9;
  object-fit: cover;
  object-position: center;
  background: var(--ground-alt);
}
/* DARK-mode library imagery: most packshots are pure-white studio shots, which
   otherwise sear as ~17:1 white rectangles on the navy ground (in light they melt
   into the bone at ~1:1). Dim them toward the ground — same brightness idiom as
   the reader figures — and give the edge a faint navy inset ring so the white
   rectangle boundary dissolves instead of glowing. Applies to every shelf/thumb. */
[data-theme="dark"] .ed-tile-media,
[data-theme="dark"] .resume-cover,
[data-theme="dark"] .s-thumb,
[data-theme="dark"] .p-thumb {
  filter: brightness(.86) contrast(1.02);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ground, #0a1526) 60%, transparent);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .ed-tile-media,
  :root:not([data-theme]) .resume-cover,
  :root:not([data-theme]) .s-thumb,
  :root:not([data-theme]) .p-thumb {
    filter: brightness(.86) contrast(1.02);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ground, #0a1526) 60%, transparent);
  }
}
.ed-tile-eyebrow {
  font-family: var(--font-grotesque);
  font-weight: 700;
  font-size: .66rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 1rem 0 .35em;
}
.ed-tile-title {
  font-family: var(--font-grotesque);
  font-weight: 700;
  font-size: clamp(1.25rem, 2.4vw, 1.6rem);
  line-height: 1.05;
  letter-spacing: -.02em;
  text-transform: lowercase;
  color: var(--ink);
  margin: 0;
  text-wrap: balance;
}

/* ============================================================
   TYPOGRAPHY NAVIGATION SAFETY
   Display copy, control labels and route rows may wrap only at natural word
   boundaries. Flex/grid children are allowed to shrink so the browser can
   choose those boundaries; no emergency mid-token fragmentation is permitted.
   This is behavior-only and leaves the approved type scale untouched.
   ============================================================ */
:is(
  h1, h2, h3, .display, .hero-title, .page-display, .page-head,
  .ed-hero-title, .ed-shelf-title, .ed-tile-title,
  .r-display, .r-head, .r-subhead,
  .resume-title, .review-title, .util-title,
  .s-title, .res-title, .r-bottom-title,
  .ssr-spokes .t, .ssr-faq .r-subhead
) {
  min-width: 0;
  overflow-wrap: normal;
  word-break: normal;
  -webkit-hyphens: none;
  hyphens: none;
  text-wrap: balance;
}

:is(
  .btn, .chip, .hub-nav a, .local-nav a,
  .ed-shelf-more, .res-row, .s-row, .p-row, .set-row,
  .r-bottom-link, .ssr-spokes a, .ssr-footer-nav a
) {
  min-width: 0;
  overflow-wrap: normal;
  word-break: normal;
  -webkit-hyphens: none;
  hyphens: none;
}
.ed-tile-meta {
  font-family: var(--font-mono);
  font-size: .74rem;
  letter-spacing: .02em;
  color: var(--ink-secondary);
  margin: .5em 0 0;
}
@media (hover: hover) and (pointer: fine) {
  .ed-tile:hover { transform: translateY(-4px); }
  .ed-tile:hover .ed-tile-media {
    box-shadow: var(--shadow-card-hover, 0 24px 56px -12px rgba(12, 35, 64, .16));
  }
}
/* motion + transparency safety for the editorial layer */
@media (prefers-reduced-motion: reduce) {
  .ed-tile { transition: none; }
  .ed-tile:hover { transform: none; }
}
