/* =========================================================================
   Tuqa Lynch — portfolio
   Layout & header interaction after harryjatkins.com: a full-width name that
   collapses to its initials 2s after load and re-forms on hover (the
   re-appearing letters return in a contrasting serif italic). 2-column grid.
   ========================================================================= */
@font-face{
  font-family:"Canela Text";
  src:url("../fonts/CanelaText-Medium-Trial.woff2") format("woff2");
  font-weight:500;
  font-style:normal;
  font-display:swap;
}

:root{
  /* off-white / near-black — friendsofthenew.com's palette */
  --bg:#f4f4ef;
  --fg:#000;
  --muted:#726f67;
  --muted-2:#332f2a;
  --line:rgba(0,0,0,.14);
  --edge:30px;                 /* page margin */
  --sans:"Helvetica Neue", Helvetica, Arial, "Segoe UI", Roboto, sans-serif;
  --display:"Helvetica Neue", Helvetica, Arial, sans-serif;
  --serif:"Fraunces", Georgia, "Times New Roman", serif;
  --reveal:"Fraunces", Georgia, "Times New Roman", serif;
  /* the "Tuqa Lynch" wordmark itself (masthead / topbar / preloader) only —
     the rest of the serif copy stays on Fraunces */
  --mark:"Canela Text", "Fraunces", Georgia, "Times New Roman", serif;
  --ease:cubic-bezier(.22,1,.36,1);
}

*,*::before,*::after{box-sizing:border-box}
html{
  -webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  scroll-padding-top:78px;
}
/* -------------------------------------------------------------- cursor --- */
/* A circular outline follows the pointer instead of the system arrow.
   White + mix-blend-mode:difference so it inverts to read on both the
   light page and the dark sections (case-study intro bands, hero shots)
   without needing separate light/dark variants. Fine-pointer devices only —
   main.js positions it (instant, untransitioned) and toggles .is-active. */
@media (hover:hover) and (pointer:fine){
  html{cursor:none}
  a, button{cursor:none}
}
#cursor-dot{
  position:fixed;top:0;left:0;
  width:24px;height:24px;
  border:1.5px solid #fff;
  border-radius:50%;
  pointer-events:none;
  z-index:9999;
  mix-blend-mode:difference;
  transform:translate(-100px,-100px) translate(-50%,-50%);
  opacity:0;
  transition:opacity .2s ease;
}
#cursor-dot.is-active{opacity:1}
@media (hover:none), (pointer:coarse), (prefers-reduced-motion:reduce){
  #cursor-dot{display:none}
}
body{
  margin:0;
  background:var(--bg);
  color:var(--fg);
  font-family:var(--sans);
  font-size:15px;
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}

/* Fraunces at a light weight for all the serif running text (the heavy
   weight is reserved for the "Tuqa Lynch" wordmark itself, set separately) */
.topbar-nav a,
.meta-desc,
.meta-link,
.proj-idx,
.proj-summary,
.proj-role,
.about-body p,
.foot-big,
.foot-copy,
.cs-p,
.cs-note{
  font-weight:300;
}

/* ---------------------------------------------------------------- preload -- */
/* Black screen, "Tuqa Lynch" slides up into the masthead's own slot, the
   overlay then inverts to the site's colours and fades to reveal the page. */
.preload{
  position:fixed;inset:0;z-index:300;
  background:#000;
  padding:clamp(9px,1.7vw,20px) var(--edge) 0;
  overflow:hidden;
  transition:background-color .5s ease .1s;
}
/* Same flex / 50-50 split as .mark + .mark-word (and the same padding on
   .preload as .masthead), so "Lynch" sits at EXACTLY the pixel the real
   masthead will place it at — no jump when the overlay hands off to it. */
.preload-mark{
  display:flex;width:100%;
  font-family:var(--mark);font-weight:500;
  font-size:clamp(2.7rem,14.6vw,16rem);
  line-height:1;letter-spacing:-.02em;
  color:#fff;
  transform:translateY(42vh);
  transition:transform .9s cubic-bezier(.22,1,.36,1), color .5s ease .1s;
}
.preload-mark .pm-word{flex:1 1 50%;min-width:0;white-space:nowrap}
.preload.run .preload-mark{transform:translateY(0)}
.preload.invert{background:var(--bg)}
.preload.invert .preload-mark{color:var(--fg)}
.preload.done{
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .5s ease,visibility 0s .5s linear;
}
@media (prefers-reduced-motion:reduce){ .preload{display:none} }

/* ---------------------------------------------------------------- topbar --- */
/* slides in once the masthead has scrolled away (always shown on inner pages) */
.topbar{
  position:fixed;inset:0 0 auto 0;z-index:100;
  display:flex;align-items:center;justify-content:space-between;
  padding:12px var(--edge);
  background:rgba(244,244,239,.82);
  backdrop-filter:saturate(140%) blur(10px);
  -webkit-backdrop-filter:saturate(140%) blur(10px);
  border-bottom:1px solid transparent;
  transform:translateY(-101%);
  transition:transform .42s var(--ease),border-color .42s var(--ease);
}
.topbar.show{transform:none;border-bottom-color:var(--line)}
/* Literally a shrunken version of the big masthead: same data-words build,
   same .keep/.hl/is-collapsed system (see main.js), same edge-to-edge
   50/50-word split (so "L" lands at the same horizontal midpoint the big
   masthead's "L" does) — just smaller, and already collapsed from the start
   (data-collapse-delay="0") instead of collapsing 2s in. Absolutely
   positioned across the FULL bar (not a flex child sized to its own text)
   so it can span edge-to-edge without shoving the nav links aside; .topbar
   being position:fixed already gives it a containing block. The compound
   `.mark.topbar-mark` selector (not `.topbar-mark` alone) is what lets these
   sizing rules beat `.mark`'s own regardless of source order. */
.mark.topbar-mark{
  position:absolute;left:var(--edge);right:var(--edge);top:0;bottom:0;
  display:flex;align-items:center;width:auto;
  font-family:var(--mark);
  font-weight:500;font-size:16px;letter-spacing:-.01em;
  line-height:1;padding-bottom:0;
  white-space:nowrap;
}
/* Hovering ANYWHERE on the bar reveals it (not just the small "T"/"L"
   themselves) — the mark's own hit-area is the whole row width, but its
   glyphs sit at the far left/mid-point, so this is what actually makes
   touching any point of the bar trigger the same reveal the masthead has. */
@media (hover:hover){
  .topbar:hover .topbar-mark.is-collapsed .hl{opacity:1}
}
.topbar-nav{display:flex;gap:22px;position:relative;z-index:1;margin-left:auto}
.topbar-nav a{
  font-family:var(--serif);
  color:var(--muted-2);font-size:14px;transition:color .2s ease;
}
.topbar-nav a:hover{color:var(--fg)}

/* ---------------------------------------------------------------- masthead - */
.masthead{
  padding:clamp(9px,1.7vw,20px) var(--edge) 0;
  border-bottom:1px solid var(--line);
}
.mark{
  display:flex;align-items:baseline;
  width:100%;
  font-family:var(--mark);
  font-weight:500;
  font-size:clamp(2.7rem,14.6vw,16rem);
  line-height:1;
  letter-spacing:-.02em;
  padding-bottom:.09em;      /* fixed cushion — never changes with the font */
  cursor:pointer;
  user-select:none;
  -webkit-tap-highlight-color:transparent;
}
.mark-word{
  flex:1 1 50%;
  min-width:0;
  display:inline-block;
  white-space:nowrap;
}
/* Each letter is its own element (for the baseline lock below), so normal
   cross-letter kerning between "T"→u and "L"→y is lost; main.js measures the
   live gap (via canvas, against whatever font is actually rendering) and
   sets margin-left on the first revealed letter of each word to close it —
   done in JS, not a fixed value here, so it's correct both for the fallback
   font during the page-load font-swap and for Fraunces once it's ready. */
.mark .keep{
  display:inline-block;
  line-height:1;             /* the "T" / "L" alone define the line box */
  vertical-align:baseline;
}
/* Layout (width, line box, baseline) comes ENTIRELY from an invisible strut
   letter in the fixed T/L font, so the "T", the "L" and the rule under them
   never move. The visible revealed glyph is an absolutely-positioned <i>
   (zero layout impact) dropped onto the exact T/L baseline via --hl-dy
   (measured in main.js). Same face + weight as "T"/"L" — one heavy Fraunces
   wordmark throughout, whether collapsed or expanded. */
.mark .hl{
  position:relative;
  display:inline-block;
  line-height:1;
  vertical-align:baseline;
  text-transform:lowercase;
  color:transparent;
  transition:opacity .5s var(--ease);
  transition-delay:calc(var(--i,0) * 60ms);   /* slow left-to-right sweep */
}
.mark .hl > i{
  position:absolute;
  left:0;top:0;
  color:var(--fg);
  line-height:1;
  font-family:var(--mark);
  font-style:normal;
  font-weight:500;
  transform:translateY(var(--hl-dy, 0px));
  transform-origin:0 0;
}
/* collapsed: initials stay put, "uqa"/"ynch" fade out where they sit,
   left to right; hover (or focus / tap) fades them back in */
.mark.is-collapsed .hl{opacity:0}
@media (hover:hover){
  .mark.is-collapsed:hover .hl{opacity:1}
}
.mark.is-collapsed.is-shown .hl{opacity:1}
@media (prefers-reduced-motion:reduce){
  .mark .hl{transition:none;opacity:1}
  .mark .hl > i{
    transform:none;
    position:static;
  }
}

/* ---------------------------------------------------------------- meta ----- */
.meta{
  display:grid;grid-template-columns:1fr 1fr;gap:var(--edge);
  padding:8px var(--edge) 0;
}
.meta-desc{
  margin:0;max-width:42ch;
  color:var(--muted-2);
  font-family:var(--serif);          /* same face as the About copy */
  font-size:clamp(.82rem,.95vw,.95rem);line-height:1.5;
}
.meta-col:last-child{
  display:flex;flex-direction:column;align-items:flex-start;gap:5px;
}
.meta-link{
  font-family:var(--serif);
  font-size:clamp(.82rem,.95vw,.95rem);color:var(--muted-2);
  transition:color .2s ease;
}
.meta-link:hover{color:var(--fg)}
.meta-ext{display:inline-flex;align-items:center;gap:5px}
.ext-arrow{
  width:.78em;height:.78em;flex:none;
  transition:transform .2s var(--ease);
}
.meta-ext:hover .ext-arrow{transform:translate(2px,-2px)}

/* ---------------------------------------------------------------- projects - */
/* Rows of two flush images, one ~2.6x the other. Everything that resizes on
   hover — the image, its caption, its index number — is driven purely by
   `transform` (scale/translate), never layout. The two .proj panels are a
   permanently-fixed 50/50 flex split: nothing about their OWN box ever
   animates, so a hover never triggers layout/reflow — only a GPU-composited
   transform, which is the fastest thing a browser can animate. main.js sets
   .hovering on the row and .is-active on the panel under the cursor. */
.projects{
  --proj-gap:clamp(50px,8vh,110px);
  display:flex;flex-direction:column;
  gap:var(--proj-gap);
  padding:clamp(84px,15vh,190px) var(--edge) 0;
}
.proj-row{
  display:flex;align-items:flex-start;   /* top-aligned; the small panel is
                                            shorter than the big one */
  /* Lock the row's box to the big-panel size (+ caption room) so the page
     below NEVER reflows while a panel resizes. content-visibility skips
     layout/paint entirely for rows off-screen while scrolling;
     contain-intrinsic-size reuses the same formula so nothing jumps once
     it's measured. */
  min-height:calc((100vw - 2 * var(--edge)) * .41 + 124px);
  content-visibility:auto;
  contain-intrinsic-size:0 calc((100vw - 2 * var(--edge)) * .41 + 124px);
  contain:layout;
}
.proj{position:relative;flex:1 1 0;min-width:0;display:block}   /* fixed 50/50, never animates */

/* fixed ratio — the box itself never resizes; scale + translateX (both
   compositor-only) make it LOOK like it's ~38% or ~62% of the row, flush
   against its sibling in both states. transform-origin stays at the true
   left edge for both panels so one consistent pair of values per state
   works (no origin-swap mid-transition, which would jump). */
.proj-media{
  display:block;position:relative;overflow:hidden;
  width:100%;aspect-ratio:3/2;
  background:#fff;
  transform-origin:0 0;
  transition:transform .34s cubic-bezier(.4,0,.2,1);
}
.proj:nth-child(1) .proj-media{transform:scale(.7692)}                                  /* rest: small */
.proj-row.hovering > .proj:nth-child(1).is-active .proj-media{transform:scale(1.2308)}  /* hover: big */
.proj:nth-child(2) .proj-media{transform:translateX(-23.08%) scale(1.2308)}             /* rest: big */
.proj-row.hovering > .proj:nth-child(2):not(.is-active) .proj-media{transform:translateX(23.08%) scale(.7692)} /* hover: small */

.proj-media img{
  width:100%;height:100%;object-fit:cover;display:block;
  filter:grayscale(1);
  transition:filter .45s ease;
}
.proj.is-active .proj-media img{filter:grayscale(0)}
/* a handful of projects show a short looping clip instead of the flicked
   frames — it sits over the still (the "laptop mockup") and only crossfades
   in once hovered, so at rest the tile is just the static image */
.proj-video{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .3s ease;
}
.proj.is-active .proj-video{opacity:1}
/* darken overlay — compositor-only opacity swap (cheap) */
.proj-media::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:rgba(10,10,10,.3);
  transition:opacity .3s ease;
}
.proj.is-active .proj-media::after{opacity:0}

/* tracks .proj-media's actual (transformed) right edge via the same
   translateX distance, in px rather than %, since the badge's own box is
   far too small for a meaningful percentage — no scale, so the number
   stays a constant size */
.proj-idx{
  position:absolute;top:12px;right:15px;z-index:2;
  font-family:var(--serif);font-size:13px;
  color:rgba(255,255,255,.85);text-shadow:0 1px 8px rgba(0,0,0,.55);
  font-variant-numeric:tabular-nums;
  transition:transform .34s cubic-bezier(.4,0,.2,1);
}
/* child 2's image is always flush to the ROW's own right edge in both its
   small and big state, so its idx (anchored via right:15px, relative to
   .proj's own fixed right edge, which IS the row's right edge) never needs
   to move — only child 1's idx has to chase its image's changing right edge */
.proj:nth-child(1) .proj-idx{transform:translateX(calc((100vw - 2 * var(--edge)) * -.11538))}
.proj-row.hovering > .proj:nth-child(1).is-active .proj-idx{transform:translateX(calc((100vw - 2 * var(--edge)) * .11538))}

/* the caption sits in flow at the top of .proj, then translateY drops it to
   sit right under .proj-media's current (transformed) bottom edge — a pixel
   value from the same row-width formula the row height itself is built on.
   `width` is set per-state below to the CURRENT image's actual visual width
   (small .3846 / big .6154 of the row), so the title/summary/role wrap to
   fit whichever size the image is right now instead of assuming one fixed
   line length — translateX is therefore also a px formula (not a % of the
   box's own width) so it stays correct regardless of that width. */
.proj-cap{
  position:absolute;top:0;left:0;
  padding-top:14px;padding-left:2px;padding-right:10px;
  display:flex;flex-direction:column;gap:3px;
  color:var(--muted);
  transition:color .3s ease, transform .34s cubic-bezier(.4,0,.2,1),
             width .34s cubic-bezier(.4,0,.2,1), padding-left .34s cubic-bezier(.4,0,.2,1);
}
.proj.is-active .proj-cap{color:var(--fg)}
.proj:nth-child(1) .proj-cap{
  width:calc((100vw - 2 * var(--edge)) * .3846);
  transform:translateY(calc((100vw - 2 * var(--edge)) * .25641));
}
.proj-row.hovering > .proj:nth-child(1).is-active .proj-cap{
  width:calc((100vw - 2 * var(--edge)) * .6154);
  transform:translateY(calc((100vw - 2 * var(--edge)) * .41026));
}
/* child 2's image isn't flush to ITS OWN box's left edge like child 1's is —
   it slides so the caption's left edge always matches the image's actual
   (current) left edge, big or small, instead of sitting at a fixed spot
   while the image slides under it. Small state also gets extra left padding
   (a margin from the seam against the now-enlarged left image) — it eats
   into the already-narrower width, which helps force the earlier wrap too. */
.proj:nth-child(2) .proj-cap{
  width:calc((100vw - 2 * var(--edge)) * .6154);
  transform:translateY(calc((100vw - 2 * var(--edge)) * .41026))
            translateX(calc((100vw - 2 * var(--edge)) * -.11538));
}
.proj-row.hovering > .proj:nth-child(2):not(.is-active) .proj-cap{
  width:calc((100vw - 2 * var(--edge)) * .3846);
  padding-left:18px;
  transform:translateY(calc((100vw - 2 * var(--edge)) * .25641))
            translateX(calc((100vw - 2 * var(--edge)) * .11538));
}
.proj-title{
  font-weight:500;letter-spacing:-.01em;
  font-size:clamp(.95rem,1.4vw,1.3rem);color:inherit;
}
.proj-summary{
  font-family:var(--serif);
  font-size:clamp(.92rem,1vw,1.04rem);line-height:1.35;
  color:var(--muted-2);max-width:42ch;
  transition:color .3s ease;
}
.proj.is-active .proj-summary{color:var(--fg)}
.proj-role{
  font-family:var(--serif);font-size:13px;color:inherit;opacity:.8;
}
@media (max-width:720px){
  .proj-row{flex-direction:column;min-height:0;contain:none;content-visibility:visible}
  .proj{flex:none}
  .proj-media,.proj:nth-child(1) .proj-media,.proj:nth-child(2) .proj-media,
  .proj-row.hovering > .proj .proj-media{aspect-ratio:5/4;transform:none}
  .proj-idx,.proj:nth-child(1) .proj-idx,.proj:nth-child(2) .proj-idx,
  .proj-row.hovering > .proj .proj-idx{transform:none}
  .proj-cap{position:static;padding-top:14px;padding-left:2px;width:auto}
  .proj:nth-child(1) .proj-cap,.proj:nth-child(2) .proj-cap,
  .proj-row.hovering > .proj .proj-cap{transform:none;width:auto;padding-left:2px}
}

/* ---------------------------------------------------------------- about ---- */
.about{
  display:grid;grid-template-columns:1fr 1fr;gap:var(--edge);
  padding:clamp(90px,16vh,200px) var(--edge) 0;
}
.about-h{
  margin:0;
  font-weight:700;text-transform:uppercase;letter-spacing:-.02em;
  font-size:clamp(1.4rem,3vw,2.4rem);line-height:1.02;
  position:sticky;top:78px;align-self:start;
}
.about-photo{
  margin-top:clamp(28px,4vh,44px);
  max-width:260px;
  position:sticky;top:calc(78px + clamp(1.4rem,3vw,2.4rem) * 1.02 + clamp(28px,4vh,44px));
}
.about-photo img{
  width:100%;height:auto;display:block;background:#fff;
  filter:grayscale(1);
  transform:rotate(0deg);
  transition:filter .5s ease, transform .5s ease;
}
.about-photo:hover img{
  filter:grayscale(0);
  transform:rotate(-3deg);
}
.about-body{max-width:60ch}
.about-body p{
  font-family:var(--serif);
  font-size:clamp(1rem,1.2vw,1.12rem);line-height:1.62;
  color:var(--muted-2);margin:0 0 1.1em;
}
.about-body p:first-child{color:var(--fg)}
.about-cta{
  display:inline-block;margin-top:4px;
  font-size:13px;color:var(--muted-2);
  border-bottom:1px solid var(--line);padding-bottom:3px;
  transition:border-color .2s ease,color .2s ease;
}
.about-cta:hover{border-color:var(--fg);color:var(--fg)}
.tools{margin-top:clamp(34px,5vh,54px)}
.tools h3{
  font-weight:400;font-size:12px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--muted);margin:0 0 16px;
}
.tool-list{display:flex;flex-wrap:wrap;gap:8px;padding:0;margin:0}
.tool-list li{
  list-style:none;display:inline-flex;align-items:center;gap:8px;
  font-size:12px;letter-spacing:.02em;color:var(--muted-2);
  border:1px solid var(--line);border-radius:999px;padding:6px 13px 6px 11px;
}
.tool-ico{width:14px;height:14px;flex:none;fill:currentColor;color:var(--muted)}
.about-body p.about-open{
  font-family:var(--serif);font-weight:600;
  font-size:clamp(1rem,1.2vw,1.12rem);line-height:1.62;
  color:var(--fg);margin:0 0 1.1em;
}

/* ---------------------------------------------------------------- footer --- */
.site-foot{
  margin-top:clamp(90px,16vh,180px);
  padding:clamp(50px,9vh,96px) var(--edge) 46px;
  border-top:1px solid var(--line);
}
.foot-inner{display:flex;flex-direction:column;gap:2px}
.foot-big{
  font-family:var(--serif);
  font-size:clamp(1.9rem,6vw,4.4rem);line-height:1.16;letter-spacing:-.01em;
  width:fit-content;max-width:100%;color:var(--muted-2);
  overflow-wrap:break-word;word-break:break-word;
  transition:color .2s ease;
}
.foot-big:hover{color:var(--fg)}
.foot-copy{
  margin:34px 0 0;
  font-family:var(--serif);font-size:14px;letter-spacing:.01em;
  color:var(--muted);
}

/* ---------------------------------------------------------------- reveal --- */
.reveal{opacity:0;transform:translateY(24px);transition:opacity .8s ease,transform .8s var(--ease)}
.reveal.in{opacity:1;transform:none}

/* =========================================================================
   Case-study pages
   ========================================================================= */
.case-study .cs-wrap{
  padding:78px var(--edge) 0;
}
/* Some projects' shots already sit on plain white (see figure.shot img /
   .cs-hero-full img above) — on those, the site's usual off-white page
   background left a visible seam around them. cs-white makes the whole
   page pure white instead, so everything after the black intro band reads
   as one continuous surface with the images rather than a framed block. */
body.cs-white{background:#fff}
.cs-back{
  display:inline-block;margin-top:22px;
  font-size:13px;color:var(--muted);transition:color .2s ease;
}
.cs-back:hover{color:var(--fg)}
.cs-title-block{margin:22px 0 clamp(30px,5vh,52px)}
.cs-title{
  font-family:var(--display);font-weight:700;letter-spacing:-.03em;
  font-size:clamp(2.1rem,6.5vw,5rem);line-height:1;margin:0;
  text-transform:uppercase;
}
.cs-label{
  font-size:13px;letter-spacing:.03em;color:var(--muted);margin:14px 0 0;
}
.cs-hero{margin:0;width:var(--w,100%)}
.cs-hero img{width:100%;height:auto;display:block;background:#fff}

/* full-bleed hero — breaks out of .cs-wrap's edge padding */
.cs-hero-full{
  margin:0 calc(-1 * var(--edge)) 0;
  width:calc(100% + 2 * var(--edge));
}
.cs-hero-full img,.cs-hero-full video{width:100%;height:auto;display:block;background:#fff}
.cs-hero-full figcaption{padding:0 var(--edge)}

/* dark intro band — project name + one-line descriptor, then a large pull
   quote (the case study's own overview paragraph) */
.cs-intro{
  margin:clamp(28px,5vh,56px) calc(-1 * var(--edge)) 0;
  padding:clamp(28px,5vh,56px) var(--edge);
  background:#000;color:#fff;
}
.cs-intro-tag{
  font-size:12px;letter-spacing:.03em;color:rgba(255,255,255,.55);
  margin:0 0 clamp(20px,4vh,40px);
}
.cs-intro-tag span{display:block;margin-top:2px}
.cs-intro-text{
  font-family:var(--serif);font-weight:300;
  font-size:clamp(.92rem,1.3vw,1.15rem);line-height:1.5;
  max-width:44ch;margin:0;color:#fff;
}

/* key–value meta table */
.cs-meta{
  width:100%;border-collapse:collapse;
  margin:clamp(28px,5vh,56px) 0 0;
  font-size:14px;
}
.cs-meta tr{border-bottom:1px solid var(--line)}
.cs-meta tr:first-child{border-top:1px solid var(--line)}
.cs-meta th,.cs-meta td{
  text-align:left;font-weight:400;padding:12px 10px;
}
.cs-meta th{font-family:var(--serif);font-weight:500}
.cs-meta td:first-child{color:var(--muted);width:14ch}
.cs-meta th{width:26ch}
.cs-meta td:last-child{color:var(--muted-2);font-family:var(--serif);font-weight:300}
figcaption,.row-cap{
  font-size:12px;letter-spacing:.02em;color:var(--muted);margin-top:10px;
}
.media-inner > .row-cap{margin-top:12px}
.cs-body{margin-top:clamp(46px,8vh,84px)}
.cs-h{
  font-family:var(--display);font-weight:700;letter-spacing:-.02em;
  font-size:clamp(1.5rem,3vw,2.2rem);line-height:1.1;
  text-transform:uppercase;
  margin:clamp(52px,9vh,92px) 0 20px;
}
.cs-body > .cs-h:first-child{margin-top:0}
.cs-p{
  font-family:var(--serif);
  font-size:clamp(1.02rem,1.4vw,1.2rem);line-height:1.66;
  color:var(--muted-2);max-width:66ch;margin:0 0 1.1em;
}
.cs-tags{
  font-size:12px;letter-spacing:.03em;color:var(--muted);margin:2px 0 6px;
}
.cs-note{
  font-family:var(--serif);font-style:italic;color:var(--muted);
  max-width:66ch;margin:0 0 1em;
}
.media-row{
  margin:clamp(34px,6vh,60px) 0 0;display:flex;
  /* skip layout/paint/style work for rows nowhere near the viewport — the
     biggest single win on the long case-study pages, which can carry a
     dozen+ image rows below the fold */
  content-visibility:auto;
  contain-intrinsic-size:0 520px;
}
.media-row[data-align="center"]{justify-content:center}
.media-row[data-align="right"]{justify-content:flex-end}
.media-inner{width:var(--w,100%);display:grid;gap:var(--edge)}
.media-inner.cols-1{grid-template-columns:1fr}
.media-inner.cols-2{grid-template-columns:repeat(2,1fr)}
.media-inner.cols-3{grid-template-columns:repeat(3,1fr)}
.media-inner.cols-4{grid-template-columns:repeat(4,1fr);gap:18px}
.media-inner.cols-5{grid-template-columns:repeat(5,1fr);gap:18px}
.media-inner > .row-cap{grid-column:1/-1}
figure.shot{margin:0}
/* dark card for embedded SVG animations lifted from the live site — their
   strokes/text are white, drawn for a dark ground, so the wrapper carries
   its own bg rather than inheriting the (often light) page background */
.cs-anim-card{
  background:#0a0a0a;border-radius:6px;
  padding:clamp(20px,4vw,40px);
  display:flex;flex-wrap:wrap;gap:clamp(20px,3vw,32px);
  align-items:center;justify-content:center;
}
.cs-anim-card > div{flex:1 1 260px;min-width:0;max-width:560px}
figure.shot img,figure.shot video{width:100%;height:auto;display:block;background:#fff}
/* a smaller image whose own background is a solid colour, bled full-width
   so that colour reads as two flanking blocks either side of it — the
   image's edges disappear into the matching background instead of sitting
   in a framed column */
.cs-flush-band{
  margin:0 calc(-1 * var(--edge));
  width:calc(100% + 2 * var(--edge));
  display:flex;justify-content:center;align-items:center;
  padding:clamp(28px,5vh,56px) var(--edge);
}
.cs-flush-band img{max-width:900px;width:100%;display:block;background:none}

/* ---------------------------------------------------------------- responsive */
@media (max-width:900px){
  :root{--edge:22px}
}
@media (max-width:1000px){
  .media-row[data-align]{justify-content:flex-start}
  .media-inner{width:100%!important}
  .cs-hero{width:100%!important}
  .media-inner.cols-3{grid-template-columns:repeat(2,1fr)}
  .media-inner.cols-4,.media-inner.cols-5{grid-template-columns:repeat(3,1fr)}
}
@media (max-width:720px){
  .meta{grid-template-columns:1fr;gap:16px}
  .projects{grid-template-columns:1fr;padding-top:clamp(40px,8vh,72px)}
  .proj-row{gap:var(--proj-gap)}
  .about{grid-template-columns:1fr;gap:14px}
  .about-h{position:static}
  .about-photo{position:static;max-width:200px}
  .foot-big{font-size:clamp(1.3rem,7.5vw,1.9rem)}
}
@media (max-width:640px){
  .media-inner.cols-2,.media-inner.cols-3,.media-inner.cols-4,.media-inner.cols-5{grid-template-columns:repeat(2,1fr)}
  /* per-row overrides of the generic collapse above — set via R(..., mobile_cols=N) */
  .cs-body .media-inner.mcols-1{grid-template-columns:1fr}
  .cs-body .media-inner.mcols-2{grid-template-columns:repeat(2,1fr)}
  .cs-body .media-inner.mcols-3{grid-template-columns:repeat(3,1fr)}
}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .reveal{opacity:1;transform:none;transition:none}
}
