/* ============================================================
   AVREA site.css - GENERATED BUNDLE. Do not edit this file.
   ONE stylesheet for every page: sources concatenated in the exact order the pages
   used to link them, so the cascade is byte-for-byte what it was. Contact and
   Privacy carry ~44KB of overlay rules they never use - that is deliberate. A second
   slimmer bundle meant a page-type switch paid a fresh render-blocking download of
   CSS the visitor already had; one file is cached once and never re-fetched.
   Edit the SOURCE file in avrea/ and rebuild - edits here are lost on rebuild.
   ============================================================ */
/* ============================================================
   Host Grotesk - SELF-HOSTED (avrea/fonts/). Was a render-blocking stylesheet on
   fonts.googleapis.com, which cost a DNS + TLS + CSS round trip before first paint
   on every navigation. Google ships this face as ONE variable file per subset
   covering 300-700, so every weight the site uses - 350, 400, 450, 500, 550, 600,
   700 - comes from a single 20KB file. Nothing to drop: there are no per-weight
   files to leave out. Declared here in the bundle so it costs no extra request.
   ============================================================ */
@font-face{ font-family:'Host Grotesk'; font-style:normal; font-weight:300 700; font-display:swap;
  src:url("fonts/host-grotesk-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face{ font-family:'Host Grotesk'; font-style:normal; font-weight:300 700; font-display:swap;
  src:url("fonts/host-grotesk-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }


/* ==== source: v27.css ==== */
/* ============================================================
   AVREA — v27 monochrome editorial rebuild
   Pure grayscale: black / white / gray. No gold, anywhere.
   ============================================================ */
*{ box-sizing:border-box; }

:root{
  --bg:        #0a0a0a;
  --panel:     #101010;
  --panel-2:   #161616;
  --panel-3:   #1d1d1d;
  --line:      rgba(255,255,255,0.10);
  --line-2:    rgba(255,255,255,0.18);
  --ink:       #f4f4f2;
  --muted:     rgba(255,255,255,0.60);
  --faint:     rgba(255,255,255,0.34);
  --sans: 'Host Grotesk', system-ui, sans-serif;
  --gutter: clamp(14px, 2.6vw, 34px);
  /* one interaction language - every transition uses these */
  --t-fast:.2s; --t-line:.35s; --t-fill:.35s; --t-page:.35s; --t-media:.35s; --t-slow:.35s; --t-xslow:1s;
  --ix-ease:cubic-bezier(.22,.61,.36,1); --ix-ease-out:cubic-bezier(.22,.61,.36,1);
  --radius: clamp(22px, 2.4vw, 34px);
  --maxw: 1320px;
}
html{ background:var(--bg); -webkit-text-size-adjust:100%; scroll-behavior:smooth; scrollbar-width:none; -ms-overflow-style:none; }
/* overflow-x:clip REMOVED here too - see site.css. Legacy source file, deleted so a
   re-concat cannot reintroduce it. No overflow on html or body, ever. */
/* native scrollbar fully hidden (zero layout width) — replaced by a thin custom
   OVERLAY scrollbar in v27.js, so the page never shifts sideways when overflow toggles */
html::-webkit-scrollbar{ width:0; height:0; display:none; }
body{
  margin:0; background:var(--bg); color:var(--ink);
  font-family:var(--sans); font-weight:400; letter-spacing:-0.01em;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  /* NO overflow here. overflow-x:hidden on body forces overflow-y to auto, which
     turns body into a nested scroll container and left Lenis measuring limit 0 on
     Works (dead wheel). The guard lives on html as overflow-x:clip instead. */
}
a{ color:inherit; text-decoration:none; }
img,video{ display:block; max-width:100%; }
::selection{ background:#fff; color:#000; }

/* signature emphasis: decorative serif word inside a sans headline */
.ser{ font-family:var(--sans); font-weight:400; letter-spacing:0; }

/* ---- shared shell ---- */
.shell{ max-width:var(--maxw); margin:0 auto; padding:0 var(--gutter); }
section{ position:relative; }

/* pill label */
.pill{
  display:inline-flex; align-items:center; gap:8px;
  font-size:12px; font-weight:500; letter-spacing:0.04em;
  color:var(--muted); background:var(--panel-2);
  border:1px solid var(--line); border-radius:999px;
  padding:8px 16px; line-height:1;
}
.pill .dotk{ width:5px; height:5px; border-radius:50%; background:#fff; opacity:.7; }

/* section heading block */
.sec-head{ display:flex; flex-direction:column; align-items:center; gap:20px; text-align:center; margin-bottom:clamp(44px,5vw,72px); }
.sec-head.left{ align-items:flex-start; text-align:left; }
.sec-h{
  font-family:var(--sans); font-weight:500;
  font-size:clamp(34px,5.2vw,66px); line-height:0.98; letter-spacing:-0.035em;
  margin:0; text-wrap:balance;
}
.sec-h .ser{ font-size:1.06em; }
.sec-sub{ color:var(--muted); font-size:clamp(16px,1.4vw,18px); line-height:1.6; max-width:48ch; margin:0; font-weight:350; }

/* generic glass button */
.btn{
  display:inline-flex; align-items:center; gap:8px;
  font-family:var(--sans); font-size:14px; font-weight:500; letter-spacing:-0.005em; line-height:1;
  padding:12px 20px; border-radius:999px; border:1px solid var(--line-2);
  background:var(--panel-2); color:var(--ink);
  transition:transform var(--t-line) var(--ix-ease-out), background var(--t-line) var(--ix-ease), border-color var(--t-line) var(--ix-ease), color var(--t-line) var(--ix-ease);
}
.btn svg{ width:16px; height:16px; transition:transform var(--t-line) var(--ix-ease-out); }
.btn:hover{ transform:translateY(-2px); border-color:rgba(255,255,255,0.4); }
.btn:hover svg{ transform:translate(2px,-2px); }
.btn--solid{ background:#f4f4f2; color:#0a0a0a; border-color:#f4f4f2; }
.btn--solid:hover{ background:#fff; }

/* reveal-on-scroll — graceful fade + visible slide-up, one-time as each element enters view */
/* reveal-on-scroll: opacity + a short lift, one-time as each element enters view.
   Gated on html.rv-on, which an inline head script sets before first paint - so the
   hidden state exists only when the script that will un-hide it is running. If JS
   never boots (or stalls), rv-on is dropped and every .rv is plain visible content. */
html.rv-on .rv{ opacity:0; transform:translateY(20px); will-change:opacity, transform;
  transition:opacity var(--t-rv) var(--ix-ease-out), transform var(--t-rv) var(--ix-ease-out); }
html.rv-on .rv.in{ opacity:1; transform:none; }
/* cascade for grouped items, one --rv-step (70ms) apart in reading order */
html.rv-on .rv.rv-d1{ transition-delay:calc(var(--rv-step) * 1); }
html.rv-on .rv.rv-d2{ transition-delay:calc(var(--rv-step) * 2); }
html.rv-on .rv.rv-d3{ transition-delay:calc(var(--rv-step) * 3); }
html.rv-on .rv.rv-d4{ transition-delay:calc(var(--rv-step) * 4); }
html.rv-on .rv.rv-d5{ transition-delay:calc(var(--rv-step) * 5); }
/* rv-soft lifts with the independent `translate` property instead of `transform`,
   so a card's own hover transform is never clobbered by the reveal. Same motion. */
html.rv-on .rv.rv-soft{ opacity:0; transform:none; filter:none; translate:0 20px;
  transition:opacity var(--t-rv) var(--ix-ease-out), translate var(--t-rv) var(--ix-ease-out),
             transform var(--t-fill) var(--ix-ease-out); }
html.rv-on .rv.rv-soft.in{ opacity:1; translate:0 0; }
/* phones: shorter travel, same timing */
@media (max-width:820px){
  html.rv-on .rv{ transform:translateY(16px); }
  html.rv-on .rv.rv-soft{ transform:none; translate:0 16px; }
}
@media (prefers-reduced-motion: reduce){
  html.rv-on .rv,html.rv-on .rv.rv-soft{ opacity:1 !important; transform:none !important;
    translate:0 0 !important; filter:none !important; transition:none !important; will-change:auto !important; }
}

/* ============================================================
   1. HERO
   ============================================================ */
.hero{ padding:var(--gutter); }
.hero-card{
  position:relative; overflow:hidden; isolation:isolate;
  border:1px solid var(--line); border-radius:var(--radius);
  background:
    radial-gradient(120% 90% at 50% 8%, #1a1a1a 0%, #0d0d0d 46%, #070707 100%);
  min-height:calc(100svh - 2*var(--gutter));
  padding:clamp(20px,2.4vw,30px);
  display:flex; flex-direction:column;
}
.hero-grain{ position:absolute; inset:0; z-index:0; pointer-events:none; opacity:.5;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.4'/%3E%3C/svg%3E");
  mix-blend-mode:overlay; }
.hero-card > *{ position:relative; z-index:2; }

/* floating pill nav */
.hnav{ position:absolute; top:clamp(20px,2.4vw,30px); left:50%; transform:translateX(-50%); z-index:30;
  display:flex; align-items:center; gap:2px;
  background:rgba(20,20,20,0.72); backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
  border:1px solid var(--line); border-radius:999px; padding:7px; }
.hnav a{ font-size:15.5px; font-weight:450; color:var(--muted); padding:11px 19px; border-radius:999px; transition:color var(--t-fast), background var(--t-fast); }
.hnav a:hover{ color:#fff; background:rgba(255,255,255,0.06); }
@media (max-width:760px){ .hnav{ backdrop-filter:none; -webkit-backdrop-filter:none; background:rgba(18,18,18,0.94); } }

/* ---- sticky nav: appears once the hero has scrolled out of view ---- */
.snav{ position:fixed; top:0; left:0; right:0; z-index:90;
  background:rgba(12,12,12,0.78); backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px);
  border-bottom:1px solid var(--line);
  opacity:0; transform:translateY(-100%); pointer-events:none;
  transition:opacity var(--t-page) var(--ix-ease-out), transform var(--t-page) var(--ix-ease-out); }
.snav.show{ opacity:1; transform:translateY(0); pointer-events:auto; }
.snav-inner{ display:flex; align-items:center; justify-content:space-between; gap:20px;
  max-width:var(--maxw,1400px); margin:0 auto; padding:12px var(--gutter); }
.snav-brand{ font-size:19px; }
.snav-links{ display:flex; align-items:center; gap:2px; }
.snav-links a{ font-size:15px; font-weight:450; color:var(--muted); padding:9px 16px; border-radius:999px; transition:color var(--t-fast), background var(--t-fast); }
.snav-links a:hover{ color:#fff; background:rgba(255,255,255,0.06); }
.snav-actions{ display:flex; align-items:center; gap:10px; }
.snav .hburger{ display:none; }
@media (max-width:760px){
  .snav{ backdrop-filter:none; -webkit-backdrop-filter:none; background:rgba(14,14,14,0.96); }
  .snav-links{ display:none; }
  .snav .hburger{ display:inline-flex; flex-direction:column; justify-content:center; gap:5px;
    width:44px; height:44px; padding:0 10px; box-sizing:border-box;
    background:rgba(255,255,255,0.05); border:1px solid var(--line); border-radius:12px; }
  .snav .hburger span{ display:block; width:22px; height:2px; border-radius:2px; background:#fff;
    transition:transform var(--t-page) var(--ix-ease-out), opacity var(--t-fast) var(--ix-ease); }
  .snav .hburger.is-active span:nth-child(1){ transform:translateY(7px) rotate(45deg); }
  .snav .hburger.is-active span:nth-child(2){ opacity:0; }
  .snav .hburger.is-active span:nth-child(3){ transform:translateY(-7px) rotate(-45deg); }
}
@media (prefers-reduced-motion: reduce){
  .snav{ transition:opacity var(--t-fast) linear; transform:none; }
  .snav.show{ transform:none; }
}

.hbrand{ display:inline-flex; align-items:center; gap:9px; font-weight:600; font-size:21px; letter-spacing:-0.04em; color:#fff; }
.hbrand .logo-mark{ height:0.92em; width:auto; }
.hbrand .logo-mark path{ fill:currentColor; }

.htop{ display:flex; align-items:flex-start; justify-content:space-between; gap:20px; }
.hbook{ }

/* hero body grid: corners anchored, sphere centered */
.hbody{ flex:1; position:relative; display:flex; flex-direction:column; justify-content:center; align-items:center;
  padding:clamp(40px,7vw,90px) 0 clamp(20px,3vw,40px); }
/* dark vignette scrim over the sphere so headline + sub stay legible */
.hbody::before{ content:""; position:absolute; inset:-10% -20%; z-index:3; pointer-events:none;
  background:radial-gradient(54% 56% at 50% 44%, rgba(8,8,8,0.8) 0%, rgba(8,8,8,0.55) 38%, rgba(8,8,8,0.12) 66%, rgba(8,8,8,0) 80%); }

.hheadline{
  position:relative; z-index:5; text-align:center; margin:0; padding-bottom:0.22em;
  font-family:var(--sans); font-weight:500; color:#fff;
  font-size:clamp(34px,6.6vw,116px); line-height:1.04; letter-spacing:-0.04em;
  text-wrap:balance; text-shadow:0 2px 30px rgba(0,0,0,0.8), 0 1px 6px rgba(0,0,0,0.6);
}
.hheadline .ser{ display:inline-block; font-size:1.04em; line-height:1.12; }
.hsub{ position:relative; z-index:5; text-align:center; margin:clamp(38px,4vw,54px) auto 0; max-width:42ch;
  color:rgba(255,255,255,0.82); font-size:clamp(14px,1.3vw,17px); line-height:1.55; font-weight:350;
  text-shadow:0 1px 16px rgba(0,0,0,0.9), 0 2px 6px rgba(0,0,0,0.8); }

/* chrome sphere */
.sphere-wrap{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; z-index:1; pointer-events:none; }
.sphere{
  position:relative; width:clamp(210px,29vw,410px); aspect-ratio:1; border-radius:50%;
  background:
    radial-gradient(115% 115% at 34% 26%,
      #fbfbfb 0%, #e4e4e4 6%, #b6b6b6 14%, #7e7e7e 26%,
      #3e3e3e 44%, #1a1a1a 66%, #070707 84%, #000 100%);
  box-shadow:
    inset 0 -34px 70px rgba(255,255,255,0.07),
    inset 0 34px 60px rgba(0,0,0,0.75),
    inset -22px 0 50px rgba(0,0,0,0.55),
    0 60px 110px -28px rgba(0,0,0,0.9),
    0 0 0 1px rgba(255,255,255,0.04);
  isolation:isolate;
}
/* rotating chrome reflection streaks */
.sphere::before{
  content:""; position:absolute; inset:0; border-radius:50%;
  background:conic-gradient(from 0deg,
    rgba(255,255,255,0) 0deg, rgba(255,255,255,0.34) 32deg, rgba(255,255,255,0) 70deg,
    rgba(120,120,120,0.18) 120deg, rgba(255,255,255,0) 165deg,
    rgba(255,255,255,0.22) 215deg, rgba(255,255,255,0) 255deg,
    rgba(150,150,150,0.16) 305deg, rgba(255,255,255,0) 350deg);
  mix-blend-mode:screen; opacity:.55;
  -webkit-mask:radial-gradient(circle at 50% 50%, transparent 38%, #000 62%, #000 96%, transparent 100%);
  mask:radial-gradient(circle at 50% 50%, transparent 38%, #000 62%, #000 96%, transparent 100%);
}
/* horizon environment band + bottom bounce */
.sphere::after{
  content:""; position:absolute; inset:0; border-radius:50%;
  background:
    radial-gradient(60% 26% at 50% 70%, rgba(210,210,210,0.4) 0%, rgba(210,210,210,0) 60%),
    radial-gradient(40% 30% at 30% 22%, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0) 55%);
  mix-blend-mode:screen; opacity:.9;
}
.sphere-spec{ position:absolute; top:18%; left:24%; width:20%; height:14%; border-radius:50%;
  background:radial-gradient(closest-side, rgba(255,255,255,0.95), rgba(255,255,255,0)); filter:blur(2px); z-index:3; }
@media (prefers-reduced-motion: no-preference){
  .sphere::before{ animation:sphereSpin 32s linear infinite; will-change:transform; }
  .sphere{ animation:sphereFloat 9s ease-in-out infinite; will-change:transform; }
}
/* mobile: static sphere (no continuous repaint) */
@media (max-width:760px){
  .sphere::before{ animation:none; }
  .sphere{ animation:none; }
}
@keyframes sphereSpin{ to{ transform:rotate(360deg); } }
@keyframes sphereFloat{ 0%,100%{ transform:translateY(0); } 50%{ transform:translateY(-14px); } }

/* hero corner clusters */
.hcorner{ position:relative; z-index:6; display:flex; align-items:flex-end; justify-content:space-between; gap:24px; }
.hwedo{ }
.hwedo-k{ font-size:11px; letter-spacing:0.16em; text-transform:uppercase; color:var(--faint); margin-bottom:12px; }
.hwedo-list{ font-family:var(--sans); font-size:clamp(16px,1.7vw,22px); color:rgba(255,255,255,0.82); line-height:1.35; max-width:24ch; }
.hwedo-list .sl{ color:var(--faint); font-style:normal; font-family:var(--sans); padding:0 6px; }

.hintro{ text-align:right; display:flex; flex-direction:column; align-items:flex-end; gap:16px; }
.hintro-line{ font-size:clamp(13px,1.2vw,15px); color:rgba(255,255,255,0.7); max-width:30ch; line-height:1.5; font-weight:350; }
.hsoc{ display:flex; align-items:center; gap:12px; }
.hsoc-k{ font-size:12px; color:var(--muted); }
.hsoc-ic{ display:flex; gap:8px; }
.hsoc-ic a{ width:34px; height:34px; border-radius:50%; border:1px solid var(--line-2); display:flex; align-items:center; justify-content:center; color:var(--muted); transition:transform var(--t-page), color var(--t-page), border-color var(--t-page); }
.hsoc-ic a:hover{ color:#fff; border-color:rgba(255,255,255,0.5); transform:translateY(-2px); }
.hsoc-ic svg{ width:15px; height:15px; }

/* featured thumb */
.hfeat{ position:absolute; right:clamp(20px,2.4vw,30px); bottom:clamp(86px,11vw,150px); z-index:7; width:clamp(240px,26vw,336px);
  display:block; text-decoration:none; border-radius:16px; outline:none;
  transition:transform var(--t-page) var(--ix-ease-out), filter var(--t-page) var(--ix-ease); }
.hfeat:hover{ transform:translateY(-4px) scale(1.02); filter:brightness(1.08); }
.hfeat:hover .hfeat-k b{ color:#fff; }
.hfeat:focus-visible{ box-shadow:0 0 0 2px rgba(255,255,255,0.55); }
.hfeat-k{ font-size:10.5px; letter-spacing:0.14em; text-transform:uppercase; color:var(--faint); margin-bottom:9px; text-align:right; }
.hfeat-k b{ color:var(--muted); font-weight:600; }
.hfeat-thumb{ position:relative; border-radius:14px; overflow:hidden; border:1px solid var(--line-2); aspect-ratio:4/3; background:#000; }
.hfeat-thumb video, .hfeat-thumb img{ width:100%; height:100%; object-fit:cover; }
.hfeat-thumb::after{ content:""; position:absolute; inset:0; border-radius:14px; box-shadow:inset 0 0 0 1px rgba(255,255,255,0.06); }

@media (max-width:900px){
  .hfeat{ display:none; }
  .hnav a.opt{ display:none; }
  .hcorner{ flex-direction:column; align-items:flex-start; gap:26px; }
  .hintro{ text-align:left; align-items:flex-start; }
}
@media (max-width:640px){
  .hnav{ position:static; transform:none; margin:54px auto 0; order:3; flex-wrap:wrap; justify-content:center; }
  .htop{ position:relative; }
}

/* ============================================================
   4. PORTFOLIO — stacked bands
   ============================================================ */
.work{ padding:clamp(40px,5vw,70px) 0 clamp(60px,7vw,100px); }
.band{ position:relative; border:1px solid var(--line); border-radius:var(--radius); overflow:hidden;
  background:var(--panel); margin-bottom:clamp(16px,1.8vw,26px); display:grid; grid-template-columns:1.15fr 1fr; min-height:clamp(560px,62vw,880px); }
.band:nth-child(even){ grid-template-columns:1fr 1.15fr; }
.band[data-project]{ }
.band:nth-child(even) .band-media{ order:2; }
.band:nth-child(even) .band-body{ order:1; }
.band-media{ position:relative; overflow:hidden; background:#000; }
.band-media img, .band-media video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; filter:none; }
.band-media video{ opacity:0; transition:opacity var(--t-reveal) var(--ix-ease-out); }
.band.playing .band-media video{ opacity:1; }
.band-body{ padding:clamp(36px,4.4vw,76px); display:flex; flex-direction:column; justify-content:center; gap:20px; }
.band-tags{ display:flex; flex-wrap:wrap; gap:7px; }
.band-tag{ font-size:11px; letter-spacing:0.06em; text-transform:uppercase; color:var(--muted); border:1px solid var(--line); border-radius:999px; padding:4px 12px; }
.band-title{ font-family:var(--sans); font-weight:500; font-size:clamp(36px,5.4vw,76px); line-height:0.98; letter-spacing:-0.04em; margin:0; }
.band-title .ser{ font-size:1.05em; }
.band-cap{ color:var(--muted); font-size:clamp(16px,1.45vw,18px); line-height:1.6; font-weight:350; max-width:46ch; margin:0; }
.band-disc{ font-size:11px; color:var(--faint); letter-spacing:0.02em; margin-top:4px; }
.band-no{ position:absolute; top:24px; right:24px; z-index:4; font-family:var(--sans); font-size:clamp(28px,3vw,42px); color:rgba(255,255,255,0.5); }
.band.full{ grid-template-columns:1fr; }
.band.full .band-media{ position:absolute; inset:0; }
.band.full .band-media::after{ content:""; position:absolute; inset:0; background:linear-gradient(to top, rgba(6,6,6,0.86) 0%, rgba(6,6,6,0.2) 55%, rgba(6,6,6,0.4) 100%); }
.band.full .band-body{ position:relative; z-index:3; justify-content:flex-end; min-height:clamp(620px,66vw,900px); }
.band-wip{ display:inline-flex; align-items:center; gap:7px; font-size:11px; letter-spacing:0.08em; text-transform:uppercase; color:var(--faint); }
.band-wip::before{ content:""; width:6px; height:6px; border-radius:50%; background:var(--muted); }
@media (max-width:820px){ .band, .band:nth-child(even){ grid-template-columns:1fr; } .band-media{ aspect-ratio:16/10; position:relative; order:1 !important; } .band-body{ order:2 !important; } }

/* project card text-side depth: faint radial glow + subtle gradient + dotted grid
   (split cards only — .full bands are full-bleed photos, excluded so dots never
    land over an image). Decorative, behind the text. */
.band:not(.full) .band-body{ position:relative; isolation:isolate; }
.band:not(.full) .band-body::before{
  content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:
    radial-gradient(90% 95% at 50% 32%, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.012) 42%, transparent 72%),
    linear-gradient(120deg, rgba(255,255,255,0.02) 0%, transparent 58%),
    radial-gradient(circle at 1px 1px, rgba(255,255,255,0.10) 1px, transparent 0);
  background-size:auto, auto, 22px 22px;
  -webkit-mask-image:radial-gradient(95% 92% at 50% 45%, #000 38%, transparent 92%);
          mask-image:radial-gradient(95% 92% at 50% 45%, #000 38%, transparent 92%);
}

/* ============================================================
   4b. PORTFOLIO — center-scale effect (Projects only)
   Each project scales up as it reaches the viewport center; off-center bands
   shrink + dim slightly. Pure transform+opacity, gated on .snap-ready so a JS
   failure leaves bands full-opacity. Scrolling is completely normal/free —
   NO scroll-snap, NO scroll-assist; JS only toggles the .snap-active class.
   All of it lives inside (prefers-reduced-motion: no-preference) so reduce-motion
   falls back to the normal stacked scroll above.
   ============================================================ */
@media (prefers-reduced-motion: no-preference){
  /* .work prefix raises specificity above the later `.work .band{min-height:clamp(...)}`
     rule so each project is ~one viewport tall and centers visually as you scroll. */
  .work .work-scroll .band{
    min-height:100svh; height:100svh;     /* one project ≈ one full screen */
    box-sizing:border-box;                /* 1px border stays inside 100svh */
    margin:0;                             /* edge-to-edge full-screen projects */
  }
  /* centered band sits at full scale; off-center bands shrink + dim slightly
     (GPU: transform+opacity only, smooth settle) */
  .work-scroll.snap-ready .band{
    transform:scale(.94); opacity:.62;
    transform-origin:center center; will-change:transform, opacity;
    transition:transform var(--t-page) var(--ix-ease-out), opacity var(--t-page) var(--ix-ease);
  }
  .work-scroll.snap-ready .band.snap-active{ transform:scale(1); opacity:1; }
  /* while the case study closes & restores scroll, freeze band fx so the nearest
     project never animates its scale (no "slide to fill") — state is committed
     instantly at the restored position, identical to before opening */
  .work-scroll.cs-fx-freeze .band{ transition:none !important; }
}
/* Mobile: DROP the scale/dim — prioritise smooth, light scrolling */
@media (max-width:820px) and (prefers-reduced-motion: no-preference){
  .work-scroll.snap-ready .band,
  .work-scroll.snap-ready .band.snap-active{ transform:none; opacity:1; }
}

/* ============================================================
   8. FOOTER
   ============================================================ */
.footer{ padding:clamp(60px,7vw,100px) 0 0; border-top:1px solid var(--line); position:relative; overflow:hidden; }
/* footer depth: dotted grid + soft glow behind the columns and AVREA wordmark */
.footer::before{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background:
    radial-gradient(60% 70% at 50% 78%, rgba(255,255,255,0.045) 0%, rgba(255,255,255,0.012) 44%, transparent 74%),
    radial-gradient(circle at 1px 1px, rgba(255,255,255,0.10) 1px, transparent 0);
  background-size:auto, 22px 22px;
  -webkit-mask-image:radial-gradient(100% 88% at 50% 60%, #000 34%, transparent 92%);
          mask-image:radial-gradient(100% 88% at 50% 60%, #000 34%, transparent 92%);
}
.foot-cols{ display:grid; grid-template-columns:1.4fr 1fr 1fr 1fr 1fr; gap:clamp(24px,3vw,48px); position:relative; z-index:2; }
.foot-brand .hbrand{ font-size:26px; margin-bottom:18px; }
.foot-brand p{ color:var(--muted); font-size:14px; line-height:1.6; font-weight:350; max-width:30ch; margin:0; }
.foot-col h4{ font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--faint); margin:0 0 18px; font-weight:600; }
.foot-col a, .foot-col .fline{ display:block; color:var(--muted); font-size:14px; padding:8px 0; transition:color var(--t-fast); }
.foot-col a:hover{ color:#fff; }
.foot-col a.foot-soc{ display:flex; align-items:center; gap:10px; }
.foot-soc svg{ width:16px; height:16px; flex:none; opacity:.75; transition:opacity var(--t-fast); }
.foot-soc:hover svg{ opacity:1; }
.foot-mark{ font-weight:700; font-size:clamp(110px,22vw,360px); letter-spacing:-0.05em; line-height:0.8; color:rgba(255,255,255,0.035);
  text-align:center; margin:clamp(24px,3vw,44px) 0 -0.12em; user-select:none; pointer-events:none; white-space:nowrap; position:relative; z-index:1; }
.foot-bar{ border-top:1px solid var(--line); padding:24px var(--gutter); display:flex; align-items:center; justify-content:space-between; gap:16px;
  max-width:var(--maxw); margin:0 auto; position:relative; z-index:2; }
.foot-bar span{ font-size:13px; color:var(--faint); }
.foot-top{ display:inline-flex; align-items:center; gap:8px; font-size:13px; color:var(--muted); background:none; border:0; font-family:var(--sans); transition:color var(--t-fast); }
.foot-top:hover{ color:#fff; }
.foot-top svg{ width:14px; height:14px; }
@media (max-width:820px){ .foot-cols{ grid-template-columns:1fr 1fr; } .foot-brand{ grid-column:1 / -1; } }
@media (max-width:520px){ .foot-cols{ grid-template-columns:1fr; } }

/* ============================================================
   CALENDLY BOOKING OVERLAY (original flow — recolored to v27 dark grayscale)
   ============================================================ */
.cal-back{ position:fixed; inset:0; z-index:230; background:rgba(6,6,6,0.62);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  opacity:0; pointer-events:none; transition:opacity var(--t-page) var(--ix-ease);
  display:flex; align-items:center; justify-content:center; padding:clamp(16px,4dvh,48px) 16px; }
.cal-back.open{ opacity:1; pointer-events:auto; }
.cal-modal{ position:relative; width:min(720px,100%); height:min(760px,90dvh);
  display:flex; flex-direction:column; background:var(--panel-2); color:var(--ink);
  border:1px solid var(--line-2); border-radius:24px; overflow:hidden;
  box-shadow:0 40px 110px -30px rgba(0,0,0,0.8), 0 4px 14px rgba(0,0,0,0.4);
  transform:translateY(16px) scale(.99); transition:transform var(--t-page) var(--ix-ease-out); }
.cal-back.open .cal-modal{ transform:none; }
.cal-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:16px;
  padding:24px 24px 16px; border-bottom:1px solid var(--line); flex:none; }
.cal-eyebrow{ font-family:var(--sans); font-weight:600; font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--faint); margin-bottom:8px; }
.cal-title{ font-family:var(--sans); font-weight:500; font-size:22px; letter-spacing:-0.025em; color:#fff; margin:0; line-height:1.1; }
.cal-close{ flex:none; width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line-2); border-radius:50%; background:var(--panel-3); color:var(--ink); transition:background var(--t-fast) var(--ix-ease), transform var(--t-fast) var(--ix-ease), border-color var(--t-fast); }
.cal-close:hover{ background:#262626; border-color:rgba(255,255,255,0.4); transform:scale(1.05); }
.cal-close svg{ width:16px; height:16px; }
.cal-embed{ flex:1 1 auto; min-height:0; background:#fff; }
.cal-embed .calendly-inline-widget, .cal-embed iframe{ width:100%; height:100% !important; min-width:0 !important; border:0; }
@media (max-width:560px){ .cal-modal{ height:92dvh; border-radius:18px; } .cal-head{ padding:16px 16px 16px; } .cal-title{ font-size:18px; } }
@media (prefers-reduced-motion: reduce){ .cal-back, .cal-modal{ transition:opacity var(--t-fast) var(--ix-ease); } .cal-modal{ transform:none; } }

/* ============================================================
   HERO headline — metallic chrome on the italic line only
   Scoped to .hheadline .ser so other .ser accents stay unchanged.
   ============================================================ */
.hheadline .ser, .ser-chrome{
  color:#d8d8d8; /* fallback for no background-clip:text support */
  background:linear-gradient(180deg,
    #ffffff 0%,
    #e9e9ec 26%,
    #b8b8be 50%,
    #8c8c93 70%,
    #c4c4ca 100%);
  -webkit-background-clip:text;
          background-clip:text;
  -webkit-text-fill-color:transparent;
  text-shadow:0 1px 0 rgba(255,255,255,0.12);
}
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .hheadline .ser, .ser-chrome{ color:#d8d8d8; -webkit-text-fill-color:#d8d8d8; }
}
/* ============================================================
   CHANGE 2 — Projects span near-full page width
   (lift the 1320 cap; keep the site's --gutter frame margins;
    scale media + text up so the bands read bold and spacious)
   ============================================================ */
.work.shell{ max-width:none; padding-left:var(--gutter); padding-right:var(--gutter); }
.work .band{ min-height:clamp(580px,52vw,1000px); }
.work .band-body{ padding:clamp(40px,5vw,100px); gap:24px; }
.work .band-title{ font-size:clamp(42px,6vw,96px); }
.work .band-cap{ font-size:clamp(16px,1.55vw,22px); max-width:56ch; }
.work .band-no{ top:32px; right:32px; font-size:clamp(32px,3.6vw,56px); }
.work .band.full .band-body{ min-height:clamp(640px,56vw,1040px); }

/* ============================================================
   CHANGE 3 — Quote / contact form modal
   (original Web3Forms flow restored, recolored to v27 grayscale)
   ============================================================ */
.q-lock{ overflow:hidden; }
.q-back{ position:fixed; inset:0; z-index:210; background:rgba(6,6,6,0.62);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  opacity:0; pointer-events:none; transition:opacity var(--t-page) var(--ix-ease);
  display:flex; align-items:center; justify-content:center; padding:clamp(16px,4dvh,48px) 16px; overflow-y:auto; }
.q-back.open{ opacity:1; pointer-events:auto; }
.q-modal{ position:relative; width:min(560px,100%);
  background:var(--panel-2); color:var(--ink);
  border:1px solid var(--line-2); border-radius:24px; overflow:hidden;
  box-shadow:0 40px 110px -30px rgba(0,0,0,0.8), 0 4px 14px rgba(0,0,0,0.4);
  transform:translateY(16px) scale(.99); transition:transform var(--t-page) var(--ix-ease-out); }
.q-back.open .q-modal{ transform:none; }
.q-head{ position:relative; padding:32px 32px 24px; border-bottom:1px solid var(--line); }
.q-close{ position:absolute; top:16px; right:16px; width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line-2); border-radius:50%; background:var(--panel-3); color:var(--ink); transition:background var(--t-fast) var(--ix-ease), transform var(--t-fast) var(--ix-ease), border-color var(--t-fast); }
.q-close:hover{ background:#262626; border-color:rgba(255,255,255,0.4); transform:scale(1.05); }
.q-close svg{ width:16px; height:16px; }
.q-eyebrow{ font-family:var(--sans); font-weight:600; font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--faint); margin-bottom:8px; }
.q-title{ font-family:var(--sans); font-weight:500; font-size:clamp(22px,2.6vw,28px); letter-spacing:-0.03em; color:#fff; margin:0; line-height:1.1; }
.q-sub{ color:var(--muted); font-size:14px; line-height:1.5; font-weight:350; margin:12px 0 0; max-width:42ch; }
.q-form{ padding:24px 32px 32px; }
.q-grid{ display:flex; flex-direction:column; gap:16px; }
.q-field{ display:flex; flex-direction:column; gap:8px; }
.q-label{ font-size:12px; font-weight:500; letter-spacing:0.01em; color:var(--muted); }
.q-label .req{ color:var(--faint); }
.q-input, .q-textarea{ font-family:var(--sans); font-size:16px; color:var(--ink); background:var(--panel);
  border:1px solid var(--line); border-radius:13px; padding:12px 16px; width:100%; min-height:44px; transition:border-color var(--t-fast) var(--ix-ease), background var(--t-fast) var(--ix-ease); }
.q-input::placeholder, .q-textarea::placeholder{ color:var(--faint); }
.q-input:focus, .q-textarea:focus{ outline:none; border-color:rgba(255,255,255,0.45); background:var(--panel-2); }
.q-textarea{ min-height:120px; resize:vertical; line-height:1.5; }
.q-error{ display:none; font-size:12px; color:#ff8b8b; }
.q-field.invalid .q-input, .q-field.invalid .q-textarea{ border-color:rgba(255,120,120,0.6); }
.q-field.invalid .q-error{ display:block; }
.q-foot{ display:flex; flex-direction:column; gap:16px; margin-top:24px; }
.q-submit{ width:100%; justify-content:center; padding:16px 20px; font-size:16px; }
.q-call{ display:inline-flex; align-items:center; justify-content:center; gap:9px; width:100%;
  font-family:var(--sans); font-size:14px; font-weight:550; color:#fff;
  background:rgba(255,255,255,0.07); border:1px solid rgba(255,255,255,0.55); border-radius:999px; padding:16px 20px;
  transition:background var(--t-fast) var(--ix-ease), border-color var(--t-fast) var(--ix-ease), transform var(--t-line) var(--ix-ease-out); }
.q-call:hover{ background:rgba(255,255,255,0.14); border-color:#fff; color:#fff; transform:translateY(-2px); }
.q-call .q-call-ic svg{ width:16px; height:16px; }
.q-call-ic{ display:inline-flex; }
.q-call-ar{ transition:transform var(--t-line) var(--ix-ease); }
.q-call:hover .q-call-ar{ transform:translate(2px,-2px); }
.q-note{ font-size:12px; color:var(--faint); line-height:1.5; margin:4px 0 0; text-align:center; }
.q-note a{ color:var(--muted); text-decoration:underline; text-underline-offset:2px; }
.q-senderror a{ color:#ffb3b3; }
.q-done{ display:none; flex-direction:column; align-items:center; text-align:center; padding:48px 40px 48px; gap:8px; }
.q-modal.sent .q-form{ display:none; }
.q-modal.sent .q-head{ display:none; }
.q-modal.sent .q-done{ display:flex; }
.q-tick{ width:56px; height:56px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line-2); background:var(--panel-3); color:#fff; margin-bottom:16px; }
.q-tick svg{ width:24px; height:24px; }
.q-done h3{ font-family:var(--sans); font-weight:500; font-size:24px; letter-spacing:-0.02em; color:#fff; margin:0; }
.q-done p{ color:var(--muted); font-size:14px; line-height:1.5; margin:8px 0 16px; max-width:34ch; }
.q-resend{ font-family:var(--sans); font-size:14px; font-weight:500; color:var(--ink);
  background:var(--panel-3); border:1px solid var(--line-2); border-radius:999px; padding:12px 20px; min-height:44px; transition:background var(--t-fast), border-color var(--t-fast); }
.q-resend:hover{ background:#262626; border-color:rgba(255,255,255,0.4); }
@media (max-width:560px){ .q-head{ padding:24px 24px 16px; } .q-form{ padding:20px 24px 24px; } .q-modal{ border-radius:18px; } }
@media (prefers-reduced-motion: reduce){ .q-back, .q-modal{ transition:opacity var(--t-fast) var(--ix-ease); } .q-modal{ transform:none; } }

/* ============================================================
   CHANGE (depth) — subtle monochrome background texture
   Purely decorative: pointer-events:none, behind content, no color.
   ============================================================ */
/* (site-wide dot grid removed — dots are now scoped to Testimonials,
   Pricing and project card text-sides only, for contrast/rhythm) */

/* shared dotted-grid texture (your exact pattern, inverted for dark theme) */

/* ============================================================
   HERO — liquid-metal / chrome shader background (replaces the sphere)
   The canvas carries a STATIC chrome gradient as its CSS background;
   WebGL paints over it when it runs. Mobile / reduced-motion / no-WebGL
   keep the static gradient (heroMetal.js bails out).
   ============================================================ */
.hero-card .sphere-wrap{ display:none !important; }
/* ============================================================
   HERO background video (replaces the metal shader canvas).
   Sits behind everything (z-0); object-fit:cover fills the card.
   ============================================================ */
.hero-video{
  position:absolute; inset:0; z-index:0; width:100%; height:100%;
  display:block; pointer-events:none; border-radius:inherit;
  object-fit:cover; object-position:center;
  background:#070707;   /* fallback while poster/video loads */
}
/* subtle dark scrim over the video so the white headline stays legible */
.hero-vid-scrim{
  position:absolute; inset:0; z-index:1; pointer-events:none; border-radius:inherit;
  background:
    linear-gradient(180deg, rgba(6,6,8,0.50) 0%, rgba(6,6,8,0.18) 30%, rgba(6,6,8,0.26) 68%, rgba(6,6,8,0.58) 100%),
    radial-gradient(120% 105% at 50% 46%, transparent 48%, rgba(4,4,6,0.40) 100%);
}
/* mobile: white text blends worst on bright video frames, so lean stronger on the
   scrim (darker top + bottom where the corner text sits, plus a light overall tint).
   Still just a CSS gradient between video (z0) and text (z2) — video stays visible. */
@media (max-width:760px){
  .hero-vid-scrim{
    background:
      linear-gradient(180deg, rgba(5,5,7,0.62) 0%, rgba(5,5,7,0.30) 26%, rgba(5,5,7,0.34) 60%, rgba(5,5,7,0.68) 100%),
      linear-gradient(rgba(4,4,6,0.16), rgba(4,4,6,0.16));
  }
}
/* reduced motion: hold the poster frame, don't play.
   Do NOT hide the video - paused, it shows its own poster attribute
   (avrea-hero-poster.webp) with the same object-fit:cover, filling the card.
   The previous ".hero-video + .hero-vid-scrim" poster hack never matched,
   because the inline hero <script> sits between the video and the scrim as an
   element sibling - so the adjacent-sibling selector failed and the hero
   rendered as an empty dark block. Showing the paused video fixes it. */
@media (prefers-reduced-motion: reduce){
  .hero-video{ display:block; }
}
.hero-grain{ z-index:1; }

/* Fallback play button: shown only while the hero video is paused (autoplay
   blocked, or reduced-motion where autoplay is intentionally off). A user click
   always starts playback, which no browser blocks. Subtle, muted, dark. */
.hero-card > .hero-play{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  z-index:3; width:72px; height:72px; border-radius:999px;
  display:none; align-items:center; justify-content:center;
  background:rgba(12,12,14,0.40);
  border:1px solid rgba(255,255,255,0.45);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  color:rgba(255,255,255,0.88);
  transition:background var(--t-page) var(--ix-ease), border-color var(--t-page) var(--ix-ease), transform var(--t-page) var(--ix-ease);
}
.hero-card > .hero-play.is-visible{ display:flex; }
.hero-card > .hero-play:hover{
  background:rgba(20,20,22,0.55); border-color:rgba(255,255,255,0.75);
  transform:translate(-50%,-50%) scale(1.06);
}
.hero-card > .hero-play svg{ width:25px; height:25px; margin-left:3px; fill:currentColor; }

/* ============================================================
   Hero scroll-down indicator — subtle grayscale chevron pill,
   bottom-center of the hero card. Smooth-scrolls to #services.
   ============================================================ */
.hscroll{
  position:absolute; z-index:7; left:50%; bottom:clamp(16px,2.2vw,26px);
  transform:translateX(-50%);
  display:flex; align-items:center; justify-content:center;
  width:56px; height:36px; border-radius:13px;
  color:rgba(255,255,255,0.78);
  background:rgba(255,255,255,0.08);
  border:1px solid rgba(255,255,255,0.22);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  text-decoration:none;
  transition:opacity var(--t-page) var(--ix-ease), color var(--t-page) var(--ix-ease), background var(--t-page) var(--ix-ease), border-color var(--t-page) var(--ix-ease);
  animation:hscrollFloat 2.2s ease-in-out infinite alternate;
}
.hscroll svg{ width:24px; height:24px; display:block; }
.hscroll:hover{ color:#fff; background:rgba(255,255,255,0.14); border-color:rgba(255,255,255,0.38); }
.hscroll:focus-visible{ outline:2px solid rgba(255,255,255,0.7); outline-offset:3px; }
.hscroll.is-hidden{ opacity:0; pointer-events:none; }
@keyframes hscrollFloat{ from{ transform:translate(-50%,-3px); } to{ transform:translate(-50%,3px); } }
@media (prefers-reduced-motion: reduce){
  .hscroll{ animation:none; transform:translateX(-50%); }
}


/* ============================================================
   PROJECT GRID TILES - rows of vertical (4:5) + square (1:1) cards.
   Reuses .band so the existing hover-video + case-study JS is untouched;
   .tile overrides the split full-screen band layout.
   ============================================================ */
.pgrid{ display:grid; gap:clamp(14px,1.6vw,24px); margin-bottom:clamp(14px,1.6vw,24px); }
.pgrid.three{ grid-template-columns:repeat(3,1fr); }
.pgrid.two{ grid-template-columns:repeat(2,1fr); }
.work .pgrid .band.tile{ display:block; position:relative; min-height:0; height:auto; margin:0; }
.work .pgrid .band.tile.v{ aspect-ratio:4/5; }
.work .pgrid .band.tile.sq{ aspect-ratio:1/1; }
.work .pgrid .band.tile .band-media{ position:absolute; inset:0; order:0; aspect-ratio:auto; }
.work .pgrid .band.tile .band-media::after{ content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to top, rgba(6,6,6,0.88) 0%, rgba(6,6,6,0.32) 46%, rgba(6,6,6,0) 72%); }
.work .pgrid .band.tile .band-body{ position:absolute; inset:auto 0 0 0; z-index:3; padding:clamp(20px,2vw,34px);
  gap:12px; justify-content:flex-end; min-height:0; }
.work .pgrid .band.tile .band-body::before{ display:none; }
.work .pgrid .band.tile .band-title{ font-size:clamp(22px,2.1vw,34px); letter-spacing:-0.03em; }
.work .pgrid .band.tile .band-cap, .work .pgrid .band.tile .band-disc{ display:none; }
.work .pgrid .band.tile .band-no{ top:16px; right:20px; font-size:clamp(20px,1.7vw,28px); z-index:4; }
.work .pgrid .band.tile .band-tag{ font-size:11px; padding:4px 8px; }
.work .pgrid .band.tile[data-project]{ transition:border-color var(--t-line) var(--ix-ease); }
/* ...but that shorthand outranks html.rv-on .rv.rv-soft and would reset
   transition-property to border-color alone, so a revealing tile would snap.
   Re-state the whole animated set at a specificity that survives it. */
.work .pgrid .band.tile[data-project].rv-soft{
  transition:border-color var(--t-line) var(--ix-ease),
             opacity var(--t-rv) var(--ix-ease-out),
             translate var(--t-rv) var(--ix-ease-out),
             transform var(--t-fill) var(--ix-ease-out); }
@media (prefers-reduced-motion:reduce){
  .work .pgrid .band.tile[data-project].rv-soft{ transition:none !important; }
}
.work .pgrid .band.tile[data-project]:hover{ border-color:rgba(255,255,255,0.22); }

/* empty vertical slot - awaiting asset */
.work .pgrid .band.tile.slot{ background:rgba(255,255,255,0.012); border-style:dashed; border-color:rgba(255,255,255,0.13); cursor:default; }
.slot-mark{ position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px; text-align:center; padding:24px; }
.slot-num{ font-family:var(--sans); font-size:clamp(30px,3vw,46px); color:rgba(255,255,255,0.16); line-height:1; }
.slot-label{ font-size:11px; letter-spacing:0.1em; text-transform:uppercase; color:var(--muted); }
.slot-hint{ font-size:12px; color:var(--faint); max-width:22ch; line-height:1.5; }

@media (max-width:820px){
  .pgrid.three{ grid-template-columns:repeat(3,1fr); }
  .work .pgrid .band.tile .band-title{ font-size:clamp(15px,2.6vw,22px); }
  .work .pgrid .band.tile .band-tags{ display:none; }
}
@media (max-width:600px){
  .pgrid.three, .pgrid.two{ grid-template-columns:1fr; }
  .work .pgrid .band.tile .band-title{ font-size:clamp(24px,7vw,34px); }
  .work .pgrid .band.tile .band-tags{ display:flex; }
}


/* ============================================================
   GOOGLE REVIEW CARDS - big quote, name + source bottom-left, avatar bottom-right
   ============================================================ */
.rev-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:clamp(14px,1.6vw,24px); margin-top:clamp(44px,5vw,72px); }
.rev{ display:flex; flex-direction:column; gap:clamp(24px,3vw,44px); padding:clamp(24px,2.6vw,40px);
  border:1px solid var(--line); border-radius:var(--radius); background:var(--panel);
  transition:border-color var(--t-page) var(--ix-ease), transform var(--t-fill) var(--ix-ease-out); }
.rev:nth-child(2){ background:#141414; }
.rev:nth-child(3){ background:#0e0e0e; }
.rev:hover{ border-color:var(--line-2); transform:translateY(-4px); }
@media (prefers-reduced-motion:reduce){ .rev:hover{ transform:none; } }
.rev-q{ margin:0; flex:1; font-family:var(--sans); font-weight:350;
  font-size:clamp(19px,1.75vw,25px); line-height:1.42; letter-spacing:-0.02em;
  color:rgba(255,255,255,0.90); text-wrap:pretty; }
.rev-foot{ display:flex; align-items:center; justify-content:space-between; gap:16px;
  border-top:1px solid var(--line); padding-top:clamp(18px,1.8vw,24px); }
.rev-who{ display:flex; flex-direction:column; gap:4px; min-width:0; }
.rev-who b{ font-size:16px; font-weight:600; letter-spacing:-0.01em; color:var(--ink); }
.rev-who span{ font-size:11px; letter-spacing:0.06em; text-transform:uppercase; color:var(--faint); }
.rev-av{ flex:0 0 auto; width:46px; height:46px; border-radius:50%; object-fit:cover;
  background:#161616; border:1px solid var(--line-2); }
.rev-av-mono{ display:flex; align-items:center; justify-content:center; background:var(--panel-3);
  font-family:var(--sans); font-size:18px; font-weight:500; color:var(--muted); line-height:1; }
@media (max-width:900px){ .rev-grid{ grid-template-columns:1fr; max-width:620px; margin-left:auto; margin-right:auto; } }


/* ==== source: mobile.css ==== */
/* ============================================================
   AVREA - phone / tablet refinements (v29)
   Loaded AFTER v27.css so these rules win. Scoped ENTIRELY to
   small viewports via media queries - desktop layout untouched.
   Uses the site's --gutter for side spacing so edges line up.
   ============================================================ */

/* ============================================================
   WORK CARDS - content-height, media-on-top, clear gaps
   ============================================================ */
@media (max-width:820px){
  /* kill the desktop full-screen band height so cards fit content */
  .work .band,
  .work .work-scroll .band{ min-height:auto; height:auto; }

  /* clear vertical separation between stacked project blocks */
  .work .work-scroll .band{ margin-bottom:clamp(44px,12vw,76px); }
  .work .work-scroll .band:last-child{ margin-bottom:0; }

  /* keep review cards off the screen edges (matches other sections) */
  .testi.shell{ padding-left:var(--gutter); padding-right:var(--gutter); }
}
@media (max-width:640px){
  .work .band-body{ padding:clamp(24px,6vw,32px); gap:16px; }
}

/* Section vertical rhythm on phones - restore the --gutter side padding
   that the shorthand would otherwise drop to 0 for .shell sections. */
@media (max-width:480px){
  .work{ padding:32px 0 56px; }
  .testi{ padding:56px var(--gutter); }
}

/* REMOVED - see site.css. body{overflow-x:hidden} makes body a nested scroll
   container and kills vertical scrolling on phones. This file is a legacy source
   (no page links it; it is concatenated into site.css), but the rule is deleted
   here too so a future re-concat cannot reintroduce it. Never on html or body. */

/* ---- Footer bar: stack the legal line -------- */
@media (max-width:560px){
  .mfoot-bar{ flex-direction:column; align-items:flex-start; gap:12px; }
}

/* ---- Touch targets: comfortable tap areas ----------------- */
@media (hover:none){
  /* .clinks a is 16px 0 in the base rule now - no override needed */
  .pbar-links a{ padding:12px 0; }
}


/* ==== source: casestudy.css ==== */
/* ============================================================
   AVREA - case study view
   ============================================================ */
.cs-root{
  /* The overlay carries the opaque, page-covering background, so it must NEVER fade
     its OWN opacity on open — fading the bg-carrier makes the whole overlay
     semi-transparent and the grid behind shows through (the Firefox open-flash).
     Show/hide via VISIBILITY and keep opacity:1; the entrance fade+rise runs on the
     inner .cs-content wrapper instead (see playOpen). Close still fades opacity 1->0
     via WAAPI to dissolve the overlay, which is the intended reveal. */
  position:fixed; inset:0; z-index:200; background:var(--bg);
  opacity:1; visibility:hidden;
  transition:visibility var(--t-fast) linear var(--t-page);
}
.cs-root.open{ visibility:visible; transition:visibility var(--t-fast); }

/* dark veil for the zoom + crossfade open: sits above the grid, below the overlay,
   so the tile can press in under a dimming page before the panel rises over it. */
.cs-veil{ position:fixed; inset:0; z-index:195; background:#0E0E0E; opacity:0; pointer-events:none; will-change:opacity; }

/* ============================================================
   per-project DARK theme - applied ONLY when a project sets theme:'dark'
   (currently Mono Keyboard). Pure CSS-variable swap so every token-based
   element re-themes at once; the main site behind stays light.
   ============================================================ */
.cs-root.cs-dark{
  --bg:        #0E0E0E;
  --surface:   #1A1A1A;
  --surface-2: #232323;
  --ink:       #FAFAF9;
  --muted:     #B4B0A8;
  --faint:     #8F8B82;
  --line:      rgba(255,255,255,0.08);
  --line-2:    rgba(255,255,255,0.15);
  --accent:    #A8A2A2;          /* light grey accent - reads on the dark overlay */
  --shadow:    0 4px 24px -8px rgba(0,0,0,0.5);
  --shadow-lift: 0 18px 48px -16px rgba(0,0,0,0.6);
}
.cs-dark .cs-bar{ background:rgba(14,14,14,0.8); border-bottom-color:var(--line); }
/* media placeholders stay dark-on-dark (already), so no white flash while gifs load */
.cs-dark .cs-media, .cs-dark .cs-media-gif{ background:#161616; }

/* ============================================================
   CRIMSON theme - extends .cs-dark with a deep crimson gradient backdrop.
   Applied alongside .cs-dark for the Crimson Temptress case study.
   ============================================================ */
.cs-root.cs-crimson{
  --bg:        #14060A;
  --surface:   #2C0E15;
  --surface-2: #3B141C;
  --ink:       #FBEEF0;
  --muted:     #D6AAB1;
  --faint:     #9C737B;
  --line:      rgba(235,180,190,0.10);
  --line-2:    rgba(235,180,190,0.18);
  --accent:    #E0556A;          /* crimson accent - pairs with the deep red */
  background:
    radial-gradient(125% 85% at 50% -12%, #5A1322 0%, #3A0D18 30%, #220910 66%, #140509 100%);
  background-attachment:fixed;
}
.cs-crimson .cs-bar{ background:rgba(20,6,10,0.80); border-bottom-color:var(--line); }
.cs-crimson .cs-media, .cs-crimson .cs-media-gif{ background:#220910; }
/* keep section surfaces translucent so the gradient reads through the page */
.cs-crimson .cs-callout{ background:rgba(44,14,21,0.5); }

/* ============================================================
   COBALT theme - extends .cs-dark with a deep-blue→black gradient.
   Applied alongside .cs-dark for the Apple Vision Pro case study.
   ============================================================ */
.cs-root.cs-cobalt{
  --bg:        #04060A;
  --surface:   #0D131F;
  --surface-2: #131C2C;
  --ink:       #EEF3FB;
  --muted:     #A4B0C6;
  --faint:     #74809A;
  --line:      rgba(200,215,255,0.10);
  --line-2:    rgba(200,215,255,0.18);
  --accent:    #5B8DEF;          /* cobalt accent for this project */
  background:
    radial-gradient(125% 85% at 50% -12%, #163A78 0%, #0E2550 26%, #081428 58%, #04060A 100%);
  background-attachment:fixed;
}
.cs-cobalt .cs-bar{ background:rgba(4,6,10,0.78); border-bottom-color:var(--line); }
.cs-cobalt .cs-media, .cs-cobalt .cs-media-gif{ background:#0A111E; }
.cs-cobalt .cs-callout{ background:rgba(19,28,44,0.5); }

/* ============================================================
   GRAPHITE theme - extends .cs-dark with a grey→black gradient.
   Applied alongside .cs-dark for the Mono Keyboard case study.
   ============================================================ */
.cs-root.cs-graphite{
  --bg:        #0A0A0B;
  background:
    radial-gradient(125% 85% at 50% -12%, #3A3A3E 0%, #26262A 26%, #141416 58%, #0A0A0B 100%);
  background-attachment:fixed;
}
.cs-graphite .cs-bar{ background:rgba(10,10,11,0.78); border-bottom-color:var(--line); }
.cs-graphite .cs-media, .cs-graphite .cs-media-gif{ background:#161618; }
.cs-graphite .cs-callout{ background:rgba(38,38,42,0.5); }
/* neutral theme → the "THE CHALLENGE" eyebrow reads grey, not caramel */
.cs-graphite .cs-callout-k{ color:var(--muted); }
.cs-scroll{ position:absolute; inset:0; overflow-y:auto; overflow-x:hidden; -webkit-overflow-scrolling:touch; scrollbar-width:none; -ms-overflow-style:none; }
.cs-scroll::-webkit-scrollbar{ width:0; height:0; display:none; }
body.scroll-locked{ position:fixed; left:0; right:0; width:100%; overflow:hidden; }
/* cs-open is a STATE class only - the lock lives in body.scroll-locked above, set by
   avrea/scrolllock.js, so the burger and the overlay can never fight over it. */
@media (prefers-reduced-motion: reduce){
  /* instant show/hide via visibility — never fade the opaque bg-carrier (no flash) */
  .cs-root{ transition:visibility var(--t-fast) linear var(--t-fast); }
  .cs-root.open{ transition:visibility var(--t-fast); }
}

/* sticky mini header */
.cs-bar{
  --cs-bar-h:76px;
  position:sticky; top:0; z-index:20; display:flex; align-items:center; justify-content:space-between; gap:16px;
  height:var(--cs-bar-h); box-sizing:border-box;
  padding:16px clamp(16px,3.4vw,48px);
  background:rgba(250,250,249,0.78); -webkit-backdrop-filter:saturate(180%) blur(20px); backdrop-filter:saturate(180%) blur(20px);
  border-bottom:1px solid var(--line);
  transition:background var(--t-media) var(--ix-ease), border-color var(--t-media) var(--ix-ease), backdrop-filter var(--t-media) var(--ix-ease), -webkit-backdrop-filter var(--t-media) var(--ix-ease);
}
.cs-bar-l{ display:flex; align-items:baseline; gap:16px; min-width:0; }
.cs-bar-title{ font-family:var(--font-display); font-weight:500; font-size:20px; letter-spacing:-0.02em; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cs-bar-pos{ font-family:var(--font-mono); font-weight:600; font-size:14px; letter-spacing:0.06em; color:var(--muted); flex:0 0 auto; }
/* no box: the mark alone. The 44x44 hit area stays, it is just invisible - the X is
   drawn at 26px inside it - sized against the 20px bar title, not the hit box, so the target never shrinks to the glyph. */
.cs-close{ width:44px; height:44px; padding:0; border:0; border-radius:0; box-sizing:border-box; flex:0 0 auto;
  display:inline-flex; align-items:center; justify-content:center; color:var(--ink);
  background:none; box-shadow:none; -webkit-backdrop-filter:none; backdrop-filter:none;
  transition:color var(--t-fast) var(--ix-ease); }
.cs-close svg{ width:26px; height:26px; display:block; }
.cs-close:hover{ color:var(--accent); }
.cs-close:focus-visible{ outline:2px solid var(--ink); outline-offset:2px; }

/* shared section bits */
.cs-eyebrow{ font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.16em; text-transform:uppercase; color:var(--accent); }
.cs-no{ font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.08em; color:var(--muted); }
.cs-sec-head{ display:flex; align-items:center; gap:16px; margin-bottom:clamp(16px,2vw,24px); }
.cs-h2{
  font-family:var(--font-display); font-weight:500; font-size:clamp(23px,3.4vw,46px);
  letter-spacing:-0.035em; line-height:0.98; color:var(--ink); margin-bottom:clamp(26px,3.2vw,44px); text-wrap:balance;
}

/* hero */
.cs-hero{ max-width:1720px; margin:0 auto; padding:clamp(40px,6vw,84px) clamp(16px,3.4vw,48px) 0; }
.cs-hero-top{ display:flex; align-items:baseline; justify-content:flex-start; gap:20px; flex-wrap:wrap; }
/* same small, muted, letter-spaced voice as the section eyebrows - spacing does the
   separating now, so the middle dots are gone with the full-width rule */
.cs-metarow{ font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.16em; text-transform:uppercase; color:var(--muted); display:flex; align-items:center; gap:clamp(18px,2.2vw,30px); flex-wrap:wrap; }
.cs-dot{ color:var(--faint); }
/* spec-work note: the metarow eyebrow's type, one step quieter. Deliberately NOT
   .cs-spec - that is the spec-table row component below, with its own borders. */
.cs-selfinit{ display:inline-block; font-family:var(--font-mono); font-weight:600; font-size:12px; letter-spacing:0.04em; color:var(--muted); border:0; padding:0; }
.cs-titleblock .cs-selfinit,.cs-split-body .cs-selfinit{ display:block; grid-column:1/-1; margin:0 0 10px; }
/* rhythm: meta row - small gap (18px) - title - medium gap (34px) - paragraph */
.cs-title{ font-family:var(--font-display); font-weight:500; font-size:clamp(44px,9vw,128px); letter-spacing:-0.04em; line-height:0.98; color:var(--ink); margin:clamp(14px,1.4vw,18px) 0 0; text-wrap:balance; }
.cs-lede{ font-family:var(--font-display); font-weight:400; font-size:clamp(18px,2vw,27px); line-height:1.45; letter-spacing:-0.015em; color:var(--muted); max-width:50ch; margin:clamp(26px,2.8vw,34px) 0 0; text-wrap:balance; }

/* BLOCK 1 opener */

/* .cs-fig.is-full carries margin-inline:auto (to centre under its max-width cap) plus
   min-width:0. Inside .cs-photos - a COLUMN flex container - auto cross-axis margins
   disable flex stretch, so the figure shrink-to-fit its only child .cs-media, which is
   aspect-ratio driven with no intrinsic width: every Details figure resolved to 0x0.
   An explicit width restores the box; the max-width cap and the centring still apply. */
.cs-photos > .cs-fig.is-full{ width:100%; }

/* ---------- BLOCK 1 - silent full-viewport opener ----------
   Sits outside .cs-body, so it reaches both edges with no bleed maths. No top padding
   and no gap: the thin bar is the only thing before the work. svh, not vh, so a phone's
   collapsing browser chrome cannot make it taller than the visible viewport. */
/* Height comes from the ASPECT RATIO, never from the viewport: a forced 100svh with
   object-fit:cover cropped the top and bottom off the frame. 16/9 + object-fit:contain
   means the complete original frame is always visible, at every width, with nothing
   cut. No height, no min-height, no cover. */
.cs-hero-silent{ position:relative; width:100%; max-width:none; aspect-ratio:16/9; margin:0; padding:0; overflow:hidden; background:#0b0b0b;
  /* Edge to edge by negating the container's side padding - .cs-content is the parent
     and carries none, but .cs-scroll's own padding and any inherited inline padding
     are cancelled here. Deliberately NOT 100vw: that counts the scrollbar and would
     overflow. max() keeps the wide-screen term at 0 below the 1720px cap. */
  --cs-bleed-out:calc(clamp(16px,3.4vw,48px) + max(0px,(100vw - 1720px) / 2));
  margin-left:0; margin-right:0; }
.cs-silent-vid{ width:100%; height:100%; object-fit:contain; display:block; }
/* the title block that follows keeps its own rhythm, with nothing above it to clear */
.cs-hero-silent + .cs-hero{ padding-top:clamp(28px,4vw,56px); }

/* Phones: a 16:9 band is only ~190px tall on a portrait screen and reads as a
   thumbnail. The block becomes a standing frame instead - ratio dropped, height from
   the viewport, capped so its bottom always stops short of the fold. svh, not vh, so
   collapsing browser chrome cannot push it past the screen. The 84px reserve covers
   the sticky bar plus a visible slice of the title block below, and it is what keeps
   the scroll arrow (pinned 18-32px above this block's own bottom edge) inside the
   frame and on screen. cover, so the crop takes the SIDES only - top and bottom are
   never cut; --cs-hero-op is the per-project anchor set in casestudy.js. */
@media (max-width:760px){
  .cs-hero-silent{ aspect-ratio:auto; height:78vh; height:min(78svh, calc(100svh - 84px)); }
  .cs-silent-vid{ object-fit:cover; object-position:var(--cs-hero-op,50% 50%); }
}

/* body / sections */
.cs-body{ max-width:1720px; margin:0 auto; padding:0 clamp(16px,3.4vw,48px); }
/* spacing alone separates sections now - the hairlines chopped the page into boxes
   and fought the full-bleed blocks. .cs-credits keeps its own top rule. */
.cs-sec{ padding:clamp(56px,8.5vw,132px) 0; }
.cs-prose{ max-width:62ch; }
.cs-prose p{ font-family:var(--font-body); font-weight:350; font-size:clamp(16px,1.4vw,18px); line-height:1.6; color:var(--ink); }
.cs-prose p + p{ margin-top:1.1em; }
.cs-prose-2{ columns:2; column-gap:56px; max-width:none; }
.cs-prose-2 p{ break-inside:avoid; }
.cs-prose-2 p + p{ margin-top:1.1em; }
@media (max-width:760px){ .cs-prose-2{ columns:1; } }

.cs-callout{ margin-top:32px; padding:24px 28px; border-radius:var(--radius); background:var(--surface); border:1px solid var(--line); max-width:62ch; }
.cs-callout-k{ font-family:var(--font-mono); font-weight:600; font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--accent); display:block; margin-bottom:12px; }
.cs-callout p{ font-family:var(--font-display); font-weight:400; font-size:clamp(18px,1.8vw,22px); line-height:1.45; letter-spacing:-0.015em; color:var(--ink); }

.cs-note{ font-family:var(--font-body); font-weight:350; font-size:16px; line-height:1.6; color:var(--muted); max-width:58ch; margin-top:24px; }
.cs-quote{ font-family:var(--font-display); font-weight:450; font-size:clamp(24px,3.4vw,44px); letter-spacing:-0.03em; line-height:1.1; color:var(--ink); max-width:20ch; margin-top:44px; text-wrap:balance; }
.cs-quote::before{ content:"“"; color:var(--accent); }
.cs-quote::after{ content:"”"; color:var(--accent); }

/* media */
.cs-fig{ margin:0; }
.cs-media{ position:relative; overflow:hidden; border-radius:18px; background:var(--card-dark); box-shadow:var(--shadow); }
.cs-fig.is-full .cs-media{ border-radius:22px; box-shadow:var(--shadow-lift); }
.cs-img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; z-index:1; }
.cs-img:not([src]), .cs-img[src=""]{ display:none; }
.cs-fill{ position:absolute; inset:0; z-index:0;
  background:
    radial-gradient(85% 100% at 62% 26%, var(--glow,rgba(255,255,255,0.16)) 0%, transparent 56%),
    radial-gradient(70% 90% at 14% 96%, rgba(154,154,154,0.08) 0%, transparent 62%),
    repeating-linear-gradient(135deg, rgba(255,255,255,0.022) 0 2px, transparent 2px 12px),
    var(--card-dark);
}
.cs-ph{ position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); z-index:2; text-align:center;
  font-family:var(--font-mono); font-weight:600; font-size:11px; letter-spacing:0.16em; text-transform:uppercase; color:rgba(255,255,255,0.30); line-height:1.8; pointer-events:none; }
.cs-ph span{ display:block; font-size:10px; color:rgba(255,255,255,0.18); letter-spacing:0.1em; }
.cs-media:has(.cs-img[src]:not([src=""])) .cs-fill,
.cs-media:has(.cs-img[src]:not([src=""])) .cs-ph{ display:none; }
/* embedded Google Drive film */
.cs-media-video{ overflow:hidden; }
.cs-media-video .cs-video-embed{ position:absolute; inset:0; width:100%; height:100%; border:0; display:block; z-index:3; }
/* YouTube film container */
.cs-media-yt{ position:relative; }

/* click-to-play poster facade */
.cs-video-poster{
  position:absolute; inset:0; z-index:3; width:100%; height:100%; padding:0;
  border:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px;
  background-color:#0c0c0e; background-size:cover; background-position:center;
  transition:filter var(--t-media) var(--ix-ease);
}
.cs-video-poster::after{
  content:""; position:absolute; inset:0; z-index:0;
  background:linear-gradient(180deg, rgba(8,8,10,0.18) 0%, rgba(8,8,10,0.42) 100%);
  transition:background var(--t-media) var(--ix-ease);
}
.cs-video-poster:hover::after{ background:linear-gradient(180deg, rgba(8,8,10,0.10) 0%, rgba(8,8,10,0.34) 100%); }
.cs-play{
  position:relative; z-index:1; width:78px; height:78px; border-radius:999px;
  display:flex; align-items:center; justify-content:center;
  background:rgba(255,255,255,0.14); border:1px solid rgba(255,255,255,0.5);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  color:#fff; box-shadow:0 10px 40px rgba(0,0,0,0.4);
  transition:transform var(--t-page) var(--ix-ease-out), background var(--t-fill) var(--ix-ease);
}
.cs-play svg{ width:32px; height:32px; margin-left:4px; }
.cs-video-poster:hover .cs-play{ transform:scale(1.08); background:rgba(255,255,255,0.24); }
.cs-play-label{
  position:relative; z-index:1; font-family:var(--font-mono); font-weight:600;
  font-size:11px; letter-spacing:0.16em; text-transform:uppercase; color:#fff;
}
@media (prefers-reduced-motion: reduce){
  .cs-play, .cs-video-poster, .cs-video-poster::after{ transition:none; }
}
.cs-tag{ position:absolute; top:16px; left:16px; z-index:3; font-family:var(--font-mono); font-weight:600; font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:rgba(255,255,255,0.85); background:rgba(10,10,12,0.4); border:1px solid rgba(255,255,255,0.18); padding:4px 12px; border-radius:999px; -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px); }
.cs-cap{ display:flex; align-items:center; gap:8px; margin-top:12px; font-family:var(--font-mono); font-weight:600; font-size:12px; letter-spacing:0.03em; color:var(--muted); }
.cs-cap-dot{ width:5px; height:5px; border-radius:999px; background:var(--accent); flex:0 0 auto; }

/* grids */
.cs-grid{ display:grid; gap:clamp(16px,1.8vw,24px); }
.cs-grid-4{ grid-template-columns:repeat(4,1fr); }
.cs-grid-3{ grid-template-columns:repeat(3,1fr); }
.cs-grid-2{ grid-template-columns:repeat(2,1fr); }
.cs-compare-stills{ margin-bottom:clamp(16px,1.8vw,24px); }
@media (max-width:900px){ .cs-grid-4{ grid-template-columns:repeat(2,1fr); } .cs-grid-3{ grid-template-columns:1fr; } }
@media (max-width:620px){ .cs-grid-4{ grid-template-columns:1fr 1fr; } .cs-grid-2{ grid-template-columns:1fr; } }

/* numbered BTS notes */
.cs-steps{ list-style:none; margin:40px 0 0; display:grid; grid-template-columns:1fr 1fr; gap:24px 56px; max-width:none; }
.cs-steps li{ display:grid; grid-template-columns:auto 1fr; gap:16px; padding-top:16px; border-top:1px solid var(--line); }
.cs-step-no{ font-family:var(--font-mono); font-weight:600; font-size:12px; color:var(--accent); }
.cs-steps p{ font-family:var(--font-body); font-weight:350; font-size:16px; line-height:1.6; color:var(--muted); }
@media (max-width:760px){ .cs-steps{ grid-template-columns:1fr; } }

/* finals - alternate full-bleed / contained */
.cs-finals{ display:flex; flex-direction:column; gap:clamp(20px,3vw,44px); }
.cs-finals .cs-fig.is-full .cs-media{ }
.cs-finals .cs-fig:not(.is-full){ align-self:center; width:100%; }

/* motion gallery - 2×2 grid of looping clips (behave like gifs) */
.cs-motion-grid{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(16px,1.8vw,24px); }
@media (max-width:700px){ .cs-motion-grid{ grid-template-columns:1fr; } }
.cs-motion-grid .cs-tile{ margin:0; min-width:0; }
.cs-motion-vid{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; z-index:1; background:#0c0c0e; }

/* photo gallery - hero full-width still, then a 2-up grid of the rest */
.cs-photos{ display:flex; flex-direction:column; gap:clamp(20px,3vw,44px); }
.cs-photo-row{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(16px,1.8vw,24px); }
.cs-photo-grid{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(16px,1.8vw,24px); align-items:start; }
.cs-photos .cs-fig{ min-width:0; }
/* full-width animated gif - natural aspect ratio (no fixed crop, no freeze) */
.cs-media-gif{ position:relative; overflow:hidden; border-radius:22px; background:var(--card-dark); box-shadow:var(--shadow-lift); }
.cs-gif{ display:block; width:100%; height:auto; }
/* gif used as a fixed-aspect grid cell - aspect set inline (4:3 small, 16:9 span) */
.cs-gif-cell .cs-gif{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
/* breakdown - full content-width gif block (aligns with the section header) */
.cs-fig-break{ max-width:100%; margin-left:0; margin-right:0; }

/* ============================================================
   Before / after comparison slider
   ============================================================ */
/* HEIGHT CAP - the slider used to take the width it was given and let the ratio set
   its height, which on a wide window made it the tallest thing on the page. It is now
   capped at 60vh and the WIDTH is derived from that cap and the media's own ratio
   (--arw / --arh, emitted with the aspect-ratio by the builder), so the frame shrinks
   proportionally instead of being letterboxed or cropped, and centres in the section. */
.cs-compare{
  --cmp-h:60dvh;
  position:relative; width:min(100%, calc(var(--cmp-h) * var(--arw,16) / var(--arh,9)));
  margin-inline:auto; overflow:hidden;
  border-radius:1.5rem; background:var(--card-dark);
  box-shadow:var(--shadow-lift); cursor:ew-resize;
  user-select:none; -webkit-user-select:none; touch-action:none;
  contain:paint;
}
.cs-cmp-img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }
.cs-cmp-after{ z-index:1; }
/* the BEFORE layer is clipped from the right edge to the handle position */
.cs-cmp-clip{ position:absolute; inset:0; z-index:2; clip-path:inset(0 calc(100% - var(--x,50%)) 0 0); }
/* the compositor hint is held only for the duration of a drag - a permanent will-change
   pins a layer for a slider the visitor may never touch */
.cs-compare.is-dragging .cs-cmp-clip{ will-change:clip-path; }
.cs-compare:not(.is-dragging) .cs-cmp-clip{ transition:clip-path var(--t-fast) var(--ix-ease); }
.cs-cmp-clip .cs-cmp-img{ z-index:auto; }
/* placeholder layer */
.cs-cmp-ph{ display:grid; place-items:center; }
.cs-cmp-after.cs-cmp-ph{ background:var(--card-dark); }
.cs-cmp-clip .cs-cmp-ph{ background:color-mix(in srgb, var(--accent) 7%, var(--card-dark)); }

/* labels */
.cs-cmp-lab{
  position:absolute; top:24px; z-index:4; pointer-events:none;
  font-family:var(--font-mono); font-weight:600; font-size:14px;
  letter-spacing:0.13em; text-transform:uppercase; color:#fff;
  background:rgba(8,8,10,0.46); -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border:1px solid rgba(255,255,255,0.16); border-radius:999px; padding:8px 12px;
  transition:opacity var(--t-line) var(--ix-ease);
}
.cs-cmp-lab-l{ left:24px; }
.cs-cmp-lab-r{ right:24px; }

/* handle */
.cs-cmp-handle{
  position:absolute; top:0; bottom:0; left:0; z-index:5;
  /* TRANSFORM, not left: left is a layout property, so moving the handle with it forced
     a reflow of the whole frame on every pointermove. --tx is written in px by the drag
     handler alongside --x, and both land in the same rAF frame. */
  transform:translate3d(var(--tx,0px),0,0) translateX(-50%); will-change:transform;
  width:48px; display:flex; align-items:center; justify-content:center;
  pointer-events:none;
}
.cs-compare:not(.is-dragging) .cs-cmp-handle{ transition:transform var(--t-fast) var(--ix-ease); }
.cs-cmp-line{ position:absolute; top:0; bottom:0; left:50%; width:3px; transform:translateX(-50%); background:rgba(255,255,255,0.9); box-shadow:0 0 12px rgba(0,0,0,0.4); }
.cs-cmp-grab{
  position:relative; pointer-events:auto; cursor:ew-resize; flex:0 0 auto;
  width:68px; height:68px; border-radius:999px;
  display:flex; align-items:center; justify-content:center;
  background:rgba(255,255,255,0.92); color:#111; border:1px solid rgba(255,255,255,0.9);
  box-shadow:0 6px 22px rgba(0,0,0,0.34); transition:transform var(--t-fast) var(--ix-ease), background var(--t-fast) var(--ix-ease);
}
.cs-cmp-grab svg{ width:24px; height:24px; }
.cs-cmp-grab:hover{ transform:scale(1.06); }
.cs-compare.is-dragging .cs-cmp-grab{ transform:scale(1.1); background:#fff; }
.cs-cmp-grab:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
@media (max-width:620px){
  .cs-cmp-lab{ font-size:12px; padding:8px 12px; top:16px; }
  .cs-cmp-lab-l{ left:16px; } .cs-cmp-lab-r{ right:16px; }
  .cs-cmp-grab{ width:56px; height:56px; }
}
@media (prefers-reduced-motion: reduce){
  .cs-cmp-clip, .cs-cmp-handle, .cs-cmp-grab{ transition:none; }
}
@media (max-width:760px){ .cs-fig-break{ max-width:100%; } }
@media (max-width:620px){
  .cs-photo-row{ grid-template-columns:1fr; }
  .cs-photo-grid{ grid-template-columns:1fr; }
}

/* breakdown - two side-by-side looping slots */
.cs-break-grid{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(16px,1.8vw,24px); }
.cs-break-grid .cs-tile{ margin:0; min-width:0; }
@media (max-width:700px){ .cs-break-grid{ grid-template-columns:1fr; } }

/* specs - refined dotted-leader list (no boxes) */
.cs-specs{ margin:0; max-width:760px; }
.cs-spec{ display:flex; align-items:baseline; gap:16px; padding:clamp(12px,1.3vw,16px) 0; border-bottom:1px solid var(--line); }
.cs-spec:first-child{ border-top:1px solid var(--line); }
.cs-spec-k{ flex:none; font-family:var(--font-display,'Host Grotesk',sans-serif); font-weight:600; font-size:12px; letter-spacing:0.08em; text-transform:uppercase; color:var(--muted); }
.cs-spec-dots{ flex:1 1 auto; min-width:24px; align-self:center; height:0; border-bottom:1.5px dotted var(--line-2); transform:translateY(2px); }
.cs-spec-v{ flex:none; margin:0; font-family:var(--font-body,'Inter',sans-serif); font-size:16px; color:var(--ink); text-align:right; }
@media (max-width:480px){
  .cs-spec{ flex-wrap:wrap; gap:4px 14px; }
  .cs-spec-dots{ display:none; }
  .cs-spec-v{ text-align:left; }
}

/* reel */
.cs-reel{ cursor:default; }
.cs-play{ position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); z-index:3; width:64px; height:64px; border-radius:999px; background:rgba(255,255,255,0.12); border:1px solid rgba(255,255,255,0.4); -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px); display:flex; align-items:center; justify-content:center; color:#fff; font-size:20px; padding-left:4px; }
.cs-media:has(.cs-img[src]:not([src=""])) .cs-play{ display:none; }

/* credits */
.cs-credits{ max-width:1720px; margin:0 auto; padding:clamp(48px,6vw,92px) clamp(16px,3.4vw,48px); border-top:1px solid var(--line); }
.cs-credit-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:0; border-top:1px solid var(--line); }
.cs-credit{ display:flex; flex-direction:column; gap:8px; padding:24px; border-bottom:1px solid var(--line); }
.cs-credit:not(:nth-child(3n)){ border-right:1px solid var(--line); }
.cs-credit .k{ font-family:var(--font-mono); font-weight:600; font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--faint); }
.cs-credit .v{ font-family:var(--font-display); font-size:16px; letter-spacing:-0.01em; color:var(--ink); }
.cs-disc{ font-family:var(--font-mono); font-weight:600; font-size:11px; letter-spacing:0.04em; color:var(--muted); margin-top:24px; }
@media (max-width:760px){ .cs-credit-grid{ grid-template-columns:1fr; } .cs-credit{ border-right:none !important; } }

/* footer nav */
.cs-foot{ max-width:1720px; margin:0 auto; padding:clamp(32px,4vw,56px) clamp(16px,3.4vw,48px) clamp(56px,7vw,96px); display:flex; align-items:center; justify-content:space-between; gap:24px; border-top:1px solid var(--line); flex-wrap:wrap; }
.cs-foot-back{ font-family:var(--font-body); font-size:16px; font-weight:500; color:var(--muted); background:none; border:none; padding:8px 0; min-height:44px; transition:color var(--t-fast) var(--ix-ease); }
.cs-foot-back:hover{ color:var(--ink); }
.cs-foot-next{ text-align:right; background:none; border:none; padding:8px 0; min-height:44px; display:flex; flex-direction:column; gap:8px; align-items:flex-end; }
.cs-foot-k{ font-family:var(--font-mono); font-weight:600; font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--faint); }
.cs-foot-name{ font-family:var(--font-display); font-weight:500; font-size:clamp(24px,3.4vw,40px); letter-spacing:-0.03em; color:var(--ink); transition:color var(--t-fast) var(--ix-ease); }
.cs-foot-ar{ color:var(--accent); display:inline-block; transition:transform var(--t-line) var(--ix-ease); }
/* the Next-project hover lives in ONE gated block - see cs-foot-ar */
.cs-foot-back:focus-visible, .cs-foot-next:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; border-radius:4px; }

/* ============================================================
   narrative template - two-col overview · placeholders · breakdown
   ============================================================ */

/* two-column editorial overview (label left, copy right) */
.cs-twocol{ display:grid; grid-template-columns:minmax(0,0.4fr) minmax(0,1fr); gap:clamp(24px,4vw,72px); align-items:start; }
.cs-twocol-k{ font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.16em; text-transform:uppercase; color:var(--faint); position:sticky; top:84px; }
.cs-twocol-r{ min-width:0; }
@media (max-width:760px){ .cs-twocol{ grid-template-columns:1fr; gap:18px; } .cs-twocol-k{ position:static; } }

/* compact, swapped brief - challenge LEFT, copy RIGHT */
.cs-sec--brief{ padding-top:clamp(40px,5vw,72px); padding-bottom:clamp(40px,5vw,72px); }
.cs-sec--brief .cs-h2{ font-size:clamp(26px,3.6vw,46px); margin-bottom:clamp(20px,2.4vw,32px); }
.cs-twocol-brief{ grid-template-columns:minmax(0,0.86fr) minmax(0,1fr); align-items:start; }
.cs-twocol-brief .cs-twocol-r{ padding-top:2px; }
.cs-callout--compact{ margin-top:0; padding:20px 24px; max-width:none; }
.cs-callout--compact p{ font-family:var(--font-body); font-weight:350; font-size:clamp(16px,1.4vw,18px); line-height:1.6; letter-spacing:0; }
@media (max-width:760px){ .cs-twocol-brief{ grid-template-columns:1fr; gap:20px; } }
/* solo brief — overview only (no challenge callout): one readable column */
.cs-twocol-solo .cs-prose{ max-width:72ch; }
.cs-twocol-solo .cs-prose p{ font-size:clamp(16px,1.5vw,22px); line-height:1.6; }

/* placeholder flag + muted placeholder copy */
.cs-flag{ display:inline-block; vertical-align:middle; font-family:var(--font-display); font-weight:600; font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--accent); background:var(--accent-soft); border:1px solid rgba(154,154,154,0.28); padding:4px 8px; border-radius:999px; margin-right:8px; transform:translateY(-1px); }
.cs-ph-copy{ font-family:var(--font-body); font-weight:350; font-size:clamp(16px,1.4vw,18px); line-height:1.6; color:var(--muted); }
.cs-prose .cs-ph-copy{ margin:0; }

/* breakdown facts grid */
.cs-facts{ display:grid; grid-template-columns:repeat(3,1fr); gap:0; border-top:1px solid var(--line); }
.cs-fact{ display:flex; flex-direction:column; gap:8px; padding:24px; border-bottom:1px solid var(--line); }
.cs-fact:not(:nth-child(3n)){ border-right:1px solid var(--line); }
.cs-fact .k{ font-family:var(--font-display); font-weight:600; font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--faint); }
.cs-fact .v{ font-family:var(--font-display); font-weight:500; font-size:clamp(16px,1.5vw,18px); letter-spacing:-0.01em; color:var(--ink); }
@media (max-width:760px){ .cs-facts{ grid-template-columns:1fr 1fr; } .cs-fact:nth-child(2n){ border-right:none; } .cs-fact:not(:nth-child(3n)){ border-right:1px solid var(--line); } .cs-fact:nth-child(2n){ border-right:none !important; } }
@media (max-width:480px){ .cs-facts{ grid-template-columns:1fr; } .cs-fact{ border-right:none !important; } }

/* ============================================================
   "Still working on this" - in-production / under-construction state
   ============================================================ */
/* small status tag in the hero meta row */
.cs-wip-tag{
  display:inline-flex; align-items:center; gap:7px;
  font-family:var(--font-mono); font-weight:600; font-size:11px; letter-spacing:0.08em;
  text-transform:uppercase; color:var(--accent);
}
.cs-wip-tag-dot{ width:8px; height:8px; border-radius:50%; background:var(--accent); box-shadow:0 0 0 0 var(--accent); animation:csWipPulse 1.9s ease-out infinite; }

/* the banner above the hero image */
.cs-wip{
  display:flex; align-items:flex-start; gap:16px;
  margin:clamp(22px,3vw,34px) 0 clamp(26px,3.6vw,40px);
  padding:16px 24px 16px 20px;
  border:1px solid var(--line-2); border-radius:14px;
  background:
    repeating-linear-gradient(-45deg, color-mix(in srgb, var(--accent) 13%, transparent) 0 10px, transparent 10px 20px) left/8px 100% no-repeat,
    color-mix(in srgb, var(--accent) 7%, var(--surface));
  position:relative; overflow:hidden;
}
.cs-wip::before{
  content:""; position:absolute; left:0; top:0; bottom:0; width:5px;
  background:repeating-linear-gradient(-45deg, var(--accent) 0 6px, color-mix(in srgb, var(--accent) 30%, transparent) 6px 12px);
}
.cs-wip-dot{
  flex:0 0 auto; width:12px; height:12px; margin-top:4px; border-radius:50%;
  background:var(--accent); box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 70%, transparent);
  animation:csWipPulse 1.9s ease-out infinite;
}
.cs-wip-txt{ display:flex; flex-direction:column; gap:4px; min-width:0; }
.cs-wip-k{ font-family:var(--font-display); font-weight:600; font-size:clamp(16px,1.5vw,18px); letter-spacing:-0.01em; color:var(--ink); }
.cs-wip-d{ font-family:var(--font-body); font-weight:350; font-size:clamp(14px,1.2vw,16px); line-height:1.5; color:var(--muted); max-width:64ch; text-wrap:pretty; }
@keyframes csWipPulse{
  0%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent); }
  70%{ box-shadow:0 0 0 9px color-mix(in srgb, var(--accent) 0%, transparent); }
  100%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce){ .cs-wip-dot, .cs-wip-tag-dot{ animation:none; } }

/* under-construction page - soft light-blue gradient wash (echoes the sky in the renders) */
.cs-root.cs-wip-page{
  /* dark Aston Martin racing-green backdrop */
  --bg:        #080D0B;
  --surface:   #141D18;
  --surface-2: #1D2A23;
  --ink:       #EEF4F0;
  --muted:     #A6B7AE;
  --faint:     #71837A;
  --line:      rgba(180,225,205,0.10);
  --line-2:    rgba(180,225,205,0.18);
  --accent:    #2E9E6F;          /* Aston Martin racing green, lifted for dark */
  background:
    radial-gradient(125% 85% at 50% -12%, #143D30 0%, #0E2A22 26%, #0A1A16 58%, #070D0B 100%);
  background-attachment:fixed;
}
/* keep the sticky header reading as a solid bar over the wash */
.cs-wip-page .cs-bar{ background:rgba(8,13,11,0.82); border-bottom-color:var(--line); backdrop-filter:saturate(1.3) blur(10px); }
.cs-wip-page .cs-media, .cs-wip-page .cs-media-gif{ background:#0F1714; }

/* ============================================================
   Process - production plan (scene sequence + weekly schedule)
   ============================================================ */
.cs-process-intro{ max-width:72ch; }
.cs-scene-flow{ display:grid; grid-template-columns:repeat(4,1fr); gap:clamp(12px,1.4vw,18px); margin:clamp(22px,2.6vw,32px) 0 clamp(30px,3.4vw,44px); }
.cs-scene{ display:flex; flex-direction:column; gap:12px; }
.cs-scene-media{ position:relative; aspect-ratio:16/10; border-radius:12px; overflow:hidden; background:var(--surface); border:1px solid var(--line); }
.cs-scene-no{ position:absolute; left:12px; top:12px; z-index:2; font-family:var(--font-mono); font-weight:700; font-size:12px; letter-spacing:0.08em; color:var(--accent); text-shadow:0 1px 4px rgba(0,0,0,0.5); }
.cs-scene-img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }
.cs-scene-meta{ display:flex; flex-direction:column; gap:4px; }
.cs-scene-name{ font-family:var(--font-display); font-weight:600; font-size:clamp(16px,1.5vw,18px); letter-spacing:-0.01em; color:var(--ink); }
.cs-scene-note{ font-family:var(--font-body); font-weight:350; font-size:12px; line-height:1.5; color:var(--muted); }

.cs-sched{ display:grid; grid-template-columns:repeat(4,1fr); gap:clamp(12px,1.4vw,18px); align-items:start; }
.cs-week{ border:1px solid var(--line); border-radius:14px; background:color-mix(in srgb, var(--accent) 5%, var(--surface)); padding:16px 20px 20px; }
.cs-week-top{ display:flex; align-items:center; justify-content:space-between; gap:12px; padding-bottom:16px; margin-bottom:16px; border-bottom:1px solid var(--line); }
.cs-week-k{ font-family:var(--font-mono); font-weight:700; font-size:12px; letter-spacing:0.1em; text-transform:uppercase; color:var(--ink); }
.cs-week-score{ font-family:var(--font-mono); font-weight:700; font-size:11px; letter-spacing:0.04em; color:#fff; background:var(--accent); border-radius:999px; padding:4px 12px; white-space:nowrap; }
.cs-week-score.is-pending{ color:var(--accent); background:color-mix(in srgb, var(--accent) 14%, transparent); text-transform:uppercase; letter-spacing:0.08em; }
.cs-tasklist{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:12px; }
.cs-tasklist li{ display:flex; align-items:flex-start; gap:12px; font-family:var(--font-body); font-weight:350; font-size:14px; line-height:1.5; color:var(--ink); }
.cs-task-mark{ flex:0 0 auto; width:18px; height:18px; border-radius:50%; display:grid; place-items:center; margin-top:1px; }
.cs-task-mark svg{ width:12px; height:12px; }
.cs-tasklist li.is-done .cs-task-mark{ background:#4E9E76; color:#fff; }
.cs-tasklist li.is-pending .cs-task-mark{ border:1.5px dashed var(--line-2); }
.cs-tasklist li.is-pending{ color:var(--muted); }
.cs-week-bar{ margin-top:16px; height:4px; border-radius:999px; background:var(--line); overflow:hidden; }
/* pinned to the muted ink, NOT --accent: nothing in the overlay carries the accent at
   rest, and these four bars sit at rest in the schedule */
.cs-week-bar span{ display:block; height:100%; border-radius:999px; background:var(--muted); }
@media (max-width:1080px){ .cs-sched{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:860px){ .cs-scene-flow{ grid-template-columns:1fr 1fr; } .cs-sched{ grid-template-columns:1fr; } }
@media (max-width:460px){ .cs-scene-flow{ grid-template-columns:1fr; } }

/* ---------- hero split: vertical film left, name + description right (Purist) ---------- */
.cs-split{ display:grid; grid-template-columns:minmax(0,0.9fr) minmax(0,1.1fr); gap:clamp(24px,4vw,64px); align-items:center; }
.cs-split-media{ position:relative; width:100%; overflow:hidden; background:rgba(0,0,0,0.04); }
.cs-split-media .cs-split-vid{ width:100%; height:auto; display:block; }
.cs-split-media[style*="aspect-ratio"] .cs-split-vid{ position:absolute; inset:0; height:100%; object-fit:cover; }
.cs-split-ph{ position:relative; display:flex; align-items:flex-end; justify-content:center; }
.cs-split-ph span{ position:relative; z-index:2; margin-bottom:16px; font-size:12px; letter-spacing:0.12em; text-transform:uppercase; color:rgba(255,255,255,0.85); background:rgba(0,0,0,0.45); padding:8px 12px; }
/* no rule under the title: the only hairline in the block is the one above the facts
   line, same as the header projects */
.cs-split-title{ margin:0 0 16px; font-size:clamp(30px,3.4vw,54px); line-height:1.1; letter-spacing:-0.02em; display:block; border-bottom:0; padding-bottom:0; }
.cs-split-desc{ margin:0; max-width:52ch; font-size:clamp(16px,1.1vw,18px); line-height:1.6; opacity:0.72; text-wrap:pretty; }
@media (max-width:820px){ .cs-split{ grid-template-columns:1fr; gap:24px; } }

/* ---------- split hero play button + still slots ---------- */
.cs-split-play{ position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); z-index:4; display:none; align-items:center; justify-content:center; width:64px; height:64px; border:0; background:rgba(255,255,255,0.92); color:#0a0a0a; }
.cs-split-play:hover{ background:#fff; }
.cs-slots{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:clamp(12px,1.6vw,22px); }
.cs-slot{ position:relative; aspect-ratio:4/5; background:rgba(0,0,0,0.045); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px; }
.cs-slot-no{ font-family:var(--font-mono); font-size:12px; letter-spacing:0.12em; opacity:0.45; }
.cs-slot-lab{ font-size:12px; letter-spacing:0.1em; text-transform:uppercase; opacity:0.4; }
@media (max-width:760px){ .cs-slots{ grid-template-columns:repeat(2,minmax(0,1fr)); } }

/* ---------- 3-up square gallery, images fit whole (no crop) ---------- */
.cs-photos.is-3up .cs-photo-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); }
.cs-photos.is-3up .cs-media{ background:none; }
.cs-photos.is-3up .cs-img{ object-fit:cover; }
@media (max-width:760px){ .cs-photos.is-3up .cs-photo-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }

/* ============================================================
   WIDE MEDIA - the work is the largest thing on every project page.
   Media blocks break out past the text column to a near-full-viewport
   track; the gutter is preserved so nothing overflows horizontally.
   ============================================================ */
.cs-photos, .cs-finals, .cs-motion-grid, .cs-break-grid, .cs-split, .cs-sec .cs-fig.is-full{ margin-inline:calc(50% - 50vw + clamp(16px,3vw,56px)); }
.cs-photo-grid, .cs-photo-row, .cs-motion-grid, .cs-break-grid{ gap:clamp(18px,2vw,34px); }
.cs-photos.is-3up .cs-photo-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); }
.cs-split{ grid-template-columns:minmax(0,1.25fr) minmax(0,1fr); }
@media (max-width:1080px){ .cs-photos.is-3up .cs-photo-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:900px){
  .cs-photos, .cs-finals, .cs-motion-grid, .cs-break-grid, .cs-split, .cs-sec .cs-fig.is-full{ margin-inline:0; }
}
@media (max-width:760px){ .cs-photos.is-3up .cs-photo-grid{ grid-template-columns:1fr; } }

/* ============================================================
   HEIGHT CAPS - a single main visual always fits the screen.
   Ratio is preserved (contain); rows/grids are untouched.
   ============================================================ */
.cs-split-media{ max-height:82dvh; display:flex; align-items:center; justify-content:center; }
.cs-split-media .cs-split-vid{ max-height:82dvh; width:auto; max-width:100%; height:auto; object-fit:contain; margin-inline:auto; }
.cs-sec .cs-fig.is-full .cs-media, .cs-hero-media .cs-media{ max-height:85dvh; }
.cs-sec .cs-fig.is-full .cs-media > img, .cs-sec .cs-fig.is-full .cs-media > video,
.cs-hero-media .cs-media > img, .cs-hero-media .cs-media > video{ object-fit:contain; }
/* scale the whole frame instead of clipping it: a 16:9 figure never exceeds the cap */
.cs-sec .cs-fig.is-full, .cs-hero-media{ max-width:calc(85dvh * 16 / 9); margin-inline:auto; }
@media (max-width:760px){
  .cs-split-media, .cs-split-media .cs-split-vid{ max-height:70dvh; }
  .cs-sec .cs-fig.is-full .cs-media, .cs-hero-media .cs-media{ max-height:70dvh; }
  .cs-sec .cs-fig.is-full, .cs-hero-media{ max-width:100%; }
}


/* ---------- Osaka: full-bleed top reel, capped main film, title block ---------- */
/* full-bleed WITHOUT 100vw: .cs-body carries clamp(16px,3.4vw,48px) of side padding,
   so negating exactly that reaches both edges and cannot overflow when the overlay
   shows a scrollbar (100vw includes it, the container width does not). */
.cs-sec--bleed{ padding:0; border-top:0; width:auto;
  /* escape BOTH the side padding and, on displays wider than the 1720px .cs-body cap,
     the centring gutter - otherwise "full-bleed" stopped 1720px wide on a large
     monitor. max() keeps the wide-screen term at 0 below the cap, so narrow viewports
     negate the padding only and can never overflow. The overlay hides its scrollbar
     (.cs-scroll, and body.cs-open is position:fixed), so 100vw here is the visible
     width with no scrollbar to account for. */
  --cs-bleed-out:calc(clamp(16px,3.4vw,48px) + max(0px,(100vw - 1720px) / 2));
  margin-left:calc(-1 * var(--cs-bleed-out)); margin-right:calc(-1 * var(--cs-bleed-out)); }
.cs-sec--bleed + .cs-sec{ padding-top:clamp(40px,6vw,88px); }
/* text inside a bleed section stays in the text column */
.cs-bleed-pad{ padding:0 var(--cs-bleed-out,clamp(16px,3.4vw,48px)); }
.cs-sec--compare{ padding-top:clamp(40px,6vw,88px); }
/* the media itself must lose the contained cap, not just its margins: the standing
   rule .cs-sec .cs-fig.is-full,.cs-hero-media{max-width:calc(151.111vh)} is a
   HEIGHT-based cap with auto-centering, and it outranked a margin-only override -
   so both bleed blocks sat 816px wide, inset 54px, narrower than the text column.
   Inside a bleed section the media fills the width and keeps its natural ratio. */
.cs-sec--bleed .cs-fig,.cs-sec--bleed .cs-fig.is-full,.cs-sec--bleed .cs-hero-media,
.cs-sec--compare .cs-fig,.cs-sec--compare .cs-fig.is-full{ max-width:none; width:100%; margin:0; }
.cs-sec--bleed .cs-fig.is-full .cs-media,.cs-sec--bleed .cs-hero-media .cs-media{ max-width:none; width:100%; margin:0; }
.cs-bleed-img{ width:100%; height:auto; display:block; }
/* BLOCK 2 - the quiet line between the film and the stills */
.cs-sec--lead{ padding:clamp(40px,6vw,96px) 0 clamp(32px,4.5vw,72px); }
.cs-lead-line{ font-family:var(--font-display); font-weight:400; font-size:clamp(19px,2.2vw,30px);
  line-height:1.4; letter-spacing:-0.015em; color:var(--ink); max-width:46ch; margin:0; text-wrap:pretty; }
/* 11. phones: the desktop rhythm is far too tall */
@media (max-width:760px){
  .cs-sec{ padding:clamp(32px,7vw,52px) 0; }
  .cs-sec--bleed + .cs-sec,.cs-sec--compare{ padding-top:clamp(28px,6vw,44px); }
  .cs-sec--lead{ padding:clamp(28px,6vw,44px) 0 clamp(22px,5vw,34px); }
}
.cs-bleed-media{ background:none; }
.cs-bleed-vid{ width:100%; height:auto; display:block; }
.cs-sec--film{ border-top:0; padding-top:0; }
.cs-film-media{ background:none; max-width:min(100%, calc(78dvh * 16 / 9)); margin:0 auto; }
.cs-film-vid{ width:100%; height:auto; display:block; }
.cs-sec--titleblock{ border-top:0; padding-top:clamp(28px,3.4vw,52px); }
/* the two-column block matches the HEADER grid exactly (see .cs-hero-grid): the title
   is bottom-aligned in its own column, so it always closes on the same line as the copy
   beside it and sits directly above the facts hairline. align-items:end anchors it to the
   bottom of the row whichever column is taller, so three paragraphs of copy bottom-align
   the title the same way two do. */
.cs-titleblock{ display:grid; grid-template-columns:minmax(0,0.9fr) minmax(0,1.1fr); gap:clamp(24px,4vw,64px); align-items:end; }
.cs-titleblock .cs-split-title{ margin:0; }
.cs-titleblock .cs-split-desc{ max-width:58ch; }
/* one column below 820px: no bottom-align to do, the title simply leads the block */
@media (max-width:820px){ .cs-titleblock{ grid-template-columns:1fr; gap:18px; align-items:start; } }
/* the right column can hold SEVERAL paragraphs at one single size */
.cs-titleblock-copy{ display:flex; flex-direction:column; gap:1.15em; min-width:0; }
.cs-titleblock-copy .cs-split-desc{ max-width:58ch; }
.cs-split-desc.is-ph{ color:var(--muted); }
/* the facts line closes the whole two-column block, not just its left column */
.cs-titleblock .cs-hero-facts{ grid-column:1/-1; }


/* ============================================================
   CASE STUDY - scroll reveal
   casestudy.js already marks every section .reveal and runs an observer
   rooted on .cs-scroll (the overlay scrolls itself, not the window).
   This is the missing half: the same opacity + 20px lift the site uses,
   on the same --t-rv / --ix-ease-out tokens.
   ============================================================ */
html.rv-on .cs-scroll .reveal{ opacity:0; transform:translateY(20px); will-change:opacity, transform;
  transition:opacity var(--t-rv) var(--ix-ease-out), transform var(--t-rv) var(--ix-ease-out); }
html.rv-on .cs-scroll .reveal.in{ opacity:1; transform:none; }
@media (max-width:820px){ html.rv-on .cs-scroll .reveal{ transform:translateY(16px); } }
@media (prefers-reduced-motion:reduce){
  html.rv-on .cs-scroll .reveal{ opacity:1 !important; transform:none !important;
    transition:none !important; will-change:auto !important; }
}


/* ---------- BLOCK 1 - the bar OVER the opening film ----------
   .cs-cine is set on .cs-content whenever the silent hero is the first block after
   the bar. The bar then costs no layout height (negative margin-bottom equal to its
   own height), so the film touches the very top edge of the overlay with nothing
   above it. The bar keeps position:sticky, so it still travels with the scroll and
   returns to its solid state once the film has passed behind it (.is-over-hero is
   driven by an IntersectionObserver on the hero, root = .cs-scroll, top margin
   pulled in by the bar height - the state flips the instant the film clears it).
   These rules sit at the END of the file on purpose: the per-theme .cs-dark /
   .cs-crimson / .cs-wip-page bar fills have equal specificity, so source order is
   what lets the transparent state win over all of them. */
.cs-cine .cs-bar{ margin-bottom:calc(-1 * var(--cs-bar-h)); }
.cs-bar.is-over-hero{ background:transparent; border-bottom-color:transparent; box-shadow:none;
  -webkit-backdrop-filter:none; backdrop-filter:none; }
/* legibility without a scrim over the frame: a short fall from a soft dark at the
   very top to nothing by 120px. Painted behind the bar's own content (z-index:-1
   inside the bar's stacking context), so it can never dim the film below 120px. */
.cs-bar::before{ content:""; position:absolute; left:0; right:0; top:0; height:120px; z-index:-1; pointer-events:none;
  background:linear-gradient(to bottom, rgba(0,0,0,0.45) 0%, rgba(0,0,0,0.20) 52%, rgba(0,0,0,0) 100%);
  opacity:0; transition:opacity var(--t-media) var(--ix-ease); }
.cs-bar.is-over-hero::before{ opacity:1; }
.cs-bar.is-over-hero .cs-bar-title{ color:#fff; text-shadow:0 1px 10px rgba(0,0,0,0.42); }
.cs-bar.is-over-hero .cs-bar-pos{ color:rgba(255,255,255,0.86); text-shadow:0 1px 10px rgba(0,0,0,0.42); }
/* over the hero the X turns white like the bar text; a drop-shadow does for the
   stroke what text-shadow does for the titles, so it still reads on a bright frame */
.cs-bar.is-over-hero .cs-close{ color:#fff; }
.cs-bar.is-over-hero .cs-close svg{ filter:drop-shadow(0 1px 10px rgba(0,0,0,0.45)) drop-shadow(0 0 2px rgba(0,0,0,0.35)); }
.cs-bar.is-over-hero .cs-close:hover{ color:var(--accent); }
.cs-bar.is-over-hero .cs-close:focus-visible{ outline-color:rgba(255,255,255,0.9); }
.cs-close svg{ transition:filter var(--t-media) var(--ix-ease); }
.cs-bar-title,.cs-bar-pos{ transition:color var(--t-media) var(--ix-ease), text-shadow var(--t-media) var(--ix-ease); }


/* ---------- BLOCK 1 - scroll hint ----------
   Only ever printed on a VIDEO hero (.has-hint), never on a still. Both the arrow and
   the shallow bottom gradient read from --hint-o, which JS drives from scroll distance,
   so the pair fade together and .hint-out finishes them for the page view. */
.cs-hero-silent.has-hint::after{ content:""; position:absolute; left:0; right:0; bottom:0; height:104px; pointer-events:none;
  background:linear-gradient(to top, rgba(0,0,0,0.34) 0%, rgba(0,0,0,0.14) 52%, rgba(0,0,0,0) 100%);
  opacity:var(--hint-o,1); }
/* The arrow carries its own contained backdrop - a 44px circle, dark and blurred - so
   contrast comes from the control itself rather than from a gradient over the film. That
   is what makes it hold against a bright city, a dark room and a warm product shot alike.
   No border, no solid fill, no shadow; a circle because this is a control over moving
   media, where the site's square language would read as a UI panel sitting on the film. */
/* A filled white triangle carrying its own thin dark outline - no circle, no panel, no
   backdrop, the same way the site cursor holds its silhouette. A tight dark shadow on the
   shape backs the outline up on the brightest frames. The 44px button is the tap target;
   the visible mark is 18x12. */
.cs-hero-hint{ position:absolute; left:50%; bottom:clamp(18px,2.6vh,32px); transform:translateX(-50%);
  width:44px; height:44px; padding:0; margin:0; border:0; background:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; z-index:2;
  opacity:var(--hint-o,1);
  transition:opacity var(--t-fast) var(--ix-ease); }
.cs-hint-tri{ overflow:visible; filter:drop-shadow(0 1px 2px rgba(0,0,0,0.55)); }
.cs-hero-hint svg{ display:block; animation:cs-hint-drift 3.2s var(--ix-ease) infinite; }
.cs-hero-hint:hover svg{ animation-play-state:running; }
@media (hover:hover) and (pointer:fine){
  /* hover nudges the mark down instead of recolouring it */
  .cs-hero-hint{ transition:opacity var(--t-fast) var(--ix-ease), padding-top var(--t-fast) var(--ix-ease); }
  .cs-hero-hint:hover{ padding-top:7px; }
}
.cs-hero-hint:focus-visible{ outline:2px solid rgba(255,255,255,0.85); outline-offset:4px; border-radius:50%; }
.cs-hero-silent.hint-out .cs-hero-hint{ opacity:0; pointer-events:none; }
.cs-hero-silent.hint-out::after{ opacity:0; }
@keyframes cs-hint-drift{ 0%{ transform:translateY(-3px); } 50%{ transform:translateY(3px); } 100%{ transform:translateY(-3px); } }
@media (prefers-reduced-motion: reduce){ .cs-hero-hint svg{ animation:none; } }


/* the self-initiated note keeps the meta row's voice on the right-hand side */
.cs-hero-top .cs-selfinit{ font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.16em; text-transform:uppercase; color:var(--muted); }
/* the medium gap that used to sit under the deleted subtitle now joins the title block
   to the first paragraph below it - the block reads as one unit again */
.cs-hero + .cs-sec{ padding-top:clamp(30px,3.4vw,44px); }


/* ---------- DETAILS grid - unit-cell mosaic, ZERO crop ----------
   Every source still is 16/9, so every CELL is 16/9 and the rhythm comes from cell
   SCALE and never from proportion. A small cell is one unit. A large cell is exactly two
   units across and two units down, so two stacked small cells equal one large: the bed
   tiles with no holes and no cell has to crop its image to fit.

   The gutter is NOT grid gap. A gap adds the same pixels to a 1-unit and a 2-unit span,
   which throws one of the two off ratio (2u+g across against 2r+g down). Instead the
   tracks tile edge to edge and every cell insets its own image with PROPORTIONAL padding:
   --mo-p across, --mo-p * 9/16 down. Subtracting a 16/9-shaped border from a 16/9 track
   leaves a 16/9 content box at BOTH scales, which is what makes the no-crop claim exact
   rather than approximate. The visible gutter is 2 * --mo-p across and 2 * --mo-p * 9/16
   down: the horizontal gutter is the 20px this section always used, and the vertical one
   is that same measure read in the grid's own 16/9 rhythm.

   Blocks - each one fills its rows completely, so no white area is ever left over:
     five - 4 cols, 2 rows: one large beside a 2x2 block of four small
     trio - 3 cols, 2 rows: one large beside two stacked small
     duo  - 2 cols, 1 row
     quad - 4 cols, 1 row
     solo - one cell, full width
   The large cell alternates side from block to block (lg-lead / lg-trail), so the seams
   of one block never line up with the next. */
/* a COLUMN of blocks, not a grid of them: the blocks carry an aspect-ratio and no
   intrinsic width (their images are absolutely positioned), so an auto grid track
   collapses around them - a flex column stretches every block to the full bed instead */
.cs-mosaic{ --mo-p:10px; display:flex; flex-direction:column; gap:0;
  margin-inline:calc(50% - 50vw + clamp(16px,3vw,56px) - var(--mo-p)); }
.cs-mo-block{ display:grid; gap:0; width:100%; align-items:stretch; justify-items:stretch; }
.cs-mosaic .cs-mo{ display:block; margin:0; min-width:0; min-height:0; box-sizing:border-box;
  padding:calc(var(--mo-p) * 9 / 16) var(--mo-p); }
/* no border, no ring, no shadow: the frames separate through the gutters alone. The
   CLIP lives here, on the padded content box, so a hover scale cannot bleed into a
   gutter and the gutters themselves stay empty. */
.cs-mosaic .cs-media{ height:100%; width:100%; border:0; border-radius:0; box-shadow:none; background:#0a0a0a; aspect-ratio:auto; overflow:hidden; }
/* the stills open in the shared lightbox, so they carry the site's interactive cursor
   state exactly as the process clips do. cover, not contain: cell and source are the
   same 16/9, so cover crops nothing, and a source that is a pixel or two off ratio
   fills its cell instead of letterboxing inside it. */
.cs-mosaic .cs-img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; cursor:pointer; }
/* HOVER - the image scales inside a clipped cell, so the grid never moves. transform
   only: no width, height or margin is touched, so nothing outside the cell repaints.
   The origin is authored per image (see the builder) to match its object-position. */
@media (hover:hover) and (pointer:fine){
  .cs-mosaic .cs-img{ transition:transform .5s var(--ix-ease); will-change:transform; }
  .cs-mosaic .cs-mo:hover .cs-img{ transform:scale(1.04); }
}
@media (prefers-reduced-motion:reduce){
  .cs-mosaic .cs-mo:hover .cs-img{ transform:none; }
}

@media (min-width:761px){
  /* The block's own ratio fixes the track ratio: c columns and r rows across width W
     give tracks of W/c by W*9/(16c) - exactly 16/9 - when the block is (16c)/(9r). */
  .cs-mo-block.is-solo{ grid-template-columns:1fr; grid-template-rows:1fr; aspect-ratio:16/9; }
  .cs-mo-block.is-duo{ grid-template-columns:repeat(2,1fr); grid-template-rows:1fr; aspect-ratio:32/9; }
  .cs-mo-block.is-quad{ grid-template-columns:repeat(4,1fr); grid-template-rows:1fr; aspect-ratio:64/9; }
  .cs-mo-block.is-trio{ grid-template-columns:repeat(3,1fr); grid-template-rows:repeat(2,1fr); aspect-ratio:8/3; }
  .cs-mo-block.is-five{ grid-template-columns:repeat(4,1fr); grid-template-rows:repeat(2,1fr); aspect-ratio:32/9; }

  /* TRIO - large 2x2 with two small stacked beside it */
  .is-trio.lg-lead .cs-mo:nth-child(1){ grid-area:1 / 1 / 3 / 3; }
  .is-trio.lg-lead .cs-mo:nth-child(2){ grid-area:1 / 3 / 2 / 4; }
  .is-trio.lg-lead .cs-mo:nth-child(3){ grid-area:2 / 3 / 3 / 4; }
  .is-trio.lg-trail .cs-mo:nth-child(1){ grid-area:1 / 1 / 2 / 2; }
  .is-trio.lg-trail .cs-mo:nth-child(2){ grid-area:2 / 1 / 3 / 2; }
  .is-trio.lg-trail .cs-mo:nth-child(3){ grid-area:1 / 2 / 3 / 4; }

  /* FIVE - large 2x2 beside a 2x2 block of four small. Placement keeps the reading
     order sequential in both variants, so a chronological set stays in order. */
  .is-five.lg-lead .cs-mo:nth-child(1){ grid-area:1 / 1 / 3 / 3; }
  .is-five.lg-lead .cs-mo:nth-child(2){ grid-area:1 / 3 / 2 / 4; }
  .is-five.lg-lead .cs-mo:nth-child(3){ grid-area:1 / 4 / 2 / 5; }
  .is-five.lg-lead .cs-mo:nth-child(4){ grid-area:2 / 3 / 3 / 4; }
  .is-five.lg-lead .cs-mo:nth-child(5){ grid-area:2 / 4 / 3 / 5; }
  .is-five.lg-trail .cs-mo:nth-child(1){ grid-area:1 / 1 / 2 / 2; }
  .is-five.lg-trail .cs-mo:nth-child(2){ grid-area:1 / 2 / 2 / 3; }
  .is-five.lg-trail .cs-mo:nth-child(3){ grid-area:2 / 1 / 3 / 2; }
  .is-five.lg-trail .cs-mo:nth-child(4){ grid-area:2 / 2 / 3 / 3; }
  .is-five.lg-trail .cs-mo:nth-child(5){ grid-area:1 / 3 / 3 / 5; }
}

/* PHONES - one column, full width, every still at its own native 16/9, same order.
   The proportional inset is dropped here and a plain gap does the spacing, because a
   single column has no mixed-scale neighbour to stay in ratio with. */
@media (max-width:760px){
  .cs-mosaic{ --mo-p:0px; gap:20px; }
  .cs-mo-block{ grid-template-columns:1fr; grid-template-rows:none; aspect-ratio:auto; gap:20px; }
  .cs-mosaic .cs-media{ aspect-ratio:16/9; height:auto; }
}

/* LOOPS - silent motion beside the film. The vertical clip keeps a REAL 9/16 cell next
   to the landscape one (never cover-cropped to 16/9), and the closing clip steps in from
   the left gutter below, so the block reads as the same asymmetric composition as the
   mosaic above it rather than as a row of thumbnails. */
.cs-loops{ --loop-gap:20px; display:grid; gap:var(--loop-gap);
  grid-template-columns:repeat(12,minmax(0,1fr)); align-items:stretch;
  margin-inline:calc(50% - 50vw + clamp(16px,3vw,56px)); }
.cs-loops .cs-loop{ margin:0; min-width:0; }
.cs-loops .cs-media{ width:100%; height:100%; border:0; border-radius:0; box-shadow:none; background:#0a0a0a; }
.cs-loop-vid{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; z-index:1; }
/* the RATIO lives on the cell, so no clip is ever stretched or letterboxed: a landscape
   cell is 16/9 and a portrait cell is 9/16, and the row takes its height from them */
.cs-loops .cs-loop .cs-media{ aspect-ratio:16/9; height:auto; }
.cs-loops .cs-loop--portrait .cs-media{ aspect-ratio:9/16; }
/* EVERY ROW FILLS, same rule and the same gutters as the Details mosaic: two cells per
   row at six columns each, and an odd last cell takes the full twelve. */
.cs-loops .cs-loop{ grid-column:span 6; }
.cs-loops .cs-loop:nth-child(odd):last-child{ grid-column:span 12; }
@media (min-width:1081px){
  /* FIVE, COMPACT - two landscape across the top, then the third landscape beside the PAIR
     of verticals. The verticals are only two columns wide, and that is what caps their
     height: a 9/16 cell two columns wide comes out the same height as a 16/9 cell six
     columns wide, so both rows match and the whole section lands near ONE viewport instead
     of three. The clip beside them stretches to the row rather than setting it, so the
     verticals drive the height and nothing is letterboxed. Rows sum 6+6 and 8+2+2. */
  .cs-loops[data-n="5"] .cs-loop:nth-child(1){ grid-area:1 / 1 / 2 / 7; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(2){ grid-area:1 / 7 / 2 / 13; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(3){ grid-area:2 / 1 / 3 / 9; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(4){ grid-area:2 / 9 / 3 / 11; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(5){ grid-area:2 / 11 / 3 / 13; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(3) .cs-media{ aspect-ratio:auto; height:100%; }
  /* SIX - two landscape across the top, then the third landscape with ALL THREE verticals
     beside it. A six-column 16/9 cell and a two-column 9/16 cell come out the same height,
     so the two rows match and the section stays at one viewport. Rows sum 6+6 and 6+2+2+2. */
  .cs-loops[data-n="6"] .cs-loop:nth-child(1){ grid-area:1 / 1 / 2 / 7; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(2){ grid-area:1 / 7 / 2 / 13; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(3){ grid-area:2 / 1 / 3 / 7; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(4){ grid-area:2 / 7 / 3 / 9; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(5){ grid-area:2 / 9 / 3 / 11; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(6){ grid-area:2 / 11 / 3 / 13; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(3) .cs-media{ aspect-ratio:auto; height:100%; }
}
@media (max-width:1080px){
  /* narrow windows and phones: two columns is too small to read, so the verticals take
     half the bed each and the wide clip goes full width above them - rows sum 6+6, 12, 6+6 */
  .cs-loops[data-n="5"] .cs-loop:nth-child(1){ grid-area:1 / 1 / 2 / 7; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(2){ grid-area:1 / 7 / 2 / 13; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(3){ grid-area:2 / 1 / 3 / 13; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(4){ grid-area:3 / 1 / 4 / 7; }
  .cs-loops[data-n="5"] .cs-loop:nth-child(5){ grid-area:3 / 7 / 4 / 13; }
  /* six, narrow: the wide clip goes full width and the three verticals share the row
     below it - rows sum 6+6, 12, 4+4+4 */
  .cs-loops[data-n="6"] .cs-loop:nth-child(1){ grid-area:1 / 1 / 2 / 7; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(2){ grid-area:1 / 7 / 2 / 13; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(3){ grid-area:2 / 1 / 3 / 13; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(4){ grid-area:3 / 1 / 4 / 5; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(5){ grid-area:3 / 5 / 4 / 9; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(6){ grid-area:3 / 9 / 4 / 13; }
  .cs-loops[data-n="6"] .cs-loop:nth-child(3) .cs-media{ aspect-ratio:16/9; height:auto; }
}
@media (max-width:900px){ .cs-loops{ margin-inline:0; --loop-gap:14px; } }
@media (max-width:520px){ .cs-loops{ --loop-gap:10px; } }
@media (max-width:900px){ .cs-mosaic{ margin-inline:0; --mo-gap:14px; } }
/* phones: one full-width column in authored order, each still near its native ratio -
   no viewport-height bed, no heavy crops */
@media (max-width:760px){
  .cs-mosaic[data-n]{ grid-template-columns:minmax(0,1fr); grid-template-rows:none; grid-auto-rows:auto; height:auto; gap:14px; }
  .cs-mosaic[data-n] .cs-mo:nth-child(n){ grid-area:auto; }
  .cs-mosaic[data-n] .cs-mo:nth-child(n) .cs-media{ aspect-ratio:16/9; height:auto; }
  .cs-mosaic[data-n] .cs-mo:nth-child(n) .cs-img{ object-position:50% 50%; }
}


/* ============================================================
   SELF-HOSTED FILM PLAYER
   16/9, contained at the section width (no bleed class on the figure). Square corners,
   no shadow, no gradient fills: the only gradient is the short fall behind the control
   bar, which never covers the frame. Progress and volume fills use --accent; the time
   read-out uses the mono face the metarows use. Everything transitions on --t-fast /
   --t-media with --ix-ease.
   ============================================================ */
.cs-vp{ position:relative; width:100%; }
.cs-vp-frame{ position:relative; width:100%; aspect-ratio:16/9; overflow:hidden; background:#0a0a0a; }
.cs-vp-frame:focus-visible{ outline:2px solid #fff; outline-offset:-2px; }
.cs-vp-video,.cs-vp-poster{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }
.cs-vp-video{ z-index:1; background:#0a0a0a; }
/* the poster sits ABOVE the video and only leaves once a real frame is painted */
.cs-vp-poster{ z-index:2; transition:opacity var(--t-media) var(--ix-ease); }
.cs-vp.has-frame:not(.has-failed) .cs-vp-poster{ opacity:0; pointer-events:none; }

/* centre play control - square, quiet, no pill */
.cs-vp-big{ position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); z-index:4;
  width:64px; height:64px; padding:0; border:1px solid rgba(255,255,255,0.7); border-radius:0;
  display:flex; align-items:center; justify-content:center; color:#fff; background:rgba(10,10,10,0.42);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px); cursor:pointer;
  transition:background var(--t-fast) var(--ix-ease), border-color var(--t-fast) var(--ix-ease), opacity var(--t-fast) var(--ix-ease); }
.cs-vp-big svg{ width:24px; height:24px; }
.cs-vp-big:hover{ background:rgba(10,10,10,0.62); border-color:#fff; }
.cs-vp-big:focus-visible{ outline:2px solid #fff; outline-offset:3px; }
.cs-vp.is-started .cs-vp-big,.cs-vp.has-failed .cs-vp-big{ opacity:0; pointer-events:none; }
/* paused: the centre control returns, so a paused film reads as paused at a glance */
.cs-vp.is-paused:not(.has-failed) .cs-vp-big{ opacity:1; pointer-events:auto; }

/* buffering: a 2px accent line sweeping the top of the frame. No spinner, no library. */
.cs-vp-load{ position:absolute; left:0; right:0; top:0; height:2px; z-index:5; overflow:hidden; opacity:0;
  transition:opacity var(--t-fast) var(--ix-ease); pointer-events:none; }
.cs-vp-load span{ position:absolute; inset:0 auto 0 0; width:36%; background:var(--accent); animation:cs-vp-sweep 1.25s var(--ix-ease) infinite; }
.cs-vp.is-loading .cs-vp-load{ opacity:1; }
@keyframes cs-vp-sweep{ 0%{ transform:translateX(-100%); } 100%{ transform:translateX(380%); } }
@media (prefers-reduced-motion:reduce){ .cs-vp-load span{ animation:none; width:100%; opacity:0.5; } }

/* failure: the poster stays and one quiet line explains, with the YouTube copy behind it */
.cs-vp-fail{ position:absolute; left:0; right:0; bottom:0; z-index:6; margin:0; display:none;
  padding:14px clamp(12px,2vw,20px); font-family:var(--font-mono); font-weight:600; font-size:12px;
  letter-spacing:0.04em; color:rgba(255,255,255,0.86);
  background:linear-gradient(to top, rgba(0,0,0,0.62) 0%, rgba(0,0,0,0.28) 60%, rgba(0,0,0,0) 100%); }
.cs-vp-fail a{ color:#fff; text-decoration:underline; text-underline-offset:3px; }
.cs-vp-fail a:hover{ text-decoration-color:var(--accent); }
.cs-vp.has-failed .cs-vp-fail{ display:block; }
.cs-vp.has-failed .cs-vp-bar{ display:none; }

/* thin control bar, legible on a short gradient of its own - never a frame-wide scrim */
.cs-vp-bar{ position:absolute; left:0; right:0; bottom:0; z-index:7; display:flex; align-items:center; gap:clamp(8px,1vw,14px);
  padding:12px clamp(10px,1.4vw,18px) 10px;
  background:linear-gradient(to top, rgba(0,0,0,0.66) 0%, rgba(0,0,0,0.26) 62%, rgba(0,0,0,0) 100%);
  opacity:0; pointer-events:none; transition:opacity var(--t-media) var(--ix-ease); }
.cs-vp.is-started .cs-vp-bar{ opacity:1; pointer-events:auto; }
.cs-vp.is-started.bar-out .cs-vp-bar{ opacity:0; pointer-events:none; }
/* Controls carry a 44x44 minimum hit area via a centred pseudo-element, so the touch
   target grows without the icon or the bar's visual rhythm changing. */
.cs-vp-btn{ flex:0 0 auto; width:34px; height:34px; padding:0; border:0; border-radius:0; background:none; color:#fff;
  position:relative; display:inline-flex; align-items:center; justify-content:center; cursor:pointer;
  transition:color var(--t-fast) var(--ix-ease), opacity var(--t-fast) var(--ix-ease); opacity:0.88; }
.cs-vp-btn::after{ content:""; position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  width:44px; height:44px; }
.cs-vp-btn svg{ width:18px; height:18px; }
.cs-vp-btn:hover{ opacity:1; }
.cs-vp-btn:focus-visible{ outline:2px solid #fff; outline-offset:2px; }
.cs-vp-i-pause,.cs-vp-i-muted{ display:none; }
.cs-vp.is-playing .cs-vp-play .cs-vp-i-play{ display:none; }
.cs-vp.is-playing .cs-vp-play .cs-vp-i-pause{ display:block; }
.cs-vp.is-muted .cs-vp-mute .cs-vp-i-vol{ display:none; }
.cs-vp.is-muted .cs-vp-mute .cs-vp-i-muted{ display:block; }

/* square-track sliders: accent fill to the playhead, white rail after it */
.cs-vp-seek,.cs-vp-vol{ -webkit-appearance:none; appearance:none; height:14px; margin:0; background:none; cursor:pointer;
  border-radius:0; --vp-p:0%; }
.cs-vp-seek{ flex:1 1 auto; min-width:0; }
/* the volume slider now closes the bar (no fullscreen button after it), so it carries
   the trailing gutter itself and the row ends flush instead of leaving a hole */
.cs-vp-vol{ flex:0 0 clamp(56px,7vw,88px); width:clamp(56px,7vw,88px); margin-right:4px; }
.cs-vp-bar > *:last-child{ margin-right:0; }
.cs-vp-seek::-webkit-slider-runnable-track,.cs-vp-vol::-webkit-slider-runnable-track{ height:3px; border-radius:0;
  background:linear-gradient(to right, var(--accent) 0 var(--vp-p), rgba(255,255,255,0.2) var(--vp-p) 100%); }
.cs-vp-seek::-moz-range-track,.cs-vp-vol::-moz-range-track{ height:3px; border-radius:0;
  background:linear-gradient(to right, var(--accent) 0 var(--vp-p), rgba(255,255,255,0.2) var(--vp-p) 100%); }
.cs-vp-seek::-webkit-slider-thumb,.cs-vp-vol::-webkit-slider-thumb{ -webkit-appearance:none; appearance:none;
  width:3px; height:14px; border:0; border-radius:0; background:#fff; margin-top:-5px; }
.cs-vp-seek::-moz-range-thumb,.cs-vp-vol::-moz-range-thumb{ width:3px; height:14px; border:0; border-radius:0; background:#fff; }
.cs-vp-seek:focus-visible,.cs-vp-vol:focus-visible{ outline:2px solid #fff; outline-offset:3px; }
/* the sliders are replaced elements, so a pseudo-element cannot enlarge them: the input
   box itself grows instead while the painted track stays 3px and the thumb stays 14px */
.cs-vp-seek,.cs-vp-vol{ height:24px; }
@media (pointer:coarse){
  .cs-vp-seek,.cs-vp-vol{ height:44px; }
}
.cs-vp-time{ flex:0 0 auto; display:flex; align-items:center; gap:6px; font-family:var(--font-mono); font-weight:600;
  font-size:12px; letter-spacing:0.04em; color:rgba(255,255,255,0.9); font-variant-numeric:tabular-nums; }
.cs-vp-sep{ color:rgba(255,255,255,0.45); }

@media (max-width:760px){
  /* 12px gap so neighbouring 44px hit areas sit side by side without overlapping */
  .cs-vp-bar{ gap:12px; padding:10px 10px 8px; }
  .cs-vp-vol{ display:none; }            /* mute button stays; the slider is not needed on a phone */
  .cs-vp-time{ font-size:11px; gap:4px; }
  /* visible icon boxes stay compact; the ::after keeps the real target at 44x44 */
  .cs-vp-btn{ width:34px; height:34px; }
  .cs-vp-big{ width:54px; height:54px; }
}
@media (max-width:400px){ .cs-vp-time .cs-vp-sep,.cs-vp-time .cs-vp-dur{ display:none; } }


/* noHeader projects (their own title block) keep their copy where it is, but the
   redundant "The brief." heading goes there too, and their body copy matches the
   header column's size so the projects read as one system */
.cs-sec--brief .cs-h2{ display:none; }
.cs-sec--brief .cs-prose p{ font-size:clamp(15px,1.15vw,17px); line-height:1.62; }

/* ---------- TITLE BLOCK - two columns ----------
   Left 55, right 40, the remaining 5 as the gutter (fr units, so nothing overflows).
   The meta row sits on its own grid row above the title, and the copy column starts on
   the TITLE's row - so the text's first line is top-aligned with the title's cap line,
   not with the meta row and not centred against it. The title steps down slightly from
   the full-width size (clamp 40-92px) so it does not wrap awkwardly in a 55% column. */
/* the three left-column rows are declared explicitly (meta, title, facts) so the copy
   column can span the title and facts rows without the row line resolving into the
   implicit grid and dragging the title down */
.cs-hero-grid{ display:grid; grid-template-columns:minmax(0,55fr) minmax(0,40fr);
  /* row 3 is the flexible track: a spanning item distributes its excess height across
     every AUTO row it spans, which inflated the title row. With 1fr the excess lands in
     row 3 only, so row 2 stays at the title's content height. */
  /* two rows now: the meta row above the title is gone, so the title starts the block */
  grid-template-rows:auto 1fr;
  column-gap:5%; row-gap:clamp(14px,1.4vw,18px); align-items:start; }
/* the title's own top margin would sit INSIDE row 2 and push it below the copy column:
   the meta-to-title gap is the grid's row-gap instead, so both columns share a top line */
.cs-hero-grid > .cs-title{ margin-top:0; }

/* the title is bottom-aligned in the left column: it spans the title and facts rows and
   pins itself to the END, so it lands directly above the facts line and the leftover
   height falls ABOVE it as breathing room rather than below it as a hole. Row 3 is the
   1fr track, so a copy column taller than the title feeds its excess there; when the copy
   is SHORTER, the rows collapse to the title's own height and nothing overlaps. */
.cs-hero-grid > .cs-title{ grid-column:1; grid-row:1 / span 2; align-self:end; font-size:clamp(40px,5.6vw,92px); }
/* the copy column SPANS the title and facts rows, so the left column's own rows stack
   tightly (title, then facts ~24-32px under it) instead of row 2 stretching to the
   copy's full height and pushing the facts to the bottom of the block */
.cs-hero-grid > .cs-hero-r{ grid-column:2; grid-row:1 / span 2; min-width:0; }
/* ONE body size in this column, capped at a comfortable measure rather than the column */
.cs-hero-copy{ max-width:46ch; }
.cs-hero-copy p{ font-family:var(--font-body); font-weight:350; font-size:clamp(15px,1.15vw,17px); line-height:1.62; color:var(--ink); }
.cs-hero-copy p + p{ margin-top:0.9em; }
.cs-hero-r .cs-lede{ font-size:clamp(16px,1.2vw,18px); margin-top:1em; max-width:46ch; }
@media (max-width:900px){
  /* one column: meta row, title, text - tight rhythm, no offset grid, no bottom-align */
  .cs-hero-grid{ grid-template-columns:minmax(0,1fr); column-gap:0; }
  .cs-hero-grid > .cs-title{ grid-row:auto; align-self:auto; }
  .cs-hero-grid > .cs-title,.cs-hero-grid > .cs-hero-r{ grid-column:1; grid-row:auto; }
  .cs-hero-grid > .cs-title{ font-size:clamp(38px,8vw,66px); }
  .cs-hero-grid > .cs-hero-r{ margin-top:clamp(18px,3vw,26px); }
  .cs-hero-copy{ max-width:none; }
}


/* ---------- production facts under the title ----------
   The meta row's voice exactly: small, muted, letter-spaced, uppercase labels. Two
   aligned sub-columns, no boxes, borders or icons - it sits under the title rather than
   competing with it. In the grid it takes its own row in the LEFT column; below 900px it
   stacks under the title and before the body text. */
/* NB: cs-hero-facts, NOT cs-facts - that name belongs to the existing 3-up facts block
   elsewhere in the case study. Values only, one full-width line UNDER the two-column
   block, left-aligned with the title. Identical to .cs-metarow at the top (display face,
   600, 12px, 0.16em, uppercase, --muted, the same clamped gap), so the block reads as
   bookended by two matching lines. It wraps below 900px and never re-stacks. */
.cs-hero-facts{ margin:clamp(16px,1.8vw,22px) 0 0; padding-top:clamp(12px,1.4vw,16px);
  border-top:1px solid var(--line);
  display:flex; flex-wrap:wrap; align-items:baseline;
  gap:10px clamp(28px,3.6vw,52px);            /* wide gap BETWEEN pairs */
  font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.16em;
  text-transform:uppercase; }
/* a pair is one flex item, so a label can never wrap away from its value */
.cs-hero-facts .cs-hf{ display:inline-flex; align-items:baseline; gap:10px; white-space:nowrap; }
.cs-hero-facts .cs-hf i{ font-style:normal; color:var(--muted); }
/* the tail stays muted so the ink discipline group reads as the line's opening unit:
   labels a step fainter than their values keeps each pair legible as a pair */
.cs-hero-facts .cs-hf i{ color:var(--faint); }
.cs-hero-facts .cs-hf b{ font-weight:600; color:var(--muted); }
/* inside the grid the row-gap already carries part of the spacing, so the standalone
   margin steps down - the facts land ~24-32px under the title */
@media (max-width:900px){
}


/* the credits table is gone - the disclaimer is all that remains of it, as one quiet
   closing line with no rule and no heading, sitting between the last real section and the
   next-project link so the page does not end on an abrupt edge */
.cs-disc--foot{ margin:clamp(30px,3.6vw,48px) 0 clamp(20px,2.4vw,32px);
  padding:0 clamp(16px,3.4vw,48px); max-width:1720px; margin-inline:auto;
  font-family:var(--font-mono); font-size:11px; letter-spacing:0.04em; color:var(--faint); }


/* the noHeader projects (their own title block or split hero) print the same facts row,
   hairline INCLUDED - the rule above the facts line belongs to every project. Only the
   rule that used to sit under the title was removed. Same 1px --line, same padding-top,
   same width as the content it closes; the top margin is a touch wider than the header's
   because this row closes a body block rather than a header grid. */
.cs-titleblock .cs-hero-facts,.cs-split-body .cs-hero-facts{ border-top:1px solid var(--line);
  padding-top:clamp(12px,1.4vw,16px); margin-top:clamp(16px,1.8vw,22px); }


/* the discipline group that used to be the meta row: first in the facts line, unlabelled,
   primary ink against the muted pairs, and set off by a wider gap than the pairs use */
.cs-hero-facts .cs-hf-disc{ display:inline-flex; align-items:baseline; gap:clamp(14px,1.6vw,22px);
  white-space:nowrap; color:var(--ink); margin-right:clamp(10px,1.6vw,26px); }
/* Narrowest phones: a nowrap pair ("BLENDER, SUBSTANCE PAINTER") is wider than the
   column itself at 320px, and its min-content was widening the whole title-block grid
   track - which pushed every child 8px past the viewport. The pairs stay nowrap
   everywhere they fit; below 400px they are allowed to break. */
@media (max-width:400px){
  .cs-hero-facts{ min-width:0; }
  .cs-hero-facts .cs-hf,.cs-hero-facts .cs-hf-disc{ white-space:normal; }
}
.cs-titleblock > *{ min-width:0; }


/* ==== source: casestudy-white.css ==== */
/* ============================================================
   AVREA - CASE STUDY OVERLAY, WHITE THEME
   Loads AFTER casestudy.css. Neutralises the old dark and
   colour-tinted project themes so the overlay matches the site:
   white surfaces, near-black ink, grey secondary, hairline rules,
   sharp corners. No layout changes.
   ============================================================ */

.cs-veil{ background:var(--c-bg); }

/* every project theme resolves to the same white palette */
.cs-root,
.cs-root.cs-dark,
.cs-root.cs-crimson,
.cs-root.cs-cobalt,
.cs-root.cs-graphite,
.cs-root.cs-wip-page{
  --bg:        var(--c-bg);
  --surface:   var(--panel-2);
  --surface-2: var(--panel-3);
  --card-dark: var(--media-dark);
  --ink:       var(--c-head);
  --muted:     var(--c-body);
  --faint:     var(--c-meta);
  --line:      var(--c-rule);
  --line-2:    color-mix(in srgb,var(--c-head) 24%,var(--c-bg));
  /* the overlay deliberately uses ink as its accent - it borrows none of the site's blue */
  --accent:    var(--c-head);
  --gold:      var(--c-body);
  --accent-on-dark:var(--c-meta);
  --accent-soft: color-mix(in srgb,var(--c-head) 6%,transparent);
  --radius:0px; --radius-sm:0px;
  --shadow:0 4px 24px -12px color-mix(in srgb,var(--scrim-ink) 14%,transparent);
  --shadow-lift:0 18px 48px -20px color-mix(in srgb,var(--scrim-ink) 22%,transparent);
  background:var(--c-bg);
  background-image:none;
}

/* top bar */
.cs-bar,
.cs-dark .cs-bar,.cs-crimson .cs-bar,.cs-cobalt .cs-bar,.cs-graphite .cs-bar,.cs-wip-page .cs-bar{
  background:color-mix(in srgb,var(--c-bg) 94%,transparent); border-bottom:1px solid var(--line);
  -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px); }
.cs-close{ background:color-mix(in srgb,var(--c-head) 4%,transparent); border:1px solid var(--line-2); color:var(--ink); }
.cs-close:hover{ background:color-mix(in srgb,var(--c-head) 8%,transparent); border-color:var(--c-head); }
.cs-close:focus-visible{ outline:2px solid var(--c-head); outline-offset:3px; }

/* media frames sit as light grey wells; renders are dark by design */
.cs-media,.cs-media-gif,
.cs-dark .cs-media,.cs-dark .cs-media-gif,
.cs-crimson .cs-media,.cs-crimson .cs-media-gif,
.cs-cobalt .cs-media,.cs-cobalt .cs-media-gif,
.cs-graphite .cs-media,.cs-graphite .cs-media-gif,
.cs-wip-page .cs-media,.cs-wip-page .cs-media-gif{ background:var(--panel-3); border:1px solid var(--line); }
.cs-motion-vid{ background:var(--panel-3); }
.cs-ph{ color:color-mix(in srgb,var(--c-head) 34%,transparent); }
.cs-ph span{ color:color-mix(in srgb,var(--c-head) 22%,transparent); }

/* callouts, chips, accents */
.cs-callout,
.cs-crimson .cs-callout,.cs-cobalt .cs-callout,.cs-graphite .cs-callout,.cs-wip-page .cs-callout{
  background:var(--surface); border:1px solid var(--line); }
.cs-callout-k,.cs-eyebrow,.cs-step-no,.cs-quote::before,.cs-quote::after,.cs-foot-ar{ color:var(--muted); }
.cs-flag,.cs-tag{ background:color-mix(in srgb,var(--scrim-ink) 72%,transparent); color:var(--media-ink); border-color:transparent; }
.cs-scene-no{ color:var(--media-ink); }

/* work-in-progress block */
.cs-wip{ background:var(--surface); border:1px solid var(--line); background-image:none; }
.cs-week-score{ background:var(--c-head); color:var(--c-bg); }
.cs-week-score.is-pending{ background:color-mix(in srgb,var(--c-head) 6%,transparent); color:var(--muted); }
.cs-tasklist li.is-done .cs-task-mark{ background:var(--c-head); color:var(--c-bg); }
.cs-task-mark{ border-color:var(--line-2); }

/* video poster / play button stay light-on-dark: they sit over the render */
.cs-play{ background:color-mix(in srgb,var(--media-ink) 90%,transparent); color:var(--media-dark); border:1px solid color-mix(in srgb,var(--media-ink) 90%,transparent); box-shadow:0 10px 40px color-mix(in srgb,var(--scrim-ink) 25%,transparent); }
.cs-video-poster:hover .cs-play{ background:var(--media-ink); }

/* before/after compare handle */
.cs-cmp-line{ background:var(--media-ink); box-shadow:0 0 0 1px color-mix(in srgb,var(--scrim-ink) 25%,transparent); }
.cs-cmp-label{ background:color-mix(in srgb,var(--media-ink) 92%,transparent); color:var(--media-dark); border:1px solid color-mix(in srgb,var(--scrim-ink) 12%,transparent); }

/* footer + next-project link */
.cs-foot-next:hover .cs-foot-name{ color:var(--muted); }

/* main project media stands on white - no grey well behind contained portraits */
.cs-hero-media .cs-media,.cs-fig.is-full .cs-media,.cs-split-media .cs-media,
.cs-media-gif,.cs-hero-media .cs-media-gif,
.cs-dark .cs-media-gif,.cs-crimson .cs-media-gif,.cs-cobalt .cs-media-gif,.cs-graphite .cs-media-gif,.cs-wip-page .cs-media-gif{
  background:transparent; border:0; box-shadow:none; }
.cs-motion-vid{ background:transparent; }

/* split hero (Purist, Ogon): vertical film sits on white - no grey wash behind it */
.cs-split-media,.cs-split-ph,
.cs-dark .cs-split-media,.cs-crimson .cs-split-media,.cs-cobalt .cs-split-media,.cs-graphite .cs-split-media,.cs-wip-page .cs-split-media{
  background:transparent; border:0; }


/* ==== source: white.css ==== */
/* ============================================================
   AVREA - THEME TOKENS
   The ONE place a colour value is written. Every rule downstream reads a
   token, or a color-mix() of one, so switching theme is switching tokens
   and nothing else. [data-theme] is set on <html> by the inline gate in
   each page's <head>, before any stylesheet, so the first painted frame
   is already correct - there is no flash of the wrong theme.
   ============================================================ */
:root{
  --t-fast:.2s; --t-line:.35s; --t-fill:.35s; --t-page:.35s; --t-media:.35s; --t-reveal:.35s; --t-rv:.6s; --rv-step:70ms; --t-slow:.35s; --t-xslow:1s; --ix-ease:cubic-bezier(.22,.61,.36,1); --ix-ease-out:cubic-bezier(.22,.61,.36,1);
  --t-arw-in:.35s;
  color-scheme:light;
  /* --- light theme --- */
  --c-bg:#FFFFFF;
  --c-head:#0E0E0E;
  --c-body:#4E4E4E;
  --c-meta:#9A9A97;
  --c-rule:#E6E6E6;
  --c-accent:#0111FF; --c-accent-hover:#0111FF; --c-accent-press:#0111FF;
  /* Ink ON an accent fill is always the CURRENT theme's background: white on
     #0111FF, #0E0E0E on #0A84FF. White on the dark accent fails contrast. */
  --c-on-accent:var(--c-bg);
  /* --- theme-independent --- these sit ON footage and renders, not on the
     page, so they must not flip: the ink would vanish into the media. */
  --media-ink:#FFFFFF;
  --media-dark:#0A0A0A;
  /* the wordmark sweep. Light theme lifts the dark glyphs with a near-white
     band on screen blend, which is a no-op on a white page. Dark theme mirrors
     it: a near-black band on multiply, which is a no-op on a near-black page
     and only touches the light glyphs. Routed as tokens so the effect resolves
     per theme instead of painting a white band across the dark bar. */
  --brand-shine-ink:var(--media-ink); --brand-shine-blend:screen;
  --scrim-ink:#060606;
  --hero-fallback:#191418;
  /* --- legacy aliases: the whole site is written against these --- */
  --bg:var(--c-bg); --panel:var(--c-bg);
  --panel-2:color-mix(in srgb,var(--c-head) 3%,var(--c-bg));
  --panel-3:color-mix(in srgb,var(--c-head) 7%,var(--c-bg));
  --line:var(--c-rule);
  --line-2:color-mix(in srgb,var(--c-head) 24%,var(--c-bg));
  --ink:var(--c-head); --muted:var(--c-body); --faint:var(--c-meta);
  --accent:var(--c-accent); --accent-ink:var(--c-on-accent);
  --radius:0px;
}
[data-theme="dark"]{
  color-scheme:dark;
  --c-bg:#0E0E0E;
  --c-head:#F2F2F0;
  --c-body:#B5B5B0;
  --c-meta:#6E6E6A;
  --c-rule:#262626;
  --c-accent:#0A84FF; --c-accent-hover:#3D9DFF; --c-accent-press:#0068D6;
  --brand-shine-ink:var(--media-dark); --brand-shine-blend:multiply;
}
/* ============================================================
   AVREA - THEME LAYER (was: WHITE THEME, Phase 1)
   Loads AFTER v27.css / mobile.css. Token overrides + targeted
   fixes for rules that assumed a dark page. Layout is untouched.
   The hero video card keeps its own dark treatment and media ink -
   its markup, scrim, grain and JS are untouched.
   ============================================================ */
html,body{ background:var(--c-bg); color:var(--ink); }


/* ---------- one font only (Host Grotesk) ---------- */
.ser,.sec-h .ser,.hheadline .ser,.band-title .ser{ font-style:normal; letter-spacing:inherit; }
.sec-h .ser,.hheadline .ser,.band-title .ser{ font-size:1em; }
.hheadline .ser,.ser-chrome{
  background:none !important; -webkit-background-clip:border-box !important; background-clip:border-box !important;
  -webkit-text-fill-color:currentColor !important; color:inherit !important; text-shadow:none !important; }

/* ---------- sharp corners everywhere ---------- */
*,*::before,*::after{ border-radius:0 !important; }
/* the few genuinely circular elements stay circular */
.sphere,.sphere::before,.sphere::after,.sphere-spec,.pill .dotk,.band-wip::before,.hero-play,.rev-av,.rev-av-mono{ border-radius:50% !important; }
.hero-play{ border-radius:999px !important; }

/* ---------- global chrome ---------- */
.pill{ color:var(--muted); border-color:var(--line); }
.pill .dotk{ background:var(--c-head); opacity:.55; }
.btn{ color:var(--ink); border-color:var(--line-2); }
.btn:hover{ border-color:var(--c-head); }
.btn--solid{ background:var(--c-head); color:var(--c-bg); border-color:var(--c-head); }
.btn--solid:hover{ background:color-mix(in srgb,var(--c-head) 86%,var(--c-bg)); border-color:color-mix(in srgb,var(--c-head) 86%,var(--c-bg)); }
a{ color:inherit; }
a:hover{ color:var(--c-accent-hover); }
a:active{ color:var(--c-accent-press); }

/* ---------- hero: keeps white ink over the video ---------- */
.hero-card{ color:var(--media-ink); border-color:color-mix(in srgb,var(--c-head) 10%,transparent); }
.hero .btn--solid,.hero-card .btn--solid{ background:var(--media-ink); color:var(--media-dark); border-color:var(--media-ink); }
.hero .btn--solid:hover,.hero-card .btn--solid:hover{ background:color-mix(in srgb,var(--media-ink) 93%,var(--media-dark)); border-color:color-mix(in srgb,var(--media-ink) 93%,var(--media-dark)); }
.hero-card .hbrand{ color:var(--media-ink); }
.hero-card .pill{ color:color-mix(in srgb,var(--media-ink) 80%,transparent); border-color:color-mix(in srgb,var(--media-ink) 22%,transparent); }
.hero-card .pill .dotk{ background:var(--media-ink); }
.hero-card .hburger{ background:color-mix(in srgb,var(--media-ink) 10%,transparent); border-color:color-mix(in srgb,var(--media-ink) 28%,transparent); }
.hero-card .hburger span{ background:var(--media-ink); }
.hero-card .hsoc-ic a{ border-color:color-mix(in srgb,var(--media-ink) 32%,transparent); color:color-mix(in srgb,var(--media-ink) 85%,transparent); }
.hwedo-list,.hintro-line{ color:color-mix(in srgb,var(--media-ink) 82%,transparent); }
.hwedo-k,.hsoc-k{ color:color-mix(in srgb,var(--media-ink) 55%,transparent); }

/* ---------- sticky nav ---------- */
.snav{ background:color-mix(in srgb,var(--c-bg) 92%,transparent); border-bottom:1px solid var(--line);
  -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px); }
@media (max-width:760px){ .snav{ background:color-mix(in srgb,var(--c-bg) 97%,transparent); } }
.snav .hbrand,.snav-brand{ color:var(--c-head); }
.snav-links a{ color:var(--muted); }
.snav-links a:hover{ color:var(--accent); background:color-mix(in srgb,var(--c-head) 5%,transparent); }
.snav .hburger{ background:color-mix(in srgb,var(--c-head) 4%,transparent); border-color:var(--line); }
.snav .hburger span{ background:var(--c-head); }

/* ---------- mobile drawer ---------- */
.mnav-scrim{ background:color-mix(in srgb,var(--scrim-ink) 35%,transparent); }
.mnav-panel{ background:var(--c-bg); color:var(--c-head); border-color:var(--line); }
.mnav-link{ color:var(--c-head); border-color:var(--line) !important; }
.mnav-close{ color:var(--c-head); border-color:var(--line); background:var(--c-bg); }

/* ---------- decorative dark textures: off on white ---------- */
.testi::before,.testi::after,.band:not(.full) .band-body::before{ display:none !important; }

/* ---------- sections on white ---------- */
.sec-h{ color:var(--c-head); }
.rev,.band{ background:var(--c-bg); border-color:var(--line); }
.rev:hover{ border-color:var(--line-2); }
.band-tag{ color:var(--muted); border-color:var(--line); }

/* ---------- media-overlaid text stays white ---------- */
.band-media{ background:var(--media-dark); }
.work .pgrid .band.tile .band-body,.band.full .band-body{ color:var(--media-ink); }
.work .pgrid .band.tile .band-title,.band.full .band-title{ color:var(--media-ink); }
.work .pgrid .band.tile .band-tag,.band.full .band-tag{ color:color-mix(in srgb,var(--media-ink) 78%,transparent); border-color:color-mix(in srgb,var(--media-ink) 28%,transparent); }
.band.full .band-cap{ color:color-mix(in srgb,var(--media-ink) 78%,transparent); }
.band.full .band-disc,.band.full .band-wip{ color:color-mix(in srgb,var(--media-ink) 55%,transparent); }
.band.full .band-wip::before{ background:color-mix(in srgb,var(--media-ink) 60%,transparent); }
.work .pgrid .band.tile[data-project]:hover{ border-color:color-mix(in srgb,var(--c-head) 34%,transparent) !important; }

/* empty vertical slots */
.work .pgrid .band.tile.slot{ background:var(--panel-2); border-style:dashed; border-color:color-mix(in srgb,var(--c-head) 16%,transparent); }
.slot-num{ color:color-mix(in srgb,var(--c-head) 16%,transparent); }

/* ---------- google review cards ---------- */
.rev:nth-child(2),.rev:nth-child(3){ background:var(--c-bg); }
.rev-q{ color:var(--c-head); }
.rev-foot{ border-top-color:var(--line); }
.rev-who b{ color:var(--c-head); }
.rev-av{ background:var(--panel-3); border-color:var(--line); }
.rev-av-mono{ background:var(--panel-3); color:var(--muted); }



/* ---------- footer ---------- */


/* ---------- modals: quote + calendly ---------- */
.q-back,.cal-back{ background:color-mix(in srgb,var(--c-bg) 78%,transparent); -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px); }
.q-modal,.cal-modal{ background:var(--c-bg); border-color:var(--line-2); box-shadow:0 30px 80px -30px color-mix(in srgb,var(--scrim-ink) 35%,transparent); }
.q-title,.q-done h3,.cal-title{ color:var(--c-head); }
.q-close,.cal-close{ background:var(--c-bg); border-color:var(--line-2); color:var(--c-head); }
.q-close:hover,.cal-close:hover{ background:var(--panel-3); border-color:var(--c-head); }
.q-input,.q-textarea{ background:var(--c-bg); border-color:var(--line-2); color:var(--c-head); }
.q-input::placeholder,.q-textarea::placeholder{ color:var(--faint); }
.q-input:focus,.q-textarea:focus{ border-color:var(--accent); background:var(--c-bg); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.q-call{ color:var(--c-head); background:color-mix(in srgb,var(--c-head) 3%,transparent); border-color:color-mix(in srgb,var(--c-head) 45%,transparent); }
.q-call:hover{ background:color-mix(in srgb,var(--c-head) 7%,transparent); border-color:var(--c-head); color:var(--c-head); }
.q-tick{ background:var(--panel-3); border-color:var(--line-2); color:var(--c-head); }
.q-resend{ background:var(--panel-3); border-color:var(--line-2); color:var(--c-head); }
.q-resend:hover{ background:color-mix(in srgb,var(--c-head) 9%,var(--c-bg)); border-color:var(--c-head); }


/* ============================================================
   PHASE 2 - page shell: static top nav, page heads, minimal footer,
   contact page, inline contact form.
   ============================================================ */
.pbar{ position:sticky; top:0; z-index:100; background:color-mix(in srgb,var(--c-bg) 94%,transparent);
  -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px); border-bottom:0; }
.pbar-in{ max-width:none; margin:0; padding:var(--frame); display:flex; align-items:center; justify-content:space-between; gap:24px; }
.pbar .hbrand{ color:var(--c-head); }
.pbar-links{ display:flex; align-items:center; gap:clamp(14px,2.4vw,34px); }
.pbar-links a{ font-size:16px; color:var(--muted); transition:color var(--t-fast) var(--ix-ease); }
.pbar-links a:hover{ color:var(--c-accent-hover); }
a:active{ color:var(--c-accent-press); }
.pbar-links a.is-here{ color:var(--c-head); }
.hnav a.is-here{ color:var(--media-ink); }
.snav-links a.is-here{ color:var(--c-head); }
.mnav-link.is-here{ color:var(--c-head); }

.phead{ padding:clamp(56px,8vw,120px) var(--gutter) clamp(28px,4vw,52px); max-width:var(--maxw); margin:0 auto; }
.phead h1{ font-family:var(--sans); font-weight:500; font-size:clamp(44px,8vw,104px); line-height:0.98;
  letter-spacing:-0.045em; margin:0; color:var(--c-head); }
.phead p{ margin:16px 0 0; color:var(--muted); font-size:clamp(16px,1.4vw,18px); font-weight:350; max-width:44ch; }

.sec-head.left{ display:flex; align-items:baseline; justify-content:space-between; gap:24px; flex-wrap:wrap; }


/* ---------- minimal footer ---------- */
.mfoot{ border-top:1px solid var(--line); margin-top:clamp(60px,8vw,120px); }
.mfoot-in{ padding:clamp(40px,5vw,72px) var(--gutter); display:flex; align-items:center; justify-content:space-between; gap:28px; flex-wrap:wrap; }
.mfoot .hbrand{ color:var(--c-head); font-size:24px; }
.mfoot-mail{ font-size:clamp(16px,1.7vw,22px); color:var(--c-head); border-bottom:1px solid var(--line-2); padding-bottom:4px; transition:border-color var(--t-fast); }
.mfoot-mail:hover{ border-color:var(--c-head); }
.mfoot-soc{ display:flex; align-items:center; gap:12px; }
/* ---------- shared social-icon interaction state ----------
   ONE rule for the Contact icons (.csoc) and the footer icons (.mfoot-soc) so the
   two can never drift. It owns rest colour, hover/focus colour, border colour and
   the transition; each place keeps its own geometry below (44px vs 38px).
   The SVGs are inline and paint from currentColor - Instagram strokes, Behance and
   YouTube fill - so one `color` change carries every glyph. */
.csoc a,.mfoot-soc a{ color:var(--c-head);
  transition:border-color var(--t-fast) var(--ix-ease), background var(--t-fast) var(--ix-ease), color var(--t-fast) var(--ix-ease); }
@media (hover:hover) and (pointer:fine){
  .csoc a:hover,.mfoot-soc a:hover{ border-color:var(--accent); color:var(--accent); }
}
.csoc a:focus-visible,.mfoot-soc a:focus-visible{ border-color:var(--accent); color:var(--accent); }
.mfoot-soc a{ width:38px; height:38px; border:1px solid var(--line); display:flex; align-items:center; justify-content:center; }
.mfoot-soc svg{ width:16px; height:16px; }
.mfoot-bar{ border-top:1px solid var(--line); max-width:var(--maxw); margin:0 auto; padding:16px var(--gutter);
  display:flex; align-items:center; justify-content:space-between; gap:16px; font-size:12px; color:var(--faint); }
.mfoot-bar a{ color:var(--faint); }
.mfoot-bar a:hover{ color:var(--c-accent-hover); }
a:active{ color:var(--c-accent-press); }
@media (max-width:620px){ .mfoot-in{ flex-direction:column; align-items:flex-start; gap:24px; } }

/* ---------- contact page ---------- */
.cpage{ padding:0 var(--gutter) clamp(32px,4vw,56px); max-width:var(--maxw); margin:0 auto; }
/* contact is a one-screen page: trim the display heading's frame */
body.cpage-body .phead{ padding-top:clamp(32px,4vw,56px); padding-bottom:clamp(16px,2vw,24px); }
body.cpage-body .phead h1{ font-size:clamp(40px,5.6vw,72px); }
/* Clearing a 900px fold. The FORM column is the taller of the two (613px vs the
   left column's 520px), so this all comes out of the form's own spacing and the
   page frame - trimming the FAQ would change nothing. No type sizes involved. */
body.cpage-body .cpage{ padding-bottom:clamp(16px,1.6vw,20px); }
body.cpage-body .phead{ padding-bottom:clamp(12px,1.6vw,16px); }
body.cpage-body .q-back.q-inline .q-form{ padding:clamp(20px,2vw,24px) !important; }
body.cpage-body .q-grid{ gap:12px; }
body.cpage-body .q-foot{ gap:12px; }
body.cpage-body .q-back.q-inline .q-note{ margin-top:8px; }
.cpage-grid{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,5vw,80px); align-items:start; }
.cmail{ display:inline-block; font-size:clamp(22px,3.2vw,42px); font-weight:400; letter-spacing:-0.035em; color:var(--c-head);
  border-bottom:1px solid var(--line-2); padding-bottom:4px; }
.cmail:hover{ border-color:var(--c-head); }
/* square icon buttons, same treatment as the footer set */
.csoc{ display:flex; align-items:center; gap:8px; margin-top:clamp(20px,2.4vw,28px); }
.csoc a{ width:44px; height:44px; border:1px solid var(--line); display:flex; align-items:center; justify-content:center; }
/* Contact keeps its own faint panel tint on hover - the shared rule above carries
   colour, border and timing for both places */
@media (hover:hover) and (pointer:fine){
  .csoc a:hover{ background:var(--panel-2); }
}
.csoc svg{ width:16px; height:16px; flex:none; }
@media (max-width:820px){ .cpage-grid{ grid-template-columns:1fr; } }

/* inline contact form (same quote.js flow, no modal chrome) */
.q-back.q-inline{ position:static !important; inset:auto !important; display:block !important; padding:0 !important;
  background:none !important; -webkit-backdrop-filter:none !important; backdrop-filter:none !important;
  opacity:1 !important; visibility:visible !important; pointer-events:auto !important; overflow:visible !important; }
.q-back.q-inline .q-modal{ position:static !important; transform:none !important; opacity:1 !important;
  width:100% !important; max-width:none !important; max-height:none !important; height:auto !important;
  margin:0 !important; box-shadow:none !important; border:1px solid var(--line) !important; overflow:visible !important; }
.q-back.q-inline .q-form{ padding:clamp(24px,2.6vw,32px) !important; }
.q-back.q-inline .q-note{ margin-top:16px; }


/* ============================================================
   PHASE 3 - compact review cards, tighter section rhythm
   ============================================================ */
.testi{ padding-block:clamp(56px,6vw,92px); }
.testi-center{ text-align:left; max-width:none; margin:0; }
.rev-grid{ margin-top:clamp(24px,3vw,40px); gap:clamp(12px,1.2vw,16px); align-items:stretch; }
.rev{ gap:clamp(16px,1.6vw,24px); padding:clamp(20px,1.9vw,24px); }
.rev-q{ font-size:clamp(16px,1.15vw,18px); line-height:1.6; font-weight:400; letter-spacing:-0.012em; }
.rev-foot{ padding-top:clamp(16px,1.3vw,16px); gap:16px; }
.rev-who b{ font-size:14px; font-weight:500; }
.rev-who span{ font-size:11px; letter-spacing:0.08em; }
.rev-av,.rev-av-mono{ width:36px; height:36px; }
.rev-av-mono{ font-size:16px; }
.sec-h{ font-weight:500; }
@media (max-width:900px){ .rev-grid{ max-width:560px; } }


/* ============================================================
   PHASE 4 - nude hero, single top bar, mixed-size Works grid
   ============================================================ */
.pbar-cta{ padding:12px 16px; font-size:14px; }

/* (former max-520 pbar block removed - every rule in it was dead: .pbar-links/.pbar-cta
   are display:none below 720px, and .pbar-in is re-declared with var(--frame) in Phase 7) */

/* hero: the source is 2:1 - avrea-hero.mp4 is 1920x960, avrea-hero-mobile.mp4 is
   1280x640, the poster is 2560x1280. The card is 2/1 and the video fills it at
   height:100%, so cover has nothing to trim: no crop at any width. If either the
   card ratio or the video height moves off these values, the frame starts cutting
   the top and bottom of the plate - which is exactly what 16/8.1 plus a 111.111%
   over-scan was doing. Keep the three in step: 2/1 here, * 2 in both width
   expressions, height:100% on .hero-video. */
/* even white margin on both sides; the stack sits in the right one */
.hero{ position:relative; padding:var(--frame-gap) var(--frame) calc(var(--frame-gap) + var(--frame) + 34px); min-height:0; }
.hero-card{ --hero-h:calc(100vh - 96px - 2 * var(--frame-gap) - var(--frame) - 34px); position:relative; width:min(calc(100vw - 2 * var(--frame)), calc(var(--hero-h) * 2)); margin:0 auto; height:auto; min-height:0;
  padding:0; display:block; background:none; border:0; aspect-ratio:2/1; overflow:hidden; }
.hero-video{ position:static; width:100%; height:100%; margin-top:0; display:block; object-fit:cover;
  /* first-render fallback: the flat tone costs no network so it is in the very first
     frame - which is what ::view-transition-new(root) captures. White here made the
     incoming Home page a white rectangle on white, so the crossfade was invisible. */
  background:var(--hero-fallback) url("media/avrea-hero-poster.webp?v=7") center/cover no-repeat; }
.hero-card{ background-color:var(--hero-fallback); }
.hero-vid-scrim,.hero-grain{ display:none; }

/* ---------- Works: tile chrome (shared by masonry) ---------- */
.work .wgrid .band.tile{ display:block; position:relative; min-height:0; height:auto; margin:0; border:0; border-radius:0; overflow:hidden; }
.work .wgrid .band.tile .band-media{ position:absolute; inset:0; order:0; aspect-ratio:auto; overflow:hidden; }
.work .wgrid .band.tile .band-media img,.work .wgrid .band.tile .band-media video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.work .wgrid .band.tile .band-media::after{ content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to top, color-mix(in srgb,var(--scrim-ink) 78%,transparent) 0%, color-mix(in srgb,var(--scrim-ink) 22%,transparent) 44%, transparent 68%); }
.work .wgrid .band.tile .band-body{ position:absolute; inset:auto 0 0 0; z-index:3;
  padding:clamp(14px,1.4vw,24px); gap:0; min-height:0; justify-content:flex-end; color:var(--media-ink); }
.work .wgrid .band.tile .band-body::before{ display:none; }
.work .wgrid .band.tile .band-title{ font-size:clamp(15px,1.4vw,24px); letter-spacing:-0.03em; color:var(--media-ink); }
.work .wgrid .band.tile.quiet .band-body{ opacity:0; transform:translateY(8px);
  transition:opacity var(--t-reveal) var(--ix-ease-out), transform var(--t-reveal) var(--ix-ease-out); }
.work .wgrid .band.tile.quiet:hover .band-body,.work .wgrid .band.tile.quiet:focus-visible .band-body{ opacity:1; transform:none; }
.work .wgrid .band.tile.quiet .band-media::after{ opacity:0; transition:opacity var(--t-reveal) var(--ix-ease-out); }
.work .wgrid .band.tile.quiet:hover .band-media::after{ opacity:1; }

@media (prefers-reduced-motion:reduce){
  .work .wgrid .band.tile.quiet .band-body,.work .wgrid .band.tile.quiet .band-media::after{ opacity:1; transition:none; }
}


/* ============================================================
   PHASE 5 - home row of four, asset-true tile ratios, reserved slots
   ============================================================ */
.pgrid.four{ grid-template-columns:repeat(4,1fr); align-items:stretch; }
.work .pgrid .band.tile.a32{ aspect-ratio:3/2; }
.work .pgrid .band.tile.a169{ aspect-ratio:16/9; }
.work .pgrid .band.tile.a11{ aspect-ratio:1/1; }
/* WebKit zero-height guard. Every child of a home tile is absolutely positioned
   (.band-media and .band-body are both inset), so the tile carries NO in-flow
   content and its height can come only from aspect-ratio. As a stretch-aligned
   item in a content-sized grid row that is circular - the row wants the item's
   content height, the item wants the row - and WebKit has resolved that to zero,
   the same failure the case study Details figures hit (see the 0x0 note in the
   .cs-photos block). An in-flow ratio spacer breaks the cycle: the tile now has
   real content height, so the row is never sized from nothing and aspect-ratio
   becomes confirmation rather than the only source of height. Percentage padding
   resolves against the tile's own content width, which the 1fr track makes
   definite, and the values mirror each ratio exactly - nothing moves in engines
   where aspect-ratio already worked. width:0 keeps the spacer out of inline flow.
   The width MUST be made definite before the height, which is what width:100%
   below does. aspect-ratio is bidirectional: given a definite height it will
   happily compute the WIDTH instead, and a stretch-aligned item in an auto row
   can have its height settled first - WebKit then ran the ratio backwards and
   shrink-to-fit the tile to ~173px in a 363px column. An explicit width:100%
   removes that freedom in both engines: width comes from the track, height from
   the ratio, one direction only. The spacer carries width:100% for the same
   reason - percentage padding needs a resolved containing-block width, and a
   zero-width spacer in a shrink-to-fit pass has none, so it contributed nothing.
   No min-height here: any definite height is exactly what the ratio inverts
   from. The spacer is the collapse guard, and it cannot invert. */
.work .pgrid .band.tile{ width:100%; }
.work .pgrid .band.tile::before{ content:""; display:block; width:100%; }
.work .pgrid .band.tile.a32::before{ padding-top:66.6667%; }
.work .pgrid .band.tile.a169::before{ padding-top:56.25%; }
.work .pgrid .band.tile.a11::before{ padding-top:100%; }
.work .pgrid .band.tile.v::before{ padding-top:125%; }
.work .pgrid .band.tile.sq::before{ padding-top:100%; }
@media (max-width:900px){ .pgrid.four{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:600px){ .pgrid.four{ grid-template-columns:1fr; } }



/* ============================================================
   PHASE 6 - wide hero, studio tagline, 2x2 reviews
   ============================================================ */
.htag{ max-width:var(--maxw); margin:0 auto; text-align:center;
  padding:clamp(30px,3.4vw,56px) var(--gutter) clamp(22px,2.4vw,40px); }
.htag p{ margin:0 auto; font-family:var(--sans); font-weight:400; font-style:normal; font-size:clamp(19px,1.9vw,27px);
  line-height:1.4; letter-spacing:-0.02em; color:var(--c-head); max-width:60ch; text-wrap:pretty; }

/* four reviews read better two-up than four-up at this card width */
.rev-grid{ grid-template-columns:repeat(2,1fr); }
@media (max-width:900px){ .rev-grid{ grid-template-columns:1fr; max-width:560px; } }


/* ============================================================
   PHASE 7 - corner-anchored top bar
   ============================================================ */
:root{ --frame:clamp(20px,2.2vw,34px); --frame-gap:clamp(14px,2vh,30px); }
.pbar-l{ display:flex; flex-direction:row; align-items:center; gap:12px; }
.pbar-r{ display:flex; align-items:center; gap:clamp(14px,2.4vw,34px); }
/* socials: stacked in the white corner beside the hero, scroll with the page */
.pbar-soc{ position:absolute; right:var(--frame); bottom:var(--frame); z-index:8;
  display:flex; flex-direction:row; align-items:center; gap:20px; }

.pbar-soc a{ display:grid; place-items:center; width:24px; height:24px; color:color-mix(in srgb,var(--c-head) 96%,var(--c-bg)); transition:color var(--t-fast) var(--ix-ease),opacity var(--t-fast) var(--ix-ease); }
.pbar-soc a:hover{ color:var(--c-head); }
.pbar-soc svg{ width:22px; height:22px; }
.hero-anchor{ position:absolute; left:var(--frame); bottom:var(--frame); z-index:8;
  font-size:18px; font-weight:600; color:var(--c-head); letter-spacing:0.02em; line-height:1; white-space:nowrap;
  writing-mode:vertical-rl; transform:rotate(180deg); }
@media (max-width:720px){ .hero-anchor{ display:none; } }
.pbar .hbrand{ line-height:1; }
@media (min-width:521px){
  .pbar .hbrand{ font-size:30px; }
  /* optically matched to the wordmark: the mark is sized to cap-height (~0.72em),
     not to the full em box - 1.05em made it read ~45% taller than the letters */
  .pbar .hbrand svg,.pbar .hbrand img{ width:auto; height:0.78em; }
  .pbar-links a{ font-size:22px; }
  .pbar-r .btn{ font-size:22px; padding:16px 24px; }
  .pbar-r .btn svg{ width:1.05em; height:1.05em; }
}
@media (max-width:520px){
  .pbar-in{ padding:16px var(--frame); gap:12px; }
  .pbar-l{ gap:12px; }
  .pbar-r{ gap:12px; }
}



/* ============================================================
   MOBILE BURGER - one corner button holding every nav option
   ============================================================ */
.pbar-burger{ display:none; position:relative; z-index:60; width:44px; height:44px; padding:0; border:0; background:none; align-items:center; justify-content:center; flex-direction:column; gap:6px; }
.pbar-burger span{ display:block; width:24px; height:2px; background:var(--c-head); transition:transform var(--t-fast) var(--ix-ease), opacity var(--t-fast) var(--ix-ease); }
.pbar-burger.is-open span:first-child{ transform:translateY(4px) rotate(45deg); }
.pbar-burger.is-open span:last-child{ transform:translateY(-4px) rotate(-45deg); }
.pbar-menu{ position:fixed; inset:0; z-index:55; background:var(--c-bg); display:flex; flex-direction:column; justify-content:center; gap:28px; padding:0 var(--frame); opacity:0; transition:opacity var(--t-fast) var(--ix-ease); }
/* [hidden] must actually hide it: the display:flex above out-specifies the UA
   [hidden]{display:none} rule, so the closed panel stayed a full-viewport fixed
   layer at opacity 0 with pointer-events on - it swallowed every tap on mobile. */
.pbar-menu[hidden]{ display:none; }
.pbar-menu.is-open{ opacity:1; }
.pmenu-links{ display:flex; flex-direction:column; gap:16px; }
.pmenu-links a{ font-size:34px; letter-spacing:-0.02em; color:var(--c-head); text-decoration:none; min-height:44px; display:flex; align-items:center; }
.pmenu-links a.is-here{ opacity:0.5; }
.pmenu-cta{ align-self:flex-start; font-size:16px; padding:16px 24px; }
.pmenu-soc{ display:flex; flex-direction:column; gap:0; margin-top:8px; }
.pmenu-soc a{ font-size:16px; color:var(--muted); text-decoration:none; min-height:44px; display:flex; align-items:center; }
/* the burger's scroll lock moved to body.scroll-locked (avrea/scrolllock.js): one
   mechanism, one release path. menu-open remains for styling hooks only. */
@media (max-width:720px){
  .pbar-burger{ display:flex; }
  .pbar-links, .pbar-cta{ display:none; }
}
@media (min-width:721px){ .pbar-menu{ display:none; } }

/* ============================================================
   DARK THEME - FROSTED TRANSLUCENT SURFACES
   Dark only. On the white page a blur behind a 94%-opaque surface is
   invisible and still costs a compositing pass, so light theme is left exactly
   as it was - not one declaration here applies to it.
   One knob for all three: --glass-blur. Raise or drop it in the dark block and
   the bar, the dialog backdrop and the burger panel move together.
   14px is deliberately the bar's existing radius: content behind reads as
   softened shape and colour, not as a flat wash.
   Only the burger panel is a real addition. The bar and the dialog backdrop
   were already translucent AND already blurred - they are re-declared here
   only to move them onto the shared property.
   The panel was opaque #0E0E0E, so a blur on it would have done nothing;
   it needs the 82% background to have anything to filter. 82% keeps the
   34px menu links at 9.5:1 even over pure white content, so the text is safe
   whatever the panel happens to be sitting on.
   No animation on the blur itself - the panel cross-fades opacity as before,
   the radius is static - so prefers-reduced-motion does not apply here and
   nothing is gated on it.
   ============================================================ */
[data-theme="dark"]{ --glass-blur:14px; }
[data-theme="dark"] .pbar-menu{ background:color-mix(in srgb,var(--c-bg) 82%,transparent);
  -webkit-backdrop-filter:blur(var(--glass-blur)); backdrop-filter:blur(var(--glass-blur)); }
[data-theme="dark"] .pbar{ -webkit-backdrop-filter:blur(var(--glass-blur)); backdrop-filter:blur(var(--glass-blur)); }
[data-theme="dark"] .q-back,[data-theme="dark"] .cal-back{
  -webkit-backdrop-filter:blur(var(--glass-blur)); backdrop-filter:blur(var(--glass-blur)); }
/* No support, no translucency: a see-through panel with 34px links over the
   page would be unreadable, so it falls back to the opaque background it had. */
/* LIGHT theme, burger panel only. Harder case than dark and tuned by the
   opposite constraint: the panel ink is dark, so every point of transparency
   lets a dark video frame through and ATTACKS contrast, where in dark the
   bleed-through only ever helped.
   78% is the floor set by hand, not by the contrast maths: measured against a
   black frame the panel lands at #C9 and all three floors still clear 4.5:1 -
   nav links (#0E0E0E) 11.67:1, social links (#4E4E4E via --muted) 5.03:1, the
   Let's talk label on its own opaque #0E0E0E fill 19.3:1, which never moves
   because that fill is not translucent. The socials are the binding element
   and still have headroom, but below 78% the panel stops reading as a surface
   and the menu starts to look like text floating on video, so it stops here.
   One property name, one light value: --glass-blur is 34px here against dark's
   14px, because a wide radius is what makes the bleed-through read as
   softened rather than as noise. Radius and opacity are separate levers and
   this value was set in its own pass - do not fold them together.
   Honest note: at 22% bleed-through the blur reads over dark or high-contrast
   content behind the panel and stays close to invisible over the site's white
   sections, because white blurred against white has nothing to show.
   Dark is untouched: its declarations are more specific and come first, and
   nothing here overlaps the bar or the dialog backdrops in either theme. */
html:not([data-theme="dark"]){ --glass-blur:34px; }
html:not([data-theme="dark"]) .pbar-menu{ background:color-mix(in srgb,var(--c-bg) 78%,transparent);
  -webkit-backdrop-filter:blur(var(--glass-blur)); backdrop-filter:blur(var(--glass-blur)); }
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  [data-theme="dark"] .pbar-menu{ background:var(--c-bg); }
  /* light falls back to fully opaque white - dark 34px links and #4E4E4E
     socials over an unblurred page would be the worst outcome of the three. */
  html:not([data-theme="dark"]) .pbar-menu{ background:var(--c-bg); }
}

/* ---------- All works button - fill sweep + arrow shift ---------- */
.work-cta{ display:flex; justify-content:center; margin-top:clamp(40px,4.4vw,68px); }
.btn-sweep{ position:relative; overflow:hidden; display:inline-flex; align-items:center; gap:12px; height:56px; padding:0 40px; border:1px solid var(--c-head); border-radius:0; background:var(--c-bg); color:var(--c-head); font-family:var(--sans); font-size:16px; font-weight:500; letter-spacing:0.01em; text-decoration:none; transition:color var(--t-fill) var(--ix-ease); }
.btn-sweep::before{ content:""; position:absolute; inset:0; background:var(--accent); transform:scaleX(0); transform-origin:left center; transition:transform var(--t-fill) var(--ix-ease); z-index:1; }
.btn-sweep:hover::before,.btn-sweep:focus-visible::before{ transform:scaleX(1); }
.btn-sweep:hover,.btn-sweep:focus-visible{ color:var(--accent-ink); }
.btn-sweep:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.btn-sweep > *,.btn-sweep .btn-arw{ position:relative; z-index:2; }
.btn-sweep .btn-arw{ transition:transform var(--t-line) var(--ix-ease); }
.btn-sweep:hover .btn-arw,.btn-sweep:focus-visible .btn-arw{ transform:translateX(6px); }
@media (prefers-reduced-motion:reduce){
  .btn-sweep,.btn-sweep::before,.btn-sweep .btn-arw{ transition:none; }
  .btn-sweep:hover .btn-arw,.btn-sweep:focus-visible .btn-arw{ transform:none; }
}

/* ============================================================
   UNIFIED INTERACTION SYSTEM - one curve, three responses.
   Nothing animates on idle; every transition fires on hover/focus only.
   ============================================================ */

/* ---------- 1. buttons: fill sweep ---------- */
.btn-sweep,.pbar-cta.btn--solid,.pmenu-cta.btn--solid,.q-submit.btn--solid,.q-resend,.q-call{
  position:relative; overflow:hidden; border-radius:0; isolation:isolate; transition:color var(--t-fill) var(--ix-ease); }
.btn-sweep::before,.pbar-cta.btn--solid::before,.pmenu-cta.btn--solid::before,.q-submit.btn--solid::before,.q-resend::before,.q-call::before{
  content:""; position:absolute; inset:0; z-index:-1; transform:scaleX(0); transform-origin:left center;
  transition:transform var(--t-fill) var(--ix-ease); }
.btn-sweep > *,.pbar-cta.btn--solid > *,.pmenu-cta.btn--solid > *,.q-submit.btn--solid > *,.q-resend > *,.q-call > *{ position:relative; z-index:2; }
.btn-sweep:hover::before,.btn-sweep:focus-visible::before,
.pbar-cta.btn--solid:hover::before,.pbar-cta.btn--solid:focus-visible::before,
.pmenu-cta.btn--solid:hover::before,.pmenu-cta.btn--solid:focus-visible::before,
.q-submit.btn--solid:hover::before,.q-submit.btn--solid:focus-visible::before,
.q-resend:hover::before,.q-resend:focus-visible::before,
.q-call:hover::before,.q-call:focus-visible::before{ transform:scaleX(1); }
/* white buttons -> accent sweep. The label goes to --accent-ink (white on the fill). */
.btn-sweep::before,.q-resend::before,.q-call::before{ background:var(--accent); }
.btn-sweep:hover,.btn-sweep:focus-visible,.q-resend:hover,.q-resend:focus-visible,.q-call:hover,.q-call:focus-visible{ color:var(--accent-ink); }
/* black buttons -> inverse: the sweep itself is the accent, so the fill is what
   the user sees. It must be on ::before - a background on the element sits
   BEHIND that pseudo-element and would never show. */
.pbar-cta.btn--solid,.pmenu-cta.btn--solid,.q-submit.btn--solid{ border:1px solid var(--c-head); }
.pbar-cta.btn--solid::before,.pmenu-cta.btn--solid::before,.q-submit.btn--solid::before{ background:var(--accent); }
.pbar-cta.btn--solid:hover,.pbar-cta.btn--solid:focus-visible,
.pmenu-cta.btn--solid:hover,.pmenu-cta.btn--solid:focus-visible,
.q-submit.btn--solid:hover,.q-submit.btn--solid:focus-visible{ color:var(--accent-ink); border-color:var(--accent); opacity:1; transform:none; box-shadow:none; }

/* ---------- 2. text links: underline grows from the left ---------- */
.pbar-links a,.pmenu-links a,.pmenu-soc a,.mfoot-mail,.mfoot-bar a,.q-note a,.cmail,.lp-section a{
  position:relative; text-decoration:none; }
.pbar-links a::after,.pmenu-links a::after,.pmenu-soc a::after,.mfoot-mail::after,.mfoot-bar a::after,.q-note a::after,.cmail::after,.lp-section a::after{
  content:""; position:absolute; left:0; right:0; bottom:-2px; height:1px; background:currentColor;
  transform:scaleX(0); transform-origin:left center;
  /* currentColor + the same duration as the label = one simultaneous change */
  transition:transform var(--t-line) var(--ix-ease), background-color var(--t-fast) var(--ix-ease); }
.pbar-links a:hover::after,.pbar-links a:focus-visible::after,
.pmenu-links a:hover::after,.pmenu-links a:focus-visible::after,
.pmenu-soc a:hover::after,.pmenu-soc a:focus-visible::after,
.mfoot-mail:hover::after,.mfoot-mail:focus-visible::after,
.mfoot-bar a:hover::after,.mfoot-bar a:focus-visible::after,
.q-note a:hover::after,.q-note a:focus-visible::after,
.cmail:hover::after,.cmail:focus-visible::after,
.lp-section a:hover::after,.lp-section a:focus-visible::after{ transform:scaleX(1); }
/* hover: the text takes the accent, the growing underline stays black */
.pbar-links a:hover,.pbar-links a:focus-visible,
.pbar-links a.is-here:hover,.pbar-links a[aria-current]:hover,
.pmenu-links a:hover,.pmenu-links a:focus-visible,
.pmenu-soc a:hover,.pmenu-soc a:focus-visible,
.mfoot-mail:hover,.mfoot-mail:focus-visible,
.mfoot-bar a:hover,.mfoot-bar a:focus-visible,
.q-note a:hover,.q-note a:focus-visible,
.cmail:hover,.cmail:focus-visible,
.lp-section a:hover,.lp-section a:focus-visible{ color:var(--accent); }
/* these three carry a permanent border-bottom - drop it so the growing line is the ONLY underline */
.mfoot-mail,.cmail,.lp-section a{ border-bottom:0; }
.mfoot-mail::after,.cmail::after,.lp-section a::after{ bottom:0; }
.pbar-links a.is-here::after,.pbar-links a[aria-current]::after{ transform:scaleX(1); }

/* ---------- 3. project tiles: subtle media scale inside the frame ---------- */
.work .pgrid .band.tile .band-media img,.work .pgrid .band.tile .band-media video,
.work .wgrid .band.tile .band-media img,.work .wgrid .band.tile .band-media video{
  transform:scale(1); transition:transform var(--t-reveal) var(--ix-ease-out); }
/* the shorthand above would reset transition-property to transform alone and
   clobber the crossfade v27.css puts on the video, so re-declare both here */
.work .pgrid .band.tile .band-media video,.work .wgrid .band.tile .band-media video{
  transition:transform var(--t-reveal) var(--ix-ease-out), opacity var(--t-reveal) var(--ix-ease-out); }
.work .pgrid .band.tile:hover .band-media img,.work .pgrid .band.tile:hover .band-media video,
.work .pgrid .band.tile:focus-visible .band-media img,.work .pgrid .band.tile:focus-visible .band-media video,
.work .wgrid .band.tile:hover .band-media img,.work .wgrid .band.tile:hover .band-media video,
.work .wgrid .band.tile:focus-visible .band-media img,.work .wgrid .band.tile:focus-visible .band-media video{ transform:scale(1.03); }

/* ---------- reduced motion: instant state changes only ---------- */
@media (prefers-reduced-motion:reduce){
  .btn-sweep,.pbar-cta.btn--solid,.pmenu-cta.btn--solid,.q-submit.btn--solid,.q-resend,.q-call,
  .btn-sweep::before,.pbar-cta.btn--solid::before,.pmenu-cta.btn--solid::before,.q-submit.btn--solid::before,.q-resend::before,.q-call::before,
  .pbar-links a::after,.pmenu-links a::after,.pmenu-soc a::after,.mfoot-mail::after,.mfoot-bar a::after,.q-note a::after,.cmail::after,.lp-section a::after,
  .work .pgrid .band.tile .band-media img,.work .pgrid .band.tile .band-media video,
  .work .wgrid .band.tile .band-media img,.work .wgrid .band.tile .band-media video{ transition:none; }
  .work .pgrid .band.tile:hover .band-media img,.work .pgrid .band.tile:hover .band-media video,
  .work .wgrid .band.tile:hover .band-media img,.work .wgrid .band.tile:hover .band-media video{ transform:none; }
  .btn-sweep:hover .btn-arw,.btn-sweep:focus-visible .btn-arw{ transform:none; }
}

/* ---------- cross-page transitions (MPA View Transitions) ---------- */
@view-transition{ navigation:auto; }
/* persistent chrome is lifted out of the root snapshot so it never repaints during the transition */
.pbar{ view-transition-name:site-chrome; }
::view-transition-group(site-chrome),::view-transition-old(site-chrome),::view-transition-new(site-chrome){ animation:none; mix-blend-mode:normal; }
::view-transition-old(site-chrome){ display:none; }
/* only the page content crossfades: opacity-dominant, tiny slide, GPU-promoted */
/* Opacity ONLY, and identical in both directions. The page change is a crossfade;
   the movement the visitor sees is the per-element reveal rising from below, which
   is the site's one motion language. A translate here competed with it - and a
   mirrored direction made going back read differently from going forward. */
::view-transition-old(root),::view-transition-new(root){ will-change:opacity; backface-visibility:hidden; mix-blend-mode:normal; }
::view-transition-old(root){ animation:vt-out var(--t-page) var(--ix-ease-out) both; }
::view-transition-new(root){ animation:vt-in var(--t-page) var(--ix-ease-out) both; }
@keyframes vt-out{ to{ opacity:0; } }
@keyframes vt-in{ from{ opacity:0; } }
@media (prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-old(root),::view-transition-new(root){ animation:none; }
}


/* ============================================================
   Works - masonry: 3 independent columns, native ratios, offset rhythm
   ============================================================ */
/* ============================================================
   WORKS - authored block grid (v3, from the Affinity mockup)
   12 columns, gap 20px, FIXED placement - nothing auto-flows. Row tracks
   are multiples of the column module, so a span reads as a real ratio.

     ROW A  +-- VISION --+------- OSAKA 8x3.4 -------+
     ROW B  |   4 x 6.4  |-- OGON 3x3 --+- PURIST 5x3 +
     ROW C  +------------ MONO 12x2.7 ----------------+
     ROW D  + CRIMSON 3 -+- MODHAUSCO 5 +- RAYBAN 4 --+

   Row A is the panoramic's own height (8 cols at 21:9). Row B is the
   square's (3 cols at 1:1) - the 5-col landscape at 16:9 wants 2.81
   modules against the square's 3, so Purist takes a ~6% crop rather
   than leaving a white sliver. Vision Pro spans A+B, so all three
   bottom edges land on one line.
   ============================================================ */
.wgrid{ --wgap:20px;
  --wmod:calc((100vw - 2 * var(--pad) - 11 * var(--wgap)) / 12);
  display:grid; grid-template-columns:repeat(12,minmax(0,1fr));
  /* height of an n-col span at ratio r is (n*c + (n-1)*g) / r - the gaps are part
     of the width, so they are part of the height. Row A = 8 cols at 21:9, row B =
     3 cols at 1:1, rows C/E = 12 cols at 4.4:1, row D = 5 cols at 16:9. */
  grid-template-rows:calc(var(--wmod) * 3.4286 + var(--wgap) * 3)
                     calc(var(--wmod) * 3 + var(--wgap) * 2)
                     calc(var(--wmod) * 2.7273 + var(--wgap) * 2.5)
                     calc(var(--wmod) * 3.2 + var(--wgap) * 2.4);
  gap:var(--wgap); }
/* same one-direction rule as the home tiles: below 1100px these carry
   aspect-ratio with absolutely-positioned children, so a definite width has to
   come from the track before the ratio touches the height. Alignment is left
   alone - the .wsN spanning tiles still stretch to fill their rows. */
.work .wgrid .band.tile{ width:100%; height:auto; }
/* every tile fills its slot completely - the ratio lives in the slot, not the media */
.work .wgrid .band.tile,.work .wgrid .band-media{ height:100%; }
.work .wgrid .band-media img,.work .wgrid .band-media video{ width:100%; height:100%; object-fit:cover; }
/* Vision Pro sits low-middle in its 9:16 source: keep the headset in frame when the
   slot is shorter than the image (the 2-col and 1-col branches crop vertically) */
.work .wgrid .band.tile.ws1 .band-media img,.work .wgrid .band.tile.ws1 .band-media video{ object-position:50% 62%; }
/* Mono's banner has a bright aluminium edge running under the bottom-left corner,
   exactly where the white title sits. Deepen the scrim on this slot only. */
.work .wgrid .band.tile.ws5 .band-media::after{
  background:linear-gradient(180deg,transparent 38%,color-mix(in srgb,var(--scrim-ink) 55%,transparent) 78%,color-mix(in srgb,var(--scrim-ink) 80%,transparent) 100%); }

/* Modhausco's source is 1350x1920 portrait in a 16:9 slot: the visible band is
   1350 x 759, so 1161px of the image scrolls. The charger occupies y 600-1360, so
   the band has to start at 600/1161 = 52% to hold the whole product. */
.ws7 .band-media img,.ws7 .band-media video{ object-position:50% 52%; }

/* row-start / col-start / row-end / col-end   (--ar is the narrow-screen ratio) */
.ws1{ grid-area:1 / 1 / 3 / 5;   --ar:2/3;   }  /* Vision Pro - tall portrait  */
.ws2{ grid-area:1 / 5 / 2 / 13;  --ar:21/9;  }  /* Osaka - wide panoramic      */
.ws3{ grid-area:2 / 5 / 3 / 8;   --ar:1/1;   }  /* Ogon - square               */
.ws4{ grid-area:2 / 8 / 3 / 13;  --ar:16/9;  }  /* Purist - landscape          */
.ws5{ grid-area:3 / 1 / 4 / 13;  --ar:4.47/1; }  /* Mono - full-width banner   */
.ws6{ grid-area:4 / 1 / 5 / 4;   --ar:3/4;   }  /* Crimson - portrait-ish      */
.ws7{ grid-area:4 / 4 / 5 / 9;   --ar:16/9;  }  /* Modhausco - landscape       */
.ws8{ grid-area:4 / 9 / 5 / 13;  --ar:16/9;  }  /* Ray-Ban - landscape         */

/* below 1100px the 12-column field is too narrow to keep the block readable.
   Two columns, authored spans: wide tiles keep both columns, the portrait and
   the squares take one. Explicit placement again, so there are still no holes. */
/* Below 1100px: two columns, still authored tile by tile. Every placement is
   written at .work .wgrid .band.tile.wsN strength - a bare .wsN would lose to the
   base tile rule below it, and its grid-area shorthand would reset every span.
   Rows pair up exactly, so no cell is ever left empty:
     r1-2  VISION (tall)  | OGON / PURIST
     r3    OSAKA          full width
     r4-5  CRIMSON (tall) | MODHAUSCO / RAYBAN
     r6    MONO           full width                                          */
@media (max-width:1100px){
  .wgrid{ grid-template-columns:repeat(2,minmax(0,1fr)); grid-template-rows:none;
    grid-auto-rows:auto; }
  .work .wgrid .band.tile{ aspect-ratio:var(--ar); height:auto; }
  .work .wgrid .band.tile.ws1{ grid-area:1 / 1 / 3 / 2; aspect-ratio:auto; height:100%; }
  .work .wgrid .band.tile.ws3{ grid-area:1 / 2 / 2 / 3; }
  .work .wgrid .band.tile.ws4{ grid-area:2 / 2 / 3 / 3; }
  .work .wgrid .band.tile.ws2{ grid-area:3 / 1 / 4 / 3; }
  .work .wgrid .band.tile.ws6{ grid-area:4 / 1 / 6 / 2; aspect-ratio:auto; height:100%; }
  .work .wgrid .band.tile.ws7{ grid-area:4 / 2 / 5 / 3; }
  .work .wgrid .band.tile.ws8{ grid-area:5 / 2 / 6 / 3; }
  .work .wgrid .band.tile.ws5{ grid-area:6 / 1 / 7 / 3; }
}
@media (max-width:640px){
  .wgrid{ grid-template-columns:1fr; }
  /* [class*="ws"] ties the per-tile specificity above and wins on source order,
     so the authored two-column areas release without !important */
  .work .wgrid .band.tile[class*="ws"]{ grid-area:auto; aspect-ratio:var(--ar); height:auto; }
  /* one column: moderate the extremes so no tile runs off-screen */
  .ws1,.ws6{ --ar:3/4; } .ws2{ --ar:16/9; }
  /* the keyboard. 4.47:1 is a banner across a 12-column field and a shallow
     strip in one column - at 16/9 the render has the height to read as a
     product, and it still sits well under the portrait tiles above it. */
  .ws5{ --ar:16/9; }
}



/* ============================================================
   HIG: 44px minimum tap area on links whose visual size is smaller.
   A transparent, absolutely-positioned pseudo-box grows the hit area
   only - the rendered icon/label keeps its exact size and position.
   ============================================================ */
.mfoot-soc a,.pbar-soc a,.mfoot-bar a,.mfoot-mail{ position:relative; }
/* ============================================================
   BRAND SHINE - a specular streak sweeps the mark and the wordmark
   left to right on hover. One overlay covers both: mix-blend-mode
   screen brightens only the black glyphs and leaves white untouched,
   so the SVG mark and the letters catch the light together.
   ============================================================ */
.hbrand{ position:relative; isolation:isolate; }
.hbrand::after{
  content:""; position:absolute; inset:0; z-index:2; pointer-events:none; opacity:0;
  mix-blend-mode:var(--brand-shine-blend);
  background:linear-gradient(75deg, transparent 42%, color-mix(in srgb,var(--brand-shine-ink) 55%,transparent) 47%, var(--brand-shine-ink) 50%, color-mix(in srgb,var(--brand-shine-ink) 55%,transparent) 53%, transparent 58%);
  background-repeat:no-repeat; background-size:220% 100%; background-position:160% 0;
}
.hbrand:hover::after,.hbrand:focus-visible::after{ animation:brandShine var(--t-xslow) var(--ix-ease-out); }
/* the hero card's wordmark sits ON footage, not on the page: it keeps the media
   treatment in both themes, the same way --media-ink does. */
[data-theme="dark"] .hero-card .hbrand{ --brand-shine-ink:var(--media-ink); --brand-shine-blend:screen; }
@keyframes brandShine{
  0%{ opacity:0; background-position:160% 0; }
  12%{ opacity:1; }
  88%{ opacity:1; }
  100%{ opacity:0; background-position:-60% 0; }
}
@media (prefers-reduced-motion:reduce){
  .hbrand:hover::after,.hbrand:focus-visible::after{ animation:none; }
}

/* .pmenu-soc a gets its 44px from a taller box, so re-anchor its growing
   underline to the text line instead of the bottom of that box. */
.pmenu-soc a::after{ bottom:calc(50% - 0.72em); }

/* ============================================================
   BURGER PANEL - TARGETS AND THE CURRENT-PAGE MARK
   Two problems with one cause: .pmenu-links a and .pmenu-soc a were
   display:flex children of a column flex container, so each box stretched to
   the full 350px panel width. The growing underline is left:0 right:0 on that
   box, so the current-page mark spanned the panel and read as a row divider
   rather than as a mark on the word.
   align-self:flex-start shrinks each box to its own content, so the mark is
   now text width plus the padding - 113px under Home instead of 350px - and
   it follows the word on every page without knowing its length.
   The padding is what grows the target: the box is bigger while the type is
   unchanged. The negative margin cancels the left padding so the text still
   starts on the panel's 20px gutter and stays flush with the CTA below it -
   the tap area grows leftward into the gutter instead of indenting the label.
   The nav underline needs re-anchoring for the same reason .pmenu-soc's did:
   bottom:-2px sat 2px under a box that is now much taller than the text.
   ============================================================ */
.pmenu-links a,.pmenu-soc a{ align-self:flex-start; margin-left:-12px; }
.pmenu-links a{ min-height:56px; padding:0 12px; }
.pmenu-soc a{ min-height:56px; padding:0 12px; }
/* The mark is inset by exactly the padding that grew the tap target, so it
   spans the TEXT and nothing else: no overhang past the H, no gap before the
   final letter. left/right, never a width - the word's own box still decides
   how long the line is, so it fits Home, Works and Contact without knowing
   any of them. If the 12px padding changes, these two change with it. */
.pmenu-links a::after,.pmenu-soc a::after{ left:12px; right:12px; }
/* 2px against 34px type: reads as a mark rather than the 1px hairline the
   shared rule gives 16px links. */
.pmenu-links a::after{ bottom:calc(50% - 0.47em); height:2px; }
/* The panel's only conversion action, and it was 16px under 34px nav links -
   the hierarchy read backwards. 21px at line-height 1 with 20px/28px padding
   puts it at 0.62 of the nav links: clearly subordinate to them as a group,
   clearly the heaviest single element on the panel. Fill, border and square
   corners are untouched. */
.pmenu-cta{ font-size:21px; line-height:1; padding:20px 28px; }

/* ============================================================
   BURGER PANEL - TOUCH FEEDBACK
   Two independent bugs, both Android-only, both fixed here rather than by
   editing the shared interaction rules at the top of this file (those are
   used by the desktop bar, the footer and the dialogs - desktop is correct
   today and must not move).

   1. -webkit-tap-highlight-color was set NOWHERE in the project, so Chrome on
      Android painted its own grey-green box over the whole link. Scoped to the
      panel's own controls, not applied globally.
   2. The accent + underline were bound to :hover and :focus-visible only.
      iOS Safari synthesises hover on tap so it fired there; Android Chrome
      does not, so on Android the effect never existed. :active is what a
      touch actually sets, so the effect is bound to that as well.

   Stuck hover is the usual price of that fix and is avoided by cancelling the
   hover state under (hover:none) rather than by rewriting the shared :hover
   rule into @media (hover:hover) - same outcome, no risk to the mouse path:
   on a touch device the hover branch is neutralised, so nothing can survive
   the finger lifting; :active ends with the touch by definition.
   :focus-visible is untouched everywhere - keyboard users keep the ring.
   Text colour and the underline scale only. No background, no box-shadow,
   no plate, in any state. ============================================================ */
.pbar-menu a,.pmenu-cta,.pbar-burger{ -webkit-tap-highlight-color:transparent; }
.pmenu-links a:active,.pmenu-soc a:active{ color:var(--accent); background:none; box-shadow:none; }
.pmenu-links a:active::after,.pmenu-soc a:active::after{ transform:scaleX(1); }
@media (hover:none){
  .pmenu-links a:hover,.pmenu-soc a:hover{ color:var(--c-head); }
  .pmenu-soc a:hover{ color:var(--muted); }
  .pmenu-links a:hover::after,.pmenu-soc a:hover::after{ transform:scaleX(0); }
  .pmenu-links a.is-here:hover::after{ transform:scaleX(1); }
  .pmenu-links a:active,.pmenu-soc a:active{ color:var(--accent); }
  .pmenu-links a:active::after,.pmenu-soc a:active::after{ transform:scaleX(1); }
  .pmenu-cta.btn--solid:hover::before{ transform:scaleX(0); }
  .pmenu-cta.btn--solid:hover{ color:var(--c-bg); border-color:var(--c-head); }
}
/* Filled button, so the text-colour effect would be invisible on it: its press
   state is the fill it already owns, completed. The accent sweep runs to full
   on :active in --c-accent-press, the darker of the three accent steps, so a
   tap reads as pressed rather than merely hovered - same language as the
   desktop hover, one shade deeper. */
.pmenu-cta.btn--solid:active::before{ transform:scaleX(1); }
.pmenu-cta.btn--solid:active{ color:var(--accent-ink); border-color:var(--c-accent-press); background:none; box-shadow:none; }
.mfoot-soc a::before,.pbar-soc a::before,.mfoot-bar a::before,.mfoot-mail::before{
  content:""; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:max(100%,44px); height:max(100%,44px); }


/* ============================================================
   PHASE 8 - FRAMED LAYOUT (home + works)
   The page becomes a canvas in a frame: content is inset by a
   --rail on both sides; the left rail carries the scroll cue.
   Scoped to body.framed so contact / privacy are untouched.
   ============================================================ */
body.framed{ --rail:0px; --pad:calc(var(--gutter) + var(--rail)); }
@media (min-width:900px){ body.framed{ --rail:48px; } }
@media (min-width:1200px){ body.framed{ --rail:80px; } }

body.framed .shell,
body.framed .phead,
body.framed .htag,
body.framed .work.shell,
body.framed .testi.shell,
body.framed .mfoot-in,
body.framed .mfoot-bar{ padding-left:var(--pad); padding-right:var(--pad); }
/* hero keeps its own sizing rule - it is height-constrained in practice,
   so respecting the rail only ever narrows it on very wide, short screens */
body.framed .hero-card{ width:min(calc(100vw - 2 * var(--frame) - 2 * var(--rail)), calc(var(--hero-h) * 2)); }

/* screen-reader-only: keeps a heading in the document outline while the visual
   title block stays removed. Not display/visibility - those hide it from AT too */
.sr-only{ position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap; border:0; }

/* no page-title block above the grid: give the tiles their own top breathing
   room so they do not crowd the sticky bar */
body.framed .work--bare{ padding-top:clamp(32px,4vw,64px); }

/* ---- the left rail (scroll cue) ---- */
/* --bar-h is published by smooth.js from the bar's measured height; the fallback
   matches the bar's tallest child (the CTA at 57px) so it holds without JS too */
body.framed{ --bar-h:calc(var(--frame) * 2 + 57px); }
.rail{ position:fixed; top:0; bottom:0; width:var(--rail); z-index:9; display:none;
  flex-direction:column; align-items:center; justify-content:flex-end;
  gap:32px; padding-top:calc(var(--bar-h) + 8px); padding-bottom:var(--frame);
  overflow:hidden; pointer-events:none; }
.rail-l{ left:0; }
.rail > *{ pointer-events:auto; }
@media (min-width:900px){ .rail{ display:flex; } }

/* the vertical social labels that used to sit in a right rail are gone - the footer,
   the mobile menu and the Contact page all carry the same two links.
   The vertical studio line is retired at every width. */
.hero-anchor{ display:none; }
@media (min-width:900px){
  body.framed .pbar-soc{ display:none; }
}

/* ============================================================
   PHASE 9 - HOVER REVEAL
   One coordinated gesture on a project tile: media scales 1 -> 1.03 and
   the title lifts on hover intent, while the video crossfades in only
   once a real frame has been presented (see wireReel in v27.js).
   Every part runs --t-reveal (.45s) on --ix-ease-out.
   ============================================================ */
.work .pgrid .band.tile .band-body,.work .wgrid .band.tile .band-body{
  transition:opacity var(--t-reveal) var(--ix-ease-out), transform var(--t-reveal) var(--ix-ease-out); }
.work .pgrid .band.tile:hover .band-body,.work .pgrid .band.tile:focus-visible .band-body,
.work .wgrid .band.tile:hover .band-body,.work .wgrid .band.tile:focus-visible .band-body{ transform:translateY(-4px); }
@media (prefers-reduced-motion:reduce){
  .band-media video,
  .work .pgrid .band.tile .band-media video,.work .wgrid .band.tile .band-media video,
  .work .pgrid .band.tile .band-body,.work .wgrid .band.tile .band-body{ transition:none; }
  .work .pgrid .band.tile:hover .band-body,.work .pgrid .band.tile:focus-visible .band-body,
  .work .wgrid .band.tile:hover .band-body,.work .wgrid .band.tile:focus-visible .band-body,
  .work .wgrid .band.tile.quiet .band-body{ transform:none; }
}

/* ============================================================
   PHASE 10 - SCROLL-COMPACTED TOP BAR
   At rest the bar keeps its full --frame padding and no border.
   Past the threshold .is-compact halves the padding, steps the
   wordmark / links / CTA down, and fades in a hairline so the bar
   reads as detached from the content. smooth.js owns the class and
   the hysteresis band. Every part runs --t-fill (.3s) / --ix-ease.
   Transitions are declared with the full property list because the
   base rules already animate color - a shorthand would drop it.
   ============================================================ */
.pbar,.lp-top{ border-bottom:1px solid transparent;
  transition:border-bottom-color var(--t-fill) var(--ix-ease); }
.pbar.is-compact,.lp-top.is-compact{ border-bottom-color:color-mix(in srgb,var(--c-head) 8%,transparent); }
.pbar-in{ transition:padding var(--t-fill) var(--ix-ease); }
.pbar.is-compact .pbar-in{ padding:calc(var(--frame) * 0.5) var(--frame); }
/* privacy carries the padding on the bar itself, not on an inner row */
.lp-top{ transition:padding var(--t-fill) var(--ix-ease), border-bottom-color var(--t-fill) var(--ix-ease); }
.lp-top.is-compact{ padding:calc(var(--frame) * 0.5) var(--frame); }
.pbar .hbrand,.lp-top .hbrand{ transition:font-size var(--t-fill) var(--ix-ease); }
.pbar .pbar-links a{ transition:color var(--t-fast) var(--ix-ease), font-size var(--t-fill) var(--ix-ease); }
.pbar-r .btn.pbar-cta{ transition:color var(--t-fill) var(--ix-ease),
  font-size var(--t-fill) var(--ix-ease), padding var(--t-fill) var(--ix-ease); }
@media (min-width:521px){
  .pbar.is-compact .hbrand,.lp-top.is-compact .hbrand{ font-size:25.5px; }   /* 30 -> 25.5, 15% down */
  .pbar.is-compact .pbar-links a{ font-size:20px; }  /* 22 -> 20, still readable */
  .pbar.is-compact .pbar-r .btn.pbar-cta{ font-size:16px; padding:10px 18px; }
}
@media (max-width:520px){
  .pbar.is-compact .pbar-in,.lp-top.is-compact{ padding:8px var(--frame); }
}
@media (prefers-reduced-motion:reduce){
  .pbar,.lp-top,.pbar-in,.pbar .hbrand,.lp-top .hbrand,.pbar .pbar-links a,.pbar-r .btn.pbar-cta{ transition:none; }
}

/* ============================================================
   PHASE 11 - OXFORD BLUE ACCENT
   The accent marks interaction and state, nothing else: no heading,
   body copy, tile label, icon at rest, static border or background
   uses it. Everything below is a :hover, :focus-visible or current
   -page rule, so at rest the accent is not on screen at all.
   ============================================================ */

/* current page: black at rest, its underline simply already drawn. Same 1px as
   every other link - the state is shown by the rule being present, not thicker. */
.pbar-links a.is-here{ color:var(--c-head); }
.pbar-links a.is-here::after{ transform:scaleX(1); }
.pmenu-links a.is-here{ opacity:1; color:var(--c-head); }
.pmenu-links a.is-here::after{ transform:scaleX(1); }

/* keyboard focus: one visible ring everywhere, including the many
   interactive elements that previously had none. Never shown on mouse
   click - :focus-visible only. */
/* lime alone cannot be seen on white (1.18:1), so the ring is lime backed by an
   ink halo: the halo guarantees the 3:1 boundary, the lime carries the identity */
a:focus-visible,button:focus-visible,[role="button"]:focus-visible,
summary:focus-visible,[tabindex]:focus-visible{
  outline:3px solid var(--accent); outline-offset:2px; border-radius:1px;
  box-shadow:0 0 0 5px color-mix(in srgb,var(--c-head) 85%,transparent); }
/* Tiles are large dark-media targets. The accent is 15.9:1 on white but only ~1.5:1
   on the dark posters, so the ring needs a white band under it - and that band cannot
   be the tile's own background or an inset shadow: both paint BELOW .band-media,
   which is absolutely positioned over the whole tile. Draw it on a pseudo-element
   stacked above the media instead. The 8px white border spans 0-8px from the edge
   and the 3px ring lands at 5-8px, so the accent always sits on white. */
.band.tile:focus-visible{ outline:none; box-shadow:none; }
.band.tile:focus-visible::after{ content:""; position:absolute; inset:0; z-index:5; pointer-events:none;
  border:8px solid color-mix(in srgb,var(--media-ink) 92%,transparent); outline:3px solid var(--accent); outline-offset:-5px; }
/* Over the dark hero video the polarity flips: the dark ink halo disappears into
   the footage and the accent ring alone is ~1.5:1 on it, so the halo goes WHITE and
   carries the ring. Blue on that white halo is 8.3:1. */
.hero a:focus-visible,.hero button:focus-visible,
.hero-card a:focus-visible,.hero-card button:focus-visible,
.hscroll:focus-visible{ outline-color:var(--accent); box-shadow:0 0 0 5px color-mix(in srgb,var(--media-ink) 92%,transparent); }
/* inputs draw their own accent focus, so suppress the doubled ring */
.q-input:focus-visible,.q-textarea:focus-visible{ outline:none; }

/* scroll indicator: accent on hover only, at rest it stays grey */
.hscroll:hover{ color:var(--accent-ink); background:var(--accent); border-color:var(--accent); }

/* text selection reads as interaction too */
::selection{ background:var(--accent); color:var(--accent-ink); }

@media (prefers-reduced-motion:reduce){
  .pbar-links a.is-here::after,.pmenu-links a.is-here::after{ transition:none; }
}

/* ============================================================
   HOME - vertical SCROLL cue, bottom-left
   Sits in the left rail box: font-size, letter-spacing, colour, padding and
   distance from the bottom frame all follow the site's micro-label scale.
   ============================================================ */
/* A 1px track with a short dark segment running down it. The repeated travel is
   what makes it register peripherally - the element itself stays hairline thin.
   Padding follows the site's micro-label scale. */
.rail-scroll{ background:none; border:0; display:block; padding:8px 4px;
  color:var(--muted); transition:color var(--t-fast) var(--ix-ease); }
.rail-scroll:hover,.rail-scroll:focus-visible{ color:var(--accent); }
.rail-track{ position:relative; display:block; width:1px; height:64px; margin:0 auto;
  background:color-mix(in srgb,var(--c-head) 12%,transparent); overflow:hidden; }
.rail-track::after{ content:""; position:absolute; left:0; width:1px; height:20px;
  background:color-mix(in srgb,var(--c-head) 55%,transparent); animation:railRun 1.8s var(--ix-ease) infinite; }
/* 0-72% travels, the rest is the pause that keeps it deliberate rather than frantic */
@keyframes railRun{
  0%{ transform:translateY(-20px); }
  72%,100%{ transform:translateY(64px); }
}
/* on hover the segment picks up the link accent along with the caret */
.rail-scroll:hover .rail-track::after,.rail-scroll:focus-visible .rail-track::after{ background:currentColor; }
/* fades out once the user leaves the top of the page (home only - nothing sets
   .is-past elsewhere) */
.rail-l{ transition:opacity var(--t-fill) var(--ix-ease); }
.rail-l.is-past{ opacity:0; pointer-events:none; }
@media (prefers-reduced-motion:reduce){
  /* static: the segment simply sits at the middle of the track */
  .rail-track::after{ animation:none; transform:translateY(22px); }
  .rail-l{ transition:none; }
}

/* review rating - sits above the name in the card footer */
.rev-stars{ display:flex; gap:3px; margin-bottom:6px; color:var(--c-head); }
.rev-stars svg{ width:13px; height:13px; fill:currentColor; }
/* a half rating: put class rev-stars--half on the row and the 5th star empties */
.rev-stars--half svg:last-child{ fill:none; stroke:currentColor; stroke-width:1.4; }

/* ============================================================
   PHASE 12 - TESTIMONIAL MARQUEE
   One strip, the 6 cards rendered twice. The track translates by exactly
   -50% of its own width, so the last frame is pixel-identical to frame 0
   and the loop has no seam. Only transform animates, so it stays on the
   compositor. The hover pause is a playbackRate ramp in v27.js -
   animation-play-state alone stops dead, which reads as a glitch.
   ============================================================ */
/* The hover lift is transform:translateY(-4px) (v27.css). A masked element is
   clipped to its own box, and the strip had no vertical room, so the raised top
   edge - border included - was cut off by the mask. Reserve the lift plus the
   border, and pull the same amount back out of the top margin so the section
   rhythm is unchanged. Same padding on the mobile branch, where overflow-x:auto
   clips for the same reason. */
.rev-marquee{ position:relative; margin-top:calc(clamp(24px,3vw,40px) - 10px); padding-block:10px;
  /* break the section frame: the strip runs past the viewport on both sides,
     so cards are cut by the edge rather than sitting in a neat box */
  width:100vw; margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  /* CLIP the 5568px track here, on its own container. Without this the track escaped
     the 1593px box and inflated documentElement.scrollWidth, so the homepage dragged
     sideways into empty space on trackpad and touch - the mask below fades the edges
     visually but does not contain layout, and html{overflow-x:clip} did not hold it.
     overflow-x:clip, not hidden: hidden on one axis makes the other a scroll
     container, which would break the page's Lenis-driven vertical scrolling. */
  overflow-x:clip;
  /* edge fade as a mask, not an overlay, so it needs no backdrop colour */
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 7%,#000 93%,transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 7%,#000 93%,transparent 100%); }
/* No padding on the track: -50% counts the track's own box, so any inline
   padding would be double-counted and the seam would drift each cycle. */
.rev-track{ display:flex; gap:var(--rev-gap,24px); width:max-content; will-change:transform;
  animation:revDrift 52s linear infinite; }
@keyframes revDrift{ from{ transform:translate3d(0,0,0); } to{ transform:translate3d(-50%,0,0); } }
/* the gap is also the first card's offset, so the strip starts mid-flow */
.rev-marquee{ --rev-gap:clamp(16px,1.6vw,24px); }
.rev-track > .rev:first-child{ margin-left:var(--rev-gap); }
/* ~3 cards visible at a time */
.rev-marquee .rev{ flex:0 0 clamp(300px,29vw,440px); justify-content:space-between; }
.rev-marquee .rev-foot{ margin-top:auto; }   /* equal height, footer pinned down */
/* pointer and keyboard users get the same pause */
.rev-marquee:hover .rev-track{ animation-play-state:paused; }
.rev-marquee:focus-within .rev-track{ animation-play-state:paused; }

/* touch + small screens: no auto-scroll, native swipe instead */
@media (max-width:900px){
  .rev-marquee{ width:auto; margin-left:0; margin-right:0; padding-block:10px;
    -webkit-mask-image:none; mask-image:none;
    overflow-x:auto; scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
    scrollbar-width:none; }
  .rev-marquee::-webkit-scrollbar{ display:none; }
  .rev-track{ animation:none !important; will-change:auto; }
  .rev-track > .rev:first-child{ margin-left:0; }
  .rev-marquee .rev{ flex-basis:min(84vw,420px); scroll-snap-align:center; }
  /* the duplicate set exists only to close the loop - drop it when there is none */
  .rev-marquee .rev[aria-hidden="true"]{ display:none; }
}
@media (hover:none) and (min-width:901px){ .rev-track{ animation:none !important; } }

/* reduced motion: back to the static 2-up grid, all 6 cards readable */
@media (prefers-reduced-motion:reduce){
  .rev-marquee{ width:auto; margin-left:0; margin-right:0; overflow:visible;
    -webkit-mask-image:none; mask-image:none; }
  .rev-track{ animation:none !important; will-change:auto; display:grid; width:auto;
    grid-template-columns:repeat(2,1fr); gap:clamp(12px,1.2vw,16px); }
  .rev-track > .rev:first-child{ margin-left:0; }
  .rev-marquee .rev{ flex-basis:auto; }
  .rev-marquee .rev[aria-hidden="true"]{ display:none; }
  @media (max-width:900px){ .rev-track{ grid-template-columns:1fr; } }
}

/* ============================================================
   CONTACT - response note + FAQ accordion
   Native <details>/<summary>: keyboard operable, findable by the
   browser's own in-page search, and needs no script.
   ============================================================ */
.cform-note{ margin:0 0 16px; font-size:14px; line-height:1.5; color:var(--muted); }
/* the FAQ sits in the contact page's left column, under the social links */
.cfaq{ margin-top:clamp(28px,3vw,40px); }
.faq-h{ font-size:11px; font-weight:600; letter-spacing:0.14em; text-transform:uppercase;
  color:var(--faint); margin:0 0 16px; }
.faq-list{ border-top:1px solid var(--line); }
.faq-item{ border-bottom:1px solid var(--line); }
.faq-qh{ margin:0; font-weight:inherit; }
.faq-q{ display:flex; align-items:flex-start; justify-content:space-between; gap:20px; width:100%;
  padding:14px 0; min-height:44px; background:none; border:0; text-align:left;
  font-family:inherit; font-weight:400;
  font-size:clamp(15px,1.2vw,17px); line-height:1.4; letter-spacing:-0.01em; color:var(--c-head);
  transition:color var(--t-fast) var(--ix-ease); }

.faq-q:hover{ color:var(--accent); }
.faq-q:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
/* a plus that becomes a minus - drawn from two rules, no icon font */
.faq-ic{ position:relative; flex:0 0 auto; width:14px; height:14px; margin-top:5px; }
.faq-ic::before,.faq-ic::after{ content:""; position:absolute; inset:50% 0 auto 0; height:1px;
  background:currentColor; transition:transform var(--t-line) var(--ix-ease-out); }
.faq-ic::after{ transform:rotate(90deg); }
.faq-q[aria-expanded="true"] .faq-ic::after{ transform:rotate(0deg); }
/* v27.js drives this: it sets .faq-item.open and an inline max-height in px.
   Padding lives on the paragraph, not the panel, so a closed panel is truly 0. */
.faq-a{ max-height:0; overflow:hidden; max-width:68ch;
  transition:max-height var(--t-fill) var(--ix-ease-out); }
.faq-a p{ margin:0; padding:0 0 20px; font-size:15px; line-height:1.6; font-weight:350; color:var(--muted); text-wrap:pretty; }
@media (prefers-reduced-motion:reduce){ .faq-a{ transition:none; } }
@media (prefers-reduced-motion:reduce){ .faq-ic::before,.faq-ic::after{ transition:none; } }

/* ============================================================
   ACCENT ON HOVER - the remaining interactive surfaces
   Everything here is :hover / :focus-visible / current-page only.
   ============================================================ */
/* project tile titles. The label sits on dark media, so the accent alone would be
   ~1.5:1 there: it arrives WITH a white-to-accent underline and the scrim deepens,
   which is what carries the state. See the note in chat about this trade. */
.work .pgrid .band.tile .band-title,.work .wgrid .band.tile .band-title,.band.full .band-title{
  transition:color var(--t-reveal) var(--ix-ease-out); }
.work .pgrid .band.tile:hover .band-title,.work .pgrid .band.tile:focus-visible .band-title,
.work .wgrid .band.tile:hover .band-title,.work .wgrid .band.tile:focus-visible .band-title{
  text-decoration:underline; text-decoration-color:var(--accent);
  text-decoration-thickness:2px; text-underline-offset:5px; }
/* the case-study overlay's own chrome: hover + focus take the site accent */
.cs-foot-next:hover .cs-foot-name,.cs-foot-back:hover{ color:var(--accent); }
.cs-close:focus-visible,.cs-foot-back:focus-visible,.cs-foot-next:focus-visible,
.cs-cmp-grab:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
@media (prefers-reduced-motion:reduce){
  .work .pgrid .band.tile .band-title,.work .wgrid .band.tile .band-title,.band.full .band-title{ transition:none; }
}

/* the global a:hover accent must not apply over dark media: #0111FF is ~1.5:1 there */
.hero a:hover,.hero-card a:hover,.band.full .band-body a:hover{ color:var(--media-ink); }

/* The CTA arrow states where the click goes: diagonal up-right everywhere (opens
   the modal), rotated to point DOWN on contact, where it scrolls to the form
   already on the page. Same SVG, rotated - the shape never changes. The hover
   nudge rides in the rotated frame, so it still travels the way the arrow points. */
body.cpage-body .pbar-cta svg,body.cpage-body .pmenu-cta svg{ transform:rotate(135deg); }
body.cpage-body .pbar-cta:hover svg,body.cpage-body .pmenu-cta:hover svg{ transform:rotate(135deg) translate(2px,-2px); }
/* Arrival rotation, internal navigation only. .cta-arw-arm holds the arrow at its
   diagonal rest (added in contact.html's head, so it is the FIRST painted state -
   no flash of the down arrow); .cta-arw-spin lends it a transition; dropping -arm
   then carries it to the 135deg rest above. Transform only. */
html.cta-arw-arm body.cpage-body .pbar-cta svg,html.cta-arw-arm body.cpage-body .pmenu-cta svg{ transform:rotate(0deg); }
html.cta-arw-spin body.cpage-body .pbar-cta svg,html.cta-arw-spin body.cpage-body .pmenu-cta svg{ transition:transform var(--t-arw-in) var(--ix-ease-out); }
@media (prefers-reduced-motion:reduce){
  html.cta-arw-arm body.cpage-body .pbar-cta svg,html.cta-arw-arm body.cpage-body .pmenu-cta svg{ transform:rotate(135deg); }
  html.cta-arw-spin body.cpage-body .pbar-cta svg,html.cta-arw-spin body.cpage-body .pmenu-cta svg{ transition:none; }
}

/* ============================================================
   CURSOR - the AVREA mark (the site's ONLY cursor system)
   Art: Logo_Invert.png with its left 17% cropped away (that taper renders under
   1px and fragments), resampled undistorted from the 1500x615 original to
   32x16 / 64x32. Not rotated - the tip already points up-left, and it sits at
   the top-left of the asset, so the hotspot is 0 1 and clicks are never offset.
   Two states: black body at rest, accent #0111FF on anything clickable. Both
   carry a 1px white outline baked into the asset (2px at 2x), so the mark stays
   readable on white, on the hero footage and on the dark tile posters.

   Two things make this authoritative rather than a specificity race:
   every competing `cursor:pointer` has been deleted from the component rules,
   and this block is last in the last stylesheet. The `pointer` / `default`
   keywords after each url() are the fallbacks if the art fails to load.
   Fine pointers only - meaningless on touch, and it would fight text selection
   on hybrid devices.
   ============================================================ */
@media (hover:hover) and (pointer:fine){
  html, body, *{
    cursor:url("img/cursor.png") 0 1, default;
    cursor:-webkit-image-set(url("img/cursor.png") 1x, url("img/cursor-2x.png") 2x) 0 1, default;
    cursor:image-set(url("img/cursor.png") 1x, url("img/cursor-2x.png") 2x) 0 1, default;
  }
  /* every clickable surface, plus their descendants so the cursor never flips
     while the pointer moves inside one control */
  a, a *, button, button *, [role="button"], [role="button"] *, summary, summary *, label[for], select, [data-open-quote], [data-open-cal], [data-project], [data-project] *, [data-scroll-to], [data-scroll-to] *, .scard-trigger, .scard-trigger *, input[type="button"], input[type="submit"], input[type="checkbox"], input[type="radio"]{
    cursor:url("img/cursor-link.png") 0 1, pointer;
    cursor:-webkit-image-set(url("img/cursor-link.png") 1x, url("img/cursor-link-2x.png") 2x) 0 1, pointer;
    cursor:image-set(url("img/cursor-link.png") 1x, url("img/cursor-link-2x.png") 2x) 0 1, pointer;
  }
  /* text entry keeps the system caret */
  input:not([type="button"]):not([type="submit"]):not([type="checkbox"]):not([type="radio"]), textarea, [contenteditable="true"]{ cursor:text; }
  /* states with their own meaning win by coming last */
  .cs-cmp-grab, .cs-cmp-grab *{ cursor:ew-resize; }
  .work .pgrid .band.tile.slot, .work .pgrid .band.tile.slot *{ cursor:default; }
  [disabled], [aria-disabled="true"]{ cursor:not-allowed; }
}
/* coarse pointers: native affordances, since the component rules no longer carry them */
@media not all and (pointer:fine){
  a, button, [role="button"], summary, label[for], select, [data-project],
  [data-open-quote], [data-open-cal], [data-scroll-to], .scard-trigger,
  input[type="button"], input[type="submit"], input[type="checkbox"], input[type="radio"]{ cursor:pointer; }
  input:not([type="button"]):not([type="submit"]), textarea, [contenteditable="true"]{ cursor:text; }
}


/* ============================================================
   SPLIT HERO ON PHONES - one column, capped vertical film.
   The desktop two-column rule is restated LATER in the bundle than the old
   820px single-column override, so it won that cascade: on Modhausco, Ogon and
   Purist the 9:16 film stayed in a narrow left track with the title and copy
   crammed beside it. This block sits at the end of the bundle and wins on
   source order. DOM order is already film -> title -> copy, so one column is
   all that is needed.
   WIDTH CAP: 80vw, so the film is the dominant element on the screen rather than
   one of two equal blocks. At 375px that is 300px wide - 533px tall for a 9:16
   source, 375px for a 4:5 one - and the title follows within one short scroll.
   The svh guard keeps the whole frame on one screen on short phones: width and
   height are both auto against a max, so the browser scales the frame down with
   its ratio intact instead of letterboxing. Never cropped. Desktop unaffected.
   ============================================================ */
@media (max-width:760px){
  .cs-split{ grid-template-columns:1fr; gap:20px; align-items:start; }
  .cs-split > .cs-split-media{ width:fit-content; max-width:80vw; margin-inline:auto; max-height:none; }
  .cs-split > .cs-split-media .cs-split-vid{ width:auto; max-width:80vw; height:auto; max-height:calc(100svh - 150px); object-fit:contain; }
}


/* Openable media carries its affordance from the AUTHORED stylesheet, not from the
   style block buildLightbox() injects - that block does not exist until the first
   lightbox has been opened, so loops showed the default cursor on a first visit
   while Details stills already showed pointer (.cs-mosaic .cs-img). One value for
   both: .cs-sec wins over the global cursor rule and over the injected .cs-lb-able. */
.cs-sec .cs-lb-able{ cursor:pointer; }


/* Mono Keyboard tile hover crop. scene3.mp4 is 1440x810 (16:9) in a 4.47:1 slot on
   Works and a 3:2 slot on the home row, so the clip crops vertically and 50% landed
   mid-keycap. 15% shows the top of the source frame - the dark background and the
   knob / OLED edge - with the keys in the lower part of the window.
   VIDEO ONLY. The Works cover is mono-banner.jpg at 1920x430 in a 4.47 slot, so it
   crops nothing and object-position cannot move it; the home cover (mono-cover.webp,
   1920x1080 in 3:2) loses 8% top and bottom at centre and the keyboard has headroom
   to spare, so neither needed the same treatment. The narrow-screen 3/2 rule for this
   tile is more specific and still owns the phone crop. */
.band.tile[data-project="mono"] .band-media video{ object-position:50% 15%; }


/* ============================================================
   HOW IT WORKS - process.html only. Full-width stacked blocks:
   a wide plate, then a narrow measure beneath it, the measure
   alternating left / right. The numeral is a background folio,
   clipped by its own block - NEVER by html or body.
   The home page uses its own .hwc namespace and touches none of this.
   ============================================================ */
/* --hiw-folio-drop is the ONE knob for the folio's vertical position: the numeral is
   anchored to the BOTTOM of its text column, and this pushes it further down. In em, so
   it scales with the numeral at every breakpoint. Nudge the whole set from here. */
.hiw{ --hiw-folio-drop:0.08em; padding-block:clamp(8px,1vw,16px) 0; }
.hiw--page{ padding-top:0; }
.hiw-blocks{ display:block; }
/* overflow:hidden lives HERE, on the individual block, so the folio's bleed can
   never reach documentElement.scrollWidth */
/* padding-bottom is the folio's floor: the block clips, so without it the digits'
   feet were sliced flat by the block edge. Only the SIDE bleed is meant to be cut. */
.hiw-block{ position:relative; overflow:hidden; padding-top:clamp(30px,3.6vw,56px); padding-bottom:clamp(24px,3.4vw,52px); }
.hiw-block + .hiw-block{ margin-top:clamp(80px,9vw,140px); }
/* the case-study bleed, reused verbatim in form: 50% - 50vw plus one gutter of
   inset. Deliberately not 100vw - that counts the scrollbar and overflows. */
.hiw-plate{ position:relative; z-index:1; margin-inline:calc(50% - 50vw + var(--gutter)); }
/* full width of the bleed; the ratio sets the height and a per-block ceiling keeps
   every plate well under a case-study hero. The ceilings are ORDERED by ratio - the
   21/9 plate is capped shortest and the 3/2 tallest - because one blanket cap made
   all three land on the same value and the ratios stopped reading at all. */
.hiw-media{ display:block; width:100%; aspect-ratio:var(--r); max-height:var(--hiw-cap); background:var(--c-bg); }
.hiw-media img{ display:block; width:100%; height:100%; object-fit:cover; }
.hiw-b2 .hiw-media{ --r:16/9; --hiw-cap:min(50vh,540px); }
.hiw-b3 .hiw-media{ --r:21/9; --hiw-cap:min(42vh,450px); }
.hiw-b4 .hiw-media{ --r:3/2;  --hiw-cap:min(58vh,640px); }
.hiw-b2 .hiw-media img{ object-position:50% 40%; }
.hiw-b4 .hiw-media img{ object-position:50% 44%; }
.hiw-text{ position:relative; padding-top:clamp(34px,4.4vw,64px); }
.hiw-copy{ position:relative; z-index:2; max-width:50ch; }
/* the only thing that alternates: the measure sits right on even blocks */
.hiw-block.alt .hiw-copy{ margin-left:auto; }
.hiw-lead{ margin:0 0 15px; font-size:clamp(28px,3vw,44px); line-height:1.1; font-weight:500;
  letter-spacing:-0.02em; color:var(--ink); text-wrap:balance; }
.hiw-body{ margin:0; font-size:clamp(16px,1.35vw,17px); line-height:1.6; font-weight:350; color:var(--muted); text-wrap:pretty; }
.hiw-foot{ margin:18px 0 0; font-size:13px; line-height:1.5; color:var(--faint); max-width:40ch; }
/* background folio: sits behind the measure, bleeding past the container edge on the
   measure's own side, clipped by the block. Tinted to 0.06 - at that value the body
   text over it still measures about 5.2:1, better than the 4.5 floor, so the folio
   costs the copy nothing. A heavier tint read as a grey box behind the words. */
.hiw-no{ position:absolute; z-index:0; top:auto; bottom:0; left:calc(-1 * var(--gutter) - 0.08em);
  transform:translateY(var(--hiw-folio-drop)); font-family:var(--sans); font-weight:500;
  font-size:clamp(180px,26vw,380px); line-height:0.82; letter-spacing:-0.05em;
  color:color-mix(in srgb,var(--c-head) 6%,transparent); pointer-events:none; }
.hiw-block.alt .hiw-no{ left:auto; right:calc(-1 * var(--gutter) - 0.08em); }
/* the section's one accent, on the approval step only */
.hiw-b3 .hiw-no{ color:color-mix(in srgb,var(--c-accent) 8%,transparent); }
/* block 1 has no plate above it, so it is the shortest block. Extra top padding gives the
   bottom-anchored folio room to sit fully inside it - it bleeds off the side only. */
.hiw-b1{ padding-top:clamp(56px,7vw,110px); }
.hiw-cta{ padding-block:clamp(56px,7vw,104px) clamp(56px,7vw,104px); }
@media (max-width:900px){
  .hiw-plate{ margin-inline:calc(50% - 50vw); }
  .hiw-b2 .hiw-media{ --hiw-cap:min(40vh,420px); }
  .hiw-b3 .hiw-media{ --hiw-cap:min(34vh,360px); }
  .hiw-b4 .hiw-media{ --hiw-cap:min(46vh,480px); }
  .hiw-block.alt .hiw-copy{ margin-left:0; }
  .hiw-copy{ max-width:none; }
  .hiw-no{ font-size:clamp(110px,30vw,170px); }
  .hiw-block.alt .hiw-no{ right:auto; left:calc(-1 * var(--gutter) - 0.08em); }
}

/* ============================================================
   HOW IT WORKS - compact home-page version. Own namespace so it
   cannot reach the .hiw rules process.html depends on.
   ============================================================ */
.hwc{ padding-block:clamp(48px,5vw,80px) 0; }
.hwc-top{ border-top:1px solid var(--line); padding-top:clamp(12px,1.4vw,16px); }
.hwc-list{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); column-gap:clamp(20px,2.6vw,40px); row-gap:clamp(24px,3vw,36px); margin-top:clamp(28px,3vw,44px); }
.hwc-item{ display:block; min-width:0; }
.hwc-no{ display:block; font-family:var(--sans); font-weight:500; font-size:clamp(20px,1.8vw,26px); line-height:1; letter-spacing:-0.025em; color:var(--muted); margin:0 0 8px; }
.hwc-lead{ margin:0; font-size:clamp(15px,1.25vw,17px); line-height:1.45; font-weight:600; color:var(--ink); letter-spacing:-0.005em; text-wrap:pretty; }
.hwc-more{ position:relative; display:inline-block; margin-top:clamp(24px,2.6vw,36px); font-size:16px; color:var(--c-head); text-decoration:none; }
.hwc-more::after{ content:""; position:absolute; left:0; right:0; bottom:-2px; height:1px; background:currentColor;
  transform:scaleX(1); transform-origin:left center; transition:transform var(--t-line) var(--ix-ease), background-color var(--t-fast) var(--ix-ease); }
.hwc-more:hover{ color:var(--accent); }
@media (max-width:900px){
  .hwc-list{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (max-width:520px){
  .hwc-list{ grid-template-columns:minmax(0,1fr); row-gap:20px; }
}
@media (prefers-reduced-motion:reduce){ .hwc-more::after{ transition:none; } }


/* ============================================================
   THEME TOGGLE
   Wears the meta-label style from the case study meta bars: no border,
   no background, no icon. The label states the TARGET theme, so it reads
   DARK in light and LIGHT in dark. Both labels are in the markup and CSS
   picks one, so the right word is in the first painted frame - a JS text
   swap would flash the wrong one. Keyboard focus is the site-wide ring
   (a:focus-visible / button:focus-visible above); a mouse click shows none.
   ============================================================ */
.theme-toggle{ display:inline-flex; align-items:center; justify-content:center; gap:9px; min-height:44px; padding:0;
  border:0; background:none; font-family:var(--sans); font-size:12.5px; font-weight:490; letter-spacing:0.13em;
  text-transform:uppercase; line-height:1; white-space:nowrap; color:var(--c-meta);
  transition:color var(--t-fast) var(--ix-ease); }
.theme-toggle:hover{ color:var(--c-head); }
.tt-on-dark{ display:none; }
[data-theme="dark"] .tt-on-light{ display:none; }
[data-theme="dark"] .tt-on-dark{ display:inline; }
/* Desktop is text only - the mark exists solely for the burger cluster, where
   there is no room for a label. It is a STATE indicator, not a switch, and it
   states the mode you are IN, not the one a tap would give you: a filled disc
   with four rays in light, an open ring with its right half solid in dark.
   Two different silhouettes, so the state reads without tapping - the previous
   mark was one outlined square whose core was simply invisible in light, which
   is why it read as an empty box. Curves are fine inside a glyph; the
   square-cornered language governs containers and layout, not icon interiors.
   2px stroke at 22px matches the burger's optical weight. Nothing travels -
   no slide, no mirror, no transition on the swap. */
.tt-ico{ display:none; flex:0 0 auto; }
.tt-ico .tt-moon{ display:none; }
[data-theme="dark"] .tt-ico .tt-sun{ display:none; }
[data-theme="dark"] .tt-ico .tt-moon{ display:block; }
/* at the burger's own breakpoint the label goes and the mark takes the burger's
   optical size - 22px across, ~2px stroke - inside the same 44px target */
@media (max-width:720px){
  .theme-toggle{ width:44px; min-width:44px; gap:0; }
  .theme-toggle .tt-lab{ display:none; }
  /* 24px across and 2px strokes: the burger's own bar width and thickness, so the
     two marks carry the same optical weight. --c-head, not the label's --c-meta,
     for the same reason - a grey mark beside a black burger reads as disabled.
     Mobile only: the desktop label keeps its quieter --c-meta. */
  .theme-toggle{ color:var(--c-head); }
  .tt-ico{ display:block; width:24px; height:24px; }
}
/* privacy's bar is space-between with two children - the toggle joins the
   right-hand pair instead of becoming a third spread item */
.lp-top-r{ display:flex; align-items:center; gap:clamp(14px,2.4vw,28px); }

/* ---------- accent hover / pressed on accent fills ---------- */
.btn-sweep:hover::before,.btn-sweep:focus-visible::before,
.q-resend:hover::before,.q-resend:focus-visible::before,
.q-call:hover::before,.q-call:focus-visible::before,
.pbar-cta.btn--solid:hover::before,.pbar-cta.btn--solid:focus-visible::before,
.pmenu-cta.btn--solid:hover::before,.pmenu-cta.btn--solid:focus-visible::before,
.q-submit.btn--solid:hover::before,.q-submit.btn--solid:focus-visible::before{ background:var(--c-accent-hover); }
.btn-sweep:active::before,.q-resend:active::before,.q-call:active::before,
.pbar-cta.btn--solid:active::before,.pmenu-cta.btn--solid:active::before,
.q-submit.btn--solid:active::before{ background:var(--c-accent-press); }

/* ---------- 180ms colour crossfade ----------
   Declared once, statically, and never added or removed: swapping a transition
   onto and off an element around the switch cancelled the running var-derived
   colour animation and left the element painting its start value (a black label
   on the black CTA, stale meta grey on the toggle). A rule that is always in
   force cannot do that.
   Zero specificity and no !important, so any element that declares its own
   transition keeps it and fades on its own terms - this only reaches elements
   that had none. Only the three colour properties are listed.
   The duration is a custom property so it can be zeroed by inheritance rather
   than by editing the transition itself: a cross-document View Transition
   already crossfades the whole page, and running the colour fade underneath it
   animates the change twice. */
:root{ --theme-fade:180ms; }
*,*::before,*::after{ transition:background-color var(--theme-fade) ease,color var(--theme-fade) ease,border-color var(--theme-fade) ease; }
html:active-view-transition,html:active-view-transition *{ --theme-fade:0s; }
@media (prefers-reduced-motion:reduce){ :root{ --theme-fade:0s; } }

/* ---------- media in dark ----------
   The portfolio renders are lit on white and glare against #0E0E0E: a hairline
   in the rules colour gives every frame an edge, and 0.92 brightness takes the
   worst of the glare off. box-sizing is border-box site-wide, so the hairline
   costs no layout. Light theme gets neither. */
[data-theme="dark"] img,[data-theme="dark"] video{ border:1px solid var(--c-rule); filter:brightness(0.92); }


/* ============================================================
   SIGNATURE WIPE - the clipped label clone
   RE-AUTHORED. These rules lived only in the generated bundle and were lost
   when site.css was rebuilt from its sources; they are here now so a rebuild
   can never drop them again. smooth.js (and the inline script on 404) gives
   every button in the wipe family .btn-wipe plus one aria-hidden copy of its
   own contents. The copy sits exactly over the original, painted in the ink
   that reads ON the fill, and is clipped to the fill's advancing edge - so the
   label changes colour with the sweep instead of all at once. Same duration
   and curve as the fill (--t-fill / --ix-ease), so the two edges travel as one.
   Geometry is inherited, never restated: an absolutely-positioned child is laid
   out against the parent's PADDING box, so the clone takes the parent's padding
   and no border and its text lands on the original's baseline to the pixel.
   Written as a CHILD selector: the wipe family's own `.pbar-cta.btn--solid > *`
   rule is two classes strong and would otherwise put the clone back in flow.
   ============================================================ */
.btn-wipe{ position:relative; }
.btn-wipe > .btn-wipe-copy{ position:absolute; inset:0; z-index:3; padding:inherit; border:0;
  display:inherit; align-items:inherit; justify-content:inherit; gap:inherit;
  font:inherit; letter-spacing:inherit; text-transform:inherit; white-space:nowrap;
  pointer-events:none; color:var(--accent-ink);
  clip-path:inset(0 100% 0 0); transition:clip-path var(--t-fill) var(--ix-ease); }
.btn-wipe:hover > .btn-wipe-copy,.btn-wipe:focus-visible > .btn-wipe-copy{ clip-path:inset(0 0 0 0); }
/* the arrow inside the clone rides the same nudge as the real one */
.btn-wipe:hover > .btn-wipe-copy .btn-arw,.btn-wipe:focus-visible > .btn-wipe-copy .btn-arw{ transform:translateX(6px); }
@media (prefers-reduced-motion:reduce){
  .btn-wipe > .btn-wipe-copy{ transition:none; }
  .btn-wipe:hover > .btn-wipe-copy .btn-arw,.btn-wipe:focus-visible > .btn-wipe-copy .btn-arw{ transform:none; }
}

/* ---------- tap acknowledgement while casestudy.js is still in flight ----------
   RE-AUTHORED, same reason as the block above. v27.js dims the tapped tile and
   runs a 3px accent bar across the top of the viewport until openCaseRoute exists. */
.band[data-project].is-opening{ opacity:0.72; }
.cs-loadbar{ position:fixed; top:0; left:0; right:0; z-index:250; height:3px; overflow:hidden; pointer-events:none; }
.cs-loadbar span{ display:block; width:40%; height:100%; background:var(--accent);
  animation:csLoadRun 1.1s var(--ix-ease) infinite; }
@keyframes csLoadRun{ from{ transform:translateX(-110%); } to{ transform:translateX(360%); } }
@media (prefers-reduced-motion:reduce){ .cs-loadbar span{ animation:none; width:100%; opacity:0.6; } }

/* ---------- film player: full screen ----------
   RE-AUTHORED. .is-fs is set for every route into full screen (element API, the
   iOS native player, and the CSS fallback) and only swaps the button's glyph.
   .is-fs-css is the fallback itself: no Fullscreen API available, so the frame
   is pinned to the viewport instead. Escape and the button both leave it. */
.cs-vp .cs-vp-i-fsx{ display:none; }
.cs-vp.is-fs .cs-vp-i-fs{ display:none; }
.cs-vp.is-fs .cs-vp-i-fsx{ display:block; }
.cs-vp.is-fs-css{ position:fixed; inset:0; z-index:240; width:100%; height:100%;
  margin:0; max-width:none; background:var(--media-dark); display:flex; align-items:center; justify-content:center; }
.cs-vp.is-fs-css .cs-vp-frame{ width:100%; height:100%; aspect-ratio:auto; }
.cs-vp.is-fs-css .cs-vp-video,.cs-vp.is-fs-css .cs-vp-poster{ object-fit:contain; }

/* ---------- ?noblur=1 diagnostic ----------
   RE-AUTHORED. The head script adds .no-blur; it strips every live blur so a
   paint or compositing problem can be told apart from the backdrop filters. */
html.no-blur *,html.no-blur *::before,html.no-blur *::after{
  -webkit-backdrop-filter:none !important; backdrop-filter:none !important; }


/* ============================================================
   MOBILE HERO - TALL PORTRAIT FRAME, INSET ON THE PAGE GUTTER
   Desktop is untouched: every declaration here lives inside max-width:760px,
   and --hero-h, the 2/1 ratio and the width derivation are not referenced.

   There was no earlier mobile hero rule to repair - the base rule was simply
   the only one in play at every width, so the phone inherited the desktop's
   2:1 card at 350px wide and rendered a 177px strip. This block is the first
   mobile-specific sizing the hero has had.

   The full-bleed pass is REVERTED. It worked by zeroing the hero's own
   horizontal padding, so the card sat edge to edge; the padding is now
   var(--gutter), the same value .band and the sections below the hero use, so
   the hero's left and right edges line up with the page instead of standing
   on a margin of their own. The base rule's inset is var(--frame), 6px wider
   than the gutter at phone widths, which is why the width is still 100% -
   100% of the padded content box, not of the viewport. There is no 100vw and
   no negative margin anywhere in this block, so nothing can overflow.
   The ratio drives the height; max-height is only a ceiling for short
   viewports, and at every phone size in use the ratio wins.

   The source is 2:1 and the frame is 4:5, so ~40% of the frame width survives.
   object-fit stays cover and the sides crop, deliberately. object-position is
   set on the video AND on its poster background so the still and the first
   played frame land on the same crop - a mismatch there flashes on load.
   ============================================================ */
@media (max-width:760px){
  .hero{ padding-left:var(--gutter); padding-right:var(--gutter); }
  body.framed .hero-card,.hero-card{
    width:100%; max-width:100%;
    aspect-ratio:4/5; height:auto; max-height:78svh;
  }
  /* the desktop card is 2/1, matching the source, so it never crops. A 4/5 box
     crops horizontally instead, deliberately: the video fills the card and
     object-position picks the surviving band. */
  .hero-video{
    height:100%; margin-top:0;
    /* 50% horizontally, measured rather than guessed. The reel's racket montage
       is three vertical panels; sampling column brightness at t=8.2s puts them
       at 5.2-30.0%, 36.7-62.3% and 72.3% onward of source width. The 4:5 card
       shows 40% of a 2:1 source, so the window has to sit inside the 42.3%
       clean band between panel 1 and panel 3 - it just fits, with 2.3% to
       spare. 50% lands the window on 30.0-70.0%: panel 1 excluded at its exact
       edge, panel 3 clear by 2.3%, and the centre panel (centre 49.5%) centred
       to within half a percent, with 6.7% of background either side of it.
       42% caught panel 1, 54% and the previous 58% caught panel 3.
       The cost is the single-racket scene at t=6.5s, whose grip now reaches the
       bottom-right corner with the butt cap just touching the edge; excluding
       both side panels in the montage was the priority.
       Vertical stays 50%: the source loses nothing vertically here. */
    object-position:50% 50%;
    background-position:50% 50%;
  }
}

/* ============================================================
   THEME TOGGLE - HOME, ROTATED IN THE HERO'S RIGHT MARGIN
   Restored. The hero card is height-constrained (its width follows from
   --hero-h), so on a wide screen it leaves an empty strip either side. The
   toggle sits in the right strip, rotated a quarter turn and reading top to
   bottom, with its bottom edge on the hero card's bottom edge.

   It CANNOT affect layout, by construction:
   - the button is a direct child of <body>, not of .pbar - no in-flow ancestor
     carries it, so no parent box can be widened by it and the bar's flex row
     never has to find room for it at any width;
   - position:fixed, so it is out of flow entirely, and fixed is also what keeps
     it pinned at the top of the viewport while the page scrolls;
   - the shell is a fixed 44 x 64 box and the rotation lives on .tt-rot INSIDE
     it: .tt-rot is authored 64 wide x 44 tall, so the quarter turn maps it onto
     the shell exactly and the label has its length along the shell's height
     (the old 44 x 44 shell clipped a five-character label);
   - no margin or padding anywhere on it.
   Being outside .pbar also means the offsets resolve against the real viewport:
   the bar's backdrop-filter makes it the containing block for fixed descendants
   whenever the button lives inside it.

   z-index 9 - above page content, and below every layer that can cover it: the
   burger panel (55), the case-study overlay (200) and the quote dialog (210).
   Nothing has to be hidden or re-stacked for it. It is only ever visible at
   901px and up, where the bar's right cluster is nowhere near it, so it cannot
   collide with the bar either; at 900px and below the bar's own in-flow copy
   takes over and this one stands down.
   ============================================================ */
.theme-toggle--rot{ display:none; position:absolute; z-index:9; width:44px; height:64px;
  min-width:0; min-height:44px; margin:0; padding:0; overflow:clip; place-items:center; }
.theme-toggle--rot .tt-rot{ display:grid; place-items:center; width:64px; height:44px; }
@media (min-width:901px){
  /* In the WHITE MARGIN beside the card, never over the video. The button is a
     child of .hero (position:relative), NOT of .hero-card - the card carries
     overflow:hidden, so a child of the card positioned outside it would simply
     be clipped away. Still absolute, so it still scrolls off with the hero.

     The margin it sits in is --frame + --rail wide on each side, because that
     is exactly what the card's own width formula subtracts. Centring the 44px
     box in it is therefore (frame + rail - 44) / 2 measured from the viewport
     edge - the same 33.84px it had at 1440 when it was pinned, arrived at from
     the margin instead of from the card's width.
     Narrowest case above the 901px breakpoint: rail is 48px from 900 to 1199,
     so the margin bottoms out at 68px at 901px wide, still 24px more than the
     button needs. It cannot collide at any width where it is displayed.

     Vertically it lines up with the card's bottom edge: .hero's bottom padding
     is frame-gap + frame + 34px, so that distance up from .hero's bottom IS
     the card's lower edge. */
  body.framed:not(.subpage) .theme-toggle--rot{
    display:grid;
    bottom:calc(var(--frame-gap) + var(--frame) + 34px);
    right:calc((var(--frame) + var(--rail,0px) - 44px) / 2);
  }
  body.framed:not(.subpage) .theme-toggle--rot .tt-rot{ transform:rotate(90deg); transform-origin:center; }
  /* one toggle visible at a time: the bar's copy stands down on the home page
     exactly where the hero-margin one appears */
  body.framed:not(.subpage) .pbar-r .theme-toggle{ display:none; }
}

/* ============================================================
   THEME TOGGLE - IN THE TOP BAR, ON EVERY PAGE
   The bar is position:sticky, so the bar pins the toggle and it never scrolls
   away. The toggle itself is a plain flex child: no position:fixed, so it
   creates no stacking or containing block, nothing for the custom cursor or the
   rv reveals to resolve against, and it cannot be caught by the iOS "fixed
   inside a transformed ancestor" trap - the bar's backdrop-filter only breaks
   position:fixed DESCENDANTS, and there are none now.
   Layers: the bar is z-index 100, the quote dialog 210 and the case-study
   overlay 200, so both of those already cover it. The burger panel is 55, the
   one layer the bar paints over, so the toggle is hidden while that panel is
   open rather than left floating on top of it.
   ============================================================ */
html.menu-open .theme-toggle,
html.menu-open body.framed:not(.subpage) .theme-toggle--rot{ display:none; }

/* ============================================================
   CASE-STUDY OVERLAY IN DARK THEME
   The overlay's palette is declared as literal light values in each page's own
   <style> block, which loads after the bundle and wins on source order at equal
   specificity - so in dark theme the overlay kept --ink:#0E0E0E and its
   headings and close cross went black on a black panel. Re-pointing the same
   token names at the theme tokens, at [data-theme] strength so it outranks that
   block, corrects every piece of overlay chrome at once. Light is untouched by
   construction: the rule only exists under [data-theme="dark"].
   ============================================================ */
[data-theme="dark"] .cs-root,
[data-theme="dark"] .cs-root.cs-dark,
[data-theme="dark"] .cs-root.cs-crimson,
[data-theme="dark"] .cs-root.cs-cobalt,
[data-theme="dark"] .cs-root.cs-graphite,
[data-theme="dark"] .cs-root.cs-wip-page{
  --bg:var(--c-bg); --surface:var(--panel-2); --surface-2:var(--panel-3);
  --card-dark:var(--media-dark);
  --ink:var(--c-head); --muted:var(--c-body); --faint:var(--c-meta);
  --line:var(--c-rule); --line-2:color-mix(in srgb,var(--c-head) 22%,var(--c-bg));
}

/* guard, not a cure: with the toggle out of flow nothing here should overflow
   sideways, and the review marquee already clips its own overrun. This keeps a
   future stray wide child from turning into a horizontal scrollbar. Clip, not
   hidden - clip creates no scroll container, so the sticky bar and the smooth
   scroll are untouched. */
body{ overflow-x:clip; }

/* ============================================================
   BURGER PANEL - RIGHT-ALIGNED LIST (thumb reach)
   The list moves to the right edge because that is where a thumb rests on a
   phone; the left column was the least reachable corner of the screen.
   Horizontal only - the vertical rhythm, the sizes, the 56px tap boxes and the
   12px padding that grows them are all left exactly as they were.
   Mechanism mirrors the left-aligned version rather than replacing it:
   align-self flips flex-start to flex-end, and the -12px pull moves from the
   left margin to the right, so the padded tap box still overhangs the gutter
   by its own padding and the TEXT lands flush on the gutter. The underline is
   already inset left:12px right:12px, which is symmetric, so it keeps spanning
   the word exactly with no overhang on either side and needs no change.
   The tap boxes are shrink-to-fit already, so moving them costs no hit area.
   Not touched here: the panel's own padding (0 var(--frame) on both sides, so
   the new right gutter equals the old left one), the CTA's size, the header
   row (the AVREA mark and the burger live in .pbar, a separate element that
   this panel never contained), and everything above 720px, where the panel is
   display:none. ============================================================ */
.pmenu-links a,.pmenu-soc a{ align-self:flex-end; margin-left:0; margin-right:-12px; justify-content:flex-end; text-align:right; }
.pmenu-cta{ align-self:flex-end; }

/* ============================================================
   TOP BAR WHILE THE BURGER MENU IS OPEN
   .pbar-menu is position:fixed inset:0 at z-index 55; .pbar is sticky at
   z-index 100. The panel therefore already runs the full height of the
   viewport BEHIND the bar - the bar is not sitting beside the frosted surface,
   it is sitting on top of it. Giving the bar its own blur here would put two
   backdrop-filters over the same pixels: the bar would blur the panel, which
   has already blurred the page, and a twice-blurred backdrop reads heavier and
   murkier than either pass alone, which is the opposite of one clean surface.
   So the bar steps out of the way instead: while open it goes fully
   transparent and lets the panel's own blur show through it. One filter, one
   surface, and the bar's contents keep reading against exactly the same
   backdrop as the nav links below them.
   blur(0px) rather than none on purpose - a non-none backdrop-filter creates a
   containing block, and .pbar has carried one at all times since it got its
   closed-state blur. Dropping to none would remove it mid-animation and change
   how any fixed descendant resolves; blur(0px) keeps that behaviour identical
   and is interpolatable, so the filter fades out instead of snapping.
   No flash: both properties transition over --t-fast with --ix-ease, the exact
   duration and curve of the panel's own opacity fade, so the bar's frost
   dissolves at the same rate the panel's frost arrives and the strip is never
   momentarily unglazed. It reverses on close by the same transition.
   Closed state untouched - every declaration here is gated on html.menu-open,
   which burger.js sets only while the panel is open, and on the mobile query,
   where the burger exists at all. ============================================================ */
@media (max-width:720px){
  html.menu-open .pbar{ background:transparent; -webkit-backdrop-filter:blur(0px); backdrop-filter:blur(0px); }
}
/* Additive on purpose. The global crossfade rule above already transitions
   background-color, color and border-color on every element at --theme-fade,
   and .pbar has always been included in it - so the bar's tint already fades
   rather than snaps, and that fade carries the open-state change for free.
   Restating those three verbatim keeps them intact; a shorthand listing only
   the filter would have replaced them and made the bar's text and border snap
   on every theme switch. Only the backdrop-filter fade is new. */
.pbar{ transition:background-color var(--theme-fade) ease, color var(--theme-fade) ease, border-color var(--theme-fade) ease, -webkit-backdrop-filter var(--t-fast) var(--ix-ease), backdrop-filter var(--t-fast) var(--ix-ease); }
/* No support for backdrop-filter: the panel falls back to solid --c-bg, so the
   bar must too - a transparent strip over a solid panel would still read as
   one surface, but only by accident of them sharing a colour. Stated outright
   here so the two can never fall back to different things. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  @media (max-width:720px){ html.menu-open .pbar{ background:var(--c-bg); } }
}
