/* Anamika's hero + globe stage, lifted from her homepage zip and re-pointed at
   our tokens. Her palette vars are aliased to ours at the top so the rules
   themselves are unedited: --blue-600 becomes our --accent, her ink ramp becomes
   our --fg ramp, her DM Sans becomes our --font-display. Geometry vars
   (--globe-w, --globe-inset, --bw-size) are hers verbatim and MUST track
   public/home/globe.js, which reads the same 0.13 sphere inset. */
#stage {
  /* The globe canvas is deliberately wider than a phone (566px at 393px wide)
     and .globe-rail is absolute, so without this the hero adds horizontal scroll
     to the whole document on iOS, where a root-level overflow-x:clip does not
     reliably propagate to the viewport. */
  overflow: clip;
  --blue-600: var(--accent);
  --blue-500: var(--accent);
  --blue-400: #7fb0ff;
  --ink: var(--fg, #0a1326);
  --ink-70: var(--fg-muted, #3d4661);
  --font-display: 'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif;
  --nav-h: 76px;
  --ticker-h: 0px;
  --globe-w: min(1000px, 66vw, calc((100svh - 180px) * 1.3514));
  /* Was min(820px,144vw): deliberately oversized so the sphere bled off both
     edges as a backdrop. The owner asked for the whole globe in frame on a
     phone. Width alone was not enough: at 92vw the sphere was 362px tall inside
     a 267px band and #stage clips, so its bottom was cut flat. The svh term is
     what keeps the whole circle inside the band. */
  --globe-w-sm: min(560px, 88vw, 34svh);
  --globe-inset: 0.13;
  --bw-size: clamp(58px, 9.4vw, 138px);
  --bw-h: calc(var(--bw-size) * .92);
  --ease: cubic-bezier(.22,.61,.36,1);
  --ease-out: cubic-bezier(.16,1,.3,1);
}


/* ============================================================
   HERO
   ============================================================ */
#hero{
  position:relative;min-height:calc(100svh - var(--nav-h) - var(--ticker-h));
  /* centred, not bottom-anchored: with the headline carrying the section on
     its own, the copy block reads best level with the globe's equator */
  display:flex;align-items:center;
  padding:56px 0 64px;
  background:linear-gradient(180deg,#f4f8ff 0%, #eaf1fe 42%, #ffffff 100%);
  overflow:hidden;
}

.hero-map{position:absolute;inset:0;overflow:hidden;pointer-events:none}

.hero-map > svg{
  position:absolute;top:54%;right:-8%;left:auto;transform:translateY(-50%);
  width:min(1008px,82vw);height:auto;opacity:.9;
}

.hero-fade{position:absolute;inset:auto 0 0;height:34%;background:linear-gradient(180deg,transparent,#fff 88%);pointer-events:none}


/* Asymmetric hero: copy sits low-left, globe breathes on the right.
   Deliberately off-centre — a dead-centred stack reads mechanical. */
.hero-inner{position:relative;z-index:2;text-align:left;opacity:calc(1 - var(--hp,0))}

#stage.handoff .hero-inner{pointer-events:none}

.hero-copy{max-width:760px;padding-top:var(--bw-h)}

.hero-eyebrow{
  display:inline-flex;align-items:center;gap:10px;
  padding:8px 18px 8px 12px;border-radius:999px;
  background:rgba(255,255,255,.75);border:1px solid rgba(37,99,235,.16);
  backdrop-filter:blur(10px);
  font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--blue-600);
  box-shadow:0 4px 16px -6px rgba(37,99,235,.25);
  opacity:0;transform:translateY(14px);animation:heroIn .6s var(--ease-out) .06s forwards;
}

.hero-eyebrow .dot{width:7px;height:7px;border-radius:50%;background:var(--blue-500);position:relative}

.hero-eyebrow .dot::after{content:"";position:absolute;inset:-4px;border-radius:50%;border:1px solid var(--blue-500);opacity:.5;animation:pulseRing 2.4s ease-out infinite}

@keyframes pulseRing{0%{transform:scale(.7);opacity:.7}100%{transform:scale(1.8);opacity:0}}


/* The headline belongs to the whole stage, not to the hero: it pins in
   place and its first glyph cross-fades W -> H, so the second section's
   heading arrives without the word ever shifting. */
.bw-rail{
  /* The rail starts exactly where the word sits, so the sticky element has
     no top margin. With a margin the release point depends on how a browser
     reads the sticky containment rule, and engines disagree. */
  position:absolute;left:0;right:0;top:var(--bw-mt,38vh);
  z-index:2;pointer-events:none;
  height:var(--bw-span,60vh);
  transition:opacity .35s var(--ease-out);
}

.bw-rail.gone{opacity:0}

/* sticky is bounded by the element's own parent, so the wrap has to span
   the whole rail or the word would unstick after its own height */
.bw-rail > .wrap{height:100%}

h1.bw{
  position:sticky;
  /* the rail's own offset puts the word at its static spot, and this top
     pins it to the same viewport line, so it cannot drift on handover */
  top:var(--bw-top,38vh);
  margin:0 0 0 -.06em;
  font-size:var(--bw-size);font-weight:800;letter-spacing:-.022em;line-height:.92;
  color:var(--ink);
}

.bw .w{display:inline-block;overflow:hidden;vertical-align:top;padding:0 .06em}

.bw .w > i{
  display:inline-block;font-style:normal;white-space:nowrap;
  transform:translateY(105%) rotate(4deg);
  animation:wordUp .62s var(--ease-out) .14s forwards;
}

/* the swap sits in a box the width of the W, so nothing reflows */
.bw-swap{position:relative;display:inline-block}

.bw-swap b{font-weight:inherit}

.bw-swap .bw-b{position:absolute;left:0;top:0;white-space:pre}

/* "Wh" leaves upward, "H" arrives from below, and "ere" closes the gap:
   read as one glyph replacing another rather than two ghosts crossfading */
.bw-a{display:inline-block;opacity:var(--pa,1);
      transform:translateY(calc(var(--p,0) * -.17em))}

.bw-b{opacity:var(--pb,0);
      transform:translateY(calc((1 - var(--pb,0)) * .17em))}

.bw-rest{display:inline-block;
         transform:translateX(calc(var(--q,0) * var(--bw-dx,0px)))}


/* The original site's hook: "Where ... are your <thing>?" with the
   subject cycling. Slot width animates to each word so the closing
   question mark glides instead of jumping. */
.hero-rotator{
  display:flex;align-items:flex-end;flex-wrap:wrap;
  font-family:var(--font-display);
  font-size:clamp(30px,4.1vw,58px);font-weight:500;
  color:var(--ink-70);line-height:1.1;
  margin-top:8px;
  opacity:0;transform:translateY(14px);
  animation:heroIn .6s var(--ease-out) .42s forwards;
}

.rot-lead{line-height:1.12;margin-right:.28em}

.rot-q{line-height:1.12}

.rot-slot{
  position:relative;display:block;height:1.12em;overflow:hidden;
  width:0;transition:width .32s var(--ease-out);
}

.rot-word{
  position:absolute;left:0;bottom:0;white-space:nowrap;line-height:1.12;
  color:var(--blue-600);font-weight:600;
  opacity:0;transform:translateY(105%);
  transition:opacity .26s var(--ease), transform .32s var(--ease-out);
}

.rot-word.on{opacity:1;transform:none}

.rot-word.out{opacity:0;transform:translateY(-105%)}


.bw .grad{
  background:linear-gradient(100deg,var(--blue-600) 10%, #4f46e5 55%, var(--blue-400) 95%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}

@keyframes heroIn{to{opacity:1;transform:none}}


.hero-cta{
  display:flex;gap:14px;justify-content:flex-start;flex-wrap:wrap;margin-top:38px;
  opacity:0;transform:translateY(16px);animation:heroIn .62s var(--ease-out) .68s forwards;
}

html:not(.bw-hidden) .pstat.on{opacity:0;transform:translateY(28px);pointer-events:none}

.logo .bw{display:inline-block}


/* SVG fallback sphere only — the 3D canvas is driven from JS */
html.preloading .hero-map > svg{opacity:0}

html.preloading.pl-lift .hero-map > svg{opacity:1;transition:opacity 1.1s ease .3s}

html.preloading .hero-inner > *,
html.preloading .bw .w > i,
html.preloading .scroll-hint{animation-play-state:paused}
@media (prefers-reduced-motion:reduce){

  html.preloading .hero-map,
  html.preloading #nav,
  html.preloading #whatsnew{opacity:1}
}


/* ============================================================
   3D GLOBE (Three.js) — real Earth texture, replaces the SVG
   sphere when WebGL + the library are available.
   ============================================================ */
#globe3d{
  /* The canvas is lifted by its own inset (clipped by the stage) so the
     visible sphere hugs the top. margin-top and top carry the same value,
     which makes the static and pinned positions identical — so the globe
     never drifts as sticky takes over. */
  position:sticky;
  margin-top:calc(24px - var(--globe-w) * var(--globe-inset));
  top:calc(var(--nav-h) + var(--ticker-h) + 24px - var(--globe-w) * var(--globe-inset));
  width:100%;aspect-ratio:1;
  pointer-events:none;
  opacity:.74 !important;
  will-change:transform,opacity;
}

/* scrim now leans left, clearing the copy column while leaving the
   globe on the right almost untouched */
.hero-scrim{
  position:absolute;inset:0;pointer-events:none;z-index:1;
  background:linear-gradient(96deg,
             rgba(255,255,255,.94) 0%,
             rgba(255,255,255,.82) 26%,
             rgba(255,255,255,.42) 48%,
             rgba(255,255,255,0)   70%);
  /* and no hard bottom edge either, or the seam just moves down to where
     the stage ends and the globe is still on screen */
  -webkit-mask-image:linear-gradient(180deg,#000 0%,#000 76%,transparent 100%);
          mask-image:linear-gradient(180deg,#000 0%,#000 76%,transparent 100%);
}

#globe3d canvas{width:100%!important;height:100%!important;display:block}

/* invisible element matching the rendered sphere's exact footprint —
   the preloader hands the flying logo globe off to this box */
#globeTarget{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:74%;height:74%;border-radius:50%;pointer-events:none;
}

#hero.has3d .hero-map > svg{display:none}

/* the SVG sphere stays in the markup as a no-WebGL fallback */

/* ============================================================
   STAGE — the hero and the "Why Lepton" block sit on one shared
   canvas, with the globe pinned to the right rail so it carries
   continuously across both instead of restarting per section.
   ============================================================ */
#stage{
  position:relative;
  /* `clip` not `hidden`: hidden would make this a scroll container and
     kill the globe's position:sticky. clip confines the rail without that. */
  overflow:clip;
  background:linear-gradient(180deg,
    #f4f8ff 0%, #eaf1fe 22%, #f1f6ff 52%, #f8fbff 78%, #ffffff 100%);
}

.globe-rail{
  position:absolute;top:0;bottom:0;
  /* The sphere sits 13% inside the canvas, so offsetting by that much
     minus a margin keeps its right edge a fixed 24px off the viewport
     edge at every globe size, instead of drifting with a % offset. */
  right:calc(24px - var(--globe-w) * var(--globe-inset));
  width:var(--globe-w);
  pointer-events:none;z-index:0;
}

#stage > #hero,
#stage > #pillars{position:relative;z-index:1}

#hero{background:transparent}

/* the stage gradient handles the transition now, so the hero's own
   white fade would only read as a seam between the two sections */
#stage .hero-fade{display:none}
@media (max-width:900px){

  .globe-rail{right:auto;left:50%;transform:translateX(-50%);width:var(--globe-w-sm)}
}
@media (max-width:900px){

  /* hero returns to a stacked reading order; globe sits behind the copy */
  #hero{align-items:center;padding:44px 0 72px}

  /* Opacity is set by the globe-ready rule further down, which carries
     !important; this one only positions. */
  #globe3d{margin-top:0;top:calc(38svh - var(--globe-w-sm) / 2)}

  .hero-map > svg{right:auto;left:50%;top:42%;transform:translate(-50%,-50%);width:min(560px,88vw,34svh)}

  .hero-scrim{background:linear-gradient(180deg,
              rgba(255,255,255,.72) 0%, rgba(255,255,255,.5) 46%, rgba(255,255,255,.78) 100%)}
}
@media (max-width:640px){

  /* min-height:0 made the hero content-driven, but the globe behind it is sized
     from the viewport (34svh). On a tall phone the globe grew past the section
     and #stage clipped its bottom flat: measured at a 393x852 viewport, globe
     bottom 405 against a hero bottom of 382. The floor below is the globe plus
     room for the headline, so the section always contains the sphere. */
  #hero{min-height:calc(34svh + 230px);padding:calc(var(--nav-h) + 34px) 0 62px}

  .hero-cta .btn{width:100%}
}

/* keyframes referenced by the rules above */
@keyframes wordUp{to{transform:translateY(0) rotate(0)}}

/* ── ADDED ON PORT ────────────────────────────────────────────────────────
   The headline is positioned by --bw-top, which hero.js only sets once it can
   measure the copy block. Before that it falls back to 38vh and lands ON TOP of
   the rotator. Hold it invisible until the measurement lands, then fade in. */
#bwRail { opacity: 0; transition: opacity .28s ease; }
html.bw-ready #bwRail { opacity: 1; }
/* ...but never while the intro is running. `html.preloading` PAUSES the wordUp
   animation, so `i` sits at translateY(105%) inside `.w { overflow: hidden }` and
   only the tops of the glyphs peek out — the two black bars. bw-ready is set by
   measure() which runs well before the preloader finishes, so without this the
   sliver is painted for the whole intro. */
html.preloading #bwRail { opacity: 0 !important; }
/* MUST out-rank the reveal above. bwHide() puts .gone on the rail once the next
   section arrives, but `html.bw-ready #bwRail` carries an id (110) against
   `.bw-rail.gone` (30), so the hide lost and the headline stayed pinned — it then
   scrolled under the following section and showed as clipped glyph fragments. */
html.bw-ready #bwRail.gone { opacity: 0; }

/* The sphere is a pale disc until the 2048x1024 texture decodes, which reads as a
   broken blank circle on a cold cache. Fade the canvas in on the globe's own
   ready event instead of showing the undressed sphere. */
#globe3d { opacity: 0; transition: opacity .45s ease; }
/* Her own mobile rule drops the globe to .26 so the copy stays readable on a
   phone; a flat .74 here overrode it and buried the buttons in ocean. */
html.globe-ready #globe3d { opacity: var(--globe-op, .74); }
/* Direct, not through the var: the var resolves correctly on #stage but the
   measured opacity on #globe3d still came back .74, and on a phone the globe
   sitting at .74 behind the CTAs is the difference between readable and not. */
@media (max-width: 900px) {
  /* .26 was set to keep the CTAs readable over the globe. The hero no longer
     has CTAs over it, and on a real handset in daylight .26 read as almost
     nothing, so it is .46 now. The scrim above still carries the headline's
     contrast. */
  #stage { --globe-op: .46; }
  /* !important because the identical rule without it lost to something the
     cascade inspector could not show: the media rule matched, was last, and
     outranked every other #globe3d rule, and the element still computed .74. */
  html.globe-ready #globe3d { opacity: .46 !important; }
}


/* The word is hidden the moment it is no longer legible, independent of where the
   next section happens to sit. bwHide() keys off the following section's top, and
   .bw-rail is absolutely positioned with no height, so its sticky child releases
   almost at once and scrolls away with the page. On a short viewport that leaves a
   window where the glyphs are half under the header and `.w{overflow:hidden}` clips
   them into two black rectangles — the leftover "H". Same specificity as the reveal
   guard, declared after it, so it wins. */
html.bw-ready #bwRail.bw-clipped { opacity: 0; }
