Skytrace
aadmin2026-09-03 发布Site模板9 阅读

Skytrace

类型:Site模板 · 分类:未分类 · 收录:2026-09-03 19:57:57 · 源码:会员可见

[hidecontent type="vip1"]
Build a single static web page — one HTML file, one CSS file, one JS file — reproducing the
"Axiom — Cloud Observability" landing page EXACTLY to the spec below. Every number is literal;
do not round, re-derive, or "clean up" values. No framework, no build step, no CDN.


=============================================================
0. THE THREE THINGS THAT GO WRONG. FIX THESE FIRST.
=============================================================
Previous attempts at this spec failed in exactly three ways. Read all three before writing code.

--- FAILURE 1: the responsive architecture was never built. ---
SYMPTOM: at a ~1000px-wide window the navbar still shows five inline links and a Login button
  with no hamburger, and ALL the type — navbar worst of all — is microscopic (~8.5px).
CAUSE: the build applied the desktop artboard downscale at every width. The page is a 1800x2612
  artboard scaled by scale = paintableWidth / 1700.406. At 1009px paintable that scale is
  0.5934, so authored 14.268px navbar type lands on screen at 14.268 x 0.5934 = 8.5px.
FIX: the 1280px breakpoint in section 7 is MANDATORY, not optional polish. Below it both
  sections CANCEL the scale with transform: scale(calc(1 / var(--artboard-scale))) and lay
  themselves out in real device pixels, the inline nav links move into a closed drawer, and
  .nav-toggle becomes visible. Verified on the reference build at 1009px paintable:
  architecture TABLET, navToggle visible, drawer visibility hidden, nav link font 14.268px
  rendering at its true 14.268px because the two scales cancel.

--- FAILURE 2: the partner rail's right edge is visibly cut. ---
SYMPTOM: a logo at the right end of the "Trusted by engineering teams globally" row is sliced
  off against a hard edge instead of dissolving out.
CAUSE: .partner-fade--right was omitted, not right-anchored, or sized wrong. That fade is
  LOAD-BEARING, not decoration. The rail image is 1252.026 wide inside a 612.268 window and
  animates translateX(0) -> translateX(-650.766px). Because 1252.026 - 650.766 = 601.26, which
  is 11.008px LESS than the 612.268 window, the last ~11px of the strip is genuinely EMPTY at
  the end of every 18s cycle, and the sequence restart is a jump, not a seam-free wrap. The
  right fade is what makes both invisible.
FIX: build the fades to the exact geometry in section 5 and assert the coverage test in
  section 12. Do NOT "fix" the rail by changing 650.766, 1252.026, 612.268 or the 18s duration
  — those are authored values. Cover the tail; do not re-cut the rail.

--- FAILURE 3: the hero is not one viewport tall. ---
SYMPTOM: a gap of sky below the fold, or the hero overflowing a short window.
CAUSE: clamping the hero height. Measured on the unfixed reference build: at 1425x1440 the hero
  was 961.8px = 66.8% of the viewport (478px short); at 1425x700 it was 754.2px = 107.7%; at
  1009x768 it was 647.6px = 84.3%.
FIX: the [100VH] lines in sections 3, 5, 7, 8 and 11. The hero band is EXACTLY 100vh at every
  width and height, with no minimum and no maximum.

Two standing constraints govern everything else:

  [100VH]  Lines so marked enforce failure-3's fix.
  [FROZEN] EVERY other piece of UI is unchanged, pixel for pixel. Reproduce verbatim.

If a [100VH] instruction seems to require altering a [FROZEN] element, you have misread it:
the hero BAND changes height; the hero's CONTENTS are never restyled.


=============================================================
1. [FROZEN] THINGS THAT MUST NOT CHANGE
=============================================================
Do not restyle, resize, recolour, reposition, re-crop, replace, "improve", "modernise",
"clean up", or regenerate ANY of the following. They are already correct:

  * THE NAVBAR LOGO. Both marks stay exactly as supplied:
      - a.brand-mark   -> assets/brand-mark.svg      left:84.924px  top:85.359px
                          42.434 x 42.434, img width:100% height:100% display:block
      - a.wordmark     -> assets/axiom-wordmark.svg  left:788.123px top:93.441px
                          79.152 x 19.706, img width:100% height:100% display:block
    Use the supplied SVG files byte-for-byte. Do NOT redraw them, do NOT substitute live text
    "axiom." for the wordmark, do NOT swap in an icon font or an inline <svg> you author, do
    NOT recolour or filter them, do NOT change either box's coordinates or dimensions, and do
    NOT alter their responsive rules in section 7. If the files are missing, STOP and ask —
    do not improvise a replacement.
  * The rest of the navigation: .nav-left, .nav-right, .nav-links, .login-button, .nav-toggle,
    .nav-drawer, and all their coordinates, colours, blurs and radii.
  * The headline's two-overlaid-node construction and all its type metrics.
  * .hero-copy > p, .trial-button, .hero-dots.
  * .insight-card and .diamond-grid in full.
  * .trusted-block, .partner-strip, .partner-fade and the 18s rail animation.
  * .hero-divider, .carousel-controls, .carousel-button.
  * The entire problem section: kicker, divider, heading, four cards, both captions, every
    illustration coordinate.
  * All fonts, sizes, weights, line-heights, letter-spacings, colours and radii anywhere.
  * The entrance-motion timeline (section 10) — every duration, delay and easing.
  * All asset URLs (section 2).

The only things that differ from a naive build are the [100VH] items and the correct
construction of failures 1 and 2 above. Nothing else.


=============================================================
2. ASSETS  [FROZEN]
=============================================================
REMOTE (use verbatim — live CloudFront assets):

  Hero background video — 10s, 1440x1440, silent, seamless cloud loop:
  https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260827_000114_3a4353ea-66bd-4c61-afe8-db78a4495313.mp4

  Hero cloud still (2048x2048) — the video's fallback plate:
  https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/bb93fb3b-7156-469c-8d7d-d48c4adcf876.png

  Hero cloud desktop plate (1254x1254) — atmosphere-only variant, >=1280px:
  https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/60fdb0d8-1e5e-4e7b-ace7-50b782304c5b.png

  Card illustration A — "Distributed nodes converging into a server" (transparent PNG):
  https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260827_000149_54b3763c-dd74-44a1-9678-dcf83880d28d.png

  Card illustration B — "Infrastructure and server visibility diagram" (transparent PNG):
  https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260827_000152_2c23e513-b00b-46b0-8be7-b09ffad2bb4d.png

  Card illustration C — "Network connections breaking at a bottleneck" (transparent PNG):
  https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260827_000158_92c0c95f-dead-4c0b-a911-7c4b39530270.png

LOCAL, required in assets/ (vector — must be supplied, cannot be generated from text):
  brand-mark.svg        square mark, 42.434px                 <- NAVBAR LOGO, see section 1
  axiom-wordmark.svg    "axiom." wordmark, 79.152 x 19.706    <- NAVBAR LOGO, see section 1
  partner-logos.svg     THE RAIL. Authored as
                          <svg preserveAspectRatio="none" overflow="visible"
                               style="display:block" width="1252.03" height="25.6219"
                               viewBox="0 0 1252.03 25.6219" fill="none">
                        containing the Intel / Google / Sony / Amazon / Adobe wordmarks laid
                        out left to right and then repeated, in flat #C2C2C2 paths. Its
                        intrinsic 1252.026 x 25.622 and its preserveAspectRatio="none" both
                        matter — see failure 2 and section 5.
  card-grid.svg         faint technical grid, 809.466 x 525.446
  arrow-prev.svg, arrow-next.svg   16.165px chevrons

FONTS, local in assets/fonts/, via @font-face, font-display: swap:
  geist-mono-regular.ttf       -> "Geist Mono" 400 normal
  geist-mono-medium.ttf        -> "Geist Mono" 500 normal
  playfair-display-italic.ttf  -> "Playfair Display" 400 italic
  aeonik-trial-regular.woff2   -> "Aeonik TRIAL" 400 normal
  aeonik-trial-bold.woff2      -> "Aeonik TRIAL" 700 normal
  NOTE: Aeonik TRIAL's charset covers only A-Z a-z 0-9 space comma hyphen period. The slash in
  "Login / Sign in" intentionally falls through the rest of the stack per-glyph. Do not fix it.
  Aeonik TRIAL is a trial licence — do not redistribute it publicly. Free substitutes: Inter or
  Manrope for Aeonik; Geist Mono and Playfair Display are both on Google Fonts.

Stacks:
  --aeonik: "Aeonik TRIAL", "Aeonik", "Helvetica Neue", Arial, sans-serif;
  mono: "Geist Mono", monospace       italic accent: "Playfair Display", Georgia, serif


=============================================================
3. ARCHITECTURE AND THE TYPE SCALE
=============================================================
ONE fixed 1800 x 2612 px artboard, uniformly scaled to the viewport by JS.
  scale = document.documentElement.clientWidth / 1700.406
Desktop (>=1280px) renders that artboard verbatim, absolutely positioned. Below 1280px both
sections CANCEL that scale and re-lay out in real device pixels as CSS grids.

The artboard is WIDTH-driven; the hero's HEIGHT is driven independently to the viewport.
Keep those two ideas separate — conflating them is failure 3.

*** THE TYPE SCALE IS NOT A BUG. *** Above 1280px every glyph scales with viewport WIDTH,
because the whole artboard does. Authored px are NOT device px there. Reference build,
measured:

  viewport   paintable   scale     nav link + login    trusted label   hero p    h1 mono / italic
  1920x1080    1905     1.1203        15.98px            15.85px       18.11px    73.39 / 82.41px
  1440x900     1425     0.8380        11.96px            11.85px         —              —
  1024x768     1009     0.5934     ARCHITECTURE SWITCHES — see below

  All from one authored 14.268px nav rule. If your navbar type does not track the viewport
  this way above 1280px, the artboard transform is wrong.

  At 1024x768 the reference build reports architecture TABLET, .nav-toggle display != none,
  .nav-drawer visibility hidden, and .nav-links a computed font-size 14.268px rendering at a
  true 14.268px because the artboard scale and its inverse cancel. A build showing five inline
  links and 8.5px type at this width has skipped section 7 entirely — that is failure 1.

DOM skeleton:
  body > .site-shell#site-shell > main.artboard#artboard
    > .page-surface (aria-hidden)
    > header.hero#home
    > section.problem#problem
  <script src="js/script.js"></script> at end of body.

<head>, in this order:
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="Axiom cloud observability platform">
  <title>Axiom — Cloud Observability</title>
  An INLINE blocking script (before the stylesheet):
      document.documentElement.classList.add("motion-ready");
      window.__motionFallback = window.setTimeout(function () {
        document.documentElement.classList.remove("motion-ready");
      }, 5000);
  <link rel="stylesheet" href="css/style.css">
  <noscript><style>.motion-ready * { animation: none !important; }</style></noscript>

:root custom properties:
  --ink: #000e23;  --stage: #000c1e;
  --artboard-width: 1800;  --artboard-height: 2612;
  --hero-offset: 0px;
  --hero-top-shift: 0px;
  --hero-content-scale: 1;              /* [100VH] the ONLY new property; see section 11 */
  --hero-title-size: 65.512px;
  --hero-title-leading: 1.1;
  --hero-title-line: calc(var(--hero-title-size) * var(--hero-title-leading));
  --problem-heading-shift: 0px;  --problem-card-shift: 0px;
  --problem-card-scale: 1;  --problem-caption-shift: 0px;
  --aeonik: (stack above)
  --entrance-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --entrance-ease-soft: cubic-bezier(0.16, 1, 0.3, 1);
  --entrance-shift: 22px;               /* 14px at max-width:475px */
JS also writes --artboard-scale, --viewport-width, --hero-content-scale at runtime.


=============================================================
4. BASE / RESET  [FROZEN]
=============================================================
  * { box-sizing: border-box }
  html { scroll-behavior: smooth; background:#fff; scrollbar-gutter: stable; }
     (scrollbar-gutter is load-bearing: the artboard scales off paintable width, so a
      mid-resize scrollbar would feed back into its own scale.)
  body { margin:0; min-width:320px; overflow-x:hidden; background:#fff; }
  ::selection { color:#fff; background:#2372ae; }
  p,h1,h2,h3,span,em { user-select: text }
  img { -webkit-user-drag:none; user-select:none }
  a,button { -webkit-tap-highlight-color: transparent }
  .site-shell { position:relative; margin:0 auto; width:100%; height:100vh; overflow:hidden;
                background:#fff }
  .artboard   { position:absolute; inset:0 auto auto 0; width:1800px; height:2612px;
                overflow:hidden; transform-origin:top left; background:#fff }
  .page-surface { position:absolute; z-index:0; left:52.592px; top:50px;
                  width:1700.406px; height:3570.481px; background:#fff }
  .hero, .problem { position:absolute; inset:0 }


=============================================================
5. SECTION 1 — HERO (desktop artboard geometry)
=============================================================
MARKUP, in this order inside <header class="hero" id="home">:

  <div class="hero-sky" aria-hidden="true">
    img.cloud-layer.cloud-layer--wide                     src=<hero cloud still> alt=""
    img.cloud-layer.cloud-layer--wide.cloud-layer--shift  src=<hero cloud still> alt=""
    img.cloud-layer.cloud-layer--computer                 src=<hero cloud still> alt=""
    video.cloud-layer.cloud-layer--computer.cloud-layer--live
        src=<CloudFront mp4>  muted loop playsinline preload="metadata" aria-hidden="true"
  </div>
  <div class="nav-left" aria-hidden="true"></div>
  <div class="nav-right" aria-hidden="true"></div>
  <div class="hero-panel" aria-hidden="true"></div>
  <nav class="navigation" aria-label="Primary navigation">
    <!-- [FROZEN] navbar logo: exactly these two links, these two SVG files, nothing else -->
    a.brand-mark  href="#home" aria-label="Axiom home" > img src=assets/brand-mark.svg alt=""
    a.wordmark    href="#home" aria-label="Axiom home" > img src=assets/axiom-wordmark.svg
                                                             alt="axiom."
    button.nav-toggle#nav-toggle type=button aria-label="Open menu" aria-expanded="false"
        aria-controls="nav-drawer" > span.nav-toggle-bars aria-hidden > <i><i><i>
    div.nav-drawer#nav-drawer
      > div.nav-links > a href="#problem" x5: Problem, Technology, Pricing, About, Contact
      > a.login-button href="#home">Login / Sign in</a>
  </nav>
  <div class="hero-dots" aria-hidden="true"><i></i><i></i><i></i></div>
  <section class="hero-copy" aria-labelledby="hero-title">
    <h1 id="hero-title"><span>The evolution</span><em>of cloud</em><span>observability</span></h1>
    <p>An intelligent platform to visualize dependencies, predict bottlenecks, and protect your distributed systems from downtime</p>
    <a class="trial-button" href="#problem">Start Free Trial</a>
  </section>
  <aside class="insight-card" aria-live="polite">
    <div class="diamond-grid" aria-hidden="true">
      <i></i><i></i><i class="outline"></i>
      <i></i><i class="outline"></i><i></i>
      <i></i><i></i><i></i>
    </div>
    <h2 id="insight-title">Data Bottlenecks</h2>
    <p id="insight-copy">Microservices and distributed nodes make traditional tracking</p>
  </aside>
  <div class="trusted-block">
    <p>Trusted by engineering teams globally</p>
    <div class="partner-strip"><img src=assets/partner-logos.svg alt="Intel, Google, Sony, Amazon and Adobe"></div>
    <span class="partner-fade partner-fade--left" aria-hidden="true"></span>
    <span class="partner-fade partner-fade--right" aria-hidden="true"></span>
  </div>
  <div class="hero-divider" aria-hidden="true"></div>
  <div class="carousel-controls" aria-label="Insight carousel controls">
    button.carousel-button#previous-slide aria-label="Previous insight" > img arrow-prev.svg alt=""
    button.carousel-button#next-slide     aria-label="Next insight"     > img arrow-next.svg alt=""
  </div>
  NOTE the order: both .partner-fade spans are SIBLINGS of .partner-strip and come AFTER it,
  inside .trusted-block. They are not children of the strip and must not be nested in it.

CSS (desktop):
  /* [100VH] Only these two BAND heights read --hero-offset for sizing. */
  .hero-sky   z-index:1; left:52.592px; top:50px; width:1700.406px;
              height: calc(1147.749px + var(--hero-offset));      /* [100VH] */
              overflow:hidden; background:#2372ae
  .hero-panel left:70.779px;  top:150.027px; width:821.409px;
              height: calc(1029.539px + var(--hero-offset));      /* [100VH] tracks the sky */
              background:#fff

  [FROZEN] the cloud plates:
  .cloud-layer            position:absolute; display:block; max-width:none; object-fit:cover;
                          pointer-events:none
  .cloud-layer--wide      left:-14.914px; top:-380.352px; width:2038.81px; height:2038.81px
  .cloud-layer--shift     top:-208.82px
  .cloud-layer--computer  left:659.754px; top:-9.098px; width:1207.359px; height:1207.359px
  .cloud-layer--live      aspect-ratio:1;
                          mask-image: linear-gradient(to right, transparent 0, #000 8%);
                          opacity:0; transition: opacity 420ms ease
  .cloud-layer--live[data-playing] { opacity:1 }
     (Revealed ONLY once genuinely playing, so blocked autoplay / load failure /
      reduced-motion all land on the identical still underneath, never on a gap. The 8% left
      feather dissolves the video's slightly different blue into the still.)
  @media (min-width:1280px):
     .cloud-layer--wide { content: url(<hero cloud desktop plate>) }
     .cloud-layer--computer { mask-image: linear-gradient(to right, transparent 0 16%, #000 32%) }
  @media (prefers-reduced-motion: reduce) { .cloud-layer--live { transition:none } }

  .nav-left,.nav-right,.hero-panel { position:absolute; z-index:2; border-radius:4.041px }
  .nav-left   left:70.779px;  top:68.188px;  width:821.409px; height:72.745px; background:#fff
  .nav-right  left:902.291px; top:68.188px;  width:832.522px; height:72.745px;
              background: rgba(255,255,255,.1); backdrop-filter: blur(19.55px)
  .navigation { position:absolute; z-index:10; inset:0; pointer-events:none }
  .navigation a, .nav-links { pointer-events:auto }
  .brand-mark,.wordmark,.login-button,.nav-links { position:absolute }
  /* ---- [FROZEN] NAVBAR LOGO — these three rules ARE the logo. Do not touch. ---- */
  .brand-mark  left:84.924px;  top:85.359px; width:42.434px; height:42.434px
  .brand-mark img,.wordmark img { width:100%; height:100%; display:block }
  .wordmark    left:788.123px; top:93.441px; width:79.152px; height:19.706px
  /* ------------------------------------------------------------------------------ */
  .nav-links   left:929.568px; top:91.426px; display:flex; align-items:center; gap:63.652px
  .nav-links a, .login-button {
      color: rgba(255,255,255,.84); font-family:var(--aeonik); font-size:14.268px;
      font-weight:700; line-height:26.158px; letter-spacing:-0.143px;
      text-decoration:none; white-space:nowrap }
      (700 is correct — the frame labels it SemiBold but its stored label widths match Bold.
       14.268px is the AUTHORED size; see the type-scale table in section 3 for what the eye
       actually gets at each width. Do not inflate it to "fix" apparent smallness.)
  .login-button left:1587.307px; top:78.285px; width:136.734px; height:53.158px;
      display:grid; place-items:center; border-radius:2.378px; color:#252525; background:#fff

  /* [100VH] The TOP copy group. Coordinates, type and colour are [FROZEN]; the only addition
     is the guard scale appended to the transform each already had. Both boxes scale about the
     SAME artboard point so they stay locked together at every height. */
  .hero-dots   z-index:4; left:457.74px; top:344.014px; display:flex; gap:8.081px;
               transform: translateY(var(--hero-top-shift)) scale(var(--hero-content-scale));
               transform-origin: 23.25px 0
  .hero-dots i { width:10.103px; height:10.103px; display:block; background:var(--ink) }
  .hero-copy   z-index:4; inset:0; color:#000; text-align:center;
               transform: translateY(var(--hero-top-shift)) scale(var(--hero-content-scale));
               transform-origin: 481.2px 344.014px
                 /* 481.2 = 220.309 + 521.797/2, the copy column's centre; 344.014 = the top
                    of the stack, matching hero-dots' own origin in artboard space. */

  [FROZEN] .hero-copy h1 { position:absolute; left:220.309px; top:407.66px; width:521.797px;
                  margin:0; text-transform:uppercase }
  .hero-copy h1 span { display:block; font-family:"Geist Mono",monospace;
                       font-size:var(--hero-title-size); font-weight:400;
                       line-height:var(--hero-title-leading); letter-spacing:-3.931px }
  .hero-copy h1 span:last-child { margin-top: var(--hero-title-line) }
  .hero-copy h1 em { position:absolute; z-index:1; left:0; top:65.512px; width:100%;
                     font-family:"Playfair Display",Georgia,serif; font-size:73.557px;
                     font-weight:400; line-height:0.95; letter-spacing:-4.413px;
                     text-transform:none }
     CRITICAL: the headline is two overlaid nodes. The mono block reserves an EMPTY middle
     line (that is what span:last-child's margin-top does) and the Playfair italic is
     absolutely positioned into it. Do NOT flatten this into three flowed lines — the air
     above and below "of cloud" comes from the reserved line box.
  .hero-copy > p { position:absolute; left:310.23px; top:656.207px; width:342.506px; margin:0;
                   font-family:var(--aeonik); font-size:16.165px; line-height:1.2;
                   letter-spacing:-0.323px }
  .trial-button { position:absolute; left:381.965px; top:759.258px; width:198.683px;
                  height:56.579px; display:grid; place-items:center; border-radius:2.021px;
                  color:#fff; background:var(--ink); font-family:"Geist Mono",monospace;
                  font-size:14.145px; font-weight:500; line-height:1; letter-spacing:-0.283px;
                  text-decoration:none; text-transform:uppercase }

  /* [100VH] The BOTTOM group pins to the sky's lower edge by taking the FULL offset. The
     rules are unchanged, but the offset is now signed BOTH ways, so this pins correctly
     whether the viewport is shorter or taller than the design height. */
  .insight-card { z-index:4; left:922.498px; top:901.719px; width:312.196px; height:265.72px;
                  margin:0; border-radius:3.031px; color:#fff; background:rgba(255,255,255,.2);
                  backdrop-filter: blur(31.169px);
                  transform: translateY(var(--hero-offset)) }
  .diamond-grid { position:absolute; left:19.196px; top:19.198px; width:54.929px;
                  height:54.929px; display:grid; grid-template-columns:repeat(3,15.474px);
                  grid-auto-rows:15.474px; gap:4.253px }
  .diamond-grid i { width:10.941px; height:10.941px; place-self:center; transform:rotate(45deg);
                    border-radius:0.608px; background:#fff }
  .diamond-grid i.outline { border:0.608px solid #fff; background:transparent }
  .insight-card h2 { position:absolute; left:18.187px; top:176.814px; margin:0;
                     font-family:"Geist Mono",monospace; font-size:20.207px; font-weight:400;
                     line-height:1.1; letter-spacing:-1.212px; text-transform:uppercase }
  .insight-card p  { position:absolute; left:18.187px; top:213.183px; width:231.368px; margin:0;
                     font-family:var(--aeonik); font-size:15.155px; line-height:1.2;
                     letter-spacing:-0.303px }

  /* ================= THE TRUSTED BLOCK AND ITS FADES — see failure 2 ================= */
  /* Geometry inside .trusted-block's own 660.766 x 99.012 box:
        label      y 0      -> 11.9        (its own line box)
        strip      x 42.435 -> 654.703     y 47.482 -> 72.741
        fade left  x 0      -> 113.158     y 22.226 -> 99.012
        fade right x 547.608-> 660.766     y 22.226 -> 99.012
     So each fade overhangs the strip's cut edge — 70.7px on the left, 107.1px on the right —
     and overruns it vertically top and bottom. That is deliberate: the rail's tail gap and
     its restart jump both happen at the right edge, under the right fade. */
  .trusted-block { z-index:4; left:150.595px; top:1058.322px; width:660.766px; height:99.012px;
                   transform: translateY(var(--hero-offset)) }
  .trusted-block > p { position:absolute; left:190.957px; top:0; margin:0; color:#4a4a4a;
                       font-family:"Geist Mono",monospace; font-size:14.145px; font-weight:500;
                       line-height:1; letter-spacing:-0.283px; text-transform:uppercase;
                       white-space:nowrap }
  .partner-strip { position:absolute; left:42.435px; top:47.482px; width:612.268px;
                   height:25.259px; overflow:hidden }
      /* overflow:hidden is what makes it a window; without it the 1252px rail escapes and
         runs across the whole panel. */
  .partner-strip img { position:absolute; inset:0 auto auto 0; width:1252.026px;
                       height:25.622px; opacity:.28;
                       animation: brand-rail-loop 18s linear infinite; will-change:transform }
  @keyframes brand-rail-loop { from{transform:translateX(0)} to{transform:translateX(-650.766px)} }
  @media (hover:hover) { .partner-strip:hover img { animation-play-state: paused } }
  .partner-fade { position:absolute; z-index:2; top:22.226px; width:113.158px; height:76.786px;
                  background: linear-gradient(90deg,#fff 31.7%, rgba(255,255,255,0) 93.304%) }
  .partner-fade--left  { left:0 }
  .partner-fade--right { right:0; transform:rotate(180deg) }
      /* right:0, NOT left:547.608px — it must stay welded to the block's right edge at every
         scale. The 180deg rotation is what reverses the gradient; do not instead author a
         second mirrored gradient, and do not drop the rotation. z-index:2 puts both fades
         OVER the strip — a fade painted under the rail hides nothing. */
  /* ==================================================================================== */

  .hero-divider { z-index:5; left:71.791px; top:1018.918px; width:820.398px; height:1.01px;
                  background: rgba(0,14,35,.12); transform: translateY(var(--hero-offset)) }
  .carousel-controls { z-index:5; left:1620.643px; top:1116.922px; display:flex; gap:2.023px;
                       transform: translateY(var(--hero-offset)) }
  .carousel-button { width:50.517px; height:50.517px; display:grid; place-items:center;
                     padding:0; border:0; border-radius:3.031px; cursor:pointer;
                     background: rgba(255,255,255,.2); backdrop-filter: blur(31.169px) }
  .carousel-button img { width:16.165px; height:16.165px }
  #previous-slide img { transform: rotate(90deg) }
  #next-slide img     { transform: rotate(-90deg) }
  .carousel-button:focus-visible, .trial-button:focus-visible, .navigation a:focus-visible {
      outline: 3px solid #fff; outline-offset: 4px }


=============================================================
6. SECTION 2 — PROBLEM (desktop artboard geometry)  [FROZEN ENTIRELY]
=============================================================
MARKUP inside <section class="problem" id="problem" aria-labelledby="problem-heading">:
  div.section-kicker  > <i aria-hidden></i><span>The Problem</span><i aria-hidden></i>
  div.section-divider aria-hidden
  h2#problem-heading  "The cloud is growing faster<br />than your ability to monitor it"
  article.problem-card.card-one   > img.card-grid card-grid.svg alt=""
                                  + img.card-illustration.illustration-one   src=<CloudFront A>
                                    alt="Distributed nodes converging into a server"
  article.problem-card.card-two   > img.card-grid + img.card-illustration.illustration-two
                                    src=<CloudFront B> alt="Infrastructure and server visibility diagram"
  article.problem-card.card-three > img.card-grid + img.card-illustration.illustration-three
                                    src=<CloudFront C> alt="Network connections breaking at a bottleneck"
  article.problem-card.card-four  > img.card-grid + img.card-illustration.illustration-four
                                    src=<CloudFront A>   (same asset as card-one, reused)
                                    alt="Distributed nodes converging into a server"
  div.card-caption.caption-one > h3 "Data Bottlenecks"
        + p "Microservices and distributed nodes make traditional tracking obsolete"
  div.card-caption.caption-two > h3 "Infrastructure Blind Spots"
        + p "Fragmented metrics and outdated logs obscure the real picture of your server health"

CSS (desktop):
  /* [100VH] Rides the hero's now-signed offset, so it always begins exactly at the hero's
     lower edge — one viewport down, whatever the viewport is. Rule unchanged; only the value
     flowing through it is new. */
  .problem { z-index:6; pointer-events:none; transform: translateY(var(--hero-offset)) }
  .section-kicker, .problem > h2, .card-caption, .card-caption * {
      pointer-events:auto; user-select:text }
  .section-kicker { position:absolute; left:80px; top:1233px; width:1645.252px; height:14px;
      display:flex; align-items:center; justify-content:space-between; color:#4a4a4a;
      font-family:"Geist Mono",monospace; font-size:14.145px; font-weight:500; line-height:1;
      letter-spacing:-0.283px; text-transform:uppercase }
  .section-kicker i { width:10.103px; height:10.103px; display:block; opacity:.3;
                      background:var(--ink) }
  .section-divider { position:absolute; left:47px; top:1280.555px; width:1706px; height:1.01px;
                     background: rgba(0,14,35,.13) }
  .problem > h2 { position:absolute; left:522px; top:1391px; width:756.979px; margin:0;
      color:#000; font-family:"Geist Mono",monospace; font-size:44.748px; font-weight:400;
      line-height:1.1; letter-spacing:-2.685px; text-align:center; text-transform:uppercase;
      transform: translateY(var(--problem-heading-shift)) }
  .problem-card { position:absolute; width:821px; height:538px; overflow:hidden;
                  border-radius:4.041px; background: rgba(224,224,224,.3) }
  .card-one   left:71px;  top:1535px; transform: translateY(var(--problem-card-shift))
                                       scale(var(--problem-card-scale)); transform-origin: top center
  .card-two   left:904px; top:1535px; (same transform)
  .card-three left:71px;  top:2225px; transform: translateY(var(--problem-caption-shift))
  .card-four  left:904px; top:2225px; transform: translateY(var(--problem-caption-shift))
  .card-grid { position:absolute; z-index:1; left:7px; top:7px; width:809.466px;
               height:525.446px; transform: rotate(180deg); opacity:.34 }
  .card-illustration { position:absolute; z-index:2; display:block; object-fit:cover;
                       border-radius:4.041px }
  .illustration-one   { left:97px;  top:103px; width:592px; height:388px }
  .illustration-two   { left:119px; top:88px;  width:574px; height:403px }
  .illustration-three { left:87px;  top:72px;  width:602px; height:394px }
  .illustration-four  { left:109px; top:60px;  width:595px; height:418px }
  .card-caption { position:absolute; color:#000 }
  .caption-one { left:90px;  top:2102px; transform: translateY(var(--problem-caption-shift)) }
  .caption-two { left:941px; top:2105px; transform: translateY(var(--problem-caption-shift)) }
  .card-caption h3 { width:522px; margin:0; font-family:"Geist Mono",monospace; font-size:24px;
                     font-weight:400; line-height:1.1; letter-spacing:-1.44px;
                     text-transform:uppercase }
  .card-caption p  { margin:7px 0 0; font-family:var(--aeonik); font-size:16.165px;
                     line-height:1.2; letter-spacing:-0.323px; opacity:.6; white-space:nowrap }
  @media (prefers-reduced-motion: reduce) {
      html { scroll-behavior:auto } .partner-strip img { animation:none } }


=============================================================
7. RESPONSIVE — TABLET (max-width: 1279px)   *** THIS IS FAILURE 1. DO NOT SKIP IT. ***
=============================================================
Boundary is 1279/1280 on purpose: an earlier 1368px boundary sat on top of the common 1366px
desktop width, so browser zoom flipped the site between two unrelated architectures.

Outside any media query:  [FROZEN]
  .nav-toggle { display:none }
  .nav-drawer { display: contents }   /* wide screens: wrapper is not in the box tree at all */

@media (max-width: 1279px) — HERO:
  .hero { inset:auto; left:52.592px; top:50px; width: var(--viewport-width, 100vw);
          /* [100VH] Below the boundary the hero cancels the artboard scale, so its layout
             pixels ARE device pixels and viewport units mean what they say. A bare
             height:auto is what made it content-sized and short — add a 100vh floor. dvh
             second so mobile browsers exclude the collapsing URL bar; vh is the fallback. */
          height: auto;
          min-height: 100vh;
          min-height: 100dvh;
          /* THIS is the line that restores readable type. Without it the desktop downscale
             persists and every glyph, the navbar most visibly, shrinks with the window. */
          transform: scale(calc(1 / var(--artboard-scale, 1))); transform-origin: 0 0;
          /* [FROZEN] every token below */
          --v: 1vw; --v: min(1vw, 1.5svh);       /* declared twice for no-svh browsers */
          --pad: clamp(14px, 2.1vw, 26px);
          --card-pad: clamp(24px, 3.4vw, 46px);
          --sky-pad: clamp(16px, 2.2vw, 28px);
          --nav-h: clamp(54px, 5.4vw, 72.745px);
          --nav-gap: 9.094px;
          --radius: 4.041px;
          --hero-title-size: clamp(40px, calc(var(--v) * 4.6), 65.512px);
          --hero-title-line: calc(var(--hero-title-size) * var(--hero-title-leading));
             /* re-declared here on purpose: the :root copy is frozen at desktop size */
          --body-size: clamp(15px, 1.25vw, 16.165px);
          display:grid;
          grid-template-columns: minmax(0,1fr) minmax(288px,0.8fr);
          /* [100VH] Final row is flexible, so height beyond what the content needs is
             absorbed there instead of leaving the hero short. Rows 1-5 unchanged. */
          grid-template-rows: var(--nav-h) var(--nav-gap) auto auto auto minmax(auto, 1fr);
          column-gap: var(--pad); padding: var(--pad) }

  /* [FROZEN] The desktop squeeze and the guard scale have nothing to do here — the grid sizes
     itself. This existing reset already neutralises --hero-content-scale; keep it. */
  .hero-copy,.hero-dots,.insight-card,.trusted-block,.hero-divider,.carousel-controls
      { transform:none }

  [FROZEN] from here to the end of the block:
  .nav-left,.nav-right { position:relative; inset:auto; width:auto; height:auto; grid-row:1 }
  .nav-left{grid-column:1} .nav-right{grid-column:2}
  .hero-panel { position:relative; inset:auto; width:auto; height:auto; grid-row:3/-1; grid-column:1 }
  .hero-sky   { position:relative; inset:auto; width:auto; height:auto; grid-row:1/-1;
                grid-column:1/-1; margin: calc(var(--pad) * -1) }   /* bleeds to viewport edges */
  .cloud-layer--wide,.cloud-layer--shift { inset:0; width:100%; height:100%; object-fit:cover;
                                           object-position:50% 20% }
  .cloud-layer--computer { left:38.8%; top:-0.79%; width:73%; height:auto }
  .navigation { display:contents }
  /* ---- [FROZEN] NAVBAR LOGO, tablet placement. Do not touch. ---- */
  .brand-mark,.wordmark { position:relative; inset:auto; z-index:11; grid-row:1; grid-column:1;
                          align-self:center }
  .brand-mark { justify-self:start; margin-inline-start: clamp(12px,1.4vw,18.187px);
                width: clamp(30px,3.1vw,42.434px); height: clamp(30px,3.1vw,42.434px) }
  .wordmark   { justify-self:end; margin-inline-end: clamp(14px,1.6vw,24.153px);
                width: clamp(58px,5.8vw,79.152px); height:auto; aspect-ratio: 79.152/19.706 }
  /* --------------------------------------------------------------- */
  .nav-toggle { display:grid; place-items:center; position:relative; z-index:11; grid-row:1;
                grid-column:2; justify-self:end; align-self:center;
                margin-inline-end: clamp(10px,1.2vw,16px); width:46px; height:46px; padding:0;
                border:0; pointer-events:auto; border-radius:2.378px; background:transparent;
                cursor:pointer; -webkit-tap-highlight-color:transparent }
      /* .navigation switches pointer events off and the existing exception only names links,
         so the button has to opt back in. */
  .nav-toggle-bars { display:grid; gap:4.5px; width:20px }
  .nav-toggle-bars i { display:block; height:2px; background: rgba(255,255,255,.84);
                       transition: transform 180ms ease, opacity 120ms ease }
  [data-nav-open="true"] .nav-toggle-bars i:nth-child(1){ transform: translateY(6.5px) rotate(45deg) }
  [data-nav-open="true"] .nav-toggle-bars i:nth-child(2){ opacity:0 }
  [data-nav-open="true"] .nav-toggle-bars i:nth-child(3){ transform: translateY(-6.5px) rotate(-45deg) }
  .nav-drawer { display:grid; position:absolute; z-index:12;
                top: calc(var(--pad) + var(--nav-h) + 8px); right: var(--pad);
                width: min(300px, calc(100% - var(--pad)*2)); gap:14px; padding:14px;
                border:1px solid rgba(255,255,255,.14); border-radius: var(--radius);
                background: rgba(0,14,35,.9); backdrop-filter: blur(19.55px);
                pointer-events:auto; opacity:0; visibility:hidden; transform: translateY(-6px);
                transition: opacity 180ms ease, transform 180ms ease, visibility 0s linear 180ms }
  [data-nav-open="true"] .nav-drawer { opacity:1; visibility:visible; transform:none;
                                       transition-delay:0s }
  .nav-links { position:relative; inset:auto; display:grid; gap:2px }
  .nav-links a { display:flex; align-items:center; min-height:44px; padding-inline:10px;
                 border-radius:2.378px }
  .nav-links a:hover { background: rgba(255,255,255,.08) }
  .login-button { position:relative; inset:auto; width:100%; height:48px }
  .hero-dots { position:relative; inset:auto; grid-row:3; grid-column:1; justify-self:center;
               margin-top: var(--card-pad) }
  .hero-copy { position:relative; inset:auto; grid-row:4; grid-column:1; display:flex;
               flex-direction:column; align-items:center; padding-inline: var(--card-pad);
               margin-top: clamp(22px, calc(var(--v)*3.4), 52px) }
  .hero-copy h1 { position:relative; left:auto; top:auto; width:100%; text-wrap:balance }
  .hero-copy h1 span { letter-spacing: -0.06em }
  .hero-copy h1 em { top: var(--hero-title-size);
                     font-size: calc(var(--hero-title-size) * 1.1228);
                     letter-spacing: -0.06em }
  .hero-copy > p { position:relative; left:auto; top:auto; width: min(100%, 44ch);
                   margin-top: clamp(16px, calc(var(--v)*2.2), 34px);
                   font-size: var(--body-size); letter-spacing:-0.02em }
  .trial-button { position:relative; left:auto; top:auto;
                  margin-top: clamp(18px, calc(var(--v)*2.6), 38px);
                  width:198.683px; min-height:52px; height:auto }
  .hero-divider { position:relative; inset:auto; grid-row:5; grid-column:1; width:auto;
                  margin: clamp(24px, calc(var(--v)*3.6), 54px) var(--card-pad) 0 }

  /* The block narrows to the rail itself here, so the fades stop being inset from the
     block's edges and glue directly to the rail's own cut edges instead — same job, new
     geometry. Reference build at 1009px paintable: block and strip both x 132 w 303.5,
     fades 44px wide at x 132 and x 391.6, all three sharing y 566.9 h 25.3. */
  .trusted-block { position:relative; inset:auto; grid-row:6; grid-column:1; display:flex;
                   flex-direction:column; align-items:center; gap: clamp(14px,1.8vw,22px);
                   width:auto; height:auto; max-width:612.268px;
                   align-self:start;   /* [100VH] keeps it under the divider at its natural
                      height rather than being stretched by the now-flexible final row */
                   margin: clamp(18px, calc(var(--v)*2.6), 34px) auto var(--card-pad) }
  .trusted-block > p { position:relative; left:auto; top:auto; text-align:center }
  .partner-strip { position:relative; left:auto; top:auto; width:100% }
  .partner-fade { top:auto; bottom:0; height:25.259px; width: clamp(44px, 11%, 113.158px) }

  .insight-card { position:relative; inset:auto; z-index:5; grid-row:3/-1; grid-column:2;
                  align-self:end; justify-self:start; display:flex; flex-direction:column;
                  width:auto; height:auto;
                  max-width: min(312.196px, calc(100% - var(--sky-pad)*2 - 104px));
                  min-height:0; margin: var(--sky-pad); padding:19.196px }
      /* the max-width leaves the carousel its corner: two 44px buttons, their gap, margins */
  .diamond-grid { position:relative; inset:auto }
  .insight-card h2 { position:relative; left:auto; top:auto; margin-top: clamp(40px,6vw,82px);
                     font-size: clamp(17px,1.6vw,20.207px); letter-spacing:-0.06em }
  .insight-card p  { position:relative; left:auto; top:auto; width:auto; margin-top:8px;
                     font-size: clamp(13.5px,1.2vw,15.155px); letter-spacing:-0.02em }
  .carousel-controls { position:relative; inset:auto; z-index:5; grid-row:3/-1; grid-column:2;
                       align-self:end; justify-self:end; margin: var(--sky-pad) }
  .carousel-button { width:44px; height:44px }

@media (max-width: 1279px) — PROBLEM:  [FROZEN]
  .problem { inset:auto; left:52.592px;
             top: calc(1197.749px + var(--hero-offset));
             width: var(--viewport-width, 100vw); height:auto;
             transform: scale(calc(1 / var(--artboard-scale, 1))); transform-origin: 0 0;
             --pad: clamp(14px, 2.1vw, 26px);
             --card-gap: clamp(8px, 0.9vw, 12px);
             --row-gap: clamp(34px, 4.1vw, 70px);
             display:grid; grid-template-columns: repeat(2, minmax(0,1fr));
             grid-template-areas: "kick kick" "rule rule" "head head" "c1 c2" "p1 p2" "c3 c4";
             column-gap: var(--card-gap);
             padding: clamp(20px,2.1vw,35.25px) var(--pad) clamp(40px,5vw,88px) }
  .problem > h2,.problem-card,.card-caption { transform:none }
  .section-kicker { position:relative; inset:auto; grid-area:kick; width:auto;
                    letter-spacing:-0.02em }
  .section-divider { position:relative; inset:auto; grid-area:rule; width:auto;
                     margin: clamp(18px,2vw,33.6px) calc(var(--pad) * -1) 0 }
  .problem > h2 { position:relative; left:auto; top:auto; grid-area:head; width:auto;
                  margin-top: clamp(48px,6.4vw,109.4px); font-size: clamp(26px,5.5vw,44.748px);
                  letter-spacing:-0.06em }
  .problem-card { position:relative; inset:auto; width:auto; height:auto;
                  aspect-ratio: 821/538 }
  .card-one{grid-area:c1} .card-two{grid-area:c2} .card-three{grid-area:c3} .card-four{grid-area:c4}
  .card-one,.card-two   { margin-top: clamp(24px,2.7vw,45.6px) }
  .card-three,.card-four{ margin-top: var(--row-gap) }
  .card-grid { left:0.8526%; top:1.3011%; width:98.5951%; height:97.6666% }
  .card-illustration { height:auto }
  .illustration-one   { left:11.8149%; top:19.1450%; width:72.1072%; height:72.1190% }
  .illustration-two   { left:14.4945%; top:16.3569%; width:69.9147%; height:74.9071% }
  .illustration-three { left:10.5968%; top:13.3829%; width:73.3252%; height:73.2342% }
  .illustration-four  { left:13.2765%; top:11.1524%; width:72.4726%; height:77.6952% }
     (Percentages, not insets — an <img> is a replaced element, so width:auto would keep its
      intrinsic size however the box is anchored.)
  .card-caption { position:relative; inset:auto; margin-top: clamp(16px,1.7vw,29px);
                  padding-inline-start: 2.314% }
  .caption-one{grid-area:p1} .caption-two{grid-area:p2}
  .card-caption h3 { width:auto; font-size: clamp(18px,1.9vw,24px); letter-spacing:-0.06em }
  .card-caption p  { white-space:normal; font-size: clamp(14.5px,1.25vw,16.165px);
                     letter-spacing:-0.02em }

@media (prefers-reduced-motion: reduce) { .nav-drawer, .nav-toggle-bars i { transition:none } }


=============================================================
8. RESPONSIVE — STACKED (max-width: 895px)
=============================================================
HERO:
  .hero { --sky-band: clamp(240px, min(41vw, 30svh), 420px);           /* [FROZEN] */
          --hero-title-size: clamp(36px, min(7vw, 9svh), 65.512px);    /* [FROZEN] */
          grid-template-columns: minmax(0,1fr) var(--nav-h);           /* [FROZEN] */
             /* a square the height of the nav row, not an auto box sized to the button plus
                its trailing margin — which is what left the bars off-centre inside it */
          /* [100VH] The sky band is the flexible row here: slack goes into the open sky where
             the machine sits, which is where it belongs. Rows 1-7 unchanged. */
          grid-template-rows: var(--nav-h) var(--nav-gap) auto auto auto auto var(--pad)
                              minmax(var(--sky-band), 1fr) }
  [FROZEN] the rest:
  .hero-panel { grid-row:3/7; grid-column:1/-1 }
  .hero-dots,.hero-copy,.hero-divider,.trusted-block { grid-column:1/-1 }
  .nav-toggle { justify-self:center; margin-inline:0 }
  .cloud-layer--wide,.cloud-layer--shift { object-position: 50% 50% }
  .cloud-layer--shift { top:14% }
  .cloud-layer--computer { left:50%; top:auto;
      bottom: calc(var(--sky-band)/2 - var(--viewport-width,100vw) * 0.627);
      width: calc(var(--viewport-width,100vw) * 1.1); height:auto; translate:-50% 0 }
  .cloud-layer--live { mask-image: linear-gradient(to bottom, transparent 0, #000 8%) }
      (Stacked, the plate overruns both sides and it is the TOP edge that shows — so the
       feather turns downward.)
  .insight-card,.carousel-controls { grid-row:8; grid-column:1/-1 }
PROBLEM:  [FROZEN]
  .problem { grid-template-columns: minmax(0,1fr);
             grid-template-areas: "kick" "rule" "head" "c1" "p1" "c2" "p2" "c3" "c4" }
  .card-two  { margin-top: var(--row-gap) }
  .card-four { margin-top: var(--card-gap) }


=============================================================
9. RESPONSIVE — MOBILE (max-width: 475px)  [FROZEN ENTIRELY]
=============================================================
  :root { --entrance-shift: 14px }
  .hero { --sky-band: clamp(390px, 92vw, 450px);
          --hero-title-size: clamp(29px, min(7vw, 9svh), 65.512px);
          --card-pad: clamp(20px, 5vw, 46px) }
  .insight-card { justify-self:stretch; max-width:none;
                  margin-bottom: calc(var(--sky-pad) + 54px) }
      (Lifted by the CAROUSEL's fixed height, not its own variable height, so the gap holds
       at 10px from 475 down to 320 and through the longer second slide.)
  .insight-card h2 { margin-top: clamp(24px, 7vw, 82px) }
  .cloud-layer--computer { bottom: calc(var(--sky-band) * 0.81
                                        - var(--viewport-width,100vw) * 0.79315);
                           width: calc(var(--viewport-width,100vw) * 1.45) }
  .trusted-block > p { white-space:normal; text-wrap:balance }
  .problem { padding-bottom: clamp(32px, 9vw, 88px) }
  .problem > h2 { margin-top: clamp(36px, 11vw, 109.4px); text-wrap: pretty }
      (`pretty`, not `balance` — it is what pulls a stranded one-word last line back up here.)
  .card-one,.card-two { margin-top: clamp(20px, 6vw, 45.6px) }
  .card-caption { margin-top: clamp(13px, 4vw, 29px) }
  .motion-ready .hero-copy h1 span:first-child,
  .motion-ready .hero-copy h1 em,
  .motion-ready .hero-copy h1 span:last-child { animation-duration: 680ms }


=============================================================
10. ENTRANCE MOTION  [FROZEN ENTIRELY]
=============================================================
Scoped under .motion-ready (set by the inline head script, removed by a 5000ms fallback timer
that script.js clears). The CLOUD ARTWORK IS DELIBERATELY EXCLUDED — it is the static stage.
Use `translate`/`scale`/`clip-path` individual properties, never `transform`, so the layout's
own transform calculations — including the [100VH] ones — stay untouched.

Keyframes:
  entrance-wipe-right : clip-path inset(0 100% 0 0) -> inset(0)
  entrance-wipe-down  : clip-path inset(0 0 100% 0) -> inset(0)
  entrance-nav-content: opacity 0 / translate 0 -12px -> opacity 1 / translate none
  entrance-type       : opacity 0 / clip-path inset(100% 0 0 0) / translate 0 62%
                        -> opacity 1 / inset(0) / none
  entrance-support    : opacity 0 / translate 0 var(--entrance-shift) -> 1 / none
  entrance-action     : opacity 0 / translate 0 16px / scale .975 -> 1 / none / none
  entrance-visual     : opacity 0 / translate 0 var(--entrance-shift) / scale .985 -> 1/none/none
  entrance-rule       : opacity 0 / scale 0 1 -> opacity 1 / scale none

Hero timeline (all `both`):
  .nav-left      entrance-wipe-right   680ms  ease        40ms
  .nav-right     entrance-wipe-right   760ms  ease       110ms
  .hero-panel    entrance-wipe-down    920ms  ease-soft  230ms
  .brand-mark    entrance-nav-content  560ms  ease       150ms      <- logo, timing unchanged
  .wordmark      entrance-nav-content  560ms  ease       220ms      <- logo, timing unchanged
  .nav-links     entrance-nav-content  560ms  ease       240ms
  .login-button  entrance-nav-content  560ms  ease       310ms
  .nav-toggle    entrance-nav-content  560ms  ease       310ms
  .hero-dots     entrance-support      460ms  ease       430ms
  h1 span:first  entrance-type         780ms  ease-soft  500ms
  h1 em          entrance-type         780ms  ease-soft  610ms
  h1 span:last   entrance-type         780ms  ease-soft  720ms
  .hero-copy > p entrance-support      620ms  ease       940ms
  .trial-button  entrance-action       600ms  ease      1080ms
  .hero-divider  entrance-rule         760ms  ease      1100ms  transform-origin: left center
  .insight-card  entrance-visual       820ms  ease-soft 1160ms  transform-origin: center bottom
  .trusted-block entrance-support      620ms  ease      1200ms
  .carousel-controls entrance-support   560ms  ease      1380ms

Problem section HELD until JS adds .problem-is-visible:
  .motion-ready .problem:not(.problem-is-visible) :is(.section-kicker, >h2, .problem-card,
      .card-caption) { opacity:0; translate: 0 var(--entrance-shift) }
  ...:not(.problem-is-visible) > h2 { clip-path: inset(100% 0 0 0) }
  ...:not(.problem-is-visible) .problem-card { scale: .985 }
  ...:not(.problem-is-visible) .section-divider { opacity:0; scale:0 1;
                                                  transform-origin: left center }
Then under .problem.problem-is-visible (all `both`):
  .section-kicker   entrance-support  520ms ease         0ms
  .section-divider  entrance-rule     760ms ease        70ms  transform-origin: left center
  > h2              entrance-type     820ms ease-soft  160ms
  .card-one         entrance-visual   800ms ease-soft  390ms
  .card-two         entrance-visual   800ms ease-soft  480ms
  .caption-one      entrance-support  580ms ease       650ms
  .caption-two      entrance-support  580ms ease       730ms
  .card-three       entrance-visual   760ms ease-soft  760ms
  .card-four        entrance-visual   760ms ease-soft  840ms

@media (prefers-reduced-motion: reduce): for
  .motion-ready .hero :is(.nav-left,.nav-right,.hero-panel,.brand-mark,.wordmark,.nav-links,
      .login-button,.nav-toggle,.hero-dots,.hero-copy h1 span,.hero-copy h1 em,.hero-copy > p,
      .trial-button,.hero-divider,.trusted-block,.insight-card,.carousel-controls),
  .motion-ready .problem :is(.section-kicker,.section-divider,h2,.problem-card,.card-caption)
  set: animation:none !important; clip-path:none !important; opacity:1 !important;
       scale:none !important; translate:none !important;


=============================================================
11. js/script.js — IIFE, "use strict"
=============================================================
Constants (exact):
  CONTENT_LEFT 52.592, CONTENT_TOP 50, CONTENT_WIDTH 1700.406,
  CONTENT_HEIGHT = 2612 - CONTENT_TOP, HERO_HEIGHT 1147.749,
  HERO_COMPOSITION_MIN 900,      // [100VH] guard threshold — NOT a height floor
  DESKTOP_MIN_WIDTH 1024, PROBLEM_SECTION_HEIGHT 970,
  MAX_PROBLEM_GAP_REDUCTION 40, PROBLEM_CARD_HEIGHT 538, MIN_PROBLEM_CARD_SCALE 0.88,
  HERO_TOP_GAP 193.987, HERO_ACTION_GAP 203.081,
  HERO_TOP_GAP_SHARE = HERO_TOP_GAP / (HERO_TOP_GAP + HERO_ACTION_GAP),
  TABLET_BREAKPOINT 1279
  (There is deliberately NO MIN_DESKTOP_HERO_HEIGHT.)
On start: clear window.__motionFallback and null it, so .motion-ready survives.
tabletLayout = matchMedia("(max-width: 1279px)")   // must MIRROR the stylesheet's boundary

fitArtboard(force):
  viewportWidth  = document.documentElement.clientWidth    // NOT innerWidth — excludes scrollbar
  viewportHeight = document.documentElement.clientHeight
  if (!force && width & height unchanged since last fit) return;   // prevents observer feedback
  scale = viewportWidth / CONTENT_WIDTH
  shouldFitDesktopHeight = viewportWidth >= DESKTOP_MIN_WIDTH
  viewportHeroHeight = viewportHeight / scale        // the viewport, in artboard units
  set --artboard-scale = scale, --viewport-width = viewportWidth + "px"
      // published BEFORE the hero is measured: the tablet architecture reads these to cancel
      // this scale and lay itself out in real pixels. Failure 1 is what happens when the
      // stylesheet never consumes them.

  // ***** [100VH] THE FIX — do not reintroduce either clamp *****
  // A naive build reads
  //     Math.min(HERO_HEIGHT, Math.max(MIN_DESKTOP_HERO_HEIGHT, viewportHeroHeight))
  // whose Math.min caps the hero at its 1147.749 design height — measured at 66.8% of a
  // 1440px-tall viewport, 478px short — and whose Math.max floors it at 900 units, measured
  // at 107.7% of a 700px-tall one. Both clamps must be gone.
  fittedHeroHeight = shouldFitDesktopHeight ? viewportHeroHeight : HERO_HEIGHT
  heroOffset   = fittedHeroHeight - HERO_HEIGHT          // signed in BOTH directions
  heroTopShift = heroOffset * HERO_TOP_GAP_SHARE         // same formula, works both ways

  // The composition, not the band, is what cannot survive an arbitrarily short viewport:
  // under ~900 units the CTA would reach the divider (at 1440x600 the button's bottom lands
  // near 605 units against a divider at ~587). Shrink the top copy group instead of
  // shortening the hero. Nothing is restyled — only scaled.
  heroContentScale = shouldFitDesktopHeight
      ? Math.min(1, viewportHeroHeight / HERO_COMPOSITION_MIN) : 1

  if (tabletLayout.matches) {
      // The grid sizes itself against min-height:100dvh there, so measure what it became.
      heroOffset = hero.getBoundingClientRect().height / scale - HERO_HEIGHT
      heroTopShift = 0
      heroContentScale = 1 }

  [FROZEN] problem-section maths, unchanged:
  problemOverflow = shouldFitDesktopHeight
      ? Math.max(0, PROBLEM_SECTION_HEIGHT - viewportHeroHeight) : 0
  problemGapReduction = Math.min(problemOverflow, MAX_PROBLEM_GAP_REDUCTION)
  remainingProblemOverflow = Math.max(0, problemOverflow - problemGapReduction)
  problemCardScale = Math.max(MIN_PROBLEM_CARD_SCALE,
                              1 - remainingProblemOverflow / PROBLEM_CARD_HEIGHT)
  problemCardShift = -problemGapReduction
  problemCaptionShift = problemCardShift + PROBLEM_CARD_HEIGHT * (problemCardScale - 1)

  write --hero-offset, --hero-top-shift, --hero-content-scale,
        --problem-heading-shift (= problemCardShift * 0.5),
        --problem-card-shift, --problem-card-scale, --problem-caption-shift
  shell.style.width = viewportWidth + "px"
  artboard.style.transform =
      `scale(${scale}) translate(${-CONTENT_LEFT}px, ${-CONTENT_TOP}px)`
  if (tabletLayout.matches) {
      stackHeight = hero.rect.height + problem.rect.height
      shell.style.height = stackHeight + "px"
      artboard.style.height = (CONTENT_TOP + stackHeight / scale) + "px"
  } else {
      shell.style.height = ((CONTENT_HEIGHT + heroOffset) * scale) + "px"
      artboard.style.height = "" }

Call fitArtboard() immediately; addEventListener("resize", fitArtboard, {passive:true}).
If ResizeObserver exists: observe document.documentElement -> fitArtboard() (catches the
scrollbar-gutter settling after the first parse-time fit, plus zoom and scrollbar changes that
fire no resize event); and a second observer on hero and problem calling fitArtboard(true)
only when tabletLayout.matches (the hero's height is content-driven there, so the next
section's offset must follow it through webfont swap, drawer toggles and reflow).

[FROZEN] Nav drawer:
  setNavOpen(open): root.dataset.navOpen = "true"/"false"; navToggle aria-expanded =
    String(open); aria-label = open ? "Close menu" : "Open menu". Call setNavOpen(false) at init.
  toggle click -> flip; when opening, focus the drawer's first <a>.
  click inside drawer on an <a> (closest) -> close.
  keydown Escape while open -> close + return focus to toggle.
  document click outside toggle and drawer while open -> close.
  tabletLayout "change" -> setNavOpen(false) + fitArtboard(true).

[FROZEN] Hero video:
  heroVideo = .cloud-layer--live ; calmMotion = matchMedia("(prefers-reduced-motion: reduce)")
  syncHeroVideo(): if calmMotion.matches -> pause() + removeAttribute("data-playing"), return.
    else const started = heroVideo.play();
    if (started) started.then(-> setAttribute("data-playing","")).catch(-> remove it).
  Call once; re-run on calmMotion "change".
  This is the whole reveal mechanism — never set data-playing optimistically.

[FROZEN] Insight carousel:
  insights = [
    { title: "Data Bottlenecks",
      copy: "Microservices and distributed nodes make traditional tracking" },
    { title: "Infrastructure Blind Spots",
      copy: "Fragmented metrics and outdated logs obscure the real picture of your server health" }
  ]
  insightIndex = 0; delegate a document click on .carousel-button (closest);
  showInsight(dir): index = (index + dir + len) % len; write textContent into
  #insight-title and #insight-copy. #next-slide -> +1, otherwise -1.
  Then document.documentElement.dataset.carouselReady = "true".

[FROZEN] Problem-section one-shot entrance:
  target = problem.querySelector(".section-kicker"); lastScrollPosition = window.scrollY.
  revealProblemSection(): if already .problem-is-visible return; add the class; remove the
    scroll listener; disconnect + null the observer.
  armProblemEntry(): movingDown = scrollY > lastScrollPosition + 1; update lastScrollPosition;
    return unless movingDown and not already armed; set armed = true; remove scroll listener;
    if (target.getBoundingClientRect().top <= innerHeight * 0.88) revealProblemSection() and
    return (covers fast scrolls / anchor jumps that outrun the first intersection record);
    else create IntersectionObserver({rootMargin:"0px 0px -12% 0px", threshold:0.01}) that
    reveals on any isIntersecting entry, and observe(target).
  syncProblemEntryMotion(): if calmMotion.matches -> revealProblemSection() and return;
    else if not visible and not armed -> lastScrollPosition = scrollY and
    addEventListener("scroll", armProblemEntry, {passive:true}).
  Call once; re-run on calmMotion "change".
  Intentionally NOT observed until the first DOWNWARD scroll, so a sliver of the section in a
  tall opening viewport cannot fire during the hero entrance.


=============================================================
12. ACCEPTANCE CHECKS — run all three groups
=============================================================
GROUP A — FAILURE 1, the architecture switch. At 1024x768:
    matchMedia('(max-width: 1279px)').matches                       === true
    getComputedStyle(document.querySelector('.nav-toggle')).display !== 'none'
    getComputedStyle(document.querySelector('.nav-drawer')).visibility === 'hidden'
    getComputedStyle(document.querySelector('.nav-links a')).fontSize === '14.268px'
    and that 14.268px must MEASURE 14.268px on screen — the artboard scale and its inverse
    cancel. If the rendered cap-height looks like ~8px, the inverse scale is missing.
  At 1440x900 and 1920x1080 the nav must be five inline links plus the Login button with NO
  hamburger, and .nav-links a must render at 11.96px and 15.98px respectively — matching the
  type-scale table in section 3. A navbar whose size does not move between those two widths
  means the artboard transform is not being applied.

GROUP B — FAILURE 2, the rail and its fades. At 1440x900 and 1920x1080:
    const s  = document.querySelector('.partner-strip').getBoundingClientRect();
    const fl = document.querySelector('.partner-fade--left').getBoundingClientRect();
    const fr = document.querySelector('.partner-fade--right').getBoundingClientRect();
    fl.left <= s.left && fl.right  > s.left      // left cut edge covered
    fr.right >= s.right && fr.left < s.right     // right cut edge covered  <- the failing one
    fl.top  <= s.top  && fl.bottom >= s.bottom   // covered vertically too
  Reference build: leftOverlap 79.2px / rightOverlap 120px at 1920; both edges covered at 1440
  and at 1009 in tablet form. Then WATCH one full 18s cycle at 1440: no logo may ever appear
  hard-cut at either end, and the restart must not be perceptible. Confirm too that
  getComputedStyle('.partner-strip').overflow === 'hidden' and that both fades compute a
  higher stacking position than the rail image.

GROUP C — FAILURE 3, the hero band. At 1440x700, 1440x900, 1440x1440, 1280x1024, 1024x1366,
768x1024, 414x896 and 320x568:
    document.querySelector('.hero-sky').getBoundingClientRect().height
      === document.documentElement.clientHeight          (within 1px)
    document.querySelector('.section-kicker').getBoundingClientRect().top >= 0
  Reference failures that must now read 100%: 1425x1440 -> 961.8px (66.8%);
  1425x700 -> 754.2px (107.7%); 1009x768 -> 647.6px (84.3%).

REGRESSION — diff against a build without these fixes at 1440x900, where the hero already
happened to equal 100vh. At that size the two must be VISUALLY IDENTICAL. Confirm:
  - .brand-mark 42.434x42.434 at (84.924, 85.359) desktop, rendering assets/brand-mark.svg
  - .wordmark 79.152x19.706 at (788.123, 93.441) desktop, rendering assets/axiom-wordmark.svg
  - neither logo recoloured, refiltered, re-cropped, replaced by text, or moved
  - --hero-content-scale computes to exactly 1 at every viewport >= 900 artboard units tall
  - every other section's geometry byte-identical

ALSO:
- On a short desktop viewport (<=780px tall) the CTA never touches .hero-divider, and
  --hero-content-scale is below 1 there.
- The hero video fades in over the identical still only after play() resolves; killing the
  network leaves the still with no visible gap or flash.
- The headline keeps a full empty line box between "The evolution" and "observability", with
  the Playfair italic "of cloud" centred in it, at every width.
- Section two animates in exactly once, only after the user's first downward scroll.
- prefers-reduced-motion: reduce -> no entrance animations, no rail scroll, no video, section
  two visible immediately, scroll-behavior auto.
- No horizontal overflow from 320px up. (body.scrollWidth exceeding
  documentElement.clientWidth by ~15px is the reserved scrollbar gutter, not overflow.)
[/hidecontent]
← 返回首页