类型:Site模板 · 收录:2026-10-25 13:38:20 · 源码:会员可见
Build a single self-contained file, index.html (all HTML, CSS and JS inline, no build step), for a two-section, scroll-driven 3D landing page called "Skyra AI". Recreate it EXACTLY as specified below — same copy, colors, sizes, timings and math. Do not add sections, copy or libraries beyond what is listed.
========================================
1. ASSETS (use these exact URLs, loaded directly)
========================================
- Cloud background video (MP4, 1920x1080, 10 s, 16:9, muted loop):
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261016_130129_cfe21363-8606-4dd9-a8cc-d19b6c24d2d7.mp4
- Poster / fallback still of the same clouds (PNG):
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261016_130037_46341e32-6cd0-4fac-ad09-00d747e2f806.png
- 3D jet model (GLB, white glossy business jet with orange T-tail, orange rear twin engines, orange fuselage stripe, orange winglets, Y-up, single mesh):
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261017_132803_66d7da07-a6ef-4081-976a-8cd35f92833c.glb
- Three.js r160 via import map (ES modules):
"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).
- Font: Google Fonts "Manrope" weights 300;400;500;600, display=swap, with preconnect to fonts.googleapis.com and fonts.gstatic.com (crossorigin).
========================================
2. HEAD
========================================
<html lang="en">, meta charset utf-8, meta viewport "width=device-width, initial-scale=1".
<title>Skyra AI — Airborne Safety</title>
========================================
3. CSS (exact)
========================================
:root tokens:
--ink: #1c1a18;
--ink-soft: rgba(28,26,24,0.72);
--orange: #ff7a1f;
--orange-deep: #f2650b;
--sky: #f3e3d0;
--glass: rgba(255,255,255,0.28);
--glass-line: rgba(255,255,255,0.55);
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body { font-family: "Manrope", system-ui, sans-serif; color: var(--ink); background: var(--sky); overflow-x: hidden; -webkit-font-smoothing: antialiased; }
Fixed stage (sits behind everything):
.stage { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
.stage video, .stage .poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.stage .poster { background: center / cover no-repeat; }
.stage canvas { position: absolute; inset: 0; width: 100% !important; height: 100% !important; }
.stage .tint { position: absolute; inset: 0; background: radial-gradient(120% 80% at 50% 45%, rgba(255,240,225,0) 40%, rgba(240,214,190,0.35) 100%); }
Stacking inside .stage, bottom to top: .poster div, <video>, WebGL canvas, .tint.
Nav:
nav { position: fixed; top: 0; left: 0; right: 0; z-index: 20; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; padding: 14px 30px; }
nav .links { display: flex; gap: 30px; }
nav .links.right { justify-content: flex-end; align-items: center; }
nav a { color: var(--ink); text-decoration: none; font-size: 16px; font-weight: 400; transition: opacity .25s; }
nav a:hover { opacity: .6; }
.logo { display: block; width: 42px; height: 42px; }
.btn { display: inline-flex; align-items: center; justify-content: center; height: 50px; padding: 0 37px; border-radius: 999px; background: var(--orange); color: #fff !important; font-size: 16px; box-shadow: 0 8px 24px rgba(255,122,31,0.28), inset 0 1px 0 rgba(255,255,255,.35); transition: background .25s, transform .25s, opacity .25s !important; }
.btn:hover { background: var(--orange-deep); transform: translateY(-1px); opacity: 1 !important; }
Scroll track:
main { position: relative; z-index: 10; }
section { position: relative; height: 150vh; } (#hero and #tracking are both 150vh → total page 300vh)
.headline { position: absolute; top: 17vh; left: 0; right: 0; text-align: center; font-weight: 400; font-size: clamp(34px, 3.6vw, 48px); line-height: 1.12; letter-spacing: -0.035em; will-change: transform, opacity; }
Section-2 header (fixed, fades in by JS):
.s2-head { position: fixed; top: 92px; left: 0; right: 0; z-index: 15; text-align: center; opacity: 0; transform: translateY(12px); pointer-events: none; will-change: opacity, transform; }
.s2-head small { display: block; font-size: 11px; letter-spacing: .02em; color: var(--ink-soft); }
.s2-head h2 { font-size: 19px; font-weight: 500; letter-spacing: -0.02em; margin: 2px 0 8px; }
.s2-head p { font-size: 11px; color: var(--ink-soft); margin-bottom: 8px; }
.s2-head .icons { display: flex; justify-content: center; gap: 7px; }
.s2-head .icons span { width: 18px; height: 18px; border-radius: 5px; display: grid; place-items: center; background: rgba(28,26,24,.72); color: #fff; font-size: 9px; font-weight: 600; }
.s2-head.on { pointer-events: auto; }
Glass caption pill (fixed, two stacked texts that crossfade):
.pill { position: fixed; z-index: 15; left: 50%; bottom: 80px; transform: translateX(-50%); width: min(494px, calc(100vw - 32px)); height: 100px; border-radius: 999px; background: var(--glass); border: 1px solid var(--glass-line); backdrop-filter: blur(14px) saturate(1.2); -webkit-backdrop-filter: blur(14px) saturate(1.2); box-shadow: 0 10px 40px rgba(170,120,80,.12), inset 0 1px 0 rgba(255,255,255,.6); display: grid; place-items: center; text-align: center; }
.pill p { grid-area: 1 / 1; font-size: 19px; line-height: 1.42; letter-spacing: -0.015em; color: #2c2925; padding: 0 40px; will-change: opacity, filter; }
.pill p.b { opacity: 0; }
Mobile (max-width: 760px):
nav { padding: 12px 16px; }
hide every left nav link except the first ("Craft"), and hide right-side links that are not .btn ("Story")
.btn { height: 42px; padding: 0 22px; font-size: 14px; }
.pill { height: 84px; bottom: 40px; }
.pill p { font-size: 15px; padding: 0 24px; }
.headline { top: 15vh; padding: 0 16px; }
========================================
4. HTML (exact structure and copy)
========================================
<div class="stage" id="stage">
<div class="poster" id="poster"></div>
<video id="bg" autoplay muted loop playsinline preload="auto"></video>
<div class="tint"></div>
</div>
<nav>
<div class="links left">
<a href="#hero">Craft</a>
<a href="#tracking">Highlights</a>
<a href="#tracking">Connect</a>
</div>
<a href="#hero" aria-label="Skyra AI home">
<svg class="logo" viewBox="0 0 48 48" fill="none" aria-hidden="true">
<path d="M17 13c-6 4-9.5 11-8 17.5 1.3 5.7 6.4 8.4 11.6 6.8 6.8-2.1 11.4-9.6 12.6-17.6" stroke="#1c1a18" stroke-width="4.2" stroke-linecap="round"/>
<path d="M26.5 21.5 34 8l5.5 14.5-6.7-3.3-6.3 2.3Z" fill="#1c1a18"/>
<path d="M22 16.5c-2.6 3.8-3.7 8.5-3 13" stroke="#1c1a18" stroke-width="3" stroke-linecap="round"/>
</svg>
</a>
<div class="links right">
<a href="#tracking">Story</a>
<a class="btn" href="#tracking">Request</a>
</div>
</nav>
<main>
<section id="hero">
<h1 class="headline" id="headline">The next era of flight safety<br />enabled by Skyra AI</h1>
</section>
<section id="tracking"></section>
</main>
<div class="s2-head" id="s2head">
<small>Live intel from</small>
<h2>Skyra Predictive Avionics</h2>
<p>Join us in the sky</p>
<div class="icons"><span>in</span><span>X</span><span>◎</span></div>
</div>
<div class="pill" id="pill">
<p class="a">Equipping pilots with intelligent AI for safer, more efficient flight</p>
<p class="b">Live aircraft monitoring and forward-looking insights</p>
</div>
========================================
5. BACKGROUND JS
========================================
Set #poster backgroundImage to the poster PNG URL. Set video.src to the MP4 URL and video.poster to the PNG URL.
========================================
6. THREE.JS SCENE
========================================
Renderer: WebGLRenderer({ antialias: true, alpha: true }); pixelRatio = min(devicePixelRatio, 2); size = window; outputColorSpace = SRGBColorSpace; toneMapping = ACESFilmicToneMapping; toneMappingExposure = 1.05. Insert the canvas inside .stage directly before .tint.
Environment: PMREMGenerator(renderer).fromScene(new RoomEnvironment(), 0.04).texture → scene.environment. No scene background (transparent, so the video shows through).
Camera: PerspectiveCamera(fov 30, aspect window, near 0.1, far 200).
Lights:
HemisphereLight(0xfff3e6, 0xf0c8a0, 1.3)
DirectionalLight "sun" (0xffe8d2, 2.4) at (-4, 8, 6)
DirectionalLight "rim" (0xffb27a, 1.2) at (6, 2, -6)
Jet hierarchy: scene → jetPivot (scroll-driven yaw/bank) → jetBob (idle float) → holder → GLB scene.
Convention: jet nose points along local +X, up = +Y.
GLB load (GLTFLoader):
model.rotation = Euler(0, Math.PI, 0) // makes the GLB nose point +X
put model in a "holder" Group; compute Box3 of holder → size, center
s = 4.6 / max(size.x, size.z) (jetLength = 4.6)
model.position.sub(center); holder.scale.setScalar(s)
for every mesh material: envMapIntensity = 1.1; roughness = min(roughness, 0.45)
engine points (for contrails): L = size.x*s, Hh = size.y*s, Wd = size.z*s →
(-L*0.2, Hh*0.04, Wd*0.1) and (-L*0.2, Hh*0.04, -Wd*0.1)
rebuild contrails at those points, then add holder to jetBob.
Contrails (one per engine point, children of jetBob):
ShaderMaterial { transparent: true, depthWrite: false, side: DoubleSide, uniforms { uTime: 0, uOpacity: 0.75 } }
vertex: pass uv as vUv; standard projection.
fragment:
along = vUv.x (0 at engine, 1 at the far end); across = abs(vUv.y - .5) * 2.
width = mix(.25, 1., along)
core = 1. - smoothstep(0., width, across); core *= core
fade = pow(1. - along, 1.4) * smoothstep(0., .05, along)
flicker = .9 + .1 * sin(along * 40. - uTime * 18.)
col = mix(vec3(1., .93, .84), vec3(1.), along)
gl_FragColor = vec4(col * 1.25, min(1., core * fade * flicker * uOpacity * 1.6))
Geometry: PlaneGeometry(9, 0.55); translate(-4.5, 0, 0) so it extends backwards (-X); flip the UV x (u = 1 - u) so u=0 sits at the engine. Make a second copy rotated rotation.x = PI/2 (crossed planes). Group both, position = enginePoint + (-0.42, 0, 0).
Glow ring under the jet (hero):
ShaderMaterial { transparent: true, depthWrite: false, NormalBlending, uniforms { uOpacity: 1, uFill: 0.35, uColor: Color(0xff7a2a) } }
fragment:
r = length(vUv - .5) * 2.
rimLine = 1. - smoothstep(0., .06, abs(r - .9))
halo = (1. - smoothstep(0., .35, abs(r - .9))) * .55
fill = (1. - smoothstep(.2, 1., r)) * uFill
col = mix(uColor, vec3(1., .97, .92), rimLine * .55)
a = (rimLine + halo + fill) * (1. - smoothstep(.93, 1., r))
gl_FragColor = vec4(col, a * uOpacity)
Mesh PlaneGeometry(3.6, 3.6), rotation.x = -PI/2, position.y = -0.85, added to scene (not to the jet).
Radar group (section 2): Group at y = -0.85, added to scene.
4 thin white rings: RingGeometry(r - w, r + w, 160), rotation.x = -PI/2, MeshBasicMaterial white, transparent, opacity 0, depthWrite false.
(r, w) = (2.6, 0.035), (4.3, 0.03), (6.4, 0.025), (9.0, 0.02); max opacities = 0.85, 0.7, 0.55, 0.4.
Pulse: clone of the glow-ring material with its own cloned uniforms, uColor = white, uFill = 0.55; PlaneGeometry(1,1), rotation.x = -PI/2.
16 tiny aircraft markers: PlaneGeometry(0.32, 0.32) lying flat (rotation.x = -PI/2), MeshBasicMaterial with a canvas texture, transparent, opacity 0, depthWrite false; tint 0xff7a2a for every 4th marker (i % 4 === 0), else white.
Marker texture: 128x128 canvas, translate(64,64), white fill, path:
moveTo(0,-50) quadraticCurveTo(7,-40,7,-18) lineTo(48,6) lineTo(48,14) lineTo(7,4) lineTo(6,32) lineTo(18,42) lineTo(18,48) lineTo(0,42) lineTo(-18,48) lineTo(-18,42) lineTo(-6,32) lineTo(-7,4) lineTo(-48,14) lineTo(-48,6) lineTo(-7,-18) quadraticCurveTo(-7,-40,0,-50) fill.
CanvasTexture with SRGBColorSpace.
Placement uses a seeded PRNG (mulberry32, seed 7):
function mulberry(a){return()=>{a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296;};}
per marker, in this order: ring = 1 + floor(rand()*3); r = [2.6,4.3,6.4,9.0][ring] + (rand()-0.5)*0.5; a = rand()*2PI; speed = (0.02 + rand()*0.05) * (rand() > 0.5 ? 1 : -1); delay = rand()*0.25.
========================================
7. SCROLL + ANIMATION LOGIC (per frame, requestAnimationFrame)
========================================
Helpers:
ease(t) = t < .5 ? 4t³ : 1 - (-2t+2)³/2 (easeInOutCubic)
clamp01, range(p,a,b) = clamp01((p-a)/(b-a)), lerp
Scroll progress: target = clamp01(scrollY / (document.documentElement.scrollHeight - innerHeight)), updated on passive scroll and on resize.
Smoothing: dt = min(clock delta, 0.05); prog += (target - prog) * (1 - 0.0015^dt). p = prog.
Mouse: normalized -1..1 from pointermove; mouseS lerps toward it by (1 - 0.02^dt).
Camera keyframes:
A (hero): pos (0.3, 1.05, 11.2), lookAt (0, 0.25, 0)
B (tracking): pos (0.0, 12.8, 9.6), lookAt (0, -0.9, -0.2)
k = ease(range(p, 0.08, 0.92))
fit = aspect < 1 ? 1 + (1 - aspect) * 1.1 : 1 (pulls back on portrait/mobile)
camPos = lerp(A.pos, B.pos, k) * fit; camLook = lerp(A.look, B.look, k)
camPos.x += mouseS.x * 0.35; camPos.y += -mouseS.y * 0.2; camera.lookAt(camLook)
Jet:
jetPivot.rotation.y = lerp(3.42, 2.42, k) (hero: nose to lower-left toward viewer → section 2: nose to upper-left)
jetPivot.rotation.x = lerp(0.12, -0.04, k) (bank)
jetPivot.rotation.z = lerp(0.06, 0.0, k) (pitch)
jetBob.position.y = sin(t*0.9)*0.06; jetBob.rotation.x = sin(t*0.7)*0.025; jetBob.rotation.z = sin(t*0.55+1)*0.015
contrail uTime = t
Glow ring (leaves the hero):
r1 = range(p, 0, 0.3); scale = 1 + ease(r1)*2.6
opacity = (1 - ease(r1)) * (0.9 + sin(t*2.2)*0.1)
position.y = -0.85 + jetBob.position.y * 0.4
Radar (enters section 2):
r2 = ease(range(p, 0.5, 0.95)); radar.scale = lerp(0.35, 1, r2)
ring i opacity = ease(range(p, 0.5 + i*0.07, 0.75 + i*0.07)) * maxOpacity[i]
pulse: cycle = (t*0.35) % 1; scale = lerp(2.2, 11, cycle); opacity = range(p, 0.55, 0.75) * (1 - cycle) * 0.75
markers: vis = ease(range(p, 0.62 + delay, 0.82 + delay)); angle = a + t*speed;
position (cos(angle)*r, 0.01, sin(angle)*r); rotation.z = -angle + (speed > 0 ? 0 : PI); opacity = vis * 0.9
DOM:
headline opacity = 1 - range(p, 0, 0.22) (it also scrolls up naturally)
pill text A: outA = range(p, 0.3, 0.42) → opacity 1 - outA, filter blur(outA*6 px)
pill text B: inB = range(p, 0.48, 0.6) → opacity inB, filter blur((1 - inB)*6 px)
section-2 header: sIn = ease(range(p, 0.72, 0.9)) → opacity sIn, transform translateY((1 - sIn)*12 px); toggle class "on" when sIn > 0.5
Render scene every frame. On resize: update camera aspect + projection, renderer size, re-read scroll.
========================================
8. EXPECTED RESULT
========================================
Section 1 (top): warm peach cloud video full-bleed; fixed nav (Craft · Highlights · Connect | black swoosh-arrow logo centered | Story + orange "Request" pill button); centered two-line headline; the white/orange jet hovers in the center, nose pointing left toward the viewer, with soft white contrails streaming to the right from the engines and an orange glowing ellipse ring beneath it; frosted glass caption pill at the bottom.
While scrolling: the headline fades out, the orange ring expands and dissolves, the camera smoothly orbits up to a high top-down view while the jet turns so its nose points up-left, the pill text blurs out and the new caption blurs in.
Section 2 (bottom): concentric white radar rings grow in under the jet with a repeating white pulse wave and small orange/white plane icons slowly orbiting on the rings; a small centered header ("Live intel from / Skyra Predictive Avionics / Join us in the sky" + three tiny dark icon squares) fades in under the nav.
The jet always floats gently and the camera shifts slightly with the mouse. Mobile: compact nav (Craft, logo, Request), smaller pill and button, camera pulls back so the wings fit.[/hidecontent]