/* ============================================================
   01 - HERO (redesign 2026-08)
   Typography led. Copy sits in the shared 900px reading column,
   the product window sits in the page shell.

   2026-08-02 pass: the fold had two things competing for the eye.
   The headline was set at 68px maximum, which is a large heading
   rather than a display size, while the app window under it ran
   1000px wide carrying five separate accent moments including a
   filled accent button. Both have moved: the headline is now a
   true display size with display tracking and real air around it,
   and the window is narrower and quieter so it reads as evidence
   under the claim instead of a second claim.

   The old 02-stats band also lives here now, as the .hero-facts
   row. See sections/01-hero.html for why.

   The only motion here is entrance motion: the demo runs once and
   settles, the tilt follows a fine pointer and returns to rest. No
   perpetual float or bob loops.
   ============================================================ */

.hero {
  text-align: center;
  /* The nav floats over the hero, so its height is part of the top pad. The
     second term is the actual breathing room above the headline, and it is
     deliberately larger than a default section step: a display headline needs
     to be given the room or the size alone just reads as shouting. */
  padding-top: calc(var(--nav-h) + clamp(26px, 4vw, 64px));
  padding-bottom: var(--sec-pad);
  overflow: hidden;                 /* the tilted window overhangs, it never scrolls */
}
.hero-inner { display: flex; flex-direction: column; align-items: center; }

/* copy block: .col-text caps it at --maxw-text (900px) */
.hero-copy { display: flex; flex-direction: column; align-items: center; }

/* No text-wrap: balance here. The markup carries an explicit break between the
   two sentences, and balance would override it into an even but wrong split.

   Display size, display tracking. -0.038em is tighter than the -0.028em the
   global h1 rule uses because that value is tuned for 26-40px section titles;
   at 88px the same tracking reads loose and slightly cheap. Held above -0.04em
   so the 40px floor on a small phone does not turn cramped. */
.hero-title {
  font-size: clamp(40px, 7.2vw, 88px);
  font-weight: 700; letter-spacing: -0.038em; line-height: 1.04;
  color: var(--text);
}
/* ---------- typed second line ----------
   The line is its own flex row with a locked min-height, so the headline block
   never changes height as the phrase grows, shrinks or swaps. Width is free to
   change: that IS the typewriter. nowrap stops a phrase breaking mid-animation
   and taking the hero's height with it.

   min-height is in em and must stay equal to the line-height above it. Do not
   change one without the other. */
.hero-typeline {
  display: inline-flex; align-items: baseline; justify-content: center;
  min-height: 1.04em;
}
/* The zero-width space keeps a full line box alive when the phrase empties
   between cycles. Without it the span collapses and the whole headline jumps
   about 8px every time the text clears. */
.hero-type { white-space: nowrap; }
.hero-type::after { content: "\200B"; }

.hero-caret {
  display: inline-block; width: 3px; height: .74em; margin-left: 7px;
  border-radius: 2px; background: var(--accent);
  animation: heroCaret 1.05s steps(1, end) infinite;
}
@keyframes heroCaret { 0%, 47% { opacity: 1; } 48%, 96% { opacity: 0; } 100% { opacity: 1; } }

/* Reduced motion: no blink, and the JS leaves the first phrase in place, so the
   headline is simply a normal static line. */
@media (prefers-reduced-motion: reduce) {
  .hero-caret { animation: none; opacity: .55; }
}

/* the accent half of the headline, in the brand gradient.
   The flat colour is the fallback for engines without text clipping. */
.hero-acc {
  color: var(--accent);
  background: var(--accent-grad);
  -webkit-background-clip: text;
          background-clip: text;
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .hero-acc { color: transparent; -webkit-text-fill-color: transparent; }
}

/* The gaps below the headline are set here rather than left at their defaults,
   and they step up in size as they go down: 24 to the sub, 34 to the button, 32
   to the facts, then the big one to the window. An even gap everywhere is what
   makes a hero read as "stacked by a machine". */
/* Sized up from a 16px cap. One 16px line under an 88px headline reads as a
   caption that got left behind; 17.5px reads as the second line of the same
   thought. The copy is untouched: this is scale, not more words. */
.hero-sub {
  margin: clamp(20px, 2vw, 28px) auto 0; max-width: var(--maxw-text);
  font-size: clamp(15.5px, 1.55vw, 17.5px); line-height: 1.6;
  color: var(--text-dim); text-wrap: balance;
}
.hero-actions { margin-top: clamp(26px, 2.6vw, 34px); display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

/* ---------- the three facts (was section 02) ----------
   Figures only. The values sit in the one instrument face at a size that gives
   them presence, the labels are two or three words underneath, and there is no
   sentence of explanation anywhere in the row: that was the whole problem with
   the band this replaces.

   Auto columns rather than three equal fractions, so the row hugs its own
   content and stays optically centred instead of leaving two wide empty
   gutters at the ends. */
.hero-facts {
  margin-top: clamp(26px, 3vw, 34px);
  display: grid;
  grid-template-columns: repeat(3, auto);
  justify-content: center;
  gap: clamp(26px, 5vw, 68px);
  /* it kept id="stats" from the deleted section, so it keeps the anchor offset */
  scroll-margin-top: calc(var(--nav-h) + 14px);
}
.hero-fact { text-align: center; }
.hero-fact-v {
  font-family: var(--font-price);
  font-variant-numeric: tabular-nums;
  font-size: clamp(22px, 2.1vw, 27px);
  font-weight: 700; line-height: 1; letter-spacing: -0.03em;
  color: var(--text);
}
.hero-fact-k {
  margin-top: 8px;
  font-size: 12.5px; font-weight: 500; letter-spacing: .01em; line-height: 1.4;
  color: var(--text-mute);
}

/* ---------- product window (3D) ---------- */
.hero-shot { margin-top: clamp(38px, 4.4vw, 62px); width: 100%; perspective: 1700px; }
/* pointer-events: the stage is a flat plane at z=0 inside the 3D scene, and
   rotateY(-7deg) pushes the LEFT third of the window behind that plane, so the
   stage was painting over it for hit testing. Measured: elementFromPoint at the
   dead centre of the demo's Optimize button returned DIV.hero-stage, not the
   button, so the demo's own control could not be clicked at all on desktop. It
   was not caused by the resize in this pass, it reproduces identically at the
   old 1000px width. The stage carries no content and no background of its own,
   so it has nothing to lose by being transparent to the pointer; the window
   takes them back. The parallax listener lives on .hero-shot, an ancestor of
   both, so it still sees every move either way. */
.hero-stage { transform-style: preserve-3d; display: flex; justify-content: center; pointer-events: none; }
.hero-win {
  pointer-events: auto;             /* see the note on .hero-stage above */
  /* 900, not 1000. At 1000 the window was within 100px of the full page shell,
     which made it the largest object on the page and put it in competition with
     the headline. 900 matches the reading column the copy sits in, so the two
     line up and the window reads as belonging under the words. */
  width: min(900px, 100%);
  text-align: left;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, rgba(122,197,240,.06), transparent 34%), var(--bg-1);
  overflow: hidden;
  /* softer than the old 4/-11: reads as a product shot, not a stunt.
     NOTE: js/01-hero.js hardcodes these two values as REST_X / REST_Y for the
     pointer parallax. Changing them here without changing them there makes the
     window jump on the first pointer move. */
  transform: rotateX(3deg) rotateY(-7deg);
  transform-style: preserve-3d;
  transition: transform var(--t-slow) var(--e-out);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.07),
    -1px 0 0 rgba(122,197,240,.14),           /* faint blue leading edge */
    0 55px 90px -46px rgba(0,0,0,.92),
    0 22px 50px -38px rgba(0,0,0,.7);
}

/* title bar + Windows controls (minimize / maximize / close, top-right) */
.hero-win-bar {
  display: flex; align-items: center; justify-content: space-between;
  height: 38px; padding: 0 4px 0 15px;
  background: linear-gradient(180deg, rgba(255,255,255,.035), transparent);
  border-bottom: 1px solid var(--hairline);
}
.hero-win-brand { display: inline-flex; align-items: center; gap: 9px; font-weight: 700; font-size: 13px; letter-spacing: .2px; color: var(--text); }
.hero-win-logo { width: 18px; height: 18px; }
.hero-win-ctl { display: inline-flex; }
.hero-win-b { display: grid; place-items: center; width: 40px; height: 37px; color: var(--text-mute); }
.hero-win-b svg { width: 12px; height: 12px; }

.hero-win-body { display: grid; grid-template-columns: 178px 1fr; }

/* sidebar */
.hero-nav { padding: 10px 10px 12px; border-right: 1px solid var(--hairline); display: flex; flex-direction: column; gap: 1px; background: rgba(0,0,0,.16); overflow: hidden; }
.hero-nav-group { font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--text-faint); margin: 9px 8px 4px; }
.hero-nav-group:first-child { margin-top: 2px; }
.hero-nav-item { display: flex; align-items: center; gap: 10px; padding: 5px 10px; border-radius: var(--r-sm); font-size: 12.5px; font-weight: 500; color: var(--text-mute); white-space: nowrap; }
.hero-nav-item svg { width: 16px; height: 16px; flex: 0 0 auto; }
.hero-nav-item.is-active { background: var(--surface-2); color: var(--text); }
.hero-nav-item.is-active svg { color: var(--accent); }

/* ---------- main: the optimize demo ---------- */
.hero-app { padding: clamp(15px, 1.6vw, 20px); display: flex; flex-direction: column; }
.hero-app-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.hero-app-title { font-size: 14px; font-weight: 700; letter-spacing: -.01em; color: var(--text); }
.hero-gtabs { display: inline-flex; gap: 3px; background: var(--bg-2); border: 1px solid var(--hairline); border-radius: var(--r-pill); padding: 3px; }
.hero-gtab { font-family: inherit; font-size: 12px; font-weight: 600; color: var(--text-mute); padding: 5px 12px; border: 0; background: transparent; border-radius: var(--r-pill); cursor: pointer; transition: color var(--t), background var(--t); }
.hero-gtab:hover { color: var(--text-dim); }
.hero-gtab.is-active { background: var(--surface-3); color: var(--text); }

.hero-app-rig { margin-top: 10px; font-family: var(--font-price); font-variant-numeric: tabular-nums; font-size: 12px; letter-spacing: .01em; color: var(--text-mute); }

/* The FPS readout: the ONE loud thing left inside the window, and capped at 40px
   so the 88px headline above it is unambiguously the larger figure on the fold. */
.hero-fpsline { margin-top: 10px; display: flex; align-items: baseline; gap: 9px; }
.hero-fps-big { font-family: var(--font-price); font-variant-numeric: tabular-nums; font-size: clamp(30px, 2.9vw, 40px); font-weight: 700; line-height: 1; letter-spacing: -.02em; color: var(--accent); }
.hero-fps-u { font-size: 13.5px; font-weight: 600; color: var(--text-mute); }
.hero-fps-state { margin-left: auto; align-self: center; font-size: 12.5px; font-weight: 600; color: var(--text-mute); font-variant-numeric: tabular-nums; }
.hero-fps-state.is-done { color: var(--accent); }

.hero-bar { margin-top: 12px; height: 5px; border-radius: 999px; background: var(--bg-2); overflow: hidden; }
.hero-bar span { display: block; height: 100%; border-radius: 999px; background: var(--accent-grad); transition: width var(--t-slow) var(--e-out); }
/* the honesty caveats sit on --text-mute, not --text-faint: a caveat nobody can
   read is not a caveat. Still the quietest text in the block. */
.hero-fps-cap { margin-top: 8px; font-size: 11.5px; line-height: 1.4; color: var(--text-mute); }
.hero-fps-cap span { font-family: var(--font-price); font-variant-numeric: tabular-nums; color: var(--text-dim); }

/* scan checklist: a quiet row per system. While a row is working a small accent
   dot pulses; once done it becomes a bare accent checkmark. The pulse belongs to
   .is-active only, which the script clears as each row finishes, so nothing here
   keeps animating once the demo has settled. */
.hero-tasks { margin-top: 12px; }
.hero-task { display: flex; align-items: center; gap: 11px; padding: 6px 2px; border-top: 1px solid var(--hairline); }
.hero-task:first-child { border-top: 0; }
.hero-task-ic {
  width: 16px; height: 16px; flex: 0 0 auto;
  display: grid; place-items: center; position: relative;
  color: var(--accent);
}
/* idle: a faint hollow marker so the row isn't lopsided before it runs */
.hero-task-ic::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--hairline-strong);
  transition: background var(--t-fast) var(--e-out), transform var(--t-fast) var(--e-out);
}
.hero-task-ic svg { width: 14px; height: 14px; opacity: 0; transform: scale(.7); transition: opacity var(--t-fast) var(--e-out), transform var(--t-fast) var(--e-out); }
.hero-task-n { font-size: 13px; font-weight: 500; color: var(--text-mute); transition: color var(--t-fast); }

/* mid-scan: the marker glows in the accent and gently pulses */
.hero-task.is-active .hero-task-ic::before { background: var(--accent); transform: scale(1.15); animation: hero-pulse 1s var(--e-out) infinite; }
.hero-task.is-active .hero-task-n { color: var(--text-dim); }

/* done: hide the dot, reveal the bare accent checkmark */
.hero-task.is-done .hero-task-ic::before { opacity: 0; transform: scale(0); animation: none; }
.hero-task.is-done .hero-task-ic svg { opacity: 1; transform: scale(1); }
.hero-task.is-done .hero-task-n { color: var(--text-dim); }

@keyframes hero-pulse { 0%,100% { opacity: 1; } 50% { opacity: .4; } }

/* Run row: the real, clickable control that drives the demo.
   It used to be a filled accent slab with its own coloured glow, sitting about
   400px below an identical filled accent slab that is the page's actual call to
   action. Two of the same object on one screen means neither is the one thing
   the eye lands on, and the one that is not for sale was the larger. It is now a
   hairline control: still obviously a button, still the brightest thing in its
   own row, no longer a second primary. */
.hero-run { margin-top: 14px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.hero-opt {
  font-family: inherit; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 18px; border-radius: var(--r-xs);
  background: var(--surface-2); border: 1px solid var(--hairline-strong);
  color: var(--text);
  font-size: 13.5px; font-weight: 600; letter-spacing: -.01em;
  transition: background var(--t), border-color var(--t), color var(--t), opacity var(--t);
}
.hero-opt:hover { background: var(--surface-3); border-color: var(--accent); }
.hero-opt:disabled { opacity: .6; cursor: default; }
.hero-run-note { font-size: 12px; color: var(--text-mute); max-width: 26ch; line-height: 1.45; }

/* ---------- responsive ---------- */
@media (max-width: 980px) {
  .hero-shot { perspective: none; }
  .hero-win { transform: none; box-shadow: inset 0 1px 0 rgba(255,255,255,.07), 0 40px 80px -46px rgba(0,0,0,.9); }
}
@media (max-width: 720px) {
  .hero-win-body { grid-template-columns: 1fr; }
  .hero-nav { display: none; }
  .hero-fps-big { font-size: 32px; }
}
/* The facts row tightens rather than stacking all the way down: three figures
   across still reads as one strip, a stacked ladder of three reads as another
   section, which is exactly what this row exists to stop being. Measured at
   380px the three cells span 22-358 inside a 20-360 gutter, so the last step
   buys back about 12px a side rather than letting it graze the edge. */
@media (max-width: 520px) {
  .hero-facts { gap: 20px; }
  .hero-fact-k { margin-top: 6px; font-size: 11.5px; }
}
@media (max-width: 420px) {
  .hero-facts { gap: 16px; }
  .hero-fact-k { font-size: 11px; }
}
@media (max-width: 460px) {
  .hero-app-top { align-items: flex-start; }
  .hero-run-note { max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-win, .hero-bar span, .hero-task-ic, .hero-task-ic::before, .hero-task-ic svg, .hero-opt { transition: none; }
  .hero-task.is-active .hero-task-ic::before { animation: none; }
}
