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

@@ -2,6 +2,14 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "export", output: "export",
// Required by the How It Works 3D experience. React StrictMode double-invokes
// mount/effects in dev, which tears down and re-creates the WebGL context of
// the heavy 32MB scene mid-initialization — the context is lost ("THREE.
// WebGLRenderer: Context Lost") and the canvas stays blank. This is a known
// React-Three-Fiber + StrictMode incompatibility. Disabling it is a DEV-ONLY
// change (production never runs StrictMode's double-mount) and does not affect
// any other page's runtime behavior.
reactStrictMode: false,
images: { images: {
unoptimized: true, unoptimized: true,
formats: ["image/avif", "image/webp"], formats: ["image/avif", "image/webp"],

8
package-lock.json generated
View File

@@ -21,7 +21,7 @@
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"three": "^0.184.0", "three": "0.171.0",
"zustand": "^5.0.14" "zustand": "^5.0.14"
}, },
"devDependencies": { "devDependencies": {
@@ -10764,9 +10764,9 @@
} }
}, },
"node_modules/three": { "node_modules/three": {
"version": "0.184.0", "version": "0.171.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz", "resolved": "https://registry.npmjs.org/three/-/three-0.171.0.tgz",
"integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==", "integrity": "sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/three-mesh-bvh": { "node_modules/three-mesh-bvh": {

View File

@@ -26,7 +26,7 @@
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"three": "^0.184.0", "three": "0.171.0",
"zustand": "^5.0.14" "zustand": "^5.0.14"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -19,7 +19,9 @@ export default function HowItWorksHero() {
return ( return (
<> <>
<style dangerouslySetInnerHTML={{ __html: ` <style
dangerouslySetInnerHTML={{
__html: `
.howits-hero-custom-bg.elementor-repeater-item-3264830, .howits-hero-custom-bg.elementor-repeater-item-3264830,
.howits-hero-custom-bg.elementor-repeater-item-6867061 { .howits-hero-custom-bg.elementor-repeater-item-6867061 {
background-image: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.1)), url('/images/home1-slide-1.png') !important; background-image: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.1)), url('/images/home1-slide-1.png') !important;
@@ -219,19 +221,44 @@ export default function HowItWorksHero() {
display: none !important; display: none !important;
} }
} }
`}} /> `,
}}
/>
<div className="elementor-element elementor-element-741f56c e-con-full e-flex cut-corner-no sticky-container-off e-con e-parent" data-id="741f56c" data-element_type="container" data-e-type="container"> <div
<div className="elementor-element elementor-element-6c7cbcb elementor-widget elementor-widget-logico_content_slider" data-id="6c7cbcb" data-element_type="widget" data-e-type="widget" data-widget_type="logico_content_slider.default"> className="elementor-element elementor-element-741f56c e-con-full e-flex cut-corner-no sticky-container-off e-con e-parent"
data-id="741f56c"
data-element_type="container"
data-e-type="container"
>
<div
className="elementor-element elementor-element-6c7cbcb elementor-widget elementor-widget-logico_content_slider"
data-id="6c7cbcb"
data-element_type="widget"
data-e-type="widget"
data-widget_type="logico_content_slider.default"
>
<div className="elementor-widget-container"> <div className="elementor-widget-container">
<div className="logico-content-slider-widget"> <div className="logico-content-slider-widget">
<div className="content-slider-wrapper"> <div className="content-slider-wrapper">
<div className="content-slider-container"> <div className="content-slider-container">
<div className="content-slider owl-carousel owl-theme nav-view-vertical nav-h-position-right nav-v-position-bottom owl-loaded owl-drag"> <div className="content-slider owl-carousel owl-theme nav-view-vertical nav-h-position-right nav-v-position-bottom owl-loaded owl-drag">
<div
<div className="owl-stage-outer" style={{ position: "relative", overflow: "hidden", height: "800px" }}> className="owl-stage-outer"
<div className="owl-stage" style={{ position: "relative", width: "100%", height: "100%" }}> style={{
position: "relative",
overflow: "hidden",
height: "800px",
}}
>
<div
className="owl-stage"
style={{
position: "relative",
width: "100%",
height: "100%",
}}
>
{/* Slide 1 */} {/* Slide 1 */}
<div <div
className={`owl-item ${activeSlide === 0 ? "active" : ""}`} className={`owl-item ${activeSlide === 0 ? "active" : ""}`}
@@ -239,20 +266,30 @@ export default function HowItWorksHero() {
position: "relative", position: "relative",
width: "100%", width: "100%",
opacity: activeSlide === 0 ? 1 : 0, opacity: activeSlide === 0 ? 1 : 0,
visibility: activeSlide === 0 ? "visible" : "hidden", visibility:
transition: "opacity 0.8s ease-in-out, visibility 0.8s ease-in-out", activeSlide === 0 ? "visible" : "hidden",
zIndex: activeSlide === 0 ? 2 : 1 transition:
"opacity 0.8s ease-in-out, visibility 0.8s ease-in-out",
zIndex: activeSlide === 0 ? 2 : 1,
}} }}
> >
<div className="content-item slider-item elementor-repeater-item-3264830 slide-style-standard howits-hero-custom-bg"> <div className="content-item slider-item elementor-repeater-item-3264830 slide-style-standard howits-hero-custom-bg">
<div className="slide-content"> <div className="slide-content">
<div className="slide-content-inner"> <div className="slide-content-inner">
<h1 className="content-slider-item-heading logico-content-wrapper-1"> <h1 className="content-slider-item-heading logico-content-wrapper-1">
<span className="heading-content">One Journey. Complete<br />Control.</span> <span className="heading-content">
One Journey. Complete
<br />
Control.
</span>
</h1> </h1>
<div className="content-slider-item-text logico-content-wrapper-2"> <div className="content-slider-item-text logico-content-wrapper-2">
<div className="text-content"> <div className="text-content">
<p>See how Doormile connects first, mid, and last mile into a seamless delivery experience powered by MileTruth AI.</p> <p>
See how Doormile connects first, mid, and
last mile into a seamless delivery
experience powered by MileTruth AI.
</p>
</div> </div>
</div> </div>
</div> </div>
@@ -269,27 +306,36 @@ export default function HowItWorksHero() {
left: 0, left: 0,
width: "100%", width: "100%",
opacity: activeSlide === 1 ? 1 : 0, opacity: activeSlide === 1 ? 1 : 0,
visibility: activeSlide === 1 ? "visible" : "hidden", visibility:
transition: "opacity 0.8s ease-in-out, visibility 0.8s ease-in-out", activeSlide === 1 ? "visible" : "hidden",
zIndex: activeSlide === 1 ? 2 : 1 transition:
"opacity 0.8s ease-in-out, visibility 0.8s ease-in-out",
zIndex: activeSlide === 1 ? 2 : 1,
}} }}
> >
<div className="content-item slider-item elementor-repeater-item-6867061 slide-style-standard howits-hero-custom-bg"> <div className="content-item slider-item elementor-repeater-item-6867061 slide-style-standard howits-hero-custom-bg">
<div className="slide-content"> <div className="slide-content">
<div className="slide-content-inner"> <div className="slide-content-inner">
<h1 className="content-slider-item-heading logico-content-wrapper-1"> <h1 className="content-slider-item-heading logico-content-wrapper-1">
<span className="heading-content">A New Freight<br />Experience</span> <span className="heading-content">
A New Logisitics
<br />
Experience
</span>
</h1> </h1>
<div className="content-slider-item-text logico-content-wrapper-2"> <div className="content-slider-item-text logico-content-wrapper-2">
<div className="text-content"> <div className="text-content">
<p>See how Doormile connects first, mid, and last mile into a seamless delivery experience powered by MileTruth AI.</p> <p>
See how Doormile connects first, mid, and
last mile into a seamless delivery
experience powered by MileTruth AI.
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -298,42 +344,98 @@ export default function HowItWorksHero() {
<button <button
type="button" type="button"
className="owl-next" className="owl-next"
onClick={() => setActiveSlide((prev) => (prev === 0 ? 1 : 0))} onClick={() =>
setActiveSlide((prev) => (prev === 0 ? 1 : 0))
}
aria-label="Next" aria-label="Next"
style={{ cursor: "pointer", border: "none", outline: "none" }} style={{
cursor: "pointer",
border: "none",
outline: "none",
}}
/> />
<button <button
type="button" type="button"
className="owl-prev" className="owl-prev"
onClick={() => setActiveSlide((prev) => (prev === 0 ? 1 : 0))} onClick={() =>
setActiveSlide((prev) => (prev === 0 ? 1 : 0))
}
aria-label="Previous" aria-label="Previous"
style={{ cursor: "pointer", border: "none", outline: "none" }} style={{
cursor: "pointer",
border: "none",
outline: "none",
}}
/> />
</div> </div>
{/* Progress indicators */} {/* Progress indicators */}
<div className="slider-footer slider-footer-position-after slider-footer-width-full slider-footer-view-inside"> <div className="slider-footer slider-footer-position-after slider-footer-width-full slider-footer-view-inside">
<div className="slider-footer-content"> <div className="slider-footer-content">
<div className="slider-pagination" style={{ display: "flex", justifyContent: "flex-end", alignItems: "center", gap: "10px" }}> <div
<div className="slider-progress-wrapper" style={{ marginRight: "35px", display: "flex", flexDirection: "column", alignItems: "flex-start" }}> className="slider-pagination"
<div style={{ fontSize: "16px", fontWeight: 600, color: "#FFFFFF", marginBottom: "4px" }}> style={{
<span className="slider-progress-current">{activeSlide === 0 ? "01" : "02"}</span> display: "flex",
justifyContent: "flex-end",
alignItems: "center",
gap: "10px",
}}
>
<div
className="slider-progress-wrapper"
style={{
marginRight: "35px",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
}}
>
<div
style={{
fontSize: "16px",
fontWeight: 600,
color: "#FFFFFF",
marginBottom: "4px",
}}
>
<span className="slider-progress-current">
{activeSlide === 0 ? "01" : "02"}
</span>
{" / "} {" / "}
<span className="slider-progress-all" style={{ opacity: 0.6 }}>02</span> <span
className="slider-progress-all"
style={{ opacity: 0.6 }}
>
02
</span>
</div> </div>
{/* Progress line — red bar slides to match the active slide (mirrors the home hero) */} {/* Progress line — red bar slides to match the active slide (mirrors the home hero) */}
<div style={{ width: "80px", height: "2px", background: "rgba(255, 255, 255, 0.2)", position: "relative", borderRadius: "1px", overflow: "hidden" }}> <div
<div style={{ style={{
position: "absolute", width: "80px",
left: activeSlide === 0 ? "0" : "50%", height: "2px",
width: "50%", background: "rgba(255, 255, 255, 0.2)",
height: "100%", position: "relative",
background: "#c01227", borderRadius: "1px",
transition: "left 0.3s ease" overflow: "hidden",
}} /> }}
>
<div
style={{
position: "absolute",
left: activeSlide === 0 ? "0" : "50%",
width: "50%",
height: "100%",
background: "#c01227",
transition: "left 0.3s ease",
}}
/>
</div> </div>
</div> </div>
<div className="owl-dots owl-dots-6c7cbcb" style={{ display: "none" }}> <div
className="owl-dots owl-dots-6c7cbcb"
style={{ display: "none" }}
>
<button <button
type="button" type="button"
role="button" role="button"
@@ -354,7 +456,6 @@ export default function HowItWorksHero() {
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -2,35 +2,57 @@ import React from "react";
import Image from "next/image"; import Image from "next/image";
import { ScrollReveal } from "../../animations/Reveal"; import { ScrollReveal } from "../../animations/Reveal";
type Stat = {
value: string;
label: string;
};
type Stage = { type Stage = {
img: string; img: string;
label: string; label: string;
title: string; title: string;
subtitle?: string;
desc: string; desc: string;
points: string[]; points: string[];
stats?: Stat[];
}; };
const STAGES: Stage[] = [ const STAGES: Stage[] = [
{ {
img: "/images/first-mile-approach.jpg", img: "/images/first-mile-approach.jpg",
label: "01 / First Mile", label: "Stage 01",
title: "Origin to Hub", title: "First Mile Warehouse",
desc: "We collect freight directly from your facility, optimise vehicle assignment in real time, and consolidate loads for maximum efficiency before they reach the hub.", subtitle: "Consolidation & Prep",
desc: "Incoming shipments are securely loaded, checked, and queued for transfer in our high-capacity fulfillment centers.",
points: ["AI-scheduled pickups", "Dynamic load consolidation", "Yard & dock management"], points: ["AI-scheduled pickups", "Dynamic load consolidation", "Yard & dock management"],
stats: [
{ value: "14,250", label: "Parcels Processed" },
{ value: "99.98%", label: "Sorting Accuracy" }
]
}, },
{ {
img: "/images/mid-mile-approach.jpg", img: "/images/mid-mile-approach.jpg",
label: "02 / Mid Mile", label: "Stage 02",
title: "Hub to Hub Transit", title: "Mid Mile Transit",
desc: "Freight moves between hubs on optimised line-haul routes. Real-time tracking, cross-docking, and SLA monitoring keep every shipment on schedule.", subtitle: "Hub-to-Hub Transport",
desc: "Freight is routed dynamically through our network of strategically located hubs using automated sortation and linehaul scheduling.",
points: ["Optimised line-haul routing", "Cross-docking & sortation", "Live SLA monitoring"], points: ["Optimised line-haul routing", "Cross-docking & sortation", "Live SLA monitoring"],
stats: [
{ value: "1,240+", label: "Daily Line-Hauls" },
{ value: "98.5%", label: "SLA Adherence" }
]
}, },
{ {
img: "/images/last-mile-approach.jpg", img: "/images/last-mile-approach.jpg",
label: "03 / Last Mile", label: "Stage 03",
title: "Hub to Doorstep", title: "Last Mile Delivery",
desc: "The final and most complex phase. We optimise multi-stop routes, deliver within precise windows, and capture digital proof of delivery at every door.", subtitle: "Hub to Doorstep",
desc: "The final handoff. Our routing engine optimizes multi-stop itineraries to deliver parcels directly to customers' doorsteps in record time.",
points: ["Multi-stop route optimisation", "Precise delivery windows", "Digital proof of delivery"], points: ["Multi-stop route optimisation", "Precise delivery windows", "Digital proof of delivery"],
stats: [
{ value: "450K+", label: "Happy Deliveries" },
{ value: "2.8 Hours", label: "Average Turnaround" }
]
}, },
]; ];
@@ -40,18 +62,12 @@ export default function WhyChooseDoormile() {
<style dangerouslySetInnerHTML={{ __html: ` <style dangerouslySetInnerHTML={{ __html: `
/* ===================================================================== /* =====================================================================
"Why Businesses Choose Doormile" — First / Mid / Last Mile stage cards. "Why Businesses Choose Doormile" — First / Mid / Last Mile stage cards.
Dark rounded card on the white page (consistent with the Miles3
section), each stage shown with a photo, numbered red label, title,
description and a red-checkmark feature list.
Card titles are <h3>; theme-core forces a dark color on bare headings
(.logico-front-end h3:not([class*=logico-title-h]) @ (0,2,1)), so the
white title rule is prefixed to outrank it.
===================================================================== */ ===================================================================== */
.wcd-section { .wcd-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: auto; width: auto;
margin: -250px 20px 20px 20px; /* Snug pull-up overlap to touch Miles3 columns without covering their text! */ margin: -250px 20px 20px 20px;
background-color: #1F1F1F; background-color: #1F1F1F;
border-radius: 0 0 25px 25px; border-radius: 0 0 25px 25px;
padding: 50px 0 110px; padding: 50px 0 110px;
@@ -63,7 +79,6 @@ export default function WhyChooseDoormile() {
padding: 0 50px; padding: 0 50px;
box-sizing: border-box; box-sizing: border-box;
} }
/* Centered header block (eyebrow + heading) with a faint map backdrop */
.wcd-head { .wcd-head {
position: relative; position: relative;
text-align: center; text-align: center;
@@ -78,16 +93,16 @@ export default function WhyChooseDoormile() {
content: ""; content: "";
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 75%; /* Shifted down to the bottom of the header block to overlap the top of the cards */ top: 75%;
transform: translateX(-50%); transform: translateX(-50%);
width: min(1180px, 95%); width: min(1180px, 95%);
aspect-ratio: 2 / 1; aspect-ratio: 2 / 1;
background: url('/images/bg-map.png') center / contain no-repeat; background: url('/images/bg-map.png') center / contain no-repeat;
opacity: 0.06; /* Elegant faint visibility */ opacity: 0.06;
filter: invert(1); /* Invert dark map dots to white/light-gray to make them visible on the #1F1F1F background */ filter: invert(1);
z-index: 0; z-index: 0;
pointer-events: none; pointer-events: none;
animation: wcd-float 20s ease-in-out infinite; /* Premium floating map animation */ animation: wcd-float 20s ease-in-out infinite;
} }
.wcd-card-wrapper { .wcd-card-wrapper {
display: flex; display: flex;
@@ -127,18 +142,23 @@ export default function WhyChooseDoormile() {
width: 100%; width: 100%;
} }
/* Premium Glassmorphism & Card Interaction */
.wcd-card { .wcd-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: rgba(255, 255, 255, 0.02); background: linear-gradient(135deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%);
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 20px; border-radius: 20px;
overflow: hidden; overflow: hidden;
transition: border-color 0.4s ease, box-shadow 0.4s ease, transform 0.4s cubic-bezier(0.165, 0.84, 0.44, 1); backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
transition: border-color 0.4s cubic-bezier(0.165, 0.84, 0.44, 1),
box-shadow 0.4s cubic-bezier(0.165, 0.84, 0.44, 1),
transform 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
} }
.wcd-card:hover { .wcd-card:hover {
border-color: #c01227 !important; border-color: #c01227 !important;
box-shadow: 0 10px 30px rgba(192, 18, 39, 0.25) !important; box-shadow: 0 20px 40px rgba(192, 18, 39, 0.15), inset 0 0 20px rgba(255, 255, 255, 0.02) !important;
transform: translateY(-8px); transform: translateY(-8px);
} }
.wcd-card-media { .wcd-card-media {
@@ -152,75 +172,106 @@ export default function WhyChooseDoormile() {
transition: transform 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); transition: transform 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
} }
.wcd-card:hover .wcd-card-media img { .wcd-card:hover .wcd-card-media img {
transform: scale(1.06); transform: scale(1.05);
} }
/* Body Data Container (Unified Div) */
.wcd-card-body { .wcd-card-body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
padding: 40px; padding: 40px;
} }
/* Modern Pill Badge for Label */
.wcd-card-label { .wcd-card-label {
display: inline-flex;
align-items: center;
align-self: flex-start;
padding: 6px 14px;
background: rgba(192, 18, 39, 0.08);
border: 1px solid rgba(192, 18, 39, 0.25);
border-radius: 100px;
font-family: var(--font-manrope), "Manrope", sans-serif; font-family: var(--font-manrope), "Manrope", sans-serif;
font-size: 14px; font-size: 11px;
font-weight: 700; font-weight: 700;
letter-spacing: 1px; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: #c01227; color: #ff3344;
margin: 0 0 20px; margin: 0 0 20px;
transition: all 0.3s ease;
} }
.wcd-card:hover .wcd-card-label {
background: rgba(192, 18, 39, 0.18);
border-color: #c01227;
box-shadow: 0 0 10px rgba(192, 18, 39, 0.15);
}
.logico-front-end .wcd-section h3.wcd-card-title { .logico-front-end .wcd-section h3.wcd-card-title {
font-family: var(--font-manrope), "Manrope", sans-serif; font-family: var(--font-manrope), "Manrope", sans-serif;
font-size: 32px; font-size: 26px;
font-weight: 700; font-weight: 700;
line-height: 1.1; line-height: 1.2;
letter-spacing: -0.02em; letter-spacing: -0.02em;
text-transform: uppercase; text-transform: uppercase;
color: #FFFFFF; color: #FFFFFF;
-webkit-text-fill-color: #FFFFFF; -webkit-text-fill-color: #FFFFFF;
margin: 0 0 22px; margin: 0 0 4px;
} }
.wcd-card-subtitle {
font-family: var(--font-manrope), "Manrope", sans-serif;
font-size: 13px;
font-weight: 600;
color: rgba(255, 255, 255, 0.55);
margin: 0 0 20px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.wcd-card-divider {
height: 1px;
background: linear-gradient(90deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.02) 100%);
margin: 0 0 20px;
width: 100%;
}
.wcd-card-desc { .wcd-card-desc {
font-size: 17px; font-size: 15px;
font-weight: 400; font-weight: 400;
line-height: 1.6; line-height: 1.6;
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.65);
margin: 0 0 34px; margin: 0 0 24px;
} }
.wcd-card-points { .wcd-card-points {
list-style: none; list-style: none;
margin: auto 0 0; margin: 0 0 30px;
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 12px;
} }
.wcd-section .wcd-card-points li { .wcd-section .wcd-card-points li {
/* Flex row so the check icon and its label always sit on the same line.
Scoped with .wcd-section to outrank the global ".logico-front-end ul li"
theme rule, which adds 1.7em padding + a fontello bullet icon. */
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 12px; gap: 12px;
padding-left: 0; padding-left: 0;
font-size: 16px; font-size: 15px;
font-weight: 700; font-weight: 600;
line-height: 1.3; line-height: 1.3;
color: #FFFFFF; color: #FFFFFF;
} }
/* Suppress the theme's default fontello list bullet
(.logico-front-end ul li:before) so only our circle-check SVG renders. */
.wcd-section .wcd-card-points li::before { .wcd-section .wcd-card-points li::before {
content: none; content: none;
display: none; display: none;
} }
/* Clean circle-check feature icon (inline SVG, see markup below) — replaces
the old border-based chevron. Brand red with thin, rounded strokes. */
.wcd-card-points .wcd-check { .wcd-card-points .wcd-check {
flex: 0 0 auto; flex: 0 0 auto;
width: 18px; width: 18px;
height: 18px; height: 18px;
margin-top: 0.12em; margin-top: 0.08em;
color: #c01227; color: #c01227;
transition: transform 0.3s ease; transition: transform 0.3s ease;
} }
@@ -228,16 +279,47 @@ export default function WhyChooseDoormile() {
transform: scale(1.1); transform: scale(1.1);
} }
/* Integrated Stats Section */
.wcd-card-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-top: auto;
padding-top: 24px;
border-top: 1px dashed rgba(255, 255, 255, 0.12);
}
.wcd-stat-item {
display: flex;
flex-direction: column;
}
.wcd-stat-value {
font-family: var(--font-manrope), "Manrope", sans-serif;
font-size: 24px;
font-weight: 800;
color: #FFFFFF;
line-height: 1.1;
letter-spacing: -0.02em;
transition: color 0.3s ease;
}
.wcd-card:hover .wcd-stat-value {
color: #ff3344;
}
.wcd-stat-label {
font-size: 11px;
font-weight: 600;
color: rgba(255, 255, 255, 0.45);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 4px;
line-height: 1.3;
}
@media (max-width: 1020px) { @media (max-width: 1020px) {
/* No pull-up overlap on mobile/tablet: the Miles3 cards stack into a
12 col layout, so a negative margin-top covers the last card's
text. Both sections share #1F1F1F + equal side margins, so butting
them at margin-top:0 keeps the seamless dark panel. */
.wcd-section { margin: 0 15px 15px 15px; padding: 40px 0 80px; } .wcd-section { margin: 0 15px 15px 15px; padding: 40px 0 80px; }
.wcd-inner { padding: 0 30px; } .wcd-inner { padding: 0 30px; }
.wcd-grid { grid-template-columns: 1fr; gap: 24px; } .wcd-grid { grid-template-columns: 1fr; gap: 24px; }
.wcd-card-body { padding: 32px; } .wcd-card-body { padding: 32px; }
.logico-front-end .wcd-section h3.wcd-card-title { font-size: 28px; } .logico-front-end .wcd-section h3.wcd-card-title { font-size: 24px; }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.wcd-section { margin: 0 12px 12px 12px; border-radius: 0 0 20px 20px; padding: 30px 0 64px; } .wcd-section { margin: 0 12px 12px 12px; border-radius: 0 0 20px 20px; padding: 30px 0 64px; }
@@ -273,10 +355,20 @@ export default function WhyChooseDoormile() {
sizes="(max-width: 1020px) 100vw, 33vw" sizes="(max-width: 1020px) 100vw, 33vw"
/> />
</div> </div>
{/* Single Parent Div for All Data Content */}
<div className="wcd-card-body"> <div className="wcd-card-body">
<div className="wcd-card-label">{stage.label}</div> <div className="wcd-card-label">{stage.label}</div>
<h3 className="wcd-card-title">{stage.title}</h3> <h3 className="wcd-card-title">{stage.title}</h3>
{stage.subtitle && (
<div className="wcd-card-subtitle">{stage.subtitle}</div>
)}
<div className="wcd-card-divider" />
<p className="wcd-card-desc">{stage.desc}</p> <p className="wcd-card-desc">{stage.desc}</p>
<ul className="wcd-card-points"> <ul className="wcd-card-points">
{stage.points.map((point) => ( {stage.points.map((point) => (
<li key={point}> <li key={point}>
@@ -297,6 +389,18 @@ export default function WhyChooseDoormile() {
</li> </li>
))} ))}
</ul> </ul>
{/* Integrated Statistics Grid */}
{stage.stats && stage.stats.length > 0 && (
<div className="wcd-card-stats">
{stage.stats.map((stat) => (
<div key={stat.label} className="wcd-stat-item">
<span className="wcd-stat-value">{stat.value}</span>
<span className="wcd-stat-label">{stat.label}</span>
</div>
))}
</div>
)}
</div> </div>
</article> </article>
</ScrollReveal> </ScrollReveal>

View File

@@ -15,11 +15,21 @@ export default function CameraRig() {
useFrame((state, delta) => { useFrame((state, delta) => {
const { camera } = state 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 // 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) // 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 // Apply lookAt orientation using the interpolated target vector
camera.lookAt(currentLookAt.current) camera.lookAt(currentLookAt.current)

View File

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

View File

@@ -38,6 +38,14 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
useFrame((state, delta) => { useFrame((state, delta) => {
if (!truckRef.current) return 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 // Detect scroll direction changes from the actual page scroll progress
const deltaScroll = scrollProgress - lastScrollProgressRef.current const deltaScroll = scrollProgress - lastScrollProgressRef.current
if (deltaScroll < -0.0001) { if (deltaScroll < -0.0001) {
@@ -95,7 +103,15 @@ export default function TruckAnimation({ truckRef, wheelRefs }) {
} }
// Smoothly damp the 1D progress scalar along the curve path // 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 // Evaluate the 3D position and orientation directly on the spline curve
const position = truckPath.getPoint(dampedProgressRef.current) 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) // 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 // Apply the yaw pivot around the local vertical axis
truckRef.current.rotateY(extraRotationRef.current) truckRef.current.rotateY(extraRotationRef.current)

View File

@@ -15,16 +15,31 @@
here because the site has a fixed header and an ancestor `overflow:hidden`. 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 ---- */ /* ---- Section shell + self-managed fixed pin ---- */
.dm-hiw-3d { .dm-hiw-3d {
position: relative; position: relative;
width: 100%; width: 100%;
font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-family: var(--dm-font-body);
line-height: 1.47; line-height: 1.5;
font-weight: 400; font-weight: 400;
color: #1d1d1f; color: var(--dm-ink);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
@@ -117,39 +132,40 @@
transform: scale(1.15); transform: scale(1.15);
} }
.dm-hiw-3d .side-nav-item.active .side-nav-label { .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 { .dm-hiw-3d .side-nav-item.active .side-nav-dot {
background-color: #0071e3; background-color: var(--dm-red);
transform: scale(1.3); 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 { .dm-hiw-3d .section-close-btn {
margin-top: 20px; margin-top: 18px;
background-color: #0071e3; background-color: var(--dm-red);
color: #ffffff; color: #ffffff;
border: none; border: none;
font-family: inherit; font-family: var(--dm-font-body);
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
padding: 8px 16px; letter-spacing: 0.01em;
border-radius: 18px; padding: 9px 18px;
border-radius: 999px;
cursor: pointer; cursor: pointer;
transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1); transition: background-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease;
box-shadow: 0 4px 12px rgba(0, 113, 227, 0.15); box-shadow: 0 8px 18px -8px rgba(192, 18, 39, 0.55);
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: auto; width: auto;
} }
.dm-hiw-3d .section-close-btn:hover { .dm-hiw-3d .section-close-btn:hover {
background-color: #0077ed; background-color: #a30f20;
box-shadow: 0 6px 16px rgba(0, 113, 227, 0.3); box-shadow: 0 10px 22px -8px rgba(192, 18, 39, 0.7);
transform: translateY(-1px); transform: translateY(-1px);
} }
.dm-hiw-3d .section-close-btn:active { .dm-hiw-3d .section-close-btn:active {
transform: translateY(1px); transform: translateY(0);
} }
/* ---- Story stage text panels ---- */ /* ---- Story stage text panels ---- */
@@ -162,112 +178,136 @@
align-items: center; 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 #first-mile-section,
.dm-hiw-3d #last-mile-section { .dm-hiw-3d #last-mile-section {
left: 6%; left: clamp(24px, 5vw, 72px);
} }
.dm-hiw-3d #mid-mile-section { .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 { .dm-hiw-3d #analytics-section {
left: 50%; left: 50%;
right: auto; right: auto;
top: 50%; top: 50%;
transform: translate(-50%, -50%) translateY(18px) scale(0.97); transform: translate(-50%, -50%) translateY(18px) scale(0.97);
max-width: 500px; width: min(400px, 90vw);
width: 90%; max-height: calc(100vh - 140px);
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%);
} }
/* ---- Unified card — all four stages share this exact chrome ---- */
.dm-hiw-3d .section-panel { .dm-hiw-3d .section-panel {
position: absolute; position: absolute;
max-width: 380px; width: min(340px, 31vw);
padding: 30px; max-height: calc(100vh - 168px); /* never taller than the viewport */
background: rgba(255, 255, 255, 0.76); overflow-y: auto;
padding: 24px 26px 26px;
background: var(--dm-card-bg);
backdrop-filter: blur(0px); backdrop-filter: blur(0px);
-webkit-backdrop-filter: blur(0px); -webkit-backdrop-filter: blur(0px);
border: 1px solid rgba(0, 0, 0, 0.06); border: 1px solid var(--dm-card-border);
border-radius: 24px; 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; opacity: 0;
transform: translateY(18px) scale(0.97); transform: translateY(18px) scale(0.97);
visibility: hidden; visibility: hidden;
transition: backdrop-filter 0.9s cubic-bezier(0.16, 1, 0.3, 1), 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), -webkit-backdrop-filter 0.9s cubic-bezier(0.16, 1, 0.3, 1),
visibility 0.9s; visibility 0.9s;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.03);
pointer-events: none; pointer-events: none;
box-sizing: border-box;
} }
.dm-hiw-3d .section-panel.active { .dm-hiw-3d .section-panel.active {
visibility: visible; visibility: visible;
backdrop-filter: blur(24px); backdrop-filter: blur(22px) saturate(140%);
-webkit-backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(22px) saturate(140%);
pointer-events: auto; 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 { .dm-hiw-3d .section-badge {
font-size: 11px; display: inline-block;
text-transform: uppercase; font-family: var(--dm-font-body) !important;
font-weight: 600; font-size: 10.5px !important;
letter-spacing: 1px; text-transform: uppercase !important;
color: #0071e3; font-weight: 700 !important;
margin-bottom: 8px; letter-spacing: 0.14em !important;
color: var(--dm-red) !important;
margin: 0 0 10px !important;
} }
.dm-hiw-3d .section-title { .dm-hiw-3d .section-title {
font-size: 26px; font-family: var(--dm-font-head) !important;
font-weight: 600; font-size: clamp(18px, 1.45vw, 22px) !important;
letter-spacing: -0.6px; font-weight: 700 !important;
color: #1d1d1f; line-height: 1.15 !important;
margin: 0 0 4px 0; letter-spacing: -0.015em !important;
text-transform: none !important;
color: var(--dm-ink) !important;
margin: 0 0 4px !important;
} }
.dm-hiw-3d .section-subtitle { .dm-hiw-3d .section-subtitle {
font-size: 15px; font-family: var(--dm-font-body) !important;
font-weight: 500; font-size: 13px !important;
color: #86868b; font-weight: 500 !important;
margin: 0 0 14px 0; 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 { .dm-hiw-3d .section-description {
font-size: 13px; font-family: var(--dm-font-body) !important;
line-height: 1.5; font-size: 13px !important;
color: #515154; font-weight: 400 !important;
margin-bottom: 20px; line-height: 1.55 !important;
color: var(--dm-body) !important;
margin: 0 0 18px !important;
} }
.dm-hiw-3d .section-metrics { .dm-hiw-3d .section-metrics {
display: flex; display: flex;
gap: 20px; gap: 18px;
border-top: 1px solid rgba(0, 0, 0, 0.05); border-top: 1px solid rgba(15, 23, 42, 0.08);
padding-top: 16px; padding-top: 16px;
} }
.dm-hiw-3d .metric-item { .dm-hiw-3d .metric-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 3px;
flex: 1; flex: 1;
} }
.dm-hiw-3d .metric-value { .dm-hiw-3d .metric-value {
font-size: 20px; font-family: var(--dm-font-head) !important;
font-weight: 600; font-size: 18px !important;
color: #1d1d1f; font-weight: 700 !important;
letter-spacing: -0.3px; line-height: 1.1 !important;
letter-spacing: -0.01em !important;
color: var(--dm-ink) !important;
} }
.dm-hiw-3d .metric-label { .dm-hiw-3d .metric-label {
font-size: 10px; font-family: var(--dm-font-body) !important;
font-weight: 500; font-size: 9.5px !important;
color: #86868b; 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) ---- */ /* ---- Animations (keyframes left global; uniquely named) ---- */
@keyframes dmHiwScrollWheel { @keyframes dmHiwScrollWheel {
@@ -284,12 +324,12 @@
50% { transform: translateX(8px); } 50% { transform: translateX(8px); }
} }
/* ---- Workflow steps styling inside the Analytics overlay ---- */ /* ---- Final-panel timeline (inside #analytics-section) ---- */
.dm-hiw-3d .workflow-steps { .dm-hiw-3d .workflow-steps {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 4px;
margin-top: 18px; margin-top: 16px;
} }
.dm-hiw-3d .workflow-step { display: flex; gap: 14px; } .dm-hiw-3d .workflow-step { display: flex; gap: 14px; }
.dm-hiw-3d .step-number-container { .dm-hiw-3d .step-number-container {
@@ -299,37 +339,43 @@
width: 24px; width: 24px;
} }
.dm-hiw-3d .step-number { .dm-hiw-3d .step-number {
font-family: var(--dm-font-head);
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
color: #0071e3; color: var(--dm-red);
background: rgba(0, 113, 227, 0.1); background: var(--dm-red-soft);
width: 22px; width: 24px;
height: 22px; height: 24px;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: 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 { .dm-hiw-3d .step-line {
width: 1px; width: 2px;
flex-grow: 1; flex-grow: 1;
background: linear-gradient(to bottom, #0071e3 40%, rgba(0, 0, 0, 0.05) 100%); background: linear-gradient(to bottom, var(--dm-red) 0%, rgba(192, 18, 39, 0.12) 100%);
margin-top: 6px; margin: 5px 0;
min-height: 24px; 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 { .dm-hiw-3d .step-title {
font-size: 14px; font-family: var(--dm-font-head) !important;
font-weight: 600; font-size: 14px !important;
color: #1d1d1f; font-weight: 700 !important;
margin: 0 0 2px 0; line-height: 1.2 !important;
color: var(--dm-ink) !important;
margin: 1px 0 3px !important;
} }
.dm-hiw-3d .step-description { .dm-hiw-3d .step-description {
font-size: 11.5px; font-family: var(--dm-font-body) !important;
line-height: 1.45; font-size: 12px !important;
color: #6e6e73; line-height: 1.5 !important;
margin: 0; color: var(--dm-body) !important;
margin: 0 !important;
} }
/* ---- Responsive ---- */ /* ---- Responsive ---- */
@@ -341,11 +387,10 @@
padding-bottom: 50px; padding-bottom: 50px;
} }
.dm-hiw-3d .section-panel { .dm-hiw-3d .section-panel {
max-width: 100%; padding: 20px 22px 22px;
width: calc(100vw - 40px); border-radius: 16px;
padding: 20px;
border-radius: 18px;
} }
/* Tablet/mobile: cards bottom-centred so the truck/scene stays visible above. */
.dm-hiw-3d #first-mile-section, .dm-hiw-3d #first-mile-section,
.dm-hiw-3d #mid-mile-section, .dm-hiw-3d #mid-mile-section,
.dm-hiw-3d #last-mile-section, .dm-hiw-3d #last-mile-section,
@@ -353,10 +398,10 @@
left: 50% !important; left: 50% !important;
right: auto !important; right: auto !important;
top: auto !important; top: auto !important;
bottom: 60px !important; bottom: 64px !important;
transform: translateX(-50%) translateY(18px) scale(0.97) !important; transform: translateX(-50%) translateY(18px) scale(0.97) !important;
max-width: 380px; width: min(380px, calc(100vw - 40px)) !important;
width: calc(100vw - 120px) !important; max-height: calc(100vh - 200px) !important;
} }
.dm-hiw-3d #first-mile-section.active, .dm-hiw-3d #first-mile-section.active,
.dm-hiw-3d #mid-mile-section.active, .dm-hiw-3d #mid-mile-section.active,
@@ -364,7 +409,6 @@
.dm-hiw-3d #analytics-section.active { .dm-hiw-3d #analytics-section.active {
transform: translateX(-50%) translateY(0) scale(1) !important; 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 { .dm-hiw-3d .side-navigation {
bottom: 12px; bottom: 12px;
top: auto; top: auto;
@@ -386,34 +430,36 @@
} }
@media (max-width: 400px) { @media (max-width: 400px) {
.dm-hiw-3d .section-panel { .dm-hiw-3d .section-panel,
padding: 16px !important; .dm-hiw-3d #analytics-section {
width: calc(100vw - 80px) !important; padding: 16px 18px 18px !important;
bottom: 40px !important; width: min(340px, calc(100vw - 28px)) !important;
bottom: 56px !important;
} }
.dm-hiw-3d .section-badge { .dm-hiw-3d .section-badge {
font-size: 9px !important; font-size: 9.5px !important;
margin-bottom: 4px !important; margin-bottom: 6px !important;
} }
.dm-hiw-3d .section-title { .dm-hiw-3d .section-title {
font-size: 20px !important; font-size: 18px !important;
letter-spacing: -0.4px !important;
} }
.dm-hiw-3d .section-subtitle { .dm-hiw-3d .section-subtitle {
font-size: 13px !important; font-size: 12.5px !important;
margin-bottom: 8px !important; margin-bottom: 10px !important;
} }
.dm-hiw-3d .section-description { .dm-hiw-3d .section-description {
font-size: 11px !important; font-size: 12px !important;
line-height: 1.4 !important; line-height: 1.45 !important;
margin-bottom: 12px !important; margin-bottom: 14px !important;
} }
.dm-hiw-3d .section-metrics { .dm-hiw-3d .section-metrics {
padding-top: 10px !important; padding-top: 12px !important;
gap: 12px !important; gap: 14px !important;
} }
.dm-hiw-3d .metric-value { font-size: 15px !important; } .dm-hiw-3d .metric-value { font-size: 16px !important; }
.dm-hiw-3d .metric-label { font-size: 8.5px !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 { .dm-hiw-3d .side-navigation {
bottom: 8px !important; bottom: 8px !important;
gap: 12px !important; gap: 12px !important;
@@ -426,8 +472,8 @@
.dm-hiw-3d #analytics-section { .dm-hiw-3d #analytics-section {
left: 50% !important; left: 50% !important;
right: auto !important; right: auto !important;
bottom: 40px !important; bottom: 56px !important;
width: calc(100vw - 80px) !important; width: min(340px, calc(100vw - 28px)) !important;
transform: translateX(-50%) translateY(18px) scale(0.97) !important; transform: translateX(-50%) translateY(18px) scale(0.97) !important;
} }
.dm-hiw-3d #first-mile-section.active, .dm-hiw-3d #first-mile-section.active,