# Adaptive hero animation performance for local-business sites

Use this pattern when a trades/local-business homepage uses a decorative Three.js/WebGL hero layer and the client asks for smoother loading on weaker hardware.

## Principle

The hero must communicate trust and service clarity before it acts as a motion showpiece. On low-end devices, reduced motion/data-saver, or weak GPUs, a static branded fallback is better than a stuttering WebGL layer.

## Recommended adaptive modes

Implement three modes:

- `full` — normal WebGL for stronger desktop hardware.
- `reduced` — WebGL still runs, but with fewer objects, lower pixel ratio, lower frame rate, no/low particles, and no antialiasing.
- `static` — skip importing Three.js entirely; hide the canvas and use a lightweight CSS fallback such as a subtle grid, material texture, or brand-color glow.

Signals that should force `static` or `reduced`:

- `prefers-reduced-motion: reduce` → `static`
- `navigator.connection.saveData` → `static`
- `navigator.deviceMemory <= 2` or `hardwareConcurrency <= 2` → `static`
- mobile viewport, `deviceMemory <= 4`, or `hardwareConcurrency <= 4` → `reduced`

## Loading strategy

- Gate initialization with `IntersectionObserver` so the heavy import only starts near the hero.
- Use a modest root margin (`~80px`) rather than eager `200px+` if the hero is above the fold and images already carry the visual.
- Schedule actual initialization in `requestIdleCallback` with a timeout fallback; use `globalThis.setTimeout` for TypeScript-safe fallback if `window.setTimeout` narrows incorrectly.
- Set a `data-performance="static|reduced|full"` attribute on the wrapper so CSS can expose a fallback state and QA can inspect the mode.

## WebGL simplification knobs

For `reduced` mode:

- `antialias: false`
- `renderer.setPixelRatio(Math.min(devicePixelRatio, 1))`
- fewer grid lines / plan primitives
- remove or greatly reduce particles
- throttle rendering to ~17–24 fps (`frameBudget` ~42–58ms)
- pause when the canvas is outside viewport or the tab is hidden

For `static` mode:

- do not import `three`
- hide canvas
- use CSS background layers to preserve visual identity

## QA checklist

- Build passes with TypeScript diagnostics.
- In browser console, verify `document.querySelector('.construction-scene')?.getAttribute('data-performance')`.
- Hero text, CTAs, and background remain visible if mode is `static`.
- No console errors.
- Visual QA should verify the fallback does not look like an empty placeholder.

## Pitfall

Do not merely lower opacity or remove animation frames while still importing Three.js on weak devices. The main win is avoiding the heavy import/renderer entirely for `static` mode and reducing geometry/pixel ratio for `reduced` mode.