/* N Admin — editorial portfolio
   Values sourced from context/n-admin-design-system.md and
   context/n-admin-interactions-spec.md (Figma file "editorial-portfolio").

   Type scale note: the design system's sizes are specified exact at the
   1440px desktop frame. They're implemented here as clamp() curves whose
   upper bound equals the spec value at 1440px viewport width, scaling down
   fluidly below that — the doc scopes its numbers to that one frame and
   doesn't define smaller breakpoints, so this is the responsive strategy,
   not a reinterpretation of the values themselves. */

@font-face {
  font-family: "Anton";
  src: url("../assets/fonts/Anton-Regular.ttf") format("truetype");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Helvetica Neue";
  src: url("../assets/fonts/HelveticaNeue-Roman.otf") format("opentype");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Helvetica Neue";
  src: url("../assets/fonts/HelveticaNeue-Medium.otf") format("opentype");
  font-weight: 600; /* closest available weight to semibold — no true
    SemiBold file ships with this project, only Light/Roman/Medium/Bold
    (brand-elements/font/Helvetica Neue/); Medium registered at 600 so
    font-weight:600 renders this real face instead of a browser-faked
    bold or silently snapping to Regular/Bold */
  font-display: swap;
}

@font-face {
  font-family: "Helvetica Neue";
  src: url("../assets/fonts/HelveticaNeue-Bold.otf") format("opentype");
  font-weight: 700;
  font-display: swap;
}

@font-face {
  font-family: "Rock Salt";
  src: url("../assets/fonts/RockSalt-Regular.ttf") format("truetype");
  font-weight: 400;
  font-display: swap;
}

:root {
  /* color */
  --bg-paper: #FFFFF5;
  --bg-paper-covered: #E6E6DC; /* card background when ~covered by the next card */
  --ink-primary: #212121;
  --ink-secondary: #6F6D6D;
  --accent-red: #FF0004;
  --pure-black: #000000;
  --pure-white: #FFFFFF;

  /* type families */
  --font-display: "Anton", sans-serif;
  --font-body: "Helvetica Neue", sans-serif;
  --font-hand: "Rock Salt", cursive;

  /* fluid type scale — clamp(min, vw-coefficient, spec-exact-at-1440) */
  --size-nav: clamp(20px, 2.222vw, 32px);
  --size-projects-title: clamp(64px, 26.736vw, 385px);
  --size-project-title: clamp(32px, 4.444vw, 64px);
  --size-hero-headline: clamp(24px, 3.333vw, 48px);
  --size-hero-subhead: clamp(16px, 1.944vw, 28px);
  --size-howwork-item: clamp(26px, 3.333vw, 48px);
  --size-howwork-subtitle: clamp(15px, 1.667vw, 24px);
  --size-project-subtitle: clamp(18px, 2.222vw, 32px);
  --size-project-tagline: clamp(13px, 1.25vw, 18px);
  --size-body: clamp(15px, 1.25vw, 18px);
  --size-kicker: clamp(12px, 1.111vw, 16px);
  /* uncapped (not clamp-ed at 1440px like the rest of the scale) — it's
     positioned directly relative to .cover__wordmark below, which is
     itself uncapped/fills 100% width; capping only this piece would
     break their proportional relationship at wider viewports. */
  --size-rocksalt-intro: max(18px, 2.5vw);
  --size-rocksalt-marginalia: clamp(15px, 1.667vw, 24px);
  --size-rocksalt-pullquote: clamp(18px, 2.222vw, 32px);
  --size-rocksalt-cta: clamp(15px, 1.389vw, 20px);
  /* Standard capped clamp(), like the rest of the type scale — not
     uncapped like the wordmarks. Live-page pixel sweep (not a synthetic
     test — that had a font-loading bug) found the true 2-line-safe
     zone for "Let's catch up over coffee." at Anton within the 1400px
     content width is ~140-240px; 250px itself is *just* past it (3
     lines). 15.278vw lands at 220px at the 1440px reference (safely
     inside that zone) and hits the 250px cap once viewport width
     passes ~1636px — so it reads as exactly 250px on a real ~3360px
     screen, matching the reference, while never re-overflowing to 3
     lines at 1440px. */
  --size-footer-headline: clamp(48px, 15.278vw, 250px);
  --size-footer-signature: clamp(26px, 3.333vw, 48px);
  /* about page (about.html) */
  --size-about-headline: clamp(45px, 5.313vw, 76.5px);
  --size-about-body: clamp(16px, 1.667vw, 24px);
  --size-about-icon: clamp(140px, 17.361vw, 250px);
  /* case study pages (project-*.html) */
  --size-cs-hero-title: clamp(64px, 12.5vw, 180px);
  --size-cs-headline: clamp(32px, 5.556vw, 80px);

  /* timing (interactions-spec.md §7) */
  --ease-snap: cubic-bezier(0.65, 0, 0.35, 1);
  --duration-snap: 700ms;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;
  background: var(--bg-paper);
  color: var(--ink-primary);
}

/* Case study pages (project-*.html) are a normal continuous document
   scroll, not the fixed-viewport card-stack the rest of the site uses —
   this opts a page back into native scrolling instead of the global
   overflow:hidden above, scoped via a class on <html> so index.html/
   about.html's JS-driven transform-based scroll is unaffected. */
html.scroll-page,
html.scroll-page body {
  height: auto;
  overflow: visible;
  overflow-x: hidden;
}

html.scroll-page {
  scroll-behavior: smooth;
}

body {
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

ul { list-style: none; margin: 0; padding: 0; }
a { color: inherit; text-decoration: none; }
p, h1, h2, h3 { margin: 0; }

/* ---------- grain overlay ---------- */
/* Single fixed full-viewport layer, not per-card — cheap because it
   never repaints from the card transitions (it isn't a descendant of
   anything that animates), unlike a filter applied to each animating
   .card would be.
   mix-blend-mode: multiply, not overlay — overlay has essentially no
   visible effect against this site's near-white #FFFFF5 paper
   background (confirmed via side-by-side render comparison); multiply
   at the same opacity is the closest visible equivalent. */
.grain-overlay {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.5;
  mix-blend-mode: multiply;
  pointer-events: none;
  z-index: 9999;
}

/* ---------- nav ---------- */
.site-nav {
  position: fixed;
  top: 22px; /* design-system.md: top: calc(50% - 490px) on the 1024px-tall
                reference frame resolves to 22px from the section top; a
                fixed px offset holds true across viewport heights, sticky
                nav per interactions-spec.md §1 recommendation */
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  font-family: var(--font-display);
  font-size: var(--size-nav);
  text-transform: uppercase;
}

.site-nav__links {
  display: flex;
  gap: 40px;
}

.site-nav__links a {
  transition: color 200ms ease-out;
}

.site-nav__links a:hover {
  color: var(--accent-red);
}

/* case study pages (project-*.html) have no nav bar — just a back link
   top-left, same fixed position/type treatment as .site-nav so it reads
   as the same UI language without implying the other nav links exist */
.cs-back {
  position: fixed;
  top: 22px;
  left: 0;
  z-index: 20;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0 20px;
  font-family: var(--font-display);
  font-size: var(--size-nav);
  text-transform: uppercase;
  transition: color 200ms ease-out;
}

.cs-back:hover { color: var(--accent-red); }

body.is-inverted .cs-back { color: var(--pure-white); }

/* nav is a single global fixed element shared by every section (not
   duplicated per-card) — themed via a body-level class rather than a
   separate hardcoded copy, toggled in main.js whenever the footer
   section becomes active. accent-red hover already reads fine against
   both backgrounds, so only the resting color needs an override. */
body.is-inverted .site-nav {
  color: var(--pure-white);
}

/* ---------- stacked-card scaffold ----------
   Discrete, gesture-triggered stacking: each wheel/touch/key gesture
   advances or retreats exactly one step (js/main.js), toggling
   .is-active / .is-covered classes that a plain timed CSS transition
   animates — not scroll-position-linked. Cards are position:fixed and
   stack purely by z-index (index order); a card slides up from
   translateY(100%) to its resting place when .is-active is added, and
   stays there — earlier cards never move again, only darkening via
   .is-covered as the next one settles over them. */
.card {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100vh;
  overflow: hidden;
  display: flex;
  align-items: center;
  background-color: var(--bg-paper);
  border-top: 1px solid rgba(33, 33, 33, 0.08); /* ink-primary, low opacity —
    the only depth cue between a card and the one it's covering, per
    design-system.md's flat-color-fields-only, no-shadow principle */
  will-change: transform;
  /* scaleY only, not scale() — uniform XY scaling makes the box's width
     visibly grow as it settles (edges sweeping outward from center),
     which reads as sliding in from the sides. scaleY keeps the "un-
     crumple" effect purely vertical, so the motion is bottom-up only. */
  transform: translateY(100%) scaleY(0.94);
  transition: transform var(--duration-snap) var(--ease-snap),
              background-color var(--duration-snap) var(--ease-snap);
}

.card.is-active {
  transform: translateY(0) scaleY(1);
}

.card.is-covered {
  background-color: var(--bg-paper-covered);
}

.card[data-fixed] {
  border-top: none;
}

/* reduced motion: no slide/scale at all, plain opacity cross-fade —
   interactions-spec.md §2's fallback ("instant jump plus a 200ms
   opacity cross-fade"), duration collapsed by the media query below */
.reduced-motion .card {
  transform: none;
  opacity: 0;
  transition: opacity 200ms ease, background-color 200ms ease;
}

.reduced-motion .card.is-active {
  opacity: 1;
}

.section__inner {
  position: relative;
  z-index: 1;
  width: 100%;
}

/* ---------- marginalia ---------- */
.marginalia {
  position: absolute;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 260px;
  opacity: 1;
  pointer-events: auto;
  transition: opacity 200ms ease;
}

/* how-work marginalia units are step-scoped; default hidden, JS toggles
   .is-visible per active step (interactions-spec.md §3 point 4) */
.section--how-work .marginalia {
  opacity: 0;
  pointer-events: none;
}

.section--how-work .marginalia.is-visible {
  opacity: 1;
  pointer-events: auto;
}

.marginalia--top-left { top: 130px; left: 40px; }
.marginalia--bottom-right { bottom: 60px; right: 60px; }
.marginalia--top-right { top: 130px; right: 60px; }
.marginalia--bottom-left { bottom: 60px; left: 40px; }

.marginalia__icon {
  width: clamp(120px, 14vw, 220px);
  height: clamp(120px, 14vw, 220px);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

/* hand-illustrated marginalia icons (brand-elements/how-i-work-icons) —
   already accent-red, replacing the earlier inline Material Symbols set */
.marginalia__icon[data-icon="strategy"] { background-image: url("../assets/icons/strategy.png"); }
.marginalia__icon[data-icon="chess_pawn"] { background-image: url("../assets/icons/chess_pawn.png"); }
.marginalia__icon[data-icon="box"] { background-image: url("../assets/icons/box.png"); }
.marginalia__icon[data-icon="alt_route"] { background-image: url("../assets/icons/alt_route.png"); }
.marginalia__icon[data-icon="palette"] { background-image: url("../assets/icons/palette.png"); }
.marginalia__icon[data-icon="draw_collage"] { background-image: url("../assets/icons/draw_collage.png"); }
.marginalia__icon[data-icon="psychology_alt"] { background-image: url("../assets/icons/psychology_alt.png"); }
.marginalia__icon[data-icon="graph_3"] { background-image: url("../assets/icons/graph_3.png"); }

.marginalia__caption {
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-marginalia);
  color: var(--accent-red);
  line-height: 1.5;
}

/* ---------- cover ---------- */
.cover__intro {
  position: absolute;
  /* bottom is set by js/main.js (positionCoverIntro), not a fixed vw
     formula here — a hand-typed CSS calc() couldn't reliably hold a
     true ~6px ink-to-ink gap across aspect ratios (Anton and Rock
     Salt's advance-width-vs-ink offsets don't cancel out as cleanly
     as expected from layout-box math alone). JS measures the
     wordmark's actual rendered position at runtime instead. This is
     just a safe fallback in case JS hasn't run yet. */
  bottom: 28%;
  left: 33.75vw; /* horizontal center of the "AD" accent (measured
    consistently across viewports) */
  transform: translateX(-50%);
  z-index: 1; /* stays readable over the wordmark, which can now grow
    tall enough on wide/short viewports to reach up to this height */
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-intro);
  color: var(--accent-red);
  opacity: 0;
  white-space: nowrap;
}

/* Frame carries the edge insets — equal on left/right/bottom, so the
   wordmark sits in a symmetric gutter instead of left:4px against a
   bottom that was a totally different (4%, ~41px) value. It's a
   container-query context so .cover__wordmark's font-size (below) can
   be sized directly off the frame's own width rather than raw
   viewport width, guaranteeing it fills exactly edge-to-edge inside
   these insets on any viewport. */
.cover__wordmark-frame {
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: 20px;
  z-index: 0;
  container-type: inline-size;
}

.cover__wordmark {
  font-family: var(--font-display);
  /* Calibrated against actual rendered pixels, not layout APIs — the
     browser's text-layout box (what getBoundingClientRect/Range report)
     includes each glyph's advance-width side-bearing, which for Anton
     at these extreme sizes is tens of pixels of real, visible gap that
     never shows up in DOM measurements. Pixel-scanned true ink-width /
     font-size ratio is 3.101 (not 3.187, the advance-width-based
     figure), so 100/3.101 fills the frame with actual visible ink. */
  font-size: 32.248cqw;
  /* Cancels the first character's left-side-bearing (~2.3% of font-size,
     also pixel-measured) so the ink itself starts flush at the frame's
     left edge, not just the invisible layout box. */
  margin-left: -0.023em;
  line-height: 1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  /* translateY cancels the font's descent metrics reserving space below
     the baseline (pixel-measured ~6.6% of font-size) so the glyphs'
     visual bottom — not just the line box — sits flush at the frame's
     bottom edge. Held constant through the entrance animation below;
     only scale animates there. */
  transform: translateY(0.066em) scale(0.97);
}

.cover__wordmark .accent { color: var(--accent-red); }

.cover__copy {
  position: absolute;
  top: 12%;
  left: 20px; /* moved from the right side, per direct request */
  z-index: 1; /* stays readable over the wordmark for the same reason */
  width: 820px; /* widened + hard-broken subhead (index.html) forces
    exactly 2 lines instead of leaving wrap count to viewport width */
  max-width: 60vw;
  text-align: left;
  opacity: 0;
  transform: translateY(16px);
}

.cover__headline {
  font-family: var(--font-body);
  font-weight: 600; /* semibold, via the Medium face registered at 600 —
    matches .projects-intro__headline's fixed size/weight, per direct
    request, decoupled from the shared --size-hero-headline scale */
  font-size: 34px;
  text-transform: uppercase;
  margin-bottom: 18px;
}

.cover__subhead {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 26px; /* matches .projects-intro__subhead's fixed size,
    decoupled from the shared --size-hero-subhead scale */
  text-transform: uppercase;
  color: var(--ink-primary);
  line-height: 1.4;
}

/* cover entrance choreography — interactions-spec.md §4 */
.section--cover.is-entering .site-nav { animation: fade-in 200ms ease-out forwards; }
.section--cover.is-entering .cover__copy { animation: rise-in 300ms ease-out forwards; }
.section--cover.is-entering .cover__intro { animation: fade-in 250ms ease-out 150ms forwards; }
.section--cover.is-entering .cover__wordmark { animation: wordmark-in 400ms ease-out 250ms forwards; }
.section--cover.is-entering .cover__wordmark .accent { animation: accent-settle 100ms ease-out 550ms forwards; }

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes rise-in { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
@keyframes wordmark-in { from { opacity: 0; transform: translateY(0.066em) scale(0.97); } to { opacity: 1; transform: translateY(0.066em) scale(1); } }
@keyframes accent-settle { from { color: var(--ink-primary); } to { color: var(--accent-red); } }

/* ---------- how-i-work ---------- */
.how-work__inner {
  position: absolute;
  left: calc(25% + 15px);
  top: 50%;
  transform: translateY(-50%);
  width: 690px;
  max-width: 60vw;
}

.how-work__kicker {
  font-family: var(--font-body);
  font-size: var(--size-kicker);
  text-transform: uppercase;
  margin-bottom: 28px;
}

.how-work__item {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--size-howwork-item);
  text-transform: uppercase;
  color: var(--ink-primary);
  transition: color 300ms ease-out;
  padding: 4px 0;
  cursor: pointer;
}

.how-work__item.is-active { color: var(--accent-red); }

.how-work__item:focus-visible {
  outline: 2px solid var(--accent-red);
  outline-offset: 4px;
}

.how-work__subtitle {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--size-howwork-subtitle);
  text-transform: none;
  color: var(--accent-red);
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: opacity 200ms ease-out, max-height 300ms ease-out;
}

.how-work__item.is-active .how-work__subtitle {
  max-height: 60px;
  opacity: 1;
  transition: opacity 200ms ease-out 150ms, max-height 300ms ease-out;
}

/* ---------- projects intro ---------- */
.projects-intro__copy {
  position: absolute;
  top: 10%;
  left: 20px;
  width: 1160px; /* wide enough to hold the headline/subhead to 2 lines
    each at the larger hero-matched font size below */
  max-width: 86vw;
  text-align: left;
}

.projects-intro__headline {
  font-family: var(--font-body);
  font-weight: 600; /* semibold, via the Medium face registered at 600 */
  font-size: 34px; /* reduced from Cover's shared --size-hero-headline
    per direct request — its own fixed size now, decoupled from Cover's */
  text-transform: uppercase;
  line-height: 1.25;
  margin-bottom: 14px;
}

.projects-intro__subhead {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 26px; /* reduced from Cover's shared --size-hero-subhead
    per direct request — its own fixed size now, decoupled from Cover's */
  text-transform: uppercase;
  color: var(--ink-primary);
  line-height: 1.5;
}

.projects-intro__annotation {
  position: absolute;
  left: 40vw; /* centered above "PROJ", matching the reference —
    vertical position handled via JS (positionAboveWordmark) for a
    tight, accurate ink-to-ink gap, same technique as .cover__intro */
  transform: translateX(-50%);
  z-index: 1;
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-intro);
  color: var(--accent-red);
  white-space: nowrap;
}

.projects-intro__wordmark-frame {
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: 20px;
  z-index: 0;
  container-type: inline-size;
}

.projects-intro__title {
  font-family: var(--font-display);
  /* Same full-bleed-fill technique as .cover__wordmark — see its
     comment for why cqw + a pixel-scanned ink-width ratio is used
     instead of a fixed clamp() value. Pixel-scanned for "PROJECTS":
     true ink-width/font-size ratio 3.563, so 100/3.563 fills the frame. */
  font-size: 27.65cqw; /* trimmed from the raw 28.064 pixel-scan figure
    — that measurement was taken at 1440px and had just enough residual
    error to fully eat the right-edge margin at larger viewports.
    Verified via pixel scan at 1440px and 3346px wide after tuning. */
  margin-left: -0.0233em; /* cancels "P"'s left-side-bearing, pixel-measured */
  transform: translateY(0.0621em); /* cancels descent-metric bottom-bearing
    (pixel-measured), trimmed from 0.0722 — that figure landed the ink
    exactly on the last pixel row (0px margin) rather than the intended
    ~4px, confirmed via pixel scan at 1440px and 3360px */
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ---------- project case study ---------- */
/* Fixed card size, no internal scroll — same overflow:hidden/
   align-items:center as every other .card, content clips rather than
   scrolling if it's taller than the viewport. */

.section--project .section__inner {
  margin-left: calc(12.5% + 17px);
  /* width computed to force the right gutter to exactly equal the left
    one (calc(12.5% + 17px) on both sides), rather than the previous
    fixed vw + margin-right combo, which left a much wider gap on the
    left than the right */
  width: calc(100% - 2 * (12.5% + 17px));
}

.project__title {
  font-family: var(--font-display);
  font-size: var(--size-project-title);
  text-transform: uppercase;
  line-height: 1.05;
}

.project__subtitle {
  font-family: var(--font-body);
  font-size: var(--size-project-subtitle);
  text-transform: uppercase;
  color: var(--ink-secondary);
  margin-top: 15px;
}

.project__tagline {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--size-project-tagline);
  text-transform: uppercase;
  color: var(--ink-secondary);
  margin-top: 15px;
}

.project__body {
  margin-top: 40px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.project__body p {
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.5;
}

.project__demonstrates {
  margin-top: 40px;
}

.project__demonstrates-label {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--size-body);
  text-transform: uppercase;
}

.project__demonstrates-answer {
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.5;
  margin-top: 10px;
}

.project__pullquote {
  margin-top: 40px;
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-pullquote);
  color: var(--accent-red);
  line-height: 1.25;
  text-align: left;
}

.project__cta {
  position: relative;
  display: inline-block;
  margin-top: 40px;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.project__cta-link {
  display: block;
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-cta);
  color: var(--accent-red);
}

.project__cta-underline {
  display: block;
  margin-top: 0; /* scribble-1.png's stroke ink starts right at the top
    of its own canvas (pixel-scan verified, no internal top padding) —
    a -24px pull would drive the ink straight through the text above.
    0 already reads as "pulled up snug" since the art has no built-in gap. */
  margin-left: -6px;
  width: calc(100% + 12px);
  height: auto;
  transform: rotate(-2deg);
  transition: transform 150ms ease-out;
}

.project__cta:hover .project__cta-underline {
  transform: rotate(-2deg) translate(2px, 1px);
}

/* project entrance choreography — interactions-spec.md §5 */
.section--project.is-entering .project__titleblock { animation: rise-in 300ms ease-out forwards; }
.section--project.is-entering .project__body { animation: rise-in 300ms ease-out 150ms forwards; opacity: 0; }
.section--project.is-entering .project__demonstrates { animation: rise-in 300ms ease-out 300ms forwards; opacity: 0; }
.section--project.is-entering .project__pullquote { animation: fade-in 300ms ease-out 700ms forwards; }
.section--project.is-entering .project__cta { animation: fade-in 300ms ease-out 800ms forwards; }

.section--project .project__titleblock { opacity: 0; transform: translateY(12px); }
.section--project .project__pullquote { opacity: 0; }
.section--project .project__cta { opacity: 0; }

/* ---------- footer / closing section ---------- */
.section--footer {
  background-color: var(--ink-primary); /* #212121 — inverted from bg-paper */
  color: var(--pure-white);
  align-items: stretch; /* override .card's align-items:center — this
    section has its own top-to-bottom rhythm (content block, then a
    bottom bar pinned to the floor), not a single vertically-centered
    unit like the other cards */
  padding: 22px 20px;
  /* the content block + bottom bar fill exactly 100% of the card's
     height by construction (margin-top:auto on .footer__bar), which
     measured as precisely zero slack below the bar — any real-world
     gap between window height and true CSS viewport height (a
     scrollbar, browser zoom, OS chrome) pushes it past the visible
     edge. overflow:auto is the same safety net .section--project
     already relies on for this exact failure mode. */
  overflow: hidden auto;
}

.footer__inner {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
}

.footer__content {
  display: flex;
  flex-direction: column;
  align-items: flex-end; /* content column right-aligned, flush right */
  text-align: right;
  /* clears the fixed global nav (top:22px + its own rendered height)
     plus the spec's 39px gap below it — nav isn't part of this
     section's DOM flow since it's shared/global, so this is a measured
     estimate, verified against a real screenshot rather than derived
     from the nav's box model directly. */
  margin-top: 87px; /* measured against a live screenshot: 77px left a
    29px gap below the nav's real rendered bottom edge, 10px short of
    the spec'd 39px */
}

.footer__headline {
  font-family: var(--font-display);
  font-size: var(--size-footer-headline);
  line-height: 1;
  text-transform: uppercase;
  width: 100%;
}

.footer__subhead {
  font-family: var(--font-body);
  font-size: var(--size-body);
  text-transform: uppercase;
  max-width: 950px;
  margin-top: 10px;
}

.footer__signature-block {
  margin-top: 120px; /* was 200px — .footer__bar is pinned flush to the
    card's bottom edge (margin-top:auto), so it has zero built-in
    buffer against real-world viewport-height quirks (scrollbars, zoom,
    browser chrome) that make the actual visible area slightly shorter
    than the reported one. Trimming this frees genuine slack instead of
    relying only on the overflow:auto scroll fallback. */
  max-width: 950px;
}

.footer__signature {
  font-family: var(--font-hand);
  font-size: var(--size-footer-signature);
  line-height: 1;
  color: var(--pure-white); /* the one place Rock Salt appears in white,
    not accent-red, matching this section's inverted palette */
}

.footer__tagline {
  font-family: var(--font-body);
  font-size: var(--size-body);
  text-transform: uppercase;
  margin-top: 20px; /* Rock Salt's "N" flourish extends ~13px below its
    own line box (pixel-scan verified) — 10px alone let it graze the
    tagline text below */
}

.footer__bar {
  margin-top: auto; /* when content fits in one screen, pins the bar to
    the bottom of the section (no scroll needed) */
  position: sticky; /* when content overflows and .section--footer
    scrolls (its own overflow:hidden auto), keeps the bar glued to the
    bottom of the viewport throughout the scroll instead of only
    appearing once fully scrolled to the end */
  bottom: 0;
  background-color: var(--ink-primary); /* opaque backing so scrolled
    content doesn't show through/overlap the bar while it's stuck */
  border-top: 1px solid var(--pure-white);
  padding: 20px 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
}

.footer__bar-links,
.footer__bar-copyright {
  font-family: var(--font-body);
  font-size: var(--size-body);
  text-transform: uppercase;
}

.footer__bar-links a {
  transition: color 200ms ease-out;
}

.footer__bar-links a:hover {
  color: var(--accent-red); /* matches .site-nav__links a:hover and
    .case-study-overlay__close:hover — the site's one hover convention */
}

/* footer entrance choreography — same ~1.1s staggered envelope as the
   project sections' titleblock/body/demonstrates/pullquote/cta */
.section--footer.is-entering .footer__content { animation: rise-in 300ms ease-out forwards; }
.section--footer.is-entering .footer__signature-block { animation: rise-in 300ms ease-out 400ms forwards; opacity: 0; }
.section--footer.is-entering .footer__bar { animation: fade-in 300ms ease-out 800ms forwards; }

.section--footer .footer__content { opacity: 0; transform: translateY(12px); }
.section--footer .footer__signature-block { opacity: 0; }
.section--footer .footer__bar { opacity: 0; }

/* ---------- in-page case study expand ---------- */
.case-study-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--bg-paper);
  overflow-y: auto;
  opacity: 0;
  visibility: hidden;
  transition: opacity 450ms ease, visibility 0s linear 450ms;
}

.case-study-overlay.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 450ms ease, visibility 0s linear 0s;
}

.case-study-overlay__inner {
  max-width: 860px;
  margin: 0 auto;
  padding: 140px 24px 120px;
}

.case-study-overlay__close {
  position: fixed;
  top: 22px;
  right: 20px;
  z-index: 51;
  font-family: var(--font-display);
  font-size: var(--size-nav);
  text-transform: uppercase;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--ink-primary);
}

.case-study-overlay__close:hover { color: var(--accent-red); }

.case-study-overlay h2 {
  font-family: var(--font-display);
  font-size: clamp(32px, 5vw, 64px);
  text-transform: uppercase;
  margin: 0 0 24px;
}

.case-study-overlay p {
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.6;
  margin-bottom: 16px;
}

/* ---------- about page (about.html) ---------- */
@keyframes color-to-accent { from { color: var(--ink-primary); } to { color: var(--accent-red); } }

/* about-cover */
/* .card centers its child vertically by default (align-items: center),
   which left the section's own 20px padding sitting on top of ~48px of
   extra centering slack at the bottom — anchoring to the bottom here
   makes that 20px the true, only gap to the viewport edge. */
.section--about-cover { align-items: flex-end; }

.about-cover__inner {
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  gap: 50px;
  padding: 20px;
}

.about-cover__photo {
  width: clamp(220px, 43.611vw, 628px);
  aspect-ratio: 628 / 837;
  object-fit: cover;
  flex-shrink: 0;
  margin-top: 50px;
}

.about-cover__copy {
  max-width: 620px;
}

.about-cover__headline {
  font-family: var(--font-display);
  font-size: var(--size-about-headline);
  letter-spacing: 0.04em;
  line-height: 1.04;
  text-transform: uppercase;
  margin-bottom: 50px;
}

.about-cover__headline .accent { color: var(--accent-red); }

.about-cover__body {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--size-about-body);
  text-transform: uppercase;
  line-height: 1.5;
}

.about-cover__body + .about-cover__body { margin-top: 16px; }

.about-cover__body strong { font-weight: 700; }

.section--about-cover .about-cover__photo,
.section--about-cover .about-cover__copy {
  opacity: 0;
  transform: translateY(12px);
}

.section--about-cover.is-entering .about-cover__photo { animation: rise-in 300ms ease-out forwards; }
.section--about-cover.is-entering .about-cover__copy { animation: rise-in 300ms ease-out 150ms forwards; }

/* shared icon + scribble treatment for experience/education (marginalia-
   style corner accent, per design-system.md's icon+annotation convention,
   reused here across both sections instead of a bound container) */
.about-icon {
  position: absolute;
  top: 130px;
  right: 60px;
  width: var(--size-about-icon);
  height: var(--size-about-icon);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  opacity: 0;
}

.about-icon[data-icon="desktop_mac"] { background-image: url("../brand-elements/about-elements/desktop_mac_24dp_1F1F1F_FILL0_wght200_GRAD0_opsz24%201.png"); }
.about-icon[data-icon="coffee"] { background-image: url("../brand-elements/about-elements/coffee_24dp_1F1F1F_FILL0_wght200_GRAD0_opsz24%201.png"); }
.about-icon[data-icon="sticky_note_2"] { background-image: url("../brand-elements/about-elements/sticky_note_2_24dp_1F1F1F_FILL0_wght200_GRAD0_opsz24%201.png"); }
.about-icon[data-icon="school"] { background-image: url("../brand-elements/about-elements/school_24dp_1F1F1F_FILL0_wght200_GRAD0_opsz24%201.png"); }
.about-icon[data-icon="auto_stories"] { background-image: url("../brand-elements/about-elements/auto_stories_24dp_1F1F1F_FILL0_wght200_GRAD0_opsz24%201.png"); }

/* scribble underline is identical across all sections in its group and
   never re-animates on scroll (see interactions note in about.html) —
   no opacity baseline / entrance keyframe, just renders in place */
.about-scribble { display: block; }
.about-scribble img { display: block; width: 100%; height: auto; }

/* experience (single persistent component, 3 internal sub-steps —
   same scroll-driven-active-index pattern as .section--how-work: state
   lives in js/about.js's cycle/hover handlers, not per-card entrance
   animation, so there are no .is-entering rules here. */
.experience__inner {
  position: absolute;
  left: 130px;
  top: 50%;
  transform: translateY(-50%);
  width: 480px;
  max-width: 42vw;
}

.experience__kicker {
  font-family: var(--font-body);
  font-size: var(--size-kicker);
  text-transform: uppercase;
  margin-bottom: 28px;
}

.experience__list {
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.experience__item {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--size-howwork-item);
  text-transform: uppercase;
  color: var(--ink-primary);
  transition: color 300ms ease-out;
  cursor: pointer;
}

.experience__item.is-active { color: var(--accent-red); }

.experience__item:focus-visible {
  outline: 2px solid var(--accent-red);
  outline-offset: 4px;
}

.experience__subtitle {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--size-howwork-subtitle);
  text-transform: capitalize;
  color: var(--accent-red);
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: opacity 200ms ease-out, max-height 300ms ease-out;
}

.experience__item.is-active .experience__subtitle {
  max-height: 60px;
  opacity: 1;
  transition: opacity 200ms ease-out 150ms, max-height 300ms ease-out;
}

/* right column: icon + description, one sub-step in normal flow at a
   time (max-height collapse, same technique as .how-work__subtitle's
   reveal) so the block's real height tracks whichever step is active —
   the scribble below it in flow moves with it instead of sitting at a
   fixed spot sized for the tallest variant */
.experience__details {
  position: absolute;
  left: 56%;
  top: 50%;
  transform: translateY(-50%);
  width: 480px;
  max-width: 38vw;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.experience__detail-stack {
  width: 100%;
}

.experience__detail {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  pointer-events: none;
  transition: max-height 300ms ease, opacity 200ms ease;
}

.experience__detail.is-visible {
  max-height: 640px; /* generous cap above any real content height —
    the box always renders at its natural content height, this just
    gives the collapse transition room to animate through. Must clear
    the icon's own height (--size-about-icon, up to 250px) plus the
    description below it, or flex-shrink squeezes the icon to fit. */
  opacity: 1;
  pointer-events: auto;
  transition: max-height 300ms ease, opacity 200ms ease 100ms; /*
    marginalia-swap stagger: incoming starts ~100ms after outgoing
    begins clearing */
}

.experience__detail .about-icon {
  position: static;
  flex-shrink: 0; /* the column's max-height cap above would otherwise
    squeeze this to fit alongside the description, same failure mode
    the cap size is guarding against */
  opacity: 1; /* overrides .about-icon's corner-icon opacity:0 baseline
    (education still uses that entrance-fade path) — visibility here is
    owned entirely by the parent .experience__detail's is-visible toggle */
  margin-bottom: 24px;
}

.experience__description {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.experience__description p {
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-marginalia);
  color: var(--accent-red);
  line-height: 1.25;
  text-align: left;
}

.experience__scribble { margin-top: 24px; width: 360px; }

/* education (single persistent component, 2 internal sub-steps — same
   scroll-driven-active-index pattern as .section--how-work /
   .section--experience: state lives in js/about.js's cycle/hover
   handlers, no .is-entering rules here. Left-aligned single-column
   stack, icon beside the description below the list — deliberately a
   different arrangement from .section--experience's two-column layout,
   per the reference screenshot. */
.education__inner {
  position: absolute;
  left: 130px;
  top: 50%;
  transform: translateY(-50%);
  width: 1000px;
  max-width: 80vw;
}

.education__kicker {
  font-family: var(--font-body);
  font-size: var(--size-kicker);
  text-transform: uppercase;
  margin-bottom: 28px;
}

.education__list {
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.education__item {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--size-howwork-item);
  text-transform: uppercase;
  color: var(--ink-primary);
  transition: color 300ms ease-out;
  cursor: pointer;
}

.education__item.is-active { color: var(--accent-red); }

.education__item:focus-visible {
  outline: 2px solid var(--accent-red);
  outline-offset: 4px;
}

.education__subtitle {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--size-howwork-subtitle);
  text-transform: capitalize;
  color: var(--accent-red);
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: opacity 200ms ease-out, max-height 300ms ease-out;
}

.education__item.is-active .education__subtitle {
  max-height: 60px;
  opacity: 1;
  transition: opacity 200ms ease-out 150ms, max-height 300ms ease-out;
}

/* icon + description row, below the list — one sub-step in normal flow
   at a time (max-height collapse, same technique as .how-work__subtitle's
   reveal) so the row's real height tracks whichever step is active —
   the scribble under it moves with it instead of sitting at a fixed
   spot sized for the tallest variant */
.education__row { margin-top: 48px; }

.education__detail-stack {
  width: 100%;
}

.education__detail {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 24px;
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  pointer-events: none;
  transition: max-height 300ms ease, opacity 200ms ease;
}

.education__detail.is-visible {
  max-height: 280px; /* generous cap above any real content height —
    must clear the icon's own height (--size-about-icon, up to 250px)
    or overflow:hidden on the collapsed row clips it */
  opacity: 1;
  pointer-events: auto;
  transition: max-height 300ms ease, opacity 200ms ease 100ms; /*
    marginalia-swap stagger: incoming starts ~100ms after outgoing
    begins clearing */
}

.education__detail .about-icon {
  position: static;
  flex-shrink: 0;
  opacity: 1; /* overrides .about-icon's corner-icon opacity:0 baseline */
}

.education__description p {
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-marginalia);
  color: var(--accent-red);
  line-height: 1.25;
  text-align: left;
}

.education__scribble { margin-top: 24px; width: 480px; }

/* closing — photo left, text right, same flex-row/vertically-centered
   pattern as .about-cover__inner, just its own class scope since the
   photo's aspect ratio and the copy's type treatment differ */
.closing__inner {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 50px;
  padding: 0 40px;
}

.closing__photo {
  width: clamp(340px, 54.4vw, 782px);
  aspect-ratio: 1243 / 1343;
  object-fit: contain; /* the red brush outline is baked into the PNG's
    transparent-bg art itself — contain keeps its irregular edge whole,
    unlike .about-cover__photo's cover crop on a plain rectangular photo */
  flex-shrink: 0;
}

.closing__copy {
  max-width: 620px;
}

.closing__headline {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--size-hero-headline);
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--ink-primary);
  margin-bottom: 24px;
}

.closing__body {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(14px, 1.667vw, 24px);
  text-transform: uppercase;
  color: var(--ink-primary);
  line-height: 1.5;
  margin-bottom: 32px;
}

.closing__signoff {
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-pullquote);
  color: var(--accent-red);
  line-height: 1.25;
}

.section--closing .closing__photo,
.section--closing .closing__copy {
  opacity: 0;
  transform: translateY(12px);
}

.section--closing.is-entering .closing__photo { animation: rise-in 300ms ease-out forwards; }
.section--closing.is-entering .closing__copy { animation: rise-in 300ms ease-out 150ms forwards; }

/* ---------- case study pages (project-*.html) ----------
   Normal continuous document scroll (see html.scroll-page above), not
   the card-stack — each section is a tall, roughly-full-viewport block
   that fades/rises into view via IntersectionObserver (js/*.js) rather
   than snapping, and closes with a footer-style divider line instead
   of the site's usual "no dividing lines" treatment (deliberate
   exception, per direct request, matching .footer__bar's border). */
.cs-section {
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding: 140px 0;
  border-bottom: 1px solid var(--ink-primary);
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 600ms ease-out, transform 600ms ease-out;
}

.cs-section.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.cs-section__inner {
  width: 100%;
  max-width: 1700px;
  margin: 0 auto;
  padding: 0 9%;
}

/* hero (title + role/domain/scope) */
.cs-hero__title {
  font-family: var(--font-display);
  font-size: var(--size-cs-hero-title);
  text-transform: uppercase;
  line-height: 1;
  letter-spacing: 0.01em;
}

.cs-hero__title .accent { color: var(--accent-red); }

.cs-hero__divider {
  border: none;
  border-top: 1px solid var(--ink-primary);
  margin: 32px 0;
}

.cs-meta__row {
  display: flex;
  gap: 40px;
  margin: 0;
  padding: 10px 0;
}

.cs-meta__row dt {
  flex: 0 0 140px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--size-kicker);
  text-transform: uppercase;
}

.cs-meta__row dd {
  flex: 1;
  margin: 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--size-kicker);
  text-transform: uppercase;
  color: var(--ink-secondary);
}

/* story sections: headline left, body right */
.cs-story__inner {
  display: flex;
  gap: 80px;
  align-items: flex-start;
}

.cs-story__headline {
  flex: 0 0 clamp(280px, 32vw, 460px);
  font-family: var(--font-display);
  font-size: var(--size-cs-headline);
  text-transform: uppercase;
  line-height: 1.05;
}

.cs-story__headline .accent { color: var(--accent-red); }

.cs-story__body {
  flex: 1;
  max-width: 900px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.cs-story__body p {
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.6;
}

.cs-story__body strong,
.cs-story__split-col strong { font-weight: 700; }

/* .cs-story__body .cs-story__logic, not .cs-story__logic alone — needs
   (0,2,0) specificity to beat .cs-story__body p's (0,1,1), since these
   are <p> tags with an extra class, not a different element */
.cs-story__body .cs-story__logic {
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-marginalia);
  line-height: 1.6;
}

.cs-story__body .cs-story__pullquote,
.cs-pivot__right .cs-story__pullquote,
.cs-story__split-col .cs-story__pullquote {
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-pullquote);
  color: var(--accent-red);
  line-height: 1.3;
}

/* stacked story variant: headline full-width on top, body full-width
   below, for headlines too long to fit .cs-story__headline's narrow
   column (see project-merry-health.html) */
.cs-story--stacked .cs-story__inner {
  flex-direction: column;
  gap: 24px;
}

.cs-story--stacked .cs-story__headline {
  flex: none;
}

.cs-story--stacked .cs-story__body {
  max-width: none;
}

/* split story variant: headline full-width on top (as .cs-story--stacked),
   then two body paragraphs side by side below it, each its own column
   (see project-educaitors.html "What changed" section) */
.cs-story--split .cs-story__inner {
  flex-direction: column;
  gap: 24px;
}

.cs-story--split .cs-story__headline {
  flex: none;
}

.cs-story__split {
  display: flex;
  gap: 80px;
  align-items: flex-start;
}

.cs-story__split-col {
  flex: 1;
  max-width: 900px;
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.6;
}

/* list row: sits inside a stacked .cs-story__body, closes it out with a
   decisions list beside a pullquote (see project-koala-living.html
   "Understanding before improving.") — reuses .cs-decisions/.cs-decision
   as-is, just needs the row split */
.cs-story__list-row {
  display: flex;
  gap: 80px;
  align-items: flex-start;
}

.cs-story__list-row .cs-decisions { flex: 1; }
.cs-story__list-row .cs-story__pullquote { flex: 1; }

/* pivot section: left column headline + paragraph, right column
   decision pairs — its own two-column split from .cs-story__inner's
   headline/body split, so it gets its own inner layout */
.cs-pivot__inner {
  display: flex;
  gap: 80px;
  align-items: flex-start;
}

.cs-pivot__left {
  flex: 0 0 clamp(280px, 32vw, 460px);
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.cs-pivot__left .cs-story__headline { flex: none; }

.cs-pivot__left p {
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.6;
}

.cs-pivot__right {
  flex: 1;
  max-width: 900px;
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.cs-decisions__label {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--size-body);
  line-height: 1.6;
}

.cs-decisions {
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.cs-decision__title {
  font-family: var(--font-hand);
  font-size: var(--size-rocksalt-marginalia);
  line-height: 1.4;
}

.cs-decision__desc {
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.6;
  margin-top: 4px;
}

/* full-bleed image sections — the box's background-color is the
   .card "covered" tint, shown only as a fallback for a section not yet
   given an asset (object-fit: cover below means a real image fills the
   box completely once one is added, so the tint never peeks through) */
.cs-image-placeholder {
  /* same 100vh min-height as the text sections (.cs-section); the box
     stretches to fill whatever the 20px padding leaves */
  align-items: stretch;
  padding: 20px;
}
.cs-image-placeholder .cs-section__inner {
  display: flex;
  padding: 0;
  max-width: none;
}

.cs-image-placeholder__box {
  flex: 1;
  width: 100%;
  height: 100%;
  background-color: var(--bg-paper-covered);
  object-fit: cover;
  display: block;
}

/* footer, reused on a normal-scroll page (see project-layrrrd.html) —
   .section--footer supplies the dark theme/copy styling; this supplies
   the same effective box model .card normally would, minus the
   card-stack's fixed positioning/transform */
.cs-footer {
  display: flex;
  align-items: stretch;
  min-height: 100vh;
}

/* .footer__content/.footer__signature-block/.footer__bar default to
   opacity:0, only revealed via the .is-entering keyframe the card-stack
   pages trigger on arrival (js/main.js retriggerEntrance) — this page
   never adds that class, so override the baseline directly instead of
   wiring up an entrance animation for a footer the user scrolls down
   to at their own pace. */
.cs-footer .footer__content,
.cs-footer .footer__signature-block,
.cs-footer .footer__bar {
  opacity: 1;
  transform: none;
}

/* ---------- phone layout (≤767px) ----------
   Everything above is tuned for side-by-side columns at 1440px; the
   clamp() minimums alone can't save it on a phone — fixed column
   minimums (.cs-story__headline's 280px, .about-cover__photo's 220px,
   .closing__photo's 340px, the absolute left:130px about blocks) push
   the other column past the right edge, where overflow:hidden silently
   eats it. Here every split collapses to one stacked column, and cards
   whose content outgrows the screen scroll internally — js/main.js and
   js/about.js only advance to the next card once the active one has
   been scrolled to its end. */
@media (max-width: 767px) {
  /* nav gets a solid backing so card content scrolling up beneath it
     doesn't collide with the links */
  .site-nav {
    top: 0;
    padding: 22px 20px 14px;
    background: var(--bg-paper);
    transition: background-color var(--duration-snap) var(--ease-snap);
  }
  body.is-inverted .site-nav { background: var(--ink-primary); }

  /* case studies scroll their text straight under the back link */
  .cs-back {
    top: 0;
    right: 0; /* full-width bar, not just the link's own box */
    padding: 22px 20px 14px;
    background: var(--bg-paper);
  }
  body.is-inverted .cs-back { background: var(--ink-primary); }
  .site-nav__links { gap: 24px; }

  .card { height: 100dvh; }

  /* content-heavy cards: top-anchored below the nav, internally scrollable */
  .section--how-work,
  .section--project,
  .section--about-cover,
  .section--experience,
  .section--education,
  .section--closing {
    align-items: flex-start;
    overflow: hidden auto;
    overscroll-behavior: contain;
    padding: 84px 0 48px;
  }

  /* cover + projects intro */
  .cover__copy,
  .projects-intro__copy {
    top: 84px;
    right: 20px;
    width: auto;
    max-width: none;
  }
  .cover__headline,
  .projects-intro__headline { font-size: 24px; }
  .cover__subhead,
  .projects-intro__subhead { font-size: 18px; }

  /* how I work: list first, then only the active step's two notes,
     in flow instead of pinned to opposite corners */
  .section--how-work {
    flex-direction: column;
    align-items: stretch;
    gap: 32px;
    padding-left: 20px;
    padding-right: 20px;
  }
  .how-work__inner {
    position: static;
    transform: none;
    width: auto;
    max-width: none;
  }
  .section--how-work .marginalia {
    position: static;
    flex-direction: row;
    align-items: center;
    gap: 16px;
    max-width: none;
  }
  .section--how-work .marginalia:not(.is-visible) { display: none; }
  .marginalia__icon {
    flex-shrink: 0;
    width: 72px;
    height: 72px;
  }

  /* project summary cards */
  .section--project .section__inner {
    margin-left: 20px;
    width: calc(100% - 40px);
  }

  /* footer */
  .footer__bar-links a {
    display: inline-block;
    padding: 12px 4px;
  }

  /* about: cover */
  .about-cover__inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 28px;
    padding: 0 20px;
  }
  .about-cover__photo {
    width: min(60vw, 280px);
    margin-top: 0;
  }
  .about-cover__copy { max-width: none; }
  .about-cover__headline { margin-bottom: 24px; }

  /* about: experience + education */
  :root { --size-about-icon: 96px; }

  .section--experience,
  .section--education {
    flex-direction: column;
    align-items: stretch;
    padding-left: 20px;
    padding-right: 20px;
  }
  .experience__inner,
  .experience__details,
  .education__inner {
    position: static;
    transform: none;
    width: auto;
    max-width: none;
  }
  .experience__list,
  .education__list { gap: 24px; }
  .experience__details { margin-top: 36px; }
  .education__row { margin-top: 36px; }
  .experience__scribble,
  .education__scribble { width: min(100%, 360px); }

  /* about: closing */
  .closing__inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 28px;
    padding: 0 20px;
  }
  .closing__photo { width: min(80vw, 340px); }
  .closing__copy { max-width: none; }

  /* case studies: every split stacks */
  .cs-section { padding: 100px 0 72px; }
  .cs-section__inner { padding: 0 20px; }

  .cs-story__inner,
  .cs-pivot__inner,
  .cs-story__split,
  .cs-story__list-row {
    flex-direction: column;
    gap: 24px;
  }
  .cs-story__headline,
  .cs-pivot__left { flex: none; }
  .cs-story__body,
  .cs-pivot__right,
  .cs-story__split-col { max-width: none; }

  .cs-meta__row { gap: 16px; }
  .cs-meta__row dt { flex-basis: 84px; }
}

/* Full-bleed case-study visuals are wide diagrams — on a portrait
   screen, filling 100vh with object-fit:cover crops their labels off
   both sides, so they fall back to their natural aspect ratio here
   (the 100vh image section sizing still holds in landscape). */
@media (orientation: portrait) {
  .cs-image-placeholder { min-height: 0; }
  .cs-image-placeholder__box {
    height: auto;
    align-self: flex-start;
    object-fit: contain;
  }
}

/* ---------- reduced motion ----------
   The stacking transform itself is handled by .reduced-motion .card
   above (pure opacity cross-fade, no slide/scale). This block covers
   the inner content choreography (cover/project entrance keyframes,
   how-work color transitions), collapsing them to interactions-spec.md's
   200ms fallback duration. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 200ms !important;
    transition-duration: 200ms !important;
  }
}
