/* ==========================================================================
   Tall Order homepage — the Ascent
   Ported from ascent-gsap.html. Structure, class names and choreography are
   the original's: #ascent / .stage / #endfade / #pitch / #toLine / .rev.
   Only the font + colour tokens are remapped onto this build's design system.
   ========================================================================== */

:root{
  /* --ground is NOT the visible page colour: styles.css sets it to #090705 as the
     base under the grain, which composites back up to --bg. This file used to
     override it to espresso, which made the homepage flat brown and hid the grain
     entirely — every other page reads black and grainy. Left to inherit. */
  /* Brand accents only — from the Figma Tall Order Brand Palette.
     Tall Poppy is the accent; First Light is its lit state on dark. */
  --amber:var(--accent);
  --gold:var(--to-first-light,#fff695);
  --font-mono-to:var(--font-mono);
}

/* No background on the body — body::before carries the grain at z-index:-1 and a
   pseudo-element paints BELOW its own element's background, so a background here
   would hide it. html already holds the ground, as on every other page. */

/* main stays opaque, because the sticky hero stage has to be covered by the
   content that scrolls over it. So it gets the same treatment as the header:
   paint the ground, then lay the grain on top, which composites to --bg. */
/* ---------- Rails and rules on the homepage ----------
   This page opts out of the frame, so the vertical rules and the dot rails had
   nothing to hang off and the homepage was the one page without either. They go
   on main: the rules as two 1px background layers standing where the frame's
   borders stand on every other page, the dots on ::after masked to the bands
   outside them.

   Two layers rather than one, because a mask applies to everything an element
   paints — a rule sitting on the mask's own boundary would be eaten by it.

   --edge is the same expression styles.css uses for the gutter dots and the page
   box uses for its margins: --gutter until the viewport outgrows --frame-max,
   half the overflow after that. Declared here so ::after and .summit inherit it
   and the three cannot drift apart.

   Both layers sit at z-index 0, under `main > *`, so the sticky hero stage still
   covers them for its full height and the bleed is untouched. They read from the
   summit band down, where the ground is what you can see. */
.ascent-page main{
  --edge:max(var(--gutter),(100% - var(--frame-max)) / 2);
  position:relative;isolation:isolate;
  background-color:var(--ground);
  background-image:linear-gradient(var(--border),var(--border)),
                   linear-gradient(var(--border),var(--border));
  background-size:1px 100%,1px 100%;
  background-position:left var(--edge) top,right var(--edge) top;
  background-repeat:no-repeat,no-repeat;
}
/* Off below 1100 like every rail on the site — the rules stay, as they do on
   every other page, because the page box keeps its borders at every width. */
@media (min-width:1101px){
  .ascent-page main::after{
    content:"";
    position:absolute;inset:0;z-index:0;pointer-events:none;
    background-image:var(--dots-band);
    background-repeat:repeat;
    -webkit-mask-image:linear-gradient(90deg,#000 0 var(--edge),
      transparent var(--edge) calc(100% - var(--edge)),#000 calc(100% - var(--edge)));
    mask-image:linear-gradient(90deg,#000 0 var(--edge),
      transparent var(--edge) calc(100% - var(--edge)),#000 calc(100% - var(--edge)));
  }
}
.ascent-page main::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--grain-url);
  background-repeat:repeat;background-size:160px 160px;background-attachment:fixed;
  opacity:var(--grain-opacity,.25);
}
.ascent-page main > *{position:relative;z-index:1}

/* ---------- Ascent scroll track ---------- */
.ascent{position:relative;height:520vh}
.stage{position:sticky;top:0;height:100vh;overflow:hidden;background:var(--ground)}

#vid{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;will-change:transform}
.grad{position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(11,10,9,.5) 0%,rgba(11,10,9,0) 28%,rgba(11,10,9,.55) 100%)}
#particles{position:absolute;inset:0;pointer-events:none;mix-blend-mode:screen}

/* Ramps in only over the last stretch of the climb, so the figure is never
   darkened mid-ascent — it just hands over cleanly to the summit. */
#endfade{position:absolute;inset:0;pointer-events:none;opacity:0;will-change:opacity;
  background:linear-gradient(to bottom,rgba(17,17,17,0) 34%,#111 86%)}

/* ---------- Left altitude rail ----------
   On --edge, which is where `.ascent-page main` paints the page's left rule a
   few rules above — so the altitude line and the gutter line below the hero are
   one axis running the length of the document, not two lines 8px apart.

   It used to be clamp(24px,4vw,56px): 56 at 1440 against a rule on 64. One was
   a clamp reading the viewport and the other a token that steps at 900 and 600,
   so the gap did not hold anywhere. It widened as the window narrowed — 8px at
   1440, 14 at 1250, 28 by 901 — then changed SIDES at 900, where --gutter drops
   to 24 and the rail was left standing 12px inside the rule it was meant to be
   on. It passes through true at about 620 and opens back to 8px below 600.
   --edge is the same expression .beats and .summit already measure from, so all
   four now move together and cannot drift again. Measured after the change at
   375, 900, 1440 and 1600: the rail's left is --edge to the pixel at each.

   The mark keeps its own -12px: it is a 25px tick centred on the line, so the
   offset is half its width and belongs to the tick, not to the rail's position. */
.rail-line{position:absolute;left:var(--edge);top:0;bottom:0;width:1px;background:rgba(255,255,255,.14)}
.rail-mark{position:absolute;left:calc(var(--edge) - 12px);top:0;width:25px;height:1px;background:#fff;will-change:top}
.rail-label{position:absolute;left:var(--edge);bottom:56px;transform-origin:left bottom;transform:rotate(-90deg);
  white-space:nowrap;font-family:var(--font-mono-to);font-size:12px;text-transform:uppercase;letter-spacing:.16em;
  color:rgba(255,255,255,.58)}
.rail-label .altimeter{display:inline-block;vertical-align:-2px;margin-right:9px}
#altNeedle{transition:none}

/* ---------- Headline beats ----------
   Left edge is the page's, not its own: the beats and the summit heading the
   flight lifts out of are the same sentence in two places, so they stand on one
   line. Same expression .summit pads to — --edge is inherited from main and its
   percentage resolves against the same full-bleed width here, since .stage is
   as wide as main. It used to be 9vw, which put the beats 25px right of the
   heading at 1440 and drifted with the viewport. */
.beats{position:absolute;left:calc(var(--edge) + 1px + var(--pad));
  right:clamp(24px,6vw,80px);bottom:96px}
/* Every beat sets on one line — no line drops, at any width. Two things had to
   go for that. This rule's max-width:600px was the first: it was a measure for
   the old copy, which was written long enough to wrap, and at 64px it broke
   three of the eight after the rewrite. The beats box is 1255px at 1440 and the
   longest line is 699px, so the cap was the only thing in the way. The floors on
   the two font-size rules below were the second — see each.

   THE NUMBER THAT MATTERS: the longest beat renders 13.34x its own font-size
   ("Climbing higher than she's meant to.", 36 characters, measured — GC Vank at
   letter-spacing:0, so it scales linearly). Every size below divides the space
   by 13.75 instead, which is that ratio plus ~3% slack. Rewrite that line and
   these want re-measuring, or the line drops come back.

   The ratio is the whole cost of a long beat, and it is paid on phones: the
   divisor sets the type size wherever the screen is too narrow for the 34px cap,
   so 13.34 instead of the previous set's 10.92 took 375px handsets from 27.8px
   to 22.8px and 320s from 22.9px to 18.8px. Shorten the longest line and every
   narrow screen gets its display size back — nothing else here needs touching. */
.beat{position:absolute;left:0;right:0;bottom:0;margin:0;
  font-family:var(--font-head);font-weight:700;text-transform:uppercase;
  font-size:clamp(2.4rem,4.6vw,64px);line-height:.96;letter-spacing:0;color:#fff;
  opacity:0;transform:translateY(14px);
  transition:opacity .45s cubic-bezier(.2,0,0,1),transform .45s cubic-bezier(.2,0,0,1)}
.beat.on{opacity:1;transform:none}

/* ---------- Climb hint ---------- */
.hint{position:absolute;right:clamp(24px,6vw,80px);bottom:56px;display:flex;align-items:center;gap:14px;
  font-family:var(--font-mono-to);font-size:12px;text-transform:uppercase;letter-spacing:.16em;
  color:rgba(255,255,255,.58);transition:opacity .18s cubic-bezier(.2,0,0,1)}
.hint .ln{display:inline-block;width:44px;height:1px;background:rgba(255,255,255,.4)}

/* ---------- Provenance chip ("behind the curtain") ---------- */
.prov{position:absolute;z-index:20;top:clamp(88px,12vh,104px);right:clamp(24px,6vw,80px);pointer-events:none;text-align:right}
/* 24x24. The glyph came down with the box — at the old 17px a "+" very nearly
   filled a 22px interior, so the chip read as a character with a border round
   it rather than a button. */
.prov-chip{pointer-events:auto;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;
  width:24px;height:24px;background:rgba(11,10,9,.5);border:1px solid rgba(255,255,255,.42);
  color:rgba(255,255,255,.92);font-family:var(--font-mono-to);font-size:13px;line-height:1;
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  transition:background .15s,color .15s,border-color .15s}
.prov-chip:hover{background:#fff;color:#0b0a09;border-color:#fff}
.prov-chip .plus{display:inline-block;transition:transform .2s}
.prov.open .prov-chip .plus{transform:rotate(45deg)}

/* The card was display:none → display:block, which is why it snapped: display
   is not an animatable property, so there was nothing for a transition to run
   on. Hidden with opacity + visibility instead — the same pattern this file
   already uses for the header at .chrome-in, so the page has one way of doing
   this rather than two.

   visibility is what keeps display:none's useful half. On the way in it flips
   immediately and the opacity carries the fade; on the way out it is held
   until the transition ends, so the card fades rather than vanishing. Without
   it the card would stay in the accessibility tree and keep its backdrop
   filter alive while invisible.

   pointer-events:none matters as much as the fade: the card still occupies its
   box while closed, and .prov above it is pointer-events:none, so without this
   a closed card would swallow clicks on the hero underneath it. */
.prov-card{pointer-events:none;margin-top:12px;width:min(430px,86vw);text-align:left;
  background:rgba(11,10,9,.94);border:1px solid rgba(255,255,255,.16);
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);padding:20px;
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .22s cubic-bezier(.2,0,0,1),transform .22s cubic-bezier(.2,0,0,1),visibility .22s}
.prov.open .prov-card{pointer-events:auto;opacity:1;visibility:visible;transform:none}
.prov-card .hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;
  font-family:var(--font-mono-to);font-size:11px;text-transform:uppercase;letter-spacing:.16em;color:rgba(255,255,255,.55)}
.prov-card .hd .x{cursor:pointer}
.prov-card .hd .x:hover{color:#fff}
.pipe{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:18px}
.pipe .pill{font-family:var(--font-mono-to);font-size:10px;text-transform:uppercase;letter-spacing:.08em;
  color:#fff;border:1px solid rgba(255,255,255,.24);padding:4px 8px}
.pipe .arrow{color:rgba(255,255,255,.38);font-family:var(--font-mono-to);font-size:11px}
.pstep{margin-bottom:16px}
.pstep:last-child{margin-bottom:0}
.pstep .lbl{display:flex;align-items:center;justify-content:space-between;margin-bottom:7px}
.pstep .lbl span{font-family:var(--font-mono-to);font-size:10px;text-transform:uppercase;letter-spacing:.12em;color:var(--to-grey-300)}
.pstep .cp{cursor:pointer;font-family:var(--font-mono-to);font-size:9px;text-transform:uppercase;letter-spacing:.1em;
  color:rgba(255,255,255,.55);border:1px solid rgba(255,255,255,.2);background:none;padding:3px 8px;transition:.15s}
.pstep .cp:hover{color:#fff;border-color:rgba(255,255,255,.45)}
.pstep .cp.done{color:#0b0a09;background:#fff;border-color:#fff}
.pstep pre{margin:0;font-family:var(--font-mono-to);font-size:11px;line-height:1.5;color:rgba(255,255,255,.78);
  white-space:pre-wrap;word-break:break-word;background:rgba(255,255,255,.04);border-left:2px solid rgba(255,255,255,.18);
  padding:10px 12px;max-height:96px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.25) transparent}
.pstep pre::-webkit-scrollbar{width:8px}
.pstep pre::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2)}
.pstep pre::-webkit-scrollbar-track{background:rgba(255,255,255,.04)}

/* ==========================================================================
   Below the hero — #111 ground with a static grain over all of it.
   The hero already fades its bottom to #111, so this continues seamlessly.
   ========================================================================== */
.below{position:relative;background:#111}
.below::after{content:"";position:absolute;inset:0;z-index:5;pointer-events:none;opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='140'%20height='140'%3E%3Cfilter%20id='n'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.85'%20numOctaves='3'%20stitchTiles='stitch'/%3E%3CfeColorMatrix%20type='saturate'%20values='0'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23n)'/%3E%3C/svg%3E")}
.below > *{position:relative;z-index:6}

.rev{transition:opacity .6s cubic-bezier(.2,0,0,1),transform .6s cubic-bezier(.2,0,0,1)}

.eyebrow{font-family:var(--font-mono-to);font-size:12px;text-transform:uppercase;letter-spacing:.16em;color:rgba(255,255,255,.58)}
.cap-italic{font-size:14px;font-style:italic;color:rgba(255,255,255,.5)}

/* ---------- Summit ---------- */
/* On the site's left edge, not its own. This band used to sit on a bespoke
   clamp(24px,6vw,80px) — 80px at 1440, so 25px inside where the rule now is.
   --edge + the rule's own 1px + --pad is the same arithmetic that puts every
   other page's body copy on 105, inherited from main so it cannot drift. */
.summit{padding:140px calc(var(--edge) + 1px + var(--pad)) 120px;display:flex;flex-direction:column;gap:34px}
/* The eyebrow goes somewhere now. Site-wide a{} is colour:inherit with no
   underline, so without these three rules the link is indistinguishable from
   the label it replaced. First Light on hover — the one colour — and the rule
   sits 4px clear so it does not cut the mono's descenders. Scoped to .summit
   because .eyebrow above is shared with the lab pages. */
.summit .eyebrow a{transition:color .2s ease}
.summit .eyebrow a:hover{color:var(--accent);text-decoration:underline;text-underline-offset:4px}
.summit .eyebrow a:focus-visible{outline:1px solid var(--accent);outline-offset:4px}
/* Its own span, at letter-spacing:0 — .16em on the arrow reads as a gap
   between it and the word, not tracking. */
.summit .eyebrow .arw{display:inline-block;letter-spacing:0;transition:transform .2s ease}
.summit .eyebrow a:hover .arw{transform:translateX(3px)}
.summit h2{margin:0;max-width:1000px;font-family:var(--font-head);font-weight:700;
  font-size:clamp(2.4rem,4.6vw,64px);line-height:.98;letter-spacing:0;text-transform:uppercase}
.summit p{margin:0;max-width:640px;font-size:clamp(1.1rem,1.6vw,22px);line-height:1.4;color:rgba(255,255,255,.62)}
/* Home runs a longer pitch line than the founders pages. 640px orphans "market."
   on a third line; 720px lands it as two balanced lines with room to spare. */
.ascent-page .summit p{max-width:720px;text-wrap:pretty}
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(24px,4vw,56px);margin-top:26px}
.stats .stat{display:flex;flex-direction:column;gap:12px;border-top:1px solid rgba(255,255,255,.12);padding-top:22px}
.stats .stat b{font-family:var(--font-head);font-weight:700;font-size:clamp(2rem,3vw,44px);line-height:1;text-transform:uppercase}
.stats .stat span{font-family:var(--font-mono-to);font-size:12px;text-transform:uppercase;letter-spacing:.1em;color:rgba(255,255,255,.58)}

/* ---------- Content sections ---------- */
.rowhead{display:flex;align-items:baseline;justify-content:space-between;gap:24px;flex-wrap:wrap}
.cols3{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(24px,4vw,56px);margin-top:12px}
.cols3 .col{display:flex;flex-direction:column;gap:18px;border-top:1px solid rgba(255,255,255,.12);padding-top:24px}
.cols3 .col .n{font-family:var(--font-mono-to);font-size:12px;text-transform:uppercase;letter-spacing:.16em;color:rgba(255,255,255,.58)}
.cols3 .col h3{margin:0;font-family:var(--font-body);font-weight:500;font-size:clamp(1.3rem,1.8vw,26px);line-height:1.1}
.cols3 .col p{margin:0;font-size:17px;line-height:1.55;color:rgba(255,255,255,.62)}

/* ---------- Portfolio logo grid ---------- */
.logogrid{display:grid;grid-template-columns:repeat(5,1fr);gap:1px;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.12)}
.logogrid .cell{background:#111;height:128px;display:flex;align-items:center;justify-content:center;transition:background .18s}
.logogrid .cell:hover{background:#15120f}
.logogrid img{max-width:124px;max-height:26px;filter:brightness(0) invert(1);opacity:.66;transition:opacity .18s ease}
.logogrid .cell:hover img{opacity:1}

/* ==========================================================================
   Ink — the deck illustrations.
   Every SVG is fill:currentColor, so colour comes from context. Two levels
   only: quiet in body flow, lit when the drawing IS the subject.
   ========================================================================== */
.ink{display:block;width:100%;max-width:150px;background-color:currentColor;color:var(--muted);opacity:.55;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:left center;mask-position:left center;
  -webkit-mask-size:contain;mask-size:contain;
  transition:opacity .25s ease,color .25s ease}

/* ---------- Full-bleed imagery: thresholds only, never inside a column ---- */
.plate{position:relative;margin-top:clamp(90px,12vh,150px);height:clamp(320px,52vh,560px);overflow:hidden}
.plate img{width:100%;height:100%;object-fit:cover;display:block}
.plate::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,#111 0%,rgba(17,17,17,0) 26%,rgba(17,17,17,0) 62%,#111 100%)}
.plate .plate-cap{position:absolute;left:clamp(24px,6vw,80px);bottom:34px;z-index:2;max-width:34ch;
  font-family:var(--font-mono-to);font-size:12px;text-transform:uppercase;letter-spacing:.14em;color:rgba(255,255,255,.72)}

/* ---------- CTA ---------- */
.cta-home{padding:clamp(120px,20vh,190px) clamp(24px,6vw,80px) 0;display:flex;flex-direction:column;align-items:center;gap:24px;text-align:center}
.cta-home h2{margin:0;max-width:860px;font-family:var(--font-head);font-weight:700;
  font-size:clamp(2.4rem,4.4vw,60px);line-height:.98;letter-spacing:0;text-transform:uppercase}
.cta-home p{margin:0;max-width:520px;font-size:clamp(1.1rem,1.5vw,21px);line-height:1.4;color:rgba(255,255,255,.62)}
.cta-home .actions{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;margin-top:14px}
.btn-amber{display:inline-flex;align-items:center;height:52px;padding:0 28px;background:#fff;color:#0b0a09;
  font-family:var(--font-mono-to);font-size:13px;text-transform:uppercase;letter-spacing:.08em;border:1px solid #fff}
.btn-amber:hover{background:rgba(255,255,255,.82);border-color:rgba(255,255,255,.82)}
.btn-line{display:inline-flex;align-items:center;height:52px;padding:0 28px;background:transparent;color:#fff;
  font-family:var(--font-mono-to);font-size:13px;text-transform:uppercase;letter-spacing:.08em;border:1px solid rgba(255,255,255,.3)}
.btn-line:hover{border-color:rgba(255,255,255,.7)}

/* ---------- Homepage nav treatment: chrome floats over the hero ----------
   Sticky chrome would push the 100vh stage below the fold and clip the beat,
   so on this page only the bar and header are lifted out of flow. The header
   sits under the notice bar, and drops to the top when it is dismissed. */
/* The notice moved to the footer, so there is nothing above the header to clear.
   Measured off whether a bar is actually present and visible rather than assumed,
   which also keeps chrome/backup/nav.html working if the six-panel nav — and its
   notice bar — is ever restored over the top. */
.ascent-page{--notice-h:0px}
.ascent-page:has(.notice-bar:not([hidden])){--notice-h:43px}
.ascent-page .notice-bar{position:fixed;top:0;left:0;right:0;z-index:101}
.ascent-page .header{position:fixed;top:var(--notice-h);left:0;right:0;z-index:100;
  background:transparent;border-bottom-color:transparent;
  transition:background .2s cubic-bezier(.2,0,0,1),border-color .2s cubic-bezier(.2,0,0,1)}

/* The homepage has no frame — main runs edge to edge — so the chrome does too.
   The BAR goes full width, not its contents: the wordmark and the buttons stay
   on the page box, so they still line up with the band content further down the
   page. This is the one page that opts out; everywhere else the header's side
   rules are part of the frame that runs the height of the page. */
.ascent-page .notice-bar,
.ascent-page .header,
.ascent-page .footer{
  width:100%;max-width:none;margin-inline:0;border-left:0;border-right:0;
}
/* The rules carry on through the footer rather than stopping dead at the last
   band. The bar still runs full width — that is the opt-out above — so they are
   painted where the frame's borders would be, the same way main draws them. The
   footer is transparent, so the gutter dots from styles.css show through it
   already and only the rules were missing. */
.ascent-page .footer{
  --edge:max(var(--gutter),(100% - var(--frame-max)) / 2);
  background-image:linear-gradient(var(--border),var(--border)),
                   linear-gradient(var(--border),var(--border));
  background-size:1px 100%,1px 100%;
  background-position:left var(--edge) top,right var(--edge) top;
  background-repeat:no-repeat,no-repeat;
}
.ascent-page .nav,
.ascent-page .footer > *:not(.footer-notice):not(.footer-watermark){
  width:calc(100% - (var(--gutter) * 2));max-width:var(--frame-max);margin-inline:auto;
}
/* Same 1px as founders: this nav stands in for a page box whose side rules are
   switched off above, so it borrows their width transparently and the wordmark
   lands on 105 like every other page. */
.ascent-page .nav{padding-inline:var(--pad);border-inline:1px solid transparent}
/* This header floats transparent over the hero, so the grain layer styles.css
   gives every header must not paint here — it would lay speckle over the video.
   It arrives with .solid, once the header actually has a ground to sit on, and
   fades in on the same timing as the background. */
.ascent-page .header::before{opacity:0;transition:opacity .2s cubic-bezier(.2,0,0,1)}
.ascent-page .header.solid::before{opacity:var(--grain-opacity,.25)}

.ascent-page .header.solid{background:rgba(11,10,9,.92);border-bottom-color:rgba(255,255,255,.12);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  .ascent{height:auto}
  .stage{position:relative;height:86vh}
  /* Same fix as the phone rule, different box: here .beats is in flow with
     clamp(24px,6vw,80px) of padding on both sides, so the constant is 48, not
     61. The old 1.6rem floor overflowed a 320 screen by 8px. */
  .beat{position:relative;opacity:1;transform:none;margin-bottom:14px;
    font-size:min(34px,calc((100vw - 48px) / 13.75))}
  .beats{position:relative;bottom:auto;left:auto;right:auto;padding:0 clamp(24px,6vw,80px) 40px}
  #particles{display:none}
  .rev{transition:none}
  /* The card still opens and closes, it just stops sliding and fading. Only
     the transition goes — leaving visibility animating here would hold a
     hidden card visible for its full duration with no fade to justify it. */
  .prov-card{transition:none;transform:none}
  .prov-chip .plus{transition:none}
}

/* ---------- Responsive ---------- */
@media (max-width:900px){
  .ascent{height:420vh}
  .stats{grid-template-columns:repeat(2,1fr)}
  .rail-label{display:none}
  .cols3{grid-template-columns:1fr}
  .logogrid{grid-template-columns:repeat(2,1fr)}
  /* Same expression, tighter verticals — a flat 24px put the copy on top of the
     rule once the rules existed. */
  .summit{padding:96px calc(var(--edge) + 1px + var(--pad)) 84px}
  #vid{object-position:68% center}   /* keep the stem + climber in a portrait crop */
}
@media (max-width:620px){
  /* ---- The hero loses its furniture on a phone ----
     The rail's readout already goes at 900, which left a bare 1px line and a
     travelling tick standing on the left edge saying nothing. The provenance
     chip is a desktop aside — a 24px target parked over the climber, opening a
     430px card onto a 375px screen. Both come off; the markup stays, because
     ascent.js guards every one of these and simply finds them hidden. */
  .rail-line,.rail-mark,.prov{display:none}

  /* ---- Bottom-anchored, and actually on screen ----
     100vh measures the viewport as if the browser's own bars were already
     collapsed, so on load "Scroll to climb" sat underneath the URL bar and was
     not seen until the reader scrolled — the wrong way round for the one line
     asking them to. (100vh - 100svh) IS that bar's height and is a constant, so
     the hint clears it on load and holds its spacing after it retracts. The
     beats box takes the same lift at the foot of this file, where the motion
     guard it needs can reach it. Nothing is anchored to dvh: the stage is
     sticky, and a height that changes mid-scroll moves the video under the
     type. A browser without svh drops the declaration and keeps the base
     rule's offset. */
  .hint{right:24px;bottom:calc(40px + (100vh - 100svh))}

  /* ---- Beat size ----
     Was min(34px,(100vw - 61px)/13.75) — 22.8px on a 375 handset, smaller than
     the paragraph two screens down. 13.75 is the longest beat's own ratio
     ("Climbing higher than she's meant to.", 13.34 plus slack), so it sized all
     eight to the worst one and the display line read as body copy.

     A phone gets two lines instead. That is the whole unlock: the size is now
     set by how big the type can go before the longest beat needs a THIRD line,
     which is a much higher ceiling than one-line-always. Measured, not
     estimated — every beat rendered at 28/30/…/48px against the real box:

       375 (box 314): 2 lines max holds to 40px, 42 breaks to three
       320 (box 259): 2 lines max holds to 34px, 36 breaks to three

     Both ceilings are the same ratio — box / 7.85 and box / 7.62 — so 7.9 sits
     just inside the tighter of the two and one divisor covers every handset:
     32.8px at 320, 39.7 at 375, 41.6 at 390, 44 (capped) at 414 and up. Nearly
     double the old size, and five of the eight beats still set on one line.

     Two numbers to re-measure if the copy changes: 7.9 is the longest beat's
     two-line ceiling, and 61px is the box's two insets (16 gutter + 1 rule +
     20 pad, and 24 on the right). The cap is what keeps the step at 620 sane —
     the desktop clamp's 2.4rem floor picks up at 38.4px on the other side. */
  .beat{font-size:min(44px,calc((100vw - 61px) / 7.9))}
  .hint .ln{width:34px}
  .plate{height:clamp(240px,40vh,340px)}

  /* ---- Reading size ----
     The summit paragraph sat on its 1.1rem floor — 17.6px under a 28px display
     line. The three panels get the same treatment at the foot of this file,
     where their own rules are: they are declared after this block, so an
     override here would lose on source order. */
  .summit p{font-size:19px;line-height:1.45}
}
/* Split out of the block above, and not folded back into it: under reduced
   motion .beats is position:relative and in flow, where a `bottom` does not
   anchor anything — it shifts the whole stack up by that much. The reduced
   motion block is declared ABOVE this one, so it cannot win the offset back on
   source order. Guarding here is the only place that holds. */
@media (max-width:620px) and (prefers-reduced-motion:no-preference){
  .beats{bottom:calc(80px + (100vh - 100svh))}
}

.stats .stat b .mul{font-family:var(--font-body);font-weight:500;font-size:.72em;vertical-align:.06em}

/* Chrome holds off through the climb, arrives with "That's the tall order." */
/* Hide the chrome before first paint, gated on the `js` class the head script
   sets. Without it the JS adds .chrome-hold after paint and the menu flashes.
   No JS at all -> these rules never apply, so the chrome stays visible. */
html.js .ascent-page .notice-bar,
html.js .ascent-page .header{opacity:0;visibility:hidden;pointer-events:none;
  transform:translateY(-6px);
  transition:opacity .45s cubic-bezier(.2,0,0,1),transform .45s cubic-bezier(.2,0,0,1),visibility .45s}
html.js .ascent-page .chrome-in{opacity:1;visibility:visible;pointer-events:auto;transform:none}

.ascent-page .chrome-hold{opacity:0;visibility:hidden;pointer-events:none;
  transform:translateY(-6px);
  transition:opacity .45s cubic-bezier(.2,0,0,1),transform .45s cubic-bezier(.2,0,0,1),visibility .45s}
.ascent-page .chrome-hold.chrome-in{opacity:1;visibility:visible;pointer-events:auto;transform:none}

/* ---------- The header picking the wordmark up ----------
   The summit heading scrolls like any other type and the bar catches it. A fixed
   clone starts life sitting exactly on the words — so the handover is invisible —
   tracks them up the page, squeezing to the mark's width and dissolving type ->
   SVG as it closes, and stops when it reaches the wordmark slot. The header's own
   slot holds empty until then, which is what makes the logo the last thing to
   arrive. Geometry is all inline from ascent.js; these are the states only. */
/* No transition on the handover in either direction: the clone is sitting on the
   words at exactly their size when this flips, so the two are the same pixels and
   a fade would only show as the antialiasing of one word doubling. */
html.js .ascent-page .to-morph.morph-out{opacity:0}

/* Hidden on opacity, not display, so the layer exists before it is needed —
   see sync(). visibility keeps it out of hit-testing and the a11y tree.
   No transform of its own: it is only the toggle, and the two things inside it
   are positioned independently. */
.to-flight{position:fixed;inset:0;z-index:105;
  opacity:0;visibility:hidden;pointer-events:none;
  -webkit-font-smoothing:antialiased}
.to-flight.on{opacity:1;visibility:visible}
/* The type: scaled, because that is the only cheap way to resize text. */
.to-flight .tf-box{position:fixed;left:0;top:0;
  transform-origin:0 0;will-change:transform}
.to-flight .tf-text{position:absolute;left:0;top:0;white-space:nowrap;
  will-change:transform,opacity}
/* The mark: sized, not scaled, so it is drawn at the size it is shown at and
   arrives as the same picture the header holds. brightness/invert is the
   treatment the header gives it, or it lands as black on black. max-width
   because the global img rule would cap it at its container's width. */
.to-flight .tf-mark{position:fixed;left:0;top:0;display:block;max-width:none;
  transform-origin:0 0;filter:brightness(0) invert(1);
  will-change:transform,opacity}

/* visibility would do, but the flight needs the slot's box to aim at. No
   transition: the swap happens with both marks on the same pixels. */
html.js .ascent-page .header.logo-pending .brand-to img{opacity:0}

/* Everything in the bar except the bar waits for the mark — the links, both
   buttons, and the hamburger that stands in for them under 900. The links have
   to: coming up out of the page the wordmark is 240px wide against the 141px the
   seated mark takes, and the first link starts at 246, so for about 40px of
   scroll the R of ORDER was drawn straight through "About", white on white. The
   buttons don't have to, they just belong to the same row — three arrivals read
   as fussy where two read as the header assembling once.
   So: bar takes hold of the wordmark, wordmark seats, the row it belongs to
   arrives with it. */
html.js .ascent-page .header .nav-links,
html.js .ascent-page .header .nav > .btn,
html.js .ascent-page .header .hamburger{
  transition:opacity .4s cubic-bezier(.2,0,0,1),transform .4s cubic-bezier(.2,0,0,1)}
html.js .ascent-page .header.logo-pending .nav-links,
html.js .ascent-page .header.logo-pending .nav > .btn,
html.js .ascent-page .header.logo-pending .hamburger{
  opacity:0;transform:translateY(-4px);pointer-events:none}


/* ---------- Dark surfaces read as the page ground ----------
   Every dark band and card on this page carried its own flat fill, so the page
   was a stack of slightly different near-blacks — #111, #111214, #110f0d — over
   a ground that is actually black plus noise. They get out of the way instead:
   main paints --ground and carries the grain, so transparent here means the
   black noise reads straight through, and the page is one surface.
   Deliberately NOT included: .prov-card and .prov-chip sit over the hero video
   and need their own ground to stay legible. */
.ascent-page .below,
.ascent-page .logogrid .cell{
  background:transparent;
}

/* Hover still needs to register, but as a lift off the ground rather than a
   different colour — no hue, so nothing reintroduces a brown cast. */
.ascent-page .logogrid .cell:hover{
  background:rgba(250,250,249,.03);
}

/* ==========================================================================
   Three ways in — Founders / Investors / Our AI Ops.
   The panels-on-one-lattice option: 1px gaps over a border-coloured ground, so
   the three rules read as a single grid rather than three boxes each drawing
   its own edge.

   Horizontal padding is .summit's expression verbatim —
   calc(var(--edge) + 1px + var(--pad)) — NOT --gutter and not .page-box. This
   page does not draw the site's framed box; --edge is declared on
   .ascent-page main so the gutter dots, the ::after ground and .summit all read
   the same value and cannot drift. The +1px is the page box's border, --pad the
   inset to content. Anything else here lands off the block above it: a plain
   clamp(24px,6vw,80px) sits 25px left of the summit at 1440.
   ========================================================================== */
.asc-ways-band{padding:0 calc(var(--edge) + 1px + var(--pad)) 120px}

/* The lattice is drawn on the cells, not bled through 1px gaps from a
   border-coloured parent. It used to be the parent: `gap:1px` over
   `background:var(--border-2)`, which works only while every cell carries an
   opaque fill of its own. These cells are transparent now so the ground's
   black and grain read straight through them (see "Dark surfaces read as the
   page ground" above), and a transparent cell over a --border-2 parent renders
   as a light grey panel — the parent showing through the whole cell, not just
   the gaps. Hairlines per cell, and the parent paints nothing. */
/* Flex, not grid, so the band fits however many cells it has: it was
   repeat(3,1fr) and left a third of itself empty the moment one was dropped.
   grid-auto-columns:1fr looks like the fix and is not — implicit fr tracks do
   not claim the container's free space, so two cells sized to 409px inside a
   1310px band. flex:1 1 0 does claim it. */
.asc-ways{display:flex;gap:0;
  background:none;border:1px solid var(--border-2)}
.asc-way{flex:1 1 0;min-width:0;
  background:transparent;padding:34px 30px 26px;
  display:flex;flex-direction:column;gap:14px;min-height:260px;
  border-left:1px solid var(--border-2);
  transition:background .18s}
.asc-way:first-child{border-left:none}
/* A lift off the ground rather than a different colour, matching
   .logogrid .cell:hover — no hue, so nothing reintroduces a brown cast. */
.asc-way:hover{background:rgba(250,250,249,.03)}
/* Every cell can lead with a cover. The picture bleeds to the cell's own edges:
   the cell drops its top padding and the picture takes the side padding back off
   itself, because a cover inset by 30px reads as a framed thumbnail rather than
   a cover. 16:9 is the sources' own ratio, so nothing is cropped and the
   compositions -- figure low, light high -- survive whole. The 12px plus the
   column's own 14px gap is the 26px under the picture. */
.asc-way.has-shot{padding-top:0}
.asc-way .shot{display:block;overflow:hidden;background:#141210;
  margin:0 -30px 12px;aspect-ratio:16/9}
.asc-way .shot img{display:block;width:100%;height:100%;object-fit:cover}
.asc-way .n{font-family:var(--font-mono-to);font-size:12px;text-transform:uppercase;
  letter-spacing:.16em;color:var(--muted-2)}
.asc-way h3{margin:0;font-family:var(--font-body);font-weight:500;
  font-size:clamp(1.25rem,1.7vw,24px);line-height:1.12}
.asc-way p{margin:0;font-size:16px;line-height:1.55;color:var(--muted)}
.asc-way .foot{margin-top:auto;padding-top:22px;display:flex;align-items:center;
  justify-content:space-between;gap:12px;border-top:1px solid var(--border)}
.asc-way .foot .w{font-family:var(--font-mono-to);font-size:12px;
  text-transform:uppercase;letter-spacing:.1em;color:var(--muted-2)}
.asc-way .foot .ar{font-size:18px;line-height:1;transition:transform .18s}
.asc-way:hover .foot .ar{transform:translateX(4px)}
.asc-way:hover .foot .w{color:var(--accent)}


@media (max-width:900px){
  .asc-ways{flex-direction:column}
  /* flex:1 1 0 sizes the MAIN axis, which is now vertical: basis 0 with nothing
     to grow into collapses every card to nothing and its copy overlaps the card
     below. Back to content height once stacked. */
  .asc-way{flex:0 0 auto;min-height:0;border-left:none;border-top:1px solid var(--border-2)}
  .asc-way:first-child{border-top:none}
}
/* Matches the summit's own step-down at this breakpoint. */
@media (max-width:900px){
  .asc-ways-band{padding-bottom:84px}
}

/* The three panels read as body copy on a phone under the bigger hero. Here
   rather than in the phone block above because these rules are declared below
   it — same specificity, so source order decides and that block would lose. */
@media (max-width:620px){
  .asc-way h3{font-size:21px}
  .asc-way p{font-size:17px}
}

/* ==========================================================================
   Latest writing — .srow / .post-card / .grid-3.ruled, the same three boxes
   the writing index draws under "Also at launch", so a post looks the same
   wherever it appears. .ruled is the site's canonical card lattice and is
   already the treatment this page wanted: rules on the children, no fill and
   no parent bleed, so main's ground and grain read straight through the cards.
   Nothing here restates it — this block is the band's position only.

   Neither .srow nor .sec-head carries a wrap of its own — on the writing index
   the shell around them supplies it. There is no shell here, so the band takes
   the same calc(var(--edge) + 1px + var(--pad)) every other block on this page
   uses, which is what puts its header, its cards and the three-up above all on
   one edge.
   ========================================================================== */
/* Top padding is small on purpose. .asc-ways-band above already ends on 120px,
   so the two compound: at clamp(64px,9vh,110px) — news.css's own band value,
   which was written for a band that follows nothing — the gap between the
   three-up's bottom rule and this header came to 201px. */
.asc-writing{padding:clamp(32px,4vh,56px) calc(var(--edge) + 1px + var(--pad)) 0}
/* .ruled pads its own cells 32px all round. Only the sides need changing: the
   first column's rule is the page's own left edge, so 32px there would push the
   copy 32px inside every other block on the page. Outer columns come back onto
   the edge, the internal gutters keep theirs, and the vertical padding stays
   .ruled's 32px — the byline is the last thing in a card and sits on that.
   Top is the exception, dropped to 0 because .sec-head's 30px margin already
   spaces the grid off its rule. */
.asc-writing .ruled > *{padding:0 32px 32px}
.asc-writing .ruled.grid-3 > *:nth-child(3n+1){padding-left:0}
.asc-writing .ruled.grid-3 > *:nth-child(3n){padding-right:0}
/* .ruled's bottom rule closes the grid on the writing index because a
   subscribe module follows it. Here the CTA's own top rule is the boundary, and
   both would render as two parallel lines a hundred-odd px apart. */
.asc-writing .ruled > *{border-bottom:none}
.asc-writing .sec-head{padding-bottom:24px;border-bottom:1px solid var(--border);margin-bottom:30px}
.asc-writing .sec-head .aside{color:var(--muted-2);white-space:nowrap;
  font-family:var(--font-mono-to);font-size:12px;text-transform:uppercase;letter-spacing:.1em}
.asc-writing .sec-head .aside:hover{color:var(--text)}
@media (max-width:900px){
  /* Stacked, and .ruled drops its verticals here — so the only thing holding
     one card off the next is this padding, and it does the job on its own
     without a top value stacking on top of it. */
  .asc-writing .ruled > *{padding:0 0 40px}
}

/* ==========================================================================
   Closing CTA. .cta in styles.css already carries the type and the centring
   (and clamps the 60px h2 at 900), so this only supplies the band's own
   vertical rhythm — .cta's flat 140px leaves the button a long way from the
   grid above it and from the footer below.
   ========================================================================== */
/* This band is one headline and one button — 176px of content — so it takes
   the .cta-band rhythm from vc.css rather than .cta's flat 140px. At 15/16vh it
   was carrying 279px of padding and standing 455px tall, half a viewport for
   two elements. */
.asc-cta{padding:clamp(64px,8vh,96px) calc(var(--edge) + 1px + var(--pad))
  clamp(72px,9vh,108px)}
.asc-cta h2{margin-bottom:32px;text-wrap:balance}
/* No rule above this band. It used to carry one on a ::before, inset to the
   content edge so it lined up with the .sec-head rule of the "Latest writing"
   section above it — the two read as one axis. That section is gone: the ways
   band sits here now, and it draws its own full border box, so a second
   hairline a few pixels under it was a rule against a rule. */
