/* ==========================================================================
   StudioStudio — main.css
   Layers, in order:
     1. Tokens        design variables
     2. Base          reset, element defaults
     3. Utilities     wrap, mono, eyebrow, section
     4. Motion        chromatic split, reveal, reduced-motion
     5. Chrome        nav
     6. Sections      hero, reel, statement, services, work, quotes, contact
     7. Engine        cursor-follow thumbnail
   ========================================================================== */


/* ==========================================================================
   1. TOKENS
   ========================================================================== */
:root{
  --ink:#FFFFFF;     /* page */
  --ink-2:#F6F6F5;   /* subtle raised surface */
  --ink-3:#EFEFEE;   /* image placeholder */
  --bone:#141414;    /* primary text */
  --mute:#7A7A78;    /* secondary text */
  --line:#E4E4E2;    /* hairlines */
  --band:#F1F1EF;    /* testimonial band */
  --dark:#111111;    /* footer / playground */
  --red:#007AB7;    /* accent: link hovers, button fills, selection */
  --cyan:#08AEBC;

  /* Display face under test. Falls through to Jakarta until the licensed
     PP Editorial Old webfonts are added to assets/fonts/. */
  --disp:"PP Editorial Old", "Plus Jakarta Sans", system-ui, sans-serif;
  --body:"Plus Jakarta Sans", system-ui, sans-serif;
  /* Kept as a role name for small caps/labels, now the same face. */
  --mono:"Plus Jakarta Sans", system-ui, sans-serif;

  /* Every surface reads from these, so flipping them inverts the whole site. */
  color-scheme:light;

  --pad: clamp(1.25rem, 5vw, 4.5rem);
  --gut: clamp(1.5rem, 3.4vw, 3.25rem);  /* full-bleed gutter */
  --sect: clamp(5rem, 13vh, 11rem);
}


/* Dark mode, applied while the work section fills the viewport. Only the
   tokens change — no component needs its own dark variant. */
body.is-dark{
  --ink:#111111;
  --ink-2:#1A1A1A;
  --ink-3:#222222;
  --bone:#F4F4F2;
  --mute:#8C8C88;
  --line:#2C2C2A;
  --band:#171717;
  color-scheme:dark;
}
body{transition:background-color .5s ease,color .5s ease}
.page-layer,.quotes-band,header.site{transition:background-color .5s ease,color .5s ease}
body.is-dark .q{background:var(--ink-2)}
body.is-dark .mark img{filter:brightness(0) invert(1)}
body.is-dark .btn-big__label,body.is-dark .btn-big__disc{background:var(--bone);color:var(--ink)}


/* ==========================================================================
   2. BASE
   ========================================================================== */
*{box-sizing:border-box;margin:0;padding:0}
/* No overflow on the root. overflow:clip here (or on body) makes every
   descendant's position:sticky attach to that clip container instead of the
   window — which silently disabled the hero pin. The hero's overhang is
   contained on .hero itself instead, which is no longer a sticky ancestor. */
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}

/* --- smooth scroll (Lenis) -------------------------------------------------
   Lenis interpolates the scroll position toward its target every frame, which
   is where the eased momentum comes from. It needs the native smooth behaviour
   off, or the two fight each other. These classes are put on <html> by the
   library itself, so do not remove them while the script is enqueued. */
.lenis,.lenis body{height:auto}
/* Applied to the whole .lenis state, not just .lenis-smooth: that class is only
   present while a scroll is animating, which would leave the theme's native
   scroll-behavior:smooth fighting lenis.scrollTo() on anchor clicks at rest. */
.lenis{scroll-behavior:auto !important}
/* Opt an element out with data-lenis-prevent — its own scrolling is left
   alone, so inner scrollers keep working. */
.lenis.lenis-smooth [data-lenis-prevent]{overscroll-behavior:contain}
.lenis.lenis-stopped{overflow:hidden}
.lenis.lenis-scrolling iframe{pointer-events:none}
/* --- end smooth scroll ---------------------------------------------------- */
body{
  background:var(--ink);
  color:var(--bone);
  font-family:var(--body);
  font-size:clamp(1rem,0.6vw + 0.9rem,1.15rem);
  line-height:1.55;
  font-weight:400;
  -webkit-font-smoothing:antialiased;
}
::selection{background:var(--red);color:var(--ink)}
a{color:inherit;text-decoration:none}
img{display:block;max-width:100%}

/* Keyboard focus — the prototype had none. */
:focus-visible{outline:2px solid var(--cyan);outline-offset:3px}

/* Skip link. Hidden by clipping it to nothing rather than parking it at
   left:-9999px — off-origin absolute content is counted in the scrollable area
   by some mobile browsers, which lets the whole page slide sideways. */
.skip{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}
.skip:focus{position:fixed;left:var(--pad);top:.6rem;width:auto;height:auto;margin:0;
  clip-path:none;overflow:visible;z-index:300;background:var(--bone);color:var(--ink);
  padding:.8rem 1.2rem;font-family:var(--mono);font-size:.72rem;letter-spacing:.14em;text-transform:uppercase}


/* ==========================================================================
   3. UTILITIES
   ========================================================================== */
.wrap{width:100%;max-width:none;margin:0;padding-inline:var(--gut)}
/* Full-bleed container for the work grid. The reference has no max-width
   anywhere in its ancestor chain and only a 24px gutter, which is what makes
   its tiles so much larger on a wide display than a boxed 1320px layout. */
.wrap-wide{width:100%;max-width:none;margin:0;padding-inline:var(--gut)}
.mono{font-family:var(--mono);font-size:.72rem;letter-spacing:.14em;text-transform:uppercase}
.eyebrow{font-family:var(--mono);font-size:.72rem;letter-spacing:.18em;text-transform:uppercase;color:var(--mute)}
.section{padding-block:var(--sect)}
.section--tight{padding-block:0 var(--sect)}
.hide{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
.tone-mute{color:var(--mute)}


/* ==========================================================================
   4. MOTION
   ========================================================================== */

/* .split is retained as a plain inline wrapper — the chromatic RGB ghosting
   was removed at the client's request. */
.split{position:relative;display:inline-block}

/* Scroll reveal. Scoped to .js so the hidden start state only ever exists when
   JavaScript is actually running — otherwise a blocked, failed or throttled
   script leaves every section below the hero permanently invisible. */
.js .reveal{opacity:0;transform:translateY(28px);transition:opacity .9s ease,transform .9s cubic-bezier(.16,1,.3,1)}
.js .reveal.in{opacity:1;transform:none}

/* Masked line reveal — each .ln is an overflow-hidden window and its inner
   span slides up into it. Shared by the H1, project titles and project meta. */
/* Generous bottom padding: at .08em the mask clipped descenders on g/y/p. */
.ln{display:block;overflow:hidden;padding-bottom:.22em}
/* The display face has long descenders on g/p/j/y that overflow the inline box,
   so the H1 needs a deeper window than the shared .ln default. The negative
   margin gives the padding back, so the mask grows without opening up the line
   spacing that the hero layout is already balanced around. */
.h1 .ln{padding-top:.12em;margin-top:-.12em;padding-bottom:.34em;margin-bottom:-.12em}
.ln > span{display:inline-block;will-change:transform}
/* Lines alternate their entry angle: odd lines drift in from the bottom-left,
   even lines from the bottom-right, so a stack reads as hand-set rather than
   mechanical. The mask still only reveals vertical travel. */
/* 175%, not 110%: the offset is a percentage of the span's own height, but it
   has to clear the mask, which is taller (line-height plus .ln padding). At
   110% the top of the text was visible in the window before the animation
   started, and .h1 .ln now pads top and bottom so the display face's caps and
   descenders both clear the edges. */
.js .lines .ln > span{transform:translate(-.14em,175%)}
.js .lines .ln:nth-child(even) > span{transform:translate(.14em,175%)}
.js .lines.in .ln > span{animation:lineUpLeft .9s cubic-bezier(.16,1,.3,1) both}
.js .lines.in .ln:nth-child(even) > span{animation-name:lineUpRight}
.js .lines.in .ln:nth-child(1) > span{animation-delay:.02s}
.js .lines.in .ln:nth-child(2) > span{animation-delay:.12s}
.js .lines.in .ln:nth-child(3) > span{animation-delay:.22s}
.js .lines.in .ln:nth-child(4) > span{animation-delay:.32s}
@keyframes lineUpLeft{from{transform:translate(-.14em,175%)}to{transform:translate(0,0)}}
@keyframes lineUpRight{from{transform:translate(.14em,175%)}to{transform:translate(0,0)}}

/* Hover swap — the label leaves through the top of its mask while an identical
   copy arrives from the bottom. Used on nav links and project titles. */
.swap{position:relative;display:inline-block;overflow:hidden;vertical-align:top}
.swap__in,.swap__out{display:block;transition:transform .42s cubic-bezier(.16,1,.3,1),color .42s ease}
.swap__out{position:absolute;left:0;top:0;transform:translateY(100%)}
.swap:hover .swap__in,
a:hover .swap .swap__in,
a:focus-visible .swap .swap__in{transform:translateY(-100%)}
.swap:hover .swap__out,
a:hover .swap .swap__out,
a:focus-visible .swap .swap__out{transform:translateY(0);color:var(--red)}

/* Projects rise into place, staggered so the two in a row are not simultaneous. */
.js .proj .ph{opacity:0;transform:translateY(56px);
  /* The media sits in a mask that opens from 70% of the box to full size.
     clip-path is used rather than scaling .ph itself: scaling would enlarge the
     picture along with the window, whereas this holds the image still and grows
     the opening over it — and inset() animates on the compositor, so it does not
     re-composite the decoded video frames on every tick. 15% inset per side
     leaves 70% showing. */
  clip-path:inset(15%);
  transition:opacity .85s ease,transform .95s cubic-bezier(.16,1,.3,1),
    clip-path 1.1s cubic-bezier(.16,1,.3,1)}
.js .proj.in .ph{opacity:1;transform:none;clip-path:inset(0%)}
.js .proj:nth-child(even) .ph{transition-delay:.12s}
/* Title then meta, after the image has begun rising. */
.js .proj.in h3 .ln > span{animation-delay:.18s}
.js .proj.in .meta .ln > span{animation-delay:.26s}
.js .proj:nth-child(even).in h3 .ln > span{animation-delay:.30s}
.js .proj:nth-child(even).in .meta .ln > span{animation-delay:.38s}

/* Services slide in from the right. */
.js .svc li{opacity:0;transform:translateX(46px);
  transition:opacity .7s ease,
             transform .8s cubic-bezier(.16,1,.3,1),
             padding-left .45s cubic-bezier(.16,1,.3,1)}
.js .svc.in li{opacity:1;transform:none}
.js .svc.in li:nth-child(1){transition-delay:.04s}
.js .svc.in li:nth-child(2){transition-delay:.12s}
.js .svc.in li:nth-child(3){transition-delay:.20s}
.js .svc.in li:nth-child(4){transition-delay:.28s}
.js .svc.in li:nth-child(5){transition-delay:.36s}
.js .svc.in li:nth-child(6){transition-delay:.44s}

@media (prefers-reduced-motion: reduce){
  *{animation:none !important;transition:none !important;scroll-behavior:auto}
  .lines .ln > span,.js .lines .ln > span{transform:none;animation:none}
  .js .proj .ph,.js .svc li{opacity:1;transform:none}
  .js .proj .ph{clip-path:none}
  .js .reveal,.reveal{opacity:1;transform:none}
}


/* ==========================================================================
   5. CHROME
   ========================================================================== */

/* ---- nav ---- */
/* Transparent bar — no background, no rule beneath it. */
header.site{position:fixed;top:0;left:0;right:0;z-index:100;color:var(--bone);background:none;
  /* Transparent, so the hero's cursor dot reads through the bar; the links
     themselves stay above it and remain clickable. */
  pointer-events:none}
header.site a{pointer-events:auto}
.nav{display:flex;align-items:center;justify-content:space-between;padding:1.3rem var(--gut)}
/* The mark is two brand colours on transparency, so it must NOT be filtered —
   brightness(0) would flatten the blue and the black into one silhouette. A
   separate light variant is swapped in for dark mode instead. */
.mark{display:block;line-height:0}
.mark{display:block;line-height:0}
.mark img{width:auto;height:clamp(40px,4vw,56px);display:block}
.mark .mark__light{display:none}
body.is-dark .mark .mark__dark{display:none}
body.is-dark .mark .mark__light{display:block}
.nav-right{display:flex;align-items:center;gap:clamp(1.4rem,3vw,2.6rem)}
.nav-link{font-family:var(--body);font-size:clamp(1rem,1.15vw,1.15rem);font-weight:500;letter-spacing:0;
  text-transform:none;color:currentColor;white-space:nowrap}
/* Kept visible on phones — they are the only navigation the site has, and
   there is no burger menu to fall back on. Type and gap tighten instead. */
@media(max-width:520px){
  .nav-right{gap:1.1rem}
  .nav-link{font-size:.95rem}
  .mark img{height:36px}
}
.btn{
  font-family:var(--mono);font-size:.72rem;letter-spacing:.12em;text-transform:uppercase;
  border:1px solid currentColor;color:currentColor;padding:.62rem 1.05rem;border-radius:40px;white-space:nowrap;
  transition:background .25s,color .25s;
}
.btn:hover{background:currentColor;color:var(--ink)}
header.site.is-solid .btn:hover{background:var(--bone);color:var(--ink)}

/* ==========================================================================
   6. SECTIONS
   ========================================================================== */

/* ---- hero ----
   Headline and intro sit above the media, which is an ordinary block in the
   flow. It still drifts with the cursor and its playhead is still tied to
   scroll — see initHero(). */
/* Horizontal clip only: it contains the reveal layer's sideways overhang while
   leaving the vertical overhang (the dot in the nav strip) intact. Safe to clip
   here because the pinned media now lives outside this section. */
.hero{padding-top:clamp(8rem,17vh,12rem);overflow-x:clip}
/* Weighted toward the headline: "Brands worth remembering." is 1367px wide at
   100px type, so an even split forced it to wrap at anything above ~53px. */
/* Two columns, two rows: the strapline sits alone on row 1, and the headline
   and intro share row 2 so their tops line up. display:contents dissolves the
   .hero-col wrapper so its children become grid items directly — an explicit
   margin would have had to guess the strapline's height, which changes with
   the viewport. */
.hero-row{display:grid;grid-template-columns:1.45fr 1fr;
  column-gap:clamp(1.5rem,4vw,4rem);row-gap:clamp(1.2rem,3vh,2.2rem);
  align-items:start}
.hero-col{display:contents}
.hero-row .strapline{grid-column:1;grid-row:1}
.hero-row .h1{grid-column:1;grid-row:2}
.hero-row .hero-intro{grid-column:2;grid-row:2}
.strapline{font-size:clamp(.76rem,.85vw,.88rem);font-weight:500;color:var(--mute);line-height:1.4}
.h1{
  font-family:var(--disp);font-weight:200;
  /* Largest that keeps all three lines unwrapped in the column above. */
  font-size:clamp(1.9rem, 4vw, 4.6rem);
  line-height:.96;letter-spacing:-.035em;
  text-wrap:balance;
  text-transform:none;
}
.h1 em{font-style:normal;color:var(--bone)}
.hero-intro{font-size:clamp(1.3rem,2.4vw,2.15rem);font-weight:400;line-height:1.28;letter-spacing:-.02em;max-width:26.4ch}
/* Single column: drop the explicit placement so the three stack in DOM order
   (strapline, headline, intro) instead of holding their two-column rows. */
/* Mobile type. The headline is set from vw so it grows with the screen but
   never outruns its column — at 390px the longest line ("Design people
   notice.") wraps above ~48.7px, so 11.5vw keeps a safe margin at every width
   from 320px up. */
@media(max-width:640px){
  .h1{font-size:clamp(2.2rem,11.5vw,3.2rem)}
  .hero-intro{font-size:clamp(1.35rem,6.2vw,1.7rem)}
  .lede{font-size:clamp(1.2rem,4.6vw,1.5rem)}
}

@media(max-width:820px){
  .hero-row{grid-template-columns:1fr}
  .hero-row .strapline,.hero-row .h1,.hero-row .hero-intro{grid-column:1;grid-row:auto}
  .hero-intro{max-width:none}
}

/* Type matches .strapline so the three small labels in the hero read as one set. */
.hero-caption{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;
  margin-top:clamp(3rem,9vh,7rem);padding-bottom:.9rem;
  font-size:clamp(.76rem,.85vw,.88rem);font-weight:500;line-height:1.4;color:var(--mute)}
.hero-caption__right{display:flex;gap:1.2rem;align-items:baseline}

/* A plain block in the flow. Reverted to the state before the scroll-scaling
   experiment: no sticky, no stage, no geometry animation. Anything that changes
   the size, clip or position of a box containing a video forces the GPU to
   re-composite every decoded frame, which is what made the scrub judder. */
/* The media pins at the top of the viewport and the rest of the page rides up
   over it. The stage is padded out below so sticky has room to travel, and the
   section that follows is pulled back up by the same amount — so the layout is
   unchanged and the page simply overlaps the pinned video.
   Geometry is still never animated here: pinning costs nothing, whereas scaling
   or clipping a box containing video forces the GPU to re-composite every
   decoded frame, which is what made this judder before. */
.hero-stage{position:relative;padding-bottom:var(--hero-pin,100vh)}
/* The pin lives on a bare wrapper, not on the media. A plain div sticks in this
   container; the media — with width:100vw, a negative margin, aspect-ratio and
   overflow:hidden — did not. Keeping the two jobs on separate elements. */
/* Pinned by initHeroPin() in main.js, which switches this between static flow,
   position:fixed (while held at the top) and position:absolute (once passed).
   It writes nothing per frame: earlier versions translated the element on every
   scroll event, which always lands a frame behind macOS compositor scrolling and
   reads as shake. While fixed, the browser holds it with no JS involved at all.
   The stage's height is kept constant with a matching padding-top so that taking
   this out of flow does not collapse the page below. */
.hero-pin{position:relative}
/* Full-bleed by simply filling its parent: .hero-stage is a direct child of
   <main> and already spans the window. The old width:100vw + negative margin
   dated from when this sat inside the padded .hero wrapper, and 100vw counts
   the scrollbar — which is what was insetting it. */
.hero-media{position:relative;overflow:hidden;background:var(--ink-3);
  width:100%;margin-left:0;aspect-ratio:16/9;
  /* Never taller than the screen. A sticky element that does not fit its
     scrollport has nowhere to pin, so on wide windows — where 16:9 of 100vw
     exceeds the viewport height — it simply scrolled away. The inner media is
     object-fit:cover, so capping the height crops rather than distorts. */
  max-height:100vh}
/* The phone cut is square, so the box must match or object-fit:cover crops it
   back to a letterbox. Placed after the base rule deliberately: both are
   specificity 0,1,0, so this only wins by coming later. Same 640px breakpoint
   initHeroVideo() uses to choose the file. */
@media(max-width:640px){.hero-media{aspect-ratio:1/1}}

/* Cancel the stage's padding so following content sits exactly where it did. */
.hero-stage + .page-layer{margin-top:calc(-1 * var(--hero-pin,100vh))}
/* Static. The cursor-follow drift was removed: it kept the video layer in
   constant motion, which is exactly what the scrub cannot afford. */
.hero-media__inner{position:absolute;inset:0}
.hero-media img,.hero-media video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* Video fades in over the still once its first frame is decoded, so a blocked
   or slow file never leaves a blank rectangle where the hero should be. */
.hero-media .hero-video{opacity:0;transition:opacity .6s ease}
.hero-media .hero-video.is-playing{opacity:1}


/* Opaque layers that scroll over the pinned hero media. */
.page-layer{position:relative;z-index:2;background:var(--ink)}

/* ---- reel ---- */
.reel{position:relative;aspect-ratio:16/8;overflow:hidden;background:
  radial-gradient(120% 140% at 18% 18%, #23232a 0%, #0c0c0e 60%);
  border:1px solid var(--line);display:flex;align-items:center;justify-content:center;cursor:pointer}
.reel::after{content:"";position:absolute;inset:0;background:repeating-linear-gradient(0deg,rgba(255,255,255,.025) 0 1px,transparent 1px 4px);mix-blend-mode:overlay;pointer-events:none}
.reel video,.reel > img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.reel .meta{position:absolute;top:1.3rem;left:1.4rem;font-family:var(--mono);font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;color:var(--mute)}
.reel .tc{position:absolute;bottom:1.3rem;right:1.4rem;font-family:var(--mono);font-size:.72rem;letter-spacing:.14em;color:var(--mute)}
.reel .big{font-family:var(--disp);font-weight:800;font-size:clamp(2.2rem,8vw,6rem);letter-spacing:-.04em;color:#F4F1EA;text-align:center;line-height:.92;opacity:.96}
.play{position:absolute;width:clamp(64px,9vw,104px);height:clamp(64px,9vw,104px);border-radius:50%;border:1px solid #F4F1EA;display:flex;align-items:center;justify-content:center;background:rgba(12,12,14,.35);transition:transform .3s, background .3s}
.reel:hover .play{transform:scale(1.08);background:var(--red)}
.play svg{width:30%;height:30%;fill:#F4F1EA}
.reel:hover .play svg{fill:#15140F}

/* ---- statement ---- */
.statement{font-family:var(--disp);font-weight:200;font-size:clamp(2rem,6vw,5.2rem);line-height:1.02;letter-spacing:-.035em}
.statement .dim{color:var(--mute)}
.lede{max-width:62ch;margin-top:clamp(2.5rem,6vh,4rem);font-size:clamp(1.3rem,1.85vw,1.7rem);line-height:1.5;color:var(--bone)}
.lede b{color:var(--bone)}
.lede .by{display:block;margin-top:1.6rem;font-family:var(--mono);font-size:.74rem;letter-spacing:.12em;text-transform:uppercase;color:var(--mute)}

/* ---- services ---- */
.svc-head{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;flex-wrap:wrap;margin-bottom:2.5rem}
/* Rows enter from translateX(46px), which extends the document to the right
   until they animate in — enough to make the whole page scroll sideways on a
   phone. Contain it here. `clip` rather than `hidden`: hidden on one axis
   forces the other to `auto`, which would turn this into a scroll container. */
.svc{border-top:1px solid var(--line);overflow-x:clip}
.svc li{list-style:none;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:1.2rem;padding:clamp(1rem,2.6vw,1.9rem) 0;cursor:default;position:relative;transition:padding-left .35s}
.svc li:hover{padding-left:1.4rem}
.svc li .no,.svc li .name{transition:color .3s ease,opacity .3s ease}
.svc li .no{font-family:var(--mono);font-size:.74rem;color:var(--mute);width:2.5rem;flex:none}
.svc li .name{font-family:var(--disp);font-weight:700;font-size:clamp(1.7rem,5.2vw,3.6rem);letter-spacing:-.03em;transition:color .3s, opacity .3s}
.svc:hover .name{opacity:.4}
.svc li:hover .name{opacity:1;color:var(--cyan)}
.svc li .desc{margin-left:auto;text-align:right;color:var(--mute);max-width:34ch;font-size:.96rem}
@media(max-width:740px){.svc li{flex-wrap:wrap}.svc li .desc{margin-left:3.7rem;text-align:left}}

/* ---- work ---- */
/* After julieguzal.fr: a 12-column field where each project owns a whole row
   and occupies half its width, alternating right then left. The generous feel
   comes from that — one project per row with 180px between, not from the gap
   value alone. Measured from the reference at 1280x720: 12 cols, 24px column
   gap, 180px row gap, tiles 50% wide at 1.65:1. */
.work2{
  display:grid;
  grid-template-columns:repeat(12,1fr);
  column-gap:clamp(1rem,1.9vw,1.5rem);
  row-gap:clamp(3rem,7vh,5.5rem);
  align-items:start;
}
/* Tighter than the site's default section rhythm, per the brief. */
.section--work{padding-block:clamp(3rem,7vh,5.5rem)}
/* Two projects per row, evenly spanning the 12-column field. */
.proj{grid-column:span 6}
.proj{display:block;color:inherit}
/* No hover scale or tilt — the video playing is the hover state. */
/* Placeholder state: each tile is a flat block of the project's own brand
   colour. The hover video plays over it. Remove .is-flat (see
   ss_tile_needs_letterbox in functions.php) once real tile artwork is in. */
.proj .ph{overflow:hidden;background:var(--ink-3);position:relative}
.proj .ph img{width:100%;height:100%;object-fit:cover}
/* Scroll-driven scale. Applied to the media, not the tile, so the tile's own
   reveal transform is untouched. The floor is 1 rather than something smaller
   because .ph clips: scaling below 1 would expose the background at the edges.
   Image and video share the value so a hover mid-scroll stays registered. */
.proj .ph img,.proj .ph .tile-video{
  transform:scale(var(--tile-scale,1));
  transform-origin:50% 50%;
  will-change:transform;
}
.proj .ph.is-flat img{display:none}

/* Video tiles sit over the still, which stays as the poster/fallback layer. */
.proj .tile-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .35s ease}
.proj .tile-video.is-playing{opacity:1}
@media (prefers-reduced-motion: reduce){.proj .tile-video{display:none}}
/* Title sits under the image, then a wide gap, then the disciplines stacked one
   per line on the left with the year pushed to the right edge of the tile. */
.proj h3{font-family:var(--disp);font-weight:500;font-size:clamp(1.4rem,2.2vw,1.9rem);
  letter-spacing:-.015em;line-height:1.15;margin-top:clamp(1.2rem,2.2vh,1.9rem)}

.proj .meta{display:flex;justify-content:space-between;align-items:flex-start;gap:1.5rem;
  margin-top:clamp(1.6rem,4.5vh,3.4rem)}
.proj .tags{list-style:none;display:flex;flex-direction:column;gap:.1rem;color:var(--mute);font-size:.94rem;line-height:1.35}
.proj .industry{color:var(--mute);font-size:.94rem;flex:none;line-height:1.35;text-align:right}
/* One shape for every tile, matching julieguzal.fr's landscape proportion. */
.proj .ph{aspect-ratio:5/3}

/* Interim: the current tile artwork is all 1000x1000, which a 5:3 box would
   crop by 40%. Squares are letterboxed against the project's own palette colour
   instead, so nothing is lost. Applied per-tile at render time — as soon as a
   tile is re-exported landscape this stops applying on its own. */
.proj .ph.is-letterboxed img{object-fit:contain}

@media(max-width:900px){
  /* Below the alternation's useful width, tiles go full-bleed single column. */
  .proj,.proj:nth-child(odd){grid-column:1 / -1;width:100%;margin-left:0}
  .work2{row-gap:clamp(4rem,14vh,7rem)}
}

/* ---- testimonials ---- */
/* Tinted band, white cards — the band colour is what separates the section. */
/* The testimonials pin once fully in view; the contact block then slides up
   over them as its own layer. */
.quotes-band{background:var(--band);position:sticky;top:0;z-index:1}
.contact{position:relative;z-index:3}
/* Horizontal carousel: native scroll-snap, so it drags on touch and trackpad
   with no library, and the arrows just call scrollBy. */
.quotes-nav{display:flex;gap:.6rem;justify-content:flex-end;margin-bottom:1.4rem}
.quotes-nav button{width:3rem;aspect-ratio:1;border-radius:50%;border:1px solid var(--line);
  background:none;color:var(--bone);font-size:1rem;cursor:pointer;display:flex;align-items:center;justify-content:center;
  transition:background .25s ease,color .25s ease,border-color .25s ease}
.quotes-nav button:hover{background:var(--bone);color:var(--ink);border-color:var(--bone)}
.quotes-nav button[disabled]{opacity:.3;cursor:default}
.quotes-nav button[disabled]:hover{background:none;color:var(--bone);border-color:var(--line)}

.quotes{--q-pad:clamp(1.7rem,3vw,2.6rem);display:flex;gap:clamp(1rem,2vw,1.6rem);
  /* overflow-y MUST be stated. Setting only overflow-x:auto makes the computed
     overflow-y "auto" too, which turned this into a vertical scroll container —
     so a wheel over the cards scrolled them inside the box instead of the page. */
  overflow-x:auto;overflow-y:hidden;
  overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;scroll-behavior:smooth;
  /* Break out of the section gutters so the track runs edge to edge, while the
     first card still lines up with everything else. */
  margin-inline:calc(-1 * var(--gut));
  /* First card's left edge sits on the same line as the section heading.
     scroll-padding matters as much as padding here: without it, scroll-snap
     aligns cards to the container edge and ignores the inset, so the moment the
     track scrolls at all the first card slams flush to the window. */
  padding-left:var(--gut);
  padding-right:var(--gut);padding-bottom:.5rem;
  scroll-padding-left:var(--gut);
  scrollbar-width:none;-ms-overflow-style:none}
.quotes::-webkit-scrollbar{display:none}
.quotes .q{scroll-snap-align:start;flex:0 0 clamp(280px,27vw,380px)}
.q{--q-pad:clamp(1.7rem,3vw,2.6rem);
  background:#fff;border-radius:14px;padding:var(--q-pad);display:flex;flex-direction:column;
  box-shadow:0 1px 2px rgba(20,20,20,.04)}

.q-head{display:flex;align-items:center;gap:1rem;margin-bottom:1.6rem}
.q-avatar{width:56px;height:56px;flex:none;border-radius:10px;overflow:hidden;background:var(--ink-2)}
.q-avatar img{width:100%;height:100%;object-fit:cover}
.q-avatar--initials{display:flex;align-items:center;justify-content:center;
  font-family:var(--disp);font-weight:600;font-size:1.05rem;color:var(--mute);letter-spacing:.02em}
.q-who .nm{font-weight:600;color:var(--bone);line-height:1.25}
.q-who .rl{font-size:.88rem;color:var(--mute);margin-top:.15rem;line-height:1.3}

.q blockquote{border:0;margin:0;padding:0}
.q p{font-size:14px;line-height:1.6}
@media(max-width:680px){.quotes .q{flex-basis:82vw}}

/* ---- showreel ----
   Facade only: poster plus play control. The iframe is injected on click. */
.reel-frame{position:relative;aspect-ratio:16/9;overflow:hidden;background:var(--dark);cursor:pointer}
.reel-frame img,.reel-frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0;object-fit:cover}
.reel-frame img{transition:transform .8s cubic-bezier(.16,1,.3,1),filter .5s ease;filter:brightness(.82)}
.reel-frame:hover img{transform:scale(1.03);filter:brightness(.7)}
.reel-play{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1.1rem;background:none;border:0;cursor:pointer;color:#fff;font-family:var(--body)}
.reel-play__ring{width:clamp(72px,7vw,112px);aspect-ratio:1;border-radius:50%;border:1px solid rgba(255,255,255,.7);
  display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.28);
  transition:background .3s ease,transform .3s ease,border-color .3s ease}
.reel-play__ring svg{width:30%;height:30%;fill:#fff;margin-left:8%;transition:fill .3s ease}
.reel-frame:hover .reel-play__ring{background:var(--red);border-color:var(--red);transform:scale(1.06)}
.reel-play__label{font-size:.92rem;font-weight:500;letter-spacing:.02em}
.reel-frame.is-playing .reel-play,.reel-frame.is-playing img{display:none}

/* ---- playground button ---- */
.work-cta{margin-top:clamp(2.5rem,6vh,4.5rem);display:flex;justify-content:center}
.btn-solid{display:inline-flex;align-items:center;gap:.7rem;background:var(--bone);color:var(--ink);
  font-family:var(--mono);font-size:.74rem;letter-spacing:.14em;text-transform:uppercase;
  padding:1.05rem 1.8rem;border-radius:40px;transition:background .25s,color .25s,gap .25s}
.btn-solid:hover{background:var(--red);color:#fff;gap:1.1rem}

/* Large pill with the arrow in its own disc, butted against the pill's edge. */
/* Pill and disc share one height token, so the circle can never become an oval
   however the size changes. aspect-ratio:1 off that height keeps it true. */
.btn-big{--big-h:clamp(3.8rem,6.4vh,5.4rem);display:inline-flex;align-items:center;gap:2px}
.btn-big__label{display:flex;align-items:center;height:var(--big-h);
  background:var(--bone);color:var(--ink);
  font-size:clamp(1.05rem,1.6vw,1.45rem);font-weight:500;letter-spacing:-.01em;
  padding-inline:clamp(2rem,3vw,3rem);border-radius:100px;
  transition:background .3s ease,color .3s ease}
.btn-big__disc{display:flex;align-items:center;justify-content:center;flex:none;
  height:var(--big-h);aspect-ratio:1;border-radius:50%;
  background:var(--bone);color:var(--ink);font-size:1.25rem;line-height:1;border:0;
  transition:background .3s ease,color .3s ease,transform .3s cubic-bezier(.16,1,.3,1)}
.btn-big:hover .btn-big__label,.btn-big:hover .btn-big__disc{background:var(--red);color:#fff}
.btn-big:hover .btn-big__disc{transform:translateX(4px)}

/* ---- contact ---- */
.contact{background:var(--dark);color:#fff;border-top:0}
.contact .eyebrow{color:rgba(255,255,255,.55)}
.contact .ccol .lbl{color:rgba(255,255,255,.5)}
.contact .ccol a,.contact .ccol .v{color:#fff}
.contact .ccol a:hover{color:var(--red)}
.contact .ccol a::after{background:var(--red)}
/* The only serif in this block — everything around it is sans. */
.cta-big{font-family:var(--disp);font-weight:200;font-size:clamp(3rem,15vw,13rem);line-height:.86;letter-spacing:-.05em;margin:.3em 0 .4em;display:inline-block}
/* The closing headline is the largest type on the site, so its mask needs more
   room than the shared default before descenders touch the edge. */
.cta-big .ln{padding-bottom:.16em}
.contact .row{display:flex;justify-content:space-between;flex-wrap:wrap;gap:2.5rem;margin-top:clamp(2rem,5vh,4rem)}
.ccol .lbl{font-family:var(--mono);font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--mute);margin-bottom:.7rem}
.ccol a,.ccol .v{font-family:var(--body);font-weight:500;font-size:clamp(1.05rem,1.6vw,1.35rem);letter-spacing:-.005em;display:inline-block;position:relative}
.ccol a::after{content:"";position:absolute;left:0;bottom:-2px;height:1px;width:0;background:var(--cyan);transition:width .3s}
.ccol a:hover::after,.ccol a:focus-visible::after{width:100%}
.ccol a:hover{color:var(--cyan)}


/* ---- case study (single project) ----
   Ruled five-column grid behind the page, oversized title, OVERVIEW band,
   facts column, gallery, neighbours. After swimduckswim.co/projects/jan. */
.case{position:relative}
.rules{position:absolute;inset:0;z-index:0;pointer-events:none;
  display:grid;grid-template-columns:repeat(5,1fr);padding-inline:var(--gut)}
.rules i{border-right:1px solid var(--line)}
.case-inner{position:relative;z-index:1}
@media(max-width:820px){.rules{grid-template-columns:repeat(2,1fr)}}

.case-head{padding-top:clamp(8rem,18vh,12rem)}
.case-head .eyebrow a:hover{color:var(--bone)}
.case-title{font-family:var(--disp);font-weight:200;
  font-size:clamp(3.5rem,14vw,13rem);line-height:.86;letter-spacing:-.05em;
  margin-top:clamp(1rem,3vh,2rem);text-transform:uppercase}

.case-hero{margin-top:clamp(2.5rem,7vh,5rem);overflow:hidden;background:var(--ink-3)}
.case-hero img{width:100%;height:auto}

.case-overview{display:grid;grid-template-columns:1.6fr 1fr;gap:clamp(2rem,6vw,6rem);
  padding-block:clamp(3.5rem,10vh,7rem);align-items:start}
.case-overview__quote{margin-top:1.6rem;font-size:clamp(1.1rem,1.5vw,1.35rem);color:var(--mute)}
.case-overview .lede{margin-top:0}
@media(max-width:820px){.case-overview{grid-template-columns:1fr}}

.case-facts{display:flex;flex-direction:column;gap:1.6rem;margin:0}
.case-facts dt{font-size:.78rem;letter-spacing:.16em;text-transform:uppercase;color:var(--mute);margin-bottom:.4rem}
.case-facts dd{font-weight:600;font-size:clamp(1rem,1.3vw,1.15rem);margin:0}
.case-palette{list-style:none;display:flex;gap:.8rem;margin-top:.2rem}
.case-palette li{display:flex;flex-direction:column;gap:.45rem}
.case-palette .sw{width:44px;aspect-ratio:1;border:1px solid var(--line);display:block}
.case-palette .hx{font-size:.62rem;letter-spacing:.05em;color:var(--mute)}

/* Top padding is separate from .case-overview's padding-block, which is shared
   top and bottom — raising that would push the intro text down too. */
.case-gallery-wrap{padding-top:clamp(2rem,5vh,3.5rem);padding-bottom:clamp(3.5rem,10vh,7rem)}
.case-gallery{--col-gap:clamp(1.2rem,3vw,2.4rem);display:grid;grid-template-columns:1fr 1fr;gap:var(--col-gap)}
/* No placeholder fill here: gallery images are often transparent PNGs, and a
   grey backdrop shows straight through them. The trade is a white gap rather
   than a grey one while a lazy image loads. */
.case-gallery figure{margin:0;overflow:hidden}
.case-gallery figure.is-wide{grid-column:1 / -1}
/* Edge to edge: cancels the .wrap gutter with a matching negative margin, so it
   tracks --gut at every breakpoint rather than assuming a fixed padding. */
.case-gallery figure.is-bleed{grid-column:1 / -1;margin-inline:calc(-1 * var(--gut))}
/* A half with no partner beside it: same width as a half column, centred on
   the row instead of sitting in the left one. Which image that is comes from
   ss_project_gallery_items(), since CSS cannot see how the halves pair up. */
.case-gallery figure.is-solo{grid-column:1 / -1;width:calc((100% - var(--col-gap)) / 2);margin-inline:auto}
.case-gallery img,.case-gallery video{width:100%;height:auto;display:block}
@media(max-width:680px){
  .case-gallery{grid-template-columns:1fr}
  /* One column, so nothing is ever unpaired. */
  .case-gallery figure.is-solo{width:auto}
}

.case-more{border-top:1px solid var(--line);padding-block:clamp(3.5rem,10vh,7rem)}
.case-more__title{font-family:var(--disp);font-weight:200;
  font-size:clamp(2rem,5.5vw,5rem);line-height:.96;letter-spacing:-.04em;text-transform:uppercase}
.case-more__grid{display:grid;grid-template-columns:repeat(12,1fr);
  column-gap:clamp(1rem,1.9vw,1.5rem);row-gap:clamp(3rem,7vh,5.5rem);
  margin-top:clamp(2.5rem,7vh,4.5rem)}
.case-more__all{margin-top:clamp(2.5rem,7vh,4.5rem);display:flex;justify-content:center}
@media(max-width:680px){.case-more__grid{grid-template-columns:1fr}}

/* ---- contact page ---- */
.ct-head{padding-top:clamp(8rem,18vh,12rem);padding-bottom:clamp(2.5rem,7vh,4.5rem)}
.ct-head .h1{margin-top:clamp(1rem,3vh,2rem)}
.ct-lede{max-width:38ch;margin-top:clamp(1.5rem,4vh,2.5rem);font-size:clamp(1.05rem,1.4vw,1.3rem);color:var(--mute);line-height:1.5}

.ct-body{display:grid;grid-template-columns:1fr 1.3fr;gap:clamp(2.5rem,7vw,7rem);
  padding-bottom:clamp(4rem,12vh,8rem);align-items:start}
@media(max-width:820px){.ct-body{grid-template-columns:1fr;gap:3rem}}

.ct-details{display:flex;flex-direction:column;gap:clamp(1.6rem,4vh,2.6rem);
  border-top:1px solid var(--line);padding-top:clamp(1.6rem,4vh,2.4rem)}
.ct-detail .lbl{font-size:.78rem;letter-spacing:.16em;text-transform:uppercase;color:var(--mute);margin-bottom:.5rem}
/* Sans — the page's only serif is the H1. */
.ct-detail a,.ct-detail .v{font-family:var(--body);font-weight:500;font-size:clamp(1.05rem,1.6vw,1.35rem);
  letter-spacing:-.005em;position:relative;display:inline-block}
.ct-detail a::after{content:"";position:absolute;left:0;bottom:-2px;height:1px;width:0;background:var(--red);transition:width .3s}
.ct-detail a:hover{color:var(--red)}
.ct-detail a:hover::after{width:100%}

.ct-form-wrap{border-top:1px solid var(--line);padding-top:clamp(1.6rem,4vh,2.4rem)}
.ct-notice{padding:.9rem 1.1rem;border-radius:8px;font-size:.95rem;margin-bottom:1.6rem}
.ct-notice--ok{background:#EAF6EE;color:#1E6B36}
.ct-notice--bad{background:#FDECEA;color:#A8271B}
body.is-dark .ct-notice--ok{background:#16301E;color:#8FD3A4}
body.is-dark .ct-notice--bad{background:#341A17;color:#F3A79C}

.ct-field{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1.5rem}
.ct-field label{font-size:.78rem;letter-spacing:.16em;text-transform:uppercase;color:var(--mute)}
.ct-field input,.ct-field textarea{font-family:var(--body);font-size:1.05rem;color:var(--bone);
  background:none;border:0;border-bottom:1px solid var(--line);padding:.7rem 0;width:100%;
  transition:border-color .25s ease}
.ct-field textarea{resize:vertical;min-height:8rem;line-height:1.5}
.ct-field input:focus,.ct-field textarea:focus{outline:none;border-bottom-color:var(--bone)}
.ct-field input::placeholder,.ct-field textarea::placeholder{color:var(--mute)}

/* Honeypot: off-screen rather than display:none, which some bots skip. */
/* Honeypot. Clipped rather than parked off-origin, for the same reason as
   the skip link — and it must stay in the layout so bots still fill it. */
.ct-hp{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}

.ct-submit{margin-top:clamp(1.5rem,4vh,2.5rem)}
.ct-submit .btn-big{border:0;background:none;padding:0;cursor:pointer;font-family:var(--body)}


/* ---- playground ----
   The page does not scroll: .is-playground locks the document and wheel/touch
   input drives four looping columns instead. Columns 1 and 3 travel up, 2 and 4
   travel down, each wrapping by exactly one run height so the loop never ends.
   Nothing is clickable and nothing reacts to hover — a wall to look at. */
body.is-playground{overflow:hidden;height:100%;overscroll-behavior:none}
html:has(body.is-playground){overflow:hidden;height:100%}

.pg{position:fixed;inset:0;background:var(--dark);color:#fff;overflow:hidden}

.pg-word{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  pointer-events:none;z-index:0;overflow:hidden}
.pg-word span{font-family:var(--disp);font-weight:800;font-size:clamp(3rem,13vw,13rem);
  letter-spacing:-.05em;color:#fff;white-space:nowrap;line-height:1}

.pg-cols{position:absolute;inset:0;z-index:1;display:grid;grid-template-columns:repeat(4,1fr);
  gap:clamp(1rem,2.4vw,2.6rem);padding-inline:var(--gut);align-items:start}
.pg-col{will-change:transform;transform:translate3d(0,var(--pg-y,0px),0)}
.pg-run{display:flex;flex-direction:column;align-items:center;gap:clamp(2rem,4.5vh,3.5rem)}
/* 85% of the column rather than filling it, so the wall breathes a little.
   .pg-run centres them, so the extra space splits evenly either side. */
.pg-thumb{margin:0;width:85%;aspect-ratio:1/1;overflow:hidden;background:rgba(255,255,255,.06)}
.pg-thumb img{width:100%;height:100%;object-fit:cover}


.pg-foot{position:absolute;bottom:clamp(1.5rem,4vh,2.5rem);left:0;right:0;z-index:3;display:flex;justify-content:center}
/* Light variant for the dark playground background. */
.btn-big--light .btn-big__label,.btn-big--light .btn-big__disc{background:#fff;color:var(--dark)}
.btn-big--light:hover .btn-big__label,.btn-big--light:hover .btn-big__disc{background:var(--red);color:#fff}

@media(max-width:900px){.pg-cols{grid-template-columns:repeat(2,1fr)}}


/* ==========================================================================
   7. ENGINE
   ========================================================================== */

/* Cursor-follow thumbnail on the services list */
#follow{position:fixed;left:0;top:0;z-index:60;pointer-events:none;width:clamp(168px,19vw,256px);aspect-ratio:1/1;opacity:0;overflow:hidden;will-change:transform,opacity;transition:opacity .35s ease;box-shadow:0 26px 60px rgba(20,20,15,.28)}
#follow.on{opacity:1}
#follow img{width:100%;height:100%;object-fit:cover;display:block}
@media (hover:none){#follow{display:none}}


/* ==========================================================================
   WP ADMIN BAR
   ========================================================================== */
body.admin-bar header.site{top:32px}
@media screen and (max-width:782px){body.admin-bar header.site{top:46px}}


/* ==========================================================================
   JOURNAL
   ========================================================================== */
.jr-head{padding-top:clamp(8rem,18vh,12rem);padding-bottom:clamp(2.5rem,7vh,4.5rem)}
.jr-head .h1{margin-top:clamp(1rem,3vh,2rem)}
.jr-lede{max-width:48ch;margin-top:clamp(1.5rem,4vh,2.5rem);color:var(--mute);font-size:clamp(1.05rem,1.4vw,1.3rem)}

.jr-list{padding-bottom:clamp(4rem,12vh,8rem)}
.jr-item{border-top:1px solid var(--line)}
.jr-item:last-of-type{border-bottom:1px solid var(--line)}
.jr-item__link{display:grid;grid-template-columns:180px 1fr;gap:clamp(1.5rem,4vw,4rem);
  padding-block:clamp(1.8rem,4.5vh,2.8rem);align-items:start}
.jr-item__meta{display:flex;flex-direction:column;gap:.3rem;font-size:.84rem;color:var(--mute)}
.jr-item__title{font-family:var(--disp);font-weight:400;font-size:clamp(1.5rem,3vw,2.6rem);
  letter-spacing:-.02em;line-height:1.12}
.jr-item__excerpt{margin-top:.8rem;color:var(--mute);max-width:62ch;font-size:1rem}
@media(max-width:680px){.jr-item__link{grid-template-columns:1fr;gap:.8rem}}

.jr-pagination{margin-top:clamp(2.5rem,7vh,4rem)}
.jr-pagination .nav-links{display:flex;gap:.5rem;justify-content:center;flex-wrap:wrap}
.jr-pagination .page-numbers{display:flex;align-items:center;justify-content:center;min-width:2.6rem;height:2.6rem;
  border:1px solid var(--line);border-radius:50%;font-size:.9rem;transition:background .25s,color .25s,border-color .25s}
.jr-pagination .page-numbers:hover,.jr-pagination .page-numbers.current{background:var(--bone);color:var(--ink);border-color:var(--bone)}

/* ---- article ---- */
.ar-head{padding-top:clamp(8rem,18vh,12rem)}
.ar-title{font-family:var(--disp);font-weight:200;font-size:clamp(2.2rem,5.5vw,5rem);
  line-height:1;letter-spacing:-.03em;margin-top:clamp(1rem,3vh,2rem);max-width:20ch}
.ar-meta{display:flex;gap:1.6rem;flex-wrap:wrap;margin-top:clamp(1.4rem,3.5vh,2.2rem);
  font-size:.86rem;color:var(--mute)}
.ar-meta a{color:var(--mute)}
.ar-meta a:hover{color:var(--red)}

.ar-hero{margin-top:clamp(2.5rem,7vh,5rem);overflow:hidden;background:var(--ink-3)}
.ar-hero img{width:100%;height:auto}

/* Readable measure regardless of the full-bleed container. */
.ar-body{padding-block:clamp(3rem,8vh,5rem)}
.ar-body > *{max-width:68ch}
.ar-body p{margin-bottom:1.4rem;font-size:clamp(1.05rem,1.25vw,1.18rem);line-height:1.7}
.ar-body h2{font-family:var(--disp);font-weight:400;font-size:clamp(1.6rem,2.6vw,2.3rem);
  letter-spacing:-.02em;line-height:1.2;margin:clamp(2.5rem,6vh,3.5rem) 0 1rem}
.ar-body h3{font-weight:600;font-size:clamp(1.15rem,1.6vw,1.4rem);margin:2rem 0 .8rem}
.ar-body ul,.ar-body ol{margin:0 0 1.4rem 1.2rem}
.ar-body li{margin-bottom:.5rem;line-height:1.65}
.ar-body a{text-decoration:underline;text-underline-offset:3px}
.ar-body a:hover{color:var(--red)}
.ar-body blockquote{border-left:2px solid var(--red);padding-left:1.4rem;margin:2rem 0;
  font-family:var(--disp);font-size:clamp(1.2rem,1.9vw,1.65rem);line-height:1.35}
.ar-body img,.ar-body figure{max-width:100%;margin:2rem 0}
.ar-body figcaption{font-size:.84rem;color:var(--mute);margin-top:.6rem}

.ar-nav{display:grid;grid-template-columns:1fr 1fr;gap:clamp(1.5rem,4vw,3rem);
  border-top:1px solid var(--line);padding-block:clamp(2.5rem,7vh,4rem)}
.ar-nav__item{display:flex;flex-direction:column;gap:.5rem}
.ar-nav__item--next{text-align:right}
.ar-nav__item .lbl{font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;color:var(--mute)}
.ar-nav__item .nm{font-family:var(--disp);font-weight:400;font-size:clamp(1.1rem,1.8vw,1.5rem);
  letter-spacing:-.015em;transition:color .3s}
.ar-nav__item:hover .nm{color:var(--red)}
@media(max-width:680px){.ar-nav{grid-template-columns:1fr}.ar-nav__item--next{text-align:left}}


/* ==========================================================================
   HERO CURSOR REVEAL
   Two identical stacked layers. The back one carries alternative copy and is
   masked to a circle that follows the pointer, so it is only visible through
   that circle. The circle grows while the pointer is over the headline.
   A radial-gradient mask is used rather than an SVG file — one less request,
   and mask-size/position animate on the compositor.
   ========================================================================== */
/* --reveal-size must be a registered property, not a plain custom property.
   The mask's position is derived from it (x - size/2 keeps the circle centred
   on the cursor), and an unregistered custom property snaps between values
   instead of interpolating — so the centre lurched by half the size change
   while mask-size eased. Registering it makes both animate in lockstep. */
@property --reveal-size{syntax:'<length>';inherits:true;initial-value:0px}

.hero-reveal{position:relative}
.hero-layer--front{position:relative;z-index:1}
.hero-layer--back{
  /* Extended beyond the row on ALL four sides so a large circle is never
     clipped by the layer's own edges; the matching padding puts the copy back
     exactly on the front layer's baseline and left edge. */
  position:absolute;inset:-220px;z-index:2;pointer-events:none;
  padding:220px;
  background:var(--bone);color:var(--ink);
  -webkit-mask-image:radial-gradient(circle, #000 68%, transparent 70%);
          mask-image:radial-gradient(circle, #000 68%, transparent 70%);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:var(--reveal-size,0px) var(--reveal-size,0px);
          mask-size:var(--reveal-size,0px) var(--reveal-size,0px);
  -webkit-mask-position:calc(var(--reveal-x,-999px) - var(--reveal-size,0px) / 2) calc(var(--reveal-y,-999px) - var(--reveal-size,0px) / 2);
          mask-position:calc(var(--reveal-x,-999px) - var(--reveal-size,0px) / 2) calc(var(--reveal-y,-999px) - var(--reveal-size,0px) / 2);
  /* Matching the reference: the circle swells quickly (0.2s) and relaxes back
     slowly (0.5s), both on a sine.out curve. Transitioning --reveal-size itself
     keeps mask-size and mask-position perfectly in step. Position is tweened
     in JS to trail the cursor. */
  transition:--reveal-size .5s cubic-bezier(.39,.575,.565,1);
}
.hero-layer--back.is-open{
  transition-duration:.2s;
}
/* Everything on the back layer stays in the flow — it holds the headline in
   register with the front — but nothing renders until the circle is open.
   While the dot is small it must read as a solid disc: light text caught inside
   a 30px circle disappears against the white page, which is why the dot seemed
   to vanish over the headline and the intro. */
.hero-layer--back .strapline,
.hero-layer--back .hero-intro,
.hero-layer--back .h1{visibility:hidden}
/* The alternative headline appears only once the mask has opened over it. */
.hero-layer--back.is-open .h1{visibility:visible}
.hero-layer--back .ln > span{transform:none;animation:none}

/* No pointer, no reveal: the alternative copy would be unreachable anyway. */
@media (hover:none),(prefers-reduced-motion:reduce){
  .hero-layer--back{display:none}
}
