类型:Site模板 · 分类:未分类 · 收录:2026-09-19 14:02:02 · 源码:会员可见
Build a single self-contained file named index.html — one full-viewport hero
section, no scrolling, no build step, no frameworks, no external CSS or JS.
All CSS in one <style> in <head>; all JS in inline <script> tags. Plain HTML,
CSS and vanilla JS only. Reproduce every numeric value below exactly as written.
════════════════════════════════════════════════════════════════════════
1. DOCUMENT HEAD
════════════════════════════════════════════════════════════════════════
<!DOCTYPE html>, <html lang="en">.
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>REACT — Think Smarter. React Faster. Evolve Daily.</title>
<meta name="description" content="Next-gen cognitive smart glasses designed to sharpen your focus, enhance memory, and accelerate your decision-making in real time.">
════════════════════════════════════════════════════════════════════════
2. ASSETS
════════════════════════════════════════════════════════════════════════
Background video (use this exact URL, do not download or localise it):
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260912_160926_d79a96cc-7981-42c6-9f15-1de6bb32b67b.mp4
Video poster (exact URL):
https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/162797fa-e568-4e0e-a3ea-013907f60a1d.png
The video is 1920x1080, exactly 16:9, 10.04s, silent, ~4.3MB. Its content is
a woman in profile wearing grey "REACT" smart glasses, composited on the right
side of a pale blue-grey studio gradient, bleeding slightly off the right edge.
She breathes, drifts her head slightly, blinks once; a small green element in
the lens pulses; the background light drifts. Her position is stable across the
clip. The subject is PART OF THE VIDEO — do not add a separate <img> for her.
Two small local images, referenced by relative path:
assets/avatar.webp — 256x256, circular crop of a person's face
assets/orb.webp — 256x256, glowing green abstract orb
If these files are absent, substitute a neutral 256x256 placeholder of the same
description; do NOT inline base64 and do NOT change the markup or class names.
Font: a local variable font, referenced by relative path:
@font-face{
font-family:'ReactSans';
src:url(assets/react-sans.woff2) format('woff2');
font-weight:100 900;font-style:normal;font-display:block;
}
body font stack:
'ReactSans',-apple-system,BlinkMacSystemFont,'SF Pro Display','Helvetica Neue',Arial,sans-serif
The design uses font-variation-settings with 'opsz' and 'wght' axes (values
given per component below) — keep every one of those declarations verbatim even
if the font file is missing. If assets/react-sans.woff2 is unavailable, add
Inter Variable from Google Fonts as the first fallback after 'ReactSans'
(it also carries opsz/wght); change nothing else.
════════════════════════════════════════════════════════════════════════
3. THE SCALING SYSTEM (this is the core of the layout — get it right)
════════════════════════════════════════════════════════════════════════
The whole composition is proportional to one custom property --u, and every
size, gap, radius and letter-spacing is expressed as calc(N * var(--u)).
Never use px for layout except where a px value is literally given below.
Desktop design canvas is 1506 x 1045:
:root{
--u:min(100vw / 1506, 100vh / 1045);
--padx:calc(71 * var(--u));
--accent:#8CEE52;
--muted:#3A424C;
--glass:rgba(255,255,255,0.825);
--tint:rgba(255,255,255,0.1);
--hair:rgba(0,0,0,0.94);
}
@supports (height:100dvh){:root{--u:min(100vw / 1506, 100dvh / 1045)}}
Reset:
html,body{height:100%;background:#c9d6e0;overflow:hidden;overscroll-behavior:none}
body{font-family:<stack above>;
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
text-rendering:geometricPrecision;color:#000}
════════════════════════════════════════════════════════════════════════
4. LAYER STACK
════════════════════════════════════════════════════════════════════════
.shell{position:fixed;inset:0;overflow:hidden;isolation:isolate}
.bg (z-index:0) — a static CSS gradient that paints instantly underneath the
video, and remains the fallback if the video never loads:
.bg{position:absolute;inset:0;z-index:0;
background-image:radial-gradient(73.04% 137.80% at 69.06% 49.76%,rgba(240,248,255,0.510) 0%,rgba(240,248,255,0) 100%),linear-gradient(180deg,rgb(144,159,174) 0%,rgb(144,159,174) 5%,rgb(147,162,178) 10%,rgb(154,169,184) 15%,rgb(160,175,190) 20%,rgb(166,181,196) 25%,rgb(172,187,202) 30%,rgb(175,190,205) 35%,rgb(179,194,208) 40%,rgb(182,197,211) 45%,rgb(185,200,214) 50%,rgb(190,204,218) 55%,rgb(195,208,222) 60%,rgb(203,216,229) 65%,rgb(214,226,239) 70%,rgb(224,235,247) 75%,rgb(230,241,252) 80%,rgb(232,243,253) 85%,rgb(233,244,253) 90%,rgb(233,243,253) 95%,rgb(232,243,253) 100%)}
.bgv (z-index:0, after .bg in DOM so it paints over it) — the video layer:
.bgv{position:absolute;inset:0;z-index:0;width:100%;height:100%;
object-fit:cover;object-position:100% 50%;
pointer-events:none;-webkit-user-drag:none;user-select:none}
object-position:100% 50% is load-bearing: it right-anchors the clip so the
1506x1045 design region maps onto the layout correctly at the design ratio.
.scrim (z-index:2) — invisible on desktop, enabled in portrait/mobile to keep
copy legible over the imagery:
.scrim{position:absolute;inset:0;z-index:2;pointer-events:none;opacity:0;
background:linear-gradient(180deg,rgba(228,236,243,0) 30%,rgba(226,235,242,.55) 58%,rgba(228,236,243,.92) 82%)}
.page{position:absolute;inset:0;z-index:3;display:flex;flex-direction:column}
════════════════════════════════════════════════════════════════════════
5. MARKUP (exact structure, classes and order)
════════════════════════════════════════════════════════════════════════
<div class="shell">
<div class="bg"></div>
<video class="bgv" autoplay muted loop playsinline preload="auto"
disablepictureinpicture role="img" aria-label="Woman wearing REACT cognitive smart glasses, seen in profile"
poster="<POSTER URL>" src="<VIDEO URL>"></video>
<div class="scrim" aria-hidden="true"></div>
<div class="page">
<header>
<nav class="navgroup" aria-label="Primary">
<a href="#top" class="mark" aria-label="REACT — home">
<svg viewBox="0 0 59 59" aria-hidden="true">
<circle cx="29.5" cy="29.5" r="29.5" fill="#000"/>
<circle class="ring" cx="29.5" cy="29.5" r="15" fill="none" stroke="var(--accent)" stroke-width="1.8"/>
<circle class="dot" cx="29.5" cy="29.5" r="2.3" fill="#fff"/>
</svg>
</a>
<div class="navmenu" id="nav-menu">
<span class="navlinks">
<a href="#how" class="pill solid p-how">How it works</a>
<a href="#solutions" class="pill glass p-sol">Solutions</a>
<a href="#cases" class="pill glass p-use">Use cases</a>
</span>
<a href="#waitlist" class="pill solid join">Join the waitlist
<svg class="arrow" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M2 8h11.4M8.9 3.4 13.5 8l-4.6 4.6" stroke="var(--accent)" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
</div>
</nav>
<button class="burger" type="button" aria-expanded="false" aria-controls="nav-menu" aria-label="Menu">
<i></i><i></i><i></i>
</button>
</header>
<div class="gap-a"></div>
<main>
<div class="tags">
<span class="tag t-vis">Visual glance</span>
<span class="tag t-hnd">Hands-free</span>
</div>
<h1><span class="rv rv1"><span class="rv-i lede">Think Smarter.</span></span><br class="mb"> <span class="rv rv2"><span class="rv-i">React Faster.</span></span><br><span class="rv rv3"><span class="rv-i l2">Evolve Daily.</span></span></h1>
<div class="cta">
<a href="#start" class="btn">Start for free
<svg class="arrow" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M2 8h11.4M8.9 3.4 13.5 8l-4.6 4.6" stroke="var(--accent)" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
<img class="chip avatar" alt="" src="assets/avatar.webp">
<img class="chip orb" alt="" src="assets/orb.webp">
</div>
</main>
<div class="gap-b"></div>
<p class="sub">Next-gen cognitive smart glasses designed to sharpen your<br class="hb">
focus, enhance memory, and accelerate your decision-making<br class="hb">
in real time.</p>
</div>
</div>
The .gap-a / .gap-b divs are flex spacers that carry the vertical rhythm —
do not replace them with margins.
════════════════════════════════════════════════════════════════════════
6. DESKTOP CSS (base tier, exact values)
════════════════════════════════════════════════════════════════════════
NAV
header{position:relative;flex:0 0 auto;height:calc(59 * var(--u));margin-top:calc(45 * var(--u));display:flex;align-items:center;justify-content:center}
.navgroup{display:flex;align-items:center;gap:calc(7.5 * var(--u));height:100%;margin-right:calc(4.5 * 2 * var(--u))}
.navmenu{display:contents}
.navlinks{display:flex;align-items:center;gap:calc(7.5 * var(--u));height:100%}
.mark{width:calc(59 * var(--u));height:calc(59 * var(--u));flex:0 0 auto;display:block;border-radius:50%}
.mark svg{display:block;width:100%;height:100%}
.pill{display:inline-flex;align-items:center;justify-content:center;height:100%;flex:0 0 auto;
border-radius:999px;text-decoration:none;white-space:nowrap;color:inherit;
font-size:calc(17.7 * var(--u));font-weight:500;
font-variation-settings:'opsz' 14,'wght' 500;
transition:transform .3s cubic-bezier(.2,.7,.3,1),background-color .3s ease}
.pill.solid{background:#000;color:#fff}
.pill.glass{background:var(--glass);color:#000;
-webkit-backdrop-filter:blur(16px) saturate(1.2);backdrop-filter:blur(16px) saturate(1.2)}
.p-how{width:calc(148 * var(--u));letter-spacing:calc(-0.93 * var(--u))}
.p-sol{width:calc(131.5 * var(--u));letter-spacing:calc(0.22 * var(--u))}
.p-use{width:calc(138 * var(--u));letter-spacing:calc(-0.02 * var(--u))}
.join{position:absolute;top:0;right:calc(45.5 * var(--u));height:100%;width:calc(218 * var(--u));
justify-content:flex-start;padding-left:calc(31 * var(--u));gap:calc(23.5 * var(--u));
letter-spacing:calc(-0.5 * var(--u))}
.arrow{width:calc(16.4 * var(--u));height:calc(16.4 * var(--u));flex:0 0 auto;display:block}
.burger{display:none;position:relative;flex:0 0 auto;border:0;padding:0;cursor:pointer;
width:calc(59 * var(--u));height:calc(59 * var(--u));border-radius:50%;background:#000;
align-items:center;justify-content:center;transition:transform .3s cubic-bezier(.2,.7,.3,1)}
.burger i{position:absolute;left:50%;width:calc(19 * var(--u));height:calc(1.9 * var(--u));
border-radius:calc(2 * var(--u));background:#fff;transform:translateX(-50%);
transition:transform .32s cubic-bezier(.2,.7,.3,1),opacity .2s linear}
.burger i:nth-child(1){transform:translate(-50%,calc(-6 * var(--u)))}
.burger i:nth-child(3){transform:translate(-50%,calc(6 * var(--u)));background:var(--accent)}
.nav-open .burger i:nth-child(1){transform:translate(-50%,0) rotate(45deg)}
.nav-open .burger i:nth-child(2){opacity:0}
.nav-open .burger i:nth-child(3){background:#fff}
.nav-open .burger i:nth-child(3){transform:translate(-50%,0) rotate(-45deg)}
HERO
.gap-a{flex:240 1 0;min-height:calc(14 * var(--u))}
.gap-b{flex:276 1 0;min-height:calc(14 * var(--u))}
main{flex:0 0 auto;margin-left:var(--padx)}
.tags{display:flex;gap:calc(5.5 * var(--u));height:calc(45 * var(--u));margin-top:calc(-1 * var(--u))}
.tag{display:inline-flex;align-items:center;justify-content:center;height:100%;flex:0 0 auto;
border-radius:999px;border:calc(1.3 * var(--u)) solid var(--hair);background:var(--tint);
-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
font-size:calc(15.9 * var(--u));font-weight:500;
font-variation-settings:'opsz' 14,'wght' 500;white-space:nowrap}
.t-vis{width:calc(135 * var(--u));letter-spacing:calc(-0.25 * var(--u))}
.t-hnd{width:calc(121 * var(--u));letter-spacing:calc(-0.22 * var(--u))}
h1{margin-top:calc(28.45 * var(--u));font-size:calc(51.2 * var(--u));line-height:calc(53.1 * var(--u));
font-weight:700;letter-spacing:calc(-0.72 * var(--u));font-variation-settings:'opsz' 32,'wght' 700}
h1 .lede{color:var(--muted);font-weight:300;letter-spacing:calc(-0.3 * var(--u));
font-variation-settings:'opsz' 32,'wght' 300}
h1 .l2{letter-spacing:calc(-0.3 * var(--u))}
.mb{display:none}
.cta{margin-top:calc(43.6 * var(--u));height:calc(62 * var(--u));display:flex;align-items:center}
.btn{display:inline-flex;align-items:center;height:100%;border-radius:999px;background:#000;color:#fff;
text-decoration:none;white-space:nowrap;width:calc(229 * var(--u));padding-left:calc(40.5 * var(--u));
padding-bottom:calc(1.8 * var(--u));gap:calc(25 * var(--u));
font-size:calc(19.7 * var(--u));font-weight:500;letter-spacing:calc(-0.12 * var(--u));
font-variation-settings:'opsz' 14,'wght' 500;transition:transform .3s cubic-bezier(.2,.7,.3,1)}
.btn .arrow{margin-bottom:calc(-1.8 * var(--u))}
.chip{width:calc(63.4 * var(--u));height:calc(63.4 * var(--u));border-radius:50%;display:block;flex:0 0 auto;object-fit:cover}
.chip.avatar{margin-left:calc(5 * var(--u))}
.chip.orb{margin-left:calc(6 * var(--u))}
.sub{flex:0 0 auto;margin-bottom:calc(63 * var(--u));margin-left:var(--padx);
font-size:calc(19.4 * var(--u));line-height:calc(25.4 * var(--u));font-weight:400;
letter-spacing:calc(-0.18 * var(--u));font-variation-settings:'opsz' 14,'wght' 400;
max-width:calc(640 * var(--u))}
STATES
@media (hover:hover){
.pill.solid:hover,.btn:hover{transform:translateY(-1px)}
.pill.glass:hover{background:rgba(255,255,255,.96)}
}
a:focus-visible,.burger:focus-visible{outline:calc(2 * var(--u)) solid var(--accent);outline-offset:calc(3 * var(--u))}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
════════════════════════════════════════════════════════════════════════
7. RESPONSIVE — six tiers, in this source order
════════════════════════════════════════════════════════════════════════
Each tier re-authors the composition on its own canvas by redefining --u.
Always pair a 100vh definition with a 100dvh @supports override.
--- 7a. VIDEO IN PORTRAIT (place immediately after .page in the CSS) ---
A full-bleed 16:9 clip in a tall viewport scales the subject up until the
headline lands across her face. In portrait she instead holds a band in the
upper field and dissolves into the .bg gradient below:
@media (max-aspect-ratio:1/1){
.bgv{bottom:auto;height:58dvh;object-position:76% 6%;
-webkit-mask-image:linear-gradient(180deg,#000 62%,rgba(0,0,0,0) 100%);
mask-image:linear-gradient(180deg,#000 62%,rgba(0,0,0,0) 100%)}
}
@media (max-width:603px){
.bgv{height:46dvh;object-position:72% 4%}
}
--- 7b. TABLET — landscape ---
@media (max-width:1099px),(max-height:762px){
:root{--u:min(100vw / 1240, 100vh / 940, 1px);--padx:calc(56 * var(--u))}
@supports (height:100dvh){:root{--u:min(100vw / 1240, 100dvh / 940, 1px)}}
header{margin-top:calc(40 * var(--u))}
.mb{display:inline}
h1{margin-top:calc(26 * var(--u));font-size:calc(50 * var(--u));line-height:calc(54 * var(--u))}
.cta{margin-top:calc(36 * var(--u))}
.sub{margin-bottom:calc(54 * var(--u));max-width:calc(600 * var(--u))}
.gap-a{flex:210 1 0}
.gap-b{flex:250 1 0}
}
--- 7c. TABLET — portrait (self-contained; a portrait viewport can exceed 1099px) ---
@media (min-width:604px) and (max-aspect-ratio:1/1){
:root{--u:min(100vw / 800, 100vh / 1240, 1.15px);--padx:calc(56 * var(--u))}
@supports (height:100dvh){:root{--u:min(100vw / 800, 100dvh / 1240, 1.15px)}}
header{margin-top:calc(40 * var(--u))}
.mb{display:inline}
h1{margin-top:calc(26 * var(--u))}
.cta{margin-top:calc(36 * var(--u))}
.scrim{opacity:1}
h1{font-size:calc(56 * var(--u));line-height:calc(60 * var(--u))}
.sub{max-width:calc(620 * var(--u));margin-bottom:calc(48 * var(--u))}
.gap-a{flex:320 1 0}
.gap-b{flex:140 1 0}
}
--- 7d. NAV → BURGER (reuses the very same pill elements, no duplicate markup) ---
@media (max-width:860px),(max-aspect-ratio:1/1) and (max-width:1099px){
header{justify-content:space-between;padding:0 var(--padx)}
.navgroup{display:contents}
.burger{display:inline-flex}
.mark{position:relative}
.mark::after,.burger::before{content:'';position:absolute;inset:calc(-7 * var(--u));border-radius:50%}
.navmenu{display:block;position:absolute;z-index:5;
top:calc(100% + 59 * 0.24 * var(--u));right:var(--padx);
width:calc(330 * var(--u));padding:calc(14 * var(--u));border-radius:calc(34 * var(--u));
background:rgba(236,242,248,.78);border:calc(1 * var(--u)) solid rgba(0,0,0,.06);
-webkit-backdrop-filter:blur(22px) saturate(1.3);backdrop-filter:blur(22px) saturate(1.3);
opacity:0;visibility:hidden;transform:translateY(calc(-10 * var(--u))) scale(.97);transform-origin:100% 0;
transition:opacity .22s ease,transform .3s cubic-bezier(.2,.7,.3,1),visibility .3s}
.nav-open .navmenu{opacity:1;visibility:visible;transform:none}
.navlinks{flex-direction:column;align-items:stretch;height:auto;gap:calc(8 * var(--u))}
.navmenu .pill{width:100%;height:calc(52 * var(--u));justify-content:flex-start;
padding:0 calc(22 * var(--u));font-size:calc(17 * var(--u));letter-spacing:calc(-0.2 * var(--u))}
.navmenu .join{position:static;margin-top:calc(8 * var(--u));justify-content:space-between;
padding-right:calc(20 * var(--u))}
.navmenu .join .arrow{width:calc(16 * var(--u));height:calc(16 * var(--u))}
}
--- 7e. MOBILE (405x700 canvas; safe-area aware) ---
@media (max-width:603px){
:root{--u:min(100vw / 405, 100vh / 700, 1px);
--padx:max(calc(30 * var(--u)),env(safe-area-inset-left),env(safe-area-inset-right))}
@supports (height:100dvh){:root{--u:min(100vw / 405, 100dvh / 700, 1px)}}
.scrim{opacity:1}
header{height:calc(46 * var(--u));margin-top:calc(calc(26 * var(--u)) + env(safe-area-inset-top))}
.mark,.burger{width:calc(46 * var(--u));height:calc(46 * var(--u))}
.navmenu{width:calc(300 * var(--u));top:calc(100% + calc(10 * var(--u)))}
.navmenu .pill{height:calc(50 * var(--u));font-size:calc(16.5 * var(--u))}
main{margin-right:var(--padx)}
.tags{height:calc(33 * var(--u));gap:calc(6 * var(--u));margin-top:0}
.tag{font-size:calc(14 * var(--u));width:auto;padding:0 calc(14 * var(--u));border-width:calc(1 * var(--u))}
h1{margin-top:calc(20 * var(--u));font-size:calc(44 * var(--u));line-height:calc(47 * var(--u))}
.cta{margin-top:calc(26 * var(--u));height:calc(54 * var(--u));flex-wrap:wrap;row-gap:calc(10 * var(--u))}
.btn{width:auto;padding:0 calc(26 * var(--u)) calc(1.5 * var(--u)) calc(26 * var(--u));
font-size:calc(16 * var(--u));gap:calc(16 * var(--u))}
.btn .arrow{width:calc(14 * var(--u));height:calc(14 * var(--u))}
.chip{width:calc(52 * var(--u));height:calc(52 * var(--u))}
.chip.avatar,.chip.orb{margin-left:calc(8 * var(--u))}
.sub{margin-bottom:calc(calc(30 * var(--u)) + env(safe-area-inset-bottom));margin-right:var(--padx);
max-width:calc(400 * var(--u));font-size:calc(16.5 * var(--u));line-height:calc(22.5 * var(--u))}
.hb{display:none}
.gap-a{flex:235 1 0}
.gap-b{flex:120 1 0}
}
--- 7f. MOBILE — landscape (height is the scarce axis; keep side-by-side) ---
@media (max-height:480px) and (max-width:950px){
:root{--u:min(100vw / 900, 100vh / 500, 1px);
--padx:max(calc(40 * var(--u)),env(safe-area-inset-left),env(safe-area-inset-right))}
@supports (height:100dvh){:root{--u:min(100vw / 900, 100dvh / 500, 1px)}}
header{height:calc(58 * var(--u));margin-top:calc(calc(20 * var(--u)) + env(safe-area-inset-top))}
.mark,.burger{width:calc(58 * var(--u));height:calc(58 * var(--u))}
.navmenu .pill{height:calc(46 * var(--u));font-size:calc(16 * var(--u))}
.tags{height:calc(40 * var(--u));margin-top:0}
h1{margin-top:calc(20 * var(--u));font-size:calc(42 * var(--u));line-height:calc(45 * var(--u))}
.cta{margin-top:calc(24 * var(--u));height:calc(60 * var(--u))}
.chip{width:calc(56 * var(--u));height:calc(56 * var(--u))}
.sub{margin-bottom:calc(calc(26 * var(--u)) + env(safe-area-inset-bottom));max-width:calc(600 * var(--u))}
.gap-a{flex:150 1 0;min-height:0}
.gap-b{flex:170 1 0;min-height:0}
}
@media (max-height:430px){
.gap-a,.gap-b{min-height:0}
}
════════════════════════════════════════════════════════════════════════
8. ENTRANCE ANIMATION (one timeline, once, on load)
════════════════════════════════════════════════════════════════════════
Every rule is scoped to html.anim, which a head script adds and a later script
removes once the last animation finishes — after that the document carries no
animation, transform or clip at all. The gradient stage never moves.
Three behaviours only: A masked lift (headline), B rise & settle (pills, tags,
button, paragraph), C aperture (circular marks).
.rv,.rv-i{display:inline-block}
:root{
--e-out:cubic-bezier(.16,1,.3,1);
--e-soft:cubic-bezier(.25,.6,.25,1);
--rise:14px;
}
/* A — masked lift */
html.anim .rv{clip-path:inset(-.34em -.09em -.16em -.09em)}
html.anim .rv-i{opacity:0;transform:translateY(112%);animation:e-lift .92s var(--e-out) var(--d) both}
html.anim .rv1 .rv-i{--d:.60s}
html.anim .rv2 .rv-i{--d:.72s}
html.anim .rv3 .rv-i{--d:.84s}
/* B — rise & settle */
html.anim .navlinks .pill,html.anim .join,html.anim .tag,html.anim .btn,html.anim .sub{
animation:e-rise .62s var(--e-out) var(--d) both}
html.anim .p-how{--d:.30s} html.anim .p-sol{--d:.36s} html.anim .p-use{--d:.42s}
html.anim .join{--d:.48s}
html.anim .tag:first-child{--d:.52s} html.anim .tag:last-child{--d:.58s}
html.anim .btn{--d:1.04s}
html.anim .sub{--d:1.20s;--rise:10px}
/* C — aperture */
html.anim .mark,html.anim .burger,html.anim .chip{animation:e-open .70s var(--e-out) var(--d) both}
html.anim .mark{--d:.18s} html.anim .burger{--d:.30s}
html.anim .chip.avatar{--d:1.12s} html.anim .chip.orb{--d:1.18s}
html.anim .mark .ring{stroke-dasharray:94.25;animation:e-draw .95s var(--e-soft) .34s both}
html.anim .mark .dot{transform-box:fill-box;transform-origin:center;animation:e-dot .5s var(--e-out) .56s both}
@keyframes e-lift{from{opacity:0;transform:translateY(112%)} 55%{opacity:1} to{opacity:1;transform:translateY(0)}}
@keyframes e-rise{from{opacity:0;transform:translateY(var(--rise))} to{opacity:1;transform:translateY(0)}}
@keyframes e-open{from{opacity:0;transform:scale(.88)} to{opacity:1;transform:scale(1)}}
@keyframes e-draw{from{stroke-dashoffset:94.25} to{stroke-dashoffset:0}}
@keyframes e-dot{from{transform:scale(0)} to{transform:scale(1)}}
/* shorter travel where the composition is tighter */
@media (max-width:603px),(max-height:480px){
:root{--rise:10px}
html.anim .sub{--rise:8px}
}
(94.25 is the ring circumference, 2*pi*15 — keep dasharray and dashoffset equal.)
════════════════════════════════════════════════════════════════════════
9. SCRIPTS (four, in this order)
════════════════════════════════════════════════════════════════════════
9a. LAST THING IN <head> — arm the timeline before first paint so the opening
state is painted first (no flash) and the page stays fully visible if JS
never runs:
(function(){var r=document.documentElement;
if(!matchMedia('(prefers-reduced-motion: reduce)').matches)r.classList.add('anim');})();
9b. After the .shell markup — reduced motion keeps the hero but not the
movement. Do NOT hide the video: the subject lives inside it, so hiding it
would erase her. Hold frame one instead, and keep holding it against any
autoplay attempt:
(function(){
if(!matchMedia('(prefers-reduced-motion: reduce)').matches)return;
var v=document.querySelector('.bgv');
if(!v)return;
v.removeAttribute('autoplay');v.removeAttribute('loop');
var hold=function(){v.pause()};
v.addEventListener('play',hold);hold();
})();
9c. Strip every animation hook once the entrance ends (2400ms fallback timer,
plus document.getAnimations() when available):
(function(){
var root=document.documentElement;
if(root.classList.contains('anim')){
var stop=function(){
if(!root.classList.contains('anim'))return;
root.classList.remove('anim');
};
var t=setTimeout(stop,2400);
if(document.getAnimations){
Promise.all(document.getAnimations().map(function(a){return a.finished;}))
.then(function(){clearTimeout(t);stop();},function(){});
}
}
})();
9d. Burger menu — toggles .nav-open on <header>; keeps aria-expanded and
aria-label in sync; moves focus to the first link on open; closes on link
click, on outside click, on Escape (returning focus to the button), and when
the breakpoint stops matching:
(function(){
var hdr=document.querySelector('header'),
btn=document.querySelector('.burger'),
menu=document.getElementById('nav-menu'),
mq=matchMedia('(max-width:860px),(max-aspect-ratio:1/1) and (max-width:1099px)');
function set(open){
hdr.classList.toggle('nav-open',open);
btn.setAttribute('aria-expanded',open?'true':'false');
btn.setAttribute('aria-label',open?'Close menu':'Menu');
if(open){var f=menu.querySelector('a');if(f)f.focus({preventScroll:true});}
}
var isOpen=function(){return hdr.classList.contains('nav-open');};
btn.addEventListener('click',function(e){e.stopPropagation();set(!isOpen());});
menu.addEventListener('click',function(e){if(e.target.closest('a'))set(false);});
document.addEventListener('click',function(e){
if(isOpen()&&!menu.contains(e.target)&&!btn.contains(e.target))set(false);
});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&isOpen()){set(false);btn.focus();}
});
(mq.addEventListener?mq.addEventListener.bind(mq,'change'):mq.addListener.bind(mq))(function(){
if(!mq.matches&&isOpen())set(false);
});
})();
════════════════════════════════════════════════════════════════════════
10. ACCEPTANCE CHECKS
════════════════════════════════════════════════════════════════════════
- At 1506x1045 and 1920x1080: pill nav centred at top, headline three lines
at lower left ("Think Smarter." in --muted weight 300, the other two black
weight 700), button + two circular chips below it, paragraph bottom left,
woman on the right from the video. Nothing overlaps her face.
- At 1920x1080 exactly, the video maps 1:1 (no crop).
- At 768x1024 and 375x812: burger replaces the pill row; she occupies a band
in the upper field that fades into the gradient; the headline sits on clean
ground and is fully legible; .scrim is active.
- At 900x420: side-by-side composition retained, copy on the left.
- Page never scrolls in any tier; no horizontal overflow.
- Video: autoplaying, muted, looping, 1920x1080, no console errors.
- With prefers-reduced-motion: the video is paused on frame one and still
visible (the woman must NOT disappear); no entrance animation runs.
- After ~2.4s the <html> element has no "anim" class.[/hidecontent]