Lumea
aadmin2026-11-05 发布Site模板11 阅读

Lumea

类型:Site模板 · 收录:2026-11-05 01:12:59 · 源码:会员可见

[hidecontent type="vip1"]
# Recreate this site as a single HTML file: Lumea

You are an expert creative front-end developer. Produce a **single self-contained `index.html`**
that reproduces the project below **exactly** — same layout, visuals, motion, and interaction.
Pure HTML/CSS/JS in one file: no build step, no framework, no bundler. Use ES modules with a CDN
importmap for Three.js 0.180.0. Hardcode every value given here as a fixed constant. Rebuild each
block described below as a section of the one file.

## What it is

A single full-bleed, real-time 3D hero for **Lumea**, a fictional design studio. A stone monk sits
in meditation on a floating rock pedestal in a near-black void, ringed by two glowing amber orbit
lines, inside a slowly turning vertical spiral of ~300 tumbling rock shards. One soft volumetric
light shaft rakes down onto him from high above; white dust motes drift through the dark. The
camera circles the figure endlessly on a low, **dutch-tilted** orbit (~9.7° roll), and the cursor
gently swings the orbit and pushes nearby rock shards aside — they drift away with weight and
settle back like heavy boulders. The rocks (and only the rocks) carry a subtle chromatic-aberration
fringe. Over the canvas: a frosted-glass nav pill, a serif headline that blooms in letter-by-letter
through blur, a right-hand intro paragraph and "Pitch your idea" line, a bottom sparkle + uppercase
tagline, and a large white outline ring that trails the OS cursor. Boot is covered by a light-mist
preloader: a square outline draws itself clockwise while a serif counter ticks to 100%, then the
same square inks black and expands to swallow the screen before fading to reveal the scene.

## Page shell & libraries

- **Importmap:**

  ```html
  <script type="importmap">
  { "imports": {
    "three": "https://unpkg.com/three@0.180.0/build/three.module.js",
    "three/addons/": "https://unpkg.com/three@0.180.0/examples/jsm/"
  } }
  </script>
  ```

  From `three/addons/`: `loaders/GLTFLoader.js`, `loaders/DRACOLoader.js`,
  `postprocessing/EffectComposer.js`, `postprocessing/RenderPass.js`,
  `postprocessing/ShaderPass.js`, `postprocessing/UnrealBloomPass.js`,
  `postprocessing/OutputPass.js`, `shaders/VignetteShader.js` (or an equivalent inline vignette
  shader). Both models are **Draco-compressed** — configure `DRACOLoader` with
  `setDecoderPath("https://unpkg.com/three@0.180.0/examples/jsm/libs/draco/gltf/")` and attach it
  to the `GLTFLoader`.

- **Fonts** — two faces, both on public CDNs; load their stylesheets:

  ```html
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Instrument+Serif&display=swap" />
  <link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=general-sans@200,300,400,500,600,700&display=swap" />
  ```

  → `font-family: "Instrument Serif", serif` for the brand word, headline and preloader counter;
  `font-family: "General Sans", system-ui, sans-serif` for everything else.

- **No scroll.** The page is one `100vh/100vw` fixed stage (`overflow: hidden` on body). Layers,
  bottom-up: WebGL canvas → UI overlay (`z 10`, `pointer-events: none` root; interactive controls
  opt back in) → mobile menu (`z 40`) → preloader (`z 50`) → custom cursor ring (`z 60`).

- **Adaptive rem grid** — rem-based layout with viewport-scaled root font-size, exactly:

  ```css
  html { font-size: 16px; }
  @media (max-width: 1920px) { html { font-size: 0.833333vw; } } /* 16*100/1920 */
  @media (max-width: 1440px) { html { font-size: 1.111111vw; } } /* 16*100/1440 */
  @media (max-width: 1024px) { html { font-size: 1.5625vw;   } } /* 16*100/1024 */
  @media (max-width: 640px)  { html { font-size: 4.444444vw; } } /* 16*100/360  */
  ```

  Breakpoints used below: `md` = 768px, `lg` = 1024px.

- **Colour tokens:**

  ```css
  :root {
    --scene-void: #05060a;                       /* page + canvas pre-paint backdrop  */
    --overlay-ink: #ffffff;                      /* all hero UI text                  */
    --nav-panel: rgba(48, 48, 55, 0.32);         /* frosted nav panel fill            */
    --nav-panel-strong: rgba(10, 10, 14, 0.38);  /* darker nav panel + mobile menu    */
    --nav-border: rgba(255, 255, 255, 0.15);     /* hairline borders between panels   */
    --preloader-surface: #e6e6e6;                /* light mist preloader backdrop     */
    --preloader-ink: #171717;                    /* preloader outline + counter       */
    --duration-fast: 150ms; --duration-normal: 250ms;
    --ease-entrance: cubic-bezier(0.2, 0, 0, 1);
  }
  ```

- **Springs.** All DOM motion is spring-based. Implement one tiny helper used everywhere:
  a per-rAF integrator with react-spring semantics (mass 1 unless stated) —
  `accel = −(tension/mass)·(x − target) − (friction/mass)·v`, semi-implicit Euler at the frame
  `dt`. Each animation below gives its `{ tension, friction }`. Values animated: opacity,
  translate, and `filter: blur()`.

## The device tiers

Read ONCE at boot; everything derives from it:

- `mobile` = `innerWidth < 768` **or** `matchMedia("(hover: none) and (pointer: coarse)")` matches;
  `tablet` = `innerWidth < 1180`; else `desktop`.
- **DPR** (canvas pixel ratio): mobile `min(devicePixelRatio, 1)`; tablet clamp to `[0.75, 1.25]`;
  desktop clamp to `[0.75, 1.5]`. Never below 1× on mobile — the thin glowing rings alias visibly.
- **Frame budget**: mobile renders at most every `1000/30` ms, tablet `1000/45`, desktop every rAF.
  Skip rendering entirely while `document.hidden`.
- **Counts**: dust motes `{ desktop: 4200, tablet: 2400, mobile: 1400 }`; debris rocks
  `{ desktop: 300, tablet: 200, mobile: 130 }`.
- **Composer multisampling (MSAA)**: `{ desktop: 4, tablet: 2, mobile: 0 }`. On mobile only, add an
  SMAA pass at the end of the chain instead.
- **Shadows**: desktop only.
- **Masked dispersion** (the rocks' chromatic fringe + its mask passes): desktop + tablet only —
  mobile never mounts the effect or the mask renderer.
- **Pointer effects** (rock repulsion + cursor orbit): off on mobile. On other tiers they are also
  gated on "has the pointer ever moved" — before the first `pointermove`, treat the pointer as
  absent (an ungated cursor at NDC (0,0) would punch a hole through the centre of the field).
- **Bloom intensity** is halved on mobile.
- **Reduced motion / energy saver**: if `prefers-reduced-motion: reduce` (or mobile with
  `navigator.connection.saveData` / `deviceMemory ≤ 2`), keep rendering through the intro, then
  ~1.4 s after the reveal stop the render loop on a settled frame (the canvas keeps its last frame).

## The WebGL scene

### Renderer, camera, atmosphere

- `WebGLRenderer({ antialias: false, alpha: false, stencil: false, depth: true,
  powerPreference: desktop ? "high-performance" : "default" })`.
- `renderer.toneMapping = ACESFilmicToneMapping`, `toneMappingExposure = 0.98`.
- `scene.background = new Color("#15161f")`; `scene.fog = new Fog("#15161f", 8, 34)`.
- `PerspectiveCamera(fov 34)` starting at `(-0.1, 1.4, 5.95)`.
- Render on demand through the composer, throttled to the tier's frame budget.

### Camera rig (every frame)

The camera auto-orbits the monk with a cursor-driven offset and a constant dutch roll:

```
// smooth = low-passed pointer NDC, ease 0.05 per frame toward (px, py)
// (px,py) = pointer if it has ever moved, else (0,0)
angle  = 1.591 + elapsedTime * 0.24 + smooth.x * 0.5
camera.position.set(0 + cos(angle) * 4.9,
                    1.4 + smooth.y * 0.75,
                    1.05 + sin(angle) * 4.9)
camera.lookAt(0, 1.7, 1.05)
camera.rotation.z += 0.17          // dutch tilt AFTER lookAt (lookAt levels the horizon)
```

### The content group

The monk, rings, debris spiral and all lights live inside one group at
`position (0, 0.95, 1.05)`, `rotation.y 0.39`, `scale 1`. (The beam, dust and backdrop are in
world space, outside it.) All positions in "content space" below are relative to this group.

### Backdrop (verbatim shader)

A sphere of `scale 40` (geometry `SphereGeometry(1, 32, 32)`), `side: BackSide`,
`depthWrite: false`, `fog: false`, `renderOrder: -2` — a vertical gradient plus a soft directional
top glow instead of a flat clear colour:

```glsl
// vertex
varying vec3 vDir;
void main() {
  vDir = normalize(position);
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
```

```glsl
// fragment
uniform vec3 uTop;
uniform vec3 uBottom;
uniform vec3 uGlow;
uniform float uGlowStrength;
uniform float uGlowSharpness;
uniform vec3 uGlowDir;
varying vec3 vDir;

void main() {
  float t = clamp(vDir.y * 0.5 + 0.5, 0.0, 1.0);
  vec3 base = mix(uBottom, uTop, smoothstep(0.0, 1.0, t));
  float g = pow(max(dot(normalize(vDir), normalize(uGlowDir)), 0.0), uGlowSharpness);
  vec3 color = base + uGlow * (g * uGlowStrength);
  gl_FragColor = vec4(color, 1.0);
}
```

Uniforms: `uTop #2b2b31`, `uBottom #050507`, `uGlow #000000`, `uGlowStrength 0.65`,
`uGlowSharpness 4.5`, `uGlowDir (0, 0.9, 0.25)`.

### The monk + the rings

Load `monk.glb` (see Assets). It contains the seated figure **with its stone pedestal** as one
textured mesh (`mesh.001`, centred on the origin, y ∈ [−0.95, 0.95]) plus two toruses (`Torus`,
r≈1.44 at y≈1.6, and `Torus.002`, r≈0.83 at y≈−0.09). Place the model at `(0, 0, 0)`,
`rotation.y 0.35` inside the content group; every mesh casts + receives shadows (desktop).

Replace both torus materials with `MeshStandardMaterial({ roughness: 0.4, metalness: 0.2,
toneMapped: false })`, `color #ffe3b3`, `emissive = #ffe3b3 scaled by 3.0` (set the emissive colour
and multiply it by the intensity; `emissiveIntensity` stays 1). `toneMapped: false` + bloom makes
them glow as thin amber orbit lines. The figure keeps its baked textured material.

### Lighting (inside the content group)

- `AmbientLight` intensity `0.2`.
- `HemisphereLight` sky `#aebbdd`, ground `#080910`, intensity `0.52`.
- **Key**: `SpotLight` colour `#f3ede2` at `(0.4, 8.6, 1.4)`, target `(0, 0.1, 0)`, intensity
  `132`, angle `0.66`, penumbra `1`, distance `26`, decay `1.45`; `castShadow`, shadow map
  `1024²`, bias `−0.0004`. The one dramatic top shaft — wide and soft so the figure reads as
  evenly-lit marble, not a hotspot.
- **Fill**: `PointLight` colour `#8593bb` at `(2.4, 2.2, 5.4)`, intensity `16`, distance `18`,
  decay `2` — a dim cool front fill so the robe and face read against the dark.
- **Source glow**: a small sphere (`radius 0.22`, 16×16) at `(0.3, 6.2, 0.4)`,
  `MeshBasicMaterial({ color: "#fff6e6", toneMapped: false })` — a bright nub that blooms into the
  top haze.

### The debris spiral (the rocks)

Load `rocks-lite.glb` (see Assets) — 7 rock meshes sharing one textured material. Collect the
geometries **sorted by mesh name**, clone the material once, lift it with `emissive #5c6479`,
`emissiveIntensity 0.22`, and set its map's `anisotropy` to the renderer max. Build one
`InstancedMesh` per geometry (shadows on, desktop).

Wind `count` shards (per tier) onto a vertical helix with a deterministic PRNG —
**mulberry32, seed 1337** (include this exact PRNG so the layout is identical on every load):

```js
const mulberry32 = (seed) => { let a = seed; 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; }; };
```

Spiral constants: `turns 6`, `radius 3.6`, `radiusJitter 1.7`, `yBottom −4.5`, `ySpan 12`,
`yJitter 0.7`, `angleJitter 0.5`, `zCenter −1.6`. Per shard `i` of `count`
(`angleStep = turns·2π/count`, and draw from the PRNG **in exactly this order**):

```
angle  = i*angleStep + (rng()−0.5)*angleJitter
radius = 3.6 + (rng()−0.5)*1.7
y      = −4.5 + (i/count)*12 + (rng()−0.5)*0.7
euler  = (rng()*2π, rng()*2π, rng()*2π)            → starting quaternion
spinAxis = normalize(rng()*2−1, rng()*2−1, rng()*2−1)
spinRate = (rng()*2−1) * 0.45                       // rad/s, signed
sizeRand = rng()
basePos  = (cos(angle)*radius, y, sin(angle)*radius − 1.6)
scale    = 0.16 + sizeRand * (0.7 − 0.16)
omega    = 3.2 + sizeRand * (1.7 − 3.2)             // spring frequency: big = heavier
geometry bucket = floor(rng() * geometryCount)
```

Per frame:
- The whole field group spins: `group.rotation.y += 0.04 * delta`.
- Each rock tumbles on its own axis: rotate its quaternion by
  `spinAxis, spinRate * 4 * delta` (the ×4 is the shipped spin multiplier).
- **Cursor repulsion** (desktop/tablet, only after the pointer has moved): cast the pointer ray
  into the field's local space (invert the group's world matrix). For each shard, find its
  perpendicular distance `d` to the ray (only if the along-ray projection is positive). If
  `d < 2.4`: `f = 1 − d/2.4`, `falloff = f·f·(3 − 2f)`, and the push target is the perpendicular
  direction scaled by `0.9 * falloff` (bounded — no 1/d spike). Otherwise the target is zero.
- Integrate each shard's displacement toward its push target as a **mass-normalized damped
  oscillator** (semi-implicit Euler, `dt = min(delta, 1/30)`):
  `accel = −ω²·(offset − target) − 2·0.42·ω·velocity` — ω is the shard's `omega`, damping ratio
  `0.42` (underdamped: rocks overshoot and settle with weight; big rocks are slower/heavier).
- Compose each instance matrix from `basePos + offset`, the accumulated quaternion, and the scale.

### The light shaft

One additive plane, world space, that reads as a soft beam raking down onto the monk with **no
visible source**. Group at `(0, 7.6, 1.05)`; inside it a `PlaneGeometry(1.5, 13)` mesh at
`y = −6.5` (half the length). Material: `ShaderMaterial`, `transparent: false`,
`depthWrite: false`, `side: DoubleSide`, `blending: AdditiveBlending`, `renderOrder: -1` — it
lives in the **opaque queue** after the backdrop (−2) and before solid geometry (0), so every
opaque object draws cleanly over it: no seams, no cut-outs at any orbit angle. Each frame,
billboard the group around Y only:
`group.rotation.y = atan2(camera.x − group.x, camera.z − group.z)`.

```glsl
// vertex
varying vec2 vUv;
void main() {
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
```

```glsl
// fragment
uniform vec3 uColor;
uniform float uOpacity;
varying vec2 vUv;
void main() {
  // Bright near the top (source), fading down; soft Gaussian across the width.
  float lengthFade = pow(clamp(vUv.y, 0.0, 1.0), 1.3) * smoothstep(0.0, 0.12, vUv.y);
  float across = exp(-pow((vUv.x - 0.5) * 4.5, 2.0));
  gl_FragColor = vec4(uColor, uOpacity * lengthFade * across);
}
```

Uniforms: `uColor #fff8eb`, `uOpacity 0.31`.

### Dust motes

A `Points` field (world space), seeded with **mulberry32, seed 99**. Per mote:
`position = center + ((rng()−0.5)·radius·2, (rng()−0.5)·height, (rng()−0.5)·radius·2)` with
`center (0, 1, 1.05)`, `radius 7`, `height 11`; `phase = rng()·2π`;
`bright = 0.4 + pow(rng(), 1.5) · 0.6`. Material: `ShaderMaterial`, `transparent`,
`depthWrite: false`, `AdditiveBlending`. Uniforms: `uSize 0.12`, `uDrift 0.05`,
`uColor #eaf1ff`, `uOpacity 0.85`, `uTime` = elapsed seconds.

```glsl
// vertex
attribute float phase;
attribute float bright;
uniform float uTime;
uniform float uSize;
uniform float uDrift;
varying float vBright;

void main() {
  vBright = bright;
  vec3 p = position;
  p.y += sin(uTime * uDrift + phase) * 0.35;
  p.x += cos(uTime * uDrift * 0.7 + phase * 1.3) * 0.25;
  vec4 mv = modelViewMatrix * vec4(p, 1.0);
  // Keep a min size well above 1px: sub-pixel points twinkle/flicker in motion.
  gl_PointSize = clamp(uSize * (220.0 / -mv.z), 2.0, 7.0);
  gl_Position = projectionMatrix * mv;
}
```

```glsl
// fragment
uniform vec3 uColor;
uniform float uOpacity;
varying float vBright;

void main() {
  float d = length(gl_PointCoord - 0.5);
  if (d > 0.5) discard;
  float alpha = smoothstep(0.5, 0.0, d) * uOpacity * vBright;
  gl_FragColor = vec4(uColor, alpha);
}
```

### Masked chromatic aberration (desktop/tablet)

The chromatic split is confined to the debris rocks — the monk, rings and pedestal never fringe.

**The mask.** Tag the debris instanced meshes with layer 3 (mask) and layer 4 (occluder); tag the
monk figure meshes (not the toruses) with layer 4 as well. Each frame, before the composer runs,
render a **full-resolution** offscreen target (a half-res mask shimmers along rock edges as the
camera orbits): clear to black, then (1) camera restricted to layer 4, scene override material =
flat black `MeshBasicMaterial` (fog off), rendered **writing depth** — so the monk lays down depth
and masks out rocks behind him; then (2) camera restricted to layer 3, override = flat white
`MeshBasicMaterial` (fog off, `depthWrite: false`), depth-tested against pass 1 — hidden rocks
stay black. Restore all renderer/scene/camera state after. (Set `scene.background = null` and
`autoClear = false` around the two passes.)

**The effect** — a full-screen pass immediately after the render pass and **before bloom** (so the
monk's bloom halo is computed from the clean image and never picks up the RGB split). Fragment
logic, verbatim (`inputBuffer` is the composed frame, `uMask` the mask texture):

```glsl
uniform sampler2D uMask;
uniform float uStrength;
uniform vec3 uColorA;
uniform vec3 uColorB;

void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
  float mask = texture(uMask, uv).r;
  if (mask < 0.01) {
    outputColor = inputColor;
    return;
  }

  vec2 offset = (uv - 0.5) * uStrength * mask;
  vec3 base = inputColor.rgb;
  vec3 plus = texture(inputBuffer, uv + offset).rgb;
  vec3 minus = texture(inputBuffer, uv - offset).rgb;

  vec3 baseWeight = clamp(1.0 - uColorA - uColorB, 0.0, 1.0);
  vec3 color = base * baseWeight + plus * uColorA + minus * uColorB;
  outputColor = vec4(color, inputColor.a);
}
```

Uniforms: `uStrength 0.01`, `uColorA #ffffff`, `uColorB #b0967d`.

### Post chain (in order)

1. Render pass.
2. Masked dispersion (above — desktop/tablet only).
3. **Bloom**: intensity `0.66` (`0.33` mobile), luminance threshold `0.82`, smoothing `0.3`,
   mipmap blur.
4. **Vignette**: offset `0.26`, darkness `1`.
5. **Film grain**: an animated noise overlay blended with `overlay` mode, opacity `0.21`
   (a small random-noise shader pass re-seeded per frame works).
6. SMAA (mobile only). Composer MSAA per tier otherwise.

### Prewarm + ready signal

While the preloader still covers the screen: once both models are loaded, call
`renderer.compileAsync(scene, camera)` (fall back to `compile`), then let **3 full frames** render
through the real composer chain. Only then flip `sceneReady` — the signal the preloader's counter
waits for. No shader may compile mid-scene after the reveal.

## The loader / reveal

Fixed overlay, `z 50`, background `--preloader-surface` (#e6e6e6), ink `#171717`. Top-centre:
"Lumea." in Instrument Serif, `1.25rem`. Centre: a square of `min(380px, 70% of the smaller
viewport side)` holding an SVG outline (viewBox `0 0 100 100`, `preserveAspectRatio="none"`,
path `M50 0.5 H99.5 V99.5 H0.5 V0.5 H50` — starts top-centre, draws clockwise, `pathLength 1`,
`strokeDasharray "1 1"`, stroke width 1, `vectorEffect="non-scaling-stroke"`) and, centred inside,
a serif counter at `5rem`.

Timeline:
1. **Load** — after a `150ms` delay the outline draws over `1400ms` (ease-in-out cubic;
   `strokeDashoffset = 1 − p`) while the counter ticks `0% → 100%` on the same progress. The
   counter **holds at 99%** until the scene's `sceneReady` flag is set — 100% must mean
   "rendered", not "timer elapsed". Failsafe: if WebGL never comes up, force ready at `9000ms`.
2. **Fill** (700ms) — the SAME square blackens and expands: spring `{ tension 150, friction 26 }`
   to `scaleX = innerWidth/square · 1.06`, `scaleY = innerHeight/square · 1.06`, while its
   background fades in as `rgba(5, 6, 10, min(1, v·2.2))` (inks faster than it grows). The outline
   is a child of the same element so it scales with the fill — one square, not a second one popping
   over it. The brand word + counter fade out.
3. **Reveal** (550ms) — the hero enter animations start underneath, and the whole preloader fades
   out with spring `{ tension 70, friction 26 }`, then unmounts. The fill colour equals the scene
   void, so the hand-off is seamless.

## The UI overlay

Root: absolute inset-0, `z 10`, `pointer-events: none`, colour `--overlay-ink`, General Sans.
Every enter animation is gated on the reveal (step 3 above); delays below are from that moment.

- **Nav pill** (top centre, `top 0.5rem`, pointer-events auto). A row of frosted-glass panels
  separated by 1px `--nav-border` dividers, with an outer 1px `--nav-border` border. Each panel:
  `backdrop-filter: blur(24px)`. Panels, left to right: **"Lumea."** (Instrument Serif, `1.25rem`,
  line-height 0.9, fill `--nav-panel`, padding `0.6875rem 2.4375rem`; `0.6875rem 1.5rem` below lg);
  links **Studio · Showcase · Journal · Contact** (`0.875rem`, gap `2rem`, fill
  `--nav-panel-strong`, padding `0.75rem 4rem`; hidden below md); **"Say hello!"** (`0.875rem`,
  fill `--nav-panel`, padding `0.75rem 2.15625rem`; hidden below md). Below md a burger panel
  (two 1px white bars) replaces the right side. Link hover: opacity 0.6, 150ms, `--ease-entrance`.
  Reveal: delay 220ms, spring `{ tension 90, friction 22 }` from `opacity 0, y −14px` —
  **opacity + translate only, never a filter/blur** (a CSS `filter` on the pill would kill the
  panels' `backdrop-filter`).
- **Mobile menu** (burger target, md down): full-screen `--nav-panel-strong` +
  `backdrop-filter: blur(40px)`, the four links in Instrument Serif `2.25rem`, a "Say hello!"
  pill (1px `--nav-border` border, radius 9999px, uppercase `0.875rem`, padding `0.75rem 2rem`),
  an × close button top-right. Opacity spring `{ tension 210, friction 26 }`.
- **Headline** — `<h1>` "Crafted to spark wonder", Instrument Serif. Desktop (lg+): left `2rem`,
  vertically centred, width `26.4375rem`, `4.5rem`, line-height 0.9, left-aligned. Below lg:
  horizontally centred at `top 15%`, centre-aligned, max-width `22rem`, `2.25rem` (sm: `28rem`,
  `2.75rem`), line-height 1.05. Letters animate individually: from `opacity 0,
  filter: blur(18px)` to `opacity 1, blur(0)`, stagger **46ms per letter**, spring
  `{ mass 1, tension 66, friction 24 }`, starting 200ms after reveal.
- **Right intro + field** — desktop: right `2rem`, vertically centred, width `20.1875rem`; below
  lg: left/right `1.5rem`, `bottom 24%`. The block itself springs in (delay 380ms,
  `{ tension 120, friction 26 }`, from `opacity 0, y 16px`). Inside:
  - Paragraph (`1rem`, line-height 1.3): "Brand identity, motion, and immersive web work. The
    tools evolve — the craft stays human." Animates **word by word**: from `opacity 0, y 16px,
    blur(8px)`, stagger 38ms, spring `{ tension 130, friction 26 }`, delay 480ms.
  - `margin-top 2rem`, a full-width button (pointer-events auto): "Pitch your idea" left, a
    22.5×7.4 rightward arrow SVG right, 1px solid white bottom border, `padding-bottom 0.75rem`,
    `0.875rem`. On hover the arrow translates right `0.25rem` (250ms, `--ease-entrance`).
    Reveal: delay 820ms, from `opacity 0, y 12px, blur(6px)`, `{ tension 120, friction 26 }`.
    Arrow path (viewBox `0 0 22.5 7.36396`, `fill: currentColor`):
    `M22.3536 4.03553C22.5488 3.84027 22.5488 3.52369 22.3536 3.32843L19.1716 0.146447C18.9763 -0.0488155 18.6597 -0.0488155 18.4645 0.146447C18.2692 0.341709 18.2692 0.658291 18.4645 0.853554L21.2929 3.68198L18.4645 6.51041C18.2692 6.70567 18.2692 7.02225 18.4645 7.21751C18.6597 7.41278 18.9763 7.41278 19.1716 7.21751L22.3536 4.03553ZM0 3.68198V4.18198H22V3.68198V3.18198H0V3.68198Z`
- **Bottom tagline** — centred column, `bottom 2rem` (lg) / `1.5rem`, lg width `40rem`, gap `1rem`:
  a 24px four-point sparkle mark (delay 560ms, from `opacity 0, y 10px, blur(6px)`,
  `{ tension 120, friction 24 }`), then "MOTION, IDENTITY, AND IMMERSIVE WEB EXPERIENCES. THE
  TOOLS SERVE US — THE VISION IS OURS." (uppercase, centred, `0.875rem` / lg `1rem`, line-height
  1.3) rising **line by line**: from `opacity 0, y 18px, blur(8px)`, stagger 130ms,
  `{ tension 120, friction 26 }`, delay 660ms. Sparkle path (viewBox `0 0 24 24`,
  `fill: currentColor`):
  `M12 12C12.0004 12.0007 14.7995 16.827 14.7998 19.3789C14.7998 21.9308 13.5464 24 12 24C10.4536 24 9.2002 21.9308 9.2002 19.3789C9.20049 16.8269 12 12 12 12ZM4.62109 9.2002C7.1731 9.20049 11.9999 11.9999 12 12C12 12 7.17314 14.7995 4.62109 14.7998C2.06916 14.7998 1.38396e-07 13.5464 0 12C6.75952e-08 10.4536 2.06916 9.2002 4.62109 9.2002ZM19.3789 9.2002C21.9308 9.2002 24 10.4536 24 12C24 13.5464 21.9308 14.7998 19.3789 14.7998C16.8273 14.7995 12.0016 12.0009 12 12C12 12 16.8269 9.20049 19.3789 9.2002ZM12 0C13.5464 2.05991e-07 14.7998 2.06916 14.7998 4.62109C14.7995 7.17296 12.0004 11.9993 12 12C12 12 9.20049 7.17314 9.2002 4.62109C9.2002 2.06916 10.4536 0 12 0Z`
- **Custom cursor** — a `6rem` (96px) circle, 1px solid white border, `mix-blend-mode:
  difference`, fixed, `z 60`, `pointer-events: none`. It eases toward the OS cursor each rAF at
  `pos += (target − pos) · 0.15`, hidden until the first mousemove and on `mouseleave`; never
  rendered on `(hover: none)` devices.

## Fixed parameters (bake these in)

```
CAMERA   fov 34 · roll 0.17 rad · orbit pivot (0, ·, 1.05) radius 4.9 height 1.4
         azimuth0 1.591 speed 0.24 rad/s · lookAt (0, 1.7, 1.05)
         pointerOrbit: azimuth ±0.5 rad, height ±0.75, ease 0.05
CONTENT  group (0, 0.95, 1.05) rotY 0.39 scale 1
MONK     (0,0,0) rotY 0.35
RINGS    color/emissive #ffe3b3 · intensity 3.0 (both toruses) · roughness 0.4 metalness 0.2
DEBRIS   counts 300/200/130 · scale 0.16–0.7 · emissive #5c6479 × 0.22 · seed 1337
         spiral: turns 6 radius 3.6 ±1.7 y −4.5+12 ±0.7 angle ±0.5 zCenter −1.6
         field spin 0.04 rad/s · self-spin ±0.45 × 4 · repel r 2.4 s 0.9 ω 3.2→1.7 ζ 0.42
LIGHTS   ambient 0.2 · hemi #aebbdd/#080910 0.52
         key spot #f3ede2 (0.4,8.6,1.4)→(0,0.1,0) i132 a0.66 p1 d26 decay1.45
         fill point #8593bb (2.4,2.2,5.4) i16 d18 decay2 · glow nub #fff6e6 r0.22 (0.3,6.2,0.4)
FOG      #15161f 8→34 · void #05060a
BACKDROP top #2b2b31 bottom #050507 glow #000000 ×0.65 dir (0,0.9,0.25) sharp 4.5
RAYS     #fff8eb op 0.31 · plane 1.5×13 at (0,7.6,1.05)
DUST     4200/2400/1400 · #eaf1ff op 0.85 · uSize 0.12 (clamp 2–7px) · box r7 h11 @ (0,1,1.05)
         drift 0.05 · seed 99
POST     bloom 0.66 (mobile ×0.5) thresh 0.82 smooth 0.3 · vignette 0.26/1 · grain 0.21 overlay
         dispersion 0.01 A #ffffff B #b0967d
TONE     ACESFilmic, exposure 0.98
LOADER   delay 150 · count 1400 · fill 700 · exit 550 · failsafe 9000 · square min(380, 70vmin)
TIERS    mobile <768 or coarse · tablet <1180 · fps 30/45/uncapped · MSAA 0/2/4
```

## Assets

Load these exact URLs (Draco-compressed glTF; use the DRACOLoader configured above):

- Monk + rings: https://storage.getlayers.ai/assets/lumea-e7c8ac2bce/monk.glb
- Debris rocks: https://storage.getlayers.ai/assets/lumea-e7c8ac2bce/rocks-lite.glb

(The fonts load from Google Fonts / Fontshare — see "Page shell & libraries".)

If either model fails to load, show a small visible notice over the void (do not leave a silent
blank canvas), and still reveal the page via the preloader failsafe.
[/hidecontent]
← 返回首页