/* ============================================================
   FORVR — shared stylesheet
   One file. Previously this block was duplicated byte-identical
   across all 7 pages, so a token change was a 7-file edit.

   System derived from the Shovel corpus (199 reels):
   - 4px base unit                        (A24 563k, Marcello 19k)
   - body min 14px, 16px legibility floor (Domino's 32k, Gap 18k)
   - one display family, weight carries hierarchy (Prada 435k)
   - one display moment per page          (Denim Tears 37k)
   - 1-2 step animation ceiling           (Post Archive Faction 67k)
   - "massive gaps between the core elements, then little to
      nothing when they actually meet"   (Homer 793k)
   ============================================================ */

@font-face {
  font-family: "BringBold Nineties";
  src: url("../static/fonts/BringBold-Nineties.woff2") format("woff2");
  font-weight: 400 900;
  font-display: swap;
}

:root {
  /* colour — 6 tokens. was 13 near-blacks plus strays. */
  --bg:     #fafafa;
  --fg:     #0a0a0a;
  --ink:    #121212;   /* the single near-black for dark sections */
  --muted:  #6a6a6a;
  --line:   #e5e5e5;
  --accent: #5BC2E7;

  /* families */
  --display: "BringBold Nineties", "Anton", Impact, "Arial Black", sans-serif;
  --body:    -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
  --mono:    ui-monospace, "SF Mono", Menlo, monospace;

  /* type scale — 8 steps. was 96 sizes including 8.5px and 14.5px. */
  --t-label: 12px;
  --t-small: 14px;
  --t-body:  16px;
  --t-lead:  20px;
  --t-h3:    24px;
  --t-h2:    32px;
  --t-h1:    clamp(40px, 6vw, 72px);
  --t-hero:  clamp(48px, 11vw, 168px);

  /* spacing — every value a multiple of 4 */
  --s1: 4px;  --s2: 8px;   --s3: 12px;  --s4: 16px;
  --s5: 24px; --s6: 32px;  --s7: 48px;  --s8: 64px;
  --s9: 96px; --s10: 128px; --s11: 160px;

  /* containers — 3. was 25. */
  --c-narrow: 720px;
  --c-mid:   1100px;
  --c-wide:  1440px;

  /* motion — one curve */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --dur:  0.6s;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; scroll-padding-top: 80px; }
body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--body);
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
a { color: inherit; text-decoration: none; }
img, video { display: block; max-width: 100%; }
::selection { background: var(--accent); color: #000; }

/* ---------- primitives ---------- */
.wrap   { width: 100%; max-width: var(--c-mid);    margin-inline: auto; padding-inline: var(--s5); }
.wrap-w { width: 100%; max-width: var(--c-wide);   margin-inline: auto; padding-inline: var(--s5); }
.wrap-n { width: 100%; max-width: var(--c-narrow); margin-inline: auto; padding-inline: var(--s5); }

.section      { padding-block: var(--s11); }
.section-tight{ padding-block: var(--s9); }
.dark { background: var(--ink); color: #fff; }
.dark .label { color: rgba(255,255,255,.5); }
.dark .rule  { border-color: rgba(255,255,255,.14); }

.label {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.dot { color: var(--accent); }

.display {
  font-family: var(--display);
  font-weight: 900;
  line-height: 0.88;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}
h1, h2, h3, h4 { font-weight: 400; line-height: 1.15; }
.lead { font-size: var(--t-lead); line-height: 1.5; }
.small{ font-size: var(--t-small); }
.muted{ color: var(--muted); }

/* THE SIGNATURE DEVICE — one idea, run system-wide.
   pupil / period / list marker / hover state. */
.pip {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

/* section header: label left, link right, rule under */
.head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s5); flex-wrap: wrap;
  padding-bottom: var(--s5);
  border-bottom: 1px solid var(--line);
}
.rule { border-bottom: 1px solid var(--line); }

/* ---------- nav ---------- */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--s4) var(--s5);
  mix-blend-mode: difference;
}
.nav a {
  font-family: var(--mono); font-size: var(--t-label);
  letter-spacing: 0.1em; text-transform: uppercase; color: #fff;
}
.nav-links { display: flex; gap: var(--s5); }

/* below the breakpoint the five links overran the viewport and the last
   one (Contact) was clipped off-screen entirely, so it could not be
   tapped. tightened rather than collapsed into a burger: five items is
   few enough to keep visible, and a menu button would hide the one link
   that matters most. */
@media (max-width: 767px) {
  .nav { padding-inline: var(--s4); gap: var(--s3); }
  .nav a { font-size: 11px; letter-spacing: 0.06em; }
  .nav-links { gap: var(--s3); }
}

/* ---------- buttons ----------
   solid = primary ("solid black gets more clicks", Prada 435k)
   never more than two key actions in a view (TextFlow 6k) */
.cta {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-family: var(--mono); font-size: var(--t-label);
  letter-spacing: 0.1em; text-transform: uppercase;
  color: #fff; background: var(--fg);
  padding: var(--s3) var(--s5);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.cta:hover { background: var(--accent); color: #000; }
.cta-light { color: #000; background: #fff; }
.cta-light:hover { background: var(--accent); }

/* ---------- page hero (non-home) ---------- */
.phero { padding-block: var(--s10) var(--s9); }
.phero h1 { font-size: var(--t-h1); letter-spacing: -0.02em; }
.phero .display { font-size: var(--t-h1); }

/* ---------- index rows (work list, service list) ---------- */
.row {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  align-items: center;
  gap: var(--s4);
  padding-block: var(--s5);
  border-bottom: 1px solid var(--line);
  transition: padding-left var(--dur) var(--ease);
}
.row:hover { padding-left: var(--s3); }
.row-idx  { font-family: var(--mono); font-size: var(--t-label); color: var(--muted); }
.row-name { font-size: var(--t-h3); letter-spacing: -0.01em; }
.row-meta { font-family: var(--mono); font-size: var(--t-label); color: var(--muted); letter-spacing: 0.06em; text-transform: uppercase; }
.row .pip { opacity: 0; transition: opacity var(--dur) var(--ease); }
.row:hover .pip { opacity: 1; }

/* a row that is not a link. the hover shift promises navigation, so on a
   static list it is a lie. used by the services lanes and the studio rules. */
.lane { cursor: default; }
.lane:hover { padding-left: 0; }
.lane .pip { opacity: 1; }

/* hover-peek image — the one interactive flourish */
.peek {
  position: fixed; top: 0; left: 0;
  width: 300px; height: 380px;
  object-fit: cover;
  pointer-events: none; opacity: 0; z-index: 40;
  transform: translate(-50%, -50%);
  transition: opacity 0.35s var(--ease);
}
.peek.on { opacity: 1; }

/* hovering one row drops the rest back, so the list reads as a focus
   rather than a menu. pointer devices only, JS adds the classes. */
[data-peek-list] .row { transition: padding-left var(--dur) var(--ease), opacity 0.4s var(--ease); }
[data-peek-list].dimmed .row { opacity: 0.3; }
[data-peek-list].dimmed .row.lit { opacity: 1; }

/* ---------- full-bleed image band ----------
   one large picture running the full width, type sat on top, the frame
   drifting against the scroll. the moment of scale the light pages
   otherwise never get. */
.band { position: relative; overflow: hidden; background: var(--ink); isolation: isolate; }
.band-media { position: absolute; inset: 0; z-index: 0; }
.band-media img, .band-media video { width: 100%; height: 100%; object-fit: cover; will-change: transform; }
.band::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(90deg, rgba(0,0,0,.72) 0%, rgba(0,0,0,.35) 45%, rgba(0,0,0,.1) 100%);
}
.band-inner { position: relative; z-index: 2; padding-block: var(--s11); color: #fff; }
.band-inner .label { color: rgba(255,255,255,.6); }
@media (max-width: 767px) {
  .band::after { background: linear-gradient(180deg, rgba(0,0,0,.35) 0%, rgba(0,0,0,.75) 100%); }
}

/* ---------- step grid (process) ---------- */
.steps { display: grid; gap: var(--s5); grid-template-columns: 1fr; margin-top: var(--s7); }
.step img { width: 100%; aspect-ratio: 4/5; object-fit: cover; background: var(--line); }
.step-meta { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s3); }
.step h3 { font-size: var(--t-body); margin-top: var(--s1); }
.step p  { font-size: var(--t-small); color: var(--muted); margin-top: var(--s1); }

/* ---------- campaign gallery ----------
   deliberately uneven. one full-bleed anchor, then mixed spans and
   ratios, so the page reads as a shoot rather than a product grid.
   "vary card treatments, mixed aspect ratios, a hero" */
.gal { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s3); }
.gal figure { margin: 0; }
.gal img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gal figcaption { margin-top: var(--s3); }
.gal .g-wide { grid-column: 1 / -1; }
/* the wide anchor is a portrait composition, so keep it tall enough that the
   figure survives the crop and bias the frame downward to where they stand */
.gal .g-wide img { aspect-ratio: 3/2; object-position: 50% 62%; }
.gal .g-tall img { aspect-ratio: 3/4; }
.gal .g-sq img   { aspect-ratio: 1/1; }

@media (min-width: 768px) {
  .gal { grid-template-columns: repeat(6, 1fr); gap: var(--s4); }
  .gal .g-wide { grid-column: span 6; }
  .gal .g-2    { grid-column: span 2; }
  .gal .g-3    { grid-column: span 3; }
  .gal .g-4    { grid-column: span 4; }
}

/* ---------- engagement baseline grid ----------
   the five stages of working together. used twice: summarised on the
   homepage, and as part one of the process page. one definition so the
   two cannot drift apart. deliverable named per stage is the bar the
   benchmark sets (Exact's pipeline section). */
.way-grid { display: grid; gap: var(--s5); grid-template-columns: 1fr; margin-top: var(--s7); }
/* flex column so the deliverable is pushed to the bottom of every card and
   the five OUT: lines sit on one baseline regardless of copy length */
.way-step { border-top: 1px solid var(--line); padding-top: var(--s4); display: flex; flex-direction: column; }
.way-step .label { display: inline-flex; align-items: center; gap: var(--s2); }
.way-step h3 { font-size: var(--t-body); margin-top: var(--s2); }
.way-step p:not(.label):not(.way-out) { font-size: var(--t-small); color: var(--muted); margin-top: var(--s2); }
.way-out { font-family: var(--mono); font-size: var(--t-label); letter-spacing: .06em; text-transform: uppercase; margin-top: auto; padding-top: var(--s4); }

/* ---------- swatches ---------- */
.swatches { display: flex; flex-wrap: wrap; gap: var(--s2); }
.sw { width: 40px; height: 40px; flex: none; }

/* ---------- split ---------- */
.split { display: grid; gap: var(--s8); align-items: center; }

/* ---------- footer ---------- */
.foot { border-top: 1px solid var(--line); padding-block: var(--s6); }
.foot-row { display: flex; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; }

/* ---------- reveals ----------
   visible by default; only hidden once JS confirms it can reveal them,
   so a JS failure degrades to a static page rather than a blank one. */
.js .rv { opacity: 0; transform: translateY(12px); transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
.js .rv.in { opacity: 1; transform: none; }

/* ============================================================
   MOTION AND LAYOUT KIT
   Added deliberately above the original two-effect ceiling. The
   ceiling was there to stop decorative motion; everything below is
   structural (it reveals type and imagery in reading order) and
   every piece degrades to a static page without JS or under
   prefers-reduced-motion.
   ============================================================ */

/* --- line mask reveal. display type rises out of a mask, one line at a
   time. the craft-tier signature (Locomotive, Obys). needs the extra
   markup: .ln wrapper per line, inner span carries the transform. --- */
/* the mask itself is standalone, so the hero can drive it with a load
   animation while everything else drives it from the scroll observer */
.ln { display: block; overflow: hidden; padding-bottom: 0.08em; margin-bottom: -0.08em; }
.js .rv-lines .ln > span { display: block; transform: translateY(110%); transition: transform 0.95s var(--ease); }
.js .rv-lines.in .ln > span { transform: none; }
.js .rv-lines .ln:nth-child(2) > span { transition-delay: 0.07s; }
.js .rv-lines .ln:nth-child(3) > span { transition-delay: 0.14s; }
.js .rv-lines .ln:nth-child(4) > span { transition-delay: 0.21s; }

/* --- image reveal. the frame opens upward and the picture settles back
   from a slight push-in. wrapper clips, image scales. --- */
.rv-img { overflow: hidden; }
.js .rv-img { clip-path: inset(0 0 100% 0); transition: clip-path 1.1s var(--ease); }
.js .rv-img.in { clip-path: inset(0 0 0 0); }
.js .rv-img > img, .js .rv-img > video { transform: scale(1.12); transition: transform 1.6s var(--ease); }
.js .rv-img.in > img, .js .rv-img.in > video { transform: none; }
/* when a frame is also parallaxed, JS owns the transform, so hold the
   overscale permanently and let the reveal be the clip alone */
.js .rv-img > [data-para] { transform: none; transition: none; }

/* --- stagger. children of a .rv-stag reveal in sequence. --- */
.js .rv-stag > * { opacity: 0; transform: translateY(16px); transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
.js .rv-stag.in > * { opacity: 1; transform: none; }
.js .rv-stag.in > *:nth-child(1) { transition-delay: 0s; }
.js .rv-stag.in > *:nth-child(2) { transition-delay: 0.07s; }
.js .rv-stag.in > *:nth-child(3) { transition-delay: 0.14s; }
.js .rv-stag.in > *:nth-child(4) { transition-delay: 0.21s; }
.js .rv-stag.in > *:nth-child(5) { transition-delay: 0.28s; }
.js .rv-stag.in > *:nth-child(6) { transition-delay: 0.35s; }

/* --- marquee. a running line of hard facts. benchmark differentiator
   (Rejouice's ticker). track is duplicated in the markup so -50%
   lands seamlessly. --- */
.marquee { overflow: hidden; border-block: 1px solid var(--line); padding-block: var(--s5); }
.dark .marquee { border-color: rgba(255,255,255,.14); }
.marquee-track { display: flex; width: max-content; gap: var(--s7); align-items: center; }
.js .marquee-track { animation: marq 44s linear infinite; }
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-track > span {
  display: inline-flex; align-items: center; gap: var(--s4); white-space: nowrap;
  font-family: var(--mono); font-size: var(--t-label);
  letter-spacing: 0.1em; text-transform: uppercase;
}
@keyframes marq { to { transform: translateX(calc(-50% - var(--s7) / 2)); } }

/* --- feature block. one image, one column of text, alternating side.
   this is what breaks the five-identical-pages skeleton. --- */
.feature { display: grid; gap: var(--s6); align-items: center; }
.feature-media { position: relative; }
.feature-media img, .feature-media video { width: 100%; height: 100%; object-fit: cover; }

/* --- oversized index numeral, used as a graphic element --- */
.bignum {
  font-family: var(--display); font-weight: 900;
  font-size: clamp(56px, 10vw, 150px); line-height: 0.8;
  letter-spacing: -0.02em; color: var(--line);
}
.dark .bignum { color: rgba(255,255,255,.13); }

/* --- sticky section label, holds position while its column scrolls --- */
.sticky-l { position: sticky; top: 96px; align-self: start; }

/* --- second line of the meta cell: the real count of pieces published
   on that project page. dated-index convention (Adoratorio). --- */
.row-facts { display: block; margin-top: var(--s1); color: var(--fg); }
.dark .row-facts { color: #fff; }

/* --- a row that carries its own thumbnail. this is the work index on
   mobile, where the cursor peek cannot exist and the list would
   otherwise be six lines of text. --- */
.row-thumb {
  display: block; grid-column: 1 / -1;
  width: 100%; aspect-ratio: 16/10; object-fit: cover;
  background: var(--line); margin-top: var(--s4);
}

/* --- available to assistive tech, never painted --- */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- hover kit ----------
   every interactive element answers the cursor. all colour or transform
   transitions, nothing layout-shifting. */

/* nav: a hairline draws in under the link */
.nav-links a { position: relative; }
.nav-links a::after {
  content: ""; position: absolute; left: 0; right: 100%; bottom: -4px;
  height: 1px; background: #fff;
  transition: right 0.35s var(--ease);
}
.nav-links a:hover::after { right: 0; }

/* label links: colour lifts, the arrow steps forward */
a.label { transition: color 0.3s var(--ease); }
a.label:hover { color: var(--fg); }
.dark a.label:hover, .band a.label:hover { color: #fff; }
a.label .dot { display: inline-block; transition: transform 0.3s var(--ease); }
a.label:hover .dot { transform: translateX(4px); }

/* index rows: the numeral takes the accent while the row slides */
.row-idx { transition: color 0.3s var(--ease); }
.row:hover .row-idx { color: var(--accent); }

/* footer */
.foot a { transition: color 0.3s var(--ease); }
.foot a:hover { color: var(--fg); }

/* engagement stages: the rule above the stage takes the accent */
.way-step { transition: border-color 0.4s var(--ease); }
.way-step:hover { border-top-color: var(--accent); }

/* ---------- directional reveals ----------
   rv-l / rv-r slide in from the side. used on split columns so the two
   halves of a section arrive toward each other. */
.js .rv-l { opacity: 0; transform: translateX(-24px); transition: opacity 0.7s var(--ease), transform 0.8s var(--ease); }
.js .rv-r { opacity: 0; transform: translateX(24px);  transition: opacity 0.7s var(--ease), transform 0.8s var(--ease); }
.js .rv-l.in, .js .rv-r.in { opacity: 1; transform: none; }

/* ---------- gallery: stagger and hover captions ----------
   frames arrive in sequence, and each carries its project name on hover
   (always visible on touch, where there is no hover). */
.js .gal figure:nth-child(3n+2) .rv-img,
.js .gal figure:nth-child(3n+2) .rv-img > img { transition-delay: 0.08s; }
.js .gal figure:nth-child(3n) .rv-img,
.js .gal figure:nth-child(3n) .rv-img > img { transition-delay: 0.16s; }
.gal figure { position: relative; overflow: hidden; }
.gal figure img { transition: transform 1.2s var(--ease); }
.gal figure:hover img { transform: scale(1.05); }
.gcap {
  position: absolute; left: var(--s3); bottom: var(--s3); z-index: 2;
  font-family: var(--mono); font-size: var(--t-label);
  letter-spacing: 0.1em; text-transform: uppercase;
  color: #fff; background: rgba(10,10,10,.66);
  padding: var(--s1) var(--s3);
  opacity: 0; transform: translateY(6px);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
  pointer-events: none;
}
.gal figure:hover .gcap { opacity: 1; transform: none; }
@media (hover: none) { .gcap { opacity: 1; transform: none; } }

/* --- media card, used for the service lanes and studio --- */
.card-media { overflow: hidden; background: var(--line); }
.card-media img, .card-media video { width: 100%; height: 100%; object-fit: cover; transition: transform 1.2s var(--ease); }
a:hover > .card-media img, a:hover > .card-media video,
.card:hover .card-media img, .card:hover .card-media video { transform: scale(1.05); }

@media (min-width: 768px) {
  .feature { grid-template-columns: 1fr 1fr; gap: var(--s9); }
  .feature-wide { grid-template-columns: 5fr 4fr; }
  .feature.flip .feature-media { order: 2; }
  .row-thumb { display: none; }
}

/* ---------- breakpoint: one. was 16. ---------- */
@media (min-width: 768px) {
  .wrap, .wrap-w, .wrap-n { padding-inline: var(--s7); }
  .nav   { padding: var(--s5) var(--s7); }
  .steps { grid-template-columns: repeat(4, 1fr); }
  .split { grid-template-columns: 1fr 1fr; }
  .row   { grid-template-columns: 40px 1fr 1fr auto; }
  .row-name { font-size: var(--t-h2); }
  .way-grid { grid-template-columns: repeat(5, 1fr); gap: var(--s4); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
  .js .rv { opacity: 1; transform: none; }
  .peek { display: none; }
}

/* =====================================================================
   THEME LINK (2026-07-29) — the case pages read as a different site:
   the display face never appeared below the hero (section heads were
   12px mono labels) and nothing sat on the centred axis the rest of the
   site uses. These overrides pull them into the family. Appended last so
   they win on order inside this file; hero rules carry extra specificity
   because the hero styles live inline in each page.
   ===================================================================== */

/* Section heads: mono eyebrow above, BringBold headline below, centred. */
.head {
  flex-direction: column-reverse;
  align-items: center; justify-content: center;
  text-align: center; gap: var(--s3);
  border-bottom: 0;
}
.head h2.label {
  font-family: var(--display); font-weight: 900; text-transform: uppercase;
  font-size: clamp(34px, 4.6vw, 72px); line-height: 0.92; letter-spacing: -0.02em;
  color: var(--fg);
}
.dark .head h2.label, .band .head h2.label { color: #fff; }
/* the eyebrow gets the site's chapter-marker square */
.head span.label { display: inline-flex; align-items: center; gap: 10px; }
.head span.label::before {
  content: ''; width: 7px; height: 7px; background: var(--accent); flex: 0 0 auto;
}

/* Standing copy and fact rows sit on the axis. */
.lead { margin-inline: auto; text-align: center; }
.facts { text-align: center; }

/* Hero: centred on the axis, headline at full display scale. */
header.case-hero .case-hero-inner {
  align-items: center; text-align: center;
}
header.case-hero h1.display { font-size: var(--t-hero); }
