motionsites中文站 / Lost Little Explorer
Lost Little Explorer
aadmin2026-10-17 发布Site模板0 阅读

Lost Little Explorer

类型:Site模板 · 收录:2026-10-17 12:02:08 · 源码:会员可见

[hidecontent type="vip1"]
Build a single standalone index.html (inline <style> and one inline <script type="module">, no build step, no local assets) for a 404 page called "Vulpa". The page is exactly one viewport tall (100vh / 100dvh, no scrolling) and shows a 3D fox, loaded from a remote GLB, whose head and eyes smoothly follow the cursor. Reproduce every value below exactly.

=== HEAD ===
- <html lang="en">, charset utf-8, viewport "width=device-width, initial-scale=1, viewport-fit=cover", <title>404 · Vulpa</title>, theme-color #fbdc80.
- Fonts (Google Fonts, with preconnect to fonts.googleapis.com and fonts.gstatic.com crossorigin):
  https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&family=Quicksand:wght@700&display=swap
- Three.js via import map:
  "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/"

=== CSS TOKENS (:root) ===
--sky-top:#fbd873; --sky-mid:#fce595; --sky-low:#fef1c9; --mist:#fffaf0; --ink:#121212; --ink-soft:#6b6862; --fox:#f28b3a; --fox-deep:#e9741f; --pad:clamp(18px,4.2vw,54px);
Global: *,*::before,*::after{box-sizing:border-box}. html,body{height:100%;margin:0}. body{font-family:"Manrope",system-ui,sans-serif;color:var(--ink);background:var(--sky-top);overflow:hidden;-webkit-font-smoothing:antialiased}.

=== MARKUP (in this order) ===
<div class="page">
  <div class="sky" aria-hidden="true"> five <span class="cloud cN"> in the order c5, c1, c3, c2, c4, then <span class="mist"> </div>
  <header class="top">
    <a class="brand" href="/" aria-label="Vulpa home"> inline SVG logo + <span>Vulpa</span> </a>
    <a class="login" href="/login">Log on</a>
  </header>
  <main class="stage">
    <p class="digits" aria-hidden="true">404</p>
    <canvas id="fox" role="img" aria-label="A little anxious fox with a map, following your pointer"></canvas>
    <span class="shadow" aria-hidden="true"></span>
    <span class="loading" id="loading">Calling the fox…</span>
  </main>
  <section class="copy">
    <h1>Uh-oh! This map is wrong</h1>
    <p>We’ll guide you back to a place you know</p>
    <a class="home" href="/"> chevron SVG + "Take me home" </a>
  </section>
</div>

Logo SVG: viewBox "0 0 40 40", fill currentColor, containing
  <circle cx="20" cy="8.4" r="5.4"/>
  <path d="M4.6 14.2c2.3-.9 4.6.1 5.7 2.4 1.7 3.6 5.3 6 9.7 6s8-2.4 9.7-6c1.1-2.3 3.4-3.3 5.7-2.4 2.4.9 3.4 3.5 2.4 5.9C34.7 28.1 27.9 33 20 33S5.3 28.1 2.2 20.1c-1-2.4 0-5 2.4-5.9Z"/>
Chevron SVG: viewBox "0 0 24 24", fill none, stroke currentColor, stroke-width 2.4, round caps and joins, <path d="M15 5l-7 7 7 7"/>.

=== LAYOUT CSS ===
.page: position relative; height 100vh then height 100dvh; display flex; flex-direction column; overflow hidden; isolation isolate;
  background: radial-gradient(120% 70% at 50% 0%, #fbd568 0%, rgba(251,213,104,0) 60%), linear-gradient(180deg, var(--sky-top) 0%, var(--sky-mid) 38%, var(--sky-low) 66%, var(--mist) 90%).

.sky: position absolute; inset 0; z-index -1; pointer-events none.

.cloud: position absolute; border-radius 50%; filter blur(14px); animation drift 38s ease-in-out infinite alternate; four layered backgrounds:
  radial-gradient(closest-side, rgba(255,255,255,.95), rgba(255,255,255,0)) 0% 70% / 46% 62% no-repeat,
  radial-gradient(closest-side, rgba(255,255,255,.9), rgba(255,255,255,0)) 34% 20% / 52% 78% no-repeat,
  radial-gradient(closest-side, rgba(255,255,255,.95), rgba(255,255,255,0)) 78% 55% / 50% 70% no-repeat,
  radial-gradient(closest-side, rgba(255,255,255,.8), rgba(255,255,255,0)) 55% 100% / 90% 60% no-repeat
.c1 {left:-9%; top:33%; width:30%; height:30%; opacity:.75}
.c2 {left:4%; top:49%; width:34%; height:34%; opacity:.9; animation-duration:46s}
.c3 {right:-8%; top:22%; width:30%; height:34%; opacity:.85; animation-duration:52s; animation-direction:alternate-reverse}
.c4 {right:2%; top:46%; width:36%; height:36%; opacity:.9; animation-duration:42s; animation-direction:alternate-reverse}
.c5 {left:8%; top:27%; width:14%; height:12%; opacity:.45}
@keyframes drift { from {transform:translateX(-1.5%)} to {transform:translateX(1.5%)} }

.mist: position absolute; left -10%; right -10%; bottom -6%; height 56%; background:
  radial-gradient(60% 70% at 18% 62%, rgba(255,255,255,.95), rgba(255,255,255,0) 70%),
  radial-gradient(60% 70% at 82% 60%, rgba(255,255,255,.95), rgba(255,255,255,0) 70%),
  linear-gradient(180deg, rgba(255,250,240,0) 0%, rgba(255,250,240,.85) 45%, var(--mist) 75%)

.top: position absolute; inset 0 0 auto 0; z-index 3; display flex; align-items center; justify-content space-between; padding calc(var(--pad) * .72 + env(safe-area-inset-top)) var(--pad) 0.

.brand: inline-flex; align-items center; gap 9px; color #fff; no underline; font 700 clamp(24px,2.5vw,32px)/1 "Quicksand","Manrope",sans-serif; letter-spacing -.02em; border-radius 10px. Its svg: width and height 1.22em, flex none.

.login (glass button): inline-flex centred; height clamp(42px,4vw,50px); padding 0 clamp(14px,1.3vw,18px); color #fff; font 600 clamp(15px,1.35vw,17px)/1 "Manrope"; letter-spacing -.01em; border-radius 14px; background linear-gradient(180deg, rgba(255,255,255,.5), rgba(255,255,255,.28)); border 1px solid rgba(255,255,255,.7); box-shadow 0 6px 18px rgba(203,150,32,.22), inset 0 1px 0 rgba(255,255,255,.7); backdrop-filter blur(8px) (plus -webkit- prefix); text-shadow 0 1px 2px rgba(176,124,20,.35); transition transform .2s ease, background .2s ease.
  :hover → translateY(-1px), background linear-gradient(180deg, rgba(255,255,255,.62), rgba(255,255,255,.38)).

.stage: position relative; flex 1 1 auto; min-height 0; container-type size.

.digits (giant translucent 404 behind the fox): position absolute; left 50%; bottom 3.5%; transform translateX(-50%) scaleX(.86); margin 0; font 700 min(116cqh, 52cqw)/.73 "Manrope"; letter-spacing -.012em; white-space nowrap; color rgba(255,255,255,.62); mask-image linear-gradient(180deg, #000 55%, rgba(0,0,0,.35) 100%) (plus -webkit- prefix); user-select none; pointer-events none.

#fox canvas: position absolute; inset 0; width 100%; height 100%; display block; touch-action pan-y; opacity 0; transition opacity .7s ease. #fox.ready → opacity 1.

.shadow (soft ground shadow): position absolute; left 50%; bottom 1.2%; width min(34cqh, 40cqw); height 3.2cqh; translateX(-50%); background radial-gradient(closest-side, rgba(196,132,40,.3), rgba(196,132,40,0)); opacity 0; transition opacity .7s ease. "#fox.ready + .shadow" → opacity 1.

.loading: position absolute; left 50%; top 52%; translate(-50%,-50%); font 600 14px/1 "Manrope"; color rgba(140,96,18,.75); letter-spacing .02em; transition opacity .4s ease. .loading.hide → opacity 0.

.copy: position relative; z-index 2; flex none; text-align center; padding clamp(6px,1.4vh,16px) var(--pad) calc(clamp(14px,2.3vh,24px) + env(safe-area-inset-bottom)).
h1: margin 0; font 600 clamp(29px, min(4.15vw, 7.4vh), 56px)/1.08 "Manrope"; letter-spacing -.045em; text-wrap balance.
.copy p: margin clamp(6px,1.3vh,12px) 0 0; font 500 clamp(16px, min(1.95vw, 3.5vh), 26px)/1.25 "Manrope"; letter-spacing -.03em; color var(--ink-soft); text-wrap balance.

.home (orange button): inline-flex; align-items center; gap 8px; margin-top clamp(12px,2.4vh,20px); height clamp(44px,6.6vh,50px); padding 0 16px 0 12px; color #fff; font 600 clamp(15px,1.35vw,17px)/1 "Manrope"; letter-spacing -.01em; border-radius 13px; background linear-gradient(180deg, #f7a258 0%, var(--fox) 45%, var(--fox-deep) 100%); box-shadow 0 10px 22px rgba(233,116,31,.34), 0 2px 4px rgba(190,86,12,.28), inset 0 1px 0 rgba(255,255,255,.45); text-shadow 0 1px 1px rgba(160,70,6,.4); transition transform .2s ease, box-shadow .2s ease.
  svg 18×18 with transition transform .2s ease.
  :hover → translateY(-2px), first shadow becomes 0 14px 26px rgba(233,116,31,.4), and the svg moves translateX(-3px). :active → translateY(0).

a:focus-visible: outline 3px solid #1a1a1a; outline-offset 3px.

=== RESPONSIVE ===
@media (max-width:640px): .cloud blur(10px); .c1 {width:60%; left:-24%}; .c2 {width:64%; left:-20%}; .c3 {width:60%; right:-24%}; .c4 {width:66%; right:-20%}; .digits {font-size:min(110cqh, 72cqw); bottom:13%}.
@media (max-height:430px): .top padding-top 12px; hide .copy p.
@media (prefers-reduced-motion: reduce): no cloud animation; no transitions on .login, .home, .home svg, #fox, .shadow.

=== 3D FOX (three.js r160, inline module) ===
Imports: THREE; GLTFLoader, DRACOLoader (decoder path https://www.gstatic.com/draco/versioned/decoders/1.5.6/) and MeshoptDecoder from three/addons.

Model URL, loaded directly, never downloaded or bundled:
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261014_013004_603cedaa-048c-46da-a737-4c013ad994ae.glb
It is a fluffy orange cartoon fox standing upright, holding a paper map, with a bushy cream-tipped tail curling out to its left (viewer's right) and BLANK WHITE eyes; the page draws the moving eyes itself.

Renderer: WebGLRenderer on #fox, alpha true, antialias true, pixel ratio min(devicePixelRatio, 2), outputColorSpace SRGBColorSpace.
Camera: PerspectiveCamera(24, 1, 0.1, 100).
Lights: HemisphereLight(0xfffaf0, 0xf6cf86, 2.1); DirectionalLight(0xffffff, 1.5) at (-2.5, 4, 5); DirectionalLight(0xffe2a8, 0.8) at (3, 2.5, -4).

Rig constants ("fox space": feet at y=0, ear tips at y=2, facing +z):
  neck [0.98, 1.14]; pivot [-0.165, 1.02, 0.15];
  eyeL [-0.34, 1.30, 0.474]; eyeR [0.011, 1.313, 0.496];
  eyeSize [0.108, 0.112, 0.075]; eyeTilt 0.25; eyeToe 0.12;
  headYaw 0.42; headPitch 0.24; eyeYaw 0.5; eyePitch 0.38.

On load:
1. model.rotation.y = -Math.PI/2 - 0.16 (the mesh faces +x), then updateMatrixWorld(true).
2. Take Box3 of the model. s = 2/size.y. fit = Scale(s,s,s) × Translate(-centre.x, -box.min.y, -centre.z). foxWidth = size.x × s.
3. For every mesh: geometry.applyMatrix4(fit × mesh.matrixWorld), computeBoundingSphere, frustumCulled false; material metalness 0, roughness 1, map anisotropy = max; material.onBeforeCompile = bendHead; add a new THREE.Mesh(geometry, material) to a Group called fox.
4. Add a Group called head to fox at the pivot; create the two eyes inside it (left side = -1, right side = +1).
5. Call resize(), add class "ready" to the canvas, add class "hide" to #loading, start renderer.setAnimationLoop(frame).
On load error set #loading text to: "Our fox got lost too. Please reload."

bendHead(shader): head turning is done in the vertex shader. Add uniform mat3 uHead (shared object { value: new THREE.Matrix3() }) and this function after "#include <common>":
    float headWeight(vec3 p) {
      float w = smoothstep(0.980, 1.140, p.y);
      float tail = smoothstep(0.26, 0.36, p.x) * (1.0 - smoothstep(0.0, 0.14, p.z)) * (1.0 - smoothstep(1.18, 1.28, p.y));
      float map = smoothstep(0.58, 0.66, p.z) * (1.0 - smoothstep(1.0, 1.06, p.y));
      return w * (1.0 - tail) * (1.0 - map);
    }
  After "#include <beginnormal_vertex>": float hw = headWeight(position); objectNormal = normalize(mix(objectNormal, uHead * objectNormal, hw));
  After "#include <begin_vertex>": transformed = mix(transformed, P + uHead * (transformed - P), hw); where P = vec3(-0.165, 1.020, 0.150).
  Result: the head rotates about the neck while the body, the tail and the map stay still.

Eye texture: one 1024×512 canvas shared by both eyes. Fill #fffdf8. Centre everything at (256,256), which faces forward on a three.js sphere. Draw in order: disc r118 #4a2208; disc r110 with a radial gradient from (256,282) r20 to (256,256) r110 with stops 0 #f2a04a, .55 #c8661c, 1 #7a3a0e; disc r66 #140a04; white ellipse at (222,216) radii 24×20 rotated -0.5 rad; small disc at (292,296) r9 rgba(255,255,255,.85). Use it as a CanvasTexture, SRGBColorSpace, max anisotropy.

makeEye(pos, side): a Group "socket" positioned at pos minus pivot, rotation.y = side × eyeTilt, scale = eyeSize; inside it a Mesh(SphereGeometry(1, 48, 32), MeshStandardMaterial({ map, roughness .22, metalness 0 })) with userData.side = side. Add the socket to head and return the sphere.

Pointer tracking: Vector2 aim, look, turn; lastMove = -1e9. On window pointermove and pointerdown (passive): project (pivot.x, eyeL.y, eyeL.z) through the camera to get the face's screen position inside the canvas rect; reach = max(innerWidth, innerHeight) × .42; aim = ((clientX − faceX)/reach, (clientY − faceY)/reach), each clamped to −1..1; lastMove = performance.now(). On document pointerleave set lastMove = -1e9.

resize() (also driven by a ResizeObserver on .stage): w,h = stage client size (return if zero); renderer.setSize(w, h, false); camera.aspect = w/h; tan = tan(fov/2); visible = max(2/.9, (foxWidth/.86)/aspect); dist = visible/(2·tan); camera.position = (0, visible × .465, dist); lookAt(0, visible × .465, 0); updateProjectionMatrix().

frame(), using a THREE.Clock: dt = min(delta, .05); t = elapsed time.
  idle = performance.now() − lastMove > 3500. If idle and motion is allowed: aim = (sin(t·.55)·.55, sin(t·.9+1)·.22 − .05), so the fox glances around on touch screens. If idle and reduced motion: aim = (0,0).
  look.lerp(aim, 1 − exp(−dt·11)); turn.lerp(aim, 1 − exp(−dt·4.5)).
  euler (order 'YXZ') = (turn.y·headPitch, turn.x·headYaw, −turn.x·.05); head.rotation = euler; uHead = that rotation as a Matrix3.
  ex = clamp(look.x·1.25 − turn.x·.35, −1, 1); ey = clamp(look.y·1.25 − turn.y·.35, −1, 1).
  Each eye sphere: rotation.set(ey·eyePitch, ex·eyeYaw − side·eyeToe, 0, 'YXZ').
  fox.rotation.y = turn.x·.1. Unless reduced motion: fox.position.y = sin(t·1.7)·.008 and fox.scale.y = 1 + sin(t·1.7)·.006 (gentle breathing).
  Render.

=== ACCEPTANCE ===
- Exactly one viewport tall, no scrollbars, no horizontal overflow at 375px wide.
- Yellow-to-cream sky with soft drifting clouds and bottom mist; white logo top-left; glass "Log on" button top-right; huge translucent white "404" behind a centred fox; headline, subline and orange "Take me home" button centred at the bottom.
- The fox faces the viewer at rest; moving the cursor left/right/up/down turns its head and slides its amber irises toward the cursor smoothly, with the eyes moving faster than the head.
- Everything stays in one index.html; the GLB is fetched from the CloudFront URL above.
[/hidecontent]
← 返回首页