@import url("tokens.css");

/* ===========================================================================
   Northgate Labs — redesign 2026-08-24

   The first cut passed every gate and still read as underwhelming. Two causes,
   both structural rather than decorative:

     1. NOTHING MOVED. A page that is fully painted before you look at it gives
        the eye no sequence, so a visitor has no idea what to read first.
     2. THE TYPE WAS POLITE. 55px headlines with generous line-height read as a
        brochure. Confidence in a landing page is expressed in scale and tight
        tracking, not in more words.

   Motion is done with CSS scroll-driven animations -- `animation-timeline:
   view()` and `scroll()` -- so there is still NO JAVASCRIPT. That keeps the CSP
   at `default-src 'none'`, keeps the page a few KB, and keeps the animation off
   the main thread.

   Support is Chrome/Edge 115+, Safari 18+, ~84% of traffic; Firefox has it
   behind a flag and is NOT baseline. So every animated element is authored in
   its FINISHED state and the animation is layered on top -- a browser that does
   not understand `animation-timeline` ignores it and shows the final design.
   Nothing is ever hidden behind an animation that might not run.
   =========================================================================== */

*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0;background:#fff;color:var(--n-ink);
  font:var(--text-0)/1.62 var(--sans);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
h1,h2,h3{font-family:var(--serif);color:var(--b-black);margin:0}
h1{font-size:var(--text-5);line-height:1.02;letter-spacing:-.03em}
h2{font-size:var(--text-4);line-height:1.06;letter-spacing:-.022em}
h3{font-size:var(--text-1);line-height:1.2;letter-spacing:-.01em}
p{margin:0 0 var(--space-s)}
a{color:var(--b-ink)}
img,svg{max-width:100%;height:auto;display:block}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 var(--space-s)}
:focus-visible{outline:3px solid var(--brand);outline-offset:3px;border-radius:3px}
.skip{position:absolute;left:-9999px}
.skip:focus{left:12px;top:12px;z-index:99;background:#fff;padding:12px 18px;border-radius:8px}

/* --------------------------------------------------------- scroll progress */
/* A 3px rule across the top that fills as you read. Tied to the document
   scroller, so it is a genuine reading position rather than a guess. */
.progress{position:fixed;top:0;left:0;height:3px;width:100%;transform-origin:0 50%;
  background:var(--brand);z-index:80;transform:scaleX(0)}
@supports (animation-timeline:scroll()){
  .progress{animation:grow linear both;animation-timeline:scroll(root block)}
}
@keyframes grow{to{transform:scaleX(1)}}

/* ---------------------------------------------------------------- header */
header{position:sticky;top:0;z-index:70;background:rgba(255,255,255,.86);
  backdrop-filter:blur(10px);border-bottom:1px solid var(--n-tint)}
header .wrap{display:flex;align-items:center;justify-content:space-between;
  gap:var(--space-s);padding:var(--space-xs) var(--space-s)}
.brand{display:flex;align-items:center;gap:10px;text-decoration:none;color:inherit}
.brand svg{width:30px;height:30px;color:var(--brand);flex:none}
.brand > span{display:block}
.brand b{display:block;font-family:var(--serif);font-size:var(--text-1);
  font-weight:700;color:var(--b-black);line-height:1;letter-spacing:-.02em}
.brand > span > span{display:block;font-size:var(--text-2xs);letter-spacing:.28em;
  color:var(--n-body);text-transform:uppercase;margin-top:3px}
nav{display:none;gap:var(--space-m);align-items:center}
@media(min-width:760px){nav{display:flex}}
nav a{color:var(--n-ink);text-decoration:none;font-size:var(--text-1xs);font-weight:600}
nav a:hover{color:var(--b-ink)}

/* ------------------------------------------------------------------ hero */
.hero{position:relative;background:var(--b-black);color:#fff;overflow:hidden;
  padding:calc(var(--space-3xl) * 1.15) 0 var(--space-3xl)}
/* The mark, enormous and barely there, bled off the right edge. It fills the
   dead space the first version left there without competing with the words. */
.hero-mark{position:absolute;right:-11%;top:48%;transform:translateY(-50%);
  width:min(620px,62vw);color:var(--brand);opacity:.20;pointer-events:none}
@supports (animation-timeline:view()){
  .hero-mark{animation:drift linear both;animation-timeline:scroll(root block);
    animation-range:0 90vh}
}
@keyframes drift{to{transform:translateY(-50%) translateY(70px) rotate(-3deg)}}

.hero h1{color:#fff;max-width:15ch;position:relative}
.hero .lead{font-size:var(--text-1);color:var(--b-line);max-width:46ch;
  margin-top:var(--space-m);position:relative}
.eyebrow{display:inline-block;font-size:var(--text-2xs);font-weight:700;
  letter-spacing:.24em;text-transform:uppercase;color:var(--b-edge);
  margin-bottom:var(--space-s)}
.cta{display:inline-flex;align-items:center;gap:10px;margin-top:var(--space-l);
  background:#fff;color:var(--b-black);text-decoration:none;font-weight:700;
  padding:16px 30px;border-radius:6px;position:relative;
  transition:transform .18s ease, box-shadow .18s ease}
.cta:hover{transform:translateY(-2px);box-shadow:0 10px 26px rgba(0,0,0,.28)}

/* Staggered arrival for the hero block. Fires once on load, not on scroll,
   because the hero is already in view when the page opens. */
.hero .eyebrow,.hero h1,.hero .lead,.hero .cta{animation:rise .75s cubic-bezier(.22,.68,.28,1) both}
.hero h1{animation-delay:.06s}
.hero .lead{animation-delay:.14s}
.hero .cta{animation-delay:.22s}
/* Transform only here too. A 0.75s fade on load is short enough to feel
   harmless, but an audit that happens to sample during it reads the hero as
   near-invisible text and fails the page intermittently — which is worse than a
   plain failure, because it passes often enough to look fine. */
@keyframes rise{from{transform:translateY(22px)}to{transform:none}}

/* ----------------------------------------------------------------- bands */
section{padding:var(--space-3xl) 0}
.alt{background:var(--n-wash);border-block:1px solid var(--n-tint)}
.dark{background:var(--b-black);color:#fff}
.dark h2{color:#fff}
.dark p{color:var(--b-line)}
.lede{max-width:56ch;margin-bottom:var(--space-xl)}
.lede p{color:var(--n-body);font-size:var(--text-1);margin-top:var(--space-s)}
.dark .lede p{color:var(--b-line)}

/* ------------------------------------------------------- reveal on scroll */
/* Authored in the FINISHED state. The animation only replaces it where the
   browser supports view timelines, so no content depends on motion running. */
@supports (animation-timeline:view()){
  .reveal{animation:reveal linear both;animation-timeline:view();
    animation-range:entry 8% cover 26%}
}
/* Transform only — deliberately NO opacity.
   Fading in on scroll means every element below the fold sits at opacity:0
   until you reach it. axe measures that as text at 1.28:1 against white and
   fails the page, and it is right to: content that is invisible until an
   animation runs is content some people never see. Moving is enough. */
@keyframes reveal{from{transform:translateY(26px)}to{transform:none}}

/* ----------------------------------------------------------------- steps */
/* The walkthrough. Each step sticks while the next scrolls over it, so the
   sequence is read in order instead of skimmed as four equal boxes. */
.steps{display:grid;gap:var(--space-s)}
.step{position:sticky;top:96px;background:#fff;border:1px solid var(--n-tint);
  border-radius:14px;padding:var(--space-l);box-shadow:0 1px 0 var(--n-tint)}
.step:nth-child(2){top:112px}
.step:nth-child(3){top:128px}
.step:nth-child(4){top:144px}
/* --b-body (5.5:1), not --b-edge (3.0:1). The edge step is for borders, icon
   strokes and large text -- using it for small text on white fails AA, which is
   exactly what dropped the accessibility score to 93. */
.step .num{display:block;font-family:var(--serif);font-size:var(--text-2);
  color:var(--b-body);line-height:1;margin-bottom:var(--space-xs)}
.step h3{margin-bottom:var(--space-2xs)}
.step p{margin:0;color:var(--n-body)}
@supports (animation-timeline:view()){
  .step{animation:step linear both;animation-timeline:view();
    animation-range:entry 0% entry 60%}
}
@keyframes step{from{transform:translateY(36px) scale(.99)}
                to{transform:none}}

/* ----------------------------------------------------------------- cards */
.grid{display:grid;gap:var(--space-s);grid-template-columns:1fr}
@media(min-width:700px){.grid{grid-template-columns:repeat(3,1fr)}}
.card{border:1px solid var(--n-tint);border-radius:14px;padding:var(--space-m);
  background:#fff;transition:border-color .2s ease, transform .2s ease}
.card:hover{border-color:var(--b-line);transform:translateY(-3px)}
.card .tag{display:block;font-size:var(--text-2xs);font-weight:700;
  letter-spacing:.2em;text-transform:uppercase;color:var(--b-body);margin-bottom:10px}
.card h3{margin-bottom:8px}
.card p{margin:0;color:var(--n-body);font-size:var(--text-1xs)}

/* ----------------------------------------------------------------- facts */
.facts{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-l)}
@media(min-width:760px){.facts{grid-template-columns:repeat(4,1fr)}}
.fact b{display:block;font-family:var(--serif);font-size:var(--text-4);
  line-height:.95;letter-spacing:-.03em;color:#fff}
.fact span{display:block;font-size:var(--text-1xs);color:var(--b-edge);margin-top:10px}

/* ------------------------------------------------------------------ form */
form{max-width:36rem}
label{display:block;font-size:var(--text-1xs);font-weight:600;color:var(--n-ink);
  margin:var(--space-s) 0 6px}
input,textarea{width:100%;font:var(--text-0) var(--sans);color:var(--n-black);
  padding:13px 15px;border:1px solid var(--n-line);border-radius:8px;background:#fff}
input:focus,textarea:focus{border-color:var(--b-edge);outline-offset:1px}
textarea{min-height:8rem;resize:vertical}
.consent{display:flex;gap:12px;align-items:flex-start;margin-top:var(--space-m)}
.consent input{width:auto;margin-top:6px;flex:none}
.consent span{font-size:var(--text-1xs);color:var(--n-body);line-height:1.9}
.consent a{display:inline-block;padding:3px 2px}
button{margin-top:var(--space-m);background:var(--b-black);color:#fff;border:0;
  font:700 var(--text-0) var(--sans);padding:15px 32px;border-radius:6px;cursor:pointer;
  transition:transform .18s ease, background .18s ease}
button:hover{background:var(--b-ink);transform:translateY(-2px)}

/* ---------------------------------------------------------------- footer */
footer{background:var(--b-black);color:var(--b-line);padding:var(--space-2xl) 0;
  font-size:var(--text-1xs)}
footer a{color:var(--b-tint)}
footer strong{color:#fff}
footer .wrap{display:flex;flex-wrap:wrap;gap:var(--space-l);justify-content:space-between}
footer p{margin:0 0 6px}

/* ------------------------------------------------------------ legal pages */
.prose{max-width:68ch}
.prose h1{font-size:var(--text-4)}
.prose h2{font-size:var(--text-2);margin:var(--space-l) 0 var(--space-2xs)}
.prose h3{font-size:var(--text-0);margin:var(--space-m) 0 4px}
.prose li{margin-bottom:var(--space-2xs);color:var(--n-body)}
.updated{font-size:var(--text-1xs);color:var(--n-body)}
.spaced{margin-top:var(--space-m)}

/* Motion is decoration here, never information. Everything above is authored in
   its finished state, so switching all of it off changes nothing a reader needs. */
@media(prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important;scroll-behavior:auto!important}
  .progress{transform:scaleX(0)}
  .step{position:static}
}
