agentleFS
lizliz404lizliz404/agent-skills/skills/webgl-threejs-background-animation/SKILL.md

webgl-threejs-background-animation

Skill0 starsChanged 26 days agoMITView on GitHub

用 Three.js 做融入页面背景的 WebGL 3D 动画与游戏内动画工艺——批处理 LineSegments/Points、config 驱动材质、双材质溶解、轨道相机视差、缓动与 game feel、视觉杠杆、性能预算、零 GPU 离屏休眠。触发词:做个 3D 背景、landing page 动效、WebGL hero、网页 3D 动画、Three.js 背景、游戏动画、粒子反馈、相机平滑、game feel、WebGL background animation、3D hero section、背景动画、融入背景的动画。Use when building decorative animated 3D backgrounds or in-game WebGL motion — batched LineSegments/Points, TUNING + CATEGORIES, dual-material dissolve, orbital/FPV camera, frame-rate-independent damping, juice/secondary motion, fog/halo levers, full lifecycle hygiene.

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 5 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈5

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Default NO: when most of these are true (Acriva / 融销通 2026-07-21):

    line 67
  2. Very specific:

    Prefer: CSS paper texture / light noise / vignette (effort S) over Three.js.

    line 74
  3. Very specific:

    Default YES / MAYBE: only when: user will iterate on the animation, hero lacks product proof, brand allows technical craft as signal, and …

    line 76
  4. Very specific:

    When Liz asks “能不能用 Three.js skill?”, answer with this decision table first — do not auto-implement.

    line 78
  5. Very specific:

    Three.js wins when the user will iterate: Ask: "Will you modify composition/colors/behavior in 3–6 months?" If yes → Three.js.

    line 92
  6. Very specific:

    Every tunable number lives in one TUNING object.

    line 116
  7. Distinctive:

    Follow damp λ typically 5–12.

    line 187
  8. Distinctive:

    Never accumulate uncapped FOV/shake — decay toward 0 with exp.

    line 254
  9. Distinctive:

    When the product *is* a game (or has a playable WebGL scene), keep the same config + lifecycle spine, then layer:

    line 260
  10. Distinctive:

    Only lower DPR when fill-rate bound.

    line 382
  11. Very specific:

    When referencing the skill's reference implementation path, explicitly say "the skill documents this at <path>" — cite the skill as …

    line 444

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

WebGL Three.js Background Animation & Motion Craft

改名历史:lightweight-three-hero → threejs-landing-background → webgl-threejs-background-animation。本质:Three.js + 融入页面(或游戏世界)的动画,不是画框里的展品。

2026-08 升级: 从「landing 背景专用」扩展为「Three.js WebGL 动画工艺」——覆盖游戏内动画、视觉杠杆、性能预算、缓动插值。目标:低成本换高感知,不是堆 GPU。

Cookbook for animated WebGL that doesn't punish the GPU. Core pattern: batched geometry → config-driven materials → dual-material crossfade / juice feedback → interaction-driven progress → lifecycle gate. Same spine for decorative backgrounds and lightweight in-game worlds.

Design Preferences(美学准则,不可违反)

动画不是画框里的画,而是融入页面/世界的背景。 具体:

  1. 不要矩形画框:不用 border-radius + overflow: clip + 固定宽高比把动画框成"挂在墙上的展品"。
  2. 要边缘渐隐融入:radial-gradient mask、vignette、或 scene fog,让它"长"在页面上。
  3. 背景层级:WebGL canvas 默认 position: fixed; inset: 0; z-index: -1(游戏全屏例外:canvas 即视口)。
  4. 内容可读性:文字区要有 backdrop(blur / 半透明),但不要完全遮住动画。
  5. 尺寸:默认全视口;装饰件可跟容器,仍要边缘淡出。

反模式(禁止):

/* ❌ 画框感 */
.animation-frame {
  width: clamp(260px, 42vw, 360px);
  aspect-ratio: 1 / 1;
  border-radius: 42% 58% 53% 47% / 48% 44% 56% 52%;
  overflow: clip;
}

正模式(推荐):

/* ✅ 融入背景 */
.animation-background {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  mask-image: radial-gradient(ellipse at center, black 40%, transparent 85%);
}

Trigger phrases (中英文都能触发):

  • "做个 3D 背景" / "加个 WebGL 动效" / "landing page 3D 动画"
  • "Three.js background" / "WebGL hero animation" / "3D hero section"
  • "游戏动画优化" / "game feel" / "粒子反馈" / "相机平滑"
  • "hero 3D" / "landing 3D" / "网页 3D 背景"

Core Thesis

A decorative (or ambient game) component that carries full-application-level runtime burden has failed its job. The highest virtue is not "impressive" — it is light, quiet, high-perceived quality. Fix the burden first; add narrative/juice only after the base is cheap. Prefer visual levers (fog, halo, easing, secondary motion) over post-processing and custom pipelines.

Decision Framework: When NOT to add Three.js (conversion landings)

This skill is strong for controllable decorative backgrounds and lightweight game worlds. It is not the default upgrade for every landing.

Default NO when most of these are true (Acriva / 融销通 2026-07-21):

  1. Audience trusts status, money amounts, seats, proof — not SF 3D atmosphere.
  2. Hero already has product proof UI (device mocks, desk windows, interactive demos).
  3. Brand DESIGN forbids cold SaaS / neon / glass; paper-wood desk language wins.
  4. Job is P0 SEO/conversion; WebGL adds bundle + GPU lifecycle without changing the buy path.

Prefer: CSS paper texture / light noise / vignette (effort S) over Three.js.

Default YES / MAYBE only when: user will iterate on the animation, hero lacks product proof, brand allows technical craft as signal, and SEO/meta/CTA are already solid — or the product is a WebGL game/world.

When Liz asks “能不能用 Three.js skill?”, answer with this decision table first — do not auto-implement.

Decision Framework: When to Replace a Working Canvas 2D Animation

The deciding question is controllability over time. Canvas 2D pixel animations hit a controllability ceiling fast:

Canvas 2D pain signalWhy it matters
Hardcoded x/y coordinates per frameAny composition change = hand-tuning dozens of values
Single-file IIFE with coupled state750+ lines means every edit risks breaking unrelated parts
No config layerColors, speed, composition are scattered magic numbers
Pixel-grid constraint (e.g. 48×48)Complex compositions literally cannot fit
No lifecycle managementKeeps rendering off-screen; battery drain on mobile

Three.js wins when the user will iterate. Ask: "Will you modify composition/colors/behavior in 3–6 months?" If yes → Three.js. If truly static one-shot → Canvas 2D may still be fine.

Brand continuity is not a blocker. Rebuild brand characters in Three.js geometry. Migration: move old Canvas 2D to a secondary slot; don't delete.


The System(背景动画核心架构)

Config Layer    TUNING (magic numbers) + CATEGORIES (visual weights)
       ↓
Geometry Layer  Float32 arrays pushed by domain-specific helpers
       ↓
Material Layer  One dashed + one solid material per category (dual for dissolve)
       ↓
Scene Layer     GridHelper ×2 + zone fills + fog + camera
       ↓
Animation Layer Helix camera + parallax + progress crossfade (+ juice if game)
       ↓
Lifecycle Layer IntersectionObserver + visibilitychange + ResizeObserver + full dispose

1. Config: Single Source of Truth

Every tunable number lives in one TUNING object. Every visual category lives in one CATEGORIES array. Materials and per-frame updates iterate the same array.

const TUNING = { camera: { radius: 13, baseY: 6.5 }, spin: { base: 0.06 }, /* ... */ } as const

const CATEGORIES: CategorySpec[] = [
  { key: 'primary',   color: 0x..., solidColor: 0x..., weight: 0.72, dash: {...}, phase: 0 },
  { key: 'secondary', color: 0x..., solidColor: 0x..., weight: 0.50, dash: {...}, phase: 0.6 },
  { key: 'detail',    color: 0x..., solidColor: 0x..., weight: 0.34, dash: {...}, phase: 1.3 },
  { key: 'accent',    color: 0x..., solidColor: 0x..., weight: 0.66, dash: {...}, phase: 2.1 },
]

Adding a 5th visual weight = one entry + one segment array. Nothing else changes.

2. Geometry: Batched Float Arrays

Push all segments into typed arrays → one LineSegments per category (not one Line per primitive).

function pushSeg(seg: number[], ax: number, az: number, bx: number, bz: number, y = FLOOR) {
  seg.push(ax, y, az, bx, y, bz)  // 6 floats per segment pair
}

function makeLineSegments(seg: number[], material: LineDashedMaterial | LineBasicMaterial, renderOrder = 0) {
  const geo = new BufferGeometry()
  geo.setAttribute('position', new Float32BufferAttribute(seg, 3))
  const line = new LineSegments(geo, material)
  if (material instanceof LineDashedMaterial) line.computeLineDistances()
  line.renderOrder = renderOrder
  return line
}

Draw-call choice boundary (2026):

Primitive needPreferAvoid when
Many static/semi-static lines1× LineSegments / categoryPer-segment Line
Many particles / points1× Points + shared attrsN× Mesh sprites
Many identical meshes, per-instance transformInstancedMesh (count ≥ ~100–200 meaningful)InstancedMesh for <50 trivial boxes
Static multi-color mesh pilemergeGeometries + vertexColorsMerge entire terrain (kills frustum culling)

Desktop target: <100 draw calls. Mobile: <50. Monitor renderer.info.render.calls.

3. Materials: Dual for Dissolve

MaterialRoleprogress=0progress=1
LineDashedMaterialSketchFull opacityFaded
LineBasicMaterialSolidZero opacityFull opacity
cat.dashMat.opacity  = pulse * (1 - progressEffect * 0.7)
cat.solidMat.opacity = pulse * progressEffect * 0.85
cat.dashMat.gapSize  = cat.dash.gapSize * (1 - progressEffect * 0.6)

Progress: hover/move + click/tap + idle ambient auto-ramp. Smooth with 1 - exp(-smoothK * dt). Cap at PROGRESS_MAX (e.g. 0.7).

4. Camera: Helix + Parallax + Portrait

camera.x = radius * cos(angle) + easedMouseX * parallaxAmount
camera.z = radius * sin(angle)
camera.y = (baseY - cameraDrop * progress) + amp * sin(angle * 0.5) + easedMouseY * parallaxAmount
  • Azimuth: (spinBase + spinWobble * sin(angle * 0.7)) * dt — frame-rate independent
  • Parallax: 1 - exp(-parallaxK * dt) — identical at 60/120/144 Hz
  • Portrait: when aspect < 1.3, boost FOV and orbital radius
  • Follow damp λ typically 5–12; never use raw lerp factor > 0.15 on variable dt

5. Lifecycle: Zero GPU When Invisible

GateMechanismEffect
Off-screenIntersectionObserver (threshold 0)Cancel rAF → zero GPU
Hidden tabvisibilitychangePause; resume only if in view; reset clock (no physics spike)
ResizeResizeObserver + window.resizeRecalc aspect
Reduced motionprefers-reduced-motion: reduceStatic frame / no shake / no decorative drift
Context losswebglcontextlost → dispose / reload promptFall back to CSS
UnmountFull dispose()traverse geo/mats/textures → forceContextLoss()

Also: decorative backgrounds: powerPreference: 'low-power', depth: false, stencil: false. Games may use high-performance on desktop.

6. CSS Fallback

Canvas alpha-composited over CSS gradient + grid. If WebGL unavailable, component returns null and CSS shows through.


7. 缓动与插值(Easing & Interpolation)

7.1 帧率无关平滑(强制)

// ❌ Frame-coupled — 2× faster on 120Hz
eased += (target - eased) * (dt * K)

// ✅ Exponential decay — identical feel at any Hz
eased += (target - eased) * (1 - Math.exp(-λ * dt))
Use caseλ 建议Notes
UI / opacity crossfade2–4Soft settle
Camera follow / parallax5–12Sweet spot; >15 feels snappy/harsh
FOV kick return8–12Punch then soft
Physics-facing (collision lane)—Don't damp position that collision depends on

Shared helper (put in TUNING consumers, not magic):

export const damp = (current: number, target: number, λ: number, dt: number) =>
  current + (target - current) * (1 - Math.exp(-λ * dt))

7.2 常用缓动曲线

CurveWhenExample
ease-out (default UI)User-initiated feedbackScore pulse, button settle, spawn fade-in — 200–300ms
ease-in-outCamera / mode blendsViewRig switch; use smoothstep or smootherstep
ease-inRare — exits that accelerateDespawn whoosh
linearConstant-speed world scrollPipe advance, ground UV (physics authority)
spring / overshootJuice momentsScore pop, flap squash — keep amplitude tiny
const smoothstep = (x: number) => { const t = Math.min(1, Math.max(0, x)); return t * t * (3 - 2 * t) }
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3)

7.3 规则

  1. Physics timesteps always * dt (never per-frame constants).
  2. Visual smoothing always 1 - exp(-λ·dt) or eased progress 0→1 over seconds.
  3. Interruptible tweens: kill previous before starting new (killTweensOf if GSAP; else overwrite local progress).
  4. Never accumulate uncapped FOV/shake — decay toward 0 with exp.

8. 游戏内动画(In-Game Motion Craft)

When the product is a game (or has a playable WebGL scene), keep the same config + lifecycle spine, then layer:

8.1 游戏循环与帧率无关运动

rAF → dt = min(clock.getDelta(), 0.05) → physics(dt) → visuals(dt) → render
  • Cap dt (e.g. 0.05) so tab-return doesn't teleport.
  • On visibilitychange hidden→visible: reset clock / ignore one huge dt.
  • Separate authority (collision, score) from presentation (squash, halo, camera tilt).

8.2 物理手感平滑(damping / 弹簧)

LayerDoDon't
Core hop / gravity / speedKeep instant / authority numbersSoften flap vy "for feel"
Camera pitch/roll from vydamp λ≈8–12Snap rotation to vy
Mesh squash / wing flapshort envelope 80–200msChange hitbox with squash
Death trauma shaketrauma 0→1 then decayContinuous Perlin camera noise in FPV

8.3 程序化世界(天空 / 云 / 山 / 草 / 路径)

ElementPreferred techniqueCost note
SkyBackSide sphere + one-time CanvasTexture gradient OR gradient shaderNever per-frame canvas redraw
SunSoft radial CanvasTexture billboard, fog: falseTiny
HillsSeamless sine-cycle CanvasTexture + UV scroll ×0.2–0.5Parallax depth free
GrassTiling CanvasTexture; side shade as separate world-space overlayDon't bake edge into repeating tile
PathNon-tiling-X strip textureScroll V with ground
FogFog / FogExp2; scene.background === fog.colorNear-zero GPU
CloudsFew flattened sphere groups, layered speed/opacityPrefer opacity layers over volumetric
Drift / dustShared geo+mat tiny meshes or one PointsKeep clear of play corridor

Textures: generate once; dynamic HUD only → reuse canvas + needsUpdate = true.

8.4 粒子系统(轻量)

ScaleApproach
≤ ~50 feedback bits (death burst)Mesh pool + gravity + ease-out scale; recycle
~100–5k ambientSingle Points + PointsMaterial; prefer shader uTime if CPU update hurts
>5kGPU sim / don't for decorative games

Rules: preallocate; depthWrite: false; AdditiveBlending only when it helps; never new Float32Array every frame; gate with reduced-motion.

8.5 相机 Rig 与视角混合

ViewSample { y, vy, dt, t, shake, score } → ViewRig.update(cam, sample)
  • Game never sets camera directly; rig owns pose.
  • Mode blend: capture from/to pos+quat, smoothstep/slerp over blendSec.
  • FPV: position exact (collision lane); only damp rotation.
  • Chase/side: damp follow; look-at target ahead.
  • Motion sickness: prefers-reduced-motion → force plain FPV; no shake; no shifter.
  • Dynamic FOV: kick on flap then exp decay; portrait FOV boost when aspect < 1.3.

8.6 Game Feel 原则(Juice)

Priority = perceived impact ÷ code cost (GDC "Juice it or lose it"):

PrincipleCheap implementationBudget
AnticipationWarn flash / tick 0.3s before view switchDOM + SFX
Follow-throughFOV kick + wing settle after flapTUNING numbers
Secondary motionIdle bob, cloud Y bob, halo breathe, wing flapsin / damp
Squash & stretchScale Y 0.88→1.08 on flap/land envelope2–3 frames…~150ms
Timing / rhythmEase-out UI 200–300ms; hit-stop 30–50ms optionalDon't stall physics long
Particles10–24 pooled cubes on death; optional score sparkBound pool
Screen shakeTrauma decay, amp in TUNING, skip if reduced-motionradians ≤ ~0.08

Hit recipe (portable): 2–3 frame squash + optional 50ms hit-stop + ~10 particles → huge feel, <30 lines.


9. 视觉杠杆(Visual Levers — 低成本高感知)

按「感知收益 ÷ 实现成本」排序。优先做表上半;后处理 / 自定义管线默认不做。

#LeverCostPerceived gainHow
1Fog + matching background1–3 linesDepth, melt edgesFog(color, near, far); color === scene.background
2Edge fade / vignetteCSS or soft fogNon-frame blendradial mask / fog far
3Exponential damping1 helperPro motionreplace linear lerp
4Idle / secondary motionsin 0.2–0.5 Hz, 2–3%Alive worldbob, breathe, drift Y
5Colour hierarchypalette stopsReadable layersnear saturated, far desat + fog
6Halo / rim (not bloom)BasicMaterial ring / emissive pulseAffordance + juicecollision ring; approach emissive
7Parallax layersUV ×0.2–0.5Depth freehills/clouds/ground speeds
8Procedural detail onceCanvasTexture load-timeCraft without assetsgrass/hills/sky
9Tone / exposureACES 0.8–1.2 if usedMoodgames only; backgrounds often skip
10Full-screen bloom / DOFEffectComposerOften muddyTrap — avoid for line/simple scenes

Halo: prefer mesh ring / emissiveIntensity pulse / view·normal rim — not default EffectComposer bloom.


10. 性能预算与测量(Budgets & Measurement)

10.1 移动端预算

BudgetDesktopMobile
Target FPS6030–60 (thermal reality)
Draw calls<100<50
pixelRatio capmin(dpr, 2) lines: 1.25–1.5min(dpr, 1) or 1.5 max
Shadowsoptionaloff
Textures1024–2048512–1024
powerPreferencehigh-performance (game)low-power (bg) / game case-by-case
antialiasok if budgetoff on low tier

3× DPR ≈ 9× pixels vs 1× — always cap.

10.2 pixelRatio 策略

renderer.setPixelRatio(Math.min(devicePixelRatio, TUNING.maxPixelRatio))
  • Line-only decorative: cap 1.5 or 1.25 (lines are 1px GL_LINES).
  • Adaptive ladder (EMA dt): e.g. 2 → 1.5 → 1 with hysteresis (drop <45fps, recover >55fps) + cooldown ≥6s.
  • Only lower DPR when fill-rate bound; verify FPS rises after each step.

10.3 Draw-call 边界

见 §2 表。Shared geometry/material singleton + Map for color variants. Prefill InstancedMesh capacity; runtime only shrink visible count.

10.4 FPS 监控与自适应降级

开发期:renderer.info.render.calls / memory.geometries / memory.textures.

运行期:EMA of dt → quality ladder (pixelRatio → disable drift → reduce cloud count). Optional boot micro-benchmark for initial tier. Dirty-flag render for static backgrounds (games loops still render every frame while playing).

10.5 内存

Unload: scene.traverse → dispose geometry, materials, all map textures; renderer.dispose(); forceContextLoss() when tearing down; Chrome ~8 contexts/page.


11. prefers-reduced-motion 与无障碍

PreferenceRequired behavior
prefers-reduced-motion: reduceNo camera shake; no decorative scroll/drift; no shifter cams; static or minimal idle; UI flashes softened
Game logicStill playable — physics/input remain
Background-onlyRender one static frame at idleTarget
AnnounceDon't rely on motion alone for scoring cues — keep SFX / HUD text
const reduceMotion =
  typeof matchMedia === 'function' &&
  matchMedia('(prefers-reduced-motion: reduce)').matches

Pitfalls

Collected from real audits + 2026 research. Real bugs, not hypotheticals.

  1. CDN dynamic import of Three.js — kills tree-shaking/types/CSP. Always import { … } from 'three'.
  2. Frame-rate coupled lerp — use 1 - exp(-K * dt).
  3. rAF when off-screen / tab hidden — IntersectionObserver + visibilitychange; games also reset clock.
  4. Incomplete dispose — traverse geo/mats/textures; forceContextLoss().
  5. useEffect([], …) without StrictMode guard — initedRef in React.
  6. setPixelRatio(min(dpr,2)) on line-only scenes — cap 1.25–1.5.
  7. alpha: true when bg controllable — prefer alpha: false + clear color.
  8. No webglcontextlost handler — mobile freezes; provide restore/reload UX (transient overlay, not permanent brick).
  9. Missing renderOrder for layered lines — without depth test.
  10. Assuming "keep Canvas 2D" is safe — controllability over time wins.
  11. Per-frame new Texture() / new Float32Array for canvas/particles — reuse + needsUpdate.
  12. scene.background ≠ fog color — harsh silhouette seams.
  13. Baking corridor edge into repeating grass tile — zebra stripes × N.
  14. Damping collision-critical positions — FPV eye must match hit lane.
  15. Permanent context-lost overlay — remove on webglcontextrestored.
  16. InstancedMesh without computeBoundingSphere after matrix updates — culling/raycast break.
  17. Merging entire terrain into one mesh — disables useful frustum culling.
  18. Bloom by default — 3–5ms+; use selective halo instead.
  19. Pause by setting ω=0 but keeping rAF — still burns GPU; cancelAnimationFrame.
  20. Softening core flap/gravity for "feel" — break determinism; juice the presentation layer.

Communication pitfall (agent-side)

When referencing the skill's reference implementation path, explicitly say "the skill documents this at <path>" — cite the skill as authority.

Over-optimization traps(2026 / three@0.185+ 校准)

TrapWhy net negative
Line2 / LineMaterial for decorative bg+15KB; only for UI-critical viz
Custom shader replacing LineDashedMaterialALU win tiny; maintainability loss
InstancedMesh for < ~100 simple objectsBatched LineSegments/Points simpler
EffectComposer (bloom/FXAA/DOF)Line/simple scenes don't benefit; eats ms
R3F for single decorative componentReconciler overhead; OK for 3+ 3D islands
WebGPU backend for decorative/simple gamesEvolving; switching cost ≫ benefit in 2026H1
OffscreenCanvas + Worker for mouse-parallax bgComms overhead > savings
Volumetric clouds / raymarch for Flappy-scaleMesh pancakes + fog win
BatchedMesh everywhere "because new"Use when transparency sort actually needed
detect-gpu + 5 quality tiers before shipping juiceShip fog/easing first; tier later

Adaptation Guide

  1. Design 3–5 visual categories (colors, weights, dash).
  2. Domain geometry helpers: push*(seg: number[], …).
  3. Organize into zones.
  4. FOG_COLOR = page mid-tone; match scene.background.
  5. Tune camera radius/baseY/amp/spin.
  6. Set idleTarget so mobile sees dissolve.
  7. For games: add ViewRig seam, juice envelopes in TUNING, never scatter magic numbers.

Replacing Canvas 2D

Preserve old animation in a secondary location; primary slot gets config-driven Three.js.

Site adaptation

See references/site-adaptation-recipe.md: metaphor → palette from CSS → dark-mode category builder → geometry helpers → camera for compact scenes.


Reference Implementation

Decorative background: bundled examples/HeroCanvas.tsx — the lizliz.xyz "Paper Ink Garden" full-page background (862 lines, three@0.185)

Engineering audit: keep your own review notes in your project's docs/ — the audit that shaped §9 Visual Levers & §10 Budgets is summarized inside those sections

Game-shaped sibling: an FPV flappy-style game — TUNING + ViewRig + scenery CanvasTextures + reduced-motion gates (see §8 In-Game Motion Craft)


Build Checklist

Background / decorative

  • 3–5 visual categories with clear weight hierarchy
  • One TUNING object — no stray magic numbers
  • Domain geometry helpers (append to number[])
  • One segment array per category + dashed/solid pair
  • CSS fallback; null on WebGL failure
  • IntersectionObserver + visibilitychange gate
  • Full dispose + forceContextLoss()
  • StrictMode initedRef (React)
  • Reduced-motion static frame
  • Portrait FOV/radius boost
  • Idle progress ramp
  • powerPreference appropriate; stencil off if unused
  • Fog color === background
  • All smoothers use 1 - exp(-λ·dt)
  • No bloom / no CDN whole three / no R3F-for-one-scene

Game / interactive world (additional)

  • dt-clamped game loop; clock reset on tab resume
  • Physics authority vs presentation split
  • ViewRig owns camera; collision lane undamped in FPV
  • Juice: squash / halo / burst / FOV kick in TUNING
  • Particle/mesh pools bounded; reduced-motion skips
  • Procedural textures generated once
  • Parallax layer speeds; corridor kept readable
  • pixelRatio cap + optional adaptive ladder
  • renderer.info sanity in dev
  • Skin/theme changes don't allocate new geos

Extensions & Future Directions

Priority = perceptual impact ÷ implementation cost.

High priority

ExtensionCostWhy
Depth fog3 linesDistant fade; edge melt
Grid / world drift~4 linesBreathing illusion
Dark mode palette~15 linesprefers-color-scheme / theme attr
Exp damp everywhere1 helperKill frame-coupled feel
Secondary idle motion~10 linesAlive without assets
Approach / score halo~15 linesFairness + juice

Medium priority

ExtensionCostWhy
Cursor-as-light opacity boost~30 linesFlashlight over blueprint
Adaptive quality EMA ladder~20 linesProtect low-end
Isometric / Orthographic toggle~50 linesNarrative punch
Score particles (Points pool)~40 linesJuice without bloom
Skin color crossfade (damp hex)~20 linesTheme polish

Low priority / don't do yet

ExtensionWhy not now
GPU particle FBO simsSeparate architecture
Scroll-scrub timelineCouples lifecycle hard
Voronoi / heavy procedural fieldsDomain-specific companion
WebGPU backendRevisit when stable for prod decorative
Full EffectComposer stackCost/clarity fail for this skill's targets

Research Top 20 → Skill Mapping(吸收清单)

#PracticeSkill home
1Fog + matching background§5 Scene, §9 Lever #1, Checklist
2Squash + hit-stop + particles§8.6 Game Feel
3pixelRatio cap§10.2
4InstancedMesh / merge boundary§2 table
5lerp via 1-exp(-λ·dt)§7.1
6prefers-reduced-motion§11, Lifecycle
7Dirty-flag static render§10.4
8Points + uTime particles§8.4
9UI ease-out 200–300ms§7.2
10BackSide sky / one-shot gradient§8.3
11Camera damp λ 5–12§4, §7.1, §8.5
12Mobile: no shadow, half tex, low-power§10.1
13Halo not bloom§9, Over-opt traps
14traverse dispose§5, Pitfall 4
15CanvasTexture reuse§8.3, Pitfall 11
16Monitor draw calls§10.4
17Interruptible tweens§7.3
18Parallax UV 0.2–0.5§8.3, §9
19Boot quality tier optional§10.4, Low priority
20Shared geo/mat singleton§2, §10.3

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.