feat(how-it-works): integrate scroll-driven 3D experience

Migrate the standalone Vite + React Three Fiber experience into the existing
Next.js site as the body of the How It Works page, replacing the Miles3 /
WhyChooseDoormile / TheDoormileWay content sections while preserving the
Elementor hero, global Header/Footer, layout, routing and SEO.

- New self-contained module: src/modules/how-it-works-3d/ (R3F scene, hooks,
  zustand store, animations, curves, constants, utils, scoped CSS). App.jsx →
  Experience3D.jsx; 3d_scene.jsx → models/Scene3D.jsx.
- 32MB GLB moved to public/models/3d_scene_final.glb; useGLTF paths updated.
- Client-only entry via dynamic ssr:false loader (Experience3DLoader).
- Self-managed fixed pin (tall section + absolute stage toggled
  absolute(top)→fixed→absolute(bottom) from ScrollTrigger pin state), mirroring
  the site's StrategySection, since the fixed header + ancestor overflow:hidden
  break CSS sticky / GSAP pin.
- experience.css fully scoped under .dm-hiw-3d to avoid colliding with the
  site's Elementor CSS.
- Global Lenis disabled on /how-it-works; module runs its own tuned Lenis;
  jump-to-section scroll math made spacer-relative.
- Added zustand + maath; ESLint-ignored the ported module.

Rendering fixes (root causes found by driving headless Chrome):
- Bump three 0.171 → 0.184 to match @react-three/fiber@9.6 / drei@10.7 /
  postprocessing@6.39 (0.171 silently failed to render this GLB and caused the
  EffectComposer getContextAttributes().alpha crash). Other 3D routes verified.
- EffectComposer: Bloom + Vignette only. SSAO needs a NormalPass (v3 dropped
  the old `disableNormalPass`), and that extra full-scene pass exhausted the
  WebGL context on this heavy scene.
- Cap Canvas dpr to [1,1.5] to bound framebuffer memory on retina displays.
- Defer Canvas mount via IntersectionObserver (mountScene), matching
  StrategySection, to ease StrictMode/first-render GPU pressure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 19:58:34 +05:30
parent e93785f2b6
commit 3d53f82e7b
37 changed files with 13694 additions and 29 deletions

View File

@@ -0,0 +1,24 @@
import { clamp } from '../utils/helpers'
export const animateDashboard = (bars, pieQuarters, progress) => {
// progress is 0 at scrollProgress = 0.75, and 1 at scrollProgress = 1.0
// Scale bar charts on their Y axis with a staggered effect
bars.forEach((barRef, index) => {
if (barRef.current) {
const delay = index * 0.08
const scaleY = clamp((progress - delay) / 0.5, 0, 1)
// Interpolate scale Y
barRef.current.scale.y = scaleY
}
})
// Rotate pie chart quarters around their local Y axis
pieQuarters.forEach((quarterRef, index) => {
if (quarterRef.current) {
// Rotate based on progress (offset each slice slightly for dynamic feeling)
const rotationSpeed = 2 + index * 0.5
quarterRef.current.rotation.y = -0.709 + progress * Math.PI * 2 * rotationSpeed
}
})
}