/* ══════════════════════════════════════════════════════════════════════
   SHOEBILL STUDIO — SCENES
   The rebuilt compositions: hero, projects, the reel, contact.

   This file owns a real design system (tokens, fluid type scale, 12-col
   grid) and re-composes every scene on top of it. The old poster-literal
   layout in styles.css is left behind: the markup it targeted no longer
   exists, so none of it applies here.

   OUT OF SCOPE: the playable hall (.page--hall / .hall-* / #hall-canvas)
   keeps its original styling in styles.css, untouched.
   ══════════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────────────────────
   1 · TOKENS
   ───────────────────────────────────────────────────────────── */

:root {
  /* value ramp — the site now runs dark-first, with paper as the accent
     surface, so the 3D hero and the video work have somewhere to live */
  --void: #080706;
  --ink: #100d0b;
  --ink-2: #16120f;
  --ink-3: #221c17;
  --paper: #d9d4c9;
  --paper-2: #e6e1d6;
  --bone: #f4f1e9;

  --red: #a32017;
  --red-bright: #c0392b;
  --red-hot: #ff3a20;
  --red-deep: #7e120d;

  --on-dark: #e9e4da;
  --on-dark-dim: rgba(233, 228, 218, 0.52);
  --on-light: #100d0b;
  --on-light-dim: rgba(16, 13, 11, 0.56);

  --font-ui: "Jost", system-ui, sans-serif;
  --font-mono: "DM Mono", ui-monospace, monospace;
  --font-display: "Archivo Black", "Jost", sans-serif;

  /* fluid type scale */
  --t-mega: clamp(3.4rem, 13vw, 13rem);
  --t-xxl: clamp(2.4rem, 7.4vw, 7.5rem);
  --t-xl: clamp(1.9rem, 4.6vw, 4.4rem);
  --t-lg: clamp(1.35rem, 2.6vw, 2.5rem);
  --t-md: clamp(1rem, 1.35vw, 1.3rem);
  --t-sm: clamp(0.82rem, 0.95vw, 1rem);
  --t-xs: clamp(0.6rem, 0.68vw, 0.76rem);

  /* rhythm */
  --edge: clamp(20px, 4.4vw, 84px);
  --gut: clamp(12px, 1.7vw, 28px);
  --s1: clamp(6px, 0.7vw, 12px);
  --s2: clamp(12px, 1.4vw, 22px);
  --s3: clamp(20px, 2.4vw, 38px);
  --s4: clamp(32px, 4vw, 64px);
  --s5: clamp(48px, 6.5vw, 108px);
  --s6: clamp(72px, 10vw, 180px);

  /* motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-cut: cubic-bezier(0.76, 0, 0.18, 1);
  --ease-pop: cubic-bezier(0.34, 1.5, 0.5, 1);
}

/* ─────────────────────────────────────────────────────────────
   2 · PRIMITIVES
   ───────────────────────────────────────────────────────────── */

.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gut);
}

/* small tracked mono label used to head every block */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.8em;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--red-bright);
}
.eyebrow em {
  font-style: normal;
  padding: 0.3em 0.55em 0.24em;
  background: var(--red);
  color: var(--bone);
  letter-spacing: 0.16em;
}
.eyebrow i {
  flex: 1 1 2rem;
  min-width: 1.5rem;
  height: 1px;
  background: linear-gradient(90deg, currentColor, transparent);
  opacity: 0.7;
}

.rule {
  height: 1px;
  border: 0;
  background: currentColor;
  opacity: 0.16;
}

/* — buttons: one shape, two weights — */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.9em;
  padding: 0.95em 1.6em;
  border: 0;
  background: none;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--t-sm);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: none;
  isolation: isolate;
  transition: color 0.3s var(--ease-out), transform 0.3s var(--ease-out);
}
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  clip-path: polygon(0 6%, 98% 0, 100% 94%, 2% 100%);
  transition: transform 0.4s var(--ease-out), background 0.3s var(--ease-out);
}
/* the fill that wipes in from the bottom on hover */
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--red);
  clip-path: polygon(0 6%, 98% 0, 100% 94%, 2% 100%);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform 0.4s var(--ease-out);
}
.btn:hover::after { transform: scaleY(1); }
.btn__arrow { font-size: 1.15em; transition: transform 0.35s var(--ease-out); }
.btn:hover .btn__arrow { transform: translateX(0.35em); }

.btn--solid { color: var(--void); }
.btn--solid::before { background: var(--bone); }
.btn--solid:hover { color: var(--bone); }

/* The ghost outline used to be an inset box-shadow, which the slab's own
   clip-path cut away almost completely -- the button rendered as floating
   text with no edge at all while still taking clicks. The edge is a real
   shape now: the same torn quad with a slightly smaller copy of itself
   punched out of it, so the stroke follows the cut instead of being eaten
   by it. */
.btn--ghost { color: var(--on-dark); }
.btn--ghost::before {
  background: rgba(233, 228, 218, 0.42);
  box-shadow: none;
  clip-path: polygon(evenodd,
    0 6%, 98% 0, 100% 94%, 2% 100%,
    calc(0% + 1.5px) calc(6% + 1.5px), calc(98% - 1.5px) calc(0% + 1.5px),
    calc(100% - 1.5px) calc(94% - 1.5px), calc(2% + 1.5px) calc(100% - 1.5px));
  transition: transform 0.4s var(--ease-out), background 0.3s var(--ease-out);
}
.btn--ghost:hover { color: var(--bone); }
.btn--ghost:hover::before { background: rgba(244, 241, 233, 0.72); }

/* on paper scenes */
.on-light .btn--ghost { color: var(--on-light); }
.on-light .btn--ghost::before { background: rgba(16, 13, 11, 0.38); }
.on-light .btn--ghost:hover { color: var(--bone); }
.on-light .btn--ghost:hover::before { background: rgba(16, 13, 11, 0.62); }
.on-light .btn--solid { color: var(--bone); }
.on-light .btn--solid::before { background: var(--ink); }

/* — surfaces — */
.surface-dark {
  background: var(--void);
  color: var(--on-dark);
}
.surface-paper {
  background: var(--paper);
  color: var(--on-light);
}
/* real paper: stucco scan under a flat wash, plus an uneven lit centre */
.surface-paper::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(rgba(217, 212, 201, 0.9), rgba(217, 212, 201, 0.9)),
    url("assets/tex-wall-stucco.jpg") center / cover;
  z-index: 0;
}
.surface-paper::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(ellipse 74% 62% at 50% 40%, rgba(255, 252, 244, 0.3), transparent 74%),
    radial-gradient(ellipse 62% 52% at 8% 92%, rgba(96, 86, 72, 0.16), transparent 72%);
}

/* ─────────────────────────────────────────────────────────────
   3 · PAGE SHELLS
   ───────────────────────────────────────────────────────────── */

.page--hero { background: var(--void); }
.page--contact { background: var(--void); }

.scene {
  position: relative;
  width: 100%;
  min-height: 100%;
}
.scene__inner {
  position: relative;
  z-index: 3;
  height: 100%;
  padding: calc(var(--edge) + 34px) var(--edge) var(--edge);
}

/* ─────────────────────────────────────────────────────────────
   4 · SCENE 01 · HERO
   A dark stage: the bird stands in the void (3D once it streams in),
   the wordmark sews itself on, everything else is slate.
   ───────────────────────────────────────────────────────────── */

.hero { position: absolute; inset: 0; overflow: hidden; }

.hero__bg { position: absolute; inset: 0; z-index: 0; }

/* the mockup's stepped block masses survive as low-contrast relief:
   black paper on black, so they read as depth rather than as a poster */
.hero__blocks {
  position: absolute;
  inset: 0;
  opacity: 0.85;
  background:
    linear-gradient(rgba(20, 16, 13, 0.55), rgba(20, 16, 13, 0.55)),
    url("assets/tex-black-big.jpg") 41% 50% / cover;
  mask-image: url("assets/mask-hero.png");
  -webkit-mask-image: url("assets/mask-hero.png");
  mask-size: 100% 100%;
  -webkit-mask-size: 100% 100%;
  mask-repeat: no-repeat;
  -webkit-mask-repeat: no-repeat;
}

/* key + rim light around where the bird stands */
/* Almost nothing: a whisper of key over where the bird stands, and the
   top/bottom falloff. Any more and the warm radial reads as fog hanging in
   the frame rather than as light. */
.hero__light {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(38% 46% at 62% 44%, rgba(255, 226, 190, 0.035), transparent 62%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.45) 0 8%, transparent 32% 70%, rgba(0, 0, 0, 0.6) 100%);
}

/* the bird, and the only bird: it fades up on its first drawn frame */
.hero__3d {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 1.2s var(--ease-out);
  pointer-events: none;
}
.page--hero.is-3d .hero__3d { opacity: 1; }

/* — copy — */
.hero__inner {
  position: absolute;
  inset: 0;
  z-index: 3;
  align-content: center;
  padding: calc(var(--edge) + 40px) var(--edge) calc(var(--edge) + 34px);
  pointer-events: none;
}
.hero__inner > * { pointer-events: auto; }

.hero__head {
  grid-column: 1 / span 6;
  align-self: center;
}
.hero__name {
  margin: var(--s2) 0 var(--s1);
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  font-weight: 500;
  letter-spacing: 0.46em;
  text-transform: uppercase;
  color: var(--on-dark);
}
/* sewStitches sets an explicit pixel width (it has to, to size the canvas
   backing store); CSS only guards against overflow */
.hero__stitch,
.reel__stitch,
.end__stitch {
  display: block;
  max-width: 100%;
}
.hero__role {
  margin-top: var(--s2);
  max-width: 34ch;
  font-size: var(--t-md);
  font-weight: 300;
  line-height: 1.5;
  letter-spacing: 0.02em;
  color: var(--on-dark-dim);
}
.hero__role b { font-weight: 500; color: var(--on-dark); }
.hero__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s3);
}

/* slate column, bottom right */
.hero__slate {
  grid-column: 10 / span 3;
  align-self: end;   /* its bottom edge lands on the CTA row's */
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.hero__slate div {
  display: flex;
  justify-content: space-between;
  gap: 1em;
  padding: 0.75em 0;
  border-top: 1px solid rgba(233, 228, 218, 0.14);
}
.hero__slate dt { color: var(--on-dark-dim); letter-spacing: 0.3em; }
.hero__slate dd { color: var(--on-dark); text-align: right; }
.hero__slate .is-live { color: var(--bone); }
.hero__slate .is-live::before {
  content: "";
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-right: 0.7em;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 8px currentColor;
  vertical-align: middle;
}

/* ─────────────────────────────────────────────────────────────
   5 · SCENE 02 · STATEMENT + INDEX  (paper)
   ───────────────────────────────────────────────────────────── */

.stage {
  position: relative;
  width: 100%;
  height: 100vh;
  min-height: 34rem;
  overflow: hidden;
}

/* the wings bird, duotoned into the paper as a plate rather than a sticker */
.stage__bird {
  position: absolute;
  left: 50%;
  top: 54%;
  z-index: 1;
  width: min(88vw, 132vh);
  transform: translate3d(-50%, -50%, 0) scale(var(--bird-scale, 1));
  transition: transform 0.7s var(--ease-out);
  pointer-events: none;
}
.stage__bird img {
  display: block;
  width: 100%;
  filter: grayscale(1) contrast(1.22) brightness(0.9);
  mix-blend-mode: multiply;
  opacity: 0.92;
}
/* the old-TV pass. It used to sit in the global grade, which put scanlines
   over the hero, the reel and the hall as well; it belongs to this scene. */
.stage__scan {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0.5;
  background:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.16) 0 1px, rgba(0, 0, 0, 0) 1px 4px),
    radial-gradient(120% 100% at 50% 50%, rgba(0, 0, 0, 0) 55%, rgba(24, 18, 14, 0.22) 100%);
}

.stage__blocks {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(rgba(10, 8, 6, 0.46), rgba(10, 8, 6, 0.46)),
    url("assets/tex-paper-black.jpg") center / cover;
  mask-image: url("assets/mask-projects.png");
  -webkit-mask-image: url("assets/mask-projects.png");
  mask-size: 100% 100%;
  -webkit-mask-size: 100% 100%;
  mask-repeat: no-repeat;
  -webkit-mask-repeat: no-repeat;
}

.stage__inner {
  position: absolute;
  inset: 0;
  z-index: 3;
  align-content: space-between;
  padding: calc(var(--edge) + 40px) var(--edge) calc(var(--edge) + 34px);
  pointer-events: none;
}
.stage__inner > * { pointer-events: auto; }

.stage__top { grid-column: 1 / span 5; grid-row: 1; }

/* the discipline index — numbered rows, right column */
.stage__index {
  grid-column: 9 / span 4;
  grid-row: 1;
  align-self: start;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.stage__index h2 {
  margin-bottom: var(--s2);
  font: inherit;
  letter-spacing: 0.42em;
  color: var(--red);
}
/* the rules were as wide as the grid column; they only need to be as wide
   as the longest discipline */
.stage__index h2,
.stage__index ol { width: fit-content; min-width: 11em; margin-left: auto; }
.stage__index ol { counter-reset: d; list-style: none; }
.stage__index li {
  counter-increment: d;
  display: flex;
  align-items: baseline;
  gap: 1em;
  padding: 0.62em 0;
  border-top: 1px solid rgba(16, 13, 11, 0.14);
  color: var(--on-light);
  transition: color 0.3s var(--ease-out), padding-left 0.3s var(--ease-out);
}
.stage__index li::before {
  content: counter(d, decimal-leading-zero);
  color: var(--red);
  opacity: 0.7;
}
.stage__index li:hover { color: var(--red); padding-left: 0.5em; }

/* the statement — the loudest thing on the paper */
/* It is allowed to run onto the black mass now: motion.js measures every
   word against the mask grid and flips the ones that land on black to red,
   so the composition can be wider than the paper without losing a word.
   See paintOverBlack(). */
.stage__say {
  position: absolute;
  left: var(--edge);
  top: 67%;
  /* z 2, and earlier in the source than .stage__scan, so the old-TV pass
     paints OVER the statement the way it already does over PROJECTS */
  z-index: 2;
  /* wide enough that the long lines genuinely run onto the black mass --
     which is the point: paintOverBlack() then flips those words to red */
  width: min(38vw, 580px);
}
.stage__say p {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3.1vw, 3rem);
  line-height: 0.98;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--on-light);
}
.stage__say em { font-style: normal; color: var(--red); }
/* set by motion.js: this word sits over the mask's black mass. Same ink as
   the stitched PROJECTS (#a32017) so the scene keeps one red. */
.stage__say .split__w.on-black { color: var(--red); }
/* a short rule under the first line, to give the block a shoulder */
.stage__say::before {
  content: "";
  position: absolute;
  left: 0;
  top: -0.9em;
  width: 2.6em;
  height: 3px;
  background: var(--red);
}

.stage__cue {
  grid-column: 9 / span 4;
  grid-row: 2;
  align-self: end;
  justify-self: end;
}

/* the stitched PROJECTS word floats over the plate */
.stage__stitch {
  position: absolute;
  left: 50%;
  top: 49%;
  z-index: 2;
  transform: translate(-50%, -50%);
  cursor: none;
}

/* ─────────────────────────────────────────────────────────────
   6 · SCENE 03 · THE REEL  (dark)
   Alternating case rows: big plate + meta column.
   ───────────────────────────────────────────────────────────── */

.reel {
  position: relative;
  z-index: 2;
  /* a background layer rather than an overlay element: it can never end up
     on top of a case, and it costs one raster. The noise rect sits at 0.11 --
     at 0.5 the grain lifted the whole scene to grey instead of speckling it. */
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.11'/%3E%3C/svg%3E") 0 0 / 200px 200px,
    #030303;
  color: var(--on-dark);
  padding: var(--s6) 0 0;
}
.reel::before {
  /* torn top edge where the paper scene ends and the dark reel begins */
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  /* top:2px against translateY(-100%) so the torn shape ends 2px INSIDE the
     reel. It used to be top:-1px against -99%, which put its lower edge about
     0.4px short of the reel: too little to see as a gap, but enough that the
     row underneath stayed paper, and the paper blended with the reel into a
     pale hairline right where the section ends. The overlap is invisible --
     the strip is void and so is the reel's own top -- and 2px keeps it closed
     on fractional device pixels too. */
  top: 2px;
  height: clamp(28px, 4vw, 64px);
  background: var(--void);
  clip-path: polygon(0 62%, 7% 30%, 14% 58%, 23% 22%, 33% 54%, 44% 26%, 55% 56%, 65% 24%, 76% 52%, 86% 28%, 94% 56%, 100% 34%, 100% 100%, 0 100%);
  transform: translateY(-100%);
}

.reel__head {
  padding: 0 var(--edge);
  align-items: start;
}
.reel__head-lead { grid-column: 1 / span 6; }
.reel__stitch { margin-top: var(--s2); }
.reel__count {
  grid-column: 9 / span 4;
  justify-self: end;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--on-dark-dim);
  line-height: 2;
}
.reel__count b { color: var(--bone); font-weight: 500; }
.reel__count em { font-style: normal; color: var(--red-hot); }

.reel__list {
  margin-top: var(--s6);
  padding: 0 var(--edge);
  display: flex;
  flex-direction: column;
  gap: var(--s6);
}

/* — one case — */
.case {
  align-items: center;
}
.case__media {
  position: relative;
  grid-column: 1 / span 7;
  grid-row: 1;
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--ink-2);
  cursor: none;
}
.case:nth-child(even) .case__media { grid-column: 6 / span 7; }
.case__img {
  position: absolute;
  inset: -6% 0;
  background-position: center;
  background-size: cover;
  filter: grayscale(0.55) contrast(1.12) brightness(0.82) saturate(0.75);
  /* --p is the scroll progress through the viewport, written by motion.js */
  transform: translate3d(0, calc(var(--p, 0.5) * -6%), 0) scale(1.02);
  transition: filter 0.6s var(--ease-out), transform 0.9s var(--ease-out);
  will-change: transform;
}
/* the CRT pass over each plate — scanlines plus a corner-weighted tube
   vignette. It clears completely on hover so the work itself is what you
   look at, leaving only the red framing ticks. */
.case__scrim {
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.34) 0 1px, rgba(0, 0, 0, 0) 1px 3px),
    radial-gradient(115% 100% at 50% 50%, rgba(0, 0, 0, 0) 52%, rgba(6, 5, 4, 0.5) 100%),
    linear-gradient(180deg, rgba(8, 7, 6, 0.42) 0 22%, transparent 50%, rgba(8, 7, 6, 0.5));
  transition: opacity 0.45s var(--ease-out);
}
.case__tick {
  position: absolute;
  width: clamp(16px, 1.6vw, 26px);
  height: clamp(16px, 1.6vw, 26px);
  border: 2px solid var(--red-hot);
  opacity: 0;
  transition: opacity 0.35s var(--ease-out), transform 0.5s var(--ease-pop);
}
.case__tick--tl { top: 14px; left: 14px; border-right: 0; border-bottom: 0; transform: translate(-10px, -10px); }
.case__tick--br { bottom: 14px; right: 14px; border-left: 0; border-top: 0; transform: translate(10px, 10px); }
.case__chip {
  position: absolute;
  left: 50%;
  top: 50%;
  padding: 0.85em 1.5em;
  background: var(--red);
  color: var(--bone);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  white-space: nowrap;
  clip-path: polygon(0 8%, 97% 0, 100% 92%, 3% 100%);
  transform: translate(-50%, -50%) scale(0.8) rotate(-3deg);
  opacity: 0;
  transition: opacity 0.35s var(--ease-out), transform 0.45s var(--ease-pop);
}
.case__stamp {
  position: absolute;
  right: 3%;
  bottom: 0;
  font-family: var(--font-display);
  font-size: clamp(48px, 8vw, 132px);
  line-height: 0.82;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(244, 241, 233, 0.34);
  transition: -webkit-text-stroke-color 0.5s var(--ease-out), transform 0.6s var(--ease-out);
  pointer-events: none;
}

.case__meta { grid-column: 9 / span 4; grid-row: 1; }
.case:nth-child(even) .case__meta { grid-column: 1 / span 4; }
.case__idx {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.4em;
  color: var(--red-bright);
}
.case__title {
  margin: var(--s2) 0 var(--s2);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--t-lg);
  line-height: 1.02;
  letter-spacing: -0.005em;
  color: var(--bone);
  transition: color 0.35s var(--ease-out);
}
.case__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5em;
  list-style: none;
  margin-bottom: var(--s2);
}
.case__tags li {
  padding: 0.4em 0.8em 0.34em;
  border: 1px solid rgba(233, 228, 218, 0.22);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--on-dark-dim);
}
.case__cue {
  display: flex;
  align-items: center;
  gap: 0.7em;
  padding-top: var(--s2);
  border-top: 1px solid rgba(233, 228, 218, 0.16);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--on-dark-dim);
  transition: color 0.35s var(--ease-out), gap 0.35s var(--ease-out);
}

.case:hover .case__img { filter: none; transform: translate3d(0, calc(var(--p, 0.5) * -6%), 0) scale(1.07); }
.case:hover .case__scrim { opacity: 0; }
/* with the scrim gone the label furniture needs to carry its own contrast */
.case:hover .case__tag,
.case:hover .case__stamp { text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85); }
.case:hover .case__tick { opacity: 1; transform: translate(0, 0); }
.case:hover .case__chip { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(-3deg); }
.case:hover .case__stamp { -webkit-text-stroke-color: rgba(244, 241, 233, 0.9); transform: translateY(-6px); }
.case:hover .case__title { color: var(--red-bright); }
.case:hover .case__cue { color: var(--red-hot); gap: 1.2em; }

/* entries with nothing to open yet */
.case--soon .case__img { filter: grayscale(1) brightness(0.42); }
.case--soon .case__media { cursor: none; pointer-events: none; }
.case--soon:hover .case__img { filter: grayscale(1) brightness(0.42); transform: translate3d(0, calc(var(--p, 0.5) * -6%), 0) scale(1.02); }
.case--soon:hover .case__scrim { opacity: 1; }
.case--soon:hover .case__tick { opacity: 0; }
.case--soon:hover .case__title { color: var(--bone); }
.case--soon:hover .case__cue { color: var(--on-dark-dim); gap: 0.7em; }
.case--soon:hover .case__stamp { -webkit-text-stroke-color: rgba(244, 241, 233, 0.34); transform: none; }

/* — closing block of the reel — */
/* Section 04 is a deliberate break in the dark reel: a slab of the same
   paper the Projects scene runs on, torn along its top and bottom edges. */
.reel__outro {
  position: relative;
  margin-top: var(--s6);
  padding: var(--s6) var(--edge);
  align-items: center;
  color: var(--on-light);
  background: var(--paper);
}
.reel__outro::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(rgba(217, 212, 201, 0.9), rgba(217, 212, 201, 0.9)),
    url("assets/tex-wall-stucco.jpg") center / cover;
}
.reel__outro::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 60% 50% at 8% 94%, rgba(96, 86, 72, 0.14), transparent 72%);
}
/* The torn edges are CUT OUT OF THE SLAB, not painted over it.
   They used to be two absolutely-positioned black shapes laid on top. Both
   carried a clip-path, so Chrome gave each its own render surface -- and a
   surface composites on its own schedule. Scroll fast and the two overlays
   lagged the content they were supposed to be masking, opening a band of bare
   paper along the top and bottom of the section for a frame or two.
   Clipping the slab itself leaves nothing to fall out of step: the paper
   simply ends in a torn edge, in the same paint pass as everything else, and
   the reel's own background (grain included) shows through the teeth. Two
   fewer DOM nodes and two fewer composited layers as well. */
.reel__outro {
  --tear: clamp(22px, 3.2vw, 52px);
  clip-path: polygon(
    /* top edge, left to right */
    0    calc(var(--tear) * 0.54),
    7%   calc(var(--tear) * 0.22),
    15%  calc(var(--tear) * 0.58),
    25%  calc(var(--tear) * 0.24),
    35%  calc(var(--tear) * 0.62),
    45%  calc(var(--tear) * 0.26),
    55%  calc(var(--tear) * 0.66),
    66%  calc(var(--tear) * 0.30),
    76%  calc(var(--tear) * 0.70),
    85%  calc(var(--tear) * 0.34),
    93%  calc(var(--tear) * 0.76),
    100% calc(var(--tear) * 0.42),
    /* bottom edge, right back to left */
    100% calc(100% - var(--tear) * 0.42),
    92%  calc(100% - var(--tear) * 0.74),
    84%  calc(100% - var(--tear) * 0.34),
    74%  calc(100% - var(--tear) * 0.70),
    63%  calc(100% - var(--tear) * 0.30),
    52%  calc(100% - var(--tear) * 0.68),
    42%  calc(100% - var(--tear) * 0.26),
    32%  calc(100% - var(--tear) * 0.64),
    22%  calc(100% - var(--tear) * 0.24),
    12%  calc(100% - var(--tear) * 0.60),
    5%   calc(100% - var(--tear) * 0.22),
    0    calc(100% - var(--tear) * 0.56)
  );
}
.reel__outro-copy,
.reel__outro-cta { position: relative; z-index: 2; }
.reel__outro-copy { grid-column: 1 / span 6; }
.reel__outro h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--t-xl);
  line-height: 1;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  color: var(--on-light);
  margin: var(--s2) 0 var(--s2);
}
.reel__outro h2 em { font-style: normal; color: var(--red); }
.reel__outro p {
  max-width: 38ch;
  font-size: var(--t-md);
  font-weight: 300;
  line-height: 1.6;
  color: var(--on-light-dim);
}
/* `.reel__outro p` outranks `.eyebrow`, and the kicker is a <p> too */
.reel__outro .eyebrow { color: var(--red-bright); }
.reel__outro-cta {
  grid-column: 8 / span 5;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  justify-content: flex-end;
}

/* — the jump to the contact page: the loudest thing after the cases — */
.reel__jump-row {
  display: flex;
  justify-content: center;
  margin-top: var(--s5);
  padding: 0 var(--edge);
}
.reel__jump {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.1em 0 0.16em;
  background: none;
  border: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--t-xl);
  line-height: 1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--bone);
  cursor: none;
  transition: color 0.35s var(--ease-out);
}
/* the rule under it sweeps out from the left on hover */
.reel__jump::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: var(--red);
  transform: scaleX(0.999);
  transform-origin: right;
  transition: transform 0.5s var(--ease-out), background 0.35s var(--ease-out);
}
.reel__jump-label { position: relative; }
.reel__jump-arrow {
  display: inline-block;
  color: var(--red);
  transition: transform 0.45s var(--ease-out), color 0.35s var(--ease-out);
  animation: jump-nudge 2.8s var(--ease-out) infinite;
}
@keyframes jump-nudge {
  0%, 72%, 100% { transform: translateX(0); }
  80% { transform: translateX(0.28em); }
  88% { transform: translateX(0.06em); }
}
.reel__jump:hover { color: var(--red-hot); }
.reel__jump:hover::after { transform: scaleX(0); }
.reel__jump:hover .reel__jump-arrow {
  transform: translateX(0.5em);
  color: var(--red-hot);
  animation: none;
}

/* rolling discipline strip */
/* ENTER THE HALL is the loudest control on the page and it never rests: the
   crimson ring around the slab is permanent and deliberately crooked -- the
   clip-path that cuts the slab drifts between four ragged quads -- and two
   red ghosts of the label tear across it on a fast stepped cadence, sliced
   into horizontal bands so it reads as broken signal rather than a halo. */
.btn--glitch { position: relative; }
.btn--glitch .btn__label {
  position: relative;
  display: inline-block;
}
.btn--glitch .btn__label::before,
.btn--glitch .btn__label::after {
  content: attr(data-text);
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  pointer-events: none;
}
.btn--glitch .btn__label::before {
  color: var(--red-hot);
  animation: btn-glitch-a 1.5s steps(1, end) infinite;
}
.btn--glitch .btn__label::after {
  color: var(--red);
  animation: btn-glitch-b 1.5s steps(1, end) infinite;
}
@keyframes btn-glitch-a {
  0%   { transform: translate(-2px, 1px);  opacity: 0.95; clip-path: inset(4% 0 58% 0); }
  7%   { transform: translate(3px, -1px);  opacity: 0.8;  clip-path: inset(46% 0 20% 0); }
  14%  { transform: translate(-3px, 0);    opacity: 0.9;  clip-path: inset(70% 0 0 0); }
  21%  { transform: translate(1px, 1px);   opacity: 0.55; clip-path: inset(0 0 74% 0); }
  28%, 40% { transform: none;              opacity: 0;    clip-path: inset(0); }
  47%  { transform: translate(2px, 0);     opacity: 0.9;  clip-path: inset(30% 0 40% 0); }
  54%  { transform: translate(-3px, 1px);  opacity: 0.75; clip-path: inset(0 0 62% 0); }
  61%  { transform: translate(2px, -2px);  opacity: 0.85; clip-path: inset(64% 0 0 0); }
  68%, 82% { transform: none;              opacity: 0;    clip-path: inset(0); }
  88%  { transform: translate(-2px, 0);    opacity: 0.7;  clip-path: inset(22% 0 52% 0); }
  94%  { transform: translate(3px, 1px);   opacity: 0.6;  clip-path: inset(58% 0 12% 0); }
  100% { transform: none;                  opacity: 0;    clip-path: inset(0); }
}
@keyframes btn-glitch-b {
  0%   { transform: translate(3px, -1px);  opacity: 0.7;  clip-path: inset(52% 0 14% 0); }
  7%   { transform: translate(-3px, 1px);  opacity: 0.85; clip-path: inset(0 0 66% 0); }
  14%  { transform: translate(2px, 1px);   opacity: 0.6;  clip-path: inset(26% 0 46% 0); }
  21%  { transform: translate(-1px, -1px); opacity: 0.8;  clip-path: inset(76% 0 0 0); }
  28%, 40% { transform: none;              opacity: 0;    clip-path: inset(0); }
  47%  { transform: translate(-3px, -1px); opacity: 0.75; clip-path: inset(0 0 70% 0); }
  54%  { transform: translate(3px, 1px);   opacity: 0.6;  clip-path: inset(56% 0 16% 0); }
  61%  { transform: translate(-2px, 2px);  opacity: 0.8;  clip-path: inset(34% 0 38% 0); }
  68%, 82% { transform: none;              opacity: 0;    clip-path: inset(0); }
  88%  { transform: translate(3px, 1px);   opacity: 0.65; clip-path: inset(66% 0 0 0); }
  94%  { transform: translate(-2px, 0);    opacity: 0.55; clip-path: inset(10% 0 60% 0); }
  100% { transform: none;                  opacity: 0;    clip-path: inset(0); }
}
/* the crimson ring is on at all times, and the slab it rings is never a
   clean rectangle -- both faces share the torn quad so the hover fill keeps
   the same silhouette as the border */
.btn--glitch::before,
.btn--glitch::after {
  clip-path: polygon(0 7%, 27% 1%, 62% 5%, 98% 0, 100% 92%, 64% 100%, 29% 94%, 2% 100%);
}
.btn--glitch::before {
  box-shadow: inset 0 0 0 2px var(--red);
  animation: btn-glitch-edge 1.5s steps(1, end) infinite;
}
@keyframes btn-glitch-edge {
  0%, 28% {
    clip-path: polygon(0 7%, 27% 1%, 62% 5%, 98% 0, 100% 92%, 64% 100%, 29% 94%, 2% 100%);
    box-shadow: inset 0 0 0 2px var(--red), 2px 0 0 rgba(224, 18, 44, 0.45);
  }
  14% {
    clip-path: polygon(0 3%, 25% 6%, 59% 0, 99% 4%, 100% 97%, 67% 92%, 31% 100%, 1% 95%);
    box-shadow: inset 0 0 0 2px var(--red-hot), -3px 0 0 rgba(224, 18, 44, 0.4);
  }
  47%, 68% {
    clip-path: polygon(0 5%, 30% 0, 58% 6%, 97% 2%, 100% 95%, 61% 100%, 26% 91%, 3% 98%);
    box-shadow: inset 0 0 0 2px var(--red), 3px 0 0 rgba(224, 18, 44, 0.35);
  }
  61% {
    clip-path: polygon(0 8%, 24% 3%, 64% 1%, 99% 6%, 100% 90%, 68% 98%, 33% 92%, 1% 100%);
    box-shadow: inset 0 0 0 3px var(--red-hot), -2px 0 0 rgba(224, 18, 44, 0.5);
  }
  88% {
    clip-path: polygon(0 2%, 28% 7%, 60% 2%, 98% 5%, 100% 96%, 63% 91%, 30% 99%, 2% 93%);
    box-shadow: inset 0 0 0 2px var(--red-hot), 2px 0 0 rgba(224, 18, 44, 0.4);
  }
  100% {
    clip-path: polygon(0 7%, 27% 1%, 62% 5%, 98% 0, 100% 92%, 64% 100%, 29% 94%, 2% 100%);
    box-shadow: inset 0 0 0 2px var(--red), 2px 0 0 rgba(224, 18, 44, 0.45);
  }
}

/* DEMO REELS used to be a hairline ghost standing next to an ink slab, and
   it lost every time. It gets its own weight now: bone fill, a crimson ring
   two pixels thick, red type and a rec-light dot that keeps blinking, so the
   pair reads as two offers rather than one button and a whisper. */
.btn--reels { color: var(--red); }
.btn--reels::before {
  background: rgba(244, 241, 233, 0.9);
  box-shadow: inset 0 0 0 2px var(--red);
  transition: transform 0.4s var(--ease-out), box-shadow 0.3s var(--ease-out);
}
.btn--reels .btn__dot {
  width: 8px;
  height: 8px;
  flex: none;
  margin-right: -0.25em;
  border-radius: 50%;
  background: var(--red-hot);
  box-shadow: 0 0 0 0 rgba(224, 18, 44, 0.55);
  animation: reels-rec 1.7s ease-out infinite;
}
@keyframes reels-rec {
  0%       { opacity: 1;    box-shadow: 0 0 0 0 rgba(224, 18, 44, 0.5); }
  55%      { opacity: 0.25; box-shadow: 0 0 0 7px rgba(224, 18, 44, 0); }
  100%     { opacity: 1;    box-shadow: 0 0 0 0 rgba(224, 18, 44, 0); }
}
.btn--reels:hover { color: var(--bone); }
.btn--reels:hover::before { box-shadow: inset 0 0 0 2px var(--red-hot); }
.btn--reels:hover .btn__dot { background: var(--bone); animation: none; opacity: 1; box-shadow: none; }

/* the down-arrow cues bob, so the scroll they promise reads as motion */
/* the whole cue dips, not just the glyph, so it reads as "keep going"
   from the corner of your eye, and a crimson halo pulses under the slab so
   it is the brightest thing in its corner of the frame */
/* The slab itself no longer moves: the dip and the crimson halo are gone, so
   the cue is carried entirely by its arrow.
   That arrow used to sit dead still for two thirds of the cycle and then snap
   -- the stillness is what made it read as a twitch rather than a cue. Now it
   breathes: one continuous glide down and back, eased at both ends, over a
   range small enough that you register it without it pulling focus. */
.btn__arrow--down { animation: arrow-bob 1.9s ease-in-out infinite; }
@keyframes arrow-bob {
  0%, 100% { transform: translateY(-0.06em); }
  50%      { transform: translateY(0.16em); }
}
.btn:hover .btn__arrow--down { animation: none; transform: translateY(0.34em); }

.strip {
  position: relative;
  margin-top: var(--s3);
  padding: var(--s3) 0;
  overflow: hidden;
  /* the bands are wider than the page and skewed, so the container has to
     clip generously on both sides */
  width: 100%;
}
.strip__band {
  position: relative;
  width: 118%;
  margin-left: -9%;
  overflow: hidden;
  padding: 0.55em 0;
}
.strip__band--a {
  background: var(--red);
  transform: rotate(-1.6deg);
  z-index: 2;
}
.strip__band--b {
  background: var(--ink-2);
  transform: rotate(1.3deg);
  margin-top: -0.9em;
  box-shadow: inset 0 1px 0 rgba(233, 228, 218, 0.14), inset 0 -1px 0 rgba(233, 228, 218, 0.14);
}
/* sprocket perforations along both edges, like real film stock */
.strip__band::before,
.strip__band::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 5px;
  background: repeating-linear-gradient(90deg, rgba(8, 7, 6, 0.85) 0 9px, rgba(8, 7, 6, 0) 9px 26px);
}
.strip__band::before { top: 3px; }
.strip__band::after { bottom: 3px; }
.strip__band--b::before,
.strip__band--b::after { background: repeating-linear-gradient(90deg, rgba(224, 18, 44, 0.7) 0 9px, rgba(0, 0, 0, 0) 9px 26px); }

.strip__track {
  display: flex;
  align-items: center;
  gap: 1.6vw;
  width: max-content;
  font-family: var(--font-display);
  font-size: clamp(0.85rem, 1.5vw, 1.5rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.strip__band--a .strip__track {
  color: var(--bone);
  animation: strip-left 26s linear infinite;
}
.strip__band--b .strip__track {
  color: transparent;
  -webkit-text-stroke: 1px rgba(233, 228, 218, 0.55);
  animation: strip-right 32s linear infinite;
}
.strip__track b { font-weight: 400; }
/* the separator: a small hard-edged lozenge, not a glyph */
.strip__track i {
  width: 0.5em;
  height: 0.5em;
  flex: none;
  background: currentColor;
  transform: rotate(45deg);
}
.strip__band--a .strip__track i { background: var(--ink); }
.strip__band--b .strip__track i { background: var(--red); }

/* PERF: two infinite composited animations that, left alone, keep ticking
   while you are three scenes away inside the hall. chrome.js marks the strip
   idle whenever it scrolls out of the reel or page-main stops being the
   active scene, and both bands park until it is worth running them again.
   Running is the DEFAULT, so a page whose JS never arrives still moves. */
.strip.is-idle .strip__track { animation-play-state: paused; }

/* One step of the loop is ONE copy of the track's content, gap included —
   chrome.js measures it and writes --strip-shift in px.

   It used to be a flat -50% of a track holding exactly two copies. That only
   lands seamlessly while two copies are wider than the band, and on a wide
   viewport they are not: the second copy ran out mid-travel and the tail of
   the cycle was bare tape, which is the empty stretch that then snapped back.
   Sizing the step to the content instead of to the track means the seam lands
   on a copy boundary at any width. The 50% fallback keeps the old behaviour
   if the measurement never runs. */
@keyframes strip-left  { to { transform: translate3d(calc(var(--strip-shift, 50%) * -1), 0, 0); } }
@keyframes strip-right { from { transform: translate3d(calc(var(--strip-shift, 50%) * -1), 0, 0); } to { transform: translate3d(0, 0, 0); } }

/* footer of the dark scene */
.reel__foot {
  padding: var(--s4) var(--edge) var(--s4);
  align-items: end;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--on-dark-dim);
}
.reel__foot-a { grid-column: 1 / span 6; }
.reel__foot-c { grid-column: 10 / span 3; justify-self: end; text-align: right; }
.reel__foot a { color: var(--red-bright); text-decoration: none; }
.reel__foot a:hover { color: var(--red-hot); }
.reel__mail {
  display: block;
  margin-top: 0.6em;
  font-size: var(--t-sm);
  letter-spacing: 0.06em;
  text-transform: none;
}

/* ─────────────────────────────────────────────────────────────
   7 · SCENE 04 · CONTACT  (dark end card)
   ───────────────────────────────────────────────────────────── */

.end { position: absolute; inset: 0; overflow: hidden; }
.end__bg { position: absolute; inset: 0; z-index: 0; }
.end__slab {
  position: absolute;
  right: -6%;
  top: -8%;
  width: 74%;
  height: 118%;
  background:
    linear-gradient(rgba(10, 8, 6, 0.5), rgba(10, 8, 6, 0.5)),
    url("assets/tex-paper-black.jpg") center / cover;
  clip-path: polygon(16% 0, 100% 0, 100% 100%, 0 100%, 9% 58%);
  box-shadow: -3px 0 0 var(--red);
}
/* no wash of any kind on this scene: the warm radial went first, and the
   red pool that replaced it went with it. The slab, its crimson edge and the
   watermark carry the scene on their own. */
.end__mark {
  position: absolute;
  left: -4%;
  bottom: -12%;
  width: min(46vw, 68vh);
  opacity: 0.16;
  transform: rotate(-13deg);
  pointer-events: none;
}

.end__inner {
  position: absolute;
  inset: 0;
  z-index: 3;
  align-content: space-between;
  padding: calc(var(--edge) + 40px) var(--edge) calc(var(--edge) + 34px);
}
.end__top { grid-column: 1 / span 7; }
.end__stitch { margin-top: var(--s2); }
.end__say {
  grid-column: 1 / span 5;
  align-self: center;
  margin-top: var(--s3);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--t-lg);
  line-height: 1.06;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--bone);
}
.end__say em { font-style: normal; color: var(--red); }
/* the one practical fact under the statement, set as slate rather than
   display type so it reads as a note and not a second headline */
.end__note {
  margin-top: var(--s3);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--on-dark-dim);
}
.end__note em { font-style: normal; color: var(--red-bright); margin: 0 0.5em; }

.end__cta {
  grid-column: 6 / span 7;
  align-self: center;
  justify-self: end;
  width: 100%;
  text-align: right;
  /* the address below sizes itself off THIS box rather than off the viewport,
     so it keeps filling one line whichever grid the breakpoint hands it */
  container-type: inline-size;
}
.end__mail {
  display: block;
  font-family: var(--font-mono);
  font-weight: 500;
  /* ONE LINE, always. Measured, not guessed: at 3.95cqw the 41 characters of
     DM Mono come out exactly the width of the column (ratio 1.0001), so 3.86
     leaves it ~2% of slack and the last "m" never grazes the edge. cqw and not
     vw because the column is 7 of 12 tracks on desktop and all 12 below 780px;
     sized against the viewport that needs two numbers and a breakpoint to keep
     in sync, sized against the column it needs one. Because both the type and
     the column scale with the same unit, that 2% holds at every width. The rem
     cap only ever makes it smaller -- it stops the address turning into a
     headline on an ultrawide, and cannot push it onto a second line. */
  font-size: min(3.86cqw, 3.2rem);
  white-space: nowrap;
  line-height: 1.08;
  letter-spacing: 0;
  color: var(--bone);
  text-decoration: none;
  cursor: none;
  transition: color 0.3s var(--ease-out);
}
/* read it as three parts, not one 41-character run: who, then the pivot, then
   where. The @ is the only red thing in the block, so it does the work. */
.end__mail-at {
  /* same --red as the em in "and the occasional strange idea" two blocks up */
  color: var(--red);
  padding: 0 0.06em;
}
.end__mail-host { color: rgba(244, 241, 233, 0.62); }
.end__mail:hover .end__mail-host { color: inherit; }
.end__mail-line {
  display: block;
  height: 2px;
  margin-top: var(--s2);
  background: var(--red);
  transform-origin: right;
  transform: scaleX(1);
  transition: transform 0.5s var(--ease-out), background 0.3s var(--ease-out);
}
.end__mail:hover { color: var(--red-hot); }
.end__mail:hover + .end__mail-line { transform: scaleX(0.35); background: var(--red-hot); }
.end__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
  margin-top: var(--s3);
}
.copy-flash {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #6ad07f;
  opacity: 0;
  transition: opacity 0.25s var(--ease-out);
}
.copy-flash.is-on { opacity: 1; }
.copy-flash.is-warn { color: #ff6b5e; }

.end__foot {
  grid-column: 7 / span 6;
  align-self: end;
  justify-self: end;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--on-dark-dim);
}
.end__foot strong {
  display: block;
  margin-bottom: 0.6em;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--t-md);
  letter-spacing: 0.04em;
  color: var(--red);
}

/* ─────────────────────────────────────────────────────────────
   8 · MOTION · reveal states written by motion.js
   Everything is opacity/transform only, and nothing is hidden until
   motion.js adds .anim-ready — so a failed script leaves a readable page.
   ───────────────────────────────────────────────────────────── */

.anim-ready [data-anim] { will-change: transform, opacity; }

.anim-ready [data-anim="rise"],
.anim-ready [data-anim="fade"] { opacity: 0; transform: translate3d(0, 1.4rem, 0); }
.anim-ready [data-anim="fade"] { transform: none; }
.anim-ready [data-anim="wipe"] { opacity: 0; clip-path: inset(0 100% 0 0); }
.anim-ready [data-anim="scale"] { opacity: 0; transform: scale(1.06); }

/* UNUSED, kept on the shelf: the reel's plates had one entrance variant each
   (a single shared wipe made eight identical entrances down the page). The
   plates no longer carry a data-anim at all -- buildWorksGrid() stopped setting
   it -- so nothing below matches. Put the setAttribute back and it all works
   again; that is the only reason these rules are still here. */
.anim-ready [data-anim^="case-"] { opacity: 0; }
.anim-ready [data-anim="case-wl"] { clip-path: inset(0 100% 0 0); }
.anim-ready [data-anim="case-wr"] { clip-path: inset(0 0 0 100%); }
.anim-ready [data-anim="case-wu"] { clip-path: inset(100% 0 0 0); }
.anim-ready [data-anim="case-wd"] { clip-path: inset(0 0 100% 0); }
.anim-ready [data-anim="case-sl"] { transform: translate3d(-2.4rem, 0, 0); }
.anim-ready [data-anim="case-sr"] { transform: translate3d(2.4rem, 0, 0); }
.anim-ready [data-anim="case-up"] { transform: translate3d(0, 2.2rem, 0); }
.anim-ready [data-anim="case-pop"] { transform: scale(0.94); }

.anim-ready [data-anim^="case-"].is-in {
  opacity: 1;
  clip-path: inset(0 0 0 0);
  transform: none;
  transition:
    opacity 0.34s ease var(--d, 0ms),
    clip-path 0.5s var(--ease-cut) var(--d, 0ms),
    transform 0.52s var(--ease-out) var(--d, 0ms);
}
/* the hover lift must stay instant, so it overrides the entrance timing */
.case.is-in { transition: transform 0.45s var(--ease-cut); }

/* only the variants that actually park a transform get it reset -- a blanket
   `[data-anim].is-in { transform: none }` would also wipe the centring
   translate on the absolutely-positioned stitched titles */
.anim-ready [data-anim="rise"].is-in,
.anim-ready [data-anim="fade"].is-in,
.anim-ready [data-anim="scale"].is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.9s var(--ease-out) var(--d, 0ms),
    transform 1s var(--ease-out) var(--d, 0ms);
}
.anim-ready [data-anim="wipe"].is-in {
  /* opacity matters as much as the clip here: without it the reel stayed
     invisible while still catching clicks */
  opacity: 1;
  clip-path: inset(0 0 0 0);
  transition:
    opacity 0.5s linear var(--d, 0ms),
    clip-path 1.1s var(--ease-cut) var(--d, 0ms);
}

/* split text: every word gets its own mask, and the words on a line share a
   delay -- so it reads as a line-by-line lift while the markup (and the red
   <em> accents inside a heading) stays exactly where the author put it */
.split__w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* room for descenders, which the mask would otherwise crop */
  padding-bottom: 0.16em;
  margin-bottom: -0.16em;
}
.split__i {
  display: inline-block;
  transform: translate3d(0, 115%, 0);
  opacity: 0;
}
.is-in .split__i {
  transform: none;
  opacity: 1;
  transition:
    transform 0.95s var(--ease-out) calc(var(--d, 0ms) + var(--di, 0ms)),
    opacity 0.6s linear calc(var(--d, 0ms) + var(--di, 0ms));
}

/* ─────────────────────────────────────────────────────────────
   9 · RESPONSIVE
   ───────────────────────────────────────────────────────────── */

@media (max-width: 1100px) {
  /* narrower frame: the type column gives up a column and the bird slides
     further right, so the stitched wordmark never lands on the silhouette */
  .hero__head { grid-column: 1 / span 7; }
  .hero__slate { grid-column: 8 / span 5; }
  .stage__top { grid-column: 1 / span 6; }
  .stage__index { grid-column: 8 / span 5; }
  .stage__say { grid-column: 1 / span 8; }
  .reel__head-lead { grid-column: 1 / span 7; }
  .case__media, .case:nth-child(even) .case__media { grid-column: 1 / span 7; }
  .case__meta, .case:nth-child(even) .case__meta { grid-column: 8 / span 5; }
  .reel__outro-copy { grid-column: 1 / span 7; }
  .reel__outro-cta { grid-column: 8 / span 5; }
  .end__cta { grid-column: 5 / span 8; }
}

@media (max-width: 780px) {
  :root { --t-xl: clamp(1.7rem, 8.6vw, 3rem); --t-lg: clamp(1.2rem, 5.6vw, 2rem); }

  .hero__inner,
  .stage__inner,
  .end__inner { padding: calc(var(--edge) + 30px) var(--edge) calc(var(--edge) + 26px); }

  /* phone hero reads as a stack: copy up top, bird standing in the bottom
     half and bleeding off the edge, scroll cue over its feet */
  .hero__inner { align-content: end; row-gap: var(--s3); }
  .hero__head { grid-column: 1 / span 12; align-self: start; }
  .hero__slate { display: none; }
  .hero__role { font-size: var(--t-sm); }
  .hero__cta .btn { flex: 1 1 auto; justify-content: center; }

  /* one column, three stacked rows: brand, statement, cue. The explicit rows
     matter -- say and cue both sit in row 2 on desktop and would collide here. */
  .stage__inner { align-content: space-between; row-gap: var(--s3); }
  .stage__top { grid-column: 1 / span 12; grid-row: 1; }
  .stage__index { display: none; }
  /* the phone mask is a black slab down the LEFT, so the statement stays put
     and flips to bone rather than hunting for beige that is not there */
  .stage__say { top: 38%; width: 46vw; }
  .stage__say p { color: var(--bone); }
  /* on a phone the cue is not a button any more -- just the arrow, lifted
     clear of the bottom edge. The slab and the label both go: at this size
     the glyph on its own says "keep going" and takes none of the room the
     statement needs. It keeps the full button hit area underneath. */
  .stage__cue {
    grid-column: 1 / span 12; grid-row: 3;
    align-self: end; justify-self: start;
    /* level with the foot of the vertical PROJECTS; the -24px is a nudge
       down from where it first sat */
    margin-bottom: calc(clamp(96px, 17.5vh, 200px) - 24px);
  }
  .stage__cue .btn__text { display: none; }
  .stage__cue .btn { gap: 0; padding: 0.35em 0.7em; color: var(--bone); }
  .stage__cue .btn::before,
  .stage__cue .btn::after { display: none; }
  .stage__cue .btn__arrow--down { font-size: 2em; }
  /* the vertical PROJECTS moves to the right gutter, clear of the statement */
  .stage__stitch { left: 84%; top: 46%; }
  .stage__bird { top: 60%; width: 118vw; }

  .reel__head { grid-template-columns: 1fr; }
  .reel__head-lead, .reel__count { grid-column: 1; justify-self: start; text-align: left; }
  .reel__count { margin-top: var(--s2); }
  .reel__list { gap: var(--s5); }
  .case { grid-template-columns: 1fr; row-gap: var(--s2); }
  .case__media, .case:nth-child(even) .case__media { grid-column: 1; grid-row: 1; }
  .case__meta, .case:nth-child(even) .case__meta { grid-column: 1; grid-row: 2; }
  .case__chip { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(-3deg); }
  .case__img { filter: grayscale(0.15) contrast(1.05); }

  .reel__outro { padding: var(--s5) var(--edge); }
  .reel__outro-copy, .reel__outro-cta { grid-column: 1 / span 12; }
  .reel__outro-cta { justify-content: flex-start; margin-top: var(--s3); }
  .reel__foot { row-gap: var(--s2); }
  .reel__foot-a, .reel__foot-c { grid-column: 1 / span 12; justify-self: start; text-align: left; }
  .reel__jump { font-size: clamp(1.5rem, 9vw, 2.6rem); }
  .strip__band { width: 132%; margin-left: -16%; }

  .end__inner { align-content: space-between; row-gap: var(--s3); }
  .end__top, .end__say, .end__cta, .end__foot { grid-column: 1 / span 12; justify-self: start; text-align: left; }
  .end__cta { margin-top: auto; }
  .end__row { justify-content: flex-start; }
  .end__mail-line { transform-origin: left; }
  .end__slab { right: -20%; width: 132%; clip-path: polygon(0 6%, 100% 0, 100% 100%, 0 100%); }
  .end__mark { display: none; }
}

/* ─────────────────────────────────────────────────────────────
   10 · ACCESSIBILITY
   ───────────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .anim-ready [data-anim],
  .anim-ready [data-anim].is-in { opacity: 1; transform: none; clip-path: none; transition: none; }
  .split__i, .is-in .split__i { transform: none; opacity: 1; transition: none; }
  .strip__track, .reel__jump-arrow,
  .btn__arrow--down, .btn--glitch .btn__label::before,
  .btn--glitch .btn__label::after, .btn--glitch::before,
  .btn--reels .btn__dot { animation: none; }
  /* the ghosts are pure motion; without it they just smear the label */
  .btn--glitch .btn__label::before,
  .btn--glitch .btn__label::after { opacity: 0; }
  .case__img { transform: none !important; }
}

/* ─────────────────────────────────────────────────────────────
   12 · LOOSE ENDS
   ───────────────────────────────────────────────────────────── */

.case--blank .case__img {
  background-image: repeating-linear-gradient(135deg, #1b1613 0 12px, #221c17 12px 24px);
}
.case:focus-visible { outline: 3px solid var(--red-hot); outline-offset: 8px; }

/* the reels overlay title keeps the stitched treatment */
.stitch-reels { display: block; }
