/* ==========================================================================
   BookSwap — shared stylesheet for bookswap.shop
   --------------------------------------------------------------------------
   Static CSS + two tiny scripts (site.js everywhere; journey.js on the guide).
   No fonts, no CDN, no remote url().
   Mobile-first: base rules target 360px, min-width queries enhance upward.

   HOW TO USE THIS FILE
   - Every page links it with: <link rel="stylesheet" href="styles.css">
   - Page authors should NOT write ad-hoc CSS. Every shape the site needs
     already has a class here. If you need something new, add it here so all
     three pages stay identical.

   THE SEMANTIC RULE (applied site-wide, never violated)
     A SOLID line or filled surface means "this is how it works".
     A DASHED line means "this does not exist".
   The handover diagram's "never happens" arc is dashed for exactly that
   reason. Never dash anything decorative.

   MOTION (see §16b) — three rules, no exceptions:
     1. Nothing loops. Every animation runs once and settles.
     2. Nothing moves the layout — only opacity, transform, clip-path, shadow.
     3. prefers-reduced-motion: reduce  ⇒  a fully static site (§16), and
        no-JS  ⇒  everything visible (reveal styles are gated on html.js).

   CONTENTS
     01  Tokens
     02  Reset + focus
     03  Base typography
     04  Layout primitives (container / section / bands)
     05  Section masthead (the repeating hairline device)
     06  Site header + brand lockup + skip link
     07  Site footer
     08  Buttons + CTA rows
     09  Cards, steps, features, code tiles
     10  Hero
     11  Handover diagram
     12  Good-to-know strip
     12b About the creator
     12c Download the app
     13  Closing brand-green panel
     14  Document pages (.doc-header / .sheet / .prose / .faq / .toc)
     15  Callouts, notes, dividers, utilities
     15b Motion system (.enter / .reveal / .stagger / hover)
     16  Reduced motion
     17  Print
   ========================================================================== */


/* ==========================================================================
   01  TOKENS
   The palette below is the finalised app palette. These are the ONLY colours
   allowed for text, surfaces and UI, plus #FFFFFF for text on brand green,
   and rgba() mixed only from 42,36,28 (ink), 29,92,69 (brand), 244,239,230
   (sand — the sticky header's translucent surface) or white.
   The logo image carries its own colours (slate book, red/green/gold ribbons).
   ONE sanctioned exception picks them up in CSS: the --ribbon-* tokens below,
   used solely as the painted lines on the guide's journey road (§16c).
   ========================================================================== */
:root {
  /* --- Brand palette --------------------------------------------------- */
  --brand:      #1D5C45;  /* brand green — primary buttons, wordmark, key text */
  --sage:       #3D7657;  /* sage — secondary accents, links on sand          */
  --terracotta: #9C5326;  /* terracotta — eyebrows, ordinals, the "×" glyph   */
  --sand:       #F4EFE6;  /* page background                                  */
  --cream:      #FFFDF9;  /* card + sheet surface                             */
  --sand-deep:  #E9E1D4;  /* wells, alternating bands, code tiles             */
  --border:     #DCD2C2;  /* every hairline and card border                   */
  --ink:        #2A241C;  /* primary text                                     */
  --muted:      #5C5245;  /* secondary text and ALL small print               */
  --faint:      #857A6B;  /* decorative only — see contrast note below        */

  /* --- Logo ribbon colours — PATH ONLY -------------------------------------
     Sampled from assets/logo.png (the three ribbons sweeping out of the book).
     They exist for exactly one thing: the painted lines on the guide's journey
     road (§16c), so the path visibly belongs to the mark. Never text, never a
     button, never a background — they are not part of the desert palette and
     none of them was contrast-checked against it. */
  --ribbon-red:   #C05038;
  --ribbon-green: #109060;
  --ribbon-gold:  #D0A830;

  /* CONTRAST RULES — measured, non-negotiable:
       terracotta on --sand 4.99:1 OK · on --cream 5.58:1 OK · on --sand-deep 4.40:1 FAIL
       sage on --sand 4.67:1 OK · on --sand-deep 4.12:1 FAIL
       --faint on --sand 3.67:1 FAIL at any reading size
     Therefore: on any --sand-deep surface, eyebrows/folios/links use --brand
     (6.07:1). --faint NEVER carries reading text — list ::markers only. */

  /* --- Layout ---------------------------------------------------------- */
  --page: 1080px;                    /* standard container                    */
  --narrow: 760px;                   /* document-page container               */
  --measure: 680px;                  /* prose column ≈ 68–72 characters       */
  --gutter: clamp(20px, 5vw, 40px);  /* one gutter token for every container  */

  /* --- Radii ----------------------------------------------------------- */
  --r: 14px;        /* cards, panels, sheets  */
  --r-btn: 12px;    /* buttons, icon tiles    */
  --r-sm: 10px;     /* chips, wells, notes    */
  --r-pill: 999px;  /* the station code chip  */

  /* --- Spacing rhythm --------------------------------------------------- */
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 40px;
  --space-8: 56px;
  --section-y: 64px;   /* raised at 600px and 900px in §04 */

  /* --- Shadows (ink-mixed / brand-mixed only, always subtle) ------------ */
  --shadow-hover: 0 2px 6px -2px rgba(42, 36, 28, .10), 0 18px 36px -24px rgba(42, 36, 28, .40);
  --shadow-btn: 0 1px 2px rgba(29, 92, 69, .10), 0 8px 20px rgba(29, 92, 69, .10);
  --shadow-lift: 0 2px 4px rgba(29, 92, 69, .14), 0 14px 32px rgba(29, 92, 69, .16);
  --shadow-header: 0 1px 0 var(--border), 0 8px 24px -18px rgba(42, 36, 28, .35);

  /* --- Motion ----------------------------------------------------------- */
  --t: 160ms ease;                              /* micro: colour, border      */
  --ease-out: cubic-bezier(.22, .61, .36, 1);   /* every entrance / reveal     */
  --dur-hover: 200ms;                           /* card lift, nav underline    */
  --dur-enter: 560ms;                           /* hero entrance               */
  --dur-reveal: 640ms;                          /* scroll reveal               */
  --rise: 14px;                                 /* how far a revealed block rises */
  --stagger: 70ms;                              /* gap between staggered siblings */

  /* --- Type ------------------------------------------------------------- */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}


/* ==========================================================================
   02  RESET + FOCUS
   Deliberately tiny. Just the things that bite on a static site.
   ========================================================================== */
*,
*::before,
*::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--sand);
  color: var(--ink);
  font-family: var(--font);
  font-size: 15.5px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }

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

ul, ol { margin: 0; }

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

/* Visible focus everywhere. Never remove an outline without replacing it. */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
/* On the brand-green panel a green ring would vanish, so it turns white. */
.band--brand :focus-visible,
.cta-panel :focus-visible {
  outline-color: #FFFFFF;
}


/* ==========================================================================
   03  BASE TYPOGRAPHY
   Five "voices" from one system font family — personality comes from weight,
   tracking and case, not from extra typefaces.
     DISPLAY  700 / -0.03em / 1.06     SECTION  700 / -0.02em / 1.18
     BODY     400 / 1.6                EYEBROW  700 / uppercase / +0.14em
     FOLIO    700 / tabular / +0.12em
   All headings use clamp() so they scale smoothly 360px → 1440px with no
   media queries at all.
   ========================================================================== */
h1, h2, h3, h4 {
  font-weight: 700;
  color: var(--ink);
  text-wrap: balance;
}

h1 {
  font-size: clamp(30px, 6.4vw, 56px);
  line-height: 1.06;
  letter-spacing: -0.03em;
}

h2 {
  font-size: clamp(24px, 4.4vw, 34px);
  line-height: 1.18;
  letter-spacing: -0.02em;
}

h3 {
  font-size: 17.5px;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

p { line-height: 1.65; }

/* Comfortable measure for any standalone paragraph. */
p, li { max-width: 68ch; }

strong, b { font-weight: 700; color: var(--ink); }

a { color: var(--sage); }
a:hover { color: var(--brand); }

/* The display headline: two blocks, hierarchy carried by WEIGHT not colour,
   which keeps the wordmark the only brand-green mark above the fold. */
.h1-line { display: block; max-width: 15ch; }
.h1-line--strong { font-weight: 700; color: var(--ink); }
.h1-line--soft   { font-weight: 400; color: var(--muted); }

/* Eyebrow — small terracotta label above a heading, with an inline lead rule. */
.eyebrow {
  display: block;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--terracotta);
}
.eyebrow--rule::before {
  content: "";
  display: inline-block;
  width: 24px;
  height: 2px;
  background: currentColor;
  vertical-align: 0.28em;
  margin-right: 12px;
}
/* On sand-deep, terracotta drops to 4.40:1 — switch to brand green. */
.band--deep .eyebrow,
.eyebrow--deep { color: var(--brand); }

/* Folio — the quiet right-hand note in a masthead. */
.folio {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  text-transform: uppercase;
  text-align: right;
}
/* The folio must not be unbreakable at the narrowest widths. .masthead is a
   nowrap flex row, so a nowrap folio cannot shrink: the longest one on the
   site, "GIVER → STATION → YOU", measured 187.5px at 320px and hung 18.17px
   past its column into the right gutter — 1.83px short of scrolling the whole
   page sideways. Above 400px every folio fits on one line with room to spare,
   so the single-line look is kept there and only surrendered below it. */
@media (min-width: 400px) {
  .folio { white-space: nowrap; }
}

/* Lede — the sentence under a headline. */
.lede {
  margin-top: 20px;
  font-size: 17px;
  line-height: 1.6;
  color: var(--muted);
  max-width: 58ch;
}
@media (min-width: 900px) {
  .lede { font-size: 18px; }
}

/* Sub-line under a section h2. */
.sub {
  margin-top: 10px;
  font-size: 16.5px;
  line-height: 1.6;
  color: var(--muted);
  max-width: 60ch;
}

/* Small print. Always --muted, never --faint. */
.fine,
.footnote {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--muted);
  max-width: 62ch;
}
.footnote { margin-top: 14px; }

/* The launch note under a CTA row. */
.launch-note {
  margin-top: 12px;
  font-size: 14px;
  color: var(--muted);
  max-width: 52ch;
}


/* ==========================================================================
   04  LAYOUT PRIMITIVES
   One gutter token means the wordmark, every section head and the footer all
   share a single left edge at every screen width.
   ========================================================================== */
.container {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Document pages sit in a narrower column. */
.container--narrow { max-width: var(--narrow); }

/* Prose measure helper for one-off blocks. */
.container--measure { max-width: var(--measure); }

/* Vertical rhythm for a full band. */
.section { padding-block: var(--section-y); }
.section--tight { padding-block: 56px; }
.section--flush-top { padding-top: 0; }

@media (min-width: 600px) {
  :root { --section-y: 88px; }
  .section--tight { padding-block: 72px; }
}
@media (min-width: 900px) {
  :root { --section-y: 112px; }
}

/* Band backgrounds — full-bleed colour changes between sections. */
.band--sand  { background: var(--sand); }
.band--cream { background: var(--cream); }
.band--deep  { background: var(--sand-deep); }
.band--brand { background: var(--brand); color: #FFFFFF; }

/* Cream on sand is a very low-contrast band change; these 1px rules are
   LOAD-BEARING — without them the band reads as a rendering artifact. */
.band--cream,
.band--deep {
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.band--brand h1,
.band--brand h2,
.band--brand h3 { color: #FFFFFF; }


/* ==========================================================================
   05  SECTION MASTHEAD
   The site's one repeating structural device: a hairline spanning the column
   with the eyebrow knocked out of it on the left and a folio note on the
   right.

   WHERE IT IS ACTUALLY USED: on the landing page (once per section) and on
   the guide's journey section. The document pages express the SAME device at
   document scale — .doc-header-inner draws the hairline and .eyebrow--rule
   supplies the ruled label (the guide's header drops its own rule, via
   .doc-header--flush, because the journey masthead directly below is the
   rule). Same hairline-plus-ruled-label idea at two scales; that is what makes
   the three files read as one publication.

   Markup:
     <div class="masthead">
       <span class="eyebrow">HOW IT WORKS</span>
       <span class="folio">01 — 04</span>
     </div>
   ========================================================================== */
.masthead {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.masthead::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0.62em;
  height: 1px;
  background: var(--border);
}
/* The labels carry the band's own background so the rule passes BEHIND them. */
.masthead .eyebrow,
.masthead .folio {
  position: relative;
  background: var(--sand);
}
.masthead .eyebrow { padding-right: 14px; }
.masthead .folio   { padding-left: 14px; }

/* Modifiers for the two other band colours. */
.masthead--cream .eyebrow,
.masthead--cream .folio { background: var(--cream); }

.masthead--deep .eyebrow,
.masthead--deep .folio { background: var(--sand-deep); }
.masthead--deep .eyebrow { color: var(--brand); }  /* contrast rule */
.masthead--deep .folio   { color: var(--brand); }

/* Heading that follows a masthead. */
.masthead + h2,
.section-title {
  margin-top: 22px;
  max-width: 22ch;
}


/* ==========================================================================
   06  SITE HEADER  (+ brand lockup, skip link)
   No hamburger, ever — three links do not need one, and hiding the nav
   behind a tap on a phone (most of our traffic) costs more than it saves.
   At 360px the header STACKS: lockup on row one, nav on row two, both flush
   to the same left gutter. From 560px up it collapses to a single row with the
   nav pushed right. Nothing is hidden at any width.

   From 900px up the header is STICKY with a translucent sand surface and picks
   up a soft shadow once the page has scrolled (site.js toggles .is-scrolled).
   Below 900px it scrolls away — a fixed bar costs scarce phone viewport.

   THE BRAND LOCKUP — identical to the app's own BrandMark:
     <a class="brand" href="/">
       <img class="brand-mark" src="assets/logo.png" alt="" width="…" height="…">
       <span class="wordmark">Book<span class="wordmark-accent">Swap</span></span>
     </a>
   The mark is the real BookSwap logo (assets/logo.png — a dark book with three
   ribbons). "Book" is ink, "Swap" is brand green, weight 800 — exactly the
   app's wordmark. The <img> alt is EMPTY on purpose: the wordmark text right
   beside it is the accessible name, so a screen reader hears "BookSwap" once.
   ========================================================================== */
.skip-link {
  position: absolute;
  top: 8px;
  left: 20px;
  z-index: 50;
  transform: translateY(-160%);
  background: var(--brand);
  color: #FFFFFF;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  padding: 10px 16px;
  border-radius: var(--r-sm);
  transition: transform var(--t);
}
.skip-link:focus-visible {
  transform: translateY(0);
  color: #FFFFFF;
  outline-color: #FFFFFF;
  outline-offset: -6px;
}

.site-header {
  background: var(--sand);
  border-bottom: 1px solid var(--border);
  position: relative;
  z-index: 40;
}

.site-header .bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 12px;
  padding-block: 10px;
}

/* --- The brand lockup (header + footer) ---------------------------------- */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  padding-block: 4px;
  border-radius: var(--r-sm);
}
.brand-mark {
  height: 30px;
  width: auto;
  display: block;
  flex: 0 0 auto;
}
.wordmark {
  display: inline-block;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--ink);
  line-height: 1.2;
  white-space: nowrap;
}
.wordmark-accent { color: var(--brand); }
@media (min-width: 900px) {
  .brand-mark { height: 34px; }
  .wordmark   { font-size: 21px; }
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
  margin-left: -10px;  /* pulls the first link's padding back to the gutter */
}
.site-nav a {
  position: relative;
  display: inline-block;
  font-size: 14.5px;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
  /* 10px block padding + a 23.93px line box + the 2px rule = 45.9px.
     Most visitors are on a phone, so the nav is sized for thumbs. At 9px this
     measured 43.93px in Chrome — just under the 44px it claimed. */
  padding: 10px 10px;
  border-radius: var(--r-sm);
  border-bottom: 2px solid transparent;
  transition: color var(--t), background-color var(--t);
}
/* The underline is a pseudo-element that GROWS from the left on hover, and
   sits fully open in terracotta on the current page. Transforms only. */
.site-nav a::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: -2px;
  height: 2px;
  background: var(--terracotta);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-hover) var(--ease-out);
}
.site-nav a:hover {
  color: var(--brand);
  background: var(--cream);
}
.site-nav a:hover::after { transform: scaleX(1); }

/* ACTIVE PAGE — implementers only add aria-current="page"; no extra class. */
.site-nav a[aria-current="page"] {
  color: var(--brand);
  font-weight: 700;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
}
.site-nav a[aria-current="page"]::after { transform: scaleX(1); }

@media (min-width: 560px) {
  .site-header .bar {
    flex-wrap: nowrap;
    justify-content: space-between;
    gap: 16px;
    padding-block: 0;
    min-height: 62px;
  }
  .site-nav { margin-left: 0; gap: 4px; }
  .site-nav a { font-size: 15px; padding: 10px 12px; }
  .site-nav a::after { left: 12px; right: 12px; }
}
@media (min-width: 900px) {
  .site-header {
    position: sticky;
    top: 0;
    /* Translucent sand with a blur, so content is felt sliding under it.
       Alpha is mixed from the sand token's own channels (244,239,230). */
    background: rgba(244, 239, 230, .86);
    -webkit-backdrop-filter: saturate(140%) blur(10px);
    backdrop-filter: saturate(140%) blur(10px);
    transition: box-shadow var(--dur-hover) var(--ease-out);
  }
  .site-header.is-scrolled {
    border-bottom-color: transparent;
    box-shadow: var(--shadow-header);
  }
  .site-header .bar { min-height: 70px; }
}


/* ==========================================================================
   07  SITE FOOTER  (contact card + brand/nav + legal line)
   Byte-identical markup on all three pages. Nothing changes between pages.
   Note: footer links are BRAND green, not sage — sage on sand-deep is 4.12:1
   and fails AA; brand green measures 6.07:1.

   THE CONTACT CARD (.footer-contact) opens the footer on every page: a cream
   card on the sand-deep footer — the same inset-panel language as the landing
   page's closing CTA — with an eyebrow, a question as the heading, one line of
   support copy, and the support address as a brand-green button (its label IS
   the address, so it can be read, tapped, or long-pressed to copy). It is the
   site's ONE contact area: the footer nav carries only the three page links.
   ========================================================================== */
.site-footer {
  background: var(--sand-deep);
  border-top: 1px solid var(--border);
  padding-block: 40px;
}
@media (min-width: 600px) {
  .site-footer { padding-block: 56px; }
}

/* --- Contact card -------------------------------------------------------- */
.footer-contact {
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 28px 22px;
  margin-bottom: 40px;
  display: grid;
  gap: 22px;
}
@media (min-width: 600px) {
  .footer-contact { padding: 36px; margin-bottom: 48px; }
}
@media (min-width: 768px) {
  .footer-contact {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 40px;
    padding: 40px 44px;
    margin-bottom: 56px;
  }
}
@media (min-width: 900px) {
  .footer-contact { border-radius: 20px; padding: 48px 56px; }
}
/* Sits on cream, so the eyebrow keeps its terracotta (5.58:1). */
.footer-contact h2 {
  margin-top: 12px;
  font-size: clamp(22px, 3.2vw, 30px);
  line-height: 1.2;
  max-width: 20ch;
}
.footer-contact .sub {
  margin-top: 10px;
  font-size: 15.5px;
  max-width: 46ch;
}
.footer-contact-action {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}
@media (min-width: 768px) {
  .footer-contact-action { align-items: flex-end; }
}
/* Scoped under .footer-contact on purpose: §08's .btn is declared AFTER this
   section at the same specificity, so a bare .btn--mail would lose padding,
   gap and size to it. */
.footer-contact .btn--mail {
  gap: 10px;
  padding: 15px 24px;
  font-variant-ligatures: none;   /* keep the address legible as typed */
  overflow-wrap: anywhere;        /* last resort only — the address never overflows */
}
.footer-contact .btn--mail svg { width: 18px; height: 18px; flex: 0 0 auto; }
@media (max-width: 599px) {
  .footer-contact .btn--mail { width: 100%; justify-content: center; }
}
/* On the narrowest phones (320–359px) the 21-character address plus icon
   would wrap; a slightly smaller button keeps it on one line. */
@media (max-width: 359px) {
  .footer-contact .btn--mail { font-size: 14px; padding: 14px 14px; gap: 8px; }
}
.footer-contact-note {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 34ch;
}
@media (min-width: 768px) {
  .footer-contact-note { text-align: right; }
}

.footer-top {
  display: flex;
  flex-direction: column;
  gap: 24px;
}
@media (min-width: 600px) {
  .footer-top {
    flex-direction: row;
    justify-content: space-between;
    align-items: flex-start;
    gap: 40px;
  }
}

.footer-descriptor {
  margin-top: 8px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--muted);
  max-width: 30ch;
}

.footer-nav ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.footer-nav li { max-width: none; }
.footer-nav a {
  font-size: 15px;
  font-weight: 600;
  color: var(--brand);
  text-decoration: none;
  transition: color var(--t);
}
.footer-nav a:hover {
  color: var(--brand);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
@media (min-width: 600px) {
  .footer-nav ul { text-align: right; }
}

.footer-bottom {
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 13.5px;
  color: var(--muted);
}
.footer-bottom p { max-width: none; }
@media (min-width: 600px) {
  .footer-bottom {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
  }
}


/* ==========================================================================
   08  BUTTONS + CTA ROWS
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 15.5px;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  padding: 14px 26px;
  border-radius: var(--r-btn);
  border: 1px solid transparent;
  transition: color var(--t), background-color var(--t), border-color var(--t), transform var(--t);
}

.btn--primary {
  background: var(--brand);
  color: #FFFFFF;
  border-color: var(--brand);
  box-shadow: var(--shadow-btn);
}
.btn--primary:hover {
  background: var(--sage);
  border-color: var(--sage);
  color: #FFFFFF;
  transform: translateY(-1px);
}

.btn--secondary {
  background: transparent;
  color: var(--brand);
  border-color: var(--border);
}
.btn--secondary:hover {
  color: var(--brand);
  border-color: var(--sage);
  background: var(--cream);
}

/* On-brand variants, for use inside the green closing panel. */
.btn--on-brand {
  background: #FFFFFF;
  color: var(--brand);
  border-color: #FFFFFF;
  box-shadow: none;
}
.btn--on-brand:hover {
  background: #FFFFFF;
  color: var(--brand);
  transform: translateY(-1px);
}
/* A REAL secondary link on the brand-green panel (the "Get the app" beside the
   white primary): solid white outline, white text — solid because it exists. */
.btn--outline-on-brand {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, .6);
  color: #FFFFFF;
}
.btn--outline-on-brand:hover {
  background: rgba(255, 255, 255, .12);
  border-color: #FFFFFF;
  color: #FFFFFF;
  transform: translateY(-1px);
}

.btn-row {
  margin-top: 32px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}
/* Below 400px the two CTA items go full width and centre their labels. */
@media (max-width: 399px) {
  .btn-row > * {
    flex: 1 1 100%;
    justify-content: center;
    text-align: center;
  }
}

/* A quiet text link with a literal → in it. */
.text-link {
  display: inline-block;
  font-size: 15.5px;
  font-weight: 600;
  color: var(--sage);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color var(--t);
}
.text-link:hover { color: var(--brand); }


/* ==========================================================================
   09  CARDS, STEPS, FEATURES, CODE TILES
   Cards use BORDERS, not shadows. The only elevated element on the whole site
   is the green station node in the handover diagram.
   ========================================================================== */
.card {
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 20px;
  transition: border-color var(--dur-hover) var(--ease-out),
              translate var(--dur-hover) var(--ease-out),
              box-shadow var(--dur-hover) var(--ease-out);
}
/* Hover: a 2px lift with a soft ink shadow and the border warming to sage.
   Lift + shadow only — the grid never reflows. The lift uses the individual
   `translate` property, NOT `transform`, because a revealed card's `transform`
   is held by the reveal animation's forwards fill (§15b). Disabled under
   reduced motion (§16) and on touch-only devices where hover would stick. */
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    border-color: var(--sage);
    translate: 0 -2px;
    box-shadow: var(--shadow-hover);
  }
}
@media (min-width: 600px) {
  .card { padding: 24px; }
}
.card h3 { margin-bottom: 6px; }
.card p { font-size: 15px; line-height: 1.55; color: var(--muted); }

/* Auto-fit grid: no breakpoint maths needed for simple card sets. */
.card-grid {
  margin-top: 36px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: 14px;
}
@media (min-width: 600px) { .card-grid { gap: 18px; } }
@media (min-width: 1024px) { .card-grid { margin-top: 40px; gap: 20px; } }

/* The four steps are the site's only real sequence, so they must never break
   into an orphan row. auto-fit alone does exactly that: three 240px tracks fit
   from 836px up, so 836–1023px rendered 3 across with step 04 stranded on its
   own row. Hold 2x2 through that range, then go 4-up in one move at 1024px. */
@media (min-width: 600px) and (max-width: 1023px) {
  .card-grid--4 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .card-grid--4 { grid-template-columns: repeat(4, 1fr); }
}

/* --- The step card -----------------------------------------------------
   Its head is a miniature of the section masthead: a terracotta ordinal above
   a full-width hairline. Same logic, smaller scale — that is what ties the
   card grid to the page. Ordinals appear ONLY where a sequence is real. */
.step { }

.step-head {
  border-bottom: 1px solid var(--border);
  padding-bottom: 12px;
  margin-bottom: 14px;
}
.step-num {
  display: block;
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.12em;
  color: var(--terracotta);
}

/* --- Feature / illustration icons -------------------------------------
   Decorative inline SVG only. Always aria-hidden="true" focusable="false". */
.feature { }
.feature-icon {
  display: block;
  width: 22px;
  height: 22px;
  margin-bottom: 14px;
  color: var(--brand);   /* SVGs stroke currentColor at 1.7 */
}
.feature-icon--sage { color: var(--sage); }
.feature-icon--sm { width: 18px; height: 18px; margin-bottom: 0; }

/* --- The pickup-code motif ---------------------------------------------
   Used twice site-wide: the "How it works" step 03 chip and the green station
   node in the handover diagram. Purely illustrative — always aria-hidden, and
   the word EXAMPLE is built into the label so it can never read as a live
   code. Real pickup codes are SIX NUMERIC DIGITS, so every example shows six
   digits (leading zero allowed) — never letters, never four characters. */
.code-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  background: var(--sand-deep);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 6px 10px;
}
.code-chip-label {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
}
.code-chip-value {
  font-size: 14px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.28em;
  color: var(--brand);
}


/* ==========================================================================
   10  HERO
   Left-aligned at every breakpoint, one column. A centred hero is the default
   look; left-aligned reads as a plain statement of fact, and long headlines
   set better ragged-right at 360px.

   THE ENTRANCE (page load; CSS only, no JS involved):
     0ms      the three ribbons start sweeping in behind the hero — the logo's
              own road motif, drawn once left→right (stroke-dashoffset), fanning
              in 120ms apart. They finish at ~1.6s and settle for good.
     0–120ms  the mark scales up from 92% and fades in.
     +90ms each — eyebrow, headline line 1, headline line 2, lede, buttons,
              launch note rise 14px and fade in (the existing .enter cascade,
              spaced by .hero's own --stagger of 90ms).
   Total: content fully painted well inside 1s (LCP-safe); road done at 1.6s.
   Then ONE resting movement: the road drifts 8px sideways over 14s and back —
   below the threshold of "animation", above the threshold of "static image".

   PERFORMANCE: the road is a single inline SVG (three paths, ~1KB), animated
   only via stroke-dashoffset (a paint-only property, GPU-composited path) and
   one transform on its wrapper. No layout property ever animates. On phones
   the road is thinner and the drift is switched off entirely (see 600px query).

   REDUCED MOTION (§16): everything sits in its final state — road drawn, text
   visible, no drift. NO-JS: identical to normal, since none of this needs JS.
   The road paths carry pathLength="1" so the dash maths is width-independent.
   ========================================================================== */
.hero {
  --enter-stagger: 90ms;               /* the .enter cascade inside the hero  */
  --stagger: 90ms;
  --road-h: 84px;                      /* the road band under the copy        */
  padding-block: 48px 0;
  position: relative;
  overflow: hidden;                    /* clips the road's lead-in / lead-out */
  isolation: isolate;
}
@media (min-width: 600px) { .hero { --road-h: 120px; padding-block: 72px 0; } }
@media (min-width: 900px) { .hero { --road-h: 150px; padding-block: 96px 0; } }

/* One soft brand-green glow, top-right — the only gradient on the site. */
.hero::before {
  content: "";
  position: absolute;
  inset: -20% -30% auto auto;
  width: 70vw;
  max-width: 900px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(29, 92, 69, .10), rgba(29, 92, 69, 0));
  pointer-events: none;
  z-index: 0;
}
.hero > .container { position: relative; z-index: 2; }

/* --- The road: the logo's ribbons, flowing out beneath the copy ----------
   Its own band under the text (never behind it — measured: the copy fills
   90–96% of the hero's height at every width, so a background road would
   cross the headline or the buttons). It rises from below the left edge, runs
   the width of the page and exits top-right — the mark's ribbons, at page
   scale, leading the eye down into the page. */
.hero-road {
  position: relative;                  /* in flow: reserves its own height   */
  z-index: 1;
  height: var(--road-h);
  margin-top: 28px;
  margin-inline: -6%;
  pointer-events: none;
  /* the whole road drifts once it is drawn — transform only, wrapper only */
  animation: hero-drift 14s ease-in-out 1.8s infinite alternate;
  will-change: transform;
}
@media (min-width: 900px) { .hero-road { margin-top: 20px; } }
.hero-road svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}
.hero-road path {
  fill: none;
  stroke-linecap: round;
  stroke-width: 10;
  /* pathLength="1" on every path: a full dash is exactly 1 unit long. */
  stroke-dasharray: 1 1;
  stroke-dashoffset: 1;
  animation: hero-draw 1600ms var(--ease-out) both;
  opacity: .55;
}
.hero-road .road-red   { stroke: var(--ribbon-red);   animation-delay: 0ms; }
.hero-road .road-green { stroke: var(--ribbon-green); animation-delay: 120ms; }
.hero-road .road-gold  { stroke: var(--ribbon-gold);  animation-delay: 240ms; }
@keyframes hero-draw {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}
@keyframes hero-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(-8px); }
}
/* Phones: a thinner road, no drift (nothing moves once the entrance is done)
   and slightly lower opacity so it never competes with the headline. */
@media (max-width: 599px) {
  .hero-road { animation: none; will-change: auto; }
  .hero-road path { stroke-width: 7; opacity: .55; }
}
/* Very wide screens: keep the road's stroke from thinning to a hairline. */
@media (min-width: 1440px) {
  .hero-road path { stroke-width: 12; }
}

/* --- The mark ------------------------------------------------------------ */
.hero-logo {
  display: block;
  height: clamp(60px, 11vw, 88px);
  width: auto;
  margin-bottom: 22px;
  /* its own entrance: a settle-in scale, not the text's rise */
  transform-origin: left bottom;
  animation-name: hero-logo-in;
  animation-duration: 720ms;
}
@keyframes hero-logo-in {
  from { opacity: 0; transform: scale(.92) translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.hero h1 { margin-top: 18px; }
/* The two headline lines enter one after the other. */
.hero .h1-line {
  display: block;
  animation: rise-in var(--dur-enter) var(--ease-out) both;
}
.hero .h1-line--strong { animation-delay: calc(var(--stagger) * 2); }
.hero .h1-line--soft   { animation-delay: calc(var(--stagger) * 3); }
/* …so the h1 wrapper itself must not also fade (it would double up). */
.hero h1.enter { animation: none; opacity: 1; }
/* Cascade order for the .enter siblings inside .stagger. The h1 lines take
   slots 2 and 3, so the lede/buttons/note continue from 4. */
.hero .stagger > .lede.enter        { --i: 4; }
.hero .stagger > .btn-row.enter     { --i: 5; }
.hero .stagger > .launch-note.enter { --i: 6; }

@media (min-width: 900px) {
  /* The hero is ONE column. .h1-line's 15ch measure was cut for the narrow
     left half of an old 2-up split; at full width it stranded the headline in
     the left 45% of the page. 22ch sets it in two lines and fills 66% at
     1440px / 81% at 900px — an editorial measure, not full-bleed. Below 900px
     the hero was always single-column, so the 15ch default is left alone. */
  .hero .h1-line { max-width: 22ch; }
}


/* ==========================================================================
   11  HANDOVER DIAGRAM
   Below 900px: a vertical column of three nodes joined by dashed connectors.
   At 900px+: three columns joined by horizontal dashed connectors, with the
   "never happens" arc slung underneath.
   ========================================================================== */
.flow { margin: 0; }

.flow-nodes {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}

/* Vertical connector (mobile). */
.flow-link {
  height: 36px;
  display: grid;
  place-items: center;
  position: relative;
}
.flow-link::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 10px;
  width: 0;
  border-left: 2px dashed var(--border);
}
.flow-link svg {
  position: relative;
  width: 10px;
  height: 10px;
  color: var(--sage);
  align-self: end;
}

.flow-node {
  background: var(--sand-deep);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 20px;
}
@media (min-width: 600px) { .flow-node { padding: 24px; } }

.flow-node-label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--brand);   /* sits on sand-deep — brand, not terracotta */
}
.flow-node h3 { margin-top: 8px; font-size: 17px; }
.flow-node p { margin-top: 6px; font-size: 15px; color: var(--muted); }

/* The station — the ONLY elevated element on the site, so the eye lands on
   the mechanism rather than on the people. */
.flow-node--station {
  background: var(--brand);
  border: 0;
  color: #FFFFFF;
  padding: 22px;
  box-shadow: var(--shadow-lift);
}
@media (min-width: 600px) { .flow-node--station { padding: 26px; } }
.flow-node--station h3 { color: #FFFFFF; }
.flow-node--station .flow-node-label { color: #FFFFFF; opacity: .72; }
.flow-node--station p { color: #FFFFFF; opacity: .84; }

.flow-chip {
  display: inline-flex;
  align-items: center;
  margin-top: 14px;
  padding: 6px 12px;
  border: 1px solid rgba(255, 255, 255, .45);
  border-radius: var(--r-pill);
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.22em;
  color: #FFFFFF;
}

@media (min-width: 900px) {
  .flow-nodes {
    /* Both the gap and the connector width are tokens because the connector
       offsets below are DERIVED from them. Change one and the other follows;
       hard-coding either re-introduces the off-centre bug described below. */
    --flow-gap: 56px;
    --flow-link-w: 40px;
    --flow-col: calc((100% - var(--flow-gap) * 2) / 3);
    --flow-inset: calc((var(--flow-gap) - var(--flow-link-w)) / 2);
    grid-template-columns: 1fr 1fr 1fr;
    gap: var(--flow-gap);
    align-items: stretch;
    position: relative;
  }
  /* Connectors move into the gutters as horizontal dashed rules. */
  .flow-link {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    height: auto;
    width: var(--flow-link-w);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
  }
  .flow-link::before {
    position: static;
    content: "";
    flex: 1 1 auto;
    height: 0;
    border-left: 0;
    border-top: 2px dashed var(--border);
  }
  /* The chevron path is a DOWN-pointing V (right for the stacked mobile
     column). Once the rule runs left→right it must point right: -90° turns
     the bottom tip to the right-hand side. */
  .flow-link svg { align-self: center; transform: rotate(-90deg); }

  /* Centre each connector in its GUTTER, not at a round percentage of the
     container. With three equal columns and a fixed gap, one column measures
     (100% - 2*gap)/3, so gutter 1 starts exactly one column in and gutter 2
     starts two columns + one gap in; the extra (gap - link)/2 centres the
     40px connector inside the 56px gutter.
       A plain `left: 33.333%` is NOT the same point: it ignores the 112px of
     gap the columns give up, landing the connector 9.33px right of the gutter
     centre and overlapping the station card by 1.33px. Measured in Chrome at
     900/1024/1280/1440px before this fix; 0.00px off-centre after. */
  .flow-link--1 {
    left: calc(var(--flow-col) + var(--flow-inset));
  }
  .flow-link--2 {
    left: calc(var(--flow-col) * 2 + var(--flow-gap) + var(--flow-inset));
  }
}

/* The never-happens arc. Dashed = does not exist.
   Motion: once the figure is revealed the arc WIPES in from the left (a
   clip-path transition, so the dash pattern is untouched) and the terracotta ×
   scales in last — the eye follows the path that never happens, then lands on
   the reason. Static under reduced motion / no JS (§15b, §16). */
.flow-never {
  margin-top: 20px;
  color: var(--border);
}
.flow-never svg { display: block; width: 100%; height: auto; }
/* Each half's clip-path resolves against its OWN box, so the two halves are
   sequenced explicitly: left half 350→850ms, right half 850→1350ms, × at
   1350ms — one continuous sweep, then the reason. */
html.js .flow.reveal .flow-never svg > path {   /* the two arc halves only */
  clip-path: inset(0 100% 0 0);
  transition: clip-path 500ms var(--ease-out) 350ms;
}
html.js .flow.reveal .flow-never svg > path + path { transition-delay: 850ms; }
html.js .flow.reveal .flow-never svg .x-mark {
  opacity: 0;
  transform: scale(.4);
  transform-origin: 320px 44px;   /* the ×'s own centre in SVG units */
  transition: opacity 320ms var(--ease-out) 1350ms, transform 420ms var(--ease-out) 1350ms;
}
html.js .flow.reveal.is-visible .flow-never svg > path { clip-path: inset(0 0 0 0); }
html.js .flow.reveal.is-visible .flow-never svg .x-mark { opacity: 1; transform: scale(1); }
.flow-caption {
  margin-top: 8px;
  font-size: 13.5px;
  color: var(--muted);
}
.flow figcaption { font-size: 13.5px; color: var(--muted); margin-top: 20px; }

/* The terracotta × glyph. It means, consistently and only, "this does not
   happen" — the never-happens arc above, nowhere else. */
.x-mark { color: var(--terracotta); flex: 0 0 auto; }


/* ==========================================================================
   12  "GOOD TO KNOW" STRIP
   The lightest section on the page: no cards, no icons, no shadows, no fills.
   These terracotta left-rules plus the eyebrows are the only structural uses
   of terracotta on the site — keeping it scarce is what stops it becoming a
   second brand colour and diluting the green.
   ========================================================================== */
.rules-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  margin-top: 24px;
}
@media (min-width: 768px) {
  .rules-grid { grid-template-columns: repeat(3, 1fr); gap: 32px; }
}
.rule-item {
  border-left: 2px solid var(--terracotta);
  padding-left: 16px;
}
.rule-item h3 { font-size: 15.5px; margin-bottom: 4px; }
.rule-item p { font-size: 14.5px; line-height: 1.55; color: var(--muted); }


/* ==========================================================================
   12b ABOUT THE CREATOR  (index.html)
   A cream band — the same material as the handover sheet — pairing a portrait
   with the founder's story. ≥900px: portrait + pull quote in a narrow left
   column, the story on the right; below that, everything stacks (portrait,
   quote, story). The portrait is an <img src="assets/founder.jpg"> laid over a
   styled placeholder frame: until the real photo exists (or if it ever fails
   to load) site.js marks the <img> .is-missing and the frame shows through —
   initials + "Photo coming soon". Drop the real file in and nothing else
   changes.
   ========================================================================== */
.about-grid {
  margin-top: 36px;
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
}
@media (min-width: 900px) {
  .about-grid {
    grid-template-columns: minmax(260px, 340px) minmax(0, 1fr);
    gap: 64px;
    align-items: start;
    margin-top: 40px;
  }
}

/* --- Portrait ------------------------------------------------------------ */
.about-photo { margin: 0; max-width: 340px; }
.about-photo-frame {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: 20px;
  overflow: hidden;
  background: var(--sand-deep);
  border: 1px solid var(--border);
}
.about-photo-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The file is missing (or failed): hide the broken <img> so the frame shows. */
.about-photo-frame img.is-missing { visibility: hidden; }
/* Without JavaScript nothing can add .is-missing, and a missing file would
   paint the browser's broken-image icon over the frame (it is drawn as
   replaced-element chrome, so no CSS on the img can hide it). So with JS off
   the img is not rendered at all and the styled frame stands in — the
   deliberate trade-off is that a no-JS visitor sees "Photo coming soon" even
   once the real photo exists; site.js (present on every page) is what swaps
   the two, exactly as it does the scroll reveal. */
html:not(.js) .about-photo-frame img { display: none; }
/* Placeholder content, always rendered underneath the photo. */
.about-photo-fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  gap: 10px;
  padding: 24px;
}
.about-photo-initials {
  display: block;
  font-size: clamp(56px, 12vw, 84px);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1;
  color: var(--brand);
}
.about-photo-hint {
  display: block;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);   /* muted on sand-deep is 5.89:1 */
}
.about-photo figcaption {
  margin-top: 12px;
  font-size: 13.5px;
  color: var(--muted);
}

/* --- Pull quote --------------------------------------------------------- */
.about-quote {
  margin: 28px 0 0;
  padding: 4px 0 4px 20px;
  border-left: 3px solid var(--terracotta);
  max-width: 340px;   /* the portrait's width — the quote sits under it */
}
.about-quote p {
  font-size: clamp(20px, 2.4vw, 24px);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--brand);
  max-width: none;
  text-wrap: balance;
}
.about-quote p::before { content: "\201C"; }
.about-quote p::after  { content: "\201D"; }
.about-quote blockquote { margin: 0; }
.about-quote figcaption {
  margin-top: 10px;
  font-size: 13.5px;
  color: var(--muted);
}

/* --- Story ---------------------------------------------------------------- */
.about-name {
  font-size: clamp(20px, 2.6vw, 24px);
  line-height: 1.25;
  letter-spacing: -0.015em;
}
.about-role {
  margin-top: 6px;
  font-size: 15px;
  color: var(--muted);
}
.about-body { margin-top: 22px; }
.about-body p {
  font-size: 16px;
  line-height: 1.7;
  color: var(--ink);
  max-width: 62ch;
}
.about-body p + p { margin-top: 14px; }
@media (min-width: 600px) {
  .about-body p { font-size: 16.5px; }
}


/* ==========================================================================
   12c DOWNLOAD THE APP  (download.html)
   Its own page at /download (reached from the hero's "Get the app" and the
   nav). A sand-deep band under the doc header. One cream card, two halves:
     LEFT  — masthead-style eyebrow, h2, one line, then the ACTION:
             on Android a brand-green "Download for Android" button whose
             href is /bookswap.apk with the download attribute; elsewhere the
             QR card (below). Then the three install steps and the store line.
     RIGHT — the QR: a 6KB inline SVG (assets/apk-qr.svg, generated once —
             no CDN, no runtime code) that encodes the same URL, with a
             "point your phone's camera at this" caption.
   site.js reads the user agent once and sets ONE class on the card:
     .dl--android  → button prominent, QR hidden (a phone can't scan itself)
     .dl--other    → QR + "open on your Android phone" prominent, button
                     demoted to a small text link (a desktop download is
                     useless, but never impossible)
     neither / no JS → BOTH shown at full weight. Nobody is ever stuck.
   ========================================================================== */
.download-card {
  margin-top: 36px;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 26px 22px;
  display: grid;
  gap: 28px;
}
@media (min-width: 600px) { .download-card { padding: 36px; } }
@media (min-width: 900px) {
  .download-card {
    grid-template-columns: minmax(0, 1fr) 260px;
    gap: 48px;
    padding: 44px 48px;
    border-radius: 20px;
    align-items: start;
    margin-top: 40px;
  }
}
.download-card h3 {
  font-size: clamp(20px, 2.6vw, 24px);
  line-height: 1.25;
  letter-spacing: -0.015em;
}
.download-card .sub { margin-top: 8px; max-width: 52ch; }

/* --- the action row --------------------------------------------------- */
.dl-action { margin-top: 22px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; }
.btn--download { gap: 10px; padding: 15px 24px; }
.btn--download svg { width: 18px; height: 18px; flex: 0 0 auto; }
@media (max-width: 399px) { .btn--download { width: 100%; justify-content: center; } }
.dl-size {
  font-size: 13.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* the demoted form of the button on non-Android devices */
.download-card.dl--other .btn--download {
  background: transparent;
  border-color: var(--border);
  color: var(--brand);
  box-shadow: none;
  padding: 12px 16px;     /* 12 + 22 line + 12 = 46px: still a thumb-sized target */
  font-size: 14.5px;
  min-height: 44px;
}
.download-card.dl--other .btn--download:hover { background: var(--sand); border-color: var(--sage); transform: none; }

/* --- the "open on your phone" line (non-Android) ----------------------- */
.dl-phone {
  margin-top: 22px;
  padding: 14px 16px;
  background: var(--sand-deep);
  border-radius: var(--r-sm);
  border-left: 3px solid var(--brand);
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  max-width: 52ch;
}
.dl-phone strong { color: var(--brand); }
.download-card.dl--android .dl-phone { display: none; }

/* --- install steps ------------------------------------------------------ */
.dl-steps {
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
  counter-reset: dl;
  display: grid;
  gap: 12px;
  max-width: 56ch;
}
.dl-steps li {
  counter-increment: dl;
  position: relative;
  padding-left: 40px;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  max-width: none;
}
.dl-steps li::before {
  content: counter(dl, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 2px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
  color: var(--terracotta);
}
.dl-steps-title {
  margin-top: 26px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
}
.dl-store {
  margin-top: 22px;
  font-size: 14px;
  color: var(--muted);
  max-width: 52ch;
}

/* --- the QR ---------------------------------------------------------------- */
.dl-qr {
  display: grid;
  gap: 12px;
  justify-items: center;
  text-align: center;
  padding: 18px;
  background: var(--sand);
  border: 1px solid var(--border);
  border-radius: var(--r);
  max-width: 300px;
}
@media (min-width: 900px) { .dl-qr { max-width: none; justify-self: end; width: 100%; } }
.dl-qr img {
  display: block;
  width: 100%;
  max-width: 200px;
  height: auto;
  aspect-ratio: 1;
  border-radius: 8px;
  image-rendering: pixelated;   /* crisp modules at any size */
}
.dl-qr-caption { font-size: 13.5px; line-height: 1.5; color: var(--muted); max-width: 26ch; }
.dl-qr-caption strong { color: var(--ink); }
.dl-qr-url {
  font-size: 12.5px;
  font-variant-ligatures: none;
  color: var(--brand);
  word-break: break-all;
  text-decoration: none;
}
.dl-qr-url:hover { text-decoration: underline; }
/* a phone cannot scan itself */
.download-card.dl--android .dl-qr { display: none; }
@media (min-width: 900px) { .download-card.dl--android { grid-template-columns: 1fr; } }


/* ==========================================================================
   13  CLOSING BRAND-GREEN PANEL
   INSET, not full-bleed — it stays inside the page's card language and stops
   two heavy bands stacking straight into the sand-deep footer.
   ========================================================================== */
.cta-outer { padding-block: 40px; }
@media (min-width: 600px) { .cta-outer { padding-block: 56px; } }
@media (min-width: 900px) { .cta-outer { padding-block: 72px; } }

.cta-panel {
  background: var(--brand);
  color: #FFFFFF;
  border-radius: var(--r);
  padding: 40px 24px;
}
@media (min-width: 600px) { .cta-panel { padding: 56px 40px; } }
@media (min-width: 900px) { .cta-panel { border-radius: 20px; padding: 72px 64px; } }

.cta-inner { max-width: 640px; }
.cta-panel h2 {
  font-size: clamp(26px, 4.6vw, 40px);
  color: #FFFFFF;
  max-width: 18ch;
  margin-top: 14px;
}
.cta-panel .eyebrow { color: #FFFFFF; opacity: .72; }
.cta-panel p { color: #FFFFFF; opacity: .82; font-size: 16.5px; margin-top: 14px; max-width: 46ch; }
.cta-panel .launch-note { color: #FFFFFF; opacity: .72; margin-top: 14px; }


/* ==========================================================================
   14  DOCUMENT PAGES  (guide.html, privacy.html)
   The landing page is bands of cards on sand; a document page is ONE long
   cream sheet on sand. Same materials, different scale.
   ========================================================================== */

/* --- 15a  Doc header ---------------------------------------------------- */
.doc-header { padding-block: 40px 28px; }
@media (min-width: 600px) { .doc-header { padding-block: 56px 36px; } }

.doc-header-inner {
  max-width: var(--measure);
  padding-bottom: 24px;
  border-bottom: 1px solid var(--border);
}
/* On a page whose first section opens with a .masthead (the guide's journey)
   the masthead IS the rule — a second hairline 30px above it would read as a
   doubled line with mismatched ends. */
.doc-header--flush .doc-header-inner { border-bottom: 0; padding-bottom: 0; }
.doc-header--flush { padding-bottom: 22px; }
/* Deliberately smaller than the landing h1 so site-wide hierarchy holds. */
.doc-header h1 {
  margin-top: 14px;
  font-size: clamp(28px, 5vw, 38px);
  letter-spacing: -0.02em;
  line-height: 1.14;
}
.doc-standfirst {
  margin-top: 14px;
  font-size: 17px;
  color: var(--muted);
  max-width: 52ch;
}
.doc-meta {
  margin-top: 12px;
  font-size: 14px;
  color: var(--muted);
}

/* --- 15b  The sheet ----------------------------------------------------- */
.sheet {
  max-width: var(--measure);
  margin: 0 auto 64px;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 24px;   /* no box-shadow — border only, like every card on the site */
}
@media (min-width: 600px) { .sheet { padding: 40px; } }
@media (min-width: 900px) { .sheet { padding: 56px; } }

/* --- 15c  Long-form typography ----------------------------------------- */
.prose {
  font-size: 16px;
  line-height: 1.72;
  color: var(--ink);
}
@media (min-width: 600px) { .prose { font-size: 16.5px; } }

.prose > * + * { margin-top: 14px; }

.prose h2 {
  /* Section headings must clearly outrank the item headings below them
     (.faq-item h3, 17px). At a flat 20px they read as siblings, which flattens
     the guide; this keeps a real step in the scale without shouting the way
     the 34px landing-page h2 would in a document. */
  font-size: clamp(21px, 2.4vw, 24px);
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-top: 36px;
  margin-bottom: 10px;
  padding-top: 26px;
  border-top: 1px solid var(--border);
  scroll-margin-top: 24px;
  max-width: none;
}
.prose h2:first-of-type {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
/* ...but the flush reset is only right when the heading actually opens the
   sheet. On the policy an intro note precedes it, so the heading needs its air
   back. Higher specificity than the reset above, scoped to the first heading
   only. (The guide's sheet now opens directly with its FAQ heading.) */
.prose > *:not(h2) + h2:first-of-type {
  margin-top: 30px;
}
.prose h3 {
  font-size: 17px;
  font-weight: 700;
  color: var(--brand);
  margin-top: 22px;
  margin-bottom: 6px;
}
.prose p { max-width: none; }
.prose ul,
.prose ol { padding-left: 22px; margin-top: 12px; }
.prose li { margin-block: 8px; max-width: none; }
.prose li::marker { color: var(--faint); }  /* the one permitted use of --faint */
.prose a {
  color: var(--sage);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color var(--t);
}
.prose a:hover { color: var(--brand); }
.prose strong { font-weight: 700; }
.prose hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin-block: 28px;
}

/* Numbered policy sections — same discipline as the landing page: numbers
   only where the sequence is real. */
.sec-num {
  color: var(--terracotta);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  margin-right: 8px;
}

/* --- 15d  Table of contents well --------------------------------------- */
.toc {
  background: var(--sand-deep);
  border-radius: var(--r-sm);
  padding: 18px 20px;
  margin-bottom: 28px;
}
.toc h2 {
  margin: 0 0 10px;
  padding: 0;
  border: 0;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--brand);
}
.toc ol {
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.toc li { margin: 0; max-width: none; }
.toc li::marker { color: var(--muted); }
.toc a {
  font-size: 15px;
  color: var(--brand);   /* sage fails on sand-deep */
  text-decoration: none;
}
.toc a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* --- 15e  FAQ rows — hairline-separated, never carded ------------------- */
.faq { margin-top: 8px; }
.faq-item {
  border-top: 1px solid var(--border);
  padding-block: 20px;
}
.faq-item:last-child { border-bottom: 1px solid var(--border); }
.faq-item h3 {
  margin: 0 0 6px;
  font-size: 17px;
  font-weight: 700;
  color: var(--ink);
}
.faq-item p { color: var(--muted); max-width: none; }

/* ==========================================================================
   15  CALLOUTS, DIVIDERS, UTILITIES
   ========================================================================== */
.callout,
.note {
  background: var(--sand-deep);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-sm);
  padding: 14px 18px;
  margin-block: 20px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink);
}
.callout p,
.note p { max-width: none; }
/* Any label inside a callout is brand green — terracotta is 4.40:1 here. */
.callout strong,
.note strong { color: var(--brand); }

.divider {
  border: 0;
  border-top: 1px solid var(--border);
  margin-block: 32px;
}

/* Screen-reader-only text. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.stack-sm > * + * { margin-top: 8px; }
.stack > * + * { margin-top: 16px; }
.text-muted { color: var(--muted); }
.no-measure { max-width: none; }


/* ==========================================================================
   15b MOTION SYSTEM
   Three primitives, all opacity/transform only, all run once:

   .enter    — page-load entrance for above-the-fold content (the hero). Pure
               CSS keyframes, starts immediately, no JS involved. Give siblings
               --i:0,1,2… for a stagger. Kept short (560ms) so the hero h1 —
               the page's LCP element — is fully painted well under a second.
   .reveal   — scroll reveal for everything below the fold. Hidden ONLY when
               html.js is present (site.js adds the class in <head>), so a
               visitor without JavaScript sees a complete, static page. site.js
               adds .is-visible the first time the element enters the viewport,
               then stops watching it. Never re-hides.
   .stagger  — parent utility: children get --i by position (up to 8) so a
               card grid or list reveals as a wave rather than a block.

   Reduced motion: §17 zeroes every animation and shows every .reveal at once;
   site.js ALSO reads the media query and reveals everything immediately, so
   the two agree even mid-session if the user flips the OS setting.
   ========================================================================== */
@keyframes rise-in {
  from { opacity: 0; transform: translateY(var(--rise)); }
  to   { opacity: 1; transform: none; }
}
.enter {
  animation: rise-in var(--dur-enter) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--enter-stagger, 60ms));
}
/* Reveal is ANIMATION-driven, not transition-driven, on purpose:
   - a transition would take its delay from the after-change (.is-visible)
     style, so a stagger could only be expressed there — and a transition on
     opacity/transform would also override .card's own hover transition list.
   - a keyframe animation carries its own delay, leaves the element's
     transition list untouched, and (via fill-mode: both) holds the hidden
     state during the stagger delay.
   Card hover therefore lifts with the `translate` property, which is separate
   from `transform` and so is never held by the animation's forwards fill. */
html.js .reveal { opacity: 0; }
html.js .reveal.is-visible {
  animation: rise-in var(--dur-reveal) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
/* Keyboard safety: a link/button inside a not-yet-revealed block must never
   be focused while invisible. !important beats the running animation. */
html.js .reveal:focus-within {
  opacity: 1 !important;
  transform: none !important;
  animation-delay: 0ms !important;
}

.stagger > :nth-child(1) { --i: 0; }
.stagger > :nth-child(2) { --i: 1; }
.stagger > :nth-child(3) { --i: 2; }
.stagger > :nth-child(4) { --i: 3; }
.stagger > :nth-child(5) { --i: 4; }
.stagger > :nth-child(6) { --i: 5; }
.stagger > :nth-child(7) { --i: 6; }
.stagger > :nth-child(8) { --i: 7; }

/* Slightly stronger rise for cards, so a grid visibly settles into place. */
.reveal.card,
.reveal.flow-node { --rise: 18px; }


/* ==========================================================================
   15c THE JOURNEY  (guide.html only; driven by journey.js)
   One book, five steps, laid along a "road" that echoes the logo: a sand-deep
   road bed with the mark's three ribbons (red / green / gold) painted on it.

   MARKUP (one DOM, two layouts):
     <div class="journey" id="journey">
       <svg class="journey-path" aria-hidden="true">      ← journey.js builds it
       <ol class="journey-steps">
         <li class="jn-step">
           <div class="jn-head">
             <h3 class="jn-title">
               <button class="jn-node" type="button" aria-expanded="…"
                       aria-controls="jn-detail-1" id="jn-btn-1">
                 <span class="jn-icon" aria-hidden="true"><svg…/></span>
                 <span class="jn-num" aria-hidden="true">01</span>
                 <span class="jn-label">List or find a book</span>
               </button>
             </h3>
             <p class="jn-line">one line</p>
           </div>
           <div class="jn-detail" id="jn-detail-1" role="region"
                aria-labelledby="jn-btn-1">
             <span class="jn-detail-label">Step 1 of 5</span>
             <p>full detail…</p>
           </div>
         </li> ×5

   DESKTOP (≥900px, html.js): five columns; odd steps sit high, even steps
   low, so the road waves through the node centres. The <li> is
   display:contents so its .jn-head lands in grid row 1 and its .jn-detail in
   row 2 spanning every column — the "panel". Exactly one detail is open at a
   time (the active step); the button is a disclosure that also moves the
   marker along the road.
   MOBILE (<900px) and NO-JS: a vertical timeline — road down the left, nodes
   on it, title + one-liner + detail to the right, every detail open. Buttons
   are ordinary accordion toggles here.
   ========================================================================== */
/* The section wrapper — a hook, no styling of its own beyond the shared
   .section rhythm; kept as a class so the page reads and the checker sees it. */
.journey-section { position: relative; }
.journey-hint {
  display: none;              /* desktop-only helper line, see below */
  margin-top: 8px;
  font-size: 13.5px;
  color: var(--muted);
}

.journey {
  --jn-node: 60px;            /* icon circle diameter                     */
  --jn-road: 18px;            /* road-bed stroke width (JS mirrors this)  */
  position: relative;
  margin-top: 36px;
}

/* The SVG road: absolutely fills the .journey box; journey.js sizes it and
   writes the path data from the measured node centres. */
.journey-path {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.journey-road-edge { stroke: var(--border);    stroke-width: calc(var(--jn-road) + 2px); fill: none; stroke-linecap: round; }
.journey-road      { stroke: var(--sand-deep); stroke-width: var(--jn-road);             fill: none; stroke-linecap: round; }
.journey-ribbon    { fill: none; stroke-width: 3px; stroke-linecap: round; }
.journey-ribbon--red   { stroke: var(--ribbon-red); }
.journey-ribbon--green { stroke: var(--ribbon-green); }
.journey-ribbon--gold  { stroke: var(--ribbon-gold); }
/* Draw-in: journey.js sets dasharray/dashoffset = path length, then zeroes
   the offset once the section is in view. The three ribbons fan in 90ms
   apart, like the mark. */
html.js .journey-ribbon { transition: stroke-dashoffset 1600ms var(--ease-out); }
html.js .journey-ribbon--green { transition-delay: 90ms; }
html.js .journey-ribbon--gold  { transition-delay: 180ms; }
/* On the vertical (scroll-linked) layout the offset follows the scroll, so
   the transition is just a smoothing filter. */
html.js .journey--vertical .journey-ribbon { transition: stroke-dashoffset 160ms linear; transition-delay: 0ms; }

/* The traveller: a brand-green dot with a cream ring that rides the road to
   the active step (desktop) or the reading position (mobile). */
.journey-marker { opacity: 0; transition: opacity 300ms var(--ease-out); }
.journey-marker.is-on { opacity: 1; }
.journey-marker-ring { fill: var(--cream); stroke: var(--brand); stroke-width: 2px; }
.journey-marker-dot  { fill: var(--brand); }

/* --- Steps: shared ------------------------------------------------------ */
.journey-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
}
.jn-step { max-width: none; }
.jn-title { margin: 0; font-size: inherit; line-height: inherit; }

.jn-node {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 14px;
  border-radius: var(--r-sm);
}
.jn-icon {
  position: relative;
  z-index: 1;                       /* above the road */
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: var(--jn-node);
  height: var(--jn-node);
  border-radius: 50%;
  background: var(--cream);
  border: 2px solid var(--border);
  color: var(--brand);
  box-shadow: 0 0 0 6px var(--sand);   /* punches a clean hole in the road */
  transition: border-color var(--dur-hover) var(--ease-out),
              background-color var(--dur-hover) var(--ease-out),
              color var(--dur-hover) var(--ease-out),
              translate var(--dur-hover) var(--ease-out),
              box-shadow var(--dur-hover) var(--ease-out);
}
.jn-icon svg { width: 24px; height: 24px; }
.jn-num {
  display: block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
  color: var(--terracotta);
}
.jn-label {
  display: block;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink);
  transition: color var(--t);
}
.jn-line {
  margin-top: 6px;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 34ch;
}

/* Hover / focus / active states — the node lifts and warms; the active step
   is the one filled brand green (the same "the mechanism is what matters"
   move as the station node in the handover diagram). */
@media (hover: hover) and (pointer: fine) {
  .jn-node:hover .jn-icon { border-color: var(--sage); translate: 0 -2px; box-shadow: 0 0 0 6px var(--sand), var(--shadow-hover); }
  .jn-node:hover .jn-label { color: var(--brand); }
}
.jn-node:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; }
/* Horizontal: the OPEN step is the green one. Vertical: green means "the road
   has reached this step" (journey.js sets .is-reached as the ribbons draw
   past each node), so the timeline fills in as you read down it. */
html.js .journey--horizontal .jn-node[aria-expanded="true"] .jn-icon,
.journey:not(.journey--horizontal) .jn-step.is-reached .jn-icon {
  background: var(--brand);
  border-color: var(--brand);
  color: #FFFFFF;
}
html.js .journey--horizontal .jn-node[aria-expanded="true"] .jn-label,
.journey:not(.journey--horizontal) .jn-step.is-reached .jn-label { color: var(--brand); }

/* --- Detail (the accordion body / desktop panel) ------------------------- */
.jn-detail {
  margin-top: 12px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink);
}
.jn-detail p { max-width: 62ch; }
.jn-detail p + p { margin-top: 8px; }
.jn-detail-label {
  display: none;                    /* desktop panel only */
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--terracotta);
}
/* Collapsed state (mobile accordion; desktop non-active). Both attribute and
   [hidden] are honoured so the no-JS page — where nothing sets either — shows
   every detail. */
.jn-detail[hidden] { display: none; }

/* --- MOBILE / default: vertical timeline -------------------------------- */
.jn-step {
  position: relative;
  padding-left: calc(var(--jn-node) + 18px);
  padding-bottom: 30px;
}
.jn-step:last-child { padding-bottom: 8px; }
.jn-head { position: relative; }
/* The icon sits in the left rail, on the road. */
.jn-step .jn-icon {
  position: absolute;
  left: calc(-1 * (var(--jn-node) + 18px));
  top: -4px;
}
.jn-node { display: block; width: 100%; min-height: calc(var(--jn-node) - 8px); }
.jn-node .jn-num { margin-top: 2px; }
.jn-node .jn-label { margin-top: 2px; padding-right: 30px; }
/* Accordion chevron on the vertical layout only (a chevron on the desktop
   panel-selector would promise the wrong thing). */
html.js .journey:not(.journey--horizontal) .jn-node::after {
  content: "";
  position: absolute;
  right: 4px;
  top: 14px;
  width: 9px;
  height: 9px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg);
  transition: transform var(--dur-hover) var(--ease-out);
}
html.js .journey:not(.journey--horizontal) .jn-node[aria-expanded="false"]::after { transform: rotate(-45deg); }
html.js .journey:not(.journey--horizontal) .jn-node { position: relative; }

/* No-JS fallback road (JS draws the real one): a straight rail. */
html:not(.js) .journey-steps::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--jn-node) / 2 - 1px);
  width: 2px;
  background: var(--border);
}

/* Reveal on the vertical layout: each step rises in as journey.js sees it. */
html.js .journey:not(.journey--horizontal) .jn-step .jn-head,
html.js .journey:not(.journey--horizontal) .jn-step .jn-detail {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 560ms var(--ease-out), transform 560ms var(--ease-out);
}
html.js .journey:not(.journey--horizontal) .jn-step.is-in .jn-head,
html.js .journey:not(.journey--horizontal) .jn-step.is-in .jn-detail {
  opacity: 1;
  transform: none;
}

/* --- DESKTOP: horizontal road (≥900px, JS present) ---------------------- */
@media (min-width: 900px) {
  html.js .journey-hint { display: block; }

  html.js .journey--horizontal {
    --jn-node: 64px;
    --jn-wave: 64px;                /* vertical offset of the even steps */
    padding-top: 8px;
  }
  html.js .journey--horizontal .journey-steps {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    grid-template-rows: auto auto;
    column-gap: 20px;
    row-gap: 32px;
  }
  html.js .journey--horizontal .jn-step {
    display: contents;             /* head → row 1, detail → row 2 */
  }
  html.js .journey--horizontal .jn-head {
    grid-row: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 0;
    padding-top: var(--jn-y, 0px);
    opacity: 0;
    transform: translateY(10px);
  }
  html.js .journey--horizontal .jn-step:nth-child(even) .jn-head { --jn-y: var(--jn-wave); }
  /* Steps pop in as the ribbons reach them; journey.js writes the exact
     --jn-d from the measured path, these are the fallbacks. */
  html.js .journey--horizontal .jn-step:nth-child(1) { --jn-d: 160ms; }
  html.js .journey--horizontal .jn-step:nth-child(2) { --jn-d: 480ms; }
  html.js .journey--horizontal .jn-step:nth-child(3) { --jn-d: 800ms; }
  html.js .journey--horizontal .jn-step:nth-child(4) { --jn-d: 1120ms; }
  html.js .journey--horizontal .jn-step:nth-child(5) { --jn-d: 1440ms; }
  html.js .journey--horizontal.is-in .jn-head {
    animation: rise-in 520ms var(--ease-out) both;
    animation-delay: var(--jn-d, 0ms);
  }
  /* Once the choreography has played (or was skipped: reduced motion, focus,
     arriving from the vertical layout) everything is simply in place. */
  html.js .journey--horizontal.journey--settled .jn-head {
    animation: none;
    opacity: 1;
    transform: none;
  }
  html.js .journey--horizontal .jn-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
    min-height: 0;
    padding: 4px 6px 2px;
    position: static;
  }
  html.js .journey--horizontal .jn-node::after { content: none; }
  html.js .journey--horizontal .jn-step .jn-icon {
    position: static;
    margin-bottom: 14px;
  }
  html.js .journey--horizontal .jn-node .jn-num   { margin: 0; }
  html.js .journey--horizontal .jn-node .jn-label { margin-top: 4px; padding: 0; max-width: 14ch; text-wrap: balance; }
  html.js .journey--horizontal .jn-line { margin-top: 8px; max-width: 26ch; font-size: 14px; }
  /* Even steps' one-liners would push row 1 taller — reserve nothing; the
     row is as tall as the tallest head, which is fine. */

  /* The panel: the active detail spans the whole row under the road. */
  html.js .journey--horizontal .jn-detail {
    grid-row: 2;
    grid-column: 1 / -1;
    margin-top: 8px;
    background: var(--cream);
    border: 1px solid var(--border);
    border-radius: var(--r);
    padding: 22px 26px;
    display: grid;
    grid-template-columns: 150px 1fr;
    column-gap: 28px;
    align-items: start;
    opacity: 0;               /* until the section is revealed */
    transform: none;
  }
  html.js .journey--horizontal.is-in .jn-detail {
    animation: rise-in 360ms var(--ease-out) both;
  }
  /* First appearance waits for the road to reach node 1 and the node to rise
     (journey.js holds .journey--drawing during the choreography); switching
     steps afterwards restarts the animation with no delay via display toggle. */
  html.js .journey--horizontal.journey--drawing .jn-detail { animation-delay: 620ms; }
  html.js .journey--horizontal .jn-detail[hidden] { display: none; }
  html.js .journey--horizontal .jn-detail-label { display: block; padding-top: 4px; }
  html.js .journey--horizontal .jn-detail p { max-width: 68ch; font-size: 15.5px; }
}
/* Between 900px and ~1000px five columns are tight: let the label wrap. */
@media (min-width: 900px) and (max-width: 1023px) {
  html.js .journey--horizontal .jn-node .jn-label { font-size: 16px; }
  html.js .journey--horizontal .jn-line { font-size: 13.5px; }
}


/* ==========================================================================
   16  REDUCED MOTION — the site becomes fully static.
   Every .reveal is shown, every .enter is already in place, hover lifts are
   off, and the arc/× in the handover figure sit in their final state.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  html.js .reveal,
  .enter,
  .hero .h1-line,
  .hero-logo {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
  /* The hero road sits fully drawn and still. */
  .hero-road { animation: none !important; transform: none !important; }
  .hero-road path { animation: none !important; stroke-dashoffset: 0 !important; }
  html.js .flow.reveal .flow-never svg > path { clip-path: none !important; }
  html.js .flow.reveal .flow-never svg .x-mark { opacity: 1 !important; transform: none !important; }
  .btn:hover { transform: none !important; }
  .card:hover { translate: none !important; }
  .site-nav a::after { transition: none; }
  /* The journey: road fully drawn (journey.js also skips the draw-in), every
     step and detail already in place, marker jumps rather than travels. */
  html.js .journey .jn-head,
  html.js .journey .jn-detail {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }
  /* The marker is POSITIONED by its SVG transform attribute — a CSS transform
     reset would pin it to the top-left corner. Only its fade is neutralised. */
  html.js .journey-marker.is-on { opacity: 1 !important; transition: none !important; }
  html.js .journey-ribbon { transition: none !important; }
  .jn-node:hover .jn-icon { translate: none !important; }
}


/* ==========================================================================
   17  PRINT — mostly for the privacy policy and the guide.
   ========================================================================== */
@media print {
  body { background: #FFFFFF; font-size: 11pt; line-height: 1.5; }
  /* Print is a static medium: every reveal/enter block is fully visible,
     whatever the scroll position was when the user pressed Ctrl+P. Without
     this, un-scrolled .reveal blocks (the policy's numbered headings, most of
     the guide) would print as blank space. site.js also reveals everything on
     beforeprint, but the stylesheet must not depend on that. */
  html.js .reveal,
  .enter,
  .hero .h1-line,
  .hero-logo {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }
  .hero-road { display: none !important; }
  html.js .flow.reveal .flow-never svg > path { clip-path: none !important; }
  html.js .flow.reveal .flow-never svg .x-mark { opacity: 1 !important; transform: none !important; }
  /* The journey prints as a plain vertical list with every detail open,
     whichever layout was on screen. */
  html.js .journey .jn-head,
  html.js .journey .jn-detail { opacity: 1 !important; transform: none !important; animation: none !important; }
  .journey .journey-steps,
  .journey .jn-step,
  .journey .jn-head,
  .journey .jn-detail { display: block !important; padding-top: 0 !important; }
  .journey .jn-detail { margin-top: 8px !important; border: 0 !important; background: #FFFFFF !important; padding: 0 !important; }
  .journey-path { display: none !important; }
  .journey-hint { display: none !important; }
  .site-header,
  .site-footer,
  .skip-link,
  .btn-row,
  .btn,
  .toc { display: none !important; }
  .sheet {
    max-width: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: #FFFFFF;
    border-radius: 0;
  }
  .container,
  .container--narrow { padding-inline: 0; max-width: none; }
  .prose h2 { page-break-after: avoid; }
  .prose p,
  .prose li,
  .faq-item { page-break-inside: avoid; }
  .prose a { color: var(--ink); text-decoration: underline; }
  .callout,
  .note { border: 1px solid var(--border); background: #FFFFFF; }
}
