/* Verifying a versioned asset too soon after a deploy poisons the edge: the
   request is served by the previous deployment and cached under the NEW ?v=
   URL for the full immutable TTL. Wait for propagation before the first fetch,
   or the only cure is another content change and a new hash. */

/* style.css — the home page and 404. Builds on css/brand.css, which is linked
   first and owns every colour, type, space and motion token. Nothing here
   redefines a brand token: qa-check.py fails the build if either site sheet
   does, and compares the site-only block below across both sheets.

   Rules of the identity this sheet holds to: square corners, no shadows,
   Archivo 118% uppercase for headlines, Hanken for reading, Geist Mono only for data, imagery warm-monochrome
   at rest, and Signal once per screen — the primary action, or the square in
   the symbol. Signal is never text: it measures 2.88:1 on Stone. */

/* ---- site tokens · not brand tokens; mirrored in css/doc.css ----
   Declared on every theme scope, not only :root, so they re-resolve inside the
   hero, which pins itself to dark with data-theme. A var() on :root alone is
   computed once there and inherited as a fixed value. */
:root,[data-theme]{
  --veil-strong:rgba(238,235,229,.97);
}
[data-theme="dark"]{
  --veil-strong:rgba(18,21,19,.97);
}
:root{--max:1200px}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}
  *{animation-duration:.01ms!important;transition-duration:.01ms!important}}
body{margin:0;background:var(--bg);color:var(--tx);font-family:var(--font-body);
  font-weight:400;font-size:var(--fs-body);line-height:var(--lh-body);
  -webkit-font-smoothing:antialiased;overflow-x:hidden}
img{max-width:100%;height:auto;display:block}
a{color:inherit;text-decoration:none}
/* Signal is 2.88:1 on Stone, under the 3:1 a focus ring needs; ink is 14:1. */
:focus-visible{outline:2px solid var(--tx);outline-offset:3px}
.wrap{max-width:var(--max);margin-inline:auto;padding-inline:var(--gut)}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* Ink on an accent background is --on-accent, never a literal (qa-check). */
.skip{position:absolute;left:-9999px;top:0;background:var(--accent);color:var(--on-accent);padding:.75rem 1.25rem;z-index:200;
  font-weight:600;font-size:.75rem;letter-spacing:.14em;text-transform:uppercase}
.skip:focus{left:0}

/* ---------- nav ---------- */
.nav{position:fixed;inset:0 0 auto;z-index:100;color:var(--tx);border-bottom:1px solid transparent;
  transition:background var(--dur-3),border-color var(--dur-3),color var(--dur-3)}
.nav.solid{background:var(--veil);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border-bottom-color:var(--line)}
/* The bar is chrome, not content: one width across the site whatever measure
   the page below keeps, or it slides sideways between pages. */
.nav .wrap{max-width:var(--max);display:flex;align-items:center;justify-content:space-between;gap:1.5rem;padding-block:1.15rem}
.nav ul{display:flex;gap:clamp(.9rem,1.5vw,1.6rem);list-style:none;margin:0 0 0 auto;padding:0;align-items:center}
.nav ul a{white-space:nowrap;font-family:var(--font-label);font-weight:600;font-size:.6875rem;letter-spacing:var(--ls-label);text-transform:uppercase;
  color:var(--tx2);position:relative;padding-block:.35rem;transition:color var(--dur-2)}
.nav ul a::after{content:'';position:absolute;left:0;bottom:0;height:1.5px;width:0;background:currentColor;transition:width var(--dur-3) var(--ease)}
.nav ul a:hover,.nav ul a[aria-current="true"]{color:var(--tx)}
.nav ul a:hover::after,.nav ul a[aria-current="true"]::after{width:100%}
/* The résumé download is an action, not a destination, so it reads as a button. */
.nav ul a.dl{color:var(--tx);border:1.5px solid currentColor;padding:.5rem .85rem}
.nav ul a.dl:hover{background:var(--tx);color:var(--bg)}
.nav ul a.dl::after{display:none}

/* the lockup — supplied artwork, inlined by nav.py; ink follows the theme */
.brand{display:block;flex:none;line-height:0;color:var(--tx)}
.brand .lockup{display:block;height:38px;width:auto}
.brand .ink,.brand .sym path{fill:currentColor}
.brand .c{fill:var(--signal)}
/* The lock-in: the four corners close on the Signal, once per visit, home only.
   The class is set before paint by the head snippet in build-html.py. The
   corners move 14.6 units in the symbol's own 0-100 space (nav.py draws it
   there), so the motion reads the same at any size. */
.lockin .brand .tl{animation:sps-in-tl var(--dur-4) var(--ease) both}
.lockin .brand .tr{animation:sps-in-tr var(--dur-4) var(--ease) both}
.lockin .brand .br{animation:sps-in-br var(--dur-4) var(--ease) both}
.lockin .brand .bl{animation:sps-in-bl var(--dur-4) var(--ease) both}
.lockin .brand .c{animation:sps-in-c var(--dur-3) var(--ease) var(--dur-3) both}
.brand .c{transform-box:fill-box;transform-origin:center}
@media (prefers-reduced-motion:reduce){.lockin .brand *{animation:none!important}}

.burger{display:none;background:none;border:0;color:inherit;padding:.5rem;cursor:pointer;margin-left:auto}

/* ---- theme control ---- */
.themebar{display:flex;border:1.5px solid var(--line-strong);flex:none}
.themebar button{appearance:none;background:none;border:0;cursor:pointer;font-family:var(--font-label);
  font-weight:600;font-size:.625rem;letter-spacing:var(--ls-label);text-transform:uppercase;color:var(--tx2);
  padding:.5rem .7rem;line-height:1;transition:color var(--dur-2),background var(--dur-2)}
.themebar button:hover{color:var(--tx)}
.themebar button[aria-pressed="true"]{color:var(--bg);background:var(--tx)}
@media(max-width:1199px){.themebar{display:none}}

/* The bar floats transparent over the hero until it solidifies on scroll. The
   hero is Night in both themes, so until then the bar is mist on Night. */
.nav:not(.solid):not(.menu-open){color:var(--mist);--tx:var(--mist);--tx2:var(--mist-2);--bg:var(--night);--line-strong:#3D4540}

@media screen and (max-width:1199px){
  .burger{display:block}
  .nav .wrap{gap:.5rem}
  .nav ul{position:fixed;inset:0;flex-direction:column;justify-content:center;align-items:center;gap:1.75rem;margin:0;
    background:var(--veil-strong);transform:translateY(-100%);transition:transform var(--dur-4) var(--ease)}
  .nav ul[data-open="true"]{transform:none;cursor:zoom-out}
  .nav ul a{font-size:.9375rem}
  /* The blur is off entirely on a phone, not merely reset when the menu opens.
     WebKit keeps a transitioned backdrop-filter as a containing block for
     position:fixed children even when it computes to none — so the overlay
     sized itself to the bar and the links spilled over the page. A solid bar
     reads the same on a small screen. */
  .nav,.nav.solid,.nav.menu-open,.nav.menu-open.solid{
    backdrop-filter:none;-webkit-backdrop-filter:none;transition:background var(--dur-3),border-color var(--dur-3)}
  .nav.solid{background:var(--veil-strong)}
  .nav.menu-open,.nav.menu-open.solid{background:transparent;border-bottom-color:transparent}
  .nav ul[data-open="true"] a{color:var(--tx)}

  /* ---- tap targets ----
     WCAG 2.5.8 asks for 24px on both axes; text links inside prose measured
     16-22px tall. Padding, not bigger type. qa-mobile.py holds the floor. */
  a.more,a.live,a.crumb,a.read,a.dl,.meta a,.contact .lines a,.sps-doc .a a,
  .lines a,.foot a,footer a,.cdetail a{display:inline-block;min-height:24px;padding-block:.28rem}
  .nav .brand{min-height:40px;display:flex;align-items:center}
  .nav ul a{min-height:44px;display:flex;align-items:center;padding-inline:.6rem}
  .themebar button{min-height:40px;min-width:40px;padding-inline:.9rem}
  /* the control is hidden in the bar on small screens, so surface it inside
     the open menu — otherwise a phone cannot change theme at all */
  .nav.menu-open .themebar{display:flex;position:fixed;left:50%;bottom:3rem;transform:translateX(-50%);z-index:1;
    --tx:var(--ink);--bg:var(--stone)}
  [data-theme="dark"] .nav.menu-open .themebar{--tx:var(--mist);--bg:var(--night)}
}
/* The lockup is two compact lines, 36-40px tall and never under 32px, which
   keeps STUDIO's capitals at 7.5px or more (the floor is 6). At 32px it is
   225px wide, which still fits a 320px phone beside the menu button. */
@media (max-width:1199px){.brand .lockup{height:32px}}

/* ---------- hero ----------
   Night in both themes: the markup pins data-theme="dark" on it, so every
   token inside resolves to the dark set. Warm-monochrome photograph on the
   right 61.8%, copy on the left, one Signal — the primary action. */
.hero{position:relative;min-height:clamp(600px,92svh,900px);display:flex;align-items:center;overflow:hidden;isolation:isolate;
  background:var(--night);color:var(--tx)}
.hero-media{position:absolute;inset:0 0 0 auto;width:var(--grid-major);z-index:-1}
.hero-media picture,.hero-media img{display:block;width:100%;height:100%}
.hero-media img{object-fit:cover;object-position:30% 35%;filter:grayscale(1) sepia(.08) brightness(.82)}
.hero-media::after{content:'';position:absolute;inset:0;
  background:linear-gradient(90deg,var(--night) 0%,rgba(18,21,19,.6) 26%,rgba(18,21,19,0) 58%),
             linear-gradient(0deg,rgba(18,21,19,.6) 0%,rgba(18,21,19,0) 30%)}
.hero-in{width:100%;padding-block:clamp(7rem,14vh,9rem) clamp(4rem,9vh,6rem)}
.hero-copy{max-width:min(40rem,60%)}
.hero-label{margin:0 0 var(--s4);font-family:var(--font-mono);font-size:.6875rem;letter-spacing:var(--ls-label);
  text-transform:uppercase;color:var(--tx2)}
.hero h1{margin:0;font-family:var(--font-display);font-stretch:var(--stretch-display);font-weight:var(--fw-display);
  font-size:clamp(2.125rem,4.4vw,3.75rem);line-height:.96;letter-spacing:var(--ls-display);text-transform:uppercase;text-wrap:balance}
.hero-by{margin:var(--s4) 0 0;color:var(--tx2);font-size:var(--fs-lead);line-height:1.5;max-width:30ch}
.hero-stat{margin:var(--s3) 0 0;font-family:var(--font-mono);font-size:.6875rem;letter-spacing:var(--ls-mono);
  text-transform:uppercase;color:var(--tx3)}
.hero-cta{display:flex;flex-wrap:wrap;gap:var(--s3);margin:var(--s5) 0 0}
@media(max-width:820px){
  .hero{align-items:flex-end;min-height:100svh}
  .hero-media{width:100%}
  .hero-media img{object-position:35% 20%}
  .hero-media::after{background:linear-gradient(180deg,rgba(18,21,19,.45) 0%,rgba(18,21,19,.55) 35%,var(--night) 72%)}
  .hero-copy{max-width:100%}
  .hero-in{padding-block:40svh var(--s6)}
}
@media(max-width:520px){.hero-cta{flex-direction:column;align-items:stretch}.hero-cta .sps-btn{width:100%}}

/* ---------- sections ---------- */
section[id]{scroll-margin-top:70px}
.sec{padding-block:var(--sec-y)}
.sec.alt{background:var(--surface)}
.sps-sechead p.lede{font-size:1.0625rem}
.sec-foot{display:flex;flex-wrap:wrap;gap:var(--s3);margin:var(--s5) 0 0}

/* story — 61.8 / 38.2 */
.story{display:grid;gap:var(--s6);align-items:center;grid-template-columns:1fr}
@media(min-width:900px){.story{grid-template-columns:var(--grid-major) 1fr}}
.story blockquote{margin:var(--s4) 0 0;font-family:var(--font-display);font-stretch:var(--stretch-display);font-weight:700;
  font-size:var(--fs-h2);line-height:var(--lh-heading);letter-spacing:var(--ls-heading);text-transform:uppercase}
.story .body{color:var(--tx2);margin:var(--s5) 0 0;max-width:var(--measure)}
.story-img{overflow:hidden}
.story-img img{width:100%;aspect-ratio:4/5;object-fit:cover;filter:grayscale(1) sepia(.08);
  transition:filter var(--dur-4),transform var(--dur-4) var(--ease)}
.story-img:hover img{filter:none}

/* systems — project cards at the golden ratio */
.sgrid{display:grid;gap:var(--s4);grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr))}
.sps-project picture{display:block}
.sps-project img{aspect-ratio:1.618/1;filter:grayscale(1) sepia(.08)}
.sps-project:hover img{filter:none}

/* documents: a download is not a hero */
.docs{display:grid;gap:var(--s4);grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));max-width:760px}
/* Two real links per card: the title area opens the page (its ::after covers
   the whole card, so the card is one big target), the PDF sits above it on its
   own. Nested <a> would be invalid, hence the overlay rather than a wrapper. */
.sps-doc{position:relative}
.sps-doc picture{display:block;height:100%}
.sps-doc .a a.read::after{content:'';position:absolute;inset:0}
.sps-doc .a a:not(.read){position:relative;z-index:1}
.sps-doc:hover .a a.read{color:var(--tx)}
.sps-doc .kind{font-family:var(--font-mono);font-size:.625rem;letter-spacing:var(--ls-mono);text-transform:uppercase;color:var(--tx3)}

/* films */
.vgrid{display:grid;gap:var(--s4);grid-template-columns:repeat(auto-fill,minmax(min(100%,320px),1fr))}
.vgrid .feature{grid-column:1/-1}
@media(min-width:760px){.vgrid .feature{grid-column:span 2}}
.sps-film picture{display:block;height:100%}
/* five films lead; the rest are one click away, and all ten show without JS */
.js .more-film{display:none}
.js .vgrid.show-all .more-film{display:block}

/* stills */
.pgrid{columns:1;column-gap:var(--s4)}
@media(min-width:640px){.pgrid{columns:2}}
@media(min-width:1024px){.pgrid{columns:3}}
.pgrid button{break-inside:avoid;margin:0 0 var(--s4);display:block;width:100%;
  padding:0;border:0;background:none;cursor:pointer;overflow:hidden;position:relative}
.pgrid img{width:100%;filter:grayscale(1) sepia(.08);transition:transform var(--dur-4) var(--ease),filter var(--dur-4)}
.pgrid button:hover img,.pgrid button:focus-visible img{transform:scale(1.03);filter:none}
.js .more-still{display:none}
.js .pgrid.show-all .more-still{display:block}

/* clients */
.sps-clients img{max-height:44px}
[data-theme="dark"] .sps-clients img{filter:grayscale(1) brightness(2.4) contrast(.5)}

/* contact — 38.2 / 61.8 */
.contact{background:var(--bg)}
.contact-inner{display:grid;gap:var(--s6);grid-template-columns:1fr}
@media(min-width:900px){.contact-inner{grid-template-columns:var(--grid-minor) 1fr}}
.cdetail{display:grid;gap:var(--s4);align-content:start}
.cdetail .sps-sechead{margin:0}
.cdetail p{margin:0;color:var(--tx2)}
.cdetail a{color:var(--tx);border-bottom:1.5px solid var(--line-strong);transition:border-color var(--dur-2)}
.cdetail a:hover{border-color:var(--tx)}
form{display:grid;gap:var(--s5)}
.row{display:grid;gap:var(--s5)}
@media(min-width:560px){.row{grid-template-columns:1fr 1fr}}
.hp{position:absolute;left:-9999px;opacity:0;pointer-events:none;min-height:44px}
form .sps-btn{justify-self:start}
/* The status message is display:none until a submit, so no static contrast
   sweep ever sees it. Ok-text and danger are the brand's own AA text pairs:
   5.2 and 5.0 on Stone; ok-dark and danger-dark 9.9 and 8.5 on Night. */
.form-msg{display:none;font-size:.9375rem;padding:.9rem 1rem;line-height:1.5;border-left:3px solid;margin:0}
.form-msg.show{display:block}
.form-msg.ok{background:rgba(46,125,82,.08);color:var(--ok-text);border-color:var(--ok)}
.form-msg.err{background:rgba(178,58,34,.07);color:var(--danger);border-color:var(--danger)}
[data-theme="dark"] .form-msg.ok{color:var(--ok-dark);border-color:var(--ok-dark)}
[data-theme="dark"] .form-msg.err{color:var(--danger-dark);border-color:var(--danger-dark)}
.form-msg a{color:inherit;text-decoration:underline;text-underline-offset:3px}
.form-msg .addr{font-family:var(--font-mono);white-space:nowrap}

/* the signature closes the page, in the hand it was drawn in */
.sign{display:grid;gap:var(--s2);margin:var(--s4) 0 0;justify-items:start}
.sign img{width:180px;height:auto}
.sign .ink-dark{display:none}
[data-theme="dark"] .sign .ink-light{display:none}
[data-theme="dark"] .sign .ink-dark{display:block}
.sign span{font-family:var(--font-hand);font-size:1.05rem;color:var(--tx3);line-height:1.3}

/* footer — the symbol and the legal line */
footer{border-top:1px solid var(--line);padding-block:var(--s5);background:var(--bg)}
footer .wrap{display:flex;flex-wrap:wrap;gap:var(--s4);justify-content:space-between;align-items:center}
footer .symbol{width:34px;height:34px;display:block}
footer .symbol path{fill:currentColor}
footer .symbol .c{fill:var(--signal)}
footer p{margin:0;font-size:.8125rem;color:var(--tx2)}

/* lightbox — sits over photography, so Night in both themes */
.lb{position:fixed;inset:0;z-index:300;background:rgba(13,15,14,.97);color:var(--mist);
  display:grid;place-items:center;padding:clamp(1rem,4vw,3rem);opacity:0;visibility:hidden;
  transition:opacity var(--dur-3),visibility var(--dur-3)}
.lb[data-open="true"]{opacity:1;visibility:visible}
.lb-body{width:min(1240px,100%);max-height:88svh;position:relative}
.lb-body img{max-height:88svh;width:auto;margin-inline:auto;object-fit:contain}
.lb-body .ratio{position:relative;width:100%;aspect-ratio:16/9;background:#0D0F0E}
.lb-body .ratio iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.lb-cap{margin-top:1rem;text-align:center;font-family:var(--font-mono);font-size:.6875rem;letter-spacing:var(--ls-mono);
  text-transform:uppercase;color:var(--mist-2)}
.lb button{position:absolute;background:none;border:0;color:var(--mist);cursor:pointer;padding:.75rem;
  font-size:1.6rem;line-height:1;transition:color var(--dur-2)}
.lb button:hover{color:#fff}
/* Close is pinned to the overlay, not above the image: on a phone it used to
   sit outside the viewport, so the lightbox could not be dismissed. */
.lb-x{position:fixed;top:max(.75rem,env(safe-area-inset-top));right:.75rem;
  min-width:2.75rem;min-height:2.75rem;display:grid;place-items:center;
  background:rgba(13,15,14,.72);border:1px solid var(--night-line);z-index:2}
.lb-p{top:50%;left:-3.5rem;transform:translateY(-50%)}
.lb-n{top:50%;right:-3.5rem;transform:translateY(-50%)}
@media(max-width:1100px){
  .lb-p{left:-.5rem}.lb-n{right:-.5rem}
  .lb-p,.lb-n{min-width:2.75rem;min-height:2.75rem;background:rgba(13,15,14,.6)}
}
.lb,.lb-body,.lb-cap{cursor:zoom-out}
.lb-body img,.lb-body iframe{cursor:auto}
.lb-x,.lb-p,.lb-n{cursor:pointer}

/* reveal */
.js .rv{opacity:0;transform:translateY(21px);transition:opacity var(--dur-4) var(--ease),transform var(--dur-4) var(--ease)}
.js .rv.in{opacity:1;transform:none}

/* ---- high contrast and reduced transparency ---- */
@media (prefers-reduced-transparency: reduce){
  .nav,.nav.solid{background:var(--bg);backdrop-filter:none}
  .lb{background:#0D0F0E}
}
@media (forced-colors: active){
  *{forced-color-adjust:auto}
  .nav,.nav.solid,.lb{background:Canvas;backdrop-filter:none}
  .sps-btn{border:1px solid ButtonText;background:ButtonFace;color:ButtonText}
  .themebar button[aria-pressed="true"]{background:Highlight;color:HighlightText}
  .sps-project,.sps-doc,.sps-film{border:1px solid CanvasText}
  :focus-visible{outline:3px solid Highlight;outline-offset:2px}
  .hero-media::after{display:none}
}
