/* ============================================================
   JXP V2.0 — shared project-page stylesheet
   Extracted from the Cadillac page once a second project page
   existed. The index page still carries its own tokens; merging
   those is a separate job.
   ============================================================ */
/* ============================================================
   JXP V2.0 — PROJECT PAGE — Cadillac
   Tokens are lifted from the index page deliberately.
   When the remaining three project pages land, this block and the
   home page's should be extracted to assets/site.css — doing it
   now would mean refactoring twice.
   ============================================================ */
:root{
  --ground:#100F0D; --rise:#1A1815; --rise-2:#232019; --hair:#2E2A24;
  --clay-05:#F2EFE9; --clay-20:#D6D1C7; --clay-40:#A9A399; --clay-60:#726C63;
  --mark:#FF3D20;

  --display:'Archivo', system-ui, sans-serif;
  --prose:'Instrument Sans', system-ui, sans-serif;
  --data:'JetBrains Mono', ui-monospace, monospace;
  --head-wdth:108;

  --gutter:clamp(1.25rem,4vw,4.5rem);
  --measure:1640px;
}
*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; scroll-behavior:smooth; }
@media (prefers-reduced-motion: reduce){ html{ scroll-behavior:auto; } }

body{
  margin:0; background:var(--ground); color:var(--clay-05);
  font-family:var(--prose); font-size:clamp(1rem,.95rem + .25vw,1.0625rem);
  line-height:1.6; -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
img,video{ display:block; max-width:100%; }
a{ color:inherit; }
::selection{ background:var(--mark); color:#fff; }
:focus-visible{ outline:2px solid var(--mark); outline-offset:3px; }

.shell{ width:100%; max-width:var(--measure); margin-inline:auto; padding-inline:var(--gutter); }
.data{ font-family:var(--data); font-size:.6875rem; font-weight:400; letter-spacing:.13em; text-transform:uppercase; }
.skip{ position:absolute; left:-9999px; background:var(--mark); color:#fff; padding:.75rem 1.25rem; z-index:999; }
.skip:focus{ left:1rem; top:1rem; }

.grain{
  position:fixed; inset:0; z-index:2; pointer-events:none; opacity:.16;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.5'/></svg>");
  mix-blend-mode:overlay;
}

/* ---- masthead ---- */
.masthead{
  position:fixed; inset:0 0 auto 0; z-index:100;
  max-width:var(--measure); margin-inline:auto;
  display:flex; align-items:center; justify-content:space-between; gap:1rem;
  padding:1.05rem var(--gutter); mix-blend-mode:difference; color:#fff;
}
.lockup{ font-family:var(--display); font-variation-settings:'wght' 700,'wdth' 108; font-size:.875rem; text-decoration:none; }
.lockup span{ opacity:.45; font-variation-settings:'wght' 400,'wdth' 100; }
.nav{ display:flex; gap:clamp(.85rem,2vw,2.1rem); }
.nav a{ text-decoration:none; position:relative; padding-block:.25rem; }
.nav a::after{ content:''; position:absolute; left:0; bottom:0; height:1px; width:0; background:currentColor; transition:width .35s cubic-bezier(.2,.7,.3,1); }
.nav a:hover::after,.nav a:focus-visible::after{ width:100%; }

/* ============================================================
   HERO — full screen, peels to the process plate underneath
   ============================================================ */
.hero{
  position:relative; height:100svh; min-height:520px; overflow:hidden;
  /* default, not ew-resize and not pointer: the camera reveals on
     hover with nothing to drag, and unlike an index plate the hero
     is not wrapped in a link, so there is nowhere for a click to go.
     ew-resize is left over from the peel, where dragging across the
     surface drove the wipe. */
  cursor:default; touch-action:pan-y; border:0; padding:0; width:100%;
  background:var(--ground);
}
.hero .layer{ position:absolute; inset:0; }

/* UNDER — the real breakdown. `contain`, not `cover`: this is a
   sheet of panels and cropping it destroys the information it
   exists to carry. It sits on the ground like a plate on a bench. */
.hero .under{
  background:
    linear-gradient(0deg, rgba(255,255,255,.028) 1px, transparent 1px) 0 0/100% 2.6rem,
    linear-gradient(90deg, rgba(255,255,255,.028) 1px, transparent 1px) 0 0/2.6rem 100%,
    radial-gradient(70% 60% at 50% 45%, #26231E 0%, #131210 100%);
  display:grid; place-items:center; padding:clamp(3.5rem,9vh,7rem) var(--gutter);
}
.hero .under img{ width:100%; height:auto; max-height:100%; object-fit:contain; }

/* Opt-in for under layers that are full-bleed imagery rather than a
   sheet of panels. `contain` plus padding letterboxes the image on the
   dark ground, which is right for a breakdown grid but leaves a black
   frame around a photograph or a drawing — and a frame kills the
   effect, because the two layers no longer register. */
.hero--fill .under{ display:block; padding:0; background:none; }
.hero--fill .under img{ width:100%; height:100%; max-height:none; object-fit:cover; }

/* OVER — the finished frame. It fades rather than wipes: the camera
   cross-dissolves it, and the peel that used to clip it away from the
   right is gone, as is the ripple that shared this behaviour. */
.hero .over{
  transition:opacity .22s cubic-bezier(.4,0,.2,1);
  background:radial-gradient(66% 58% at 50% 45%, #5E5850 0%, #34302A 52%, #17150F 100%);
}
.hero .over img{ width:100%; height:100%; object-fit:cover; }
.hero .over video{ width:100%; height:100%; object-fit:cover; display:block; }

/* ------------------------------------------------------------
   CAMERA SURFACES
   A hero the camera has claimed behaves differently from a peel
   hero: the under layer is a viewport that clips one large moving
   capture, so the grid centring and the padding above — right for
   a static breakdown plate resting on the ground — have to go, or
   they fight the transform. The over layer fades rather than
   wiping, and the peel it replaced is gone entirely.
   ------------------------------------------------------------ */
.hero[data-cam] .under{
  display:block; padding:0; background:#15130F; overflow:hidden;
}
.hero[data-cam] .under img.cam-img{
  position:absolute; top:0; left:0; transform-origin:0 0;
  /* max-height must be none for the same reason max-width is. The
     peel rule above caps both so a breakdown plate letterboxes on
     the ground, but camera.js sets an exact cover-fit width AND
     height, and a surviving max-height clamps the height without
     touching the width — stretching the capture horizontally by the
     hero's aspect. The index plate never had the cap, which is why
     the same image looked right there and wrong here. */
  width:auto; height:auto; max-width:none; max-height:none;
  object-fit:initial;
  will-change:transform,filter; backface-visibility:hidden;
}
.hero[data-cam].is-live .over{ opacity:0; }
/* The legibility scrim sits on the hero itself, above the reveal
   canvas and below the copy. On the over layer it vanished the moment
   the canvas took the surface over, dropping the title onto raw
   image. */
.hero::after{
  content:''; position:absolute; inset:0; z-index:4; pointer-events:none;
  background:linear-gradient(180deg, rgba(16,15,13,.66) 0%, rgba(16,15,13,0) 30%, rgba(16,15,13,.1) 55%, rgba(16,15,13,.93) 100%);
}

.hero-copy{
  position:absolute; left:0; right:0; bottom:0; z-index:6; pointer-events:none;
  padding-bottom:clamp(1.5rem,4vh,2.5rem);
}
.hero-copy h1{
  margin:0 0 1rem; font-family:var(--display);
  font-variation-settings:'wght' 800,'wdth' var(--head-wdth);
  font-size:clamp(1.9rem,6.6vw,6.4rem); line-height:.9;
  letter-spacing:-.028em; text-transform:uppercase;
}
.hero-meta{
  display:flex; flex-wrap:wrap; gap:.5rem 2.2rem;
  padding-top:1rem; border-top:1px solid rgba(242,239,233,.18); color:var(--clay-20);
}
.hero-meta b{ color:var(--clay-60); font-weight:400; margin-right:.5rem; }
.hero-hint{
  position:absolute; right:var(--gutter); top:calc(50% - 1rem); z-index:6;
  padding:.45rem .7rem; background:rgba(16,15,13,.72); color:var(--clay-20);
  pointer-events:none; transition:opacity .3s ease;
}
.hero.is-live .hero-hint{ opacity:0; }
.under-label{
  /* clear of the masthead — at 2.6rem it sat directly under CONTACT */
  position:absolute; right:var(--gutter); top:clamp(4.75rem,10vh,7rem); z-index:6;
  color:var(--mark); opacity:0; transition:opacity .3s ease; pointer-events:none;
}
.hero.is-live .under-label{ opacity:1; }

/* ============================================================
   SECTION TYPES

   A project page is an assembly of these. None of them knows which
   project it is on — no type may carry content-specific styling, or
   the next page cannot reuse it. Order and combination are the page's
   business; appearance is this file's.

   SECTIONS (outer <section>)
     .block            padded section, the default wrapper
     .tint             full-bleed raised ground; put a .shell.block inside
     .pull             a quote, ruled top and bottom
     .tail             footer nav

   BLOCKS (inside a section, any order, any number)
     .band             heading + kicker, ruled underneath
     .two-col          lede beside body copy
     .fig              one image + caption, content width
     .fig--bleed         ... spanning the viewport instead
     .grid             image grid — .grid--1 / --2 / --3 (3 is default)
     .stack            figures stacked vertically, each with a labelled caption
     .note             a small line of data type under the block above it

   Blocks are separated by one rhythm step automatically; nothing
   needs a margin of its own in the markup.
   ============================================================ */
.band{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:.6rem 1.6rem;
  padding-bottom:1.1rem; margin-bottom:clamp(1.6rem,4vh,2.6rem);
  border-bottom:1px solid var(--hair);
}
.band h2{
  margin:0; font-family:var(--display);
  font-variation-settings:'wght' 700,'wdth' var(--head-wdth);
  font-size:clamp(1.5rem,3.2vw,2.5rem); letter-spacing:-.02em; text-transform:uppercase;
}
.band .kicker{ color:var(--clay-60); }

/* Not `section.block`: .tint wraps a div.block, so a type selector
   left that one heading with no lead-in at all while every other
   heading had one. Top is deliberately larger than bottom — each
   section gets a run-up before its headline. */
.block{ padding-block:clamp(4.5rem,11vh,7.5rem) clamp(3.5rem,9vh,6.5rem); }

/* One rhythm step between blocks, set here rather than per instance.
   Every page used to carry the same inline margin-top on its grid;
   that is what this replaces. .band is exempt because it already
   ends with its own margin-bottom. */
.block > * + *{ margin-top:clamp(2rem,5vh,3.5rem); }
.block > .band + *{ margin-top:0; }

/* A text row is two columns of unequal height: the headline ends high
   and the paragraph runs on, so whatever follows meets the longer
   column, not the row. One rhythm step reads as rhythm between two
   media blocks and as a collision under a last line of body copy.
   What follows a text row gets a step and a half instead.
   This also has to out-rank .fig, which is why it is written as a
   sibling selector rather than a margin-bottom on .two-col: a
   margin-bottom would also push the Results and Overview rows that
   end their section, widening the gap between sections. */
.block > .two-col + *{ margin-top:clamp(3rem,7.5vh,5rem); }

/* The same step, restated for a figure that follows anything else.
   .fig{margin:0} resets the UA's 1em/40px figure margin, but it
   matches .block > * + * exactly on specificity and sits later in
   this file, so it was silently cancelling the step above it: every
   .fig after a text row sat flush against the paragraph. Three
   project pages shipped that way. Stated at a specificity .fig
   cannot reach, and excluding the two rows already handled above. */
.block > :not(.band,.two-col) + .fig{ margin-top:clamp(2rem,5vh,3.5rem); }

.two-col{ display:grid; gap:clamp(1.6rem,4vw,4rem); grid-template-columns:minmax(0,1fr) minmax(0,1.35fr); }
@media (max-width:860px){ .two-col{ grid-template-columns:1fr; } }
.lede{
  font-family:var(--display); font-variation-settings:'wght' 500,'wdth' var(--head-wdth);
  font-size:clamp(1.25rem,2.4vw,1.85rem); line-height:1.24; letter-spacing:-.018em; margin:0;
}
.lede em{ font-style:normal; color:var(--mark); }
.body p{ margin:0 0 1.1rem; color:var(--clay-20); max-width:64ch; }
.body p:last-child{ margin-bottom:0; }

/* A caption, so it sits close to what it describes rather than a
   full rhythm step away. */
.block > .note{ margin-top:.9rem; color:var(--clay-60); }

/* FIG — one image.
   Full shorthand, not margin-block: the UA stylesheet gives figure a
   40px inline margin, and setting only the block axis leaves it in
   place, insetting the image inside the grid everything else sits on. */
.fig{ margin:0; }
.fig img{ width:100%; height:auto; }
.fig figcaption{ margin-top:clamp(1rem,2.2vh,1.6rem); color:var(--clay-60); }
/* Breaks the shell to the full viewport. Margin, not width:100vw,
   which would overflow by the scrollbar's width. */
.fig--bleed{ margin-inline:calc(var(--gutter) * -1); }
@supports (width:100cqw){ .fig--bleed{ margin-inline:calc(50% - 50vw); } }

/* GRID — column count is a modifier, never a per-page override. */
.grid{ display:grid; gap:clamp(.6rem,1.4vw,1.1rem); grid-template-columns:repeat(3,1fr); }
.grid--1{ grid-template-columns:1fr; }
.grid--2{ grid-template-columns:repeat(2,1fr); }
.grid--3{ grid-template-columns:repeat(3,1fr); }
.grid img,.grid video{ width:100%; height:auto; background:var(--rise); display:block; }
/* Collapse in the same order whatever the starting count. */
@media (max-width:900px){ .grid,.grid--3{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:700px){ .grid--2{ grid-template-columns:1fr; } }
@media (max-width:560px){ .grid,.grid--3{ grid-template-columns:1fr; } }


.pull{
  padding-block:clamp(3.5rem,9vh,6.5rem);
  border-block:1px solid var(--hair);
}
.pull blockquote{
  margin:0; font-family:var(--display);
  font-variation-settings:'wght' 650,'wdth' var(--head-wdth);
  font-size:clamp(1.5rem,4.2vw,3.4rem); line-height:1.05;
  letter-spacing:-.024em; text-transform:uppercase; max-width:22ch;
}
.pull blockquote::before{ content:'\201C'; color:var(--mark); }
.pull blockquote::after{ content:'\201D'; color:var(--mark); }

/* TINT — a full-bleed ground for a section that should read as
   evidence rather than argument. Holds a .shell.block. */
.tint{ background:var(--rise); }

/* STACK — figures one under another, each captioned. The caption
   belongs to the image above it, so the gap below it stays clearly
   larger than the gap above, or it reads as a heading for the next. */
.stack figure{ margin:0 0 clamp(2.5rem,6vh,4rem); }
.stack figure:last-child{ margin-bottom:0; }
.stack img{ width:100%; height:auto; outline:1px solid var(--hair); outline-offset:-1px; }
.stack figcaption{ margin-top:clamp(1rem,2.2vh,1.6rem); color:var(--clay-40); max-width:70ch; }
.stack figcaption b{ display:block; font-family:var(--data); font-size:.625rem; letter-spacing:.13em; text-transform:uppercase; color:var(--mark); font-weight:400; margin-bottom:.25rem; }

/* footer nav */
.tail{ padding-block:clamp(3.5rem,9vh,6.5rem); }
.tail-row{
  display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:1rem;
  padding-block:1.2rem; border-top:1px solid var(--hair);
  text-decoration:none; transition:padding-left .3s cubic-bezier(.2,.7,.3,1), color .3s ease;
}
.tail-row:last-of-type{ border-bottom:1px solid var(--hair); }
.tail-row:hover,.tail-row:focus-visible{ padding-left:.7rem; color:var(--mark); }
.tail-row .t{ font-family:var(--display); font-variation-settings:'wght' 650,'wdth' var(--head-wdth); font-size:clamp(1.1rem,2.4vw,1.7rem); text-transform:uppercase; letter-spacing:-.02em; }
.colophon{ display:flex; flex-wrap:wrap; gap:.5rem 2rem; justify-content:space-between; margin-top:clamp(2rem,5vh,3rem); color:var(--clay-60); }
