/* ---------------------------------------------------------------------------
   site.css — shared chrome for every page: top nav, hero furniture,
   "how it works" cards, and the footer.

   Everything here is namespaced --site-* / .site-* so it can sit on top of a
   demo's own stylesheet without colliding. A page with its own palette (the
   matching engine's trading terminal, for example) just re-declares the
   --site-* tokens and the chrome follows it.

   Load this BEFORE the page's inline <style> so the page always wins.
--------------------------------------------------------------------------- */

/* Deep-space theme: the site commits to a single dark cosmic look, so there is
   no light palette. color-scheme:dark keeps form controls and scrollbars in
   step with it. */
:root{
  color-scheme:dark;
  --site-bg:#070B15;
  --site-surface:#0E1524;
  --site-ink:#E9EEF8;
  --site-muted:#94A1B7;
  --site-line:#1D2839;
  --site-line-strong:#2B3A52;
  --site-accent:#6E86FF;
  --site-accent-ink:#070B15;
  --site-nebula:#B48CFF;
  --site-grad:linear-gradient(137deg,#6E86FF 0%,#B48CFF 55%,#6E86FF 100%);
  --site-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
  --site-display:'Bricolage Grotesque',system-ui,-apple-system,sans-serif;
  --site-serif:'Instrument Serif',Georgia,serif;
  --site-body:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  /* Each page sets this to its own content width so the chrome lines up. */
  --site-w:1000px;
  --site-pad:1.25rem;
}

/* Paint the root element, not just body. Body's background normally propagates
   to the canvas, but that stops covering the full area if anything ever makes
   the page scroll horizontally — the leftover strip renders as bare white.
   Setting it here means the canvas is painted whatever else happens. Each page
   re-points --site-bg to its own palette, so this follows the page's theme. */
html{background:var(--site-bg); scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

/* Hold the footer at the bottom of the window when a page is shorter than the
   viewport. Without this the footer floats partway up on a tall screen with
   dead space under it, which reads as the page failing to finish. */
body{min-height:100vh; display:flex; flex-direction:column}
body > .site-footer{margin-top:auto}

/* ---- starfield ----------------------------------------------------------- */

/* Two fixed pseudo-elements at z-index:-1: they paint above the root canvas
   colour but below every in-flow box, so cards and panels naturally occlude
   the sky. For this to show, pages must leave body's own background
   transparent — the demo pages were edited to match.

   The sky is split across ::before (nebula + far stars) and ::after (near
   stars) so scrolling can drift them at different speeds: space.js keeps the
   unitless --scroll custom property at window.scrollY, and each layer
   translates by its own fraction of it. transform + opacity only, so the
   parallax composites on the GPU. The oversized inset hides the edges the
   translation would otherwise reveal. */
body::before{
  content:""; position:fixed; inset:-18% 0 0; z-index:-1; pointer-events:none;
  transform:translate3d(0, calc(var(--scroll,0) * -0.03px), 0);
  background-image:
    radial-gradient(ellipse 60% 45% at 18% 8%, rgba(110,134,255,.14), transparent 70%),
    radial-gradient(ellipse 55% 40% at 85% 25%, rgba(180,140,255,.09), transparent 70%),
    radial-gradient(ellipse 70% 50% at 55% 100%, rgba(110,134,255,.07), transparent 70%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='700' height='700'%3E%3Cg fill='%23E9EEF8'%3E%3Ccircle cx='34' cy='51' r='1'/%3E%3Ccircle cx='142' cy='19' r='.7'/%3E%3Ccircle cx='240' cy='84' r='1.2' opacity='.8'/%3E%3Ccircle cx='329' cy='38' r='.6' opacity='.6'/%3E%3Ccircle cx='437' cy='121' r='.9'/%3E%3Ccircle cx='512' cy='62' r='.7' opacity='.7'/%3E%3Ccircle cx='623' cy='140' r='1.1'/%3E%3Ccircle cx='58' cy='187' r='.8' opacity='.6'/%3E%3Ccircle cx='198' cy='232' r='1' opacity='.9'/%3E%3Ccircle cx='302' cy='176' r='.6'/%3E%3Ccircle cx='398' cy='259' r='1.2' opacity='.7'/%3E%3Ccircle cx='511' cy='204' r='.7'/%3E%3Ccircle cx='648' cy='287' r='.9' opacity='.8'/%3E%3Ccircle cx='81' cy='331' r='1.1'/%3E%3Ccircle cx='176' cy='389' r='.6' opacity='.6'/%3E%3Ccircle cx='289' cy='344' r='.9'/%3E%3Ccircle cx='417' cy='401' r='.7' opacity='.8'/%3E%3Ccircle cx='534' cy='352' r='1'/%3E%3Ccircle cx='612' cy='428' r='.6' opacity='.6'/%3E%3Ccircle cx='47' cy='474' r='.9' opacity='.9'/%3E%3Ccircle cx='159' cy='522' r='1.2'/%3E%3Ccircle cx='273' cy='487' r='.6' opacity='.7'/%3E%3Ccircle cx='366' cy='548' r='.9'/%3E%3Ccircle cx='487' cy='509' r='.7' opacity='.6'/%3E%3Ccircle cx='592' cy='561' r='1.1' opacity='.9'/%3E%3Ccircle cx='94' cy='614' r='.7'/%3E%3Ccircle cx='217' cy='659' r='1' opacity='.8'/%3E%3Ccircle cx='341' cy='622' r='.6'/%3E%3Ccircle cx='459' cy='671' r='.9' opacity='.7'/%3E%3Ccircle cx='571' cy='639' r='.7'/%3E%3Ccircle cx='667' cy='598' r='1.2' opacity='.8'/%3E%3Ccircle cx='688' cy='341' r='.7' opacity='.6'/%3E%3C/g%3E%3C/svg%3E");
  background-size:auto, auto, auto, 700px 700px;
  background-position:0 0, 0 0, 0 0, 0 0;
}
/* Near star layer: same tile, larger scale, its own drift speed and a
   slower counter-phased twinkle. */
body::after{
  content:""; position:fixed; inset:-18% 0 0; z-index:-1; pointer-events:none;
  transform:translate3d(0, calc(var(--scroll,0) * -0.08px), 0);
  background-image:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='700' height='700'%3E%3Cg fill='%23E9EEF8'%3E%3Ccircle cx='34' cy='51' r='1'/%3E%3Ccircle cx='142' cy='19' r='.7'/%3E%3Ccircle cx='240' cy='84' r='1.2' opacity='.8'/%3E%3Ccircle cx='329' cy='38' r='.6' opacity='.6'/%3E%3Ccircle cx='437' cy='121' r='.9'/%3E%3Ccircle cx='512' cy='62' r='.7' opacity='.7'/%3E%3Ccircle cx='623' cy='140' r='1.1'/%3E%3Ccircle cx='58' cy='187' r='.8' opacity='.6'/%3E%3Ccircle cx='198' cy='232' r='1' opacity='.9'/%3E%3Ccircle cx='302' cy='176' r='.6'/%3E%3Ccircle cx='398' cy='259' r='1.2' opacity='.7'/%3E%3Ccircle cx='511' cy='204' r='.7'/%3E%3Ccircle cx='648' cy='287' r='.9' opacity='.8'/%3E%3Ccircle cx='81' cy='331' r='1.1'/%3E%3Ccircle cx='176' cy='389' r='.6' opacity='.6'/%3E%3Ccircle cx='289' cy='344' r='.9'/%3E%3Ccircle cx='417' cy='401' r='.7' opacity='.8'/%3E%3Ccircle cx='534' cy='352' r='1'/%3E%3Ccircle cx='612' cy='428' r='.6' opacity='.6'/%3E%3Ccircle cx='47' cy='474' r='.9' opacity='.9'/%3E%3Ccircle cx='159' cy='522' r='1.2'/%3E%3Ccircle cx='273' cy='487' r='.6' opacity='.7'/%3E%3Ccircle cx='366' cy='548' r='.9'/%3E%3Ccircle cx='487' cy='509' r='.7' opacity='.6'/%3E%3Ccircle cx='592' cy='561' r='1.1' opacity='.9'/%3E%3Ccircle cx='94' cy='614' r='.7'/%3E%3Ccircle cx='217' cy='659' r='1' opacity='.8'/%3E%3Ccircle cx='341' cy='622' r='.6'/%3E%3Ccircle cx='459' cy='671' r='.9' opacity='.7'/%3E%3Ccircle cx='571' cy='639' r='.7'/%3E%3Ccircle cx='667' cy='598' r='1.2' opacity='.8'/%3E%3Ccircle cx='688' cy='341' r='.7' opacity='.6'/%3E%3C/g%3E%3C/svg%3E");
  background-size:1150px 1150px;
  background-position:380px 240px;
  animation:site-twinkle 11s ease-in-out 2s infinite;
}

/* ---- 3D models ------------------------------------------------------------ */

/* Procedural three.js emblems (space3d.js draws into these). Three sizes:
   the homepage hero centerpiece, the homepage project-card emblem, and the
   demo-page header emblem. The radial glow behind each stands in for a floor
   so the object doesn't float in nothing. */
.site-model{
  background:radial-gradient(closest-side, rgba(110,134,255,.16), transparent);
  border:none; outline:none; touch-action:pan-y; cursor:grab;
}
.site-model:active{cursor:grabbing}
.site-model canvas{display:block; width:100%; height:100%}
.site-model-hero{width:min(340px,80vw); height:min(340px,80vw)}
.site-model-card{width:118px; height:118px; flex:none}
.site-model-head{width:150px; height:150px; flex:none; float:right; margin:.25rem 0 .5rem 1.25rem}
@media (max-width:720px){
  .site-model-head{width:110px; height:110px}
}

/* ---- top nav ------------------------------------------------------------ */

/* Floating glass pill, centered under the top edge. The nav element itself is
   transparent so the starfield runs behind the capsule. */
.site-nav{
  position:sticky; top:0; z-index:50;
  padding:.7rem var(--site-pad) .55rem;
  border-bottom:none;
  /* Content scrolls under the floating pill; this veil fades it out before
     it collides with the capsule instead of letting headlines ghost through
     the gaps around it. It ends at exactly the opacity the ::after strip
     below starts with — any mismatch there paints a hard line across the
     page. */
  background:linear-gradient(to bottom,
    var(--site-bg) 0%,
    color-mix(in srgb, var(--site-bg) 92%, transparent) 55%,
    color-mix(in srgb, var(--site-bg) 72%, transparent) 100%);
}
.site-nav-inner{
  max-width:var(--site-w); margin:0 auto;
  padding:.32rem .55rem .32rem .65rem;
  display:flex; align-items:center; gap:1rem; min-height:48px;
  border:1px solid color-mix(in srgb, var(--site-ink) 12%, transparent);
  border-radius:999px;
  background:color-mix(in srgb, var(--site-surface) 94%, transparent);
  box-shadow:0 12px 32px -18px rgba(0,0,0,.85);
}
@supports (backdrop-filter:blur(1px)){
  /* With the blur underneath, the capsule can afford to stay glassier. */
  .site-nav-inner{background:color-mix(in srgb, var(--site-surface) 86%, transparent)}
}
/* The veil's tail: picks up at the same 72% the nav ends on and eases the
   rest of the way out, so there is no seam and no visible band edge. */
.site-nav::after{
  content:""; position:absolute; left:0; right:0; top:100%; height:48px;
  pointer-events:none;
  background:linear-gradient(to bottom,
    color-mix(in srgb, var(--site-bg) 72%, transparent),
    color-mix(in srgb, var(--site-bg) 38%, transparent) 45%,
    transparent 100%);
}
@supports (backdrop-filter:blur(1px)){
  .site-nav-inner{backdrop-filter:saturate(160%) blur(14px)}
}
.site-brand{
  display:flex; align-items:center; gap:.5rem; flex:none;
  font-family:var(--site-display); font-weight:700; font-size:.95rem;
  letter-spacing:-.01em; color:var(--site-ink); text-decoration:none;
}
.site-brand .site-mark{
  width:24px; height:24px; flex:none; border-radius:999px;
  background:var(--site-grad); color:var(--site-accent-ink);
  font-family:var(--site-mono); font-size:.6rem; font-weight:700; letter-spacing:.02em;
  display:flex; align-items:center; justify-content:center;
  transition:transform .25s ease;
}
.site-brand:hover{color:var(--site-accent)}
.site-brand:hover .site-mark{transform:scale(1.1) rotate(-6deg)}

/* Link strip. Scrolls sideways on narrow screens rather than wrapping or
   collapsing behind a menu button — no JS, and every demo stays one tap away. */
.site-links{
  display:flex; align-items:center; gap:.15rem; margin-left:auto;
  overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch;
}
.site-links::-webkit-scrollbar{display:none}
.site-link{
  font-family:var(--site-mono); font-size:.7rem; font-weight:500;
  letter-spacing:.09em; text-transform:uppercase; white-space:nowrap;
  color:var(--site-muted); text-decoration:none;
  padding:.42rem .6rem; border-radius:4px;
  transition:color .13s ease, background .13s ease;
}
.site-link:hover{color:var(--site-ink); background:var(--site-line)}
.site-link[aria-current="page"]{color:var(--site-accent); font-weight:600}

/* Ambient-sound toggle. space.js injects this into every nav, so the control
   only exists when the audio behind it can actually run. The equalizer bars
   idle short and muted; they take the accent and dance while sound plays. */
.site-sound{
  flex:none; width:34px; height:34px; border-radius:999px; padding:0;
  display:flex; align-items:center; justify-content:center; gap:2px;
  background:transparent; cursor:pointer;
  border:1px solid color-mix(in srgb, var(--site-ink) 14%, transparent);
  transition:border-color .2s ease, background .2s ease;
}
.site-sound:hover{
  border-color:var(--site-accent);
  background:color-mix(in srgb, var(--site-accent) 12%, transparent);
}
.site-sound:focus-visible{outline:2px solid var(--site-accent); outline-offset:2px}
.site-sound span{
  width:2px; border-radius:1px; background:var(--site-muted);
  transition:background .2s ease;
}
.site-sound span:nth-child(1){height:5px}
.site-sound span:nth-child(2){height:9px}
.site-sound span:nth-child(3){height:7px}
.site-sound span:nth-child(4){height:4px}
.site-sound.on span{background:var(--site-accent); animation:site-eq 1.1s ease-in-out infinite}
.site-sound.on span:nth-child(2){animation-delay:.28s}
.site-sound.on span:nth-child(3){animation-delay:.14s}
.site-sound.on span:nth-child(4){animation-delay:.42s}
@keyframes site-eq{
  0%,100%{transform:scaleY(.6)}
  50%{transform:scaleY(1.7)}
}

/* ---- hero furniture ----------------------------------------------------- */

.site-kicker{
  font-family:var(--site-mono); font-size:.72rem; font-weight:500;
  letter-spacing:.14em; text-transform:uppercase; color:var(--site-muted);
  display:flex; gap:.6rem; align-items:center;
}
.site-kicker::after{content:"";flex:1;height:1px;background:var(--site-line-strong)}

.site-chips{display:flex; flex-wrap:wrap; gap:.4rem; margin-top:1rem}
.site-chip{
  font-family:var(--site-mono); font-size:.68rem; font-weight:500; letter-spacing:.05em;
  color:var(--site-muted); background:var(--site-surface);
  border:1px solid var(--site-line-strong); border-radius:999px; padding:.24rem .65rem;
}

/* ---- "how it works" cards ----------------------------------------------- */

/* Most demos already define their own .section-label; this is for pages that
   don't (the matching engine terminal). */
.site-section-label{
  font-family:var(--site-mono); font-size:.72rem; font-weight:500;
  letter-spacing:.12em; text-transform:uppercase; color:var(--site-muted);
  margin:2.2rem 0 .9rem; padding-bottom:.55rem;
  border-bottom:1px solid var(--site-line-strong);
  display:flex; justify-content:space-between; align-items:baseline; gap:1rem;
}

/* Bottom margin mirrors the label's top margin, so the cards sit in their own
   band instead of crowding whatever demo follows. Adjacent-sibling margins
   collapse, so a following element with its own top margin won't double up. */
.site-how{display:grid; grid-template-columns:repeat(3,1fr); gap:.9rem;
  margin-bottom:2.2rem}
/* Gradient hairline border (padding-box/border-box trick) with a blurred
   gradient glow behind each card that breathes in on hover. */
.site-card{
  position:relative; z-index:0;
  border:1px solid transparent; border-radius:18px; padding:1.15rem 1.25rem;
  background:
    linear-gradient(var(--site-surface),var(--site-surface)) padding-box,
    linear-gradient(137deg,
      color-mix(in srgb, var(--site-accent) 55%, transparent),
      color-mix(in srgb, var(--site-nebula) 22%, transparent) 45%,
      color-mix(in srgb, var(--site-accent) 10%, transparent)) border-box;
  transition:transform .35s cubic-bezier(.25,.1,.25,1);
}
.site-card::after{
  content:""; position:absolute; inset:-2px; border-radius:inherit; z-index:-1;
  background:var(--site-grad); filter:blur(26px); opacity:.08;
  transition:opacity .45s ease; pointer-events:none;
}
.site-card:hover{transform:translateY(-4px)}
.site-card:hover::after{opacity:.3}
.site-card .site-card-n{
  font-family:var(--site-mono); font-size:.66rem; font-weight:600;
  letter-spacing:.1em; color:var(--site-accent);
}
.site-card h3{
  font-family:var(--site-display); font-weight:600; font-size:1.02rem;
  letter-spacing:-.01em; color:var(--site-ink); margin:.35rem 0 .4rem;
}
.site-card p{font-size:.86rem; color:var(--site-muted); line-height:1.6}
.site-card code{
  font-family:var(--site-mono); font-size:.86em; color:var(--site-ink);
  background:var(--site-bg); border-radius:3px; padding:.05em .3em;
}

/* ---- typography accents --------------------------------------------------- */

/* Serif-italic gradient word, the "featured *projects*" move. Pages that use it
   must load the Instrument Serif italic font. */
.site-accent-word{
  font-family:var(--site-serif); font-style:italic; font-weight:400;
  background:var(--site-grad); background-size:200% 100%;
  -webkit-background-clip:text; background-clip:text;
  color:transparent; -webkit-text-fill-color:transparent;
  padding-right:.06em;
  animation:site-grad-shift 6s ease infinite;
}
/* A rotating word changes width on every swap. Reserving the widest word's
   width (measured in space.js) stops the line reflowing every 2 seconds. */
.site-accent-word[data-rotate]{display:inline-block; text-align:left}

/* ---- motion --------------------------------------------------------------- */

@keyframes site-grad-shift{
  0%{background-position:0% 50%} 50%{background-position:100% 50%} 100%{background-position:0% 50%}
}
@keyframes site-rise{
  from{opacity:0; transform:translateY(46px)} to{opacity:1; transform:none}
}
@keyframes site-blurin{
  from{opacity:0; filter:blur(10px); transform:translateY(18px)}
  to{opacity:1; filter:blur(0); transform:none}
}
@keyframes site-rolein{
  from{opacity:0; transform:translateY(8px)} to{opacity:1; transform:none}
}
@keyframes site-scrolldown{
  from{transform:translateY(-110%)} to{transform:translateY(210%)}
}
@keyframes site-pulse{
  0%,100%{opacity:1; box-shadow:0 0 8px #34D399} 50%{opacity:.55; box-shadow:0 0 2px #34D399}
}
@keyframes site-twinkle{
  0%,100%{opacity:1} 50%{opacity:.72}
}
body::before{animation:site-twinkle 8s ease-in-out infinite}

/* Scroll-triggered reveals. space.js tags the targets, so with JS disabled
   nothing is ever hidden.

   These run as an animation, not a transition, on purpose. A transition here
   would be silently cancelled by any page that declares its own `transition`
   shorthand on the same element (index.html's `.proj` and this file's own
   `.site-card` both do) — the reveal would then pop instead of rising, and the
   card's hover timing would inherit the reveal's 800ms + stagger delay.
   Animations don't share that cascade slot, so both survive. space.js strips
   the classes once the animation ends, leaving the element in its natural
   state. */
@keyframes site-reveal-in{
  from{opacity:0; transform:translateY(28px)} to{opacity:1; transform:none}
}
.site-reveal{opacity:0}
.site-reveal.site-in{
  animation:site-reveal-in .8s cubic-bezier(.25,.1,.25,1) var(--reveal-delay,0ms) both;
}

/* Rotating role word re-triggers this on each swap. The gradient shift is
   repeated here because `animation` is a shorthand — naming only site-rolein
   would drop the gradient animation this element also carries. */
.site-role-swap{animation:site-rolein .4s ease-out, site-grad-shift 6s ease infinite}

/* Scroll cue: label over a thin line with a travelling highlight. */
.site-scrollcue{
  display:flex; flex-direction:column; align-items:center; gap:.55rem;
  font-family:var(--site-mono); font-size:.62rem; font-weight:500;
  letter-spacing:.24em; text-transform:uppercase; color:var(--site-muted);
}
.site-scrollcue-line{
  width:1px; height:40px; background:var(--site-line-strong);
  position:relative; overflow:hidden;
}
.site-scrollcue-line::after{
  content:""; position:absolute; inset:0;
  background:var(--site-grad);
  animation:site-scrolldown 1.6s ease-in-out infinite;
}

/* Pulsing availability dot for the footer. */
.site-avail{
  display:inline-flex; align-items:center; gap:.55rem; margin-top:.85rem;
  font-family:var(--site-mono); font-size:.72rem; color:var(--site-ink);
}
.site-avail::before{
  content:""; width:8px; height:8px; border-radius:50%; flex:none;
  background:#34D399; animation:site-pulse 2.4s ease-in-out infinite;
}

/* ---- launch sequence ------------------------------------------------------ */

/* First-visit preloader. The overlay is created entirely by space.js, and only
   on pages whose head guard put .launching on <html> — no JS, no overlay. The
   guard also arms a timeout that removes the class even if space.js dies, so
   nothing can stay hidden behind it. */
html.launching{overflow:hidden}
.site-launch{
  position:fixed; inset:0; z-index:9999;
  display:flex; align-items:center; justify-content:center;
  background:
    radial-gradient(ellipse 70% 55% at 30% 15%, rgba(110,134,255,.10), transparent 70%),
    var(--site-bg);
  transition:opacity .65s ease, visibility .65s;
}
.site-launch-done{opacity:0; visibility:hidden}
.site-launch-label{
  position:absolute; top:1.5rem; left:1.6rem;
  font-family:var(--site-mono); font-size:.68rem; font-weight:500;
  letter-spacing:.3em; text-transform:uppercase; color:var(--site-muted);
}
.site-launch-word{font-size:clamp(2.6rem,8vw,4.6rem); line-height:1.1}
.site-launch-count{
  position:absolute; right:1.5rem; bottom:1.6rem;
  font-family:var(--site-display); font-weight:700; line-height:1;
  font-size:clamp(3.2rem,10vw,6.5rem); letter-spacing:-.02em;
  font-variant-numeric:tabular-nums; color:var(--site-ink);
}
.site-launch-bar{
  position:absolute; left:0; right:0; bottom:0; height:3px;
  background:color-mix(in srgb, var(--site-line) 60%, transparent);
}
.site-launch-bar span{
  display:block; height:100%; background:var(--site-grad);
  transform:scaleX(0); transform-origin:left;
  box-shadow:0 0 12px rgba(110,134,255,.45);
}

@media (prefers-reduced-motion:reduce){
  body::before,body::after{animation:none}
  .site-accent-word,.site-role-swap{animation:none}
  /* Belt and braces: space.js also skips tagging, but if the preference is
     switched on mid-visit the already-tagged elements must not stay hidden. */
  .site-reveal,.site-reveal.site-in{opacity:1; transform:none; animation:none}
  .site-scrollcue-line::after{animation:none}
  .site-avail::before{animation:none}
  /* Audio itself is opt-in and not motion — only the dancing bars go still. */
  .site-sound.on span{animation:none}
  .site-card{transition:none}
  .site-launch{display:none}
  html.launching{overflow:auto}
}

/* ---- warp transitions ---------------------------------------------------- */

/* Cross-document view transitions: navigating between pages does a short
   warp — the old page blurs and stretches away, the new one snaps out of
   the blur. Browsers without the API just navigate normally. */
@view-transition{navigation:auto}
@keyframes site-warp-out{to{transform:scale(1.05); filter:blur(8px); opacity:0}}
@keyframes site-warp-in{from{transform:scale(.98); filter:blur(8px); opacity:0}}
::view-transition-old(root){animation:site-warp-out .26s ease-in both}
::view-transition-new(root){animation:site-warp-in .3s ease-out both}
@media (prefers-reduced-motion:reduce){
  ::view-transition-old(root),::view-transition-new(root){animation:none}
}

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

.site-footer{
  border-top:1px solid var(--site-line); margin-top:clamp(2.5rem,7vw,4rem);
  background:transparent;
}
.site-footer-inner{
  max-width:var(--site-w); margin:0 auto; padding:2rem var(--site-pad) 2.5rem;
  display:flex; flex-wrap:wrap; gap:1.5rem 2.5rem; align-items:flex-start;
}
.site-footer-brand{flex:1 1 15rem; min-width:0}
.site-footer-name{
  font-family:var(--site-display); font-weight:700; font-size:1.05rem;
  letter-spacing:-.01em; color:var(--site-ink);
}
.site-footer-tag{font-size:.84rem; color:var(--site-muted); margin-top:.3rem; max-width:32em}
.site-footer-col{display:flex; flex-direction:column; gap:.45rem}
.site-footer-h{
  font-family:var(--site-mono); font-size:.66rem; font-weight:600;
  letter-spacing:.12em; text-transform:uppercase; color:var(--site-muted); margin-bottom:.1rem;
}
.site-footer-col a{
  font-family:var(--site-mono); font-size:.8rem; color:var(--site-ink);
  text-decoration:none; width:fit-content; border-bottom:1px solid transparent;
}
.site-footer-col a:hover{color:var(--site-accent); border-bottom-color:var(--site-accent)}
.site-footer-note{
  max-width:var(--site-w); margin:0 auto; padding:0 var(--site-pad) 2rem;
  font-family:var(--site-mono); font-size:.7rem; color:var(--site-muted);
}

/* ---- shared behaviour --------------------------------------------------- */

.site-nav a:focus-visible,
.site-footer a:focus-visible{outline:2px solid var(--site-accent); outline-offset:2px; border-radius:3px}

@media (max-width:720px){
  .site-how{grid-template-columns:1fr}
  .site-nav-inner{gap:.75rem}
  .site-brand span:not(.site-mark){display:none}
}
/* The five links need ~410px laid out at full size. Below that the strip used
   to clip mid-word, which reads as broken rather than scrollable — it looks the
   same whether you are on a small window, a zoomed-in desktop, or a phone.
   Tightening the type and padding lets the whole set fit instead. */
@media (max-width:460px){
  .site-nav-inner{gap:.4rem; padding:0 .75rem}
  .site-link{font-size:.62rem; letter-spacing:.05em; padding:.42rem .3rem}
}
/* Touch devices only. A finger needs ~24px; a mouse pointer doesn't, and
   growing these on desktop would just loosen the nav and footer for no gain.
   Gating on pointer:coarse leaves the desktop layout byte-for-byte unchanged
   while giving phones and tablets a real target. */
@media (pointer:coarse){
  /* Below 720px the wordmark is hidden and only the 22px square remains, so the
     target needs padding out in both directions. */
  .site-brand{min-height:24px; min-width:24px; justify-content:center}
  /* Height is already fine here: padding plus line-height puts these at 28px
     even at the .62rem size used below 460px. Width is the tight one. The
     shortest label, "ML", is only 22.5px across, so it needs a floor and the
     text centred inside it. min-height is a backstop if the padding is ever
     trimmed; it does not bind today. */
  .site-link{
    display:inline-flex; align-items:center; justify-content:center;
    min-height:24px; min-width:24px;
  }
  /* Standalone links in a stacked column, not links inside a sentence, so the
     inline exemption doesn't apply. Text position is unchanged; the hit area
     grows around it. */
  .site-footer-col a{display:inline-flex; align-items:center; min-height:24px}
}

@media (prefers-reduced-motion:reduce){
  .site-link,.site-brand,.site-brand .site-mark,.site-footer-col a{transition:none!important}
}
