/* ══════════════════════════════════════════════════════════════════════════
   winch · main.css
   Palette "Signal": monochrome + one acid accent. Same hex values as the
   GitHub profile, so the two read as one identity.
   ══════════════════════════════════════════════════════════════════════════ */

:root{
  /* Signal */
  --ink:      #07090A;
  --moss:     #1C2418;
  --pill:     #10140E;
  --lime:     #C6F24E;
  --lime-lt:  #E4FF9B;
  --chalk:    #F2F6EC;
  --bone:     #E9EDE3;
  --ash:      #B4BAAB;
  --muted:    #868C7E;
  --dim:      #676C60;

  --line:     rgba(233,237,227,.11);
  --line-2:   rgba(233,237,227,.055);
  --glass:    rgba(7,9,10,.55);

  --mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
  /* A display face with a real width axis.

     The wordmark was set in Inter Tight, which is, as the name says, the
     narrow cut of Inter. At 300px that reads as cramped however the tracking
     is adjusted, because it is the letterforms themselves that are narrow,
     not the space between them. Archivo carries a wdth axis, so the display
     role can be genuinely wide rather than merely loosely spaced. */
  --display: "Archivo", "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;

  --gut: clamp(20px, 5vw, 76px);
  --maxw: 1320px;

  --ease: cubic-bezier(.22,.61,.36,1);
  --ease-out: cubic-bezier(.16,1,.3,1);
  --ease-io: cubic-bezier(.65,0,.35,1);

  --nav-h: 68px;
}

*,*::before,*::after{ box-sizing:border-box; }

/* The `hidden` attribute hides things by way of `display:none` in the user
   agent stylesheet, which any author `display` rule silently outranks. Every
   dialog in here sets its own display, so without this the shortcut sheet is
   open on load, and its full-viewport backdrop-filter blurs the entire page
   while looking, at a glance, like a rendering bug. */
[hidden]{ display:none !important; }

html{
  background:var(--ink);
  color-scheme:dark;
  -webkit-text-size-adjust:100%;
  scroll-behavior:auto;      /* JS owns smooth scrolling */
  overflow-x:clip;           /* belt and braces; the real fix is min-width:0 */
}

body{
  margin:0;
  font-family:var(--sans);
  font-size:clamp(16px,.5vw + 14px,18.5px);
  line-height:1.6;
  color:var(--bone);
  background:var(--ink);
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  font-synthesis-weight:none;
  text-rendering:optimizeLegibility;
}

/* Hide the OS cursor only when our custom one is actually live. */
body.has-cursor,
body.has-cursor a,
body.has-cursor button{ cursor:none; }

/* No text selection, anywhere on the page.

   A drag here means orbit the sculpture or grab the cloth. A drag that also
   paints a selection across the headline reads as a broken interaction
   rather than a deliberate one.

   The cost is real and worth stating: nothing on the page can be copied,
   including the shell commands. Anything that should be exempt needs
   `user-select:text` put back on it explicitly. */
*{
  -webkit-user-select:none;
  -moz-user-select:none;
  user-select:none;
  -webkit-tap-highlight-color:transparent;
}

::selection{ background:var(--lime); color:var(--ink); }

.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

.mono{ font-family:var(--mono); font-weight:400; letter-spacing:-.01em; }

a{ color:inherit; text-decoration:none; }

kbd{
  font-family:var(--mono); font-size:.78em; line-height:1;
  padding:.28em .45em; border-radius:4px;
  background:var(--pill); border:1px solid var(--line);
  color:var(--ash); box-shadow:0 1px 0 rgba(0,0,0,.6);
}

code{ font-family:var(--mono); font-size:.92em; color:var(--lime-lt); }

.linkish{
  font:inherit; color:var(--muted); background:none; border:0; padding:0;
  cursor:pointer; border-bottom:1px solid var(--line);
  transition:color .25s var(--ease), border-color .25s var(--ease);
}
.linkish:hover{ color:var(--lime); border-color:var(--lime); }

.accent{ color:var(--lime); }

/* ══ GPU LAYER ═══════════════════════════════════════════════════════════ */

#gl{
  position:fixed; inset:0; width:100%; height:100%;
  display:block; z-index:0;
  background:var(--ink);
  opacity:0; transition:opacity 1.1s var(--ease) .1s;
  touch-action:pan-y;
}
html.gl-up #gl{ opacity:1; }

/* Without a context there is nothing for these two to be about. The chip
   opens a readout with no numbers in it, and a line that begins "everything
   you see moving" is not true of a page where nothing is moving. Both are
   decoration on top of a renderer that did not start. */
html:not(.gl-up) #hud-toggle,
html:not(.gl-up) .hero-footnote,
html:not(.gl-up) .scroll-cue{ display:none; }

/* Film grain + scanline. Pure CSS so it costs the GPU pass nothing. */
#grain{
  position:fixed; inset:-50%; z-index:2; pointer-events:none;
  opacity:.032; mix-blend-mode:overlay;
  background-image:
    repeating-linear-gradient(0deg, rgba(255,255,255,.6) 0 1px, transparent 1px 3px);
  animation:grain 7s steps(6) infinite;
}
@keyframes grain{
  0%{transform:translate(0,0)}    16%{transform:translate(-2%,1%)}
  33%{transform:translate(1%,-2%)}50%{transform:translate(-1%,2%)}
  66%{transform:translate(2%,1%)} 83%{transform:translate(-2%,-1%)}
  100%{transform:translate(0,0)}
}

#vignette{
  position:fixed; inset:0; z-index:2; pointer-events:none;
  background:
    radial-gradient(130% 96% at 50% 44%, transparent 42%, rgba(0,0,0,.5) 100%),
    linear-gradient(180deg, rgba(7,9,10,.6) 0%, transparent 16%, transparent 82%, rgba(7,9,10,.86) 100%);
}

/* ══ BOOT ════════════════════════════════════════════════════════════════ */

#boot{
  position:fixed; inset:0; z-index:120;
  display:grid; place-items:center;
  background:var(--ink);
  transition:opacity .7s var(--ease), visibility .7s;
}
html.booted #boot{ opacity:0; visibility:hidden; pointer-events:none; }

.boot-inner{ display:grid; gap:22px; justify-items:center; width:min(320px,72vw); }
.boot-mark{ color:var(--lime); }
/* The letter is cut once and stays cut. What moves is how much of the block
   exists, which is exactly how much of the load is done: the previous version
   animated the cut instead, so for half of every cycle the mark was a lime
   rectangle with no letter in it. */
#boot-fill{
  clip-path:inset(0 96% 0 0);
  transition:clip-path .45s var(--ease);
}

.boot-bar{
  width:100%; height:2px; background:var(--line-2); overflow:hidden; border-radius:2px;
}
.boot-bar i{
  display:block; height:100%; width:0%; background:var(--lime);
  box-shadow:0 0 14px rgba(198,242,78,.7);
  transition:width .35s var(--ease);
}
.boot-log{
  font-family:var(--mono); font-size:11px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim); min-height:1em;
}

/* ══ NO-WEBGL2 ═══════════════════════════════════════════════════════════ */

#nogl{
  position:fixed; z-index:110; left:50%; top:50%; transform:translate(-50%,-50%);
  width:min(460px,86vw); padding:26px 28px;
  background:var(--pill); border:1px solid var(--line); border-radius:12px;
  text-align:center;
}
#nogl p{ margin:0 0 10px; color:var(--muted); font-size:14.5px; }
#nogl b{ color:var(--bone); }
#nogl button{
  margin-top:8px; font-family:var(--mono); font-size:12px;
  padding:9px 16px; border-radius:7px; cursor:pointer;
  background:var(--lime); color:var(--ink); border:0; font-weight:700;
}

/* ══ HUD ═════════════════════════════════════════════════════════════════ */

#hud{
  position:fixed; z-index:40; right:var(--gut); bottom:26px;
  width:262px; padding:12px 13px 10px;
  font-family:var(--mono); font-size:10.5px; line-height:1.5;
  background:var(--glass);
  -webkit-backdrop-filter:blur(14px) saturate(1.2);
  backdrop-filter:blur(14px) saturate(1.2);
  border:1px solid var(--line); border-radius:10px;
  color:var(--muted);
  opacity:0; transform:translateY(8px) scale(.985); pointer-events:none;
  transition:opacity .35s var(--ease), transform .35s var(--ease);
}
/* pointer-events has to come back, and it never did. The panel was opaque,
   readable and completely inert: every click went through it to the canvas,
   which is why the quality row looked like a control and behaved like a
   picture of one. */
html.hud-on #hud{ opacity:1; transform:none; pointer-events:auto; bottom:74px; }
html.photo #hud, html.photo #hud-toggle, html.photo #nav,
html.photo #rail, html.photo .scroll-cue{ opacity:0 !important; pointer-events:none; }

.hud-row{ display:flex; justify-content:space-between; gap:12px; padding:2px 0; }
.hud-row .k{ color:var(--dim); letter-spacing:.08em; text-transform:uppercase; font-size:9.5px; }
.hud-row .v{ color:var(--ash); text-align:right; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.hud-row .v b{ color:var(--lime); font-weight:500; }
.hud-head{ border-bottom:1px solid var(--line-2); padding-bottom:6px; margin-bottom:4px; }
.hud-head .v{ max-width:170px; }
.hud-foot{ border-top:1px solid var(--line-2); padding-top:6px; margin-top:5px; }
.hud-graph{ margin:7px 0 3px; }

#hud-close{
  font:inherit; font-size:14px; line-height:1; padding:0 0 0 10px; margin-left:auto;
  background:none; border:0; color:var(--dim); cursor:pointer; transition:color .18s;
}
#hud-close:hover{ color:var(--lime); }

/* Four tiers and an auto. Picking one pins it: the controller stops arguing,
   and the resolution goes on adapting underneath, because pinning is a
   statement about quality and not a request to drop frames. */
.hud-tiers{ display:flex; gap:4px; margin-top:8px; }
.hud-tiers button{
  flex:1 1 0; min-width:0; font:inherit; font-size:9.5px;
  letter-spacing:.08em; text-transform:uppercase;
  padding:5px 0; border-radius:5px; cursor:pointer;
  background:transparent; border:1px solid var(--line-2); color:var(--dim);
  transition:color .18s var(--ease), border-color .18s var(--ease), background .18s var(--ease);
}
.hud-tiers button:hover{ color:var(--bone); border-color:var(--line); }
.hud-tiers button.is-on{
  color:var(--ink); background:var(--lime); border-color:var(--lime); font-weight:600;
}
.hud-hint{ margin:8px 0 0; color:var(--dim); font-size:9.5px; letter-spacing:.06em; text-align:right; }
.hud-hint b{ color:var(--ash); font-weight:400; }

/* Every control in here is reachable by keyboard, so every one of them has
   to show where the focus is. */
#hud-close:focus-visible,
.hud-tiers button:focus-visible,
#hud-toggle:focus-visible{ outline:2px solid var(--lime); outline-offset:2px; }
#hud-graph{ display:block; width:100%; height:34px; opacity:.9; }

#hud-toggle{
  position:fixed; z-index:41; right:var(--gut); bottom:26px;
  display:flex; align-items:center; gap:7px;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.14em; text-transform:uppercase;
  padding:8px 12px; border-radius:20px; cursor:pointer;
  background:var(--glass);
  -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
  border:1px solid var(--line); color:var(--muted);
  transition:opacity .3s var(--ease), color .25s, border-color .25s;
}
/* The chip is the toggle, so it has to stay reachable while the thing it
   opened is on screen. Fading it out left the readout with no way to close
   it but a keyboard shortcut nobody had been told about. It moves out from
   under the panel instead, and says what it will do. */
html.hud-on #hud-toggle{ color:var(--lime); border-color:rgba(198,242,78,.45); }
#hud-toggle .lbl-on{ display:none; }
html.hud-on #hud-toggle .lbl{ display:none; }
html.hud-on #hud-toggle .lbl-on{ display:inline; }
#hud-toggle:hover{ color:var(--lime); border-color:rgba(198,242,78,.4); }
#hud-toggle .dot{
  width:6px; height:6px; border-radius:50%; background:var(--lime);
  box-shadow:0 0 8px var(--lime); animation:pulse 2.4s var(--ease-io) infinite;
}
@keyframes pulse{ 0%,100%{opacity:.35} 50%{opacity:1} }

/* ══ NAV ═════════════════════════════════════════════════════════════════ */

#nav{
  position:fixed; z-index:50; top:0; left:0; right:0; height:var(--nav-h);
  display:flex; align-items:center; justify-content:space-between;
  padding:0 var(--gut);
  transition:background .4s var(--ease), border-color .4s var(--ease), transform .5s var(--ease);
  border-bottom:1px solid transparent;
}
/* Opaque enough that type scrolling underneath disappears rather than
   ghosting through half-legible, which is what made the last section look
   broken rather than merely scrolled. */
html.scrolled #nav{
  background:linear-gradient(180deg, rgba(7,9,10,.97) 0%, rgba(7,9,10,.93) 62%, rgba(7,9,10,0) 100%);
  -webkit-backdrop-filter:blur(16px); backdrop-filter:blur(16px);
  border-bottom-color:var(--line-2);
}
html.nav-hide #nav{ transform:translateY(-100%); }

.nav-mark{ color:var(--bone); display:flex; align-items:center; transition:color .3s; }
.nav-mark:hover{ color:var(--lime); }

#nav nav{ display:flex; align-items:center; gap:clamp(14px,2vw,30px); }
#nav nav a{
  font-family:var(--mono); font-size:13px; letter-spacing:.08em;
  color:var(--muted); position:relative; padding:6px 0;
  transition:color .28s var(--ease);
}
#nav nav a::after{
  content:""; position:absolute; left:0; right:100%; bottom:0; height:1px;
  background:var(--lime); transition:right .4s var(--ease-out);
}
#nav nav a:hover{ color:var(--bone); }
#nav nav a:hover::after{ right:0; }
#nav nav a.is-current{ color:var(--lime); }
#nav nav a.is-current::after{ right:0; opacity:.5; }

#nav nav a.cta{
  color:var(--ink); background:var(--lime); font-weight:700;
  padding:8px 15px; border-radius:20px;
  transition:transform .3s var(--ease), box-shadow .3s var(--ease), background .3s;
}
#nav nav a.cta::after{ display:none; }
#nav nav a.cta:hover{
  background:var(--lime-lt);
  box-shadow:0 0 0 1px rgba(198,242,78,.3), 0 8px 30px -8px rgba(198,242,78,.6);
}

/* ══ SCROLL RAIL ═════════════════════════════════════════════════════════ */

#rail{
  position:fixed; z-index:40; left:calc(var(--gut) - 14px); top:50%;
  transform:translateY(-50%);
  width:1px; height:min(46vh,360px);
  background:var(--line-2);
}
#rail-fill{
  position:absolute; left:0; top:0; width:1px; height:0;
  background:linear-gradient(180deg,var(--lime),rgba(198,242,78,.25));
  box-shadow:0 0 10px rgba(198,242,78,.4);
}
#rail ol{ list-style:none; margin:0; padding:0; }
#rail li{
  position:absolute; left:-4px; width:9px; height:9px; transform:translateY(-50%);
  display:grid; place-items:center; cursor:pointer;
}
#rail li::before{
  content:""; width:3px; height:3px; border-radius:50%;
  background:var(--dim); transition:all .3s var(--ease);
}
#rail li.is-on::before{ background:var(--lime); width:5px; height:5px; box-shadow:0 0 8px var(--lime); }
/* The name stays for anything reading the page aloud and comes off the screen.

   It sat 16px to the right of a dot that is already most of the way across the
   gutter, so at 1366 the label ran from x66 into a content box starting at
   x16, and the current section's name was printed permanently over the copy.
   Widening the gutter would only move the problem to the next narrower screen.

   Nothing is lost by taking it out: every section already prints its own name
   in the top right corner, which is where the eye goes for it. What the rail
   is for is where you are and how far along, and the dot and the fill say
   both without a word. */
#rail li span{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

@media (max-width:900px){ #rail{ display:none; } }

/* ══ SECTIONS ════════════════════════════════════════════════════════════ */

main{ position:relative; z-index:10; }

/* Content-height sections, not a stack of full screens.

   Every section used to be min-height:100svh with its content vertically
   centred, so a short section carried most of a screenful of empty space
   above and below it, and scrolling felt like paging through slides rather
   than reading a page. Only the hero is a full screen now; the rest are as
   tall as their content, with padding doing the separating. */
.sec{
  position:relative;
  /* Named, because the section label is positioned against it and the two
     drifting apart is what put the label under the navigation bar. */
  --sec-pad:clamp(112px,15vh,208px);
  padding:var(--sec-pad) 0;
  /* Short sections used to let the next one's heading up over the fold. On a
     1920 screen whoami is 717px of a 1080 viewport, so after a jump landed on
     it you read four paragraphs and then found "$ materials" sitting at the
     bottom of the same screen. Filling the viewport is not the same thing as
     the full-height sections this page deliberately does not have: the tall
     ones are unaffected, and nothing here snaps or pages. */
  min-height:100svh;
}
.sec-hero{
  padding-top:0;
  min-height:100svh;
  display:flex; align-items:center;
}
.wrap{ width:100%; max-width:var(--maxw); margin:0 auto; padding:0 var(--gut); }

/* A soft scrim so type stays readable wherever the 3D happens to be bright. */
.sec:not(.sec-hero)::before{
  content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:radial-gradient(120% 78% at 26% 50%, rgba(7,9,10,.86) 0%, rgba(7,9,10,.55) 45%, transparent 78%);
}
.sec-work::before, .sec-numbers::before{
  background:linear-gradient(180deg, transparent, rgba(7,9,10,.82) 14%, rgba(7,9,10,.88) 86%, transparent);
}

/* The label is anchored to the section, and a jump lands 56px into the
   section's content, so a value picked independently of the padding rides up
   under the navigation bar as the viewport grows: measured at 1920 it sat 20px
   above the bar's lower edge, at 1440 seven, at 1280 exactly on it.

   Deriving it from the same padding fixes it at every size at once. The label
   ends up 32px below the bar after a jump, on every viewport, because both
   numbers now come from one. */
.sec::after{
  content:attr(data-label);
  position:absolute; top:calc(var(--sec-pad) - 24px); right:var(--gut);
  font-family:var(--mono); font-size:11px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--dim); opacity:.65; pointer-events:none;
}
@media (max-width:700px){ .sec::after{ display:none; } }

/* ══ REVEAL ══════════════════════════════════════════════════════════════ */

[data-reveal]{
  opacity:0; transform:translateY(14px);
  transition:opacity .58s var(--ease-out), transform .58s var(--ease-out);
  transition-delay:calc(var(--d,0) * 42ms);
}
[data-reveal].in{ opacity:1; transform:none; }
html[data-nojs] [data-reveal]{ opacity:1; transform:none; }

/* ══ HERO ════════════════════════════════════════════════════════════════ */


.eyebrow{
  display:flex; align-items:center; gap:10px; margin:0 0 clamp(18px,3vh,34px);
  font-family:var(--mono); font-size:12.5px; letter-spacing:.2em; text-transform:uppercase;
  color:var(--muted);
}
.eyebrow .tick{
  width:5px; height:5px; border-radius:50%; background:var(--lime);
  box-shadow:0 0 10px var(--lime); animation:pulse 2.6s var(--ease-io) infinite;
}

.wordmark{
  margin:0;
  font-family:var(--display);
  font-weight:780;
  font-stretch:116%;            /* Archivo's wdth axis; ignored by the fallback */
  font-size:clamp(72px, 18vw, 286px);
  line-height:.86;
  /* -0.055em was clamping five already-narrow letters together. With wider
     letterforms the tracking only needs to take the optical slack out of a
     large size, not to compensate for the face. */
  letter-spacing:-.012em;
  color:var(--chalk);
  /* NOT display:flex.

     Each letter is its own span so the hover can cascade across them, and as
     flex items the browser lays every one out as an independent box, which
     means no kerning is applied between them at all. On a face with real
     kerning pairs that reads as subtly, unplaceably wrong: the gaps come out
     mechanically equal instead of optically equal. Inline layout shapes the
     whole word as text, which is what it is. */
  display:block;
  white-space:nowrap;
  text-shadow:0 0 90px rgba(7,9,10,.75);
}
.wordmark .letter{
  transition:color .5s var(--ease);
}
.wordmark:hover .letter{ color:var(--lime); transition-delay:calc(var(--i,0)*40ms); }

.tagline{
  margin:clamp(18px,3.4vh,36px) 0 0;
  font-size:clamp(19px,2.5vw,32px); font-weight:300; letter-spacing:-.02em;
  color:var(--ash);
}

.rotator{
  margin:clamp(14px,2vh,20px) 0 0;
  font-size:clamp(13.5px,1.2vw,16px); color:var(--muted);
  min-height:1.6em; letter-spacing:0;
}
.rotator .caret{ color:var(--lime); }
.rotator .cursor{ color:var(--lime); animation:blink 1.05s steps(2) infinite; }
@keyframes blink{ 50%{opacity:0} }

.hero-meta{
  display:flex; flex-wrap:wrap; gap:clamp(12px,2vw,26px); align-items:center;
  margin:clamp(28px,5vh,54px) 0 0;
  font-size:13px; color:var(--dim); letter-spacing:.06em;
}
.hero-meta b{ color:var(--bone); font-weight:500; font-variant-numeric:tabular-nums; }
.hero-meta .live{ display:inline-flex; align-items:center; gap:6px; color:var(--dim); }
.hero-meta .live i{
  width:5px; height:5px; border-radius:50%; background:var(--dim); transition:background .4s;
}
.hero-meta .live.is-live i{ background:var(--lime); box-shadow:0 0 8px var(--lime); }

.scroll-cue{
  position:absolute; left:50%; bottom:26px; transform:translateX(-50%);
  display:grid; justify-items:center; gap:8px;
  font-size:11px; letter-spacing:.2em; text-transform:uppercase; color:var(--dim);
  transition:color .3s, opacity .5s;
}
.scroll-cue:hover{ color:var(--lime); }
.scroll-cue svg{ animation:cue 2.6s var(--ease-io) infinite; }
@keyframes cue{ 0%,100%{transform:translateY(0);opacity:.4} 50%{transform:translateY(5px);opacity:1} }
html.scrolled .scroll-cue{ opacity:0; pointer-events:none; }

/* The one line on the page that asks for something. It sits under the counts
   rather than beside the tagline, because a hero that opens with an ask reads
   as a pitch, and this page would rather demonstrate first and ask second. */
.hero-offer{
  margin-top:18px; font-size:12px; letter-spacing:.06em; color:var(--dim);
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
}
.hero-offer .sep{ color:var(--line); }
.hero-offer a{
  color:var(--lime); border-bottom:1px solid rgba(198,242,78,.35);
  padding-bottom:1px; transition:border-color .25s var(--ease);
}
.hero-offer a:hover{ border-bottom-color:var(--lime); }

.hero-footnote{
  position:absolute; left:var(--gut); bottom:26px;
  font-size:12px; color:var(--dim); letter-spacing:.05em; margin:0;
  max-width:min(46ch,60vw);
}
@media (max-width:820px){ .hero-footnote{ display:none; } }

/* ══ TYPE BLOCKS ═════════════════════════════════════════════════════════ */

.h-mono{
  margin:0 0 clamp(20px,3vh,32px);
  font-family:var(--mono); font-weight:500;
  font-size:clamp(17px,1.6vw,21px); letter-spacing:.02em;
  color:var(--bone);
}
.h-mono .prompt{ color:var(--lime); margin-right:.6em; }

.sec-head{ max-width:70ch; margin-bottom:clamp(34px,6vh,64px); }
/* Two settings, and the choice between them is the paragraph's length.

   balance evens every line, so a four-line block cannot end on one stubby
   word. Browsers cap it at a handful of lines, which is exactly the range
   the short blocks here live in: ledes, headings, card copy.

   pretty is the fallback for anything longer. It only protects the last
   line, which is all that is needed once a paragraph is tall enough that
   even lines stop mattering.

   Neither can promise a short sentence a line of its own. Where the writing
   wants that, the sentence gets its own element: see .sec-sub-lead. */
.sec-sub{
  margin:0; color:var(--muted);
  font-size:clamp(16px,1.05vw,18.5px); max-width:60ch;
  text-wrap:balance;
}
.sec-sub-lead{ margin-top:1.1em; color:var(--ash); }
.sec-sub b{ color:var(--bone); font-weight:500; }

.lede{
  font-family:var(--display); font-stretch:104%;
  font-size:clamp(21px,2.05vw,29px); font-weight:400; line-height:1.32;
  letter-spacing:-.014em; color:var(--chalk); margin:0 0 26px; max-width:24ch;
  text-wrap:balance;
}

.grid-2{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,5vw,80px); align-items:start; }
@media (max-width:900px){ .grid-2{ grid-template-columns:1fr; } }

/* Grid and flex items default to `min-width:auto`, which resolves to their
   MIN-CONTENT size, so one long unbreakable line inside the code block was
   forcing its track wider than the viewport, and `overflow-x:auto` on the
   <pre> could not save it because the track itself refused to shrink.
   On a 375px phone the page laid out 554px wide because of this.
   `min-width:0` is the fix, and it belongs on every such container. */
.grid-2 > *,
.mat-lab > *,
.phys-panel > *,
.codeblock,
.yaml{ min-width:0; }

/* :not(.lede) because .col p outranks .lede on specificity, and the lede is
   a p inside a col. It was quietly losing its colour, its margin, its
   measure and its wrap setting to this one line. */
.col p:not(.lede){ color:var(--muted); margin:0 0 18px; max-width:52ch; text-wrap:pretty; }
.col p:last-child{ margin-bottom:0; }

/* ══ CODE BLOCK ══════════════════════════════════════════════════════════ */

.codeblock{
  border:1px solid var(--line); border-radius:12px; overflow:hidden;
  background:rgba(16,20,14,.66);
  -webkit-backdrop-filter:blur(18px) saturate(1.15);
  backdrop-filter:blur(18px) saturate(1.15);
  box-shadow:0 30px 70px -40px rgba(0,0,0,.9);
}
.codeblock-bar{
  display:flex; align-items:center; gap:6px; padding:9px 13px;
  border-bottom:1px solid var(--line-2); background:rgba(7,9,10,.5);
}
.codeblock-bar span{ width:8px; height:8px; border-radius:50%; background:var(--line); }
.codeblock-bar span:first-child{ background:rgba(198,242,78,.5); }
.codeblock-bar em{
  margin-left:auto; font-style:normal; font-size:11px;
  letter-spacing:.12em; color:var(--dim); text-transform:uppercase;
}
/* This was a <pre> with the keys padded out to a column by hand, which
   meant one horizontal scrollbar and half the longest value cut off mid
   word at every width. A two-column grid aligns the same way without
   asking the text to fit on one line: the keys size to their content, the
   values take the rest and wrap inside it. Whitespace-only runs between
   the spans are not rendered as grid items, so the source stays readable. */
.yaml{
  display:grid; grid-template-columns:max-content minmax(0,1fr);
  column-gap:1.4ch; row-gap:.34em;
  padding:18px 16px;
  font-size:clamp(12.5px,.9vw,14.5px); line-height:1.75; color:var(--muted);
}
.y-k{ color:var(--ash); }
.y-k::after{ content:":"; color:var(--dim); }
.y-v{ min-width:0; overflow-wrap:break-word; }
.y-v i{ font-style:normal; }
.y-s{ color:var(--muted); }
.y-a{ color:var(--lime); }
.y-x{ color:#C97B6B; }
.y-l{ color:var(--lime-lt); text-decoration:underline; text-decoration-color:rgba(228,255,155,.3); }

/* ══ MATERIALS ═══════════════════════════════════════════════════════════ */

/* The palette is in the scene now: six orbs on a ring around the
   sculpture, clicked to be absorbed. This column is the panel that names
   whichever one is on, plus a row of chips that does the same job for a
   keyboard, a screen reader, and a thumb. */
.mat-lab{ max-width:min(52ch, 100%); }

.mat-chips{
  list-style:none; display:flex; flex-wrap:wrap; gap:8px;
  margin:26px 0 14px; padding:0;
}
.mat-chips button{
  display:flex; align-items:center; gap:8px;
  padding:9px 14px; border-radius:22px; cursor:pointer;
  background:transparent; border:1px solid var(--line); color:var(--muted);
  font-family:var(--mono); font-size:12px; letter-spacing:.03em;
  transition:color .25s var(--ease), border-color .25s var(--ease), background .25s var(--ease);
}
.mat-chips button:hover{ color:var(--bone); border-color:var(--muted); }
.mat-chips .num{ opacity:.5; }
.mat-chips li.is-on button{
  color:var(--ink); background:var(--lime); border-color:var(--lime); font-weight:700;
}
.mat-chips li.is-on .num{ opacity:.65; }

.mat-detail{ min-height:220px; max-width:620px; }
.mat-name{
  margin:0 0 10px;
  font-family:var(--display); font-stretch:106%;
  font-size:clamp(25px,3.1vw,42px); font-weight:660;
  letter-spacing:-.014em; color:var(--chalk); line-height:1.08;
}
.mat-desc{ margin:0 0 24px; color:var(--muted); max-width:46ch; font-size:16.5px; text-wrap:pretty; }
/* Flex, not grid, because the four values are not four similar widths. An
   IOR triple is three numbers and two slashes; the cell beside it holds the
   word "2". Equal columns made the wide one wrap into a three-line stack
   next to three cells that were mostly air. min-width:max-content sizes
   every cell to its own content, flex-grow shares out what is left, and a
   row that will not fit wraps whole rather than breaking a value. */
.mat-params{
  display:flex; flex-wrap:wrap;
  gap:1px; margin:0 0 22px; max-width:620px;
  background:var(--line-2); border:1px solid var(--line-2);
}
.mat-params div{
  background:var(--ink); padding:11px 12px;
  flex:1 1 auto; min-width:max-content;
}
.mat-params dt{ font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--dim); }
.mat-params dd{ margin:5px 0 0; font-size:16px; color:var(--lime); font-variant-numeric:tabular-nums; }
.mat-hint{ font-size:12px; color:var(--dim); letter-spacing:.06em; margin:0; }

/* ══ WORK ════════════════════════════════════════════════════════════════ */

.work-filters{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:26px; }
.chip{
  font-family:var(--mono); font-size:12px; letter-spacing:.1em;
  padding:9px 15px; border-radius:20px; cursor:pointer;
  background:transparent; border:1px solid var(--line); color:var(--dim);
  transition:all .28s var(--ease);
}
.chip:hover{ color:var(--bone); border-color:var(--muted); }
.chip.is-on{ color:var(--ink); background:var(--lime); border-color:var(--lime); font-weight:700; }

.work-grid{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(min(100%,320px),1fr));
  gap:1px; background:var(--line-2);
  border:1px solid var(--line-2);
}
.work-grid li{ background:var(--ink); transition:opacity .4s var(--ease), filter .4s; }
.work-grid li.is-off{ display:none; }

.card{
  display:flex; flex-direction:column; height:100%;
  padding:clamp(20px,2.2vw,30px); position:relative; overflow:hidden;
  transform-style:preserve-3d; will-change:transform;
  transition:background .4s var(--ease);
}
.card::before{
  content:""; position:absolute; inset:0; opacity:0; pointer-events:none;
  background:radial-gradient(320px 320px at var(--mx,50%) var(--my,50%), rgba(198,242,78,.10), transparent 70%);
  transition:opacity .35s var(--ease);
}
.card:hover::before{ opacity:1; }
.card:hover{ background:rgba(28,36,24,.30); }

.card-top{ display:flex; align-items:center; gap:10px; margin-bottom:14px; }
.card-name{
  font-family:var(--mono); font-size:17.5px; font-weight:500; color:var(--chalk);
  letter-spacing:-.01em; margin:0;
}
.card-name::before{ content:""; color:var(--dim); }
.card:hover .card-name{ color:var(--lime); }
.card-arrow{
  margin-left:auto; color:var(--dim); font-size:15px;
  transition:transform .4s var(--ease-out), color .3s;
}
.card:hover .card-arrow{ transform:translate(3px,-3px); color:var(--lime); }

.card-desc{ margin:0 0 auto; color:var(--muted); font-size:15.5px; line-height:1.6; text-wrap:balance; }

.card-tags{
  list-style:none; display:flex; flex-wrap:wrap; gap:5px; margin:18px 0 0; padding:0;
}
.card-tags li{
  font-family:var(--mono); font-size:11px; letter-spacing:.06em;
  padding:4px 8px; border-radius:4px; background:var(--pill); color:var(--dim);
  border:1px solid var(--line-2);
}

.card-stats{
  display:flex; align-items:center; gap:14px; margin-top:16px;
  padding-top:14px; border-top:1px solid var(--line-2);
  font-family:var(--mono); font-size:12px; color:var(--dim);
}
.card-stats b{ color:var(--ash); font-weight:400; font-variant-numeric:tabular-nums; }
.card-stats .lang{ display:flex; align-items:center; gap:6px; margin-left:auto; }
.card-stats .lang i{ width:7px; height:7px; border-radius:50%; }

.card-bar{
  position:absolute; left:0; right:0; bottom:0; height:1px;
  background:var(--lime); transform:scaleX(0); transform-origin:0 50%;
  transition:transform .5s var(--ease-out);
}
.card:hover .card-bar{ transform:none; }

/* ══ NUMBERS ═════════════════════════════════════════════════════════════ */

/* 240px, not 190px. "1,572,864" set at 42px is about 200px wide and the cell
   content box was 144px, so the longest figures in this grid were overflowing
   by up to 50px, which the layout audit caught and no screenshot did. */
.numbers{
  list-style:none; margin:0 0 26px; padding:0;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));
  gap:1px; background:var(--line-2); border:1px solid var(--line-2);
}
.numbers li{ background:var(--ink); padding:clamp(18px,2vw,26px); }
.numbers .n{
  display:block;
  font-family:var(--display); font-stretch:100%;
  font-size:clamp(21px,2.15vw,32px); font-weight:640;
  letter-spacing:-.02em; color:var(--lime); line-height:1.05;
  font-variant-numeric:tabular-nums;
}
.numbers li{ min-width:0; }
.numbers .n em{ font-style:normal; font-size:.45em; color:var(--muted); margin-left:.25em; letter-spacing:0; }
.numbers .l{
  display:block; margin-top:9px;
  font-family:var(--mono); font-size:11.5px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--dim);
}
.numbers-note{ font-size:13px; color:var(--dim); max-width:62ch; line-height:1.9; margin:0; }
.numbers-note b{ color:var(--ash); font-weight:400; }

/* ══ CONTACT ═════════════════════════════════════════════════════════════ */

.sec-contact{ padding-bottom:clamp(60px,7vh,110px); }
/* Sized so the whole contact block fits one screen.

   At 98px over two lines the section was taller than the viewport, so
   scrolling to the end left the headline sliced in half and sitting under
   the navigation bar. Nothing was broken; it was simply too big to land. */
.contact-h{
  margin:0 0 clamp(30px,5vh,54px);
  font-family:var(--display); font-stretch:108%;
  font-size:clamp(34px,5.1vw,72px); font-weight:720; line-height:1.0;
  letter-spacing:-.018em; color:var(--chalk); text-wrap:balance;
}

.links{ list-style:none; margin:0 0 clamp(32px,5vh,58px); padding:0; border-top:1px solid var(--line-2); }
.links li{ border-bottom:1px solid var(--line-2); }
.links a{
  display:flex; align-items:center; gap:clamp(14px,2vw,26px);
  padding:clamp(15px,1.7vw,21px) 4px; position:relative;
  transition:padding-left .45s var(--ease-out), color .3s;
  color:var(--bone);
}
.links a::before{
  content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(90deg, rgba(198,242,78,.07), transparent 55%);
  opacity:0; transition:opacity .4s var(--ease);
}
.links a:hover{ padding-left:16px; }
.links a:hover::before{ opacity:1; }
.l-i{ color:var(--dim); display:flex; transition:color .3s; }
.links a:hover .l-i{ color:var(--lime); }
.l-t{ display:grid; gap:2px; }
.l-t b{ font-size:clamp(19px,2vw,27px); font-weight:500; letter-spacing:-.02em; }
.l-t em{ font-style:normal; font-family:var(--mono); font-size:12px; letter-spacing:.08em; color:var(--dim); }
.l-a{ margin-left:auto; color:var(--dim); font-size:17px; transition:transform .4s var(--ease-out), color .3s; }
.links a:hover .l-a{ transform:translate(4px,-4px); color:var(--lime); }

.foot{
  display:flex; flex-wrap:wrap; gap:10px; align-items:center;
  font-size:12px; letter-spacing:.06em; color:var(--dim);
  padding-top:22px; border-top:1px solid var(--line-2);
}
.foot .sep{ opacity:.4; }

/* ══ SHEET ═══════════════════════════════════════════════════════════════ */

#sheet{
  position:fixed; inset:0; z-index:100; display:grid; place-items:center;
  background:rgba(7,9,10,.72);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  animation:fade .3s var(--ease);
}
@keyframes fade{ from{opacity:0} }
.sheet-card{
  width:min(420px,88vw); padding:26px 28px;
  background:var(--pill); border:1px solid var(--line); border-radius:14px;
  box-shadow:0 40px 90px -40px #000;
}
.sheet-card h3{
  margin:0 0 18px; font-size:11px; letter-spacing:.2em; text-transform:uppercase;
  color:var(--lime); font-weight:500;
}
.sheet-card dl{ margin:0 0 22px; display:grid; gap:9px; font-size:12px; }
.sheet-card dl > div{ display:flex; justify-content:space-between; align-items:center; gap:16px; }
.sheet-card dt{ color:var(--muted); }
.sheet-card dd{ margin:0; display:flex; gap:4px; }

/* ══ CURSOR ══════════════════════════════════════════════════════════════ */

#cursor{ position:fixed; inset:0; z-index:90; pointer-events:none; opacity:0; transition:opacity .3s; }
body.has-cursor #cursor{ opacity:1; }
.c-dot,.c-ring{
  position:absolute; left:0; top:0; border-radius:50%;
  transform:translate(-50%,-50%); will-change:transform;
}
.c-dot{ width:5px; height:5px; background:var(--lime); box-shadow:0 0 10px var(--lime); }
.c-ring{
  width:30px; height:30px; border:1px solid rgba(198,242,78,.45);
  transition:width .3s var(--ease), height .3s var(--ease), border-color .3s, background .3s;
}
body.cur-hot .c-ring{ width:52px; height:52px; border-color:var(--lime); background:rgba(198,242,78,.08); }
body.cur-drag .c-ring{ width:64px; height:64px; border-color:var(--lime); background:rgba(198,242,78,.13); }
.c-lbl{
  position:absolute; left:0; top:0; transform:translate(-50%,-50%);
  font-family:var(--mono); font-size:8.5px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--lime); opacity:0; transition:opacity .25s; white-space:nowrap;
}
body.cur-drag .c-lbl{ opacity:1; }

/* ══ FLASH ═══════════════════════════════════════════════════════════════ */

#flash{
  position:fixed; z-index:95; left:50%; bottom:34px; transform:translate(-50%,14px);
  font-family:var(--mono); font-size:11px; letter-spacing:.1em;
  padding:10px 16px; border-radius:22px;
  background:var(--pill); border:1px solid var(--line); color:var(--ash);
  opacity:0; pointer-events:none;
  transition:opacity .3s var(--ease), transform .35s var(--ease);
}
#flash.on{ opacity:1; transform:translate(-50%,0); }
#flash b{ color:var(--lime); font-weight:500; }

/* ══ RESPONSIVE / MOTION ═════════════════════════════════════════════════ */

/* Below this width the value column is too narrow to be worth aligning, so
   the key gets its own line and the value runs the full width under it. */
@media (max-width:640px){
  .yaml{
    grid-template-columns:minmax(0,1fr); row-gap:0;
    font-size:11.5px; line-height:1.7;
  }
  .y-k{ margin-top:.7em; }
  .y-k:first-child{ margin-top:0; }
}

@media (max-width:760px){
  :root{ --nav-h:58px; }

  /* On a phone the type runs the full width, so there is nowhere for the 3D
     to go that is not behind something being read. It becomes atmosphere:
     one scrim across every section, strong enough that copy always wins. */
  .sec::before{
    content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
    background:linear-gradient(180deg,
      rgba(7,9,10,.55) 0%, rgba(7,9,10,.86) 28%, rgba(7,9,10,.90) 100%) !important;
  }

  .chip{ padding:11px 16px; }
  .mat-chips button{ padding:12px 16px; }
  /* These used to be hidden, which left a phone with no way to reach a
     section short of scrolling past everything in front of it: the rail is
     display:none here too, so the only thing left in the bar was the contact
     button. They fit. At 11px with a tighter gap the row measures 270px
     inside 390 and clears the wordmark by twenty.

     The type is small, so the tap target is made out of padding rather than
     out of font size: 44px tall inside a 58px bar, while the letters stay
     where the design wants them. */
  #nav nav{ gap:12px; }
  #nav nav a:not(.cta){
    display:inline-flex; align-items:center;
    font-size:11px; padding:14px 0; min-height:44px;
  }
  @media (max-width:400px){
    #nav nav{ gap:10px; }
    #nav nav a:not(.cta){ font-size:10px; }
  }
  /* The readout is a 262px developer panel with a graph in it, and this is a
     390px screen with no keyboard to press G on, so it does not come here.
     The chip that opens it went on being drawn though: a control that does
     nothing, sitting in the corner over whatever the page put there, eating
     the tap meant for a material. Both go. */
  #hud, #hud-toggle{ display:none; }

  /* Two tap targets came in under the 24px floor: the wordmark at 22 and the
     shortcuts button at 20. Padding, not type size, again. And the shortcuts
     sheet lists keys, which is a strange offer to make to a device with no
     keyboard, so on a phone it stops being offered. */
  .nav-mark{ display:inline-flex; align-items:center; min-height:44px; }
  .linkish[data-action="shortcuts"]{ display:none; }
  .sec{ padding:clamp(86px,11vh,132px) 0; }
}

@media (prefers-reduced-motion:reduce){
  *{ animation-duration:.001ms !important; animation-iteration-count:1 !important;
     transition-duration:.001ms !important; scroll-behavior:auto !important; }
  [data-reveal]{ opacity:1 !important; transform:none !important; }
  #grain{ animation:none; }
}

@media (prefers-contrast:more){
  :root{ --muted:#B4BAAB; --dim:#868C7E; --line:rgba(233,237,227,.24); }
}

:focus-visible{ outline:2px solid var(--lime); outline-offset:3px; border-radius:3px; }
