motionsites中文站 / Roadster 3D
Roadster 3D
aadmin2026-10-26 发布未分类9 阅读

Roadster 3D

类型: · 收录:2026-10-26 23:39:21 · 源码:会员可见

付费可阅读内容 · 可复制
Build a single standalone HTML file (index.html) for a dark, cinematic electric-car landing page called "Roadster". Everything inline in one file: one <style>, one ES-module <script> for Three.js, and one small plain <script> for the nav. Do not use any local assets. Load the car and backgrounds only from the exact CloudFront URLs below. The page has exactly THREE scroll-driven sections inside one pinned 3D stage: (1) Hero, (2) Fly and swing to the side, (3) Going down / top-down. Nothing comes after the stage: no footer, no extra sections.

==================================================
ASSETS (use exactly these URLs)
==================================================
3D car model (GLB, loaded with Three.js GLTFLoader; the server sends CORS *):
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_172547_27158316-44cc-4848-a85b-5dac41faaa80.glb
Alternate model, selected only with ?model=tripo:
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_172544_35da5d62-b0db-48ff-8fd8-86c1e8012708.glb

Background 1 (studio wall + glossy black floor + soft fog on the right), also add it as <link rel="preload" as="image">:
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_172559_b0014e8c-685a-4848-b845-a91743651e7f.png
Background 2 (top-down dark floor with a soft circular silver light pool):
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_172559_28889147-787a-4567-8b27-5de3264916ab.png

Three.js via importmap (exact versions):
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
Import THREE, GLTFLoader (three/addons/loaders/GLTFLoader.js) and RoomEnvironment (three/addons/environments/RoomEnvironment.js).

Fonts (Google Fonts, with preconnect to fonts.googleapis.com and fonts.gstatic.com crossorigin):
https://fonts.googleapis.com/css2?family=Anton&family=Manrope:wght@400;500;600;700&display=swap
Display = "Anton", "Impact", sans-serif. Body = "Manrope", "Segoe UI", sans-serif.

Head: <title>Roadster — Drive the Future</title>, a meta description, <meta name="theme-color" content="#07080a">, and a viewport meta with width=device-width, initial-scale=1, viewport-fit=cover.

==================================================
DESIGN TOKENS (:root)
==================================================
--bg:#07080a; --bg-2:#0d0f12; --ink:#f2f3f5; --muted:#9aa0a8;
--line:rgba(255,255,255,.14); --line-strong:rgba(255,255,255,.28);
--chip:rgba(14,16,19,.62); --thrust:#a9cfff;
--display:"Anton","Impact",sans-serif; --body:"Manrope","Segoe UI",sans-serif;
--gutter:clamp(16px,3.4vw,44px); --ease:cubic-bezier(.2,.7,.2,1);
Reset: *{box-sizing:border-box;margin:0;padding:0}. html and body get background var(--bg), color-scheme:dark, color var(--ink), font var(--body) 16px/1.5, -webkit-font-smoothing:antialiased, overflow-x:hidden. Links inherit color with no underline. Buttons are unstyled (font:inherit; color:inherit; background:none; border:0; cursor:pointer). :focus-visible gets outline 2px solid #fff, outline-offset 3px, border-radius 6px.

==================================================
LOADER (fixed full-screen, z-index 100)
==================================================
Background var(--bg), contents centered: the lightning-bolt logo SVG (30x42, white, pulse animation: 1.6s ease-in-out infinite, opacity .35 at 50%), a 160x1px bar (background var(--line), inner fill #fff whose width transitions .3s), and a small label "LOADING 0%" (11px, letter-spacing .22em, muted, tabular numbers). The label updates from the GLTFLoader progress callback, capped at 99 until load finishes, then 100. At 100%, wait 250ms, then add .done (opacity 0 and visibility hidden over .9s var(--ease)). If loading fails, show "MODEL UNAVAILABLE" and hide the loader after 900ms.
Lightning logo SVG: viewBox="0 0 22 30", path d="M13.6 0 1.4 16.9h8.2L7.9 30 20.6 12.4h-8.3z", fill #fff.

==================================================
NAV (fixed top, z-index 50)
==================================================
3-column grid (1fr auto 1fr), align-items center, padding 20px var(--gutter). When scrollY > 40, add .scrolled, which sets background linear-gradient(to bottom, rgba(7,8,10,.85), rgba(7,8,10,0)).
- Left: links "Shop", "Customize", "Reviews", "Contact" (14px, weight 500, opacity .9 → 1 on hover, gap clamp(18px,2.6vw,40px)). All href="#top".
- Center: the lightning logo (22x30 inside a 40x40 box), linked to #top.
- Right (flex, gap 18px, justify-end):
  - Language button "ENG" with a 10px chevron (13px, weight 600). The chevron rotates 180° when open. Its dropdown: absolute, background #121418, 1px var(--line) border, radius 12px, padding 6px, min-width 110px, items "English"/"Deutsch"/"Español" mapping to ENG/DEU/ESP. Choosing one updates the label. Clicking outside closes it.
  - A 1x22px divider in var(--line-strong).
  - Instagram, YouTube and X icons (16px, fill currentColor, inside 20px boxes, gap 16px).
  - A toggle switch, 36x20, radius 20px, background #55595f, with a 16px white knob. When aria-pressed="true", the background is #8b9097 and the knob moves translateX(16px). It starts ON. It is the "Studio lights" toggle: ON sets toneMappingExposure 1.05, key light 2.2, rim light 2.6. OFF sets exposure 0.7, key 1.1, rim 3.4.
- Hamburger button (hidden on desktop): 40x40, two lines drawn with a 20x1.5px span plus box-shadow 0 6px 0 #fff.

==================================================
STAGE STRUCTURE
==================================================
<main id="top"><section id="stage"> height 640vh, containing .sticky (position sticky, top 0, height 100vh then 100svh, overflow hidden, isolation isolate). Layers inside .sticky, bottom to top:
1. .bg.bg1 and .bg.bg2: absolute inset 0, background-size cover, centered. bg2 starts at opacity 0.
2. .wash: absolute inset 0, pointer-events none, background:
   radial-gradient(120% 80% at 50% 38%, rgba(7,8,10,0) 40%, rgba(7,8,10,.75) 100%),
   linear-gradient(to bottom, rgba(7,8,10,.35), rgba(7,8,10,0) 22%, rgba(7,8,10,0) 80%, rgba(7,8,10,.9)).
3. .outline: giant outlined wordmark "ROADSTER" in a <span>. Position absolute, left 50%, top 11%, translateX(-50%), Anton, font-size clamp(84px,23.5vw,420px), line-height .86, letter-spacing .005em, nowrap, color transparent, -webkit-text-stroke 1.2px rgba(255,255,255,.5), mask-image (plus -webkit- prefix) linear-gradient(to bottom,#000 30%,rgba(0,0,0,.15) 95%), transform-origin 50% 0. The span is inline-block with transform scaleY(1.22), origin 50% 0.
4. <canvas id="gl">: absolute inset 0, 100% size, z-index 2 (the car sits in front of the wordmark).
5. Text panels (.panel: absolute, z-index 3).
6. Scroll cue: centered at bottom 22px. Text "SCROLL" (11px, letter-spacing .2em, muted) above a 1x34px line with gradient #fff → transparent, animated with keyframes cue 2.2s var(--ease) infinite: 0% scaleY(0) opacity 1, 60% scaleY(1) opacity 1, 100% scaleY(1) opacity 0, transform-origin top.

SECTION 1: HERO panel (#pHero): left var(--gutter), top 30%, max-width min(440px,90vw).
- h1 "Drive the<br>future": Anton, uppercase, clamp(46px,5.2vw,78px), line-height .98, filled with a silver gradient: background linear-gradient(180deg,#ffffff 0%,#d9dce0 45%,#8f959c 100%), background-clip text, color transparent.
- p "Electric performance, redefined.": margin-top 14px, clamp(15px,1.35vw,18px), color #dfe2e6.
- .btn "Shop now" (margin-top 24px): inline-flex, height 48px, padding 0 26px, pill radius, background #fff, color #0a0b0d, weight 700, 14px. On hover: translateY(-2px) and box-shadow 0 10px 30px -10px rgba(255,255,255,.45), transitions .3s var(--ease).

SECTION 2: SIDE panel (#pSide): full inset, starts at opacity 0, pointer-events none.
- .side-title at left var(--gutter), top 17%:
  - eyebrow "MODEL 02" (12px, letter-spacing .22em, uppercase, muted, weight 600)
  - h2 "Roadster<br>Type 01" (Anton, uppercase, clamp(44px,5.6vw,92px), line-height .92)
  - p "Hand-laid carbon body, open sky above, three motors below." (margin-top 14px, #c9cdd2, max-width 34ch)
- Five .chip elements: absolute, inline-flex, gap 10px, padding 9px 14px 9px 10px, radius 12px, background var(--chip), 1px var(--line) border, 13px weight 600, nowrap, backdrop-filter blur(6px). Each has an icon box <i> (24x24, radius 7px, background rgba(255,255,255,.08), 1px border) holding a 13px stroke icon (stroke #fff, width 1.8, round caps and joins), then <b>value</b> and <span>label</span> (muted, weight 500). Desktop positions as left%/top%:
  1. "0–100 km/h" / "1.9 s" at (22, 56), bolt icon path "M13 2 4 14h7l-1 8 9-12h-7z"
  2. "960 km" / "range" at (44, 78), battery icon: rect x2 y7 w18 h10 rx2 plus path "M22 11v2"
  3. "410 km/h" / "top speed" at (3, 66), clock icon: circle r9 at 12,12 plus path "M12 7v5l3 2"
  4. "Air thrusters" / "×10" at (20, 76), down-arrow icon "M12 3v12M8 11l4 4 4-4M5 21h14"
  5. "Carbon tub" / "1,380 kg" at (6, 86), car icon "M4 18h16M6 18l2-6h8l2 6M9 12l1-4h4l1 4"

SECTION 3: TOP-DOWN panel (#pTop): full inset, starts at opacity 0, pointer-events none.
- .top-head centered at top 10%: eyebrow "FROM ABOVE" and h2 "Built to every angle" (Anton, uppercase, clamp(28px,3vw,44px), line-height 1).
- .cards: absolute inset 0, display grid, grid-template-columns auto 1fr auto, 3 rows, align-content center, row-gap 14px, padding 9vh var(--gutter) 0. Cards 1–3 go in the left column and 4–6 in the right column (rows 1, 2, 3; the middle column stays empty so the car shows through).
- .card: width clamp(210px,19vw,290px), padding 16px 18px 18px, radius 16px, background rgba(12,14,17,.66), 1px var(--line) border, backdrop-filter blur(8px). Contents: icon box (30x30, radius 9px, margin-bottom 12px, 15px icon), h3 (15px, weight 700), p (13px, muted, line-height 1.45, margin-top 4px).
  1. "Tri-motor drive": "One motor up front, two at the rear, torque vectored per wheel in under a millisecond." (bolt icon)
  2. "200 kWh pack": "Structural cells in the floor keep the center of gravity 40 mm lower." (battery icon: rect plus "M22 11v2M6 10v4M10 10v4")
  3. "Thruster package": "Ten nozzles for launch, braking and short hover." (down-arrow icon)
  4. "Active aero": "The rear deck lifts and the floor seals at speed." (pulse icon "M3 12h4l3-8 4 16 3-8h4")
  5. "Four seats, no roof": "The glass targa stows in the trunk in eleven seconds." (seat icon "M5 12a7 7 0 0 1 14 0v6H5z" plus "M9 18v3M15 18v3")
  6. "18-minute charge": "10 to 80 percent on any 350 kW charger." (spark icon "M12 2v4M12 18v4M4.9 4.9l2.8 2.8M16.3 16.3l2.8 2.8M2 12h4M18 12h4")

==================================================
THREE.JS SCENE
==================================================
Renderer: WebGLRenderer({canvas, antialias:true, alpha:true, powerPreference:'high-performance'}), clear color transparent (0x000000, 0), outputColorSpace SRGB, ACESFilmicToneMapping, exposure 1.05. Pixel ratio = min(devicePixelRatio, width<700 ? 1.6 : 2). The renderer size equals the .sticky size.
Environment: PMREMGenerator.fromScene(new RoomEnvironment(renderer), 0.04).texture → scene.environment. No scene background, so the CSS image backgrounds show through.
Camera: PerspectiveCamera with FOV 30, near 0.1, far 200.
Lights: HemisphereLight(0xdfe6ff, 0x0a0a0a, 0.35). Key DirectionalLight(0xffffff, 2.2) at (-4,8,6). Rim DirectionalLight(0xcfd8ff, 2.6) at (3,4,-7). Top DirectionalLight(0xffffff, 0.9) at (0,10,0).

Car rig: carRoot (position + yaw via rotation.y) → carTilt (pitch = rotation.z, roll = rotation.x) → wrap → holder → gltf.scene.
On load:
- Replace every mesh material with a MeshPhysicalMaterial that copies map, normalMap, roughnessMap, metalnessMap, aoMap, emissiveMap, emissive and color, and sets roughness 0.5, metalness 0.35, clearcoat 0.55, clearcoatRoughness 0.22, envMapIntensity 1.0 (emissiveIntensity 1.5 if there is an emissiveMap). This gives a satin black clear-coat look.
- Normalize: holder.rotation.y = (size.z > size.x ? PI/2 : 0) + PI, so the nose faces screen-left (-X). Allow a ?yaw= override. Scale so the longest horizontal side is 4.6 m (CAR_LEN). Set wheels on y=0 and center X/Z. Store carDims from the final bounding box.
- Floor reflection: a group with scale.y = -1 holding a deep clone of wrap, every material cloned with transparent true, depthWrite false and opacity 0.16. Each frame, copy the car's position, yaw and tilt onto it. Its opacity = 0.16 × (1 − clamp(y/1.6)) × (1 − range(p, 0.5, 0.66)). Hide it below 0.004.
- Contact shadow: a horizontal plane at y=0.004 using a radial canvas texture (stops: 0 rgba(0,0,0,.95), .45 rgba(0,0,0,.6), 1 transparent). With sh = clamp(1 − y/2.2): scale = (carDims.x × (1.15 + (1−sh)×0.6), carDims.z × (1.5 + (1−sh)×0.8)), opacity 0.85×sh², and it rotates with the car's yaw.

AIR-BURST EFFECTS (skip all of them if prefers-reduced-motion is on):
- Nozzles in car-local coordinates, as fractions of [length, width]: [-0.36,-0.30], [-0.36,0.30], [0.30,-0.32], [0.30,0.32], [0,0].
- Particles: 1400 THREE.Points using a custom ShaderMaterial (additive blending, depthWrite false, frustumCulled false). Vertex shader: gl_PointSize = aSize × uScale / −mv.z, where uScale = (viewportHeight × dpr) / (2 × tan(FOV/2)). Fragment shader: soft round dot, a = smoothstep(.5, 0, dist), squared, times aAlpha, color #d6e6ff.
  - Emit from a random nozzle at local y 0.06 with jitter (±0.25 along length, ±0.15 across), converted to world space. Velocity: x/z random ±0.4, y = −speed × (0.75–1.25). Life 0.9–2.0 s. Base size 0.025–0.075.
  - When a particle hits y ≤ 0.03 while moving down, it sprays outward along the floor, radially away from the car center, at |vy| × (0.8–1.3) plus a little jitter, with a small upward vy (0–0.25).
  - Drag exp(−1.6·dt) on x/z. Size = base × (1 + 5t). Alpha = (1−t)^1.6 × min(1, age×8) × 0.32.
- Haze puffs: a pool of 26 horizontal planes using a radial texture (0 rgba(255,255,255,.55), .35 rgba(235,240,255,.22), 1 transparent), additive blending. Each puff drifts outward and slows by exp(−dt), grows from a scale of 1.2–2.2 up to 5–9 (ease-out), with opacity = o × sin(π × min(1, t×1.15)), where o = 0.10 + 0.08 × strength. Life 2–3.6 s.
- Shockwave rings: a pool of 3 floor planes using a 512px radial ring texture (transparent until .62, rgba(220,232,255,.85) at .84, rgba(255,255,255,.35) at .9, transparent at 1), additive blending. Each ring scales from 0.8 up to 7 + 5×strength over 1.3 s (cubic ease-out), with opacity 0.55 × strength × (1−t)^1.5.
- Thruster glows: 5 additive sprites parented to carTilt at each nozzle (y 0.05), using a radial texture (white → rgba(190,215,255,.65) at .2 → transparent), color 0xcfe2ff. Opacity = thrust × 0.85 (the center nozzle uses 0.5). Scale = (0.35 + 0.45×thrust) × (1, 0.6).
- Downwash jets: 4 additive sprites with anchor center (0.5, 1), placed at the four corner nozzles in world space. The texture is a 64x256 canvas with a vertical gradient (top rgba(225,236,255,.9) → middle rgba(200,220,255,.35) → transparent), masked by a horizontal gradient that fades both edges. Scale = (0.35 + h×0.35, h×1.05), where h is the nozzle height. Opacity = thrust × 0.4 × clamp(h/0.2) × (0.85–1.0 random flicker).
- Every frame: hover = clamp(cy/0.25); thrust = clamp(hover×0.55 + |vertical speed|×0.35). While hovering, emit round((60 + 520×thrust) × dt + random) particles at speed 4.5 + 4×thrust.
- Lift-off moment (cy crosses above 0.04): one shockwave at strength 1, 8 puffs at strength 1, and a burst of 220 particles at speed 7.
- Touchdown moment (cy drops back below 0.04): one shockwave at strength 0.9 and 10 puffs at strength 1.2.
- While airborne, there is also a random chance of dt × 2.2 × thrust per frame of one extra small puff at strength 0.6.
- Idle hover motion while airborne: bob = sin(time×1.6) × 0.035 × clamp(cy/0.4), plus pitch wobble sin(time×1.1)×0.006 and roll wobble sin(time×0.9+1)×0.008.

==================================================
SCROLL TIMELINE
==================================================
p = scroll progress through #stage = clamp(−rect.top / (rect.height − viewportHeight)). Smooth it each frame with pNow += (p − pNow) × (1 − exp(−dt×6)); with reduced motion, jump straight to p. Cap dt at 0.05.
Helpers: clamp; lerp; smooth(t) = t²(3−2t); ease = cubic in-out (t<.5 ? 4t³ : 1−(−2t+2)³/2); range(p,a,b) = clamp((p−a)/(b−a)); band(p,a,b,c,d) = min(range(p,a,b), 1−range(p,c,d)).
portrait = aspect < 0.9. tanV = tan(15°). fitW(size,frac) = size / (frac×2×tanV×aspect). fitH(size,frac) = size / (frac×2×tanV). L = carDims.x.
d0 = portrait ? fitW(L,0.98) : max(fitW(L,0.72), fitH(carDims.y,0.30)).
g0 = portrait ? 0.60 : 0.80. h0 = d0 × tanV × (2×g0 − 1).
x0 = portrait ? 0 : L×0.05.
d2 = portrait ? fitW(L,1.05) : fitW(L,0.62).
d3 = portrait ? max(fitH(L,0.42), fitW(carDims.z,0.42)) : fitH(L,0.5).
off3 = portrait ? d3×tanV×0.2 : 0.

Keyframes, with fields cx,cy,cz (car position), yaw, pitch, roll, ex,ey,ez (camera position), tx,ty,tz (camera target) and up (0 = world up, 1 = up is −Z for top-down). Between neighbouring keys, interpolate every field with the cubic ease.
base = {cx:x0, cy:0, cz:0, yaw:0, pitch:0, roll:0, ex:x0×0.3, ey:h0, ez:d0, tx:x0×0.3, ty:h0, tz:0, up:0}
p 0.00: base
p 0.05: base
p 0.15: base + {cx: portrait?0:L×0.1, cy:0.45, pitch:−0.035, ey:h0+0.08, ty:h0+0.12}
p 0.26: base + {cx: portrait?0:L×0.16, cy:1.15, pitch:−0.02, roll:0.01, ey:h0+0.2, ty:h0+0.42, ez:d0×1.1}
p 0.31: base + {cx: portrait?0:L×0.18, cy:1.2, pitch:0, ey:h0+0.22, ty:h0+0.46, ez:d0×1.12}
p 0.46: {cx: portrait?0.2:L×0.3, cy:0.75, cz:0, yaw:0.72, pitch:0.015, roll:−0.04, ex: portrait?0.2:L×0.05, ey: portrait?1.6:2.1, ez:d2×1.08, tx: portrait?0.2:L×0.1, ty: portrait?0.95:1.05, tz:0, up:0}
p 0.60: {cx: portrait?0.2:L×0.3, cy:0.8, cz:0, yaw:0.86, pitch:0.01, roll:−0.03, ex: portrait?0.2:L×0.07, ey: portrait?1.7:2.2, ez:d2×1.02, tx: portrait?0.2:L×0.11, ty: portrait?1.0:1.1, tz:0, up:0}
p 0.72: {cx:0, cy:0.9, cz:0, yaw:−0.6, pitch:0, roll:0.05, ex:0, ey:d3×0.75, ez:d3×0.45, tx:0, ty:0, tz:off3×0.5, up:0.6}
p 0.84: {cx:0, cy:0, cz:0, yaw:−π/2, pitch:0, roll:0, ex:0, ey:d3, ez:off3+0.001, tx:0, ty:0, tz:off3, up:1}
p 1.00: same as p 0.84 but ey:d3×0.96
Camera each frame: position = (ex,ey,ez); camera.up = normalize(0, 1−up, −up); lookAt(tx,ty,tz).
Rebuild the keyframes on resize and after the model loads, because carDims changes.

What the user sees:
- 0 → 0.31: the car lifts about 1.2 m with downward air jets, floor spray, haze and a shockwave ring, and drifts slightly right.
- 0.31 → 0.60: it yaws into a front three-quarter close-up on the right side of the screen.
- 0.60 → 0.84: the camera climbs overhead while the car spins to point up-screen and descends to land, with a touchdown ring.
- 0.84 → 1.00: top-down hold with the feature cards.

DOM animation tied to p:
- Hero opacity = 1 − range(p,.04,.12), with translateY(−40×(1−opacity) px). Pointer events only while opacity > .5.
- Scroll cue opacity = 1 − range(p,.01,.05).
- Outline wordmark: oT = range(p,.03,.3). transform = translateX(−50%) translateY(−oT×12vh) scale(1+oT×.08). letter-spacing = (.005 + oT×.06)em. Opacity = portrait ? 1−range(p,.05,.13) : 1−range(p,.24,.36).
- Side panel opacity = band(p,.40,.47,.60,.66). Chip i: t = smooth(range(p, .43+i×.012, .48+i×.012)) × (1 − range(p,.6,.65)), giving opacity t and translateY((1−t)×14px).
- Top panel opacity = range(p,.82,.88). Card i: t = smooth(range(p, .83+i×.01, .89+i×.01)), giving opacity t and translateY((1−t)×16px).
- Backgrounds: b = range(p,.62,.78). bg1 opacity 1−b with transform scale(1 + range(p,0,.62)×.06) translateY(−b×6%). bg2 opacity b with transform scale(1.12 − b×.12).

==================================================
RESPONSIVE
==================================================
@media (max-width:860px):
- Nav becomes grid-template-columns auto 1fr auto with padding 14px var(--gutter). Hide the links, socials and divider. Show the hamburger, and center the logo.
- The menu opens .nav-links.open as a fixed panel at inset 68px 12px auto 12px: a column, background #111317, 1px var(--line) border, radius 16px, padding 8px, with links padded 14px 12px at 16px. Tapping a link closes it.
- .outline: top 17%, font-size 25vw, span scaleY(1.7).
- .hero: top auto, bottom 9%, left and right var(--gutter); h1 clamp(44px,12vw,64px).
- .side-title: top 13%, right var(--gutter); h2 clamp(40px,12vw,64px).
- .top-head: top 10%.
- .cards: inset auto 0 0 0, a 2-column grid with gap 8px, align-content end, padding 0 var(--gutter) 18px. Reset grid-column and grid-row on every card. Cards become static with auto width, padding 10px 12px, radius 12px, a 24px icon box, 12px icons, h3 13px, and the paragraph hidden.
- Chips in portrait (set in JS): stacked under the car in 2 columns, left = (i%2 ? 50 : 0)vw plus margin-left var(--gutter), bottom = (6 + floor(i/2)×7)%. Only the first 4 chips are shown.
@media (max-width:520px): chips use 12px text and padding 7px 11px 7px 8px with the label span hidden; the language button is hidden.
@media (prefers-reduced-motion:reduce): stop the cue animation. (The JS also skips particles, haze, rings and jets and jumps straight to the scroll position.)

Testing helpers: ?tick drives frames with setTimeout(40ms) instead of requestAnimationFrame. ?p=0..1 pins the timeline and exposes window.__setP(v).

Quality bar: smooth, noise-free gradients and no grain anywhere. The car must sit visually on the glossy floor of background 1 in the hero, with its nose facing left. It must work and look composed at 1280×720 desktop and 375×812 mobile with no horizontal scroll.
[/hidecontent]
← 返回首页