/* 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. */

/* ---- brand tokens · single source; mirrored in css/doc.css ----
   qa-check fails the build if these two copies ever diverge, so this is
   duplication with a gate rather than duplication with hope. Inlined
   instead of @import because an @import is a blocking round trip on the
   critical path. */
:root{
  --bg:#0A0A0C; --surface:#121316; --raised:#17191D; --sunk:#070709;
  --line:#26292E;
  --tx:#F2F3F1; --tx2:#A9AEB4; --tx3:#7C8288;
  --gold:#D4B274;
  --green:#55AD8D;
  --on-accent:#0A0A0C;
  /* translucent chrome (sticky nav, drawers) follows the theme;
     the hero vignette and the lightbox stay dark on purpose, because
     both sit over photography. */
  --veil:rgba(10,10,12,.9); --veil-strong:rgba(10,10,12,.97);

  --disp:'Jost',ui-sans-serif,system-ui,sans-serif;
  --body:'Manrope',ui-sans-serif,system-ui,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,monospace;
  --hand:'Silverman Hand',cursive;

  --gut:clamp(1.25rem,4vw,3.5rem);
}

[data-theme="light"]{
  --bg:#F7F7F5; --surface:#FFFFFF; --raised:#FFFFFF; --sunk:#EDEEEA;
  --line:#E1E2DE;
  --tx:#17191D; --tx2:#565B62; --tx3:#666B70;
  --gold:#846626;
  --green:#1B4A3A;
  --on-accent:#FFFFFF;
  --veil:rgba(247,247,245,.9); --veil-strong:rgba(247,247,245,.97);
}

/* accent selects which family drives --accent; both are AA on every ground */
:root,[data-accent="gold"]{--accent:var(--gold)}
[data-accent="green"]{--accent:var(--green)}

/* legacy aliases so existing rules keep working while the sheets migrate */
:root{--bg-2:var(--surface); --bg-3:var(--raised);
      --fg:var(--tx); --fg-dim:var(--tx2); --fg-faint:var(--tx3);}
:root{--max:1240px}
*,*::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(--fg);font-family:var(--body);
  font-weight:300;font-size:clamp(1rem,.55vw + .9rem,1.125rem);line-height:1.7;
  -webkit-font-smoothing:antialiased;overflow-x:hidden}
img{max-width:100%;height:auto;display:block}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}
.wrap{max-width:var(--max);margin-inline:auto;padding-inline:var(--gut)}
/* Ink on an accent background is --on-accent, never a literal. The light
   themes use the *dark* accents (#846626, #1B4A3A), so #000 here was black on
   near-black: 3.91:1 and 2.09:1 — on the skip link, the first thing a keyboard
   user reaches. Every rule below that paints --accent had the same bug, and all
   of them are hover or focus states, which is why no static sweep saw them. */
.skip{position:absolute;left:-9999px;top:0;background:var(--accent);color:var(--on-accent);padding:.75rem 1.25rem;z-index:200}
.skip:focus{left:0}

/* ---------- nav ---------- */
.nav{position:fixed;inset:0 0 auto;z-index:100;transition:background .35s,backdrop-filter .35s,border-color .35s;
  border-bottom:1px solid transparent}
.nav.solid{background:var(--veil);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border-bottom-color:var(--line)}
.nav .wrap{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding-block:1.05rem}
/* The bar is chrome, not content: it keeps one width across the site while
   the pages below it keep their own deliberate measures (--max is 1240px for
   the home gallery, 1080px for documents). Without this the whole bar slid
   80px sideways as you moved between them. */
.nav .wrap{max-width:1240px}
.brand{font-family:var(--disp);font-weight:400;letter-spacing:.22em;font-size:.82rem;text-transform:uppercase}
.brand span{color:var(--accent)}
.nav ul{display:flex;gap:clamp(1rem,2.4vw,2.25rem);list-style:none;margin:0;padding:0}
.nav a{font-family:var(--disp);font-size:.74rem;letter-spacing:.17em;text-transform:uppercase;
  color:var(--fg-dim);position:relative;padding-block:.3rem;transition:color .25s}
.nav a::after{content:'';position:absolute;left:0;bottom:0;height:1px;width:0;background:var(--accent);transition:width .3s}
.nav a:hover,.nav a[aria-current="true"]{color:var(--fg)}
.nav a:hover::after,.nav a[aria-current="true"]::after{width:100%}
/* The résumé download is an action, not a destination, so it reads as a button
   on every page. Mirrors css/doc.css — the bar is shared markup, so a rule that
   lives in only one sheet makes the same element look different per page. */
.nav .dl{color:var(--accent);border:1px solid rgba(201,162,39,.4);padding:.45rem .85rem;border-radius:2px}
.nav .dl:hover{background:var(--accent);color:var(--on-accent)}
.nav .dl::after{display:none}
.burger{display:none;background:none;border:0;color:var(--fg);padding:.5rem;cursor:pointer}
@media (max-width:820px){
  .burger{display:block}
  .nav ul{position:fixed;inset:0;flex-direction:column;justify-content:center;align-items:center;gap:1.75rem;
    background:var(--veil-strong);backdrop-filter:blur(20px);transform:translateY(-100%);transition:transform .4s cubic-bezier(.4,0,.2,1)}
  .nav ul[data-open="true"]{transform:none}
  .nav a{font-size:1.05rem}
  /* backdrop-filter on .nav makes it a containing block for position:fixed
     descendants, so once the nav went solid on scroll the overlay sized itself
     to the 78px bar: the links overflowed onto the page and the background only
     painted inside the nav. Drop the filter while the menu is open and the
     overlay resolves against the viewport again. */
  /* The blur is off entirely on a phone, not merely reset when the menu opens.
     .nav declares `transition: ... backdrop-filter ...`, and WebKit keeps a
     transitioned filter property as a containing block for position:fixed
     children even when it computes to none — so the overlay sized itself to
     the 78px bar and the first four links went above the screen while Clients,
     Contact and PDF spilled down over the page. Decoration is not worth a
     class of bug this size; 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 .35s,border-color .35s}
  .nav.solid{background:var(--veil-strong)}
  .nav.menu-open,.nav.menu-open.solid{background:transparent}
  .nav ul[data-open="true"]{cursor:zoom-out}
  /* menu links follow the theme, not the over-footage pin */
  .nav ul[data-open="true"] a{color:var(--tx)}
  .nav ul[data-open="true"] a:hover{color:var(--accent)}
  /* the lockup gets tight beside the burger on a narrow screen */
  /* .brand is redeclared further down for the lockup (104x40); at equal
     specificity the later rule won and this one never applied, so phones got
     the desktop lockup in a bar sized for the small one. */
  .nav .brand{width:88px;height:34px}

  /* ---- tap targets ----
     WCAG 2.5.8 asks for 24px on both axes; these inline links measured 16-22px
     tall, and the theme controls 23-25px. They are text links inside prose, so
     the fix is padding around them rather than bigger type: the page looks the
     same and a thumb stops missing. qa-mobile.py holds the floor. */
  a.more,a.live,a.crumb,a.read,a.dl,.meta a,.contact .lines a,.doc-a a,
  .lines a,.foot a,footer a{
    display:inline-block;min-height:24px;padding-block:.28rem}
  .themebar button{min-height:40px;padding-inline:.75rem}
  .themebar .dot{width:.75rem;height:.75rem}
  .nav .brand{min-height:40px}

  /* The overlay's own links measured 34px and the accent dots 29px wide. In a
     full-screen menu there is no reason for either to be small. */
  .nav ul a{min-height:44px;display:flex;align-items:center;padding-inline:.6rem}
  .themebar button{min-width:40px}

  .nav ul a{cursor:pointer}
  /* 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}
}

/* ---------- hero ---------- */
.hero{position:relative;min-height:100svh;display:grid;place-items:center;text-align:center;overflow:hidden;isolation:isolate}
.hero-bg{position:absolute;inset:0;z-index:-2;background:#000 center/cover no-repeat}
/* The reel used to be an autoplaying iframe in the markup: several megabytes on
   the critical path, and a black rectangle until Vimeo answered. The poster is
   the background now, so the hero is composed on first paint whether or not the
   video ever loads. */
.hero-bg{background-image:url("/img/video/reel-poster-1280.webp")}
@media(max-width:700px){.hero-bg{background-image:url("/img/video/reel-poster-640.webp")}}
@media(min-width:1400px){.hero-bg{background-image:url("/img/video/reel-poster-1920.webp")}}
@media(min-width:2000px){.hero-bg{background-image:url("/img/video/reel-poster-2560.webp")}}
.hero-bg iframe{opacity:0;transition:opacity .9s ease}
.hero-bg.playing iframe{opacity:1}
.hero-bg iframe{position:absolute;top:50%;left:50%;width:100vw;height:56.25vw;min-height:100svh;min-width:177.78vh;
  transform:translate(-50%,-50%);border:0;pointer-events:none}
/* The hero sits over footage, so it is a dark surface in both themes. Fading
   the vignette to var(--bg) turned it into a dark ellipse floating on a white
   page in the light theme, with the name unreadable on top of it. The type
   tokens are pinned here for the same reason: dark ink over dark video is not
   a theme, it is a bug. */
/* Redefining the tokens is not enough: body already resolved color:var(--fg),
   and descendants inherit that computed colour rather than re-resolving it. The
   hero has to set colour itself for anything that only inherits — which was the
   whole name, invisible over the footage in the light theme. */
/* --on-accent belongs in this list and was missing. The hero pins --accent to
   the vivid variant in the light theme, because it sits over footage — but
   --on-accent kept the light theme's #FFFFFF, so the primary call to action
   rendered white on #D4B274: 2.02:1, measured off the rendered pixels, against
   the 4.5 it needs. The hero's accent is the bright one in both themes, so the
   ink on it is dark in both. */
.hero{--tx:#F2F3F1;--tx2:#A9AEB4;--tx3:#7C8288;--fg:#F2F3F1;--fg-dim:#A9AEB4;--fg-faint:#7C8288;
  --on-accent:#0A0A0C;
  color:#F2F3F1}
.hero h1,.hero .tagline,.hero .hero-stat,.hero .scroll-cue{color:inherit}
.hero .tagline{color:#C8CCD0}
.hero .hero-stat{color:#9AA0A6}

/* The nav floats transparent over the hero until it solidifies on scroll, so
   until then it is over footage too and needs the same treatment. */
.nav:not(.solid):not(.menu-open){color:#F2F3F1}
.nav:not(.solid):not(.menu-open) a{color:#E7E9EA}
.nav:not(.solid):not(.menu-open) a:hover{color:var(--accent)}
[data-theme="light"] .nav:not(.solid):not(.menu-open) .brand{background-image:url("/img/brand/sps-horiz.svg")}
[data-theme="light"] .hero{--accent:#D4B274}
[data-theme="light"][data-accent="green"] .hero{--accent:#55AD8D}
.hero::after{content:'';position:absolute;inset:0;z-index:-1;
  background:radial-gradient(ellipse at center,rgba(10,10,12,.35) 0%,rgba(10,10,12,.82) 65%,rgba(10,10,12,.96) 100%)}
.hero .eyebrow{font-family:var(--disp);font-size:.72rem;letter-spacing:.42em;text-transform:uppercase;color:var(--accent);margin:0 0 1.4rem}
.hero h1{font-family:var(--disp);font-weight:200;margin:0;line-height:.94;letter-spacing:.02em;
  font-size:clamp(2.6rem,11vw,7.5rem);text-transform:uppercase}
.hero .tagline{margin:1.5rem auto 0;max-width:34ch;color:var(--fg-dim);font-size:clamp(.95rem,1.6vw,1.2rem);letter-spacing:.03em}
.scroll-cue{position:absolute;bottom:2.25rem;left:50%;transform:translateX(-50%);color:var(--fg-faint);
  font-family:var(--disp);font-size:.62rem;letter-spacing:.3em;text-transform:uppercase;display:grid;gap:.6rem;justify-items:center}
.scroll-cue i{display:block;width:1px;height:44px;background:linear-gradient(var(--accent),transparent);animation:cue 2.2s ease-in-out infinite}
@keyframes cue{0%,100%{opacity:.25;transform:scaleY(.55)}50%{opacity:1;transform:scaleY(1)}}

/* ---------- sections ---------- */
section[id]{scroll-margin-top:70px}
.sec{padding-block:clamp(4.5rem,11vw,9rem)}
.sec.alt{background:var(--bg-2)}
.sec-head{margin-bottom:clamp(2.25rem,5vw,3.75rem)}
.sec-head .kicker{font-family:var(--disp);font-size:.68rem;letter-spacing:.4em;text-transform:uppercase;color:var(--accent);margin:0 0 .85rem}
.sec-head h2{font-family:var(--disp);font-weight:300;text-transform:uppercase;letter-spacing:.05em;
  font-size:clamp(1.9rem,5.2vw,3.4rem);margin:0;line-height:1.05}
.sec-head p{color:var(--fg-dim);max-width:56ch;margin:1.15rem 0 0}

/* story */
.story{display:grid;gap:clamp(2rem,5vw,4.5rem);align-items:center;grid-template-columns:1fr}
@media(min-width:900px){.story{grid-template-columns:1.05fr .95fr}}
.story-img{position:relative;overflow:hidden;border-radius:2px}
.story-img img{width:100%;aspect-ratio:4/5;object-fit:cover;filter:grayscale(.25) contrast(1.04);transition:filter .7s,transform .9s}
.story-img:hover img{filter:none;transform:scale(1.03)}
.story blockquote{margin:0;font-size:clamp(1.35rem,3vw,2rem);line-height:1.35;font-weight:300;letter-spacing:.01em}
.story blockquote span{color:var(--accent)}

/* video grid */
.vgrid{display:grid;gap:clamp(.85rem,1.6vw,1.5rem);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}}
.card{position:relative;display:block;overflow:hidden;background:var(--bg-3);border-radius:2px;
  aspect-ratio:16/9;cursor:pointer;border:0;padding:0;width:100%;color:inherit;text-align:left}
.card img{width:100%;height:100%;object-fit:cover;transition:transform .8s cubic-bezier(.2,.8,.2,1),filter .5s;filter:brightness(.72)}
.card:hover img,.card:focus-visible img{transform:scale(1.06);filter:brightness(.45)}
/* Anything painted on top of imagery is a dark surface in both themes — the
   thumbnail is dimmed to brightness(.72) and the scrim is black. Inheriting the
   theme's ink turned every film title dark-on-dark in the light theme. */
.card-meta{position:absolute;inset:auto 0 0;padding:1.1rem 1.2rem;display:flex;align-items:flex-end;
  justify-content:space-between;gap:1rem;background:linear-gradient(transparent,rgba(0,0,0,.85));
  color:#F2F3F1}
.card-meta .ttl{color:#F2F3F1}
.card-meta time{color:#C2C6CA}
/* was an <h3>: flow content, so illegal inside both the <span> and the
   <button> that wrap it. The button's aria-label is what gets announced, so
   nothing was lost by making it a span. */
.card-meta .ttl{font-family:var(--disp);font-weight:400;text-transform:uppercase;letter-spacing:.08em;
  font-size:clamp(.82rem,1.5vw,1rem);margin:0;line-height:1.25}
.card-meta time{font-size:.72rem;font-variant-numeric:tabular-nums;flex-shrink:0}
.play{position:absolute;top:50%;left:50%;width:58px;height:58px;color:#F2F3F1;transform:translate(-50%,-50%) scale(.85);
  border:1px solid rgba(243,241,237,.75);border-radius:50%;display:grid;place-items:center;
  opacity:0;transition:opacity .4s,transform .4s,background .3s,border-color .3s}
.play::before{content:'';border-left:13px solid currentColor;border-block:8px solid transparent;margin-left:4px}
.card:hover .play,.card:focus-visible .play{opacity:1;transform:translate(-50%,-50%) scale(1)}
.card:hover .play{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}

/* photo masonry */
.pgrid{columns:1;column-gap:clamp(.85rem,1.6vw,1.4rem)}
@media(min-width:640px){.pgrid{columns:2}}
@media(min-width:1024px){.pgrid{columns:3}}
.pgrid button{break-inside:avoid;margin:0 0 clamp(.85rem,1.6vw,1.4rem);display:block;width:100%;
  padding:0;border:0;background:none;cursor:pointer;overflow:hidden;border-radius:2px;position:relative}
.pgrid img{width:100%;transition:transform .8s cubic-bezier(.2,.8,.2,1),filter .5s;filter:grayscale(.15)}
.pgrid button:hover img,.pgrid button:focus-visible img{transform:scale(1.045);filter:none}

/* docs */
.docs{display:grid;gap:clamp(1.25rem,3vw,2.25rem);grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr))}
.doc{display:block;background:var(--bg-3);border:1px solid var(--line);border-radius:2px;overflow:hidden;
  transition:border-color .35s,transform .35s}
.doc:hover{border-color:var(--accent);transform:translateY(-4px)}
.doc img{width:100%;aspect-ratio:8.5/11;object-fit:cover;object-position:top;filter:grayscale(.4) contrast(1.05);transition:filter .5s}
.doc:hover img{filter:none}
.doc-b{padding:1.15rem 1.35rem;display:flex;align-items:center;justify-content:space-between;gap:1rem}
.doc-b h3{font-family:var(--disp);font-weight:400;text-transform:uppercase;letter-spacing:.13em;font-size:.92rem;margin:0}
.doc-b span{font-size:.72rem;color:var(--fg-faint);letter-spacing:.1em;text-transform:uppercase}

/* clients */
.clients{display:grid;gap:clamp(1.5rem,3.5vw,3rem);align-items:center;
  grid-template-columns:repeat(auto-fill,minmax(min(50%,140px),1fr))}
.clients img{width:100%;max-height:52px;object-fit:contain;filter:grayscale(1) brightness(2.4) contrast(.5);
  opacity:.5;transition:opacity .4s,filter .4s}
.clients img:hover{opacity:1;filter:grayscale(1) brightness(3)}

/* contact */
.contact{position:relative;isolation:isolate;overflow:hidden}
.contact::before{content:'';position:absolute;inset:0;z-index:-2;background:var(--bg-2)}
.contact-inner{display:grid;gap:clamp(2rem,5vw,4rem);grid-template-columns:1fr}
@media(min-width:900px){.contact-inner{grid-template-columns:.85fr 1.15fr}}
form{display:grid;gap:1.15rem}
.row{display:grid;gap:1.15rem}
@media(min-width:560px){.row{grid-template-columns:1fr 1fr}}
label{display:block;font-family:var(--disp);font-size:.68rem;letter-spacing:.22em;text-transform:uppercase;
  color:var(--fg-dim);margin-bottom:.5rem}
input,textarea{width:100%;background:var(--sunk);border:0;border-bottom:1px solid var(--line);color:var(--fg);
  font-family:var(--body);font-size:1rem;font-weight:300;padding:.7rem 0;transition:border-color .3s}
input:focus,textarea:focus{outline:0;border-bottom-color:var(--accent)}
textarea{resize:vertical;min-height:120px}
.hp{position:absolute;left:-9999px;opacity:0;pointer-events:none}
button[type=submit]{justify-self:start;margin-top:.6rem;background:transparent;color:var(--fg);
  border:1px solid var(--fg-dim);padding:.95rem 2.5rem;font-family:var(--disp);font-size:.74rem;
  letter-spacing:.25em;text-transform:uppercase;cursor:pointer;transition:.3s;border-radius:2px}
button[type=submit]:hover:not(:disabled){background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
button[type=submit]:disabled{opacity:.45;cursor:not-allowed}
/* The status message is display:none until a submit, so no static contrast
   sweep ever sees it, and these are raw hex rather than tokens so the drift
   gate does not compare them either. Both light-theme values were failing
   badly — 1.47:1 on the success line, 1.90:1 on the error — which made the
   contact form's only feedback invisible in the light theme. Measured
   against the blended panel, not the page. */
.form-msg{font-size:.9rem;padding:.85rem 1rem;border-radius:2px;display:none;line-height:1.5}
.form-msg.show{display:block}
.form-msg.ok{background:rgba(80,180,120,.12);color:#7fd6a2;border:1px solid rgba(80,180,120,.3)}
.form-msg.err{background:rgba(220,80,80,.12);color:#f08d8d;border:1px solid rgba(220,80,80,.3)}
[data-theme="light"] .form-msg.ok{color:#1B4A3A;border-color:rgba(27,74,58,.35)}    /* 8.51:1 */
[data-theme="light"] .form-msg.err{color:#9B2226;border-color:rgba(155,34,38,.35)}  /* 6.38:1 */
.form-msg a{color:inherit;text-decoration:underline;text-underline-offset:3px}
.form-msg .addr{font-family:var(--mono);white-space:nowrap}
.cdetail{display:grid;gap:1.5rem;align-content:start}
.cdetail a{border-bottom:1px solid var(--line);transition:border-color .3s,color .3s}
.cdetail a:hover{color:var(--accent);border-color:var(--accent)}

/* footer */
footer{border-top:1px solid var(--line);padding-block:2.5rem;background:var(--bg)}
footer .wrap{display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;align-items:center}
footer p{margin:0;font-size:.78rem;color:var(--fg-faint);letter-spacing:.06em}

/* lightbox */
.lb{position:fixed;inset:0;z-index:300;background:rgba(6,6,8,.97);backdrop-filter:blur(8px);
  display:grid;place-items:center;padding:clamp(1rem,4vw,3rem);opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s}
.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:#000}
.lb-body .ratio iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.lb-cap{margin-top:1rem;text-align:center;color:var(--fg-dim);font-size:.85rem;letter-spacing:.08em;color:#C2C6CA}
.lb button{position:absolute;background:none;border:0;color:var(--fg);cursor:pointer;padding:.75rem;
  font-size:1.6rem;line-height:1;transition:color .25s}
.lb button:hover{color:var(--accent)}
/* Close sat at top:-3.25rem, above the image and outside the viewport on a
   phone, so the lightbox could not be dismissed by tapping. It is pinned to
   the overlay instead, with a target big enough to hit. */
.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(6,6,8,.72);border:1px solid var(--line);border-radius:999px;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}
  /* keep the arrows off the close button and give them a real target too */
  .lb-p,.lb-n{min-width:2.75rem;min-height:2.75rem;background:rgba(6,6,8,.6);border-radius:999px}
}
/* the backdrop closes on tap; say so for anyone using a pointer */
.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(26px);transition:opacity .8s cubic-bezier(.2,.7,.2,1),transform .8s cubic-bezier(.2,.7,.2,1)}
.js .rv.in{opacity:1;transform:none}

/* ---- systems & products ---- */
.sgrid{display:grid;gap:clamp(1.25rem,3vw,2.25rem);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr))}
.scard{display:flex;flex-direction:column;background:var(--bg-2);border:1px solid var(--line);
  border-radius:2px;overflow:hidden;text-decoration:none;color:inherit;
  transition:border-color .35s,transform .35s}
.scard:hover{border-color:var(--accent);transform:translateY(-4px)}
.scard picture,.scard img{display:block;width:100%;height:auto}
.scard img{aspect-ratio:16/10;object-fit:cover}
.scard-b{padding:1.15rem 1.35rem 1.35rem;display:flex;flex-direction:column;flex:1;gap:.55rem}
.scard-b h3{font-family:var(--disp);font-weight:400;text-transform:uppercase;letter-spacing:.06em;
  font-size:1rem;margin:0}
.scard-b .what{color:var(--fg-dim);font-size:.92rem;line-height:1.6}
.scard-b .more{margin-top:auto;padding-top:.3rem;color:var(--accent);font-size:.76rem;
  letter-spacing:.12em;text-transform:uppercase;font-family:var(--disp)}
.sec-foot{display:flex;flex-wrap:wrap;gap:.9rem;margin:clamp(1.5rem,3vw,2.25rem) 0 0}

/* ---- documents: a download is not a hero ---- */
.docs{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. */
.doc{display:grid;grid-template-columns:104px 1fr;align-items:stretch;position:relative}
.doc-a{display:flex;gap:1rem;align-items:baseline}
.doc-a a{font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--fg-faint);text-decoration:none;transition:color .25s}
.doc-a a:hover,.doc:hover .doc-a a.read{color:var(--accent)}
.doc-a a.read::after{content:'';position:absolute;inset:0}
.doc-a a:not(.read){position:relative;z-index:1}
.doc-a a:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.doc img{aspect-ratio:8.5/11;height:100%}
.doc-b{flex-direction:column;align-items:flex-start;justify-content:center;gap:.35rem}

/* ---- theme + accent control ---- */
.themebar{display:flex;align-items:center;gap:.35rem;margin-left:1rem}
.themebar .grp{display:flex;border:1px solid var(--line);border-radius:999px;overflow:hidden}
.themebar button{appearance:none;background:none;border:0;cursor:pointer;
  font-family:var(--body);font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--tx3);padding:.42rem .6rem;line-height:1;transition:color .25s,background .25s}
.themebar button:hover{color:var(--tx)}
.themebar button[aria-pressed="true"]{color:var(--on-accent);background:var(--accent)}
.themebar .dot{width:.6rem;height:.6rem;border-radius:50%;display:inline-block}
.themebar .dot.gold{background:#D4B274}
.themebar .dot.green{background:#55AD8D}
@media(max-width:860px){.themebar{display:none}}
.hero-cta{display:flex;flex-wrap:wrap;gap:.85rem;justify-content:center;margin:2.1rem 0 0}
@media(max-width:520px){.hero-cta{flex-direction:column;align-items:center}.hero-cta .cta{width:min(280px,80vw);text-align:center}}

/* ---- calls to action (mirrors doc.css) ---- */
.cta{display:inline-block;font-family:var(--disp);font-weight:400;font-size:.75rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--on-accent);background:var(--accent);padding:.85rem 1.6rem;
  text-decoration:none;border-radius:2px;transition:opacity .25s,border-color .25s,color .25s}
.cta:hover{opacity:.88}
.cta.ghost{background:none;border:1px solid var(--line);color:var(--tx)}
.cta.ghost:hover{border-color:var(--accent);color:var(--accent);opacity:1}
/* the wordmark is the real lockup; light and dark ink swap with the theme */
.brand{display:inline-block;width:104px;height:40px;background-repeat:no-repeat;
  background-position:left center;background-size:contain;
  background-image:url("/img/brand/sps-horiz.svg")}
[data-theme="light"] .brand{background-image:url("/img/brand/sps-horiz-light.svg")}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* 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}
.hero-stat{margin:1.15rem auto 0;font-family:var(--mono);font-size:.7rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--tx3)}
@media(max-width:520px){.hero-stat{font-size:.62rem;letter-spacing:.06em;max-width:30ch}}
.js .more-still{display:none}
.js .pgrid.show-all .more-still{display:block}

/* ---- high contrast and reduced transparency ----
   forced-colors: Windows high contrast replaces the palette wholesale, so stop
   painting over its choices and let borders carry structure.
   prefers-reduced-transparency: the veils and the blur are decoration; solid
   surfaces read the same and cost less. */
@media (prefers-reduced-transparency: reduce){
  .nav,.nav.solid{background:var(--bg);backdrop-filter:none}
  .lb{background:#060608;backdrop-filter:none}
}
@media (forced-colors: active){
  *{forced-color-adjust:auto}
  .nav,.nav.solid,.lb{background:Canvas;backdrop-filter:none}
  .cta,.cta.ghost{border:1px solid ButtonText;background:ButtonFace;color:ButtonText}
  .themebar button[aria-pressed="true"]{background:Highlight;color:HighlightText}
  .pcard,.scard,.doc,.card{border:1px solid CanvasText}
  :focus-visible{outline:3px solid Highlight;outline-offset:2px}
  .hero::after{display:none}
}
/* the signature closes the page, in the hand it was drawn in */
.sign{display:grid;gap:.55rem;margin:1.9rem 0 0;justify-items:start}
.sign img{width:180px;height:auto;opacity:.9}
[data-theme="light"] .sign img{filter:invert(1)}
.sign span{font-family:var(--hand);font-size:1.05rem;color:var(--tx3);line-height:1.3}
