/* stephenkelman.co.uk — replica stylesheet
   Type scale + palette are the measured values from docs/design-reference/.

   SPACING SOURCE (2026-08-05): the live site's own compiled theme stylesheet is
   cached at docs/design-reference/theme.css. Every metric it declares is copied
   verbatim below — including its two real breakpoints, **932px and 540px**
   (the earlier 1000/768/520 were guesses). Anything Adobe applied at RUNTIME
   via JS — project/about module rhythm, page-header offsets, per-page
   backgrounds — is NOT in that file and stays at its live-measured value. */

:root {
  --font-sans: "neue-haas-unica", "Helvetica Neue", Arial, sans-serif;
  --font-serif: "le-monde-livre-std", Georgia, "Times New Roman", serif;

  --c-text: #282828;      /* primary */
  --c-secondary: #636569; /* h1, body, project titles */
  --c-muted: #939393;     /* nav */
  --c-faint: #b9b9b9;     /* footer */
  --c-accent: #ef4023;    /* CTA outline/text */
  --c-bg: #ffffff;

  /* body line-height — the unit for "a line of space" (matches the blank-line
     gaps authored inside the text modules themselves) */
  --line: 24px;
  /* air above AND below the project-page title; halves at the 932 breakpoint */
  --title-gap: 48px;

  --maxw-page: 1400px;
  --maxw-project: 1200px;
  --maxw-narrow: 700px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* No letter-spacing anywhere (live uses none) */
body, h1, h2, h3, p, a, nav, .btn, .masthead-title, .project-title { letter-spacing: normal; }
body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 24px;
  color: var(--c-secondary);
  background: var(--c-bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }

.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: -9999px; top: 0; background: #fff;
  padding: 8px 16px; z-index: 100;
}
.skip-link:focus { left: 8px; top: 8px; }

/* ---------- Header ---------- */
.site-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  /* live: height 60, padding 0 3% (the old 38px was 3% frozen at a 1280 window) */
  height: 60px;
  padding: 0 3%;
  position: sticky;
  top: 0;
  z-index: 50;
  background: #fff;   /* header stays white even on dark pages (live) */
}
.primary-nav { justify-self: start; }
.nav-menu {
  /* live: nav items carry padding-right 20px (last-child 0) */
  display: flex; gap: 20px; list-style: none; margin: 0; padding: 0;
}
.nav-menu a {
  font-size: 15px; line-height: 24px; color: var(--c-muted);
}
.nav-menu a:hover, .nav-menu a[aria-current="page"] { color: var(--c-text); }

.logo { justify-self: center; }
/* Live: .logo-image { max-width: 50px } with the image scaling to it —
   at the logo's intrinsic 104x87 that renders 50x41.8 (was 52x44 here). */
.logo img { width: 50px; height: auto; }

.social {
  justify-self: end;
  /* Live: 25px icons on a 35px pitch → 10px gap (measured 1280). */
  display: flex; gap: 10px; list-style: none; margin: 0; padding: 0;
  align-items: center;
}
.social a { color: var(--c-muted); display: inline-flex; }
.social a:hover { color: var(--c-text); }
.social svg { width: 25px; height: 25px; fill: currentColor; }

.nav-toggle { display: none; }

/* ---------- Dark pages ----------
   Live keeps the WHITE header + grey nav on dark pages (only the content area
   below goes black). So the header is unchanged; just the project title on the
   black content lifts to #bfbfbf. */
.theme-dark .project-title { color: #bfbfbf; }

/* ---------- Masthead (home) ---------- */
/* live .masthead: padding 95px 0% 72px. Our header is in flow (sticky) and
   takes the first 60px, so the top padding is 95-60=35. .masthead-contents caps
   the whole stack at 650px — applied here per child. */
.masthead {
  text-align: center;
  margin: 0 auto;
  padding: 35px 0 72px;
}
.masthead-title {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: 31px;
  line-height: 42px;
  color: var(--c-secondary);
  margin: 0 auto;
  max-width: 650px;
}
.masthead-sub {
  font-size: 15px; line-height: 24px; color: var(--c-secondary);
  margin: 25px auto 5px;   /* live .masthead p: margin 25px top / 5px bottom */
  max-width: 650px;
}
.masthead-buttons {
  /* live: .masthead-buttons padding-top 27px; buttons 17px apart. PADDING, not
     margin — a top margin would collapse with the subtitle's 5px bottom margin
     and swallow it, putting the buttons 5px high. */
  display: flex; justify-content: center; gap: 17px; margin: 0 auto;
  padding-top: 27px; max-width: 650px; flex-wrap: wrap;
}
.btn {
  /* live .masthead-button-1/2: height 40, padding 0 15px, 1px border.
     Live sets line-height:40 to centre the label, but that centres the EM box —
     and Unica's ascent (16px at 15px type) is far taller than its cap height
     (10.7px), so the letterforms land ~1.7px low. Centring with flex and
     trimming 2px off the bottom puts the cap block on the optical centre
     instead. Height and padding are unchanged, so the pill matches live. */
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; height: 40px; line-height: 1;
  color: var(--c-accent);
  border: 1px solid var(--c-accent);
  border-radius: 20px;
  padding: 0 15px 2px;
  transition: background .15s ease, color .15s ease;
}
.btn:hover { background: var(--c-accent); color: #fff; }

/* ---------- Project grid (home) ---------- */
/* Live structure (measured 2026-08-04 at 1280/768/375): the cell is 400×315 and
   the cover sits inside it with `margin: 10px 5px 0`. That margin — not a grid
   gap — is what separates the thumbnails, so the grid itself stays gap-0 and
   unpadded, and the 5px sides inset the outer edges of the grid too. */
.project-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;                 /* gutters come from the cover margins, as on live */
  max-width: 1200px;      /* 3 × 400px, centred (live) */
  margin: 0 auto;
  padding: 0;             /* live .project-covers: padding 0 at every width —
                             the footer's 60px top padding is the only gap */
  --cover-inset-y: 10px;  /* live cover margin-top */
  --cover-inset-x: 5px;   /* live cover margin left/right */
}
.grid-item { position: relative; display: block; background: transparent; }
/* The cover's own aspect ratio drives the cell height at every width (live's
   in-flow <img> mechanism): cell = cover height + top margin. 409/320 is the
   covers' intrinsic ratio; yields live's measured cells at 1280/768/375
   (400x315 / 384x301 / 375x300). */
.grid-cover {
  display: block; overflow: hidden;
  margin: var(--cover-inset-y) var(--cover-inset-x) 0;
  aspect-ratio: 409 / 320;
}
.grid-cover picture { display: block; width: 100%; height: 100%; }
.grid-item img { width: 100%; height: 100%; object-fit: cover; }
/* Rollover (matches live): a near-white panel fades in over the cover, inset
   0.65em from the cover on ALL FOUR sides — bottom included — with the project
   title centred in #565656 and padded 15% each side. */
.grid-title {
  position: absolute;
  top: calc(var(--cover-inset-y) + .65em); bottom: .65em;
  left: calc(var(--cover-inset-x) + .65em); right: calc(var(--cover-inset-x) + .65em);
  display: flex; align-items: center; justify-content: center;
  /* Live pads the title 15% of the PANEL width; a bare 15% here would resolve
     against the cell instead, so subtract the panel's own insets first. */
  padding: 0 calc(.15 * (100% - 2 * (var(--cover-inset-x) + .65em)));
  text-align: center;
  font-size: 15px; line-height: 24px; color: #565656;
  background: rgba(255, 255, 255, .97);
  opacity: 0; transition: opacity .15s ease;
  /* Widow control: balance evens the whole rag on this short centred text
     (and prevents a lone last word). Unsupported browsers keep old wrapping. */
  text-wrap: balance;
}
.grid-item:hover .grid-title, .grid-item:focus-visible .grid-title { opacity: 1; }

/* ---------- Project page ---------- */
/* Live (measured at 900/1440): images are FULL-BLEED — no side padding below
   the 1200px cap; each image module carries 14px bottom padding; text modules
   are a 500px box with 45px internal side padding (410px measure); buttons
   pad 14px 45px. Module spacing is per-module, not a flex gap. */
/* The project title sits in equal air: --title-gap above (as the page's own top
   padding, under the 60px header) and the same below (Stephen, 2026-08-05 —
   was 48/20 desktop and 24/14 mobile; equalised to the LARGER of the pair). */
.project { padding: var(--title-gap) 0 64px; }
.project-title {
  text-align: center;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 15px;
  line-height: 24px;
  color: var(--c-secondary);
  margin: 0 0 var(--title-gap);
}
.project-modules {
  max-width: var(--maxw-project);
  margin: 0 auto;
  display: flex; flex-direction: column; gap: 0;
}
.project-image { margin: 0; padding-bottom: 20px; }
.project-image img { width: 100%; cursor: zoom-in; }

/* Text + button modules (interleaved with images).
   Block is centred (max-width + auto margins); text itself reads left — Adobe's
   align-center centres the block, not the copy. */
/* Every module carries its own TRAILING space — that's what replaced the flex
   gap. Text modules were missing theirs, so a text block had 20px above it
   (the previous image's) and 0 below, and the next image butted straight up. */
/* One blank line of air above AND below the copy (Stephen, 2026-08-05).
   The space above already carries the preceding module's 20px trailing rhythm,
   so the text only adds its own line on top; below it must supply both. Net
   result is symmetric: 20+24 = 44px each side (14+24 = 38px under 932). */
.project-text { font-size: 15px; line-height: 24px; margin: 0 auto; padding: var(--line) 45px calc(20px + var(--line)); width: 100%; max-width: 500px; text-align: left; }
.project-text a, .about-text a, .contact-intro a { color: inherit; text-decoration: underline; }
/* FAQ block (variant B, Stephen 2026-08-06): a standard text module whose
   questions take one step of emphasis — #282828 on light pages, white on
   dark. Answers inherit the module colour (page text_color on dark). */
/* FAQ top gap = its bottom gap (Stephen 2026-08-06): space from the preceding
   module to "Questions" matches last line -> related band. Band gap totals
   88px desktop / 68px <=932; preceding module's trailing pad is 20/14, so the
   FAQ tops up the difference. */
.project-faq { padding-top: 68px; }
.project-faq .faq-q { color: var(--c-text); margin: var(--line) 0 0; }
.project-faq .faq-q:first-of-type { margin-top: 0; }
.project-faq .faq-a { margin: 0; }
.theme-dark .project-faq .faq-q { color: #ffffff; }
.project-text.align-right { text-align: right; }
.project-text p { margin: 0; }
.project-button { text-align: center; margin: 0; padding: 20px 0; }
.project-embed { margin: 0 auto; padding-bottom: 20px; width: 100%; }
.project-embed .embed-ratio { position: relative; height: 0; }
.project-embed iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
/* Video captions replicate Behance's treatment (the copy source): small,
   grey, centred under the embed. #939393 reads on both white and the dark
   page grounds (same as nav/socials there). */
.embed-caption { margin-top: 10px; font-size: 13px; line-height: 20px; color: var(--c-muted); text-align: center; }

/* ---------- Narrow pages (about / contact) ---------- */
.page-narrow { max-width: 548px; margin: 0 auto; padding: 24px 24px 80px; }
.about-image { margin: 0 0 32px; }
/* Match live rhythm: headings sit TIGHT above their body (big gap above to
   separate sections, none below); paragraphs/links are one blank line (24px)
   apart; consecutive lines within a para (award/education lists) stay tight. */
.about-text p, .about-text h2 { font-size: 15px; line-height: 24px; color: var(--c-secondary); }
/* Unica Medium (500) now in the kit — matches live's subheading weight. */
/* Measured live rhythm (2026-08-04): heading sits 28px below the previous
   block (mt 28 collapses over the para's mb 24); the big "—" section
   separators get 52px above AND below. */
.about-text h2 { font-weight: 500; margin: 28px 0 0; }
/* Services headings carry 2 authored blank lines on live (52px); the CV-tail
   headings carry 1 (28px, the default above). Flag comes from about.json. */
.about-text h2.gap-wide { margin-top: 52px; }
.about-text .about-para { margin: 0 0 24px; }
.about-text h2 + .about-para { margin-top: 0; }   /* body tight under its heading */
.about-text .about-link { margin: 0 0 24px; }
.about-text .about-divider { color: var(--c-muted); margin: 52px 0; }
.contact-intro p { margin: 0 0 20px; }
.contact-heading { font-size: 15px; font-weight: 500; color: var(--c-secondary); margin: 0 0 20px; }

/* ---------- Contact form ---------- */
.contact-form { display: flex; flex-direction: column; gap: 12px; margin-top: 28px; }
.contact-form input, .contact-form textarea {
  font-family: var(--font-sans); font-size: 15px; color: var(--c-text);
  padding: 14px 16px; border: 1px solid #dadada; border-radius: 3px; width: 100%;
}
.contact-form textarea { resize: vertical; }
.contact-form button {
  align-self: flex-start;
  font-family: var(--font-sans); font-size: 15px; color: var(--c-secondary);
  background: none; border: 1px solid #dadada; border-radius: 3px;   /* match inputs */
  padding: 12px 28px; cursor: pointer;
}
.contact-form button:hover { border-color: var(--c-text); color: var(--c-text); }

/* ---------- 404 ---------- */
.error-page { text-align: center; max-width: 560px; margin: 0 auto; padding: 80px 24px 100px; }
.error-title { font-family: var(--font-serif); font-weight: 300; font-size: 31px; line-height: 42px; color: var(--c-secondary); margin: 0 0 16px; }
.error-page .btn { margin-top: 24px; }

/* ---------- Footer ---------- */
/* live .site-footer: padding 60px 0 (28px at <=932, 25px at <=540) */
.site-footer { text-align: center; padding: 60px 0; }
.site-footer p { font-size: 12px; line-height: 20px; color: var(--c-faint); margin: 0; }

/* ---------- Lightbox ---------- */
.lightbox {
  position: fixed; inset: 0; background: rgba(255,255,255,.97);
  display: none; align-items: center; justify-content: center; z-index: 200;
  cursor: zoom-out;
}
.lightbox.open { display: flex; }
.lightbox img { max-width: 92vw; max-height: 92vh; width: auto; height: auto; }
.lightbox-close {
  position: absolute; top: 20px; right: 24px; font-size: 28px;
  background: none; border: 0; color: var(--c-text); cursor: pointer; line-height: 1;
}

/* ---------- Responsive ----------
   Live has exactly TWO breakpoints, both from theme.css: 932px and 540px. */

@media (max-width: 932px) {
  /* Masthead ladder (live): padding 79px 5% 32px, h1 18/24, p 14/20 with
     15px above and 5px below, buttons 15px under that at 38px tall.
     79px is measured from the page top; our header takes the first 60. */
  .masthead { padding: 19px 5% 32px; }
  .masthead-title { font-size: 18px; line-height: 24px; }
  .masthead-sub { font-size: 14px; line-height: 20px; margin-top: 15px; }
  .masthead-buttons { padding-top: 15px; }
  .btn { font-size: 14px; height: 38px; }   /* line-height stays 1: see .btn */

  /* Project pages drop to live's mobile rhythm: 24px under the header, 14px
     modules, 14/20 title. Measured on live at 800/375 — Adobe applies module
     spacing via JS, so theme.css can't confirm it; the 932 boundary is
     inherited from every rule that IS in the file. */
  /* --title-gap drives both the space above the title and the space below it */
  :root { --title-gap: 24px; }
  .project-title { font-size: 14px; line-height: 20px; }
  .project-image, .project-embed { padding-bottom: 14px; }
  .project-text { padding-bottom: calc(14px + var(--line)); }
  .project-faq { padding-top: 54px; }  /* band gap 68 minus the 14px trailing pad */
  .project-button { padding: 14px 0; }

  /* live .site-footer at <=932: 28px top/bottom, 5% sides */
  .site-footer { padding: 28px 5%; }

  /* Live mobile header: 60px tall, 5% padding, logo CENTRED at 48x40 (live
     caps .logo-image img to max-height 40), hamburger RIGHT, socials hidden. */
  .site-header { grid-template-columns: 1fr auto 1fr; padding: 0 5%; height: 60px; align-items: center; }
  .primary-nav { grid-column: 3; grid-row: 1; justify-self: end; }
  .logo { grid-column: 2; grid-row: 1; }
  .logo img { width: 48px; height: 40px; }
  .social { display: none; }

  /* Live glyphs (measured): burger = three 24x2 bars, 4px gaps, GREY #939393
     (var(--c-muted)); close = two 2x20 strokes at ±45°, same grey, 20x20 box. */
  .nav-toggle {
    display: inline-flex; flex-direction: column; justify-content: center; gap: 4px;
    width: 44px; height: 44px; align-items: center;
    background: none; border: 0; cursor: pointer; padding: 0;
    position: relative; z-index: 210;   /* stays above the open menu overlay */
  }
  .nav-toggle span { display: block; height: 2px; background: var(--c-muted); width: 24px; transition: transform .2s ease, opacity .2s ease, width .2s ease; }
  /* Open state: the bars become live's close button, which is its own fixed
     element at top 40px / right 5% in a 20x20 box (top 30px under 540). */
  .primary-nav.open .nav-toggle {
    position: fixed; top: 40px; right: 5%; width: 20px; height: 20px; gap: 0;
  }
  .primary-nav.open .nav-toggle span { position: absolute; }
  .primary-nav.open .nav-toggle span:nth-child(1) { width: 20px; transform: rotate(45deg); }
  .primary-nav.open .nav-toggle span:nth-child(2) { opacity: 0; }
  .primary-nav.open .nav-toggle span:nth-child(3) { width: 20px; transform: rotate(-45deg); }

  /* Live's open menu is a FULL-SCREEN white overlay: .responsive-nav pads
     50px 5% 0 and centres the link block, which itself carries an 80px bottom
     padding to clear the social bar. Links are 22/32 with 30px under EACH
     (including the last) -> a 62px pitch. */
  .nav-menu {
    display: none; position: fixed; inset: 0;
    flex-direction: column; align-items: center; justify-content: center;
    gap: 0; padding: 50px 5% 80px; background: #fff; z-index: 200;
  }
  .nav-menu li { padding-bottom: 30px; }
  .primary-nav.open .nav-menu { display: flex; }
  /* live's overlay shows no current-page highlight — every link is the same grey */
  .nav-menu a, .nav-menu a[aria-current="page"] { font-size: 22px; line-height: 32px; color: var(--c-muted); }
  .nav-menu a:hover { color: var(--c-text); }

  /* ...with the social icons centred along the bottom, above a hairline. Live:
     min-height 80, padding 5%, 1px rgba(0,0,0,.08) top border, and icons on a
     62px pitch (25px glyph + 20px li padding + the svg's own letterboxing). */
  .primary-nav.open ~ .social {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 205;
    /* the <ul> is a grid item of the header, and its justify-self:end beats the
       left/right:0 stretch — pin the width so the hairline spans edge to edge */
    width: 100%; justify-self: stretch;
    justify-content: center; gap: 37px; padding: 5%; min-height: 80px;
    background: #fff; border-top: 1px solid rgba(0, 0, 0, .08); margin: 0;
  }

  /* Live is full-bleed here (grid padding 0) with a shallower cover margin. */
  .project-grid { grid-template-columns: repeat(2, 1fr); --cover-inset-y: 8px; }
  .grid-title { font-size: 14px; line-height: 20px; }
}

@media (max-width: 540px) {
  /* live at <=540: masthead padding-bottom 28, h1 16/22, p margin-top 14,
     buttons 14px under it, stacked with a 17px spacer, 35px tall. */
  .masthead { padding-bottom: 28px; }
  .masthead-title { font-size: 16px; line-height: 22px; }
  .masthead-sub { margin-top: 14px; }
  .masthead-buttons { padding-top: 14px; flex-direction: column; align-items: center; gap: 17px; }
  .btn { height: 35px; }
  .primary-nav.open .nav-toggle { top: 30px; }
  .site-footer { padding: 25px 5%; }
  /* Single column on live drops the side margins entirely. */
  .project-grid { grid-template-columns: 1fr; --cover-inset-y: 7px; --cover-inset-x: 0px; }
}

/* asset rev: turnstile-invisible-gap
   NB the ?v= cache-buster is an md5 of this file (+ site.js + image-manifest),
   and /assets/* ships `immutable, max-age=31536000` via _headers. So a request
   made in the propagation window right after `wrangler pages deploy` — new HTML
   with the new hash, old asset bytes still at the edge — pins STALE content
   under the NEW url for a year. Wait ~60s after deploying before fetching, or
   the verification request itself poisons the cache. Bump this comment to mint
   a fresh hash if that happens. */

/* Contact form result message (post-submit redirect) */
.contact-result { color: var(--c-secondary); margin: 0 0 24px; }

/* Anti-spam honeypot — off-screen rather than display:none, which the cruder
   bots check for. Never focusable, never announced. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
/* Turnstile widget sits in the form's flex column; the iframe carries its own
   box, so it only needs the column's rhythm. */
.contact-form .cf-turnstile { margin-top: 4px; }

/* In Invisible mode Turnstile renders NO iframe — the container is left at
   zero height but still occupies a flex item, so it ate a gap either side and
   put 28px above Send where every other field gap is 12px. Take it out of
   flow when it has nothing to show. Deliberately position:absolute rather
   than display:none: the element stays rendered and scriptable (Turnstile
   still populates the hidden token input from it) and keeps its static
   position, it just stops consuming a gap. Managed/Non-interactive modes DO
   render an iframe, so they keep their slot and this rule never applies —
   flipping the widget mode back needs no CSS change. Browsers without :has()
   simply keep the old 28px. */
.contact-form .cf-turnstile:not(:has(iframe)) { position: absolute; margin-top: 0; }

/* ---------- Related-projects strip (2026-08-05, prototype for art direction) ----------
   Foot-of-page cross-links on product pages: 3 related grids + the free Colours
   card; top sellers on the colours page and the 404. Reuses the home-grid tile
   classes — the cover-inset vars are zeroed here so the grid gap, not cover
   margins, makes the gutters. */
.related {
  max-width: var(--maxw-project);
  margin: 0 auto;
  padding: 64px 0 0;
}
.related-heading {
  font-size: 15px;
  line-height: 24px;
  font-weight: 400;
  color: var(--c-secondary);
  margin: 0 0 16px;
}
.theme-dark .related-heading { color: #bfbfbf; }
.related-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  --cover-inset-y: 0px;
  --cover-inset-x: 0px;
}
/* Anchor the rollover panel to the COVER, not the grid item: the Colours
   card's tag makes its item the tallest in the row, the others stretch to
   match, and a panel anchored to the item bottom overshoots the cover by
   the tag height. */
.related-grid .grid-cover { position: relative; }
.related-grid .grid-item { position: static; }
.related-grid .grid-title {
  top: .65em; right: .65em; bottom: .65em; left: .65em;
  padding: 0 15%;
}
.related-grid .grid-cover { aspect-ratio: 409 / 320; }
/* The free-download tag under the Colours card */
.tile-tag {
  display: block;
  margin-top: 8px;
  font-size: 13px;
  line-height: 18px;
  color: var(--c-accent);
}
@media (max-width: 932px) {
  .related { padding-top: 40px; }
  /* Heading aligns to the same 45px inset as .project-text (Stephen,
     2026-08-11 — it was flush to the screen edge, out of step with the body
     copy above it). The grid stays its own, slightly shallower inset rather
     than the full 45px, so the covers don't shrink too far in 2 columns. */
  .related-heading { padding: 0 45px; }
  .related-grid { grid-template-columns: repeat(2, 1fr); padding: 0 20px; }
}
/* The band (ported from the prototype): a near-page ground — 5% black on
   light pages, 90% black on dark — running full-bleed from below the last
   module to the base of the page, footer included. The pill sits centred in
   a 64/64 field: above it the image's trailing 20 + the button's 44 top
   padding; below it the button's 20 + the band's 44 margin. */
.has-strip .project { padding-bottom: 0; }
.has-strip .project-modules > .project-button:last-child { padding-top: 44px; }
.has-strip .related {
  margin-top: 44px;
  max-width: none;
  padding: 64px 0;                 /* inner column matches the 1200px module box */
  background: #f2f2f2;
}
.theme-dark.has-strip .related { background: #1a1a1a; }
.has-strip .related-heading,
.has-strip .related-grid {
  max-width: var(--maxw-project);
  margin-left: auto;
  margin-right: auto;
}
/* carry the field through to the page base */
.has-strip .site-footer { background: #f2f2f2; }
.theme-dark.has-strip .site-footer { background: #1a1a1a; }
@media (max-width: 932px) {
  .has-strip .project-modules > .project-button:last-child { padding-top: 30px; }
  .has-strip .related { margin-top: 30px; }
}

/* 404: the strip sits below the narrow error card */
.page-404 .related { padding: 24px 5% 0; }

/* 'Features' promoted to a real h2 — pixel-identical to the body copy it was:
   same size/leading/weight, bottom margin standing in for the authored blank
   line, so the rendered rhythm is unchanged. */
.project-text-heading {
  font-size: inherit;
  line-height: inherit;
  font-weight: inherit;
  /* Module headings take the FAQ-question step of emphasis (Stephen
     2026-08-06): #282828 on light pages, white on dark; body stays grey. */
  color: var(--c-text);
  /* Features sits directly on its bullets (Stephen 2026-08-06); the FAQ
     keeps a line between "Questions" and the first question. */
  margin: 0;
}
.project-faq .project-text-heading { margin-bottom: var(--line); }
.theme-dark .project-text-heading { color: #ffffff; }

/* ---------- Journal (2026-08-06, new section — not part of the replica) ----------
   Composed from existing tokens only: masthead serif for titles, body 15/24,
   the about-page 548px measure, muted grey for dates. */
.journal-index .article-title {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: 31px;
  line-height: 42px;
  color: var(--c-secondary);
  margin: 24px 0 0;
}
/* Article header = the landing entry stack verbatim (measured: 18/26 #282828
   title, date 4px under, standfirst as the 15/24 excerpt 11px under that). */
.journal-article .article-title { font-size: 18px; line-height: 26px; font-weight: 400; color: var(--c-text); margin: 24px 0 0; }
.article-date { font-size: 13px; line-height: 20px; color: var(--c-muted); margin: 4px 0 0; }  /* 4px = the landing entry's measured gap */
.article-body { margin-top: 48px; }
.article-body p { margin: 0 0 var(--line); }
.article-figure { margin: calc(var(--line) * 2) 0; }
/* Quote: flows as a normal line and wraps only when the measure can't
   accommodate it — never hard-broken. Attribution on its own line, same
   face and size (per Stephen's reference asset). */
.article-quote { margin: calc(var(--line) * 2) 0; padding: 0; }
.article-quote p { margin: 0; }
/* Video embed: port of .project-embed, centred in the article measure.
   Its caption keeps the muted video treatment (no .article-figure override). */
.article-embed { margin: calc(var(--line) * 2) auto; }
.article-embed .embed-ratio { position: relative; height: 0; }
.article-embed iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
.article-back { margin-top: 48px; font-size: 13px; }
.article-back a { color: var(--c-muted); }
.article-back a:hover { color: var(--c-text); }

.journal-list { list-style: none; margin: 48px 0 0; padding: 0; }
.journal-entry { margin: 0 0 40px; }
.journal-entry a { display: block; }
.journal-entry-title { display: block; font-size: 18px; line-height: 26px; color: var(--c-text); }
.journal-entry a:hover .journal-entry-title { color: var(--c-accent); }
.journal-entry-excerpt { display: block; margin-top: 8px; font-size: 15px; line-height: 24px; color: var(--c-secondary); }

@media (max-width: 932px) {
  .journal-index .article-title { font-size: 24px; line-height: 32px; }
  .article-body { margin-top: 28px; }
}
.article-standfirst { font-size: 15px; line-height: 24px; font-style: italic; color: var(--c-secondary); margin: 8px 0 0; }
.article-list { list-style: none; margin: 0 0 var(--line); padding: 0; }
/* Flex marker: wrapped lines align exactly with the first word of the item —
   not past it (the old 28px hanging indent) and not under the dash. */
.article-list li { display: flex; gap: 8px; }
.article-list li::before { content: "\2014"; color: var(--c-muted); flex: none; }
/* Ordered variant: numbered steps take a counter in place of the dash,
   same flex alignment so wrapped lines sit on the item's first word. */
.article-list-ordered { counter-reset: step; }
.article-list-ordered li { counter-increment: step; }
.article-list-ordered li::before { content: counter(step); min-width: 14px; }
.article-figure .embed-caption { color: var(--c-secondary); }  /* body colour; video captions keep muted */
.article-figure .embed-caption a { text-decoration: none; }
.article-figure .embed-caption a:hover { color: var(--c-text); }
.article-newsletter { margin-top: 48px; font-size: 15px; line-height: 24px; color: var(--c-secondary); }
.article-newsletter a { text-decoration: underline; white-space: nowrap; }

/* Journal body voice (Stephen 2026-08-06): standfirst + body copy in Le Monde
   Livre Light (kit serves 300/400/700), one size up from the site's 15px on
   the unchanged 24px line, 100% black. Titles/captions stay sans.
   (A Typotheque Lava trial was evaluated and removed pre-ship — licensed
   files would be needed before any Lava returns.) */
.journal-article .article-standfirst,
.journal-article .article-body p,
.journal-article .article-list,
.journal-article .article-quote-cite,
.journal-article .article-newsletter {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: 17px; line-height: 24px;
  color: #000000;
}
