/* ==========================================================================
   Tom Mackellar — Motion Design portfolio
   Stage 05 build · "Title Sequence" direction (design-spec.md, 2026-07-21)
   Tokens are verbatim from stages/04_design/output/design-spec.md §1–§3.
   ========================================================================== */

/* ---------- 1. Colour tokens ---------- */
:root {
  /* paper / surface */
  --color-paper:      #EFEDE6;  /* bone — base */
  --color-paper-2:    #E4E0D6;  /* deeper oat — alternating bands, hairline fields */
  --color-stage:      #141109;  /* warm near-black — the "video stage" + footer + end-card */

  /* ink / text */
  --color-ink:        #17140F;  /* warm near-black espresso — primary text on paper */
  --color-muted:      #655F52;  /* warm grey — secondary text on paper */
  --color-hairline:   #D6D1C4;  /* rules/borders on paper */

  /* on the dark stage */
  --color-on-stage:       #EFEDE6;
  --color-on-stage-muted: #A8A192;
  --color-hairline-stage: rgba(239,237,230,0.16);

  /* accent — flare vermilion, rationed */
  --color-accent:      #E8390B;
  --color-accent-deep: #B72806;
  --color-accent-ink:  #FBF7EF;

  /* translucent scrims/glows — same base hues as above, alpha-only variants,
     named so no rule improvises a literal rgba() (anti-slop §3 / gate 48) */
  --color-scrim-paper: rgba(239, 237, 230, 0.92); /* = --color-paper @ 92%, scrolled nav */
  --color-scrim-stage: rgba(20, 17, 9, 0.88);      /* = --color-stage @ 88%, scrolled nav over stage */
  --color-glow-1:       rgba(226, 162, 75, 0.16);  /* warm amber hero glow, inner */
  --color-glow-2:       rgba(226, 162, 75, 0.07);  /* warm amber hero glow, outer */

  /* ---------- 2. Type tokens — single family, sizing-only ---------- */
  --font-display: "Bricolage Grotesque", "Arial Narrow", system-ui, sans-serif;
  --font-body:    "Bricolage Grotesque", system-ui, sans-serif;
  --font-ui:      "Bricolage Grotesque", system-ui, sans-serif; /* labels/timecode */

  --text-display-xl: clamp(3.25rem, 8.5vw, 8rem);
  --text-display-l:  clamp(2.25rem, 5.5vw, 4.5rem);
  --text-display-m:  clamp(1.75rem, 3.4vw, 2.6rem);
  --text-title:      1.375rem;
  --text-lede:       clamp(1.125rem, 1.6vw, 1.3rem);
  --text-body:       1.0625rem;
  --text-label:      0.8125rem;
  --text-micro:      0.75rem;

  --wt-reg: 400; --wt-med: 500; --wt-semi: 600; --wt-bold: 700; --wt-black: 800;

  /* ---------- 3. Spacing, layout, motion ---------- */
  --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px; --space-5:24px;
  --space-6:32px; --space-7:48px; --space-8:64px; --space-9:96px; --space-10:128px;
  --space-11:160px; --space-12:224px;
  --measure: 60ch;
  --container: 1216px;
  --rail: 88px;
  --ease-out: cubic-bezier(0.22,1,0.36,1);
  --ease-in-out: cubic-bezier(0.65,0,0.35,1);
  --ease-in: cubic-bezier(0.55,0,1,0.45);
  --dur-fast:180ms; --dur-mid:420ms; --dur-slow:640ms;
}

/* ---------- Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { overflow-x: clip; }
/* No smooth scrolling. Measured 2026-07-27 in Chrome with prefers-reduced-motion
   OFF: `scroll-behavior: smooth` silently no-ops here, so EVERY fragment link on
   the site did nothing — including "Skip to content", which is a WCAG 2.4.1
   Level A mechanism that had never worked. window.scrollTo() was dead too;
   behavior:'instant' worked, which is what isolated it. A glide is not worth a
   dead skip link, so the glide goes.
   scroll-margin-top keeps a jump target clear of the 62px fixed header. */
:target { scroll-margin-top: 88px; }
body {
  margin: 0;
  background: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 {
  font-family: var(--font-display);
  font-style: normal;               /* no italic headings, ever */
  overflow-wrap: anywhere;
  min-width: 0;
  margin: 0;
}
p { margin: 0 0 var(--space-4); max-width: var(--measure); }
img, video { max-width: 100%; height: auto; display: block; }
figure { margin: 0; }
a { color: inherit; }

::selection { background: var(--color-accent-deep); color: var(--color-accent-ink); }

/* Focus — instant, flare, never faded (anti-slop §8) */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  transition: none;
}

.skip-link {
  position: absolute;
  left: var(--space-4); top: -100px;
  z-index: 200;
  background: var(--color-ink); color: var(--color-paper);
  padding: var(--space-3) var(--space-5);
  font-family: var(--font-ui); font-size: var(--text-label);
  font-weight: var(--wt-semi); letter-spacing: .09em; text-transform: uppercase;
  text-decoration: none;
}
.skip-link:focus-visible { top: var(--space-4); }

/* ---------- Type utilities ---------- */
.label {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  font-feature-settings: "tnum" 1;
}
.eyebrow, .cut-mark {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  font-feature-settings: "tnum" 1;
  color: var(--color-muted);
  display: block;
  margin-bottom: var(--space-4);
}
.on-stage .eyebrow, .on-stage .cut-mark { color: var(--color-on-stage-muted); }
.cut-mark { color: var(--color-accent-deep); }
.on-stage .cut-mark { color: var(--color-accent); } /* large-ish UI mark, ≥3:1 on stage */

.display-l {
  font-size: var(--text-display-l);
  font-weight: var(--wt-bold);
  line-height: .98;
  letter-spacing: -.02em;
}
.display-m {
  font-size: var(--text-display-m);
  font-weight: var(--wt-bold);
  line-height: 1.02;
}
.title { font-size: var(--text-title); font-weight: var(--wt-semi); line-height: 1.15; }
.lede { font-size: var(--text-lede); line-height: 1.5; }
.muted { color: var(--color-muted); }
.on-stage .muted { color: var(--color-on-stage-muted); }
.micro { font-size: var(--text-micro); font-weight: var(--wt-med); }

/* flare underline — the rationed accent phrase device */
.u-flare {
  text-decoration: underline;
  text-decoration-color: var(--color-accent);
  text-decoration-thickness: 3px;
  text-underline-offset: 6px;
}

/* ---------- Layout ---------- */
.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-5);
}
@media (min-width: 1024px) {
  .container { padding-inline-start: calc(var(--rail) + var(--space-5)); }
}

/* Section archetypes — varied vertical rhythm (anti-slop §11), but the variety is
   now in the RATIO between bands, not in the absolute size of the gaps. The first
   pass ran 224px top and bottom on a statement band, so two stacked sections put
   320px of nothing between one sentence and the next and the site read as mostly
   blank. Fluid now, so the gap shrinks with the viewport instead of being cut off
   at one breakpoint. (Tom, 2026-07-27: "there's too much blank space".) */
.band-statement { padding-block: clamp(48px, 5vw, 80px); }
.band-split     { padding-block: clamp(40px, 4.2vw, 64px); }
.band-index     { padding-block: clamp(40px, 4.2vw, 64px); }
.band-stage     { padding-block: clamp(48px, 4.8vw, 72px); }

/* A section's padding is the gap. Anything that also carries a trailing margin
   adds to it, which is how a 176px join became a 210px one. */
main section .container > *:last-child { margin-bottom: 0; }

/* Where two bands share the same paper background there is no edge to see, so
   their two paddings read as one dead zone of ~190px instead of as a boundary.
   Collapse the join. Bands that change colour keep both, because there the
   padding is doing visible work. */
.band-statement:not(.band-alt):not(.on-stage) + .band-index:not(.band-alt):not(.on-stage),
.band-index:not(.band-alt):not(.on-stage) + .band-index:not(.band-alt):not(.on-stage) {
  padding-top: 0;
}

.on-stage {
  background: var(--color-stage);
  color: var(--color-on-stage);
}
.band-alt { background: var(--color-paper-2); }

/* Asymmetric splits — 7/5 or 8/4, never 3 equal (anti-slop §4) */
.split {
  display: grid;
  gap: var(--space-7) var(--space-5);
  align-items: center;
}
@media (min-width: 768px) {
  .split-75          { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
  .split-75.mirrored { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
  .split-84          { grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); }
  .split-48          { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); }
}
.split .rule + h2 { margin-top: var(--space-5); }
.mb-5 { margin-bottom: var(--space-5); }

hr.rule {
  border: 0;
  border-top: 1px solid var(--color-hairline);
  margin: 0;
}

/* ---------- Header / nav ---------- */
.site-head {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  /* 180ms, not 420 — this is a state change reacting to the header crossing a
     dark band, and at 420 it lagged visibly behind the scroll that caused it. */
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
  border-bottom: 1px solid transparent;
  color: var(--color-ink);
}
.site-head .head-inner {
  max-width: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-5);
}
@media (min-width: 1024px) {
  .site-head .head-inner { padding-inline-start: calc(var(--rail) + var(--space-5)); }
}
.site-head.is-scrolled {
  background: var(--color-scrim-paper);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-bottom-color: var(--color-hairline);
}
.site-head.is-over-stage {
  color: var(--color-on-stage);
}
.site-head.is-over-stage.is-scrolled {
  background: var(--color-scrim-stage);
  border-bottom-color: var(--color-hairline-stage);
}

.wordmark {
  font-family: var(--font-display);
  font-weight: var(--wt-bold);
  font-size: 1.125rem;
  letter-spacing: -.01em;
  text-decoration: none;
  color: inherit;
}
.wordmark .tick { color: var(--color-accent); } /* the wordmark's period — the only wordmark accent */

.site-nav ul {
  list-style: none;
  display: flex;
  gap: var(--space-6);
  margin: 0; padding: 0;
}
.site-nav a {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  text-decoration: none;
  color: inherit;
  transition: color var(--dur-fast) var(--ease-out);
  white-space: nowrap;
}
.site-nav a:hover { color: var(--color-accent-deep); }
.site-head.is-over-stage .site-nav a:hover { color: var(--color-accent); }
.site-nav a[aria-current="page"] {
  text-decoration: underline;
  text-decoration-color: var(--color-accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 6px;
}

.menu-toggle {
  display: none;
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  background: none;
  border: 0;
  color: inherit;
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
}
@media (max-width: 767px) {
  .menu-toggle { display: block; }
  .site-nav {
    position: fixed;
    inset: 0;
    background: var(--color-paper);
    color: var(--color-ink);
    display: none;
    z-index: 90;
    padding: calc(var(--space-9)) var(--space-5) var(--space-5);
  }
  .site-nav.is-open { display: block; }
  .site-nav ul { flex-direction: column; gap: var(--space-5); }
  .site-nav.is-open a {
    font-size: var(--text-display-m);
    font-weight: var(--wt-bold);
    letter-spacing: -.01em;
    text-transform: none;
  }
  .site-nav .menu-timecode {
    display: block;
    margin-top: var(--space-8);
    color: var(--color-muted);
  }
}
@media (min-width: 768px) {
  .site-nav .menu-timecode { display: none; }
}

/* ---------- The Cut — edit-timeline rail (signature move, §4) ---------- */
.rail {
  display: none;
}
@media (min-width: 1024px) {
  .rail {
    display: block;
    position: fixed;
    top: 0; left: 0; bottom: 0;
    width: var(--rail);
    z-index: 60;
    pointer-events: none;
  }
  .rail::before {                    /* the hairline */
    content: "";
    position: absolute;
    top: 0; bottom: 0;
    right: 0;
    width: 1px;
    background: var(--color-hairline);
  }
  .rail .rail-fill {                 /* the flare scroll playhead */
    position: absolute;
    top: 0; bottom: 0;
    right: 0;
    width: 3px;
    background: var(--color-accent);
    transform-origin: top;
    transform: scaleY(0);
  }
  .rail .rail-label {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    writing-mode: vertical-rl;
    font-family: var(--font-ui);
    font-size: var(--text-label);
    font-weight: var(--wt-semi);
    letter-spacing: .09em;
    text-transform: uppercase;
    font-feature-settings: "tnum" 1;
    color: var(--color-muted);
    white-space: nowrap;
  }
  .rail.on-dark .rail-label { color: var(--color-on-stage-muted); }
  .rail.on-dark::before { background: var(--color-hairline-stage); }
}

/* Staged reveals (§5.1) removed 2026-07-28. The page had 51 IntersectionObserver
   containers fading ~179 children up on scroll, staggered to 350ms on top of a
   420ms transition — nearly 800ms for a section to settle, and the single most
   recognisable machine-built tell there is. Content is simply present now. The
   left rail keeps its scroll playhead; that is the only thing that moves. */

/* ---------- Home · Cut 1 — cold open (dark stage + CSS fallback loop) ---------- */
.hero {
  position: relative;
  /* Was a full 100svh with 160px of bottom padding, so the first screen was one
     headline and the work started below the fold. Capped now so the proof band
     underneath is already visible on landing, which is the thing that has to get
     seen. Still asymmetric top/bottom (anti-slop §4), just less of it. */
  min-height: min(78svh, 680px);
  display: flex;
  align-items: flex-end;
  overflow: clip;
  padding-block: var(--space-8) var(--space-9);
}
.hero-backdrop {
  position: absolute;
  inset: 0;
  overflow: clip;
}
.hero-loop-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The real Higgsfield hero loop now plays (asset-manifest asset-01); the CSS
   raking-light stays as a subtle companion layer, reduced, so it reads as one
   coherent atmosphere with the video rather than fighting it. Still the sole
   visual under prefers-reduced-motion (video hidden there, see media query below).
   The 16s `rake` loop it used to breathe on is gone — an infinite background
   animation pulls the eye and never gives it back, and it was competing with the
   actual footage. Static light now; the video is the thing that moves. */
.hero-light {
  position: absolute;
  inset: -20%;
  background:
    radial-gradient(90rem 42rem at 18% 30%, var(--color-glow-1), transparent 62%),
    radial-gradient(70rem 36rem at 78% 68%, var(--color-glow-2), transparent 60%);
  opacity: .45;
}
.hero-grain {
  position: absolute;
  inset: 0;
  opacity: .35;
}
.hero-timecode {
  position: absolute;
  top: calc(var(--space-8) + var(--space-5));
  right: var(--space-6);
  z-index: 5;
  color: var(--color-on-stage-muted);
  margin: 0;
}
.hero-inner {
  position: relative;
  z-index: 2;
  width: 100%;
}
.hero h1 {
  font-size: var(--text-display-l);
  font-weight: var(--wt-black);
  line-height: .92;
  letter-spacing: -.03em;
  max-width: 18ch;                 /* lower-left biased, never centred. 14ch put this
                                      sentence on seven lines and left the right half
                                      of the screen empty; 18ch still reads as a column */
}

/* ---------- Home · Cut 2 — proof cut (dark stage) ---------- */
/* The scroll-scrubbed wipe that used to unveil this band is gone — the band is
   already a hard colour change against the paper above it, which does the same
   job without tying a transform to the scrollbar. */
.proof { position: relative; overflow: clip; }
.proof > .container { position: relative; z-index: 2; }

/* The proof band used to be one ad, and the section below it repeated the same
   ad. Somebody who did not want an ad met two of them and left. Three equal
   cards now, one per discipline: the film plays in the first, the site and the
   identity link into their own pages. Equal width is the point, not a layout
   default — the previous version gave the ad seven columns and the other two
   five between them, which is the thing being fixed. (Tom, 2026-07-27.) */
.proof-grid {
  display: grid;
  gap: var(--space-6);
  margin-top: var(--space-6);
}
@media (min-width: 560px) and (max-width: 899px) {
  .proof-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Four films now, so 2x2 rather than 3+1 stranded on its own row. */
@media (min-width: 900px) {
  .proof-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.proof-tile {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  gap: var(--space-4);
  margin: 0;
  text-decoration: none;
  color: inherit;
  border-top: 2px solid var(--color-accent);
  padding-top: var(--space-4);
}
/* same tile, reused on the Work page so "Work" means all of it and not just films */
.othertiles { display: grid; gap: var(--space-6); margin-top: var(--space-6); }
@media (min-width: 700px) {
  .othertiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.proof-tile-shot {
  overflow: clip;
  background: var(--color-stage);
  aspect-ratio: 16 / 9;
  border: 1px solid var(--color-hairline-stage);
}
.proof-tile-shot img,
.proof-tile-shot video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
}
.proof-tile-shot video { object-position: center; background: var(--color-stage); }
/* the identity tile shows a mark, not a photograph — contain, and give it air */
.proof-tile-shot.is-mark { display: grid; place-items: center; padding: var(--space-5); }
.proof-tile-shot.is-mark img { width: min(100%, 190px); height: auto; object-fit: contain; }
/* flex, not grid, so the "see the …" link can be pushed to the bottom of every
   card — three cards of different copy lengths otherwise end with three links at
   three different heights, which reads as sloppy at a glance */
.proof-tile-body { display: flex; flex-direction: column; gap: var(--space-2); }
.proof-tile .label { color: var(--color-on-stage-muted); }
.proof-tile .title { color: var(--color-on-stage); }
.proof-tile p { margin: 0; color: var(--color-on-stage-muted); max-width: none; }
.proof-tile .proof-tile-go {
  margin-top: auto;
  padding-top: var(--space-4);
  color: inherit;
  font-weight: var(--wt-semi);
  text-decoration: underline;
  text-decoration-color: var(--color-hairline-stage);
  text-decoration-thickness: 2px;
  text-underline-offset: 5px;
  transition: text-decoration-color var(--dur-fast) var(--ease-out);
}
.proof-tile:hover .proof-tile-go,
.proof-tile .proof-tile-go:hover { text-decoration-color: var(--color-accent); }
.proof-tile:hover .proof-tile-shot { border-color: var(--color-accent); }

/* ---------- Buttons & text links ---------- */
.btn {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 1.25rem;               /* large text — accent-ink on flare needs ≥3:1 large */
  font-weight: var(--wt-bold);
  letter-spacing: .01em;
  text-decoration: none;
  background: var(--color-accent);
  color: var(--color-accent-ink);
  padding: var(--space-4) var(--space-7);
  border: 0;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.btn:hover { background: var(--color-accent-deep); }
.btn:active { transform: translateY(1px); }
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: .5;
  cursor: not-allowed;
}
.on-stage .btn:focus-visible { outline-color: var(--color-on-stage); }

.text-link {
  font-weight: var(--wt-semi);
  color: var(--color-ink);
  text-decoration: underline;
  text-decoration-color: var(--color-hairline);
  text-decoration-thickness: 2px;
  text-underline-offset: 5px;
  transition: text-decoration-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.text-link:hover {
  color: var(--color-accent-deep);
  text-decoration-color: var(--color-accent);
}
.on-stage .text-link { color: var(--color-on-stage); }
.on-stage .text-link:hover { color: var(--color-on-stage); text-decoration-color: var(--color-accent); }

/* ---------- Portrait figure — as-is, hairline frame at most (§7) ---------- */
.portrait {
  border: 1px solid var(--color-hairline);
  padding: var(--space-2);
  background: var(--color-paper);
}
.portrait img { width: 100%; height: auto; }
.portrait figcaption {
  padding-top: var(--space-3);
  color: var(--color-muted);
}

/* ---------- Statement bands ---------- */
.statement-body { max-width: var(--measure); }
.statement-narrow {
  text-align: center;
}
.statement-narrow p {
  margin-inline: auto;
  max-width: var(--measure);
}

/* holds-space device: hairline + pulled phrase on the open side */
.holdside {
  border-top: 1px solid var(--color-hairline);
  padding-top: var(--space-5);
}
.on-stage .holdside { border-top-color: var(--color-hairline-stage); }

/* ---------- End-card (Home Cut 7 / Portfolio close) ---------- */
.endcard {
  text-align: center;
  position: relative;
}
.endcard .endcard-inner {
  max-width: 44rem;
  margin-inline: auto;
  display: grid;
  justify-items: center;
  gap: var(--space-6);
}
.endcard h2 {
  font-size: var(--text-display-l);
  font-weight: var(--wt-bold);
  line-height: .98;
  letter-spacing: -.02em;
}
.endcard .micro { color: var(--color-on-stage-muted); max-width: none; }

/* ---------- Portfolio — case-study stage modules ---------- */
.piece {
  position: relative;
  overflow: clip;
}
.piece > .container,
.endcard > .container { position: relative; z-index: 2; }
.piece + .piece { border-top: 1px solid var(--color-hairline-stage); }
.piece-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3) var(--space-6);
  margin-bottom: var(--space-6);
}
.piece-head h2 {
  font-size: var(--text-display-m);
  font-weight: var(--wt-bold);
  line-height: 1.02;
}
.spec-line {
  color: var(--color-on-stage-muted);
  font-feature-settings: "tnum" 1;
}
.piece-media { margin-block: var(--space-6); position: relative; }
.piece-media video {
  width: 100%;
  height: auto;
  background: var(--color-stage);
}
/* Sound control — only rendered on pieces that actually carry audio.
   Sits on the film, bottom-left, so the click target and the thing it
   affects are the same object. The video itself is also clickable. */
.piece-media:has(video[data-sound-available]) video { cursor: pointer; }
.sound-toggle {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-4);
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  padding: 0.5em 0.9em;
  font: inherit;
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-on-stage);
  background: rgb(0 0 0 / 0.55);
  border: 1px solid rgb(255 255 255 / 0.25);
  border-radius: 2px;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: background 160ms ease, border-color 160ms ease;
}
.sound-toggle:hover { background: rgb(0 0 0 / 0.72); border-color: rgb(255 255 255 / 0.45); }
.sound-toggle:focus-visible { outline: 2px solid var(--color-on-stage); outline-offset: 2px; }
/* Two bars = muted, three = playing. Cheap, no icon font, reads at a glance. */
.sound-toggle-icon {
  width: 0.85em;
  height: 0.85em;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: skewX(-12deg);
  opacity: 0.65;
}
.sound-toggle[aria-pressed="true"] .sound-toggle-icon { opacity: 1; border-color: var(--color-accent); }
@media (prefers-reduced-motion: reduce) {
  .sound-toggle { transition: none; }
}
.howmade {
  display: grid;
  gap: var(--space-6);
  border-top: 1px solid var(--color-hairline-stage);
  padding-top: var(--space-6);
}
@media (min-width: 768px) {
  .howmade { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
}
.howmade h3 { margin-bottom: var(--space-4); }
.stills {
  display: grid;
  gap: var(--space-4);
}
.stills figure { border: 1px solid var(--color-hairline-stage); }
.stills figcaption {
  padding: var(--space-3);
  color: var(--color-on-stage-muted);
  font-size: var(--text-micro);
  font-weight: var(--wt-med);
}

/* ---------- Spec-work notice ----------
   The attribution/disclaimer block that has to sit with any piece using a
   brand that isn't mine. Accent rule on the left so it reads as a standing
   notice rather than more body copy, and it is placed ABOVE the film so it
   cannot be scrolled past. Default styling is for the dark stage; add
   .spec-notice-paper for the light bands. */
.spec-notice {
  margin-block: var(--space-6);
  padding: var(--space-5);
  border: 1px solid var(--color-hairline-stage);
  border-left: 3px solid var(--color-accent);
  background: rgb(0 0 0 / 0.28);
}
.spec-notice .title {
  display: block;
  margin-bottom: var(--space-4);
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: var(--wt-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-on-stage);
}
.spec-notice p {
  color: var(--color-on-stage-muted);
  max-width: var(--measure);
}
.spec-notice p + p { margin-top: var(--space-4); }
.spec-notice-paper {
  border-color: var(--color-hairline);
  border-left-color: var(--color-accent);
  background: transparent;
}
.spec-notice-paper .title { color: var(--color-ink); }
.spec-notice-paper p { color: var(--color-muted); }

/* ---------- FAQ — hairline-ruled Q&A rows (archetype D, always open) ---------- */
.qa { border-top: 1px solid var(--color-hairline); }
.qa .qa-row {
  border-bottom: 1px solid var(--color-hairline);
  padding-block: var(--space-6);
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 768px) {
  .qa .qa-row { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); column-gap: var(--space-6); row-gap: 0; }
  /* Until now every row held exactly one paragraph, so nothing revealed that a
     second one lands in the QUESTION column: grid auto-flow puts it in the next
     free cell, which is column 1 of a new row. Any answer that needs two
     paragraphs stays in column 2. Row gap goes to zero because the paragraphs'
     own margins already space them, and 32px between them read as a break. */
  .qa .qa-row > p ~ p { grid-column: 2; }
}
.qa h3 { font-size: var(--text-title); font-weight: var(--wt-semi); line-height: 1.15; }

/* the one place FAQ raises its voice */
.pricing-pull {
  max-width: none;
}
.pricing-pull .display-m { max-width: 24ch; }

/* ---------- About — background placeholder (honest, labelled) ---------- */

/* a lede sitting directly under a section heading needs the gap the grid bands
   get for free elsewhere (About §3/§4) */
.section-lede { margin-top: var(--space-4); }

/* ---------- About — the eight process stages (numbered rows, on the dark stage) ---------- */
.process {
  border-top: 1px solid var(--color-hairline-stage);
  margin-top: var(--space-7);
}
.process-row {
  border-bottom: 1px solid var(--color-hairline-stage);
  padding-block: var(--space-5);
  display: grid;
  gap: var(--space-2);
}
@media (min-width: 768px) {
  .process-row {
    grid-template-columns: auto minmax(0, 3fr) minmax(0, 8fr);
    gap: var(--space-6);
    align-items: baseline;
  }
}
.process-num {
  color: var(--color-accent);
  /* the shot-number column: fixed width so 01–08 stack dead flat */
  min-width: 2.5ch;
}
.process-row h3 {
  font-size: var(--text-title);
  font-weight: var(--wt-semi);
  line-height: 1.15;
}
.process-row p {
  color: var(--color-on-stage-muted);
  margin-bottom: 0;
}

/* ---------- Capability lists — everything a door actually includes ---------- */
.caps {
  display: grid;
  gap: 0;
  margin-top: var(--space-6);
  border-top: 1px solid var(--color-hairline);
}
@media (min-width: 700px) {
  .caps { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--space-7); }
}
.cap {
  border-bottom: 1px solid var(--color-hairline);
  padding-block: var(--space-4);
}
.cap h3 {
  font-size: var(--text-body);
  font-weight: var(--wt-semi);
  line-height: 1.2;
  margin-bottom: var(--space-2);
}
.cap p { margin-bottom: 0; color: var(--color-muted); max-width: none; }
.on-stage .caps { border-top-color: var(--color-hairline-stage); }
.on-stage .cap { border-bottom-color: var(--color-hairline-stage); }
.on-stage .cap p { color: var(--color-on-stage-muted); }

/* a capability that is real but has no portfolio piece behind it yet */
.cap .cap-note {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--color-accent-deep);
}

/* ---------- Branding page — the identity showcase ---------- */
.markshow {
  display: grid;
  gap: var(--space-5);
  margin-top: var(--space-7);
}
@media (min-width: 700px) {
  .markshow { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); align-items: center; gap: var(--space-7); }
}
.markshow figure {
  border: 1px solid var(--color-hairline);
  background: var(--color-paper-2);
  padding: var(--space-7);
  display: grid;
  place-items: center;
}
.markshow figure img { width: min(100%, 300px); }
.markgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-6);
}
.markgrid figure {
  border: 1px solid var(--color-hairline-stage);
  background: var(--color-stage);
  padding: var(--space-5);
  display: grid;
  place-items: center;
}
.markgrid figure.on-paper { background: var(--color-paper-2); border-color: var(--color-hairline); }
.markgrid img { width: min(100%, 150px); }

/* the colour system, shown rather than described */
.swatches { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-top: var(--space-5); }
.swatch { display: grid; gap: var(--space-2); }
.swatch i {
  display: block;
  width: 68px; height: 44px;
  border: 1px solid var(--color-hairline);
}
.swatch span {
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--wt-semi);
  letter-spacing: .06em;
  color: var(--color-muted);
  font-feature-settings: "tnum" 1;
}

/* ---------- Websites service page — build cards ---------- */
.sitework {
  display: grid;
  gap: var(--space-8);
  margin-top: var(--space-7);
}
.sitecard {
  display: grid;
  gap: var(--space-5);
  border-top: 1px solid var(--color-hairline);
  padding-top: var(--space-6);
}
@media (min-width: 900px) {
  .sitecard { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--space-6); }
}
.sitecard img { border: 1px solid var(--color-hairline); }
/* A mark (transparent-background logo) can't take the same full-bleed
   treatment as a site screenshot — cropped flush to its own canvas, it reads
   as cut off the moment it sits against a hairline border. Frame it instead,
   same device as .markshow on the Branding page. */
.sitecard .mark-frame {
  border: 1px solid var(--color-hairline);
  background: var(--color-paper-2);
  padding: var(--space-7);
  display: grid;
  place-items: center;
}
.sitecard .mark-frame img { width: min(100%, 320px); border: none; }
.sitecard figcaption { display: grid; gap: var(--space-2); align-content: start; }
.sitecard figcaption .spec-line { color: var(--color-muted); }
.sitecard figcaption p { margin-top: var(--space-2); margin-bottom: 0; }

/* ---------- Homepage — the three doors ---------- */
.doors {
  display: grid;
  gap: var(--space-5);
  margin-top: var(--space-7);
}
@media (min-width: 768px) {
  .doors { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-6); }
}
.door {
  border-top: 2px solid var(--color-accent);
  padding-top: var(--space-4);
  display: grid;
  gap: var(--space-3);
  align-content: start;
}
.door h3 { font-size: var(--text-title); font-weight: var(--wt-semi); line-height: 1.15; }
.door p { margin-bottom: 0; max-width: none; }

/* A door that is also a link (the two service doors on Home). Kill the inherited
   link styling so it reads as the same object as the static tiers, and move the
   accent to the top rule on hover so it is obviously clickable without a button. */
a.door {
  color: inherit;
  text-decoration: none;
  border-top-color: var(--color-hairline);
  transition: border-top-color 160ms ease;
}
a.door:hover { border-top-color: var(--color-accent); }
a.door:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 6px; }
a.door .proof-tile-go { margin-top: var(--space-2); }
@media (prefers-reduced-motion: reduce) { a.door { transition: none; } }

/* the recommended tier. Same rule as .kit.is-best: the flare border marks a
   recommendation the reader can check against the rows below it, never a
   popularity claim — there is no client base to count yet, so a "most popular"
   badge here would be a fabrication. */
.door.is-best {
  border-top-width: 6px;
  padding-top: var(--space-4);
}
.door.is-best .kit-tag { margin-bottom: 0; }

/* what each tier includes. Deliberately a plain list under the price rather than
   a comparison grid — a grid of ticks and dashes reads as a SaaS pricing page,
   and the thin tier is meant to look thin, not to look punished. */
.tierlist {
  list-style: none;
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
}
.tierlist li {
  position: relative;
  padding-left: 1.15em;
  font-size: var(--text-micro);
  color: var(--color-muted);
  line-height: 1.45;
}
.tierlist li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .55em;
  width: .45em;
  height: 1px;
  background: var(--color-accent);
}
.door .door-price .per {
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--wt-semi);
  color: var(--color-muted);
  letter-spacing: .04em;
}

/* price on each door — the a-la-carte half. Mixed bundling only reads as a
   discount if the components carry their own numbers next to the bundle. */
.door .door-price {
  font-family: var(--font-ui);
  font-size: var(--text-title);
  font-weight: var(--wt-semi);
  color: var(--color-ink);
  margin-bottom: 0;
}
.door .door-price .from {
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--color-muted);
  margin-right: var(--space-2);
}

/* the bundle panel — the commercial reason the three doors sit together */
.kit {
  border: 1px solid var(--color-hairline);
  background: var(--color-paper-2);
  padding: var(--space-6);
  margin-top: var(--space-8);
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 768px) {
  .kit { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--space-7); align-items: center; }
}
.kit p { margin-bottom: 0; }
.kit .title { margin-bottom: var(--space-3); }
/* flare border marks the recommendation. The claim next to it is arithmetic
   the reader can check against the door prices above, never a popularity claim. */
.kit.is-best { border: 2px solid var(--color-accent); }
.kit-tag {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--color-accent-deep);
  margin-bottom: var(--space-2);
}
.kit-note {
  margin-top: var(--space-4);
  color: var(--color-muted);
  max-width: var(--measure);
}

/* ---------- Scope: what a build is and is not ----------
   Tom, 2026-07-27: the copy never said he builds the front end only, so a client
   could reasonably have read "custom-coded websites" as including a back end.
   Two lists side by side, because a boundary written as prose is a boundary that
   gets skimmed. The "don't" side is as legible as the "do" side: it is the half
   that saves a wasted call. */
.scope {
  display: grid;
  gap: var(--space-6);
  margin-top: var(--space-6);
}
@media (min-width: 768px) {
  .scope { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-7); }
}
.scope-col { display: grid; gap: var(--space-3); align-content: start; padding-top: var(--space-4); }
.scope-col .label { color: var(--color-muted); }
.scope-col .title { margin: 0; }
.scope-col ul { margin: var(--space-2) 0 0; padding-left: 1.15em; display: grid; gap: var(--space-3); }
.scope-col li { max-width: var(--measure); }
/* accent marks what you get; the boundary gets a plain ink rule rather than a
   warning colour, because it is a scope statement and not a problem */
.scope-yes { border-top: 2px solid var(--color-accent); }
.scope-no  { border-top: 2px solid var(--color-ink); }
/* the "don't" list stays full ink deliberately. Muting it passed contrast but
   made the one thing a client most needs to read look like fine print, which is
   the exact failure this section exists to prevent. The rule colour carries the
   distinction instead. */

/* ---------- Price strip — the home page's numbers, on every service page ----------
   Each service page used to carry its own price once, in small print, in the last
   paragraph of the page. So the answer to "what does this cost" was a thousand
   words down, and the answer to "what would the other two cost" was on another
   page entirely. Same four numbers as the home page, near the top, with the
   current page marked and the other three as links. (Tom, 2026-07-27.) */
.pricestrip {
  display: grid;
  gap: 1px;
  background: var(--color-hairline);
  border: 1px solid var(--color-hairline);
  margin-top: var(--space-6);
}
@media (min-width: 700px) {
  .pricestrip { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.pricecell {
  display: grid;
  gap: var(--space-2);
  align-content: start;
  background: var(--color-paper);
  padding: var(--space-5) var(--space-4);
  text-decoration: none;
  color: inherit;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.band-alt .pricecell { background: var(--color-paper-2); }
.pricecell:hover { background: var(--color-paper-2); }
.band-alt .pricecell:hover { background: var(--color-paper); }
.pricecell-name {
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.pricecell-num {
  font-size: var(--text-title);
  font-weight: var(--wt-bold);
  line-height: 1.1;
  font-feature-settings: "tnum" 1;
}
.pricecell-num .from {
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--color-muted);
  margin-right: var(--space-2);
}
.pricecell-note { font-size: var(--text-micro); color: var(--color-muted); margin: 0; max-width: none; }
/* the page you are on: marked, and deliberately not a link to itself */
.pricecell.is-current { background: var(--color-stage); color: var(--color-on-stage); cursor: default; }
.pricecell.is-current .pricecell-name,
.pricecell.is-current .pricecell-note,
.pricecell.is-current .pricecell-num .from { color: var(--color-on-stage-muted); }
/* the bundle: flare edge, same claim as the home page, checkable against the three cells beside it */
.pricecell.is-kit { box-shadow: inset 0 3px 0 0 var(--color-accent); }
.pricecell.is-kit .pricecell-note { color: var(--color-accent-deep); font-weight: var(--wt-semi); }
/* 60ch of measure at micro size is a 400px ribbon under a 1216px table */
.pricestrip-note { margin-top: var(--space-4); margin-bottom: 0; color: var(--color-muted); max-width: 84ch; }

/* closing note under the eight stages — the one place About mentions the tools */
.process-note {
  margin-top: var(--space-6);
  margin-bottom: 0;
  color: var(--color-on-stage-muted);
}

/* the FAQ disclosure list reuses the Q&A row grid, but follows a heading + lede */
.disclosure { margin-top: var(--space-7); }
.disclosure .qa-row p:last-child { margin-bottom: 0; }

/* The row that reconciles the two third-party answers. It exists because the two
   answers above it read as a contradiction when they are skimmed, and the person
   skimming is the one deciding whether to email. (Tom, 2026-07-27.) */
.qa-row.is-resolve {
  background: var(--color-paper-2);
  border-left: 3px solid var(--color-accent);
  padding-left: var(--space-5);
}
.band-alt .qa-row.is-resolve { background: var(--color-paper); }

/* index rows (About background) */
.index-row {
  border-top: 1px solid var(--color-hairline);
  padding-block: var(--space-6);
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 768px) {
  .index-row { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); }
}

/* "Get in touch" (About §4 closing prompt) wraps mid-phrase at 320px inside its
   sentence — keep the clickable phrase on one line and let the parent line wrap
   before it instead (anti-slop §9 / gate 49: no two-line clickable text). */
.band-index .lede a.text-link { white-space: nowrap; }

/* ---------- Contact form (anti-slop §8 input rules) ---------- */
.form-grid {
  display: grid;
  gap: var(--space-6);
}
.field { display: grid; gap: var(--space-2); }
.field label {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
}
.field .helper {
  color: var(--color-muted);
  font-size: var(--text-micro);
  font-weight: var(--wt-med);
  min-height: 1.2em;                 /* helper-text space reserved */
  margin: 0;
  max-width: none;
}
.field input,
.field textarea {
  font-family: var(--font-body);
  font-size: var(--text-body);
  color: var(--color-ink);
  background: var(--color-paper);
  border: 1px solid var(--color-hairline);   /* border width never shifts between states */
  padding: var(--space-4);
  height: 3.5rem;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.field textarea { height: auto; min-height: 9rem; resize: vertical; }
.field input:hover, .field textarea:hover { border-color: var(--color-muted); }
.field input:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--color-accent);    /* focus ring via outline, not border */
  outline-offset: 2px;
  border-color: var(--color-hairline);
}
.field input:disabled, .field textarea:disabled {
  opacity: .5;
  cursor: not-allowed;
}
.form-grid .btn { height: 3.5rem; padding-block: 0; line-height: 3.5rem; justify-self: start; }
.form-grid .btn:disabled { opacity: .5; cursor: not-allowed; }
.cf-turnstile-wrap { margin-top: var(--space-2); }
.form-status {
  color: var(--color-accent-deep);
  font-weight: var(--wt-med);
  min-height: 1.5em;
  margin: 0;
}

/* Honeypot — off-screen (not display:none, which some bots skip), never
   reachable by keyboard or a screen reader. Real visitors never see this. */
.hp-field {
  position: absolute;
  left: -9999px;
  top: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.contact-aside {
  border-top: 1px solid var(--color-hairline);
  padding-top: var(--space-6);
  align-self: start;
}

/* ---------- Footer — single compact band (anti-slop §4) ---------- */
.site-foot {
  background: var(--color-stage);
  color: var(--color-on-stage);
  border-top: 2px solid var(--color-accent);  /* the "final cut" flare hairline */
  padding-block: var(--space-8);
}
.foot-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-6);
}
.foot-inner .foot-id p { margin: 0; max-width: none; }
.foot-inner .foot-id .tagline { color: var(--color-on-stage-muted); }
.foot-nav ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin: 0; padding: 0;
}
.foot-nav a {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--color-on-stage);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.foot-nav a:hover { color: var(--color-accent); }
.foot-contact { margin: 0; max-width: none; }
.foot-contact a { color: var(--color-on-stage); }
.foot-copy { color: var(--color-on-stage-muted); margin: 0; max-width: none; }
/* low-key footer link (Privacy) — same muted register as the copyright line it sits beside */
.site-foot .text-link { color: var(--color-on-stage-muted); text-decoration-color: var(--color-hairline-stage); }
.site-foot .text-link:hover { color: var(--color-accent); text-decoration-color: var(--color-accent); }

/* ---------- Reduced motion (mandatory, §5) ---------- */
/* Much shorter than it was, because most of what it had to switch off no longer
   exists. What remains is the rail playhead and the button press. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero-loop-video { display: none; } /* poster + static CSS glow only, no autoplay */
  .rail .rail-fill { transform: scaleY(1); transition: none; }
  .btn { transition: none; }
  .btn:active { transform: none; }
}

/* ---------- Small-screen sanity ---------- */
@media (max-width: 413px) {
  .hero h1 { font-size: clamp(2.5rem, 12vw, 3.25rem); }
  .btn { font-size: 1.25rem; padding-inline: var(--space-6); } /* stays large-bold: accent-ink on flare needs the large-text 3:1 allowance */
  /* long-label CTA (Home cut 4): at very narrow widths the filled button would
     force its label to two lines (gate: no two-line clickable text) — it steps
     down to the text-link treatment instead, one line, full contrast on paper */
  .btn--long {
    background: none;
    color: var(--color-ink);
    padding: 0;
    font-size: 1.125rem;
    font-weight: var(--wt-semi);
    text-decoration: underline;
    text-decoration-color: var(--color-accent);
    text-decoration-thickness: 3px;
    text-underline-offset: 6px;
  }
  .btn--long:hover { background: none; color: var(--color-accent-deep); }
}

/* ============================================================
   before / after comparison
   Built on a real <input type="range">, deliberately: WCAG 2.2 SC 2.5.7
   requires a single-pointer alternative to any drag, and a native range
   gives keyboard, click-to-set and drag without re-implementing any of it.
   The input covers the whole frame, so a click anywhere on the image moves
   the divider. Research says most visitors never touch a control like this,
   so the 50% default has to read as a finished image on its own.
   ============================================================ */
.compare { margin: var(--space-7) 0 0; }
.compare-frame {
  position: relative;
  overflow: hidden;
  background: var(--color-stage);
  line-height: 0;
}
.compare-frame img { width: 100%; height: auto; display: block; }
/* clipped from the LEFT by --pos, so the redesign occupies the right-hand
   side and the reading order matches the Before / After tags */
.compare-after {
  position: absolute;
  inset: 0;
  clip-path: inset(0 0 0 var(--pos, 50%));
}
.compare-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos, 50%);
  width: 2px;
  margin-left: -1px;
  background: var(--color-accent);
  pointer-events: none;
}
.compare-handle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  border: 2px solid var(--color-accent);
  border-radius: 50%;
  background: var(--color-stage);
}
/* the full-frame range IS the control surface */
.compare-range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  appearance: none;
  -webkit-appearance: none;
  cursor: ew-resize;
}
.compare-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 44px;
  height: 100vh;
  background: transparent;
  cursor: ew-resize;
}
.compare-range::-moz-range-thumb {
  width: 44px;
  height: 100vh;
  background: transparent;
  border: 0;
  cursor: ew-resize;
}
.compare-range:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.compare-tag {
  position: absolute;
  top: var(--space-4);
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--wt-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--color-on-stage);
  background: rgba(20, 17, 9, 0.72);
  padding: var(--space-2) var(--space-3);
  pointer-events: none;
  line-height: 1;
}
.compare-tag--l { left: var(--space-4); }
.compare-tag--r { right: var(--space-4); }
/* On a phone a full 1568x726 screenshot renders about 145px tall and nothing in
   it is legible. Below 700px both layers get the SAME crop — same aspect ratio,
   same object-position — so the comparison stays fair, just closer in. */
@media (max-width: 699px) {
  .compare-frame { aspect-ratio: 3 / 2; }
  .compare-frame img {
    height: 100%;
    object-fit: cover;
    object-position: top left;
  }
  .compare-handle::after { width: 32px; height: 32px; margin: -16px 0 0 -16px; }
}
.compare figcaption {
  margin-top: var(--space-4);
  font-size: var(--text-body);
  color: var(--color-muted);
  max-width: var(--measure);
}
.compare figcaption .title { display: block; color: var(--color-ink); margin-bottom: var(--space-2); }
/* no JS: the range can't move, so show both images in full instead of
   stranding the "after" at half-width */
.no-js .compare-after { position: static; clip-path: none; margin-top: var(--space-3); }
.no-js .compare-handle, .no-js .compare-range, .no-js .compare-tag { display: none; }

/* portrait beside the contact form — a real specific person beats stock
   photography (Nielsen 2010). It is not load-bearing: the controlled studies
   on faces and trust (Cyr et al. 2009, Riegelsberger et al. 2003) found no
   direct effect, so this earns its place by costing nothing. */
.contact-portrait { margin-bottom: var(--space-6); max-width: 220px; }
.contact-portrait img { width: 100%; }
.contact-portrait figcaption { margin-top: var(--space-3); color: var(--color-muted); }

/* Swatch fills live here, not in a style="" attribute: the deployed CSP is
   style-src 'self' with no 'unsafe-inline', so inline styles are DROPPED in
   production and the swatches rendered as four empty boxes. */
.swatch .sw-bone  { background: var(--color-paper); }
.swatch .sw-stage { background: var(--color-stage); }
.swatch .sw-ink   { background: var(--color-ink); }
.swatch .sw-flare { background: var(--color-accent); }
.swatch-note { margin-top: var(--space-4); }

/* Focus ring for the comparison slider.
   The range fills .compare-frame exactly and the frame is overflow:hidden, so
   an outline on the range paints into clipped territory. Put the ring on the
   frame instead, where nothing clips it. */
.compare-frame:has(.compare-range:focus-visible) {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Mobile menu: .site-nav.is-open is position:fixed z-index:90 and paints over
   the in-flow toggle, so tapping "Close" hit the panel instead of the button.
   Nothing could dismiss the menu by touch. */
@media (max-width: 767px) {
  .menu-toggle { position: relative; z-index: 100; }
}

/* No-JS fallback for the header. --color-paper and --color-on-stage are the
   same hex (#EFEDE6), and .is-over-stage is hardcoded in index.html, so with
   JS off the nav text sat on the paper background at 1:1 contrast: invisible. */
.no-js .site-head {
  background: var(--color-scrim-paper);
  border-bottom: 1px solid var(--color-hairline);
}
.no-js .site-head .wordmark,
.no-js .site-head .site-nav a,
.no-js .site-head .menu-toggle { color: var(--color-ink); }

/* Resting border on form fields was #D6D1C4 on #EFEDE6 (~1.3:1), under the
   3:1 WCAG 1.4.11 floor for UI component boundaries. */
.field input, .field textarea { border-color: var(--color-muted); }
