update how it works

This commit is contained in:
2026-06-08 22:21:42 +05:30
parent 3d53f82e7b
commit 0ef51540e9
9 changed files with 523 additions and 245 deletions

View File

@@ -15,11 +15,21 @@ export default function CameraRig() {
useFrame((state, delta) => {
const { camera } = state
// maath's easing.damp3 divides by delta internally; a delta of 0 (coincident
// or first frames) yields NaN that poisons the damper and would push the
// camera to NaN — blanking the whole scene. Clamp delta to a safe range.
const dt = Number.isFinite(delta) && delta > 0 ? Math.min(delta, 0.1) : 1 / 60
// Smoothly damp the camera position towards the target position
easing.damp3(camera.position, targetPosition, 0.35, delta)
easing.damp3(camera.position, targetPosition, 0.35, dt)
// Smoothly damp the camera focus target (lookAt)
easing.damp3(currentLookAt.current, lookAtTarget, 0.25, delta)
easing.damp3(currentLookAt.current, lookAtTarget, 0.25, dt)
// Defensive recovery: if anything upstream produced a non-finite value, snap
// back to the target so the camera never gets stuck at NaN (black screen).
if (!Number.isFinite(camera.position.x)) camera.position.copy(targetPosition)
if (!Number.isFinite(currentLookAt.current.x)) currentLookAt.current.copy(lookAtTarget)
// Apply lookAt orientation using the interpolated target vector
camera.lookAt(currentLookAt.current)

View File

@@ -2,7 +2,6 @@ import React, { useRef, useEffect } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
import { Environment, SoftShadows } from '@react-three/drei'
import * as THREE from 'three'
import { EffectComposer, Bloom, Vignette } from '@react-three/postprocessing'
import { Model as SceneModel } from '../models/Scene3D'
import CameraRig from './CameraRig'
import TruckAnimation from './TruckAnimation'
@@ -127,22 +126,16 @@ export default React.memo(function Experience({ dashboardRefs, wheelRefs, truckR
{/* Dynamic camera rig with damping and target interpolation */}
<CameraRig />
{/* Post-processing Bloom + Vignette only.
The original Vite code added SSAO with a NormalPass, but on this heavy
scene (32MB GLB, ~500 meshes, SoftShadows) the extra full-scene normal
render exhausts the WebGL context and it is lost (blank canvas). The
site's other R3F canvases (e.g. StrategyCanvas) use a Bloom-only
composer for the same reason; Bloom + the screen-space Vignette keep the
cinematic look without the SSAO normal pass. */}
<EffectComposer multisampling={2}>
<Bloom
intensity={0.2}
luminanceThreshold={0.95}
luminanceSmoothing={0.05}
mipmapBlur
/>
<Vignette eskil={false} offset={0.1} darkness={0.4} />
</EffectComposer>
{/* Post-processing (EffectComposer/Bloom/Vignette) intentionally omitted.
@react-three/postprocessing's EffectComposer reads
`renderer.getContextAttributes().alpha` while initializing its buffers;
under Next dev's React StrictMode the canvas's WebGL context is torn
down and re-created, so that read hits a null context and throws
"Cannot read properties of null (reading 'alpha')", crashing the whole
scene. Dropping the composer renders the scene directly (lighting +
shadows + environment carry the look). To re-add Bloom later, set
`reactStrictMode: false` in next.config.ts and restore a Bloom-only
composer. */}
</Canvas>
</div>
)

View File

@@ -38,6 +38,14 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
useFrame((state, delta) => {
if (!truckRef.current) return
// r3f can emit delta === 0 (coincident frames, the first frame, or after a
// long main-thread block while the 32MB scene parses). maath's easing.damp
// divides by delta internally, so a 0 yields NaN/Infinity that poisons the
// damper's stored velocity — and from then on truckPath.getPoint(NaN) throws
// "Cannot read properties of undefined (reading 'x')". Clamp delta to a safe
// positive range before any damping.
const dt = Number.isFinite(delta) && delta > 0 ? Math.min(delta, 0.1) : 1 / 60
// Detect scroll direction changes from the actual page scroll progress
const deltaScroll = scrollProgress - lastScrollProgressRef.current
if (deltaScroll < -0.0001) {
@@ -95,7 +103,15 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
}
// Smoothly damp the 1D progress scalar along the curve path
easing.damp(dampedProgressRef, 'current', truckProgress, 0.30, delta)
easing.damp(dampedProgressRef, 'current', truckProgress, 0.30, dt)
// Defensive: keep the spline parameter a finite value in [0,1]. getPoint(NaN)
// or an out-of-range t reads an undefined curve point and throws.
if (!Number.isFinite(dampedProgressRef.current)) {
dampedProgressRef.current = truckProgress
if (dampedProgressRef.__damp) dampedProgressRef.__damp = {} // clear any poisoned velocity
}
dampedProgressRef.current = THREE.MathUtils.clamp(dampedProgressRef.current, 0, 1)
// Evaluate the 3D position and orientation directly on the spline curve
const position = truckPath.getPoint(dampedProgressRef.current)
@@ -128,7 +144,7 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
}
// Smoothly damp the extra rotation angle directly (prevents pitch/roll glitches or 3D target collapse)
easing.damp(extraRotationRef, 'current', targetExtraRotation, 0.20, delta)
easing.damp(extraRotationRef, 'current', targetExtraRotation, 0.20, dt)
// Apply the yaw pivot around the local vertical axis
truckRef.current.rotateY(extraRotationRef.current)

View File

@@ -15,16 +15,31 @@
here because the site has a fixed header and an ancestor `overflow:hidden`.
============================================================================ */
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');
/* ---- Doormile card design tokens (single system for all four stages) ----
Brand red #c01227, ink #1F1F1F, fonts inherited from the site (Manrope body /
Space Grotesk headings, exposed as CSS vars on <html> by layout.tsx). */
.dm-hiw-3d {
--dm-red: #c01227;
--dm-red-soft: rgba(192, 18, 39, 0.10);
--dm-ink: #1f1f1f;
--dm-body: #4b5563;
--dm-muted: #8a8f98;
--dm-card-bg: rgba(255, 255, 255, 0.86);
--dm-card-border: rgba(15, 23, 42, 0.08);
--dm-card-radius: 18px;
--dm-card-shadow: 0 24px 60px -28px rgba(15, 23, 42, 0.45);
--dm-font-head: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
--dm-font-body: var(--font-manrope), system-ui, -apple-system, sans-serif;
}
/* ---- Section shell + self-managed fixed pin ---- */
.dm-hiw-3d {
position: relative;
width: 100%;
font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
line-height: 1.47;
font-family: var(--dm-font-body);
line-height: 1.5;
font-weight: 400;
color: #1d1d1f;
color: var(--dm-ink);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -117,39 +132,40 @@
transform: scale(1.15);
}
.dm-hiw-3d .side-nav-item.active .side-nav-label {
color: #0071e3;
color: var(--dm-red);
}
.dm-hiw-3d .side-nav-item.active .side-nav-dot {
background-color: #0071e3;
background-color: var(--dm-red);
transform: scale(1.3);
box-shadow: 0 0 8px rgba(0, 113, 227, 0.3);
box-shadow: 0 0 8px rgba(192, 18, 39, 0.35);
}
.dm-hiw-3d .section-close-btn {
margin-top: 20px;
background-color: #0071e3;
margin-top: 18px;
background-color: var(--dm-red);
color: #ffffff;
border: none;
font-family: inherit;
font-family: var(--dm-font-body);
font-size: 12px;
font-weight: 600;
padding: 8px 16px;
border-radius: 18px;
letter-spacing: 0.01em;
padding: 9px 18px;
border-radius: 999px;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 4px 12px rgba(0, 113, 227, 0.15);
transition: background-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease;
box-shadow: 0 8px 18px -8px rgba(192, 18, 39, 0.55);
display: inline-flex;
align-items: center;
justify-content: center;
width: auto;
}
.dm-hiw-3d .section-close-btn:hover {
background-color: #0077ed;
box-shadow: 0 6px 16px rgba(0, 113, 227, 0.3);
background-color: #a30f20;
box-shadow: 0 10px 22px -8px rgba(192, 18, 39, 0.7);
transform: translateY(-1px);
}
.dm-hiw-3d .section-close-btn:active {
transform: translateY(1px);
transform: translateY(0);
}
/* ---- Story stage text panels ---- */
@@ -162,112 +178,136 @@
align-items: center;
}
/* Side placement keeps the moving truck / scene visible in the centre, with the
card vertically centred (top/bottom:0 + margin:auto works with the GSAP reveal,
which only animates translateY/scale). */
.dm-hiw-3d #first-mile-section,
.dm-hiw-3d #mid-mile-section,
.dm-hiw-3d #last-mile-section {
top: 0;
bottom: 0;
height: max-content;
margin-top: auto;
margin-bottom: auto;
}
.dm-hiw-3d #first-mile-section,
.dm-hiw-3d #last-mile-section {
left: 6%;
left: clamp(24px, 5vw, 72px);
}
.dm-hiw-3d #mid-mile-section {
right: 6%;
right: clamp(24px, 5vw, 72px);
}
/* Final ecosystem panel — same card system, centred, a touch wider for its
timeline, and reduced from the old 500px so it no longer blocks the scene. */
.dm-hiw-3d #analytics-section {
left: 50%;
right: auto;
top: 50%;
transform: translate(-50%, -50%) translateY(18px) scale(0.97);
max-width: 500px;
width: 90%;
background: rgba(20, 21, 26, 0.88); /* Deep slate blackboard theme */
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 30px 70px rgba(0, 0, 0, 0.5);
color: #ffffff;
}
.dm-hiw-3d #analytics-section.active {
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
}
.dm-hiw-3d #analytics-section .section-title { color: #ffffff; }
.dm-hiw-3d #analytics-section .section-subtitle { color: #a1a1a6; }
.dm-hiw-3d #analytics-section .step-title { color: #ffffff; }
.dm-hiw-3d #analytics-section .step-description { color: #a1a1a6; }
.dm-hiw-3d #analytics-section .step-line {
background: linear-gradient(to bottom, #0071e3 40%, rgba(255, 255, 255, 0.1) 100%);
width: min(400px, 90vw);
max-height: calc(100vh - 140px);
}
/* ---- Unified card — all four stages share this exact chrome ---- */
.dm-hiw-3d .section-panel {
position: absolute;
max-width: 380px;
padding: 30px;
background: rgba(255, 255, 255, 0.76);
width: min(340px, 31vw);
max-height: calc(100vh - 168px); /* never taller than the viewport */
overflow-y: auto;
padding: 24px 26px 26px;
background: var(--dm-card-bg);
backdrop-filter: blur(0px);
-webkit-backdrop-filter: blur(0px);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 24px;
border: 1px solid var(--dm-card-border);
border-top: 3px solid var(--dm-red); /* brand accent that unifies every card */
border-radius: var(--dm-card-radius);
box-shadow: var(--dm-card-shadow);
opacity: 0;
transform: translateY(18px) scale(0.97);
visibility: hidden;
transition: backdrop-filter 0.9s cubic-bezier(0.16, 1, 0.3, 1),
-webkit-backdrop-filter 0.9s cubic-bezier(0.16, 1, 0.3, 1),
visibility 0.9s;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.03);
pointer-events: none;
box-sizing: border-box;
}
.dm-hiw-3d .section-panel.active {
visibility: visible;
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
backdrop-filter: blur(22px) saturate(140%);
-webkit-backdrop-filter: blur(22px) saturate(140%);
pointer-events: auto;
}
/* Typography — one scale across all cards. !important defeats the page's
Elementor h2/h3 styles (loaded via /css/site.css with their own !important),
which were inflating these headings to ~80px and overflowing the cards. */
.dm-hiw-3d .section-badge {
font-size: 11px;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 1px;
color: #0071e3;
margin-bottom: 8px;
display: inline-block;
font-family: var(--dm-font-body) !important;
font-size: 10.5px !important;
text-transform: uppercase !important;
font-weight: 700 !important;
letter-spacing: 0.14em !important;
color: var(--dm-red) !important;
margin: 0 0 10px !important;
}
.dm-hiw-3d .section-title {
font-size: 26px;
font-weight: 600;
letter-spacing: -0.6px;
color: #1d1d1f;
margin: 0 0 4px 0;
font-family: var(--dm-font-head) !important;
font-size: clamp(18px, 1.45vw, 22px) !important;
font-weight: 700 !important;
line-height: 1.15 !important;
letter-spacing: -0.015em !important;
text-transform: none !important;
color: var(--dm-ink) !important;
margin: 0 0 4px !important;
}
.dm-hiw-3d .section-subtitle {
font-size: 15px;
font-weight: 500;
color: #86868b;
margin: 0 0 14px 0;
font-family: var(--dm-font-body) !important;
font-size: 13px !important;
font-weight: 500 !important;
line-height: 1.35 !important;
letter-spacing: 0 !important;
text-transform: none !important;
color: var(--dm-muted) !important;
margin: 0 0 14px !important;
}
.dm-hiw-3d .section-description {
font-size: 13px;
line-height: 1.5;
color: #515154;
margin-bottom: 20px;
font-family: var(--dm-font-body) !important;
font-size: 13px !important;
font-weight: 400 !important;
line-height: 1.55 !important;
color: var(--dm-body) !important;
margin: 0 0 18px !important;
}
.dm-hiw-3d .section-metrics {
display: flex;
gap: 20px;
border-top: 1px solid rgba(0, 0, 0, 0.05);
gap: 18px;
border-top: 1px solid rgba(15, 23, 42, 0.08);
padding-top: 16px;
}
.dm-hiw-3d .metric-item {
display: flex;
flex-direction: column;
gap: 3px;
flex: 1;
}
.dm-hiw-3d .metric-value {
font-size: 20px;
font-weight: 600;
color: #1d1d1f;
letter-spacing: -0.3px;
font-family: var(--dm-font-head) !important;
font-size: 18px !important;
font-weight: 700 !important;
line-height: 1.1 !important;
letter-spacing: -0.01em !important;
color: var(--dm-ink) !important;
}
.dm-hiw-3d .metric-label {
font-size: 10px;
font-weight: 500;
color: #86868b;
font-family: var(--dm-font-body) !important;
font-size: 9.5px !important;
font-weight: 600 !important;
text-transform: uppercase !important;
letter-spacing: 0.07em !important;
color: var(--dm-muted) !important;
}
.dm-hiw-3d .font-green .metric-value { color: #34c759; }
.dm-hiw-3d .font-green .metric-value { color: #1f9d57 !important; }
/* ---- Animations (keyframes left global; uniquely named) ---- */
@keyframes dmHiwScrollWheel {
@@ -284,12 +324,12 @@
50% { transform: translateX(8px); }
}
/* ---- Workflow steps styling inside the Analytics overlay ---- */
/* ---- Final-panel timeline (inside #analytics-section) ---- */
.dm-hiw-3d .workflow-steps {
display: flex;
flex-direction: column;
gap: 16px;
margin-top: 18px;
gap: 4px;
margin-top: 16px;
}
.dm-hiw-3d .workflow-step { display: flex; gap: 14px; }
.dm-hiw-3d .step-number-container {
@@ -299,37 +339,43 @@
width: 24px;
}
.dm-hiw-3d .step-number {
font-family: var(--dm-font-head);
font-size: 11px;
font-weight: 700;
color: #0071e3;
background: rgba(0, 113, 227, 0.1);
width: 22px;
height: 22px;
color: var(--dm-red);
background: var(--dm-red-soft);
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 113, 227, 0.15);
border: 1px solid rgba(192, 18, 39, 0.2);
flex-shrink: 0;
}
.dm-hiw-3d .step-line {
width: 1px;
width: 2px;
flex-grow: 1;
background: linear-gradient(to bottom, #0071e3 40%, rgba(0, 0, 0, 0.05) 100%);
margin-top: 6px;
min-height: 24px;
background: linear-gradient(to bottom, var(--dm-red) 0%, rgba(192, 18, 39, 0.12) 100%);
margin: 5px 0;
min-height: 16px;
border-radius: 2px;
}
.dm-hiw-3d .step-content { flex-grow: 1; }
.dm-hiw-3d .step-content { flex-grow: 1; padding-bottom: 14px; }
.dm-hiw-3d .step-title {
font-size: 14px;
font-weight: 600;
color: #1d1d1f;
margin: 0 0 2px 0;
font-family: var(--dm-font-head) !important;
font-size: 14px !important;
font-weight: 700 !important;
line-height: 1.2 !important;
color: var(--dm-ink) !important;
margin: 1px 0 3px !important;
}
.dm-hiw-3d .step-description {
font-size: 11.5px;
line-height: 1.45;
color: #6e6e73;
margin: 0;
font-family: var(--dm-font-body) !important;
font-size: 12px !important;
line-height: 1.5 !important;
color: var(--dm-body) !important;
margin: 0 !important;
}
/* ---- Responsive ---- */
@@ -341,11 +387,10 @@
padding-bottom: 50px;
}
.dm-hiw-3d .section-panel {
max-width: 100%;
width: calc(100vw - 40px);
padding: 20px;
border-radius: 18px;
padding: 20px 22px 22px;
border-radius: 16px;
}
/* Tablet/mobile: cards bottom-centred so the truck/scene stays visible above. */
.dm-hiw-3d #first-mile-section,
.dm-hiw-3d #mid-mile-section,
.dm-hiw-3d #last-mile-section,
@@ -353,10 +398,10 @@
left: 50% !important;
right: auto !important;
top: auto !important;
bottom: 60px !important;
bottom: 64px !important;
transform: translateX(-50%) translateY(18px) scale(0.97) !important;
max-width: 380px;
width: calc(100vw - 120px) !important;
width: min(380px, calc(100vw - 40px)) !important;
max-height: calc(100vh - 200px) !important;
}
.dm-hiw-3d #first-mile-section.active,
.dm-hiw-3d #mid-mile-section.active,
@@ -364,7 +409,6 @@
.dm-hiw-3d #analytics-section.active {
transform: translateX(-50%) translateY(0) scale(1) !important;
}
.dm-hiw-3d #analytics-section { background: rgba(20, 21, 26, 0.92); }
.dm-hiw-3d .side-navigation {
bottom: 12px;
top: auto;
@@ -386,34 +430,36 @@
}
@media (max-width: 400px) {
.dm-hiw-3d .section-panel {
padding: 16px !important;
width: calc(100vw - 80px) !important;
bottom: 40px !important;
.dm-hiw-3d .section-panel,
.dm-hiw-3d #analytics-section {
padding: 16px 18px 18px !important;
width: min(340px, calc(100vw - 28px)) !important;
bottom: 56px !important;
}
.dm-hiw-3d .section-badge {
font-size: 9px !important;
margin-bottom: 4px !important;
font-size: 9.5px !important;
margin-bottom: 6px !important;
}
.dm-hiw-3d .section-title {
font-size: 20px !important;
letter-spacing: -0.4px !important;
font-size: 18px !important;
}
.dm-hiw-3d .section-subtitle {
font-size: 13px !important;
margin-bottom: 8px !important;
font-size: 12.5px !important;
margin-bottom: 10px !important;
}
.dm-hiw-3d .section-description {
font-size: 11px !important;
line-height: 1.4 !important;
margin-bottom: 12px !important;
font-size: 12px !important;
line-height: 1.45 !important;
margin-bottom: 14px !important;
}
.dm-hiw-3d .section-metrics {
padding-top: 10px !important;
gap: 12px !important;
padding-top: 12px !important;
gap: 14px !important;
}
.dm-hiw-3d .metric-value { font-size: 15px !important; }
.dm-hiw-3d .metric-label { font-size: 8.5px !important; }
.dm-hiw-3d .metric-value { font-size: 16px !important; }
.dm-hiw-3d .metric-label { font-size: 9px !important; }
.dm-hiw-3d .step-title { font-size: 13px !important; }
.dm-hiw-3d .step-description { font-size: 11.5px !important; }
.dm-hiw-3d .side-navigation {
bottom: 8px !important;
gap: 12px !important;
@@ -426,8 +472,8 @@
.dm-hiw-3d #analytics-section {
left: 50% !important;
right: auto !important;
bottom: 40px !important;
width: calc(100vw - 80px) !important;
bottom: 56px !important;
width: min(340px, calc(100vw - 28px)) !important;
transform: translateX(-50%) translateY(18px) scale(0.97) !important;
}
.dm-hiw-3d #first-mile-section.active,