/* ============================================================================
   AMYAR GHAFFARY — Projects · 2026
   File: projects.css
   Loads SECOND, after Amyar.portfolio.css (fonts, reset, tokens, back link).

   WHAT THIS FILE IS
   -----------------
   The whole /projects.html experience. It is deliberately self-contained: it
   does not need services.css or case.css, and nothing here leaks onto another
   page because every rule is scoped under `.pj-page`.

   THE MOTION SYSTEM — read this before editing any transform
   ----------------------------------------------------------
   projects.js writes two numbers onto each [data-chapter] section as the page
   scrolls:

     --p    0 → 1    how far this chapter has travelled across the viewport
     --pc  -1 → 1    the same value re-centred, so 0 is "dead centre"

   Every scroll-linked transform in this file is a pure function of those two
   numbers. Nothing animates on a timer, so motion is always tied to the user's
   own scrolling — that is what separates "cinematic" from "a page with a lot
   of animation on it".

   WHERE THE MOVEMENT LIVES
   In the frames, and only a little. Each one drifts by --drift × --pj-drift-k
   and holds the rotation it was composed with rather than turning as you
   scroll, so the cluster keeps the arrangement you see at rest. Depth comes
   from the four planes sitting at different --z and travelling at different
   rates, not from anything moving inside them.

   NOTHING IS EVER CROPPED
   The artwork on this page carries type — release titles, wordmarks, credits
   — and type that has been trimmed by a frame looks like a mistake, not a
   crop. So the image is never scaled past its container and never panned
   inside it; instead projects.js measures each image and writes its real
   ratio back to the frame as --ar. The --ar values in the compositions below
   are the fallback for an image that has not loaded, not the final shape.

   If JS never boots, `body` never gets `.pj-live`, and every scroll-linked
   rule below is skipped — the page renders as a clean static layout with all
   case studies open. Never write a rule that HIDES content outside `.pj-live`.
   ============================================================================ */

/* Registering these lets the browser interpolate them as real numbers rather
   than swapping strings, which is what makes the scene's pointer-lean ease
   back to centre instead of snapping. */
@property --p  { syntax: "<number>"; inherits: true;  initial-value: 0; }
@property --pc { syntax: "<number>"; inherits: true;  initial-value: 0; }
@property --sx { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --sy { syntax: "<number>"; inherits: false; initial-value: 0; }

/* ============================================================================
   1. PAGE SHELL
   ============================================================================ */
body.pj-page {
  min-height: 100vh;
  background: var(--bg);
  /* `overflow-x: hidden` on body turns it into a scroll container, which
     silently breaks every `position: sticky` on the page. `clip` does the same
     job — no horizontal scrollbar — without creating a scroll container. */
  overflow-x: clip;
}

.pj-page {
  --pj-edge:    rgba(255, 255, 255, 0.09);
  --pj-edge-2:  rgba(255, 255, 255, 0.17);
  --pj-fill:    linear-gradient(180deg, rgba(255,255,255,0.038), rgba(255,255,255,0.010));
  --pj-glass:   rgba(13, 13, 13, 0.58);
  --pj-shadow:  0 40px 90px -36px rgba(0, 0, 0, 0.9);
  --pj-r:       16px;
  --pj-r-lg:    26px;
  /* How much of each plane's composed --drift actually gets used. Held well
     under 1 so the cluster reads as one composition rather than four cards
     moving at four speeds. This is the only parallax on the scene now, so
     taking it much lower than this flattens the depth entirely. */
  --pj-drift-k: 0.45;
  --pj-wrap:    min(1320px, 100% - (var(--gutter) * 2));
}

.pj-main {
  position: relative;
  z-index: 1;
}

/* Every band of the page shares one measure. */
.pj-wrap {
  width: var(--pj-wrap);
  margin-inline: auto;
}

/* --- Atmosphere: three very slow drifting light fields ---------------------
   Fixed, so they read as the room the page sits in rather than as objects
   scrolling with it. Blur is expensive, so they are pinned at low resolution
   and never animate anything but transform. */
.pj-atmos {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.pj-atmos span {
  position: absolute;
  display: block;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.5;
  will-change: transform;
}
.pj-atmos .a1 {
  width: 60vw; height: 60vw;
  top: -22vw; right: -14vw;
  background: radial-gradient(circle, rgba(168,200,238,0.16), transparent 66%);
}
.pj-atmos .a2 {
  width: 52vw; height: 52vw;
  bottom: -18vw; left: -12vw;
  background: radial-gradient(circle, rgba(111,159,209,0.13), transparent 66%);
}
.pj-atmos .a3 {
  width: 38vw; height: 38vw;
  top: 42%; left: 46%;
  background: radial-gradient(circle, rgba(168,200,238,0.07), transparent 70%);
}
.pj-live .pj-atmos .a1 { animation: pj-float-a 26s ease-in-out infinite alternate; }
.pj-live .pj-atmos .a2 { animation: pj-float-b 32s ease-in-out infinite alternate; }
.pj-live .pj-atmos .a3 { animation: pj-float-a 40s ease-in-out infinite alternate reverse; }

@keyframes pj-float-a {
  from { transform: translate3d(-3%, -2%, 0) scale(1);    }
  to   { transform: translate3d( 4%,  3%, 0) scale(1.08); }
}
@keyframes pj-float-b {
  from { transform: translate3d( 2%,  3%, 0) scale(1.05); }
  to   { transform: translate3d(-4%, -2%, 0) scale(0.96); }
}

/* ============================================================================
   2. SHARED TYPE ATOMS
   ============================================================================ */
.pj-eyebrow {
  display: inline-block;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--accent-warm);
}

.pj-kicker {
  display: flex;
  align-items: center;
  gap: 14px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.pj-kicker::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--line-2), transparent);
}

/* ============================================================================
   3. HERO
   ============================================================================ */
.pj-hero {
  position: relative;
  min-height: 92vh;
  min-height: 92svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: clamp(140px, 20vh, 220px) clamp(60px, 10vh, 110px);
}

.pj-hero-in { position: relative; z-index: 1; }

.pj-title {
  margin-top: clamp(20px, 3vh, 30px);
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(52px, 11vw, 148px);
  line-height: 0.92;
  letter-spacing: -0.045em;
  color: var(--ink);
}
/* Each line is its own mask so the words can rise out of nothing. The tiny
   bottom padding stops descenders being sheared by the mask edge. */
.pj-tl {
  display: block;
  overflow: hidden;
  padding-bottom: 0.08em;
  perspective: 800px;
}
.pj-word { display: inline-block; }
.pj-title em {
  font-style: normal;
  font-weight: 300;
  color: var(--accent-cool);
}

.pj-live .pj-word {
  animation: pj-word-in 1.15s var(--ease-out) both;
}
.pj-live .pj-tl:nth-child(1) .pj-word:nth-child(1) { animation-delay: 0.10s; }
.pj-live .pj-tl:nth-child(1) .pj-word:nth-child(2) { animation-delay: 0.18s; }
.pj-live .pj-tl:nth-child(2) .pj-word:nth-child(1) { animation-delay: 0.26s; }
.pj-live .pj-tl:nth-child(2) .pj-word:nth-child(2) { animation-delay: 0.34s; }
@keyframes pj-word-in {
  from { transform: translate3d(0, 108%, 0) rotateX(-52deg); opacity: 0; }
  to   { transform: translate3d(0, 0, 0)    rotateX(0deg);   opacity: 1; }
}

.pj-lede {
  max-width: 58ch;
  margin-top: clamp(24px, 3.4vh, 36px);
  font-size: clamp(15px, 1.35vw, 18px);
  font-weight: 300;
  line-height: 1.7;
  color: var(--ink-3);
}

/* The lede now points at #equal. One link, quiet enough that it never
   competes with the headline above it. */
.pj-lede a {
  color: var(--ink-2);
  border-bottom: 1px solid var(--pj-edge-2);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.pj-lede a:hover { color: var(--ink); border-bottom-color: var(--accent-cool); }

/* Credits strip under the lede — hairline ruled, mono keys. */
.pj-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, auto));
  justify-content: start;
  gap: clamp(28px, 5vw, 72px);
  margin-top: clamp(36px, 6vh, 60px);
  padding-top: var(--space-6);
  border-top: 1px solid var(--line);
}
.pj-stats dt {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-4);
  margin-bottom: 9px;
}
.pj-stats dd {
  margin: 0;
  font-size: clamp(14px, 1.15vw, 16px);
  font-weight: 300;
  color: var(--ink-2);
}
.pj-stats dd b {
  font-weight: 400;
  color: var(--ink);
}

/* Scroll cue */
.pj-cue {
  position: relative;
  z-index: 1;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-top: clamp(44px, 7vh, 76px);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-4);
  transition: color var(--dur) var(--ease);
}
.pj-cue:hover { color: var(--ink-2); }
.pj-cue i {
  position: relative;
  display: block;
  width: 1px;
  height: 42px;
  background: var(--line-2);
  overflow: hidden;
}
.pj-cue i::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 40%;
  background: var(--accent-cool);
}
.pj-live .pj-cue i::after { animation: pj-cue 2.4s var(--ease) infinite; }
@keyframes pj-cue {
  0%   { transform: translateY(-100%); }
  60%  { transform: translateY(250%);  }
  100% { transform: translateY(250%);  }
}

/* ============================================================================
   4. FLAGSHIP CHAPTERS
   --------------------------------------------------------------------------
   One chapter = one tall track with a sticky 100vh stage inside it. The stage
   holds the copy column, the 3D scene, and a ghost number behind both. The
   track's extra height IS the scroll runway; shorten it and the choreography
   plays faster.
   ============================================================================ */
.pj-work { position: relative; }

.pj-chapter {
  position: relative;
  /* Hairline between chapters, drawn only where the content is. */
  border-top: 1px solid var(--line);
}
.pj-chapter:first-child { border-top: 0; }

.pj-track {
  position: relative;
  height: 210vh;
}

.pj-stage {
  position: sticky;
  top: 0;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  grid-template-columns: minmax(0, 0.88fr) minmax(0, 1.12fr);
  align-items: center;
  /* The grid has one row; without this the row parks at the top of a 100vh
     stage and leaves the whole lower half empty on tall viewports. */
  align-content: center;
  gap: clamp(32px, 5vw, 76px);
  padding-block: clamp(96px, 13vh, 150px) clamp(56px, 9vh, 104px);
}

/* --- the ghost number ----------------------------------------------------
   Bottom-left of the stage, behind everything. It sits in the one region the
   composition leaves empty, so it fills dead space instead of competing with
   the copy — and because it is anchored rather than centred, it reads as a
   chapter numeral rather than a stray shape floating in the gutter. */
.pj-ghost {
  position: absolute;
  left: -0.055em;
  bottom: -0.08em;
  z-index: 0;
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(150px, 19vw, 290px);
  line-height: 1;
  letter-spacing: -0.06em;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.075);
  pointer-events: none;
  user-select: none;
}
.pj-live .pj-ghost {
  transform: translate3d(calc(var(--pc) * 1.6vw), 0, 0);
}

/* --- copy column --------------------------------------------------------- */
.pj-copy {
  position: relative;
  z-index: 2;
  display: grid;
  gap: clamp(16px, 2.2vh, 24px);
  justify-items: start;
  padding-left: clamp(18px, 2.4vw, 34px);
}
/* Progress rail — fills as the chapter plays through. A quiet "you are here". */
.pj-copy::before {
  content: "";
  position: absolute;
  left: 0;
  top: 4px;
  bottom: 4px;
  width: 1px;
  background: var(--line);
}
.pj-copy::after {
  content: "";
  position: absolute;
  left: 0;
  top: 4px;
  height: calc(100% - 8px);
  width: 1px;
  background: linear-gradient(180deg, var(--accent-cool), var(--accent-deep));
  transform-origin: top;
  transform: scaleY(0);
}
.pj-live .pj-copy::after { transform: scaleY(clamp(0, calc(var(--p) * 1.35 - 0.12), 1)); }

/* The copy fades up on entry and holds — driven by scroll, not a timer. */
.pj-live .pj-copy > * {
  opacity: clamp(0, calc((var(--p) - 0.05) * 7), 1);
  transform: translate3d(0, calc((1 - clamp(0, calc((var(--p) - 0.05) * 7), 1)) * 22px), 0);
}

.pj-idx {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.24em;
  color: var(--accent-warm);
}
.pj-name {
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(38px, 5.2vw, 76px);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--ink);
}
.pj-name em {
  font-style: normal;
  font-weight: 300;
  color: var(--accent-cool);
}
.pj-lead {
  max-width: 44ch;
  font-size: clamp(14.5px, 1.15vw, 16.5px);
  font-weight: 300;
  line-height: 1.72;
  color: var(--ink-3);
}
.pj-lead strong { font-weight: 400; color: var(--ink-2); }

.pj-meta {
  display: grid;
  gap: 10px;
  width: 100%;
  max-width: 460px;
  margin-top: clamp(4px, 1vh, 10px);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}
.pj-meta > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
}
.pj-meta dt {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-4);
  white-space: nowrap;
}
.pj-meta dd {
  margin: 0;
  font-size: 13.5px;
  font-weight: 300;
  line-height: 1.45;
  color: var(--ink-2);
  text-align: right;
}

/* --- the 3D scene -------------------------------------------------------- */
.pj-scene {
  position: relative;
  z-index: 1;
  width: min(100%, 72vh);
  width: min(100%, 72svh);
  aspect-ratio: 1;
  justify-self: center;
  perspective: 1500px;
  transform-style: preserve-3d;
  /* Pointer tilt, set by projects.js. Zero until a mouse actually moves. */
  transform: rotateY(calc(var(--sx, 0) * 1deg)) rotateX(calc(var(--sy, 0) * 1deg));
  transition: transform 0.9s var(--ease-out);
}
.pj-scene.is-tracking { transition: none; }

.pj-plane {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  aspect-ratio: var(--ar, 4 / 5);
  margin: 0;
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r);
  overflow: hidden;
  background: linear-gradient(155deg, #1c1c1c, #0b0b0b);
  box-shadow: var(--pj-shadow);
  transform: translate3d(0, 0, calc(var(--z, 0) * 1px));
  transform-style: preserve-3d;
  backface-visibility: hidden;
}
/* The frame keeps the rotation it was composed with. Nothing here turns as you
   scroll; it only shifts, and only a little. */
.pj-live .pj-plane {
  transform:
    translate3d(0, calc(var(--pc) * var(--drift, 80) * var(--pj-drift-k) * -1px), calc(var(--z, 0) * 1px))
    rotateY(calc(var(--ry, 0) * 1deg))
    rotateX(calc(var(--rx, 0) * 1deg));
}
/* No scale, no transform. The frame is already the shape of this image, so
   `cover` has nothing to trim and every word in the artwork survives. */
.pj-plane img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* A single raking highlight so the planes read as physical surfaces. */
.pj-plane::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(150deg, rgba(255,255,255,0.10), transparent 42%);
  mix-blend-mode: overlay;
  pointer-events: none;
}
.pj-plane img.broken { display: none; }
.pj-plane.no-image::before {
  content: attr(data-fallback);
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 12px;
  text-align: center;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* --- plane compositions --------------------------------------------------
   Three hand-set arrangements so consecutive chapters do not feel identical.
   Kept in CSS (not inline styles) precisely so the mobile block near the
   bottom of this file can override every value at once. --x/--y/--w are
   percentages of the square scene; --z is px of real depth; --drift is how
   far the plane travels across the chapter.

   --ar is the ONLY value here that is provisional. projects.js replaces it
   with the image's true ratio once the file loads, and narrows --w if the
   taller frame would otherwise run past the bottom of the scene. The numbers
   below are what a visitor sees while the images are still downloading, and
   what they keep if an image never arrives. ------------------------------- */
.pj-scene[data-comp="a"] .pj-plane:nth-child(1) { --x:  2%; --y:  4%; --w: 50%; --ar: 4/5;   --z:  40; --drift:  90; --ry: -7; --rx:  2; }
.pj-scene[data-comp="a"] .pj-plane:nth-child(2) { --x: 54%; --y: 14%; --w: 44%; --ar: 3/4;   --z: -70; --drift: 150; --ry:  6; --rx: -2; }
.pj-scene[data-comp="a"] .pj-plane:nth-child(3) { --x: 22%; --y: 52%; --w: 42%; --ar: 16/11; --z: 130; --drift:  40; --ry: -3; --rx:  4; }
.pj-scene[data-comp="a"] .pj-plane:nth-child(4) { --x: 68%; --y: 62%; --w: 30%; --ar: 1/1;   --z:  10; --drift: 112; --ry:  8; --rx:  0; }

.pj-scene[data-comp="b"] .pj-plane:nth-child(1) { --x:  0%; --y: 10%; --w: 42%; --ar: 3/4;   --z:  80; --drift:  60; --ry:  5; --rx:  1; }
.pj-scene[data-comp="b"] .pj-plane:nth-child(2) { --x: 46%; --y:  0%; --w: 40%; --ar: 4/5;   --z: -40; --drift: 140; --ry: -6; --rx: -2; }
.pj-scene[data-comp="b"] .pj-plane:nth-child(3) { --x: 50%; --y: 56%; --w: 48%; --ar: 16/10; --z:  20; --drift: 100; --ry:  4; --rx:  3; }
.pj-scene[data-comp="b"] .pj-plane:nth-child(4) { --x:  6%; --y: 64%; --w: 32%; --ar: 1/1;   --z: 140; --drift:  30; --ry: -8; --rx:  0; }

.pj-scene[data-comp="c"] .pj-plane:nth-child(1) { --x:  4%; --y:  0%; --w: 44%; --ar: 1/1;   --z: -60; --drift: 130; --ry:  7; --rx: -1; }
.pj-scene[data-comp="c"] .pj-plane:nth-child(2) { --x: 52%; --y:  6%; --w: 44%; --ar: 4/5;   --z:  60; --drift:  50; --ry: -5; --rx:  2; }
.pj-scene[data-comp="c"] .pj-plane:nth-child(3) { --x:  0%; --y: 50%; --w: 46%; --ar: 4/3;   --z: 120; --drift:  80; --ry:  3; --rx:  3; }
.pj-scene[data-comp="c"] .pj-plane:nth-child(4) { --x: 52%; --y: 66%; --w: 40%; --ar: 3/2;   --z: -20; --drift: 150; --ry: -4; --rx: -3; }

/* ============================================================================
   5. THE EXPAND BAR
   ============================================================================ */
.pj-toggle {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 26px);
  width: 100%;
  padding: clamp(20px, 2.6vw, 30px) clamp(18px, 2.4vw, 30px);
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r);
  background: var(--pj-fill);
  color: var(--ink-2);
  text-align: left;
  transition:
    border-color var(--dur) var(--ease),
    background    var(--dur) var(--ease),
    color         var(--dur) var(--ease);
}
.pj-toggle:hover {
  border-color: var(--pj-edge-2);
  background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.018));
  color: var(--ink);
}
.pj-toggle:focus-visible {
  outline: none;
  border-color: var(--accent-warm);
  box-shadow: 0 0 0 3px rgba(168, 200, 238, 0.18);
}
.pj-toggle-k {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent-warm);
  white-space: nowrap;
}
.pj-toggle-t {
  flex: 1;
  font-size: clamp(14px, 1.3vw, 17px);
  font-weight: 300;
  letter-spacing: -0.01em;
}
/* Plus that becomes a minus. Two bars, one rotates away. */
.pj-toggle-i {
  position: relative;
  flex: none;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid var(--pj-edge);
  border-radius: 50%;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.pj-toggle:hover .pj-toggle-i { border-color: var(--pj-edge-2); }
.pj-toggle-i i {
  position: absolute;
  width: 11px;
  height: 1px;
  background: currentColor;
  transition: transform 0.5s var(--ease-out), opacity 0.4s var(--ease);
}
.pj-toggle-i i:last-child { transform: rotate(90deg); }
.pj-toggle[aria-expanded="true"] .pj-toggle-i i:last-child { transform: rotate(0deg); opacity: 0; }
.pj-toggle[aria-expanded="true"] {
  border-color: var(--pj-edge-2);
  color: var(--ink);
}

/* The bar sits in the page flow directly under its chapter. Spacing below it
   lives on `.pj-case-body`, never on `.pj-case` — a collapsed panel has to
   measure exactly zero or it leaves a ghost gap in the layout. */
.pj-chapter .pj-toggle { margin-top: clamp(6px, 1vw, 10px); }
.pj-chapter { padding-bottom: clamp(44px, 7vh, 92px); }

/* ============================================================================
   6. THE CASE PANEL — one component, used by all six projects
   --------------------------------------------------------------------------
   Collapsed height is animated with grid-template-rows 0fr → 1fr, which is
   the only way to transition to "whatever height the content turns out to be"
   without measuring it in JS. `visibility` is delayed to the end of the
   collapse so the contents leave the tab order the moment they are hidden.
   ============================================================================ */
.pj-case {
  display: grid;
  grid-template-rows: 0fr;
  visibility: hidden;
  transition:
    grid-template-rows 0.75s var(--ease-out),
    visibility 0s linear 0.75s;
}
.pj-case.is-open {
  grid-template-rows: 1fr;
  visibility: visible;
  transition:
    grid-template-rows 0.75s var(--ease-out),
    visibility 0s linear 0s;
}
/* No JS → nothing collapses. The case studies are simply all open. */
.pj-page:not(.pj-live) .pj-case {
  grid-template-rows: 1fr;
  visibility: visible;
}
.pj-case-in {
  overflow: hidden;
  min-height: 0;
}
.pj-case-body {
  margin-top: clamp(10px, 1.4vw, 16px);
  padding: clamp(28px, 4vw, 52px);
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r-lg);
  background:
    radial-gradient(ellipse at 15% -10%, rgba(168,200,238,0.055), transparent 55%),
    var(--pj-glass);
  backdrop-filter: blur(18px) saturate(120%);
  -webkit-backdrop-filter: blur(18px) saturate(120%);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s var(--ease) 0.1s, transform 0.7s var(--ease-out) 0.1s;
}
.pj-case.is-open .pj-case-body,
.pj-page:not(.pj-live) .pj-case-body {
  opacity: 1;
  transform: none;
}

.pj-case-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(28px, 4vw, 64px);
}
.pj-case-h {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-4);
  padding-bottom: 14px;
  margin-bottom: 20px;
  border-bottom: 1px solid var(--line);
}
.pj-case-col p {
  font-size: clamp(14px, 1.1vw, 15.5px);
  font-weight: 300;
  line-height: 1.78;
  color: var(--ink-3);
}
.pj-case-col p + p { margin-top: 14px; }
.pj-case-col strong { font-weight: 400; color: var(--ink-2); }
/* Site-wide convention: <em> means accent colour, never italics. Bai Jamjuree
   has no true italic, so the browser would synthesise a slanted one. */
.pj-case-col em { font-style: normal; color: var(--accent-cool); }

.pj-case-list { display: grid; gap: 16px; }
.pj-case-list li {
  position: relative;
  padding-left: 24px;
  font-size: clamp(14px, 1.1vw, 15.5px);
  font-weight: 300;
  line-height: 1.7;
  color: var(--ink-3);
}
.pj-case-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 11px;
  width: 12px;
  height: 1px;
  background: var(--accent-warm);
}
.pj-case-list b {
  display: block;
  font-weight: 400;
  color: var(--ink);
  margin-bottom: 3px;
}

/* Figures — for verifiable numbers only. Placeholders read as an em dash. */
.pj-figs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(12px, 1.6vw, 20px);
  margin-top: clamp(28px, 4vw, 44px);
}
.pj-fig {
  padding: clamp(20px, 2.6vw, 28px);
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r);
  background: linear-gradient(180deg, rgba(255,255,255,0.035), rgba(255,255,255,0.008));
}
.pj-fig-n {
  display: block;
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(30px, 3.6vw, 46px);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--ink);
}
.pj-fig-n small {
  font-size: 0.42em;
  font-weight: 300;
  color: var(--accent-cool);
  margin-left: 3px;
}
.pj-fig-l {
  display: block;
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* Gallery strip */
.pj-gal {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(10px, 1.4vw, 16px);
  margin-top: clamp(28px, 4vw, 44px);
}
/* The strip keeps a uniform square tile so the row stays a row, and the
   artwork sits complete inside it. Same rule as the scene above: this work
   has type in it, so nothing here crops. A square is the kindest single
   shape for a mix of covers, posters and stills. */
.pj-gal figure {
  position: relative;
  margin: 0;
  aspect-ratio: 1;
  padding: 10px;
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r);
  overflow: hidden;
  background: linear-gradient(155deg, #1a1a1a, #0b0b0b);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.pj-gal img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.pj-gal figure:hover {
  border-color: var(--pj-edge-2);
  background: linear-gradient(155deg, #222, #101010);
}
.pj-gal img.broken { display: none; }
.pj-gal figure.no-image::before {
  content: attr(data-fallback);
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 10px;
  text-align: center;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* Unused until a real client quote exists. Never fill it with an invented
   testimonial — drop the block instead. */
.pj-quote {
  margin: clamp(28px, 4vw, 44px) 0 0;
  padding-left: clamp(20px, 2.6vw, 30px);
  border-left: 1px solid var(--accent-deep);
  font-size: clamp(16px, 1.7vw, 21px);
  font-weight: 200;
  line-height: 1.6;
  letter-spacing: -0.02em;
  color: var(--ink-2);
}
.pj-quote cite {
  display: block;
  margin-top: 14px;
  font-family: var(--mono);
  font-size: 10px;
  font-style: normal;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* ============================================================================
   7. THE LIFETIME PROJECT — "EQUAL"
   ----------------------------------------------------------------------------
   Deliberately NOT built like the three chapters above it. Those are client
   engagements and share one component so they read as a set; this one is the
   director's own hundred-part documentary series and has to read as something
   else entirely the moment it enters the viewport.

   WHAT MAKES IT DIFFERENT
     · It is full-bleed. It is the only element on the page that is not inside
       `.pj-wrap`, so it spans the window and physically interrupts the rhythm
       of the chapters above it.
     · It has its own ground. The page is neutral black (#0a0a0a); this band
       sits on the series' own deep navy, with a slow crimson bloom in the
       upper left that appears nowhere else on the site. The blue is shared
       with the rest of the page on purpose, so the band reads as part of one
       site rather than a widget pasted into it. Tokens below.
     · It has a counter, not a case study. The focal point is 15 / 100 — the
       part that is finished measured against the part that is not.

   THE PALETTE (one blue, shared with the rest of the site)
     --eq-a    #A8C8EE   the page's own accent, the same blue chapters 01-03
                         run on. Every rule, label and accent in the band.
     --eq-a3   #D9E8FB   a lighter tint of it. The bright end of the meter
                         fill, the sheen on the CTA, the rail thumb.
     --eq-a2   #6F9FD1   its deeper partner (--accent-deep). Gradient
                         shoulders and hairlines. Never type: 4.0:1 here.
     --eq-ink  #031225   the band's ground, and the type ON the filled CTA.
   Crimson lives only in the blurred field high-left and the ground wash. It is
   atmosphere, never a foreground colour, so nothing has to stay legible on it,
   and together with the navy it now carries the whole separation from the
   chapters above.

   ONE COMPONENT, THREE GENUINELY DIFFERENT LAYOUTS
     phone   (<= 640)   counter stacks over the meter · facts 2x2 · episodes are
                        swipe cards that snap and bleed to both screen edges ·
                        crew becomes a credit list · CTA is two full-width blocks
     tablet  (<= 1180)  counter and meter side by side · facts 4-up · episodes
                        stay a snapping rail with ~2.5 cards in view · crew 2x2
     desktop (> 1180)   the rail becomes a grid — the five newest episodes sit
                        in one even row, no scrolling, staggered entrance

   MOTION
   Unlike the chapters, this section is not scroll-linked frame by frame. It
   reveals once, on entry, via `.is-in` written by projects.js, and the meter
   fills from zero as it arrives. Everything is gated behind `.pj-live`, so
   with no JS the band renders complete and already filled.
   ============================================================================ */
.eq {
  /* The blue is the page's own --accent-cool / --accent-deep, restated here as
     local tokens so the band can be retinted without touching the chapters. */
  --eq-a:      #a8c8ee;
  --eq-a2:     #6f9fd1;
  --eq-a3:     #d9e8fb;
  --eq-ink:    #031225;
  --eq-edge:   rgba(168, 200, 238, 0.24);
  --eq-edge-2: rgba(168, 200, 238, 0.46);
  --eq-tile:   linear-gradient(180deg, rgba(168,200,238,0.055), rgba(168,200,238,0.012));
  --eq-glass:  rgba(4, 16, 32, 0.62);

  position: relative;
  isolation: isolate;
  margin-top: clamp(70px, 11vh, 130px);
  padding-block: clamp(76px, 12vh, 148px);
  border-top: 1px solid var(--pj-edge);
  border-bottom: 1px solid var(--pj-edge);
  /* Crimson high left, teal low right, navy everywhere else — the reference
     lighting, painted into the ground so it survives even if the two blurred
     fields below are dropped for performance. */
  background:
    radial-gradient(80% 55% at 6% 0%,   rgba(146, 26, 62, 0.30), transparent 64%),
    radial-gradient(70% 60% at 96% 100%, rgba(111, 159, 209, 0.26), transparent 66%),
    linear-gradient(180deg, #04101f, var(--eq-ink) 46%, #04121f);
  /* Clips the two light fields below. Safe here — nothing inside is sticky. */
  overflow: hidden;
}

/* The band's own atmosphere: one crimson field, one cyan. */
.eq-glow { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.eq-glow span {
  position: absolute;
  display: block;
  border-radius: 50%;
  filter: blur(110px);
  opacity: 0.6;
}
.eq-glow .g1 {
  width: 46vw; height: 46vw; top: -18vw; left: -12vw;
  background: radial-gradient(circle, rgba(176, 32, 74, 0.34), transparent 66%);
}
.eq-glow .g2 {
  width: 42vw; height: 42vw; bottom: -20vw; right: -10vw;
  background: radial-gradient(circle, rgba(168, 200, 238, 0.20), transparent 68%);
}
.pj-live .eq-glow .g1 { animation: pj-float-a 34s ease-in-out infinite alternate; }
.pj-live .eq-glow .g2 { animation: pj-float-b 42s ease-in-out infinite alternate; }

.eq-in {
  position: relative;
  z-index: 1;
  display: grid;
  gap: clamp(48px, 7vh, 86px);
}

/* --- entrance -------------------------------------------------------------
   Each block rises a little after the one above it. The delay lives on a
   custom property so a block can be re-ordered without re-writing timings. */
.pj-live .eq-rise {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  transition:
    opacity   0.85s var(--ease)     calc(var(--d, 0) * 1ms),
    transform 1.05s var(--ease-out) calc(var(--d, 0) * 1ms);
}
.pj-live .eq.is-in .eq-rise { opacity: 1; transform: none; }

/* ============================= 7a. MASTHEAD ============================== */
.eq-head { display: grid; gap: clamp(14px, 2vh, 20px); justify-items: start; }

.eq-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--eq-a);
}
.eq-eyebrow::before {
  content: "";
  width: 26px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--eq-a));
}

.eq-title {
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(58px, 10vw, 142px);
  line-height: 0.9;
  letter-spacing: -0.055em;
  color: var(--ink);
}
.eq-title em { font-style: normal; color: var(--eq-a); }

.eq-sub {
  max-width: 62ch;
  font-size: clamp(15px, 1.3vw, 18px);
  font-weight: 300;
  line-height: 1.75;
  color: var(--ink-3);
}
.eq-sub strong { font-weight: 400; color: var(--ink-2); }
.eq-sub em { font-style: normal; color: var(--eq-a); }

/* ======================== 7b. THE 15 / 100 COUNTER ======================= */
.eq-counter {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: clamp(28px, 5vw, 72px);
  padding: clamp(26px, 3.6vw, 46px);
  border: 1px solid var(--eq-edge);
  border-radius: var(--pj-r-lg);
  background:
    radial-gradient(ellipse at 0% 0%, rgba(168,200,238,0.12), transparent 58%),
    var(--eq-glass);
  backdrop-filter: blur(18px) saturate(125%);
  -webkit-backdrop-filter: blur(18px) saturate(125%);
}

.eq-count { display: flex; align-items: baseline; gap: 10px; }
.eq-count-n {
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(76px, 11vw, 152px);
  line-height: 0.82;
  letter-spacing: -0.06em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.eq-count-of {
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(26px, 3.4vw, 48px);
  letter-spacing: -0.04em;
  color: var(--eq-a);
}

.eq-meter { display: grid; gap: 12px; }
.eq-meter-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.eq-meter-row b { font-weight: 400; color: var(--ink-2); }

/* Ten ticks drawn into the track, so the bar reads as a scale of one hundred
   rather than as a generic progress bar. */
.eq-meter-track {
  position: relative;
  height: 12px;
  border-radius: 999px;
  border: 1px solid var(--pj-edge);
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.14) 0 1px, transparent 1px 10%),
    rgba(255, 255, 255, 0.045);
  overflow: hidden;
}
.eq-meter-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 15%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--eq-a2), var(--eq-a) 70%, var(--eq-a3));
  box-shadow: 0 0 22px rgba(168, 200, 238, 0.6);
  transform-origin: left center;
}
/* Fills on arrival, not on a loop. No JS → it is simply already full. */
.pj-live .eq-meter-fill {
  transform: scaleX(0);
  transition: transform 1.5s var(--ease-out) 0.25s;
}
.pj-live .eq.is-in .eq-meter-fill { transform: scaleX(1); }

/* ============================= 7c. THE LEDGER ============================ */
.eq-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(12px, 1.6vw, 20px);
}
.eq-fact {
  padding: clamp(20px, 2.6vw, 30px);
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r);
  background: var(--eq-tile);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.eq-fact:hover {
  border-color: var(--eq-edge);
  background: linear-gradient(180deg, rgba(168,200,238,0.10), rgba(168,200,238,0.014));
}
.eq-fact-n {
  display: block;
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(30px, 3.6vw, 48px);
  line-height: 1;
  letter-spacing: -0.045em;
  color: var(--ink);
}
.eq-fact-n small { font-size: 0.44em; font-weight: 300; color: var(--eq-a); margin-left: 4px; }
.eq-fact-l {
  display: block;
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  line-height: 1.6;
  color: var(--ink-4);
}

/* =========================== 7d. THE MANIFESTO ===========================
   The statement carries the left column so the prose beside it never runs to
   a 1300px measure — at this width a single column of body copy would be
   roughly 160 characters a line, which is unreadable however good the type. */
.eq-manifesto {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.2fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
}
.eq-pull {
  margin: 0;
  padding-left: clamp(20px, 2.6vw, 32px);
  border-left: 1px solid var(--eq-a2);
}
.eq-pull p {
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(21px, 2.5vw, 34px);
  line-height: 1.34;
  letter-spacing: -0.035em;
  color: var(--ink);
}
.eq-pull em { font-style: normal; color: var(--eq-a); }
.eq-pull cite {
  display: block;
  margin-top: 18px;
  font-family: var(--mono);
  font-size: 9.5px;
  font-style: normal;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--eq-a);
}
.eq-manifesto-body p {
  font-size: clamp(14.5px, 1.15vw, 16.5px);
  font-weight: 300;
  line-height: 1.78;
  color: var(--ink-3);
}
.eq-manifesto-body p + p { margin-top: 16px; }
.eq-manifesto-body strong { font-weight: 400; color: var(--ink-2); }
.eq-manifesto-body em { font-style: normal; color: var(--eq-a); }

/* ============================= 7e. EPISODES ============================== */
.eq-eps { display: grid; gap: clamp(18px, 2.6vh, 26px); }

.eq-sec-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.eq-sec-h {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.eq-swipe { display: none; }
.eq-sec-n {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--eq-a);
}

/* Rail by default — phones and tablets swipe it. The negative margin lets the
   cards run to both screen edges while the heading above stays on the page
   measure, which is what makes the strip feel like film rather than a widget.
   Desktop replaces this wholesale in 7h. */
.eq-rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(212px, 1fr);
  gap: clamp(12px, 1.4vw, 18px);
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
  padding-bottom: 4px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--gutter);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.eq-rail::-webkit-scrollbar { display: none; }

/* Rail position readout — replaces the scrollbar that was just hidden, so the
   strip still says how much of itself is left. */
.eq-rail-bar {
  position: relative;
  height: 2px;
  border-radius: 999px;
  background: var(--pj-edge);
  overflow: hidden;
}
.eq-rail-bar::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--eq-rw, 32%);
  border-radius: 999px;
  background: linear-gradient(90deg, var(--eq-a2), var(--eq-a3));
  transform: translateX(var(--eq-rx, 0%));
}

.eq-ep {
  scroll-snap-align: start;
  display: grid;
  grid-template-rows: auto 1fr;
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r);
  background: linear-gradient(180deg, #0a1a2c, #04101d);
  overflow: hidden;
  transition:
    border-color var(--dur) var(--ease),
    transform    var(--dur) var(--ease-out),
    box-shadow   var(--dur) var(--ease);
}
.eq-ep:hover {
  border-color: var(--eq-edge-2);
  transform: translateY(-4px);
  box-shadow: 0 26px 50px -30px rgba(0, 0, 0, 0.9);
}
.eq-ep-art {
  position: relative;
  margin: 0;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: linear-gradient(150deg, #10283d, #05121f);
}
.eq-ep-art img { display: block; width: 100%; height: 100%; object-fit: cover; }
.eq-ep-art img.broken { display: none; }
/* A slight foot shadow so a bright thumbnail still meets the card body. */
.eq-ep-art::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 48%, rgba(3,18,37,0.62));
  pointer-events: none;
}
.eq-ep-art.no-image::before {
  content: attr(data-fallback);
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.eq-ep-body { display: grid; align-content: start; gap: 8px; padding: 16px 16px 18px; }
.eq-ep-n {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--eq-a);
}
.eq-ep-t {
  font-family: var(--sans);
  font-weight: 300;
  font-size: 14.5px;
  line-height: 1.35;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.eq-ep-d {
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  color: var(--ink-4);
}

/* =============================== 7f. CREW =============================== */
.eq-crew { display: grid; gap: clamp(18px, 2.6vh, 26px); }
.eq-people {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(14px, 1.8vw, 22px);
}
.eq-person {
  margin: 0;
  display: grid;
  align-content: start;
  gap: 16px;
  padding: clamp(20px, 2.4vw, 26px);
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r);
  background: var(--eq-tile);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.eq-person:hover {
  border-color: var(--eq-edge);
  background: linear-gradient(180deg, rgba(168,200,238,0.09), rgba(168,200,238,0.014));
}

/* The portrait frame carries its own initials underneath the photograph, so a
   crew member whose picture has not been uploaded yet reads as a designed
   monogram instead of a broken image. */
.eq-face {
  position: relative;
  display: block;
  width: clamp(56px, 6vw, 76px);
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
  border: 1px solid var(--eq-edge);
  background: linear-gradient(150deg, #12354a, #050f1c);
}
.eq-face::before {
  content: attr(data-initials);
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-size: clamp(15px, 1.5vw, 19px);
  letter-spacing: 0.06em;
  color: var(--eq-a);
}
.eq-face img {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.eq-face img.broken { display: none; }

.eq-person figcaption { display: grid; gap: 6px; }
.eq-person b {
  font-family: var(--sans);
  font-weight: 400;
  font-size: clamp(15px, 1.25vw, 17px);
  letter-spacing: -0.015em;
  color: var(--ink);
}
.eq-role {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--eq-a);
}
.eq-person p {
  margin-top: 4px;
  font-size: 13.5px;
  font-weight: 300;
  line-height: 1.68;
  color: var(--ink-3);
}

/* =========================== 7g. NOTE AND CTA =========================== */
.eq-note {
  display: grid;
  gap: 10px;
  padding: clamp(22px, 2.8vw, 30px);
  border: 1px dashed var(--eq-edge);
  border-radius: var(--pj-r);
  font-size: clamp(14px, 1.1vw, 15.5px);
  font-weight: 300;
  line-height: 1.72;
  color: var(--ink-3);
}
.eq-note strong { font-weight: 400; color: var(--ink-2); }
.eq-note a {
  color: var(--eq-a);
  border-bottom: 1px solid var(--eq-edge);
  transition: border-color var(--dur) var(--ease);
}
.eq-note a:hover { border-color: var(--eq-a); }

.eq-cta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(12px, 1.4vw, 18px);
}
.eq-btn {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(14px, 1.6vw, 20px);
  padding: clamp(20px, 2.4vw, 26px) clamp(20px, 2.4vw, 28px);
  border: 1px solid var(--pj-edge-2);
  border-radius: var(--pj-r);
  overflow: hidden;
  color: var(--ink);
  transition:
    transform    var(--dur) var(--ease-out),
    border-color var(--dur) var(--ease),
    box-shadow   var(--dur) var(--ease);
}
.eq-btn:hover { transform: translateY(-3px); }
.eq-btn:focus-visible {
  outline: none;
  border-color: var(--eq-a);
  box-shadow: 0 0 0 3px rgba(168, 200, 238, 0.26);
}
.eq-btn-ico { display: grid; place-items: center; width: 30px; height: 30px; }
.eq-btn-ico svg { display: block; width: 100%; height: auto; }
.eq-btn-l { display: grid; gap: 4px; min-width: 0; }
.eq-btn-l b {
  font-family: var(--sans);
  font-weight: 400;
  font-size: clamp(15px, 1.35vw, 18px);
  letter-spacing: -0.02em;
}
.eq-btn-l i {
  font-style: normal;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.72;
}
.eq-btn-go { width: 26px; transition: transform var(--dur) var(--ease-out); }
.eq-btn:hover .eq-btn-go { transform: translateX(5px); }

/* Primary — the one filled surface in the whole band, so there is never a
   question about where the section is pointing. Navy type on the cyan/mint
   ramp runs at roughly 11:1, which is why the fill can be this loud. */
.eq-btn.is-primary {
  border-color: transparent;
  color: var(--eq-ink);
  background: linear-gradient(120deg, var(--eq-a), var(--eq-a3) 62%, var(--eq-a));
  box-shadow: 0 20px 44px -24px rgba(168, 200, 238, 0.8);
}
.eq-btn.is-primary:hover { box-shadow: 0 28px 54px -22px rgba(168, 200, 238, 0.95); }
.eq-btn.is-primary .eq-btn-l i { opacity: 0.64; }

.eq-btn.is-ghost { background: var(--eq-tile); color: var(--ink-2); }
.eq-btn.is-ghost:hover { border-color: var(--eq-edge-2); color: var(--ink); }

/* ====================== 7h. DESKTOP — RAIL BECOMES GRID ==================
   `auto-fit`, not `auto-fill`: with five cards the empty tracks collapse and
   the row divides evenly, instead of leaving a sixth column of dead space. */
@media (min-width: 1181px) {
  .eq-rail {
    grid-auto-flow: row;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    margin-inline: 0;
    padding-inline: 0;
    overflow: visible;
  }
  /* Nothing left to indicate — every episode is already on screen. */
  .eq-rail-bar { display: none; }

  .pj-live .eq.is-in .eq-ep {
    animation: eq-card-in 0.7s var(--ease-out) backwards;
    animation-delay: calc(min(var(--i, 0), 12) * 55ms + 250ms);
  }
  @keyframes eq-card-in {
    from { opacity: 0; transform: translate3d(0, 20px, 0); }
    to   { opacity: 1; transform: none; }
  }
}

/* ====================== 7i. TABLET AND BELOW — 1180px =================== */
@media (max-width: 1180px) {
  .eq-manifesto { grid-template-columns: 1fr; }
  .eq-people { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .eq-swipe {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: var(--mono);
    font-size: 9px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink-4);
  }
}

/* =========================== 7j. PHONE — 640px ========================== */
@media (max-width: 640px) {
  .eq { padding-block: clamp(60px, 10vh, 92px); }
  .eq-in { gap: clamp(40px, 6vh, 56px); }

  /* The numeral stops competing with the meter for a 360px row and takes its
     own line — which is also the only arrangement in which a 100px number
     still reads as the loudest thing in the band. */
  .eq-counter {
    grid-template-columns: 1fr;
    gap: 26px;
    padding: 24px 20px;
  }

  .eq-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .eq-fact { padding: 18px 16px; }
  .eq-fact-l { margin-top: 9px; font-size: 9px; letter-spacing: 0.14em; }

  .eq-pull { padding-left: 18px; }

  /* Wide enough that the next card always peeks in — that peek is the only
     affordance a touch rail needs. */
  .eq-rail { grid-auto-columns: 76vw; scroll-snap-type: x mandatory; }

  .eq-people { grid-template-columns: 1fr; }
  /* Rows, not cards. Four stacked portrait cards is a scroll; four rows is a
     credit list, which is what this actually is. */
  .eq-person {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: 16px;
    padding: 18px;
  }
  .eq-person p { font-size: 13px; }

  .eq-cta { grid-template-columns: 1fr; }
}

@media (max-width: 380px) {
  .eq-facts { grid-template-columns: 1fr; }
  .eq-fact { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
  .eq-fact-l { margin-top: 0; text-align: right; }
}

/* ======================= 7k. EQUAL · REDUCED MOTION ====================== */
@media (prefers-reduced-motion: reduce) {
  .pj-live .eq-glow span { animation: none; }
  .pj-live .eq-rise { opacity: 1; transform: none; transition: none; }
  .pj-live .eq-meter-fill { transform: scaleX(1); transition: none; }
  .pj-live .eq.is-in .eq-ep { animation: none; }
  .eq-ep:hover,
  .eq-btn:hover { transform: none; }
  .eq-rail { scroll-snap-type: none; }
}

/* No backdrop-filter → an opaque counter panel rather than a washed-out one. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .eq-counter { background: linear-gradient(180deg, #0a1c30, #041020); }
}

/* ============================================================================
   8. OUTRO
   ============================================================================ */
.pj-outro {
  margin-top: clamp(80px, 12vh, 150px);
  padding: clamp(36px, 5vw, 68px);
  border: 1px solid var(--pj-edge);
  border-radius: var(--pj-r-lg);
  background:
    radial-gradient(ellipse at 80% -20%, rgba(168,200,238,0.09), transparent 60%),
    var(--pj-fill);
  display: grid;
  gap: clamp(20px, 3vw, 30px);
  justify-items: start;
}
.pj-outro-t {
  font-family: var(--sans);
  font-weight: 200;
  font-size: clamp(28px, 4vw, 50px);
  letter-spacing: -0.035em;
  line-height: 1.06;
  color: var(--ink);
  max-width: 18ch;
}
.pj-outro-t em { font-style: normal; color: var(--accent-cool); }
.pj-outro-p {
  max-width: 58ch;
  font-size: clamp(14.5px, 1.15vw, 16.5px);
  font-weight: 300;
  line-height: 1.72;
  color: var(--ink-3);
}
.pj-outro-row { display: flex; flex-wrap: wrap; gap: 12px; }
.pj-btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 14px 24px;
  border: 1px solid var(--pj-edge-2);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-2);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.pj-btn:hover { color: var(--bg); background: var(--ink); border-color: var(--ink); }
.pj-btn.is-ghost { border-color: var(--pj-edge); }
.pj-btn.is-ghost:hover { color: var(--ink); background: rgba(255,255,255,0.06); border-color: var(--pj-edge-2); }
.pj-btn:focus-visible {
  outline: none;
  border-color: var(--accent-warm);
  box-shadow: 0 0 0 3px rgba(168, 200, 238, 0.18);
}

.pj-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  margin-top: clamp(40px, 6vh, 70px);
  padding-top: var(--space-6);
  padding-bottom: clamp(40px, 7vh, 80px);
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* ============================================================================
   9. SIDE MARKER — fixed chapter index, desktop only
   ============================================================================ */
.pj-marks {
  position: fixed;
  top: 50%;
  right: clamp(18px, 2vw, 30px);
  z-index: 50;
  transform: translateY(-50%);
  display: grid;
  gap: 14px;
  justify-items: end;
}
.pj-mark {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  color: var(--ink-4);
  opacity: 0.5;
  transition: opacity var(--dur) var(--ease), color var(--dur) var(--ease);
}
.pj-mark span {
  opacity: 0;
  transform: translateX(6px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.pj-mark i {
  display: block;
  width: 18px;
  height: 1px;
  background: currentColor;
  transition: width var(--dur) var(--ease), background var(--dur) var(--ease);
}
.pj-mark:hover,
.pj-mark.is-on { opacity: 1; color: var(--ink-2); }
.pj-mark:hover span,
.pj-mark.is-on span { opacity: 1; transform: translateX(0); }
.pj-mark.is-on i { width: 30px; background: var(--accent-cool); }
.pj-mark:focus-visible { outline: none; opacity: 1; color: var(--accent-warm); }

@media (max-width: 1180px) {
  .pj-marks { display: none; }
}

/* ============================================================================
   10. TABLET / NARROW DESKTOP — 1100px and down
   --------------------------------------------------------------------------
   Still two columns and still pinned: the cinematic read survives all the way
   down to the phone breakpoint, so an iPad gets the same page a laptop does.
   The scene turns portrait here because the columns are narrow and the
   viewports are tall — a square scene in a 480px column wastes most of an
   iPad's height.
   ============================================================================ */
@media (max-width: 1100px) {
  .pj-stage { gap: clamp(24px, 3.2vw, 48px); }
  .pj-scene {
    aspect-ratio: 4 / 5;
    width: min(100%, 58vh);
    width: min(100%, 58svh);
    perspective: 1200px;
  }
  .pj-track { height: 195vh; }
  .pj-meta { max-width: none; }
  .pj-case-grid { grid-template-columns: 1fr; gap: clamp(24px, 4vw, 34px); }
}

/* ============================================================================
   11. MOBILE — 900px and down
   --------------------------------------------------------------------------
   The stage stops pinning. Pinning a 100vh stage on a 700px-tall phone leaves
   no room for the copy and turns a scroll into a fight, so below this width
   the chapter becomes an ordinary flow layout — the scene above, the copy
   below. The parallax stays: --p / --pc are still written, the planes still
   drift and rotate in real 3D, just over a shorter runway.
   ============================================================================ */
@media (max-width: 900px) {
  /* Lower than desktop, for a reason that only shows up here. Stacked, the
     copy sits directly under the scene, and drift is the one thing that can
     carry a plane past the bottom of its box and into that gap. Keeping the
     travel under about 26px means the space below the artwork stays the space
     the layout asked for. */
  .pj-page { --pj-drift-k: 0.35; }

  .pj-hero { min-height: 88vh; min-height: 88svh; }
  .pj-stats {
    grid-template-columns: 1fr;
    gap: 18px;
  }
  .pj-stats > div {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
  }
  .pj-stats dt { margin-bottom: 0; }
  .pj-stats dd { text-align: right; }

  .pj-track { height: auto; }
  .pj-stage {
    /* Relative, not static: the ghost number is absolutely positioned against
       the stage and must stay inside it once the pin is gone. */
    position: relative;
    min-height: 0;
    grid-template-columns: 1fr;
    padding-block: clamp(64px, 10vh, 96px) clamp(38px, 6vh, 60px);
    /* Stacked, the artwork sits directly above the headline, so this gap is
       the only thing separating a picture from the words about it. It has to
       absorb the plane drift above as well as read as a gap. */
    gap: clamp(44px, 7.5vh, 68px);
  }
  /* One column, so the rows must be explicit — otherwise giving the scene
     row 1 would drop it into the same cell as the copy. */
  .pj-scene { grid-row: 1; }
  .pj-copy  { grid-row: 2; }
  .pj-ghost {
    font-size: clamp(110px, 30vw, 190px);
    bottom: auto;
    top: -0.06em;
    left: auto;
    right: -0.03em;
  }
  .pj-live .pj-ghost { transform: translate3d(calc(var(--pc) * 4vw), 0, 0); }

  .pj-scene {
    /* Portrait phones: the wrap is always narrower than the height cap, so this
       reads as plain `width: 100%`. The cap only bites in landscape, where a
       full-width 5/6 scene would otherwise stand taller than the screen and
       push every word of the chapter below the fold. */
    width: min(100%, 66vh);
    width: min(100%, 66svh);
    aspect-ratio: 5 / 6;
    /* Flatter than desktop. A plane with positive --z is pushed outward from
       the perspective origin as well as scaled up, and on a 390px screen that
       push is enough to shove a left-edge plane into the page gutter. */
    perspective: 1200px;
  }
  /* One composition for every chapter on a phone — predictable, and it keeps
     all four planes inside the frame at portrait proportions.
     Nothing starts at 0% or ends at 100% any more: the inset leaves room for
     the outward push above, so no frame ever reaches the edge of the screen.

     THE [data-comp] IS LOAD-BEARING, NOT DECORATION. The desktop compositions
     are written as `.pj-scene[data-comp="a"] .pj-plane:nth-child(n)`, and a
     media query adds no specificity — so a plain `.pj-scene .pj-plane` here
     LOSES to them and the phone silently keeps the desktop --x/--w/--drift.
     That is what pushed a left-edge frame off the screen. Matching the
     attribute puts these rules on equal specificity, and being later in the
     file is then what makes them win. Any new mobile plane rule needs it too. */
  .pj-scene[data-comp] .pj-plane:nth-child(1) { --x:  2%; --y:  0%; --w: 56%; --ar: 4/5;  --z:  26; --drift: 44; --ry:  4; --rx:  1; }
  .pj-scene[data-comp] .pj-plane:nth-child(2) { --x: 56%; --y: 10%; --w: 40%; --ar: 3/4;  --z: -20; --drift: 74; --ry: -5; --rx: -1; }
  .pj-scene[data-comp] .pj-plane:nth-child(3) { --x:  8%; --y: 54%; --w: 50%; --ar: 4/3;  --z:  38; --drift: 26; --ry: -3; --rx:  2; }
  .pj-scene[data-comp] .pj-plane:nth-child(4) { --x: 60%; --y: 62%; --w: 34%; --ar: 1/1;  --z:   0; --drift: 60; --ry:  6; --rx:  0; }

  /* Stacked in one column the copy has the full width, so it needs more air
     between its own parts than it does beside a scene on desktop. */
  .pj-copy { gap: clamp(18px, 2.6vh, 26px); }
  .pj-meta {
    max-width: none;
    gap: 14px;
    padding-top: clamp(22px, 3vh, 28px);
  }
  .pj-meta > div { gap: 14px; }

  .pj-chapter { padding-bottom: clamp(56px, 9vh, 96px); }
  .pj-toggle {
    gap: 12px;
    padding: 20px 18px;
    margin-top: clamp(12px, 2vh, 20px);
    flex-wrap: wrap;
  }
  .pj-toggle-k { flex: 1 0 100%; }
  .pj-toggle-t { flex: 1; font-size: 15px; }

  /* Stacked, "The brief" and "What I did" read as two separate sections, so
     the gap between them has to be bigger than it is side by side. */
  .pj-case-grid { gap: clamp(30px, 6vw, 40px); }
  .pj-figs,
  .pj-gal { margin-top: clamp(30px, 5vw, 44px); }

  .pj-figs { grid-template-columns: 1fr; }
  .pj-fig { display: flex; align-items: baseline; justify-content: space-between; gap: 18px; padding: 18px 20px; }
  .pj-fig-l { margin-top: 0; text-align: right; }

  .pj-gal { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }

  .pj-outro { justify-items: stretch; }
  .pj-outro-row .pj-btn { flex: 1; justify-content: center; }
}

@media (max-width: 560px) {
  .pj-title { font-size: clamp(46px, 15vw, 74px); }
  .pj-case-body { padding: 26px 20px; }
  .pj-foot { flex-direction: column; align-items: flex-start; gap: 10px; }
  /* Side by side, these labels wrap to two lines and the pills lose their
     shape. One per row keeps them as buttons. */
  .pj-outro-row { flex-direction: column; }
}

/* ============================================================================
   12. REDUCED MOTION
   --------------------------------------------------------------------------
   projects.js stops writing --p / --pc entirely, so the planes rest at their
   composed positions. These rules undo the things CSS would still animate on
   its own and make sure nothing that was revealed by scroll stays hidden.
   ============================================================================ */
@media (prefers-reduced-motion: reduce) {
  .pj-live .pj-atmos span,
  .pj-live .pj-cue i::after,
  .pj-live .pj-word { animation: none; }
  .pj-live .pj-word { opacity: 1; transform: none; }

  .pj-live .pj-copy > * { opacity: 1; transform: none; }
  .pj-live .pj-copy::after { transform: scaleY(1); }
  .pj-live .pj-ghost { transform: none; }
  /* Composed position and composed rotation, with the scroll term dropped. */
  .pj-live .pj-plane {
    transform:
      translate3d(0, 0, calc(var(--z, 0) * 1px))
      rotateY(calc(var(--ry, 0) * 1deg))
      rotateX(calc(var(--rx, 0) * 1deg));
  }

  .pj-scene { transition: none; transform: none !important; }
  .pj-case,
  .pj-case-body { transition-duration: 0.01s; }
}

/* ============================================================================
   13. NO BACKDROP-FILTER — an opaque panel instead of a blurred one
   ============================================================================ */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .pj-case-body { background: linear-gradient(180deg, #141414, #0e0e0e); }
}
